Backend scores updates on products
This commit is contained in:
116
scripts/sync_nutrition_scores.py
Normal file
116
scripts/sync_nutrition_scores.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copy `nutrition_insights` scores onto the brand tables and the seed catalogs.
|
||||
|
||||
WHY THIS EXISTS
|
||||
---------------
|
||||
`nutrition_score` and `health_score` are stored on `nutrition_insights`, keyed
|
||||
by (brand, image_id). Nothing joined that table to `brand_*`, so anyone reading
|
||||
product rows straight out of Postgres - which is how the merchant console does
|
||||
it - could not see a score at all. The two columns now exist on every brand
|
||||
table; this is what fills them the first time.
|
||||
|
||||
Afterwards it maintains itself: `enrich_all_products` and the `/upload/nutrition`
|
||||
endpoint both call `nutrition_score_sync.sync_after_write` when they finish, so
|
||||
the mirror stays within one run of the source. Run this by hand after anything
|
||||
that edits `nutrition_insights` outside those two paths - notably
|
||||
`purge_non_consumable_nutrition`, which deletes rows and would otherwise leave
|
||||
stale scores behind on the brand tables.
|
||||
|
||||
SAFE TO RE-RUN
|
||||
--------------
|
||||
Every statement is guarded with `IS DISTINCT FROM`, so a second run changes
|
||||
nothing and reports 0. That makes "0 rows changed" a real assertion that the
|
||||
mirror is current, not an artefact of the script doing nothing.
|
||||
|
||||
USAGE
|
||||
-----
|
||||
python -m scripts.sync_nutrition_scores # dry run
|
||||
python -m scripts.sync_nutrition_scores --apply
|
||||
python -m scripts.sync_nutrition_scores --apply --brands "Cadbury,Amul"
|
||||
python -m scripts.sync_nutrition_scores --apply --db-only
|
||||
python -m scripts.sync_nutrition_scores --apply --json-only
|
||||
|
||||
Dry run by default. `--apply` writes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||
|
||||
from app.infrastructure.settings import DB_HOST, DB_NAME # noqa: E402
|
||||
from app.services.nutrition_score_sync import ( # noqa: E402
|
||||
sync_scores_to_brand_tables,
|
||||
sync_scores_to_seed_files,
|
||||
)
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--apply", action="store_true",
|
||||
help="commit the changes (default is a dry run)")
|
||||
parser.add_argument("--dry-run", action="store_true",
|
||||
help="explicit no-op; this is already the default")
|
||||
parser.add_argument("--brands", default=None,
|
||||
help="comma-separated brand names to limit the run to")
|
||||
parser.add_argument("--db-only", action="store_true",
|
||||
help="brand tables only, leave the seed JSON alone")
|
||||
parser.add_argument("--json-only", action="store_true",
|
||||
help="seed JSON only, leave the database alone")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.db_only and args.json_only:
|
||||
parser.error("--db-only and --json-only are mutually exclusive")
|
||||
|
||||
apply = args.apply and not args.dry_run
|
||||
brands = [b.strip() for b in (args.brands or "").split(",") if b.strip()] or None
|
||||
|
||||
logger.info("Target database: %s / %s", DB_HOST, DB_NAME)
|
||||
logger.info("Mode: %s", "APPLY - this writes" if apply
|
||||
else "DRY RUN - nothing is written")
|
||||
if brands:
|
||||
logger.info("Limited to: %s", ", ".join(brands))
|
||||
logger.info("")
|
||||
|
||||
total = 0
|
||||
|
||||
if not args.json_only:
|
||||
logger.info("Brand tables")
|
||||
logger.info("-" * 52)
|
||||
changed = sync_scores_to_brand_tables(brands, dry_run=not apply)
|
||||
for brand, count in sorted(changed.items(), key=lambda kv: -kv[1]):
|
||||
logger.info(" %-38s %5d", brand, count)
|
||||
if not changed:
|
||||
logger.info(" nothing to change - the mirror is current")
|
||||
total += sum(changed.values())
|
||||
logger.info("")
|
||||
|
||||
if not args.db_only:
|
||||
logger.info("Seed catalogs")
|
||||
logger.info("-" * 52)
|
||||
changed = sync_scores_to_seed_files(brands, dry_run=not apply)
|
||||
for name, count in sorted(changed.items(), key=lambda kv: -kv[1]):
|
||||
logger.info(" %-38s %5d", name, count)
|
||||
if not changed:
|
||||
logger.info(" nothing to change - the files are current")
|
||||
total += sum(changed.values())
|
||||
logger.info("")
|
||||
|
||||
if not apply and total:
|
||||
logger.info("Re-run with --apply to write %d change(s).", total)
|
||||
elif apply:
|
||||
logger.info("Done. %d change(s) written.", total)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user