feat(ingestion): harden OFF ingestion for bulk seeding #6
Reference in New Issue
Block a user
Delete Branch "devin/1781938549-ingestion-resilience"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Makes the Open Food Facts ingestion resilient enough to bulk-seed the production database. Before this, a single transient API error or one malformed source record would abort an entire import (the loader runs one big transaction and commits at the end). Verified by loading ~460 real products into prod — https://goods.tangshasha.com now returns real search results and product detail pages (previously an empty repository).
Three independent failure modes are addressed:
Transient OFF API errors (429/5xx) —
OpenFoodFactsAdapternow routes both the per-barcode and search calls through a single_get(url, params)helper that retries on connection errors and retryable statuses with exponential backoff, honouringRetry-Afterwhen present. OFF's search endpoint intermittently 503s under load, which previously killed seed runs.Over-long source values — OFF fields can exceed our bounded columns (e.g.
food_detail.serving_size VARCHAR(32)), which raisedStringDataRightTruncationand rolled back the whole batch.transform()now clampsserving_size,net_content_unit, andcountry_of_originto their column widths via a small_clamp(value, max_len)helper.Per-record isolation — new
load_record_safe()wrapsload_recordin a savepoint so one bad record is skipped instead of aborting the import. Bothseed_offandupdate_offuse it and now report anerroredcount alongsideloaded/skipped.Tests
test_off_retry.py: retries transient 5xx then succeeds, exhausts retries, does not retry 4xx, retries connection errors.test_transform.py: longserving_sizeis clamped to 32 chars.test_load_integration.py: a NOT-NULL-violating record is skipped while a preceding good record survives (savepoint isolation).All ruff lint/format checks and the full pytest suite (39 passed) pass locally.
更新:以「国内商品为主」的市场化采集(本次提交)
之前线上首批 ~460 条以欧美英文商品为主(仅 4 条 69 开头国产码),按需求改为以国内商品为主:
adapter.fetch_by_country(country):按 OFFcountries_tags_en过滤(如china),按unique_scans_n(最常被扫描)排序,跨页去重(OFF 热度排序在分页间不稳定)。is_cn_gs1(code):判断 GS1 中国前缀(690–699),即真正的国产商品,排除「仅在中国销售」的进口品。seed_off --country <slug> [--domestic-only] [--page-size/--max-pages]:例如seed_off --country china --domestic-only只采集 69x 国产码商品(农夫山泉/康师傅/怡宝/娃哈哈/海天/蒙牛/伊利…)。已在生产库执行该任务导入国产商品;现有英文为主的旧数据的处理方式待与维护者确认。