sql_cli/data/
unit_converter.rs

1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3
4/// Unit categories for conversion
5#[derive(Debug, Clone, PartialEq)]
6enum UnitCategory {
7    Length,
8    Mass,
9    Temperature,
10    Volume,
11    Time,
12    Area,
13    Speed,
14    Pressure,
15    Energy,
16    FuelEfficiency,
17}
18
19/// Unit definition with conversion factor to SI base unit
20#[derive(Debug, Clone)]
21struct Unit {
22    category: UnitCategory,
23    to_si_factor: f64,   // Multiply by this to get SI unit
24    to_si_offset: f64,   // Add this after multiplication (for temperature)
25    from_si_factor: f64, // Multiply SI by this to get unit
26    from_si_offset: f64, // Add this after multiplication (for temperature)
27}
28
29impl Unit {
30    /// Create a simple unit with just a conversion factor
31    fn simple(category: UnitCategory, to_si_factor: f64) -> Self {
32        Unit {
33            category,
34            to_si_factor,
35            to_si_offset: 0.0,
36            from_si_factor: 1.0 / to_si_factor,
37            from_si_offset: 0.0,
38        }
39    }
40
41    /// Create a temperature unit with custom conversion
42    fn temperature(
43        to_si_factor: f64,
44        to_si_offset: f64,
45        from_si_factor: f64,
46        from_si_offset: f64,
47    ) -> Self {
48        Unit {
49            category: UnitCategory::Temperature,
50            to_si_factor,
51            to_si_offset,
52            from_si_factor,
53            from_si_offset,
54        }
55    }
56}
57
58/// Main unit converter
59pub struct UnitConverter {
60    units: HashMap<String, Unit>,
61}
62
63impl Default for UnitConverter {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl UnitConverter {
70    #[must_use]
71    pub fn new() -> Self {
72        let mut units = HashMap::new();
73
74        // LENGTH - Base unit: meter (m)
75        units.insert("m".to_string(), Unit::simple(UnitCategory::Length, 1.0));
76        units.insert("meter".to_string(), Unit::simple(UnitCategory::Length, 1.0));
77        units.insert(
78            "meters".to_string(),
79            Unit::simple(UnitCategory::Length, 1.0),
80        );
81        units.insert("metre".to_string(), Unit::simple(UnitCategory::Length, 1.0));
82        units.insert(
83            "metres".to_string(),
84            Unit::simple(UnitCategory::Length, 1.0),
85        );
86
87        // Metric length
88        units.insert("km".to_string(), Unit::simple(UnitCategory::Length, 1000.0));
89        units.insert(
90            "kilometer".to_string(),
91            Unit::simple(UnitCategory::Length, 1000.0),
92        );
93        units.insert(
94            "kilometers".to_string(),
95            Unit::simple(UnitCategory::Length, 1000.0),
96        );
97        units.insert("cm".to_string(), Unit::simple(UnitCategory::Length, 0.01));
98        units.insert(
99            "centimeter".to_string(),
100            Unit::simple(UnitCategory::Length, 0.01),
101        );
102        units.insert(
103            "centimeters".to_string(),
104            Unit::simple(UnitCategory::Length, 0.01),
105        );
106        units.insert("mm".to_string(), Unit::simple(UnitCategory::Length, 0.001));
107        units.insert(
108            "millimeter".to_string(),
109            Unit::simple(UnitCategory::Length, 0.001),
110        );
111        units.insert(
112            "millimeters".to_string(),
113            Unit::simple(UnitCategory::Length, 0.001),
114        );
115        units.insert("nm".to_string(), Unit::simple(UnitCategory::Length, 1e-9));
116        units.insert(
117            "nanometer".to_string(),
118            Unit::simple(UnitCategory::Length, 1e-9),
119        );
120        units.insert("um".to_string(), Unit::simple(UnitCategory::Length, 1e-6));
121        units.insert(
122            "micrometer".to_string(),
123            Unit::simple(UnitCategory::Length, 1e-6),
124        );
125
126        // Imperial length
127        units.insert(
128            "mi".to_string(),
129            Unit::simple(UnitCategory::Length, 1609.344),
130        );
131        units.insert(
132            "mile".to_string(),
133            Unit::simple(UnitCategory::Length, 1609.344),
134        );
135        units.insert(
136            "miles".to_string(),
137            Unit::simple(UnitCategory::Length, 1609.344),
138        );
139        units.insert("yd".to_string(), Unit::simple(UnitCategory::Length, 0.9144));
140        units.insert(
141            "yard".to_string(),
142            Unit::simple(UnitCategory::Length, 0.9144),
143        );
144        units.insert(
145            "yards".to_string(),
146            Unit::simple(UnitCategory::Length, 0.9144),
147        );
148        units.insert("ft".to_string(), Unit::simple(UnitCategory::Length, 0.3048));
149        units.insert(
150            "foot".to_string(),
151            Unit::simple(UnitCategory::Length, 0.3048),
152        );
153        units.insert(
154            "feet".to_string(),
155            Unit::simple(UnitCategory::Length, 0.3048),
156        );
157        units.insert("in".to_string(), Unit::simple(UnitCategory::Length, 0.0254));
158        units.insert(
159            "inch".to_string(),
160            Unit::simple(UnitCategory::Length, 0.0254),
161        );
162        units.insert(
163            "inches".to_string(),
164            Unit::simple(UnitCategory::Length, 0.0254),
165        );
166
167        // Nautical
168        units.insert(
169            "nmi".to_string(),
170            Unit::simple(UnitCategory::Length, 1852.0),
171        );
172        units.insert(
173            "nautical_mile".to_string(),
174            Unit::simple(UnitCategory::Length, 1852.0),
175        );
176
177        // Astronomical
178        units.insert(
179            "au".to_string(),
180            Unit::simple(UnitCategory::Length, 1.495978707e11), // astronomical unit in meters
181        );
182        units.insert(
183            "astronomical_unit".to_string(),
184            Unit::simple(UnitCategory::Length, 1.495978707e11),
185        );
186        units.insert(
187            "lightyear".to_string(),
188            Unit::simple(UnitCategory::Length, 9.4607304725808e15), // light-year in meters
189        );
190        units.insert(
191            "light_year".to_string(),
192            Unit::simple(UnitCategory::Length, 9.4607304725808e15),
193        );
194        units.insert(
195            "ly".to_string(),
196            Unit::simple(UnitCategory::Length, 9.4607304725808e15),
197        );
198        units.insert(
199            "parsec".to_string(),
200            Unit::simple(UnitCategory::Length, 3.0857e16), // parsec in meters
201        );
202        units.insert(
203            "pc".to_string(),
204            Unit::simple(UnitCategory::Length, 3.0857e16),
205        );
206
207        // MASS - Base unit: kilogram (kg)
208        units.insert("kg".to_string(), Unit::simple(UnitCategory::Mass, 1.0));
209        units.insert(
210            "kilogram".to_string(),
211            Unit::simple(UnitCategory::Mass, 1.0),
212        );
213        units.insert(
214            "kilograms".to_string(),
215            Unit::simple(UnitCategory::Mass, 1.0),
216        );
217
218        // Metric mass
219        units.insert("g".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
220        units.insert("gram".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
221        units.insert("grams".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
222        units.insert("mg".to_string(), Unit::simple(UnitCategory::Mass, 0.000001));
223        units.insert(
224            "milligram".to_string(),
225            Unit::simple(UnitCategory::Mass, 0.000001),
226        );
227        units.insert(
228            "milligrams".to_string(),
229            Unit::simple(UnitCategory::Mass, 0.000001),
230        );
231        units.insert("ug".to_string(), Unit::simple(UnitCategory::Mass, 1e-9));
232        units.insert(
233            "microgram".to_string(),
234            Unit::simple(UnitCategory::Mass, 1e-9),
235        );
236        units.insert("t".to_string(), Unit::simple(UnitCategory::Mass, 1000.0));
237        units.insert(
238            "tonne".to_string(),
239            Unit::simple(UnitCategory::Mass, 1000.0),
240        );
241        units.insert(
242            "metric_ton".to_string(),
243            Unit::simple(UnitCategory::Mass, 1000.0),
244        );
245
246        // Imperial mass
247        units.insert(
248            "lb".to_string(),
249            Unit::simple(UnitCategory::Mass, 0.45359237),
250        );
251        units.insert(
252            "lbs".to_string(),
253            Unit::simple(UnitCategory::Mass, 0.45359237),
254        );
255        units.insert(
256            "pound".to_string(),
257            Unit::simple(UnitCategory::Mass, 0.45359237),
258        );
259        units.insert(
260            "pounds".to_string(),
261            Unit::simple(UnitCategory::Mass, 0.45359237),
262        );
263        units.insert(
264            "oz".to_string(),
265            Unit::simple(UnitCategory::Mass, 0.028349523125),
266        );
267        units.insert(
268            "ounce".to_string(),
269            Unit::simple(UnitCategory::Mass, 0.028349523125),
270        );
271        units.insert(
272            "ounces".to_string(),
273            Unit::simple(UnitCategory::Mass, 0.028349523125),
274        );
275        units.insert(
276            "ton".to_string(),
277            Unit::simple(UnitCategory::Mass, 907.18474),
278        );
279        units.insert(
280            "short_ton".to_string(),
281            Unit::simple(UnitCategory::Mass, 907.18474),
282        );
283        units.insert(
284            "long_ton".to_string(),
285            Unit::simple(UnitCategory::Mass, 1016.0469088),
286        );
287        units.insert(
288            "stone".to_string(),
289            Unit::simple(UnitCategory::Mass, 6.35029318),
290        );
291
292        // TEMPERATURE - Base unit: Kelvin (K)
293        units.insert(
294            "k".to_string(),
295            Unit::simple(UnitCategory::Temperature, 1.0),
296        );
297        units.insert(
298            "kelvin".to_string(),
299            Unit::simple(UnitCategory::Temperature, 1.0),
300        );
301
302        // Celsius: K = C + 273.15
303        units.insert(
304            "c".to_string(),
305            Unit::temperature(1.0, 273.15, 1.0, -273.15),
306        );
307        units.insert(
308            "celsius".to_string(),
309            Unit::temperature(1.0, 273.15, 1.0, -273.15),
310        );
311        units.insert(
312            "centigrade".to_string(),
313            Unit::temperature(1.0, 273.15, 1.0, -273.15),
314        );
315
316        // Fahrenheit: K = (F + 459.67) × 5/9
317        units.insert(
318            "f".to_string(),
319            Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
320        );
321        units.insert(
322            "fahrenheit".to_string(),
323            Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
324        );
325
326        // VOLUME - Base unit: liter (L)
327        units.insert("l".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
328        units.insert("liter".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
329        units.insert(
330            "liters".to_string(),
331            Unit::simple(UnitCategory::Volume, 1.0),
332        );
333        units.insert("litre".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
334        units.insert(
335            "litres".to_string(),
336            Unit::simple(UnitCategory::Volume, 1.0),
337        );
338
339        // Metric volume
340        units.insert("ml".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
341        units.insert(
342            "milliliter".to_string(),
343            Unit::simple(UnitCategory::Volume, 0.001),
344        );
345        units.insert(
346            "milliliters".to_string(),
347            Unit::simple(UnitCategory::Volume, 0.001),
348        );
349        units.insert("m3".to_string(), Unit::simple(UnitCategory::Volume, 1000.0));
350        units.insert(
351            "cubic_meter".to_string(),
352            Unit::simple(UnitCategory::Volume, 1000.0),
353        );
354        units.insert("cm3".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
355        units.insert(
356            "cubic_centimeter".to_string(),
357            Unit::simple(UnitCategory::Volume, 0.001),
358        );
359        units.insert("cc".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
360
361        // Imperial volume
362        units.insert(
363            "gal".to_string(),
364            Unit::simple(UnitCategory::Volume, 3.785411784),
365        );
366        units.insert(
367            "gallon".to_string(),
368            Unit::simple(UnitCategory::Volume, 3.785411784),
369        );
370        units.insert(
371            "gallons".to_string(),
372            Unit::simple(UnitCategory::Volume, 3.785411784),
373        );
374        units.insert(
375            "qt".to_string(),
376            Unit::simple(UnitCategory::Volume, 0.946352946),
377        );
378        units.insert(
379            "quart".to_string(),
380            Unit::simple(UnitCategory::Volume, 0.946352946),
381        );
382        units.insert(
383            "quarts".to_string(),
384            Unit::simple(UnitCategory::Volume, 0.946352946),
385        );
386        units.insert(
387            "pt".to_string(),
388            Unit::simple(UnitCategory::Volume, 0.473176473),
389        );
390        units.insert(
391            "pint".to_string(),
392            Unit::simple(UnitCategory::Volume, 0.473176473),
393        );
394        units.insert(
395            "pints".to_string(),
396            Unit::simple(UnitCategory::Volume, 0.473176473),
397        );
398        units.insert(
399            "cup".to_string(),
400            Unit::simple(UnitCategory::Volume, 0.2365882365),
401        );
402        units.insert(
403            "cups".to_string(),
404            Unit::simple(UnitCategory::Volume, 0.2365882365),
405        );
406        units.insert(
407            "fl_oz".to_string(),
408            Unit::simple(UnitCategory::Volume, 0.0295735295625),
409        );
410        units.insert(
411            "fluid_ounce".to_string(),
412            Unit::simple(UnitCategory::Volume, 0.0295735295625),
413        );
414        units.insert(
415            "tbsp".to_string(),
416            Unit::simple(UnitCategory::Volume, 0.01478676478125),
417        );
418        units.insert(
419            "tablespoon".to_string(),
420            Unit::simple(UnitCategory::Volume, 0.01478676478125),
421        );
422        units.insert(
423            "tsp".to_string(),
424            Unit::simple(UnitCategory::Volume, 0.00492892159375),
425        );
426        units.insert(
427            "teaspoon".to_string(),
428            Unit::simple(UnitCategory::Volume, 0.00492892159375),
429        );
430
431        // Imperial UK volume
432        units.insert(
433            "imperial_gal".to_string(),
434            Unit::simple(UnitCategory::Volume, 4.54609),
435        );
436        units.insert(
437            "imperial_gallon".to_string(),
438            Unit::simple(UnitCategory::Volume, 4.54609),
439        );
440        units.insert(
441            "imperial_qt".to_string(),
442            Unit::simple(UnitCategory::Volume, 1.1365225),
443        );
444        units.insert(
445            "imperial_pt".to_string(),
446            Unit::simple(UnitCategory::Volume, 0.56826125),
447        );
448
449        // TIME - Base unit: second (s)
450        units.insert("s".to_string(), Unit::simple(UnitCategory::Time, 1.0));
451        units.insert("sec".to_string(), Unit::simple(UnitCategory::Time, 1.0));
452        units.insert("second".to_string(), Unit::simple(UnitCategory::Time, 1.0));
453        units.insert("seconds".to_string(), Unit::simple(UnitCategory::Time, 1.0));
454
455        units.insert("ms".to_string(), Unit::simple(UnitCategory::Time, 0.001));
456        units.insert(
457            "millisecond".to_string(),
458            Unit::simple(UnitCategory::Time, 0.001),
459        );
460        units.insert(
461            "milliseconds".to_string(),
462            Unit::simple(UnitCategory::Time, 0.001),
463        );
464        units.insert("us".to_string(), Unit::simple(UnitCategory::Time, 0.000001));
465        units.insert(
466            "microsecond".to_string(),
467            Unit::simple(UnitCategory::Time, 0.000001),
468        );
469        units.insert(
470            "microseconds".to_string(),
471            Unit::simple(UnitCategory::Time, 0.000001),
472        );
473        units.insert(
474            "ns".to_string(),
475            Unit::simple(UnitCategory::Time, 0.000000001),
476        );
477        units.insert(
478            "nanosecond".to_string(),
479            Unit::simple(UnitCategory::Time, 0.000000001),
480        );
481        units.insert(
482            "nanoseconds".to_string(),
483            Unit::simple(UnitCategory::Time, 0.000000001),
484        );
485
486        units.insert("min".to_string(), Unit::simple(UnitCategory::Time, 60.0));
487        units.insert("minute".to_string(), Unit::simple(UnitCategory::Time, 60.0));
488        units.insert(
489            "minutes".to_string(),
490            Unit::simple(UnitCategory::Time, 60.0),
491        );
492        units.insert("hr".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
493        units.insert("hour".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
494        units.insert(
495            "hours".to_string(),
496            Unit::simple(UnitCategory::Time, 3600.0),
497        );
498        units.insert("day".to_string(), Unit::simple(UnitCategory::Time, 86400.0));
499        units.insert(
500            "days".to_string(),
501            Unit::simple(UnitCategory::Time, 86400.0),
502        );
503        units.insert(
504            "week".to_string(),
505            Unit::simple(UnitCategory::Time, 604800.0),
506        );
507        units.insert(
508            "weeks".to_string(),
509            Unit::simple(UnitCategory::Time, 604800.0),
510        );
511        units.insert(
512            "month".to_string(),
513            Unit::simple(UnitCategory::Time, 2629746.0),
514        ); // Average month
515        units.insert(
516            "months".to_string(),
517            Unit::simple(UnitCategory::Time, 2629746.0),
518        );
519        units.insert(
520            "year".to_string(),
521            Unit::simple(UnitCategory::Time, 31557600.0),
522        ); // 365.25 days
523        units.insert(
524            "years".to_string(),
525            Unit::simple(UnitCategory::Time, 31557600.0),
526        );
527
528        // AREA - Base unit: square meter (m²)
529        units.insert("m2".to_string(), Unit::simple(UnitCategory::Area, 1.0));
530        units.insert("sqm".to_string(), Unit::simple(UnitCategory::Area, 1.0));
531        units.insert(
532            "square_meter".to_string(),
533            Unit::simple(UnitCategory::Area, 1.0),
534        );
535        units.insert(
536            "square_meters".to_string(),
537            Unit::simple(UnitCategory::Area, 1.0),
538        );
539
540        units.insert(
541            "km2".to_string(),
542            Unit::simple(UnitCategory::Area, 1000000.0),
543        );
544        units.insert(
545            "sqkm".to_string(),
546            Unit::simple(UnitCategory::Area, 1000000.0),
547        );
548        units.insert(
549            "square_kilometer".to_string(),
550            Unit::simple(UnitCategory::Area, 1000000.0),
551        );
552        units.insert("cm2".to_string(), Unit::simple(UnitCategory::Area, 0.0001));
553        units.insert(
554            "square_centimeter".to_string(),
555            Unit::simple(UnitCategory::Area, 0.0001),
556        );
557
558        units.insert(
559            "sq_ft".to_string(),
560            Unit::simple(UnitCategory::Area, 0.09290304),
561        );
562        units.insert(
563            "sqft".to_string(),
564            Unit::simple(UnitCategory::Area, 0.09290304),
565        );
566        units.insert(
567            "square_foot".to_string(),
568            Unit::simple(UnitCategory::Area, 0.09290304),
569        );
570        units.insert(
571            "square_feet".to_string(),
572            Unit::simple(UnitCategory::Area, 0.09290304),
573        );
574        units.insert(
575            "sq_in".to_string(),
576            Unit::simple(UnitCategory::Area, 0.00064516),
577        );
578        units.insert(
579            "square_inch".to_string(),
580            Unit::simple(UnitCategory::Area, 0.00064516),
581        );
582        units.insert(
583            "sq_mi".to_string(),
584            Unit::simple(UnitCategory::Area, 2589988.110336),
585        );
586        units.insert(
587            "sqmiles".to_string(),
588            Unit::simple(UnitCategory::Area, 2589988.110336),
589        );
590        units.insert(
591            "square_mile".to_string(),
592            Unit::simple(UnitCategory::Area, 2589988.110336),
593        );
594        units.insert(
595            "acre".to_string(),
596            Unit::simple(UnitCategory::Area, 4046.8564224),
597        );
598        units.insert(
599            "acres".to_string(),
600            Unit::simple(UnitCategory::Area, 4046.8564224),
601        );
602        units.insert(
603            "hectare".to_string(),
604            Unit::simple(UnitCategory::Area, 10000.0),
605        );
606        units.insert(
607            "hectares".to_string(),
608            Unit::simple(UnitCategory::Area, 10000.0),
609        );
610
611        // SPEED - Base unit: meters per second (m/s)
612        units.insert("mps".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
613        units.insert("m/s".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
614        units.insert(
615            "meters_per_second".to_string(),
616            Unit::simple(UnitCategory::Speed, 1.0),
617        );
618
619        units.insert(
620            "kph".to_string(),
621            Unit::simple(UnitCategory::Speed, 0.277777778),
622        );
623        units.insert(
624            "kmh".to_string(),
625            Unit::simple(UnitCategory::Speed, 0.277777778),
626        );
627        units.insert(
628            "km/h".to_string(),
629            Unit::simple(UnitCategory::Speed, 0.277777778),
630        );
631        units.insert(
632            "kilometers_per_hour".to_string(),
633            Unit::simple(UnitCategory::Speed, 0.277777778),
634        );
635
636        units.insert(
637            "mph".to_string(),
638            Unit::simple(UnitCategory::Speed, 0.44704),
639        );
640        units.insert(
641            "mi/h".to_string(),
642            Unit::simple(UnitCategory::Speed, 0.44704),
643        );
644        units.insert(
645            "miles_per_hour".to_string(),
646            Unit::simple(UnitCategory::Speed, 0.44704),
647        );
648
649        units.insert(
650            "knot".to_string(),
651            Unit::simple(UnitCategory::Speed, 0.514444444),
652        );
653        units.insert(
654            "knots".to_string(),
655            Unit::simple(UnitCategory::Speed, 0.514444444),
656        );
657        units.insert(
658            "kt".to_string(),
659            Unit::simple(UnitCategory::Speed, 0.514444444),
660        );
661
662        units.insert("fps".to_string(), Unit::simple(UnitCategory::Speed, 0.3048));
663        units.insert(
664            "ft/s".to_string(),
665            Unit::simple(UnitCategory::Speed, 0.3048),
666        );
667        units.insert(
668            "feet_per_second".to_string(),
669            Unit::simple(UnitCategory::Speed, 0.3048),
670        );
671
672        // Speed of light (for physics calculations)
673        // Note: "c" conflicts with Celsius, so we only use "light_speed"
674        units.insert(
675            "light_speed".to_string(),
676            Unit::simple(UnitCategory::Speed, 299792458.0),
677        );
678
679        // PRESSURE - Base unit: Pascal (Pa)
680        units.insert("pa".to_string(), Unit::simple(UnitCategory::Pressure, 1.0));
681        units.insert(
682            "pascal".to_string(),
683            Unit::simple(UnitCategory::Pressure, 1.0),
684        );
685        units.insert(
686            "pascals".to_string(),
687            Unit::simple(UnitCategory::Pressure, 1.0),
688        );
689
690        units.insert(
691            "kpa".to_string(),
692            Unit::simple(UnitCategory::Pressure, 1000.0),
693        );
694        units.insert(
695            "kilopascal".to_string(),
696            Unit::simple(UnitCategory::Pressure, 1000.0),
697        );
698        units.insert(
699            "mpa".to_string(),
700            Unit::simple(UnitCategory::Pressure, 1000000.0),
701        );
702        units.insert(
703            "megapascal".to_string(),
704            Unit::simple(UnitCategory::Pressure, 1000000.0),
705        );
706        units.insert(
707            "gpa".to_string(),
708            Unit::simple(UnitCategory::Pressure, 1000000000.0),
709        );
710        units.insert(
711            "gigapascal".to_string(),
712            Unit::simple(UnitCategory::Pressure, 1000000000.0),
713        );
714
715        units.insert(
716            "bar".to_string(),
717            Unit::simple(UnitCategory::Pressure, 100000.0),
718        );
719        units.insert(
720            "bars".to_string(),
721            Unit::simple(UnitCategory::Pressure, 100000.0),
722        );
723        units.insert(
724            "mbar".to_string(),
725            Unit::simple(UnitCategory::Pressure, 100.0),
726        );
727        units.insert(
728            "millibar".to_string(),
729            Unit::simple(UnitCategory::Pressure, 100.0),
730        );
731
732        units.insert(
733            "atm".to_string(),
734            Unit::simple(UnitCategory::Pressure, 101325.0),
735        );
736        units.insert(
737            "atmosphere".to_string(),
738            Unit::simple(UnitCategory::Pressure, 101325.0),
739        );
740        units.insert(
741            "atmospheres".to_string(),
742            Unit::simple(UnitCategory::Pressure, 101325.0),
743        );
744
745        units.insert(
746            "psi".to_string(),
747            Unit::simple(UnitCategory::Pressure, 6894.757293168),
748        );
749        units.insert(
750            "pounds_per_square_inch".to_string(),
751            Unit::simple(UnitCategory::Pressure, 6894.757293168),
752        );
753
754        units.insert(
755            "torr".to_string(),
756            Unit::simple(UnitCategory::Pressure, 133.322368421),
757        );
758        units.insert(
759            "mmhg".to_string(),
760            Unit::simple(UnitCategory::Pressure, 133.322368421),
761        );
762        units.insert(
763            "mm_hg".to_string(),
764            Unit::simple(UnitCategory::Pressure, 133.322368421),
765        );
766
767        // ENERGY - Base unit: Joule (J)
768        units.insert("j".to_string(), Unit::simple(UnitCategory::Energy, 1.0));
769        units.insert("joule".to_string(), Unit::simple(UnitCategory::Energy, 1.0));
770        units.insert(
771            "joules".to_string(),
772            Unit::simple(UnitCategory::Energy, 1.0),
773        );
774
775        units.insert("kj".to_string(), Unit::simple(UnitCategory::Energy, 1000.0));
776        units.insert(
777            "kilojoule".to_string(),
778            Unit::simple(UnitCategory::Energy, 1000.0),
779        );
780        units.insert(
781            "kilojoules".to_string(),
782            Unit::simple(UnitCategory::Energy, 1000.0),
783        );
784
785        units.insert("mj".to_string(), Unit::simple(UnitCategory::Energy, 1e6));
786        units.insert(
787            "megajoule".to_string(),
788            Unit::simple(UnitCategory::Energy, 1e6),
789        );
790
791        units.insert("cal".to_string(), Unit::simple(UnitCategory::Energy, 4.184));
792        units.insert(
793            "calorie".to_string(),
794            Unit::simple(UnitCategory::Energy, 4.184),
795        );
796        units.insert(
797            "calories".to_string(),
798            Unit::simple(UnitCategory::Energy, 4.184),
799        );
800
801        units.insert(
802            "kcal".to_string(),
803            Unit::simple(UnitCategory::Energy, 4184.0),
804        );
805        units.insert(
806            "kilocalorie".to_string(),
807            Unit::simple(UnitCategory::Energy, 4184.0),
808        );
809        units.insert(
810            "kilocalories".to_string(),
811            Unit::simple(UnitCategory::Energy, 4184.0),
812        );
813
814        units.insert("kwh".to_string(), Unit::simple(UnitCategory::Energy, 3.6e6));
815        units.insert(
816            "kilowatt_hour".to_string(),
817            Unit::simple(UnitCategory::Energy, 3.6e6),
818        );
819        units.insert(
820            "kilowatt_hours".to_string(),
821            Unit::simple(UnitCategory::Energy, 3.6e6),
822        );
823
824        units.insert("wh".to_string(), Unit::simple(UnitCategory::Energy, 3600.0));
825        units.insert(
826            "watt_hour".to_string(),
827            Unit::simple(UnitCategory::Energy, 3600.0),
828        );
829
830        units.insert(
831            "btu".to_string(),
832            Unit::simple(UnitCategory::Energy, 1055.056),
833        );
834        units.insert(
835            "british_thermal_unit".to_string(),
836            Unit::simple(UnitCategory::Energy, 1055.056),
837        );
838
839        units.insert(
840            "ev".to_string(),
841            Unit::simple(UnitCategory::Energy, 1.60218e-19),
842        );
843        units.insert(
844            "electron_volt".to_string(),
845            Unit::simple(UnitCategory::Energy, 1.60218e-19),
846        );
847
848        units.insert("erg".to_string(), Unit::simple(UnitCategory::Energy, 1e-7));
849        units.insert("ergs".to_string(), Unit::simple(UnitCategory::Energy, 1e-7));
850
851        // FUEL EFFICIENCY - Base unit: km/L (kilometers per liter)
852        units.insert(
853            "km/l".to_string(),
854            Unit::simple(UnitCategory::FuelEfficiency, 1.0),
855        );
856        units.insert(
857            "kml".to_string(),
858            Unit::simple(UnitCategory::FuelEfficiency, 1.0),
859        );
860
861        // Miles per gallon (US)
862        units.insert(
863            "mpg".to_string(),
864            Unit::simple(UnitCategory::FuelEfficiency, 0.425144), // 1 mpg = 0.425144 km/L
865        );
866        units.insert(
867            "miles/gallon".to_string(),
868            Unit::simple(UnitCategory::FuelEfficiency, 0.425144),
869        );
870
871        // Miles per gallon (UK/Imperial)
872        units.insert(
873            "mpg_uk".to_string(),
874            Unit::simple(UnitCategory::FuelEfficiency, 0.354006), // 1 mpg (UK) = 0.354006 km/L
875        );
876        units.insert(
877            "mpg_imp".to_string(),
878            Unit::simple(UnitCategory::FuelEfficiency, 0.354006),
879        );
880
881        // Electric vehicle efficiency - km per kWh
882        // Gasoline has ~9.5 kWh/L energy content
883        units.insert(
884            "km/kwh".to_string(),
885            Unit::simple(UnitCategory::FuelEfficiency, 0.105263), // 1 km/kWh ≈ 0.105 km/L (based on energy content)
886        );
887        units.insert(
888            "kmkwh".to_string(),
889            Unit::simple(UnitCategory::FuelEfficiency, 0.105263),
890        );
891
892        // Miles per kWh (for US EVs)
893        units.insert(
894            "mi/kwh".to_string(),
895            Unit::simple(UnitCategory::FuelEfficiency, 0.169349), // 1 mi/kWh ≈ 0.169 km/L
896        );
897        units.insert(
898            "miles/kwh".to_string(),
899            Unit::simple(UnitCategory::FuelEfficiency, 0.169349),
900        );
901
902        // MPGe - Miles Per Gallon equivalent (EPA rating for EVs)
903        // Based on 33.7 kWh = 1 gallon gasoline equivalent
904        units.insert(
905            "mpge".to_string(),
906            Unit::simple(UnitCategory::FuelEfficiency, 0.425144), // Same as regular MPG for comparison
907        );
908
909        // Nautical miles per ton (for ships)
910        // Assuming bunker fuel density ~0.95 tons/m³ = 950 kg/m³
911        units.insert(
912            "nmi/ton".to_string(),
913            Unit::simple(UnitCategory::FuelEfficiency, 0.001948), // 1 nmi/ton ≈ 0.00195 km/L
914        );
915        units.insert(
916            "nautical_miles/ton".to_string(),
917            Unit::simple(UnitCategory::FuelEfficiency, 0.001948),
918        );
919
920        // Kilometers per ton (for ships and heavy vehicles)
921        units.insert(
922            "km/ton".to_string(),
923            Unit::simple(UnitCategory::FuelEfficiency, 0.001053), // 1 km/ton ≈ 0.00105 km/L
924        );
925
926        UnitConverter { units }
927    }
928
929    /// Convert a value from one unit to another
930    pub fn convert(&self, value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
931        // Normalize unit names to lowercase for lookup
932        let from_key = from_unit.to_lowercase();
933        let to_key = to_unit.to_lowercase();
934
935        // Get unit definitions
936        let from = self
937            .units
938            .get(&from_key)
939            .ok_or_else(|| anyhow!("Unknown unit: {}", from_unit))?;
940        let to = self
941            .units
942            .get(&to_key)
943            .ok_or_else(|| anyhow!("Unknown unit: {}", to_unit))?;
944
945        // Check that units are in the same category
946        if from.category != to.category {
947            return Err(anyhow!(
948                "Cannot convert between different unit types: {} ({:?}) to {} ({:?})",
949                from_unit,
950                from.category,
951                to_unit,
952                to.category
953            ));
954        }
955
956        // Convert to SI base unit
957        let si_value = value * from.to_si_factor + from.to_si_offset;
958
959        // Convert from SI base unit to target unit
960        let result = si_value * to.from_si_factor + to.from_si_offset;
961
962        Ok(result)
963    }
964
965    /// Get list of supported units for a category
966    #[must_use]
967    pub fn get_units_for_category(&self, category: &str) -> Vec<String> {
968        let cat = match category.to_lowercase().as_str() {
969            "length" => Some(UnitCategory::Length),
970            "mass" | "weight" => Some(UnitCategory::Mass),
971            "temperature" | "temp" => Some(UnitCategory::Temperature),
972            "volume" => Some(UnitCategory::Volume),
973            "time" => Some(UnitCategory::Time),
974            "area" => Some(UnitCategory::Area),
975            "speed" | "velocity" => Some(UnitCategory::Speed),
976            "pressure" => Some(UnitCategory::Pressure),
977            "energy" => Some(UnitCategory::Energy),
978            "fuel_efficiency" | "efficiency" => Some(UnitCategory::FuelEfficiency),
979            _ => None,
980        };
981
982        if let Some(category) = cat {
983            self.units
984                .iter()
985                .filter(|(_, unit)| unit.category == category)
986                .map(|(name, _)| name.clone())
987                .collect()
988        } else {
989            Vec::new()
990        }
991    }
992}
993
994// Singleton instance
995lazy_static::lazy_static! {
996    static ref CONVERTER: UnitConverter = UnitConverter::new();
997}
998
999/// Public API for unit conversion
1000pub fn convert_units(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1001    CONVERTER.convert(value, from_unit, to_unit)
1002}
1003
1004/// Get list of units for a category
1005#[must_use]
1006pub fn list_units(category: &str) -> Vec<String> {
1007    CONVERTER.get_units_for_category(category)
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012    use super::*;
1013
1014    #[test]
1015    fn test_length_conversions() {
1016        let converter = UnitConverter::new();
1017
1018        // Kilometers to miles
1019        let result = converter.convert(100.0, "km", "miles").unwrap();
1020        assert!((result - 62.137).abs() < 0.01);
1021
1022        // Feet to meters
1023        let result = converter.convert(100.0, "ft", "m").unwrap();
1024        assert!((result - 30.48).abs() < 0.01);
1025
1026        // Inches to centimeters
1027        let result = converter.convert(12.0, "in", "cm").unwrap();
1028        assert!((result - 30.48).abs() < 0.01);
1029    }
1030
1031    #[test]
1032    fn test_mass_conversions() {
1033        let converter = UnitConverter::new();
1034
1035        // Pounds to kilograms
1036        let result = converter.convert(100.0, "lb", "kg").unwrap();
1037        assert!((result - 45.359).abs() < 0.01);
1038
1039        // Ounces to grams
1040        let result = converter.convert(16.0, "oz", "g").unwrap();
1041        assert!((result - 453.592).abs() < 0.01);
1042    }
1043
1044    #[test]
1045    fn test_temperature_conversions() {
1046        let converter = UnitConverter::new();
1047
1048        // Fahrenheit to Celsius
1049        let result = converter.convert(32.0, "F", "C").unwrap();
1050        assert!((result - 0.0).abs() < 0.01);
1051
1052        let result = converter.convert(212.0, "F", "C").unwrap();
1053        assert!((result - 100.0).abs() < 0.01);
1054
1055        // Celsius to Kelvin
1056        let result = converter.convert(0.0, "C", "K").unwrap();
1057        assert!((result - 273.15).abs() < 0.01);
1058    }
1059
1060    #[test]
1061    fn test_volume_conversions() {
1062        let converter = UnitConverter::new();
1063
1064        // Gallons to liters
1065        let result = converter.convert(1.0, "gal", "L").unwrap();
1066        assert!((result - 3.785).abs() < 0.01);
1067
1068        // Cups to milliliters
1069        let result = converter.convert(1.0, "cup", "ml").unwrap();
1070        assert!((result - 236.588).abs() < 0.01);
1071    }
1072
1073    #[test]
1074    fn test_invalid_conversion() {
1075        let converter = UnitConverter::new();
1076
1077        // Cannot convert between different categories
1078        let result = converter.convert(100.0, "km", "kg");
1079        assert!(result.is_err());
1080
1081        // Unknown unit
1082        let result = converter.convert(100.0, "xyz", "m");
1083        assert!(result.is_err());
1084    }
1085
1086    #[test]
1087    fn test_case_insensitive() {
1088        let converter = UnitConverter::new();
1089
1090        // Should handle various cases
1091        let result1 = converter.convert(100.0, "KM", "MILES").unwrap();
1092        let result2 = converter.convert(100.0, "km", "miles").unwrap();
1093        let result3 = converter.convert(100.0, "Km", "Miles").unwrap();
1094
1095        assert!((result1 - result2).abs() < 0.001);
1096        assert!((result2 - result3).abs() < 0.001);
1097    }
1098}