from packetcourt import audit_packet from packetcourt.models import Verdict from packetcourt.ocr import merge_extractions def by_claim(result, name): return next(claim for claim in result.claims if claim.claim == name) def test_no_added_sugar_is_contradicted_by_glucose_syrup(): result = audit_packet( "NO ADDED SUGAR", "Ingredients: rolled oats, glucose syrup, peanuts. Nutrition per 100g: Total Sugar 18g.", ) assert by_claim(result, "No Added Sugar").verdict == Verdict.CONTRADICTED def test_multigrain_exposes_refined_flour_context(): result = audit_packet( "MULTIGRAIN", "Ingredients: refined wheat flour, oats, ragi flour, salt.", ) assert by_claim(result, "Multigrain").verdict == Verdict.CONTEXT_MISSING def test_absolute_natural_claim_is_refused(): result = audit_packet("100% NATURAL", "Ingredients: chickpea flour, salt.") assert by_claim(result, "100% Natural").verdict == Verdict.CANNOT_VERIFY def test_relative_best_before_date_is_resolved(): result = audit_packet( "HIGH PROTEIN", "PKD: 13 JUN 26. Best before 6 months from packaging. Nutrition per 100g: Protein 12g.", ) assert result.expiry.best_before == "2026-12-13" def test_fssai_claim_is_not_treated_as_health_endorsement(): result = audit_packet( "FSSAI APPROVED", "FSSAI Lic. No. 12345678901234. Ingredients: oats.", ) audit = by_claim(result, "FSSAI Approved") assert audit.verdict == Verdict.CONTEXT_MISSING assert "not a health endorsement" in audit.summary def test_whole_packet_nutrition_exposes_persuasion_gap(): result = audit_packet( "HIGH PROTEIN", ( "Ingredients: oats, sugar. Nutrition information per 100g: Protein 12g, " "Total Sugars 22g, Sodium 410mg. Net weight 300g." ), ) assert result.whole_packet.total_sugar_g == 66 assert result.whole_packet.sugar_teaspoons == 16.5 assert result.whole_packet.sodium_mg == 1230 assert any("sugar" in finding.headline.lower() for finding in result.persuasion_gap) assert any("sodium" in finding.headline.lower() for finding in result.persuasion_gap) def test_baked_and_whole_grain_claims_keep_material_context(): result = audit_packet( "BAKED NOT FRIED | WHOLE GRAIN | ZERO TRANS FAT", ( "Ingredients: refined wheat flour, whole wheat flour, oil, salt. " "Nutrition per 100g: Sodium 780mg, Trans Fat 0g. Net weight 180g." ), ) assert by_claim(result, "Baked Not Fried").verdict == Verdict.CONTEXT_MISSING assert by_claim(result, "Whole Grain").verdict == Verdict.CONTEXT_MISSING assert by_claim(result, "Zero Trans Fat").verdict == Verdict.SUPPORTED assert any("refined" in finding.quiet_context.lower() for finding in result.persuasion_gap) def test_after_opening_instruction_is_extracted(): result = audit_packet( "NO PRESERVATIVES", "Ingredients: tomato, salt. Use by: 08 JUL 2026. Consume within 3 days after opening.", ) assert result.expiry.after_opening_instruction == "Consume within 3 days after opening" def test_investigation_requests_missing_evidence_and_stops_explicitly(): result = audit_packet("HIGH PROTEIN", "Protein 9g.") assert any(step.tool == "inspect_nutrition" for step in result.investigation.steps) assert any("nutrition panel" in item.lower() for item in result.investigation.missing_evidence) assert "missing-evidence" in result.investigation.stop_reason assert result.agent_review.status == "NOT_REQUESTED" def test_sugar_free_packet_surfaces_sweetener_and_requests_nutrition_panel(): result = audit_packet( "Sugar Free\nEnriched with Extra Calcium with DHA\nReal Badam", ( "Ingredients: Maltodextrin (65%), Badam, Soya Protein Isolate, Sucralose, " "Vitamins and Mineral Mix. Net Weight: 200g. Dates: FEB 2024 - JUL 2025." ), ) assert by_claim(result, "Sugar Free").verdict == Verdict.CANNOT_VERIFY assert any("Sucralose" in evidence.text for evidence in by_claim(result, "Sugar Free").evidence) assert any("sugar free" in finding.headline.lower() for finding in result.persuasion_gap) assert result.ingredients[-1] == "Vitamins and Mineral Mix" assert result.expiry.visible_date_texts == ["FEB 2024", "JUL 2025"] assert "visible date evidence" in result.expiry.status.lower() assert not any("visible dates" in item.lower() for item in result.investigation.missing_evidence) sugar_step = next(step for step in result.investigation.steps if "Sugar Free" in step.reason) assert sugar_step.tool == "inspect_nutrition" def test_dynamic_front_claim_is_audited_instead_of_dropped(): result = audit_packet("Real Badam", "Ingredients: Maltodextrin, Badam. Net Weight: 200g.") claim = by_claim(result, "Real Badam") assert claim.verdict == Verdict.CONTEXT_MISSING assert any("Badam" in evidence.text for evidence in claim.evidence) def test_multiple_photo_extractions_are_labeled_and_exact_duplicates_are_skipped(): text, status, images = merge_extractions( [ ("HIGH PROTEIN", "read one"), ("HIGH PROTEIN", "read duplicate"), ("REAL BADAM", "read another panel"), ], "front", ) assert "[Front photo 1]" in text assert "[Front photo 3]" in text assert text.count("HIGH PROTEIN") == 1 assert "2 unique front photos" in status assert "Exact duplicate skipped" in images[1]["status"] def test_table_style_nutrition_values_are_calculated(): result = audit_packet( "SUGAR FREE", ( "Nutrition Information Per 100g | Protein (g) 12 | Total Sugars | g | 0 | " "Added Sugar (g) 0 | Sodium (mg) 410 | Saturated Fat g 2.5 | Net Weight: 200g." ), ) assert result.nutrition.protein_g == 12 assert result.nutrition.total_sugar_g == 0 assert result.nutrition.sodium_mg == 410 assert result.whole_packet.protein_g == 24 assert result.whole_packet.total_sugar_g == 0 assert result.whole_packet.sodium_mg == 820 def test_basis_and_packet_without_nutrient_rows_explain_the_real_missing_evidence(): result = audit_packet("SUGAR FREE", "Nutrition Information Per 100g. Net Weight: 200g.") assert result.whole_packet.calculable is False assert "no readable nutrient quantities" in result.whole_packet.explanation.lower() assert any("nutrient quantities" in item.lower() for item in result.investigation.missing_evidence) def test_enrichment_claim_cites_visible_back_label_evidence(): result = audit_packet( "Extra Calcium with DHA", "Nutrition per 100g: Calcium 400mg, DHA 25mg. Net Weight: 200g.", ) claim = by_claim(result, "Extra Calcium with DHA") assert claim.verdict == Verdict.CONTEXT_MISSING assert any("Calcium" in evidence.text for evidence in claim.evidence) def test_ambiguous_dates_are_supplementary_when_not_a_front_claim(): result = audit_packet("SUGAR FREE", "Nutrition per 100g: Total Sugars 0g. Net Weight 200g. FEB 2024 JUL 2025.") assert result.expiry.visible_date_texts == ["FEB 2024", "JUL 2025"] assert not any("date" in item.lower() for item in result.investigation.missing_evidence) def test_one_character_ocr_claim_mismatch_is_surfaced_conservatively(): result = audit_packet("Real Badar", "Ingredients: ** Maltodextrin (65%), Badam, Sucralose.") claim = by_claim(result, "Real Badar") assert claim.verdict == Verdict.CANNOT_VERIFY assert claim.confidence == "low" assert any(evidence.text == "Badam" for evidence in claim.evidence) assert result.ingredients[0] == "Maltodextrin (65%)" def test_no_claims_reports_router_not_invoked_instead_of_fallback(): result = audit_packet("PRODUCT NAME", "Ingredients: oats.") assert result.claims == [] assert result.investigation.router_model == "not invoked: no auditable front claims detected"