unit_converter.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #ident "@(#)$Format:LocalFoodAI_lanfr144:unit_converter.py:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
  2. import re
  3. #ident "@(#)$Format:LocalFoodAI_lanfr144:unit_converter.py:%an:%ae:%ad:%cn:%ce:%cd:%H:%D:%N$"
  4. class UnitConverter:
  5. """
  6. Utility class to convert culinary volumetric units to metric weight (grams)
  7. based on the specific product density.
  8. """
  9. # Common culinary volumetric units and their approximate volume in milliliters (ml)
  10. VOLUME_UNITS_ML = {
  11. 'tsp': 5.0,
  12. 'teaspoon': 5.0,
  13. 'tbsp': 15.0,
  14. 'tablespoon': 15.0,
  15. 'cup': 240.0,
  16. 'fl oz': 30.0,
  17. 'fluid ounce': 30.0,
  18. 'pint': 473.0,
  19. 'quart': 946.0,
  20. 'gallon': 3785.0,
  21. 'cm3': 1.0,
  22. 'cl': 10.0,
  23. 'dl': 100.0,
  24. 'l': 1000.0,
  25. 'liter': 1000.0,
  26. 'pinch': 0.36, # rough estimate
  27. 'dash': 0.72,
  28. 'xl': 64.0,
  29. 'm': 44.0,
  30. 's': 38.0,
  31. 'extra': 64.0,
  32. 'extra large': 64.0,
  33. 'big': 64.0,
  34. 'b': 64.0,
  35. 'large': 50.0,
  36. 'medium': 44.0,
  37. 'small': 38.0,
  38. }
  39. # Densities in grams per milliliter (g/ml)
  40. PRODUCT_DENSITIES = {
  41. # Baking and flours
  42. 'flour': 0.53,
  43. 'all-purpose flour': 0.53,
  44. 'wheat flour': 0.53,
  45. 'sugar': 0.85,
  46. 'white sugar': 0.85,
  47. 'granulated sugar': 0.85,
  48. 'powdered sugar': 0.50,
  49. 'icing sugar': 0.50,
  50. 'brown sugar': 0.83,
  51. 'salt': 1.20,
  52. 'table salt': 1.20,
  53. 'baking powder': 0.90,
  54. 'baking soda': 1.10,
  55. 'cocoa powder': 0.42,
  56. # Liquids
  57. 'water': 1.0,
  58. 'milk': 1.03,
  59. 'heavy cream': 0.99,
  60. 'vegetable oil': 0.92,
  61. 'olive oil': 0.92,
  62. 'honey': 1.42,
  63. 'maple syrup': 1.32,
  64. 'butter': 0.96, # melted or solid approx
  65. 'melted butter': 0.94,
  66. # Grains and dry goods
  67. 'rice': 0.85,
  68. 'white rice': 0.85,
  69. 'oats': 0.38,
  70. 'rolled oats': 0.38,
  71. 'quinoa': 0.72,
  72. 'couscous': 0.72,
  73. 'lentils': 0.85,
  74. # Condiments
  75. 'ketchup': 1.15,
  76. 'mustard': 1.05,
  77. 'mayonnaise': 0.95,
  78. 'peanut butter': 1.08,
  79. # Default density for unknown items (approximate density of water/mixed food)
  80. 'default': 1.0
  81. }
  82. # Direct weight conversions (already in weight, just need unit conversion)
  83. WEIGHT_UNITS_G = {
  84. 'g': 1.0,
  85. 'gr': 1.0,
  86. 'grs': 1.0,
  87. 'gram': 1.0,
  88. 'kg': 1000.0,
  89. 'kilo': 1000.0,
  90. 'kilogram': 1000.0,
  91. 'oz': 28.35,
  92. 'ounce': 28.35,
  93. 'lb': 453.59,
  94. 'pound': 453.59,
  95. 'mg': 0.001
  96. }
  97. @classmethod
  98. def get_density(cls, product_name):
  99. if not product_name:
  100. return cls.PRODUCT_DENSITIES['default']
  101. product_name = str(product_name).lower().strip()
  102. # Exact match
  103. if product_name in cls.PRODUCT_DENSITIES:
  104. return cls.PRODUCT_DENSITIES[product_name]
  105. # Partial match
  106. for key, density in cls.PRODUCT_DENSITIES.items():
  107. if key in product_name:
  108. return density
  109. return cls.PRODUCT_DENSITIES['default']
  110. @classmethod
  111. def convert_to_grams(cls, amount, unit, product_name=None):
  112. """
  113. Converts an amount and unit of a specific product to grams.
  114. """
  115. unit = str(unit).lower().strip()
  116. # If it's already a weight unit, simple scalar conversion
  117. for w_unit, g_factor in cls.WEIGHT_UNITS_G.items():
  118. if unit == w_unit or unit == f"{w_unit}s":
  119. return amount * g_factor
  120. # If it's a volumetric unit, use density
  121. volume_ml = None
  122. for v_unit, ml_factor in cls.VOLUME_UNITS_ML.items():
  123. if unit == v_unit or unit == f"{v_unit}s":
  124. volume_ml = amount * ml_factor
  125. break
  126. if volume_ml is not None:
  127. density = cls.get_density(product_name)
  128. return volume_ml * density
  129. # Unrecognized unit
  130. return None
  131. @classmethod
  132. def parse_and_convert(cls, recipe_string, product_name=None):
  133. """
  134. Parses a string like "1.5 cups" or "2 tbsp" or "100" and converts to grams.
  135. """
  136. # Strip trailing non-alphanumeric chars first just in case
  137. clean_str = re.sub(r'[^\w\.\s]+$', '', str(recipe_string).strip())
  138. # Match number (including decimals/fractions roughly) followed by optional text
  139. match = re.match(r'^([\d\.]+)\s*([a-zA-Z\s]*)$', clean_str)
  140. if match:
  141. try:
  142. amount = float(match.group(1))
  143. unit = match.group(2).strip()
  144. if not unit:
  145. unit = 'g' # Default to grams if no unit provided
  146. result = cls.convert_to_grams(amount, unit, product_name)
  147. if result is not None:
  148. return round(result, 2)
  149. except ValueError:
  150. pass
  151. return None
  152. if __name__ == '__main__':
  153. # Tests
  154. print("1 cup of all-purpose flour:", UnitConverter.parse_and_convert("1 cup", "all-purpose flour"), "g")
  155. print("1 cup of white sugar:", UnitConverter.parse_and_convert("1 cup", "white sugar"), "g")
  156. print("1 cup of water:", UnitConverter.parse_and_convert("1 cup", "water"), "g")
  157. print("2 tbsp of olive oil:", UnitConverter.parse_and_convert("2 tbsp", "olive oil"), "g")
  158. print("1 pound of generic food:", UnitConverter.parse_and_convert("1 pound", "unknown"), "g")
  159. print("1 pinch of salt:", UnitConverter.parse_and_convert("1 pinch", "salt"), "g")
  160. print("1 xl egg:", UnitConverter.parse_and_convert("1 xl", "egg"), "g")
  161. print("2 large eggs:", UnitConverter.parse_and_convert("2 large", "egg"), "g")