Files

1 line
13 KiB
JSON

{"repo": "23divyajain12-afk/saffron", "files": {"ai.py": {"c0bad42f599d31416234dd4eacf71bc234f18c29": {"t": 1785674580.1911519, "n": 12176, "h": "9592160d73ee0460", "c": "import json\nimport re\nimport os\nfrom pathlib import Path\nfrom google import genai\nfrom google.genai import types\nfrom pydantic import BaseModel, Field\nfrom typing import Literal\nfrom PIL import Image\nimport dotenv\n\n# Load environment variables from .env file in project root\nproject_root = Path(__file__).parent\ndotenv.load_dotenv(project_root / '.env')\n\n# Get API key from environment\napi_key = os.getenv(\"GEMINI_API_KEY\") or os.getenv(\"GOOGLE_API_KEY\")\n\ndef get_genai_client():\n \"\"\"\n Initializes and returns the Google GenAI Client if an API key is available.\n \"\"\"\n if not api_key or \"your_actual_\" in api_key or \"your_gemini_\" in api_key or api_key.strip() == \"\":\n print(\"\\n[WARNING] Gemini API Key not found! Please set GEMINI_API_KEY in your environment or a .env file.\")\n print(\"Example in .env file:\")\n print(\"GEMINI_API_KEY=AIzaSy...\")\n return None\n try:\n return genai.Client(api_key=api_key)\n except Exception as e:\n print(f\"\\n[ERROR] Failed to initialize Gemini Client: {e}\")\n return None\n\n\ndef clean_ai_json_response(raw_response):\n raw_response = raw_response.strip()\n if raw_response.startswith(\"```\"):\n match = re.search(r'```(?:json\\s*)?(.*?)```$', raw_response, re.DOTALL)\n return match.group(1).strip() if match else raw_response\n return raw_response\n\n\ndef parse_ai_json(raw_response):\n cleaned = clean_ai_json_response(raw_response)\n return json.loads(cleaned)\n\n\n# ---------------------------------------------------------------------------\n# Receipt Scanner Definitions and Function\n# ---------------------------------------------------------------------------\n\nclass BillItem(BaseModel):\n name: str = Field(description=\"Name of the food ingredient or pantry item\")\n qty: float = Field(description=\"Numerical quantity or weight of the item\")\n\n\nclass BillAnalysis(BaseModel):\n items: list[BillItem] = Field(description=\"List of all items found on the receipt\")\n\n\ndef scan_bill_with_ai(image_path):\n \"\"\"\n Analyzes a receipt image using Gemini API, extracting items and their quantities.\n \"\"\"\n client = get_genai_client()\n if not client:\n print(\"Error: Gemini API Client could not be initialized (missing API key).\")\n return []\n\n prompt = \"\"\"\n Analyze the attached receipt image.\n Extract every line item name and its corresponding numerical quantity.\n \n Ensure all items are listed in the 'items' list with their names and quantities.\n Keep item names simple and lowercased (e.g. 'banana', 'tomato', 'chilli').\n \"\"\"\n\n try:\n print(f\"Sending receipt image '{image_path}' to Gemini...\")\n if not os.path.exists(image_path):\n print(f\"Error: Image file not found at {image_path}\")\n return []\n\n img = Image.open(image_path)\n \n response = client.models.generate_content(\n model='gemini-2.5-flash',\n contents=[img, prompt],\n config=types.GenerateContentConfig(\n response_mime_type=\"application/json\",\n response_schema=BillAnalysis,\n ),\n )\n\n \n if response.parsed and hasattr(response.parsed, 'items'):\n result = []\n for item in response.parsed.items:\n result.append({\n \"name\": item.name,\n \"qty\": item.qty\n })\n return result\n \n # Fallback to manual parsing if Pydantic parsing didn't work but text is returned\n print(\"Gemini response did not contain parsed items structure. Attempting fallback parse.\")\n raw_response = response.text\n data = parse_ai_json(raw_response)\n if isinstance(data, dict) and \"items\" in data:\n return data[\"items\"]\n elif isinstance(data, list):\n return data\n return []\n\n except json.JSONDecodeError:\n print(f\"Parsing Error: AI output was not clean JSON.\")\n return []\n except Exception as e:\n print(f\"Gemini API Call Failed: {e}.\")\n return []\n\n\n# ---------------------------------------------------------------------------\n# Ingredient Info Definitions and Function\n# ---------------------------------------------------------------------------\n\ndef is_valid_exotic_response(data):\n if not isinstance(data, dict):\n return False\n\n required_keys = {\"unit\", \"shelf_life\"}\n if set(data.keys()) != required_keys:\n return False\n\n allowed_units = {\"kg\", \"grams\", \"liters\", \"ml\", \"units\", \"bunches\", \"packets\"}\n if data.get(\"unit\") not in allowed_units:\n return False\n\n if not isinstance(data.get(\"shelf_life\"), int):\n return False\n\n return True\n\n\nclass IngredientInfo(BaseModel):\n unit: Literal[\"kg\", \"grams\", \"liters\", \"ml\", \"units\", \"bunches\", \"packets\"] = Field(\n description=\"Standard unit of measurement for household recipes\"\n )\n shelf_life: int = Field(description=\"Typical shelf life in days when stored correctly in an Indian home\")\n\n\ndef get_exotic_ingredient_info(item_name, max_retries=3):\n \"\"\"\n Queries Gemini to retrieve shelf life and unit information for a given ingredient name.\n \"\"\"\n client = get_genai_client()\n if not client:\n print(\"Error: Gemini API Client could not be initialized (missing API key).\")\n return {}\n\n prompt = f\"\"\"\n You are an expert culinary database assistant specializing in Indian kitchens.\n Analyze the food ingredient provided.\n Determine its typical storage shelf life (in days) when stored correctly in a standard Indian household.\n Also determine its standard unit of measurement.\n\n Input ingredient: {item_name}\n \"\"\"\n\n for attempt in range(1, max_retries + 1):\n try:\n print(f\"Querying exotic ingredient info for '{item_name}' via Gemini (attempt {attempt})...\")\n response = client.models.generate_content(\n model='gemini-2.5-flash',\n contents=prompt,\n config=types.GenerateContentConfig(\n response_mime_type=\"application/json\",\n response_schema=IngredientInfo,\n ),\n )\n\n if response.parsed:\n return {\n \"unit\": response.parsed.unit,\n \"shelf_life\": response.parsed.shelf_life\n }\n\n # Fallback\n raw_response = response.text\n data = parse_ai_json(raw_response)\n if is_valid_exotic_response(data):\n return data\n\n print(f\"Validation failed on attempt {attempt}. Response did not match expected schema: {data}\")\n except json.JSONDecodeError:\n print(f\"Parsing Error on attempt {attempt}: AI output was not clean JSON.\")\n except Exception as e:\n print(f\"Gemini Call Failed on attempt {attempt}: {e}.\")\n\n print(f\"Failed to get valid exotic ingredient info after {max_retries} attempts.\")\n return {}\n\n\nclass DynamicRecipeIngredient(BaseModel):\n name: str = Field(description=\"Ingredient name, matching available pantry items where possible\")\n qty: float = Field(description=\"Required quantity for this serves count\")\n unit: str = Field(description=\"Standard unit of measurement (e.g. kg, grams, liters, ml, units, bunches)\")\n\nclass DynamicRecipe(BaseModel):\n name: str = Field(description=\"Name of the recipe\")\n serves: int = Field(description=\"Number of servings/people this quantity satisfies\")\n time_mins: int = Field(description=\"Total prep and cooking time in minutes\")\n ingredients: list[DynamicRecipeIngredient] = Field(description=\"List of ingredients required\")\n recommendation_reason: str = Field(description=\"Reason for suggesting, highlighting soon-to-expire or high-stock ingredient usage\")\n\nclass DynamicRecipesResponse(BaseModel):\n recipes: list[DynamicRecipe] = Field(description=\"List of dynamically recommended recipes\")\n\n\ndef generate_recipes_from_pantry(pantry_items, people=2):\n \"\"\"\n Given currently available pantry items, uses Gemini to dynamically generate 5 delicious recipe recommendations.\n \"\"\"\n client = get_genai_client()\n if not client:\n print(\"Error: Gemini API Client could not be initialized (missing API key).\")\n return None\n\n # Format the ingredients cleanly for the prompt\n pantry_description = []\n for item in pantry_items:\n expiry_info = f\" (Expires: {item['expiry']})\" if item.get('expiry') else \"\"\n pantry_description.append(f\"- {item['name']}: {item['qty']} {item['unit']}{expiry_info}\")\n\n pantry_str = \"\\n\".join(pantry_description)\n\n prompt = f\"\"\"\n You are a professional chef specializing in zero-waste cooking for Indian households.\n Below is a list of currently available ingredients in the pantry (some have expiry dates listed).\n\n Current Pantry Inventory:\n {pantry_str}\n\n Your tasks:\n 1. Recommend exactly 5 creative, delicious recipes (such as milkshakes, smoothies, salads, snacks, quick curries, etc.) that can be prepared using the available items.\n 2. Prioritize utilizing ingredients that are close to their expiration dates or have high stock volumes to minimize waste.\n 3. You can suggest adding standard basic kitchen supplies (like water, salt, basic oils, sugar) if needed, but the primary ingredients MUST come from the pantry inventory listed above.\n 4. For each recipe, provide the exact scaled quantities for {people} servings.\n 5. Keep the ingredient names EXACTLY matching the names in the pantry inventory where possible to ensure seamless stock deduction (e.g., use 'tomato' instead of 'tomatoes').\n 6. Extremely Important: Pay close attention to the UNIT of each ingredient in the Pantry Inventory list. The quantity you return MUST be in that exact same unit. For example, if 'potato' has unit 'kg' and you want to use 200 grams, you MUST return quantity `0.2` and unit `kg` (DO NOT return `200` and unit `kg`, as that means 200 kilograms!). If 'milk' has unit 'liters' and you want to use 250 ml, you MUST return quantity `0.25` and unit `liters`.\n \"\"\"\n\n try:\n print(f\"Sending pantry inventory to Gemini to generate custom recommendations for {people} people...\")\n response = client.models.generate_content(\n model='gemini-2.5-flash',\n contents=prompt,\n config=types.GenerateContentConfig(\n response_mime_type=\"application/json\",\n response_schema=DynamicRecipesResponse,\n ),\n )\n\n if response.parsed and hasattr(response.parsed, 'recipes'):\n result = []\n for r in response.parsed.recipes:\n ing_dict = {}\n for ing in r.ingredients:\n ing_dict[ing.name.lower().strip()] = ing.qty\n result.append({\n \"name\": r.name,\n \"serves\": r.serves,\n \"time_mins\": r.time_mins,\n \"ingredients\": ing_dict,\n \"recommendation_reason\": r.recommendation_reason\n })\n return result\n\n raw_response = response.text\n data = parse_ai_json(raw_response)\n if isinstance(data, dict) and \"recipes\" in data:\n result = []\n for r in data[\"recipes\"]:\n ing_dict = {}\n for ing in r.get(\"ingredients\", []):\n ing_dict[ing[\"name\"].lower().strip()] = float(ing[\"qty\"])\n result.append({\n \"name\": r[\"name\"],\n \"serves\": r.get(\"serves\", people),\n \"time_mins\": r.get(\"time_mins\", 30),\n \"ingredients\": ing_dict,\n \"recommendation_reason\": r.get(\"recommendation_reason\", \"\")\n })\n return result\n return None\n except Exception as e:\n print(f\"Failed to generate dynamic recipes via Gemini: {e}\")\n return None\n\n\nif __name__ == \"__main__\":\n ingredient = \"kashmiri chili\"\n result = get_exotic_ingredient_info(ingredient)\n print(f\"Result: {result}\")"}}}}