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}
16
17/// Unit definition with conversion factor to SI base unit
18#[derive(Debug, Clone)]
19struct Unit {
20    category: UnitCategory,
21    to_si_factor: f64,   // Multiply by this to get SI unit
22    to_si_offset: f64,   // Add this after multiplication (for temperature)
23    from_si_factor: f64, // Multiply SI by this to get unit
24    from_si_offset: f64, // Add this after multiplication (for temperature)
25}
26
27impl Unit {
28    /// Create a simple unit with just a conversion factor
29    fn simple(category: UnitCategory, to_si_factor: f64) -> Self {
30        Unit {
31            category,
32            to_si_factor,
33            to_si_offset: 0.0,
34            from_si_factor: 1.0 / to_si_factor,
35            from_si_offset: 0.0,
36        }
37    }
38
39    /// Create a temperature unit with custom conversion
40    fn temperature(
41        to_si_factor: f64,
42        to_si_offset: f64,
43        from_si_factor: f64,
44        from_si_offset: f64,
45    ) -> Self {
46        Unit {
47            category: UnitCategory::Temperature,
48            to_si_factor,
49            to_si_offset,
50            from_si_factor,
51            from_si_offset,
52        }
53    }
54}
55
56/// Main unit converter
57pub struct UnitConverter {
58    units: HashMap<String, Unit>,
59}
60
61impl UnitConverter {
62    pub fn new() -> Self {
63        let mut units = HashMap::new();
64
65        // LENGTH - Base unit: meter (m)
66        units.insert("m".to_string(), Unit::simple(UnitCategory::Length, 1.0));
67        units.insert("meter".to_string(), Unit::simple(UnitCategory::Length, 1.0));
68        units.insert(
69            "meters".to_string(),
70            Unit::simple(UnitCategory::Length, 1.0),
71        );
72        units.insert("metre".to_string(), Unit::simple(UnitCategory::Length, 1.0));
73        units.insert(
74            "metres".to_string(),
75            Unit::simple(UnitCategory::Length, 1.0),
76        );
77
78        // Metric length
79        units.insert("km".to_string(), Unit::simple(UnitCategory::Length, 1000.0));
80        units.insert(
81            "kilometer".to_string(),
82            Unit::simple(UnitCategory::Length, 1000.0),
83        );
84        units.insert(
85            "kilometers".to_string(),
86            Unit::simple(UnitCategory::Length, 1000.0),
87        );
88        units.insert("cm".to_string(), Unit::simple(UnitCategory::Length, 0.01));
89        units.insert(
90            "centimeter".to_string(),
91            Unit::simple(UnitCategory::Length, 0.01),
92        );
93        units.insert(
94            "centimeters".to_string(),
95            Unit::simple(UnitCategory::Length, 0.01),
96        );
97        units.insert("mm".to_string(), Unit::simple(UnitCategory::Length, 0.001));
98        units.insert(
99            "millimeter".to_string(),
100            Unit::simple(UnitCategory::Length, 0.001),
101        );
102        units.insert(
103            "millimeters".to_string(),
104            Unit::simple(UnitCategory::Length, 0.001),
105        );
106        units.insert("nm".to_string(), Unit::simple(UnitCategory::Length, 1e-9));
107        units.insert(
108            "nanometer".to_string(),
109            Unit::simple(UnitCategory::Length, 1e-9),
110        );
111        units.insert("um".to_string(), Unit::simple(UnitCategory::Length, 1e-6));
112        units.insert(
113            "micrometer".to_string(),
114            Unit::simple(UnitCategory::Length, 1e-6),
115        );
116
117        // Imperial length
118        units.insert(
119            "mi".to_string(),
120            Unit::simple(UnitCategory::Length, 1609.344),
121        );
122        units.insert(
123            "mile".to_string(),
124            Unit::simple(UnitCategory::Length, 1609.344),
125        );
126        units.insert(
127            "miles".to_string(),
128            Unit::simple(UnitCategory::Length, 1609.344),
129        );
130        units.insert("yd".to_string(), Unit::simple(UnitCategory::Length, 0.9144));
131        units.insert(
132            "yard".to_string(),
133            Unit::simple(UnitCategory::Length, 0.9144),
134        );
135        units.insert(
136            "yards".to_string(),
137            Unit::simple(UnitCategory::Length, 0.9144),
138        );
139        units.insert("ft".to_string(), Unit::simple(UnitCategory::Length, 0.3048));
140        units.insert(
141            "foot".to_string(),
142            Unit::simple(UnitCategory::Length, 0.3048),
143        );
144        units.insert(
145            "feet".to_string(),
146            Unit::simple(UnitCategory::Length, 0.3048),
147        );
148        units.insert("in".to_string(), Unit::simple(UnitCategory::Length, 0.0254));
149        units.insert(
150            "inch".to_string(),
151            Unit::simple(UnitCategory::Length, 0.0254),
152        );
153        units.insert(
154            "inches".to_string(),
155            Unit::simple(UnitCategory::Length, 0.0254),
156        );
157
158        // Nautical
159        units.insert(
160            "nmi".to_string(),
161            Unit::simple(UnitCategory::Length, 1852.0),
162        );
163        units.insert(
164            "nautical_mile".to_string(),
165            Unit::simple(UnitCategory::Length, 1852.0),
166        );
167
168        // MASS - Base unit: kilogram (kg)
169        units.insert("kg".to_string(), Unit::simple(UnitCategory::Mass, 1.0));
170        units.insert(
171            "kilogram".to_string(),
172            Unit::simple(UnitCategory::Mass, 1.0),
173        );
174        units.insert(
175            "kilograms".to_string(),
176            Unit::simple(UnitCategory::Mass, 1.0),
177        );
178
179        // Metric mass
180        units.insert("g".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
181        units.insert("gram".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
182        units.insert("grams".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
183        units.insert("mg".to_string(), Unit::simple(UnitCategory::Mass, 0.000001));
184        units.insert(
185            "milligram".to_string(),
186            Unit::simple(UnitCategory::Mass, 0.000001),
187        );
188        units.insert(
189            "milligrams".to_string(),
190            Unit::simple(UnitCategory::Mass, 0.000001),
191        );
192        units.insert("ug".to_string(), Unit::simple(UnitCategory::Mass, 1e-9));
193        units.insert(
194            "microgram".to_string(),
195            Unit::simple(UnitCategory::Mass, 1e-9),
196        );
197        units.insert("t".to_string(), Unit::simple(UnitCategory::Mass, 1000.0));
198        units.insert(
199            "tonne".to_string(),
200            Unit::simple(UnitCategory::Mass, 1000.0),
201        );
202        units.insert(
203            "metric_ton".to_string(),
204            Unit::simple(UnitCategory::Mass, 1000.0),
205        );
206
207        // Imperial mass
208        units.insert(
209            "lb".to_string(),
210            Unit::simple(UnitCategory::Mass, 0.45359237),
211        );
212        units.insert(
213            "lbs".to_string(),
214            Unit::simple(UnitCategory::Mass, 0.45359237),
215        );
216        units.insert(
217            "pound".to_string(),
218            Unit::simple(UnitCategory::Mass, 0.45359237),
219        );
220        units.insert(
221            "pounds".to_string(),
222            Unit::simple(UnitCategory::Mass, 0.45359237),
223        );
224        units.insert(
225            "oz".to_string(),
226            Unit::simple(UnitCategory::Mass, 0.028349523125),
227        );
228        units.insert(
229            "ounce".to_string(),
230            Unit::simple(UnitCategory::Mass, 0.028349523125),
231        );
232        units.insert(
233            "ounces".to_string(),
234            Unit::simple(UnitCategory::Mass, 0.028349523125),
235        );
236        units.insert(
237            "ton".to_string(),
238            Unit::simple(UnitCategory::Mass, 907.18474),
239        );
240        units.insert(
241            "short_ton".to_string(),
242            Unit::simple(UnitCategory::Mass, 907.18474),
243        );
244        units.insert(
245            "long_ton".to_string(),
246            Unit::simple(UnitCategory::Mass, 1016.0469088),
247        );
248        units.insert(
249            "stone".to_string(),
250            Unit::simple(UnitCategory::Mass, 6.35029318),
251        );
252
253        // TEMPERATURE - Base unit: Kelvin (K)
254        units.insert(
255            "k".to_string(),
256            Unit::simple(UnitCategory::Temperature, 1.0),
257        );
258        units.insert(
259            "kelvin".to_string(),
260            Unit::simple(UnitCategory::Temperature, 1.0),
261        );
262
263        // Celsius: K = C + 273.15
264        units.insert(
265            "c".to_string(),
266            Unit::temperature(1.0, 273.15, 1.0, -273.15),
267        );
268        units.insert(
269            "celsius".to_string(),
270            Unit::temperature(1.0, 273.15, 1.0, -273.15),
271        );
272        units.insert(
273            "centigrade".to_string(),
274            Unit::temperature(1.0, 273.15, 1.0, -273.15),
275        );
276
277        // Fahrenheit: K = (F + 459.67) × 5/9
278        units.insert(
279            "f".to_string(),
280            Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
281        );
282        units.insert(
283            "fahrenheit".to_string(),
284            Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
285        );
286
287        // VOLUME - Base unit: liter (L)
288        units.insert("l".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
289        units.insert("liter".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
290        units.insert(
291            "liters".to_string(),
292            Unit::simple(UnitCategory::Volume, 1.0),
293        );
294        units.insert("litre".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
295        units.insert(
296            "litres".to_string(),
297            Unit::simple(UnitCategory::Volume, 1.0),
298        );
299
300        // Metric volume
301        units.insert("ml".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
302        units.insert(
303            "milliliter".to_string(),
304            Unit::simple(UnitCategory::Volume, 0.001),
305        );
306        units.insert(
307            "milliliters".to_string(),
308            Unit::simple(UnitCategory::Volume, 0.001),
309        );
310        units.insert("m3".to_string(), Unit::simple(UnitCategory::Volume, 1000.0));
311        units.insert(
312            "cubic_meter".to_string(),
313            Unit::simple(UnitCategory::Volume, 1000.0),
314        );
315        units.insert("cm3".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
316        units.insert(
317            "cubic_centimeter".to_string(),
318            Unit::simple(UnitCategory::Volume, 0.001),
319        );
320        units.insert("cc".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
321
322        // Imperial volume
323        units.insert(
324            "gal".to_string(),
325            Unit::simple(UnitCategory::Volume, 3.785411784),
326        );
327        units.insert(
328            "gallon".to_string(),
329            Unit::simple(UnitCategory::Volume, 3.785411784),
330        );
331        units.insert(
332            "gallons".to_string(),
333            Unit::simple(UnitCategory::Volume, 3.785411784),
334        );
335        units.insert(
336            "qt".to_string(),
337            Unit::simple(UnitCategory::Volume, 0.946352946),
338        );
339        units.insert(
340            "quart".to_string(),
341            Unit::simple(UnitCategory::Volume, 0.946352946),
342        );
343        units.insert(
344            "quarts".to_string(),
345            Unit::simple(UnitCategory::Volume, 0.946352946),
346        );
347        units.insert(
348            "pt".to_string(),
349            Unit::simple(UnitCategory::Volume, 0.473176473),
350        );
351        units.insert(
352            "pint".to_string(),
353            Unit::simple(UnitCategory::Volume, 0.473176473),
354        );
355        units.insert(
356            "pints".to_string(),
357            Unit::simple(UnitCategory::Volume, 0.473176473),
358        );
359        units.insert(
360            "cup".to_string(),
361            Unit::simple(UnitCategory::Volume, 0.2365882365),
362        );
363        units.insert(
364            "cups".to_string(),
365            Unit::simple(UnitCategory::Volume, 0.2365882365),
366        );
367        units.insert(
368            "fl_oz".to_string(),
369            Unit::simple(UnitCategory::Volume, 0.0295735295625),
370        );
371        units.insert(
372            "fluid_ounce".to_string(),
373            Unit::simple(UnitCategory::Volume, 0.0295735295625),
374        );
375        units.insert(
376            "tbsp".to_string(),
377            Unit::simple(UnitCategory::Volume, 0.01478676478125),
378        );
379        units.insert(
380            "tablespoon".to_string(),
381            Unit::simple(UnitCategory::Volume, 0.01478676478125),
382        );
383        units.insert(
384            "tsp".to_string(),
385            Unit::simple(UnitCategory::Volume, 0.00492892159375),
386        );
387        units.insert(
388            "teaspoon".to_string(),
389            Unit::simple(UnitCategory::Volume, 0.00492892159375),
390        );
391
392        // Imperial UK volume
393        units.insert(
394            "imperial_gal".to_string(),
395            Unit::simple(UnitCategory::Volume, 4.54609),
396        );
397        units.insert(
398            "imperial_gallon".to_string(),
399            Unit::simple(UnitCategory::Volume, 4.54609),
400        );
401        units.insert(
402            "imperial_qt".to_string(),
403            Unit::simple(UnitCategory::Volume, 1.1365225),
404        );
405        units.insert(
406            "imperial_pt".to_string(),
407            Unit::simple(UnitCategory::Volume, 0.56826125),
408        );
409
410        // TIME - Base unit: second (s)
411        units.insert("s".to_string(), Unit::simple(UnitCategory::Time, 1.0));
412        units.insert("sec".to_string(), Unit::simple(UnitCategory::Time, 1.0));
413        units.insert("second".to_string(), Unit::simple(UnitCategory::Time, 1.0));
414        units.insert("seconds".to_string(), Unit::simple(UnitCategory::Time, 1.0));
415
416        units.insert("ms".to_string(), Unit::simple(UnitCategory::Time, 0.001));
417        units.insert(
418            "millisecond".to_string(),
419            Unit::simple(UnitCategory::Time, 0.001),
420        );
421        units.insert(
422            "milliseconds".to_string(),
423            Unit::simple(UnitCategory::Time, 0.001),
424        );
425        units.insert("us".to_string(), Unit::simple(UnitCategory::Time, 0.000001));
426        units.insert(
427            "microsecond".to_string(),
428            Unit::simple(UnitCategory::Time, 0.000001),
429        );
430        units.insert(
431            "microseconds".to_string(),
432            Unit::simple(UnitCategory::Time, 0.000001),
433        );
434        units.insert(
435            "ns".to_string(),
436            Unit::simple(UnitCategory::Time, 0.000000001),
437        );
438        units.insert(
439            "nanosecond".to_string(),
440            Unit::simple(UnitCategory::Time, 0.000000001),
441        );
442        units.insert(
443            "nanoseconds".to_string(),
444            Unit::simple(UnitCategory::Time, 0.000000001),
445        );
446
447        units.insert("min".to_string(), Unit::simple(UnitCategory::Time, 60.0));
448        units.insert("minute".to_string(), Unit::simple(UnitCategory::Time, 60.0));
449        units.insert(
450            "minutes".to_string(),
451            Unit::simple(UnitCategory::Time, 60.0),
452        );
453        units.insert("hr".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
454        units.insert("hour".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
455        units.insert(
456            "hours".to_string(),
457            Unit::simple(UnitCategory::Time, 3600.0),
458        );
459        units.insert("day".to_string(), Unit::simple(UnitCategory::Time, 86400.0));
460        units.insert(
461            "days".to_string(),
462            Unit::simple(UnitCategory::Time, 86400.0),
463        );
464        units.insert(
465            "week".to_string(),
466            Unit::simple(UnitCategory::Time, 604800.0),
467        );
468        units.insert(
469            "weeks".to_string(),
470            Unit::simple(UnitCategory::Time, 604800.0),
471        );
472        units.insert(
473            "month".to_string(),
474            Unit::simple(UnitCategory::Time, 2629746.0),
475        ); // Average month
476        units.insert(
477            "months".to_string(),
478            Unit::simple(UnitCategory::Time, 2629746.0),
479        );
480        units.insert(
481            "year".to_string(),
482            Unit::simple(UnitCategory::Time, 31557600.0),
483        ); // 365.25 days
484        units.insert(
485            "years".to_string(),
486            Unit::simple(UnitCategory::Time, 31557600.0),
487        );
488
489        // AREA - Base unit: square meter (m²)
490        units.insert("m2".to_string(), Unit::simple(UnitCategory::Area, 1.0));
491        units.insert("sqm".to_string(), Unit::simple(UnitCategory::Area, 1.0));
492        units.insert(
493            "square_meter".to_string(),
494            Unit::simple(UnitCategory::Area, 1.0),
495        );
496        units.insert(
497            "square_meters".to_string(),
498            Unit::simple(UnitCategory::Area, 1.0),
499        );
500
501        units.insert(
502            "km2".to_string(),
503            Unit::simple(UnitCategory::Area, 1000000.0),
504        );
505        units.insert(
506            "square_kilometer".to_string(),
507            Unit::simple(UnitCategory::Area, 1000000.0),
508        );
509        units.insert("cm2".to_string(), Unit::simple(UnitCategory::Area, 0.0001));
510        units.insert(
511            "square_centimeter".to_string(),
512            Unit::simple(UnitCategory::Area, 0.0001),
513        );
514
515        units.insert(
516            "sq_ft".to_string(),
517            Unit::simple(UnitCategory::Area, 0.09290304),
518        );
519        units.insert(
520            "square_foot".to_string(),
521            Unit::simple(UnitCategory::Area, 0.09290304),
522        );
523        units.insert(
524            "square_feet".to_string(),
525            Unit::simple(UnitCategory::Area, 0.09290304),
526        );
527        units.insert(
528            "sq_in".to_string(),
529            Unit::simple(UnitCategory::Area, 0.00064516),
530        );
531        units.insert(
532            "square_inch".to_string(),
533            Unit::simple(UnitCategory::Area, 0.00064516),
534        );
535        units.insert(
536            "sq_mi".to_string(),
537            Unit::simple(UnitCategory::Area, 2589988.110336),
538        );
539        units.insert(
540            "square_mile".to_string(),
541            Unit::simple(UnitCategory::Area, 2589988.110336),
542        );
543        units.insert(
544            "acre".to_string(),
545            Unit::simple(UnitCategory::Area, 4046.8564224),
546        );
547        units.insert(
548            "acres".to_string(),
549            Unit::simple(UnitCategory::Area, 4046.8564224),
550        );
551        units.insert(
552            "hectare".to_string(),
553            Unit::simple(UnitCategory::Area, 10000.0),
554        );
555        units.insert(
556            "hectares".to_string(),
557            Unit::simple(UnitCategory::Area, 10000.0),
558        );
559
560        // SPEED - Base unit: meters per second (m/s)
561        units.insert("mps".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
562        units.insert("m/s".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
563        units.insert(
564            "meters_per_second".to_string(),
565            Unit::simple(UnitCategory::Speed, 1.0),
566        );
567
568        units.insert(
569            "kph".to_string(),
570            Unit::simple(UnitCategory::Speed, 0.277777778),
571        );
572        units.insert(
573            "kmh".to_string(),
574            Unit::simple(UnitCategory::Speed, 0.277777778),
575        );
576        units.insert(
577            "km/h".to_string(),
578            Unit::simple(UnitCategory::Speed, 0.277777778),
579        );
580        units.insert(
581            "kilometers_per_hour".to_string(),
582            Unit::simple(UnitCategory::Speed, 0.277777778),
583        );
584
585        units.insert(
586            "mph".to_string(),
587            Unit::simple(UnitCategory::Speed, 0.44704),
588        );
589        units.insert(
590            "mi/h".to_string(),
591            Unit::simple(UnitCategory::Speed, 0.44704),
592        );
593        units.insert(
594            "miles_per_hour".to_string(),
595            Unit::simple(UnitCategory::Speed, 0.44704),
596        );
597
598        units.insert(
599            "knot".to_string(),
600            Unit::simple(UnitCategory::Speed, 0.514444444),
601        );
602        units.insert(
603            "knots".to_string(),
604            Unit::simple(UnitCategory::Speed, 0.514444444),
605        );
606        units.insert(
607            "kt".to_string(),
608            Unit::simple(UnitCategory::Speed, 0.514444444),
609        );
610
611        units.insert("fps".to_string(), Unit::simple(UnitCategory::Speed, 0.3048));
612        units.insert(
613            "ft/s".to_string(),
614            Unit::simple(UnitCategory::Speed, 0.3048),
615        );
616        units.insert(
617            "feet_per_second".to_string(),
618            Unit::simple(UnitCategory::Speed, 0.3048),
619        );
620
621        // Speed of light (for physics calculations)
622        // Note: "c" conflicts with Celsius, so we only use "light_speed"
623        units.insert(
624            "light_speed".to_string(),
625            Unit::simple(UnitCategory::Speed, 299792458.0),
626        );
627
628        // PRESSURE - Base unit: Pascal (Pa)
629        units.insert("pa".to_string(), Unit::simple(UnitCategory::Pressure, 1.0));
630        units.insert(
631            "pascal".to_string(),
632            Unit::simple(UnitCategory::Pressure, 1.0),
633        );
634        units.insert(
635            "pascals".to_string(),
636            Unit::simple(UnitCategory::Pressure, 1.0),
637        );
638
639        units.insert(
640            "kpa".to_string(),
641            Unit::simple(UnitCategory::Pressure, 1000.0),
642        );
643        units.insert(
644            "kilopascal".to_string(),
645            Unit::simple(UnitCategory::Pressure, 1000.0),
646        );
647        units.insert(
648            "mpa".to_string(),
649            Unit::simple(UnitCategory::Pressure, 1000000.0),
650        );
651        units.insert(
652            "megapascal".to_string(),
653            Unit::simple(UnitCategory::Pressure, 1000000.0),
654        );
655        units.insert(
656            "gpa".to_string(),
657            Unit::simple(UnitCategory::Pressure, 1000000000.0),
658        );
659        units.insert(
660            "gigapascal".to_string(),
661            Unit::simple(UnitCategory::Pressure, 1000000000.0),
662        );
663
664        units.insert(
665            "bar".to_string(),
666            Unit::simple(UnitCategory::Pressure, 100000.0),
667        );
668        units.insert(
669            "bars".to_string(),
670            Unit::simple(UnitCategory::Pressure, 100000.0),
671        );
672        units.insert(
673            "mbar".to_string(),
674            Unit::simple(UnitCategory::Pressure, 100.0),
675        );
676        units.insert(
677            "millibar".to_string(),
678            Unit::simple(UnitCategory::Pressure, 100.0),
679        );
680
681        units.insert(
682            "atm".to_string(),
683            Unit::simple(UnitCategory::Pressure, 101325.0),
684        );
685        units.insert(
686            "atmosphere".to_string(),
687            Unit::simple(UnitCategory::Pressure, 101325.0),
688        );
689        units.insert(
690            "atmospheres".to_string(),
691            Unit::simple(UnitCategory::Pressure, 101325.0),
692        );
693
694        units.insert(
695            "psi".to_string(),
696            Unit::simple(UnitCategory::Pressure, 6894.757293168),
697        );
698        units.insert(
699            "pounds_per_square_inch".to_string(),
700            Unit::simple(UnitCategory::Pressure, 6894.757293168),
701        );
702
703        units.insert(
704            "torr".to_string(),
705            Unit::simple(UnitCategory::Pressure, 133.322368421),
706        );
707        units.insert(
708            "mmhg".to_string(),
709            Unit::simple(UnitCategory::Pressure, 133.322368421),
710        );
711        units.insert(
712            "mm_hg".to_string(),
713            Unit::simple(UnitCategory::Pressure, 133.322368421),
714        );
715
716        UnitConverter { units }
717    }
718
719    /// Convert a value from one unit to another
720    pub fn convert(&self, value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
721        // Normalize unit names to lowercase for lookup
722        let from_key = from_unit.to_lowercase();
723        let to_key = to_unit.to_lowercase();
724
725        // Get unit definitions
726        let from = self
727            .units
728            .get(&from_key)
729            .ok_or_else(|| anyhow!("Unknown unit: {}", from_unit))?;
730        let to = self
731            .units
732            .get(&to_key)
733            .ok_or_else(|| anyhow!("Unknown unit: {}", to_unit))?;
734
735        // Check that units are in the same category
736        if from.category != to.category {
737            return Err(anyhow!(
738                "Cannot convert between different unit types: {} ({:?}) to {} ({:?})",
739                from_unit,
740                from.category,
741                to_unit,
742                to.category
743            ));
744        }
745
746        // Convert to SI base unit
747        let si_value = value * from.to_si_factor + from.to_si_offset;
748
749        // Convert from SI base unit to target unit
750        let result = si_value * to.from_si_factor + to.from_si_offset;
751
752        Ok(result)
753    }
754
755    /// Get list of supported units for a category
756    pub fn get_units_for_category(&self, category: &str) -> Vec<String> {
757        let cat = match category.to_lowercase().as_str() {
758            "length" => Some(UnitCategory::Length),
759            "mass" | "weight" => Some(UnitCategory::Mass),
760            "temperature" | "temp" => Some(UnitCategory::Temperature),
761            "volume" => Some(UnitCategory::Volume),
762            "time" => Some(UnitCategory::Time),
763            "area" => Some(UnitCategory::Area),
764            "speed" | "velocity" => Some(UnitCategory::Speed),
765            "pressure" => Some(UnitCategory::Pressure),
766            _ => None,
767        };
768
769        if let Some(category) = cat {
770            self.units
771                .iter()
772                .filter(|(_, unit)| unit.category == category)
773                .map(|(name, _)| name.clone())
774                .collect()
775        } else {
776            Vec::new()
777        }
778    }
779}
780
781// Singleton instance
782lazy_static::lazy_static! {
783    static ref CONVERTER: UnitConverter = UnitConverter::new();
784}
785
786/// Public API for unit conversion
787pub fn convert_units(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
788    CONVERTER.convert(value, from_unit, to_unit)
789}
790
791/// Get list of units for a category
792pub fn list_units(category: &str) -> Vec<String> {
793    CONVERTER.get_units_for_category(category)
794}
795
796#[cfg(test)]
797mod tests {
798    use super::*;
799
800    #[test]
801    fn test_length_conversions() {
802        let converter = UnitConverter::new();
803
804        // Kilometers to miles
805        let result = converter.convert(100.0, "km", "miles").unwrap();
806        assert!((result - 62.137).abs() < 0.01);
807
808        // Feet to meters
809        let result = converter.convert(100.0, "ft", "m").unwrap();
810        assert!((result - 30.48).abs() < 0.01);
811
812        // Inches to centimeters
813        let result = converter.convert(12.0, "in", "cm").unwrap();
814        assert!((result - 30.48).abs() < 0.01);
815    }
816
817    #[test]
818    fn test_mass_conversions() {
819        let converter = UnitConverter::new();
820
821        // Pounds to kilograms
822        let result = converter.convert(100.0, "lb", "kg").unwrap();
823        assert!((result - 45.359).abs() < 0.01);
824
825        // Ounces to grams
826        let result = converter.convert(16.0, "oz", "g").unwrap();
827        assert!((result - 453.592).abs() < 0.01);
828    }
829
830    #[test]
831    fn test_temperature_conversions() {
832        let converter = UnitConverter::new();
833
834        // Fahrenheit to Celsius
835        let result = converter.convert(32.0, "F", "C").unwrap();
836        assert!((result - 0.0).abs() < 0.01);
837
838        let result = converter.convert(212.0, "F", "C").unwrap();
839        assert!((result - 100.0).abs() < 0.01);
840
841        // Celsius to Kelvin
842        let result = converter.convert(0.0, "C", "K").unwrap();
843        assert!((result - 273.15).abs() < 0.01);
844    }
845
846    #[test]
847    fn test_volume_conversions() {
848        let converter = UnitConverter::new();
849
850        // Gallons to liters
851        let result = converter.convert(1.0, "gal", "L").unwrap();
852        assert!((result - 3.785).abs() < 0.01);
853
854        // Cups to milliliters
855        let result = converter.convert(1.0, "cup", "ml").unwrap();
856        assert!((result - 236.588).abs() < 0.01);
857    }
858
859    #[test]
860    fn test_invalid_conversion() {
861        let converter = UnitConverter::new();
862
863        // Cannot convert between different categories
864        let result = converter.convert(100.0, "km", "kg");
865        assert!(result.is_err());
866
867        // Unknown unit
868        let result = converter.convert(100.0, "xyz", "m");
869        assert!(result.is_err());
870    }
871
872    #[test]
873    fn test_case_insensitive() {
874        let converter = UnitConverter::new();
875
876        // Should handle various cases
877        let result1 = converter.convert(100.0, "KM", "MILES").unwrap();
878        let result2 = converter.convert(100.0, "km", "miles").unwrap();
879        let result3 = converter.convert(100.0, "Km", "Miles").unwrap();
880
881        assert!((result1 - result2).abs() < 0.001);
882        assert!((result2 - result3).abs() < 0.001);
883    }
884}