NutriWeb / pipeline /04_audit.py
Krish264's picture
Deploy: Pin Streamlit to 1.61.1; add deployment parity tests
89d9642
Raw
History Blame Contribute Delete
5.51 kB
"""Step 4 — measure what is actually in the catalog.
The point of this script is to replace assumptions with numbers. The app makes
claims ("healthier alternative", "safe for your allergies") that are only as
good as the coverage underneath them, so coverage is measured rather than
hoped for. Run it after any pipeline change and read the output before
trusting a recommendation.
Usage:
python pipeline/04_audit.py
"""
from __future__ import annotations
import os
import sys
import duckdb
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from pipeline.config import CATALOG_PATH # noqa: E402
def rule(title: str) -> None:
print(f"\n{'=' * 66}\n{title}\n{'=' * 66}")
def pct(part: int, whole: int) -> str:
return f"{part:>9,} ({part / whole * 100:5.1f}%)" if whole else f"{part:>9,}"
def main() -> None:
if not CATALOG_PATH.exists():
raise SystemExit(f"{CATALOG_PATH} missing. Run steps 01-03 first.")
con = duckdb.connect(str(CATALOG_PATH), read_only=True)
total = con.execute("SELECT count(*) FROM catalog").fetchone()[0]
rule("CATALOG")
print(f"{'products':<42}{total:>9,}")
size = CATALOG_PATH.stat().st_size / 1024**2
print(f"{'catalog size':<42}{size:>8.0f} MB")
rule("FIELD COVERAGE")
fields = {
"product name": "product_name IS NOT NULL",
"brand": "brands IS NOT NULL",
"front image": "image_url IS NOT NULL",
"ingredients text": "ingredients_text IS NOT NULL",
"ingredient tags (used for similarity)": "len(COALESCE(ingredients_tags,[]::VARCHAR[])) > 0",
"category (used for candidate generation)": "primary_category IS NOT NULL",
"allergen tags": "len(COALESCE(allergens_tags,[]::VARCHAR[])) > 0",
"energy": "energy_kcal_100g IS NOT NULL",
"sugars": "sugars_100g IS NOT NULL",
"saturated fat": "saturated_fat_100g IS NOT NULL",
"salt or sodium": "salt_derived IS NOT NULL",
"protein": "proteins_100g IS NOT NULL",
"fibre": "fiber_100g IS NOT NULL",
"fruit/veg estimate": "fruits_veg_derived IS NOT NULL",
"NOVA group": "nova_group IS NOT NULL",
"Eco-Score": "environmental_score_grade IS NOT NULL",
}
for label, condition in fields.items():
n = con.execute(f"SELECT count(*) FROM catalog WHERE {condition}").fetchone()[0]
print(f"{label:<42}{pct(n, total)}")
rule("SCORING — the reason this pipeline exists")
off = con.execute(
"SELECT count(*) FROM catalog WHERE nutriscore_source = 'off'"
).fetchone()[0]
ours = con.execute(
"SELECT count(*) FROM catalog WHERE nutriscore_source = 'nutriweb'"
).fetchone()[0]
health = con.execute("SELECT count(health_score) FROM catalog").fetchone()[0]
print(f"{'Nutri-Score published by Open Food Facts':<42}{pct(off, total)}")
print(f"{'Nutri-Score computed by NutriWeb':<42}{pct(ours, total)}")
print(f"{'total graded':<42}{pct(off + ours, total)}")
print(f"{'with a health score':<42}{pct(health, total)}")
if off:
print(f"\n Computing grades ourselves increased scored coverage {(off + ours) / off:.2f}x.")
rule("VALIDATION — computed grades vs Open Food Facts' own")
print(
con.execute("""
WITH v AS (
SELECT nutriscore_grade_off AS off, nutriscore_grade_computed AS ours
FROM catalog
WHERE nutriscore_grade_off NOT IN ('unknown','not-applicable')
AND nutriscore_grade_off IS NOT NULL
AND nutriscore_grade_computed IS NOT NULL
)
SELECT count(*) AS compared,
round(100.0*count(*) FILTER (WHERE off=ours)/count(*), 2) AS exact_pct,
round(100.0*count(*) FILTER (WHERE abs(ascii(off)-ascii(ours))<=1)/count(*), 2)
AS within_one_pct
FROM v
""").fetchdf().T.to_string(header=False)
)
rule("CONFIDENCE — how much to trust each health score")
print(
con.execute("""
SELECT COALESCE(health_confidence,'unscored') AS confidence, count(*) AS n
FROM catalog GROUP BY 1 ORDER BY n DESC
""").fetchdf().to_string(index=False)
)
print(
"\n 'low' means the Nutri-Score was computed for a product with no category,\n"
" so the general-food thresholds were assumed. Surfaced in the UI."
)
rule("RECOMMENDABILITY — can we actually serve a swap?")
print(
con.execute("""
SELECT
count(*) FILTER (WHERE health_score IS NOT NULL
AND primary_category IS NOT NULL) AS category_path,
count(*) FILTER (WHERE health_score IS NOT NULL
AND primary_category IS NULL
AND len(COALESCE(ingredients_tags,[]::VARCHAR[]))>0)
AS ingredient_fallback_path,
count(*) FILTER (WHERE health_score IS NULL) AS cannot_recommend_from
FROM catalog
""").fetchdf().T.to_string(header=False)
)
rule("TOP CATEGORIES")
print(
con.execute("""
SELECT replace(split_part(primary_category,':',2),'-',' ') AS category,
count(*) AS products, round(avg(health_score),1) AS avg_health
FROM catalog WHERE primary_category IS NOT NULL AND health_score IS NOT NULL
GROUP BY 1 ORDER BY products DESC LIMIT 12
""").fetchdf().to_string(index=False)
)
con.close()
if __name__ == "__main__":
main()