# R2-A — Lucky California live-scrape spike (DONE) ## Outcome - ONE live fetch performed: `https://luckysupermarkets.com/coupons/Coupon%2Flu-featured-in-ad`, HTTP 200, 201 KB rendered HTML. - Fixture saved: `backend/tests/fixtures/lucky_ca/weekly_ad.html` + `weekly_ad.png` + `META.md`. - Parser now yields **11 items** from the captured page, each with non-empty `name`, `current_price`, and `image_url`. - Test `backend/tests/test_lucky_ca_scraper.py::test_parse_fixture` passes (`pytest` exit 0, 0.09 s, no network, no Playwright). ## Selectors (verified 2026-05-04) - Card root: `div.coupon-card-wrapper` (Swiftly-rendered tiles). - Price + short name: `.coupon-card-value-text` ("$13.97 Pepsi 24 packs"). - Long description: `.coupon-card-short-description`. - Image: nested `` from `cdn.luckysupermarkets.com/loyalty/offer/.jpg`. - No per-card hyperlink in DOM; offers are non-navigable tiles. ## Scraper diff (minimal, additive) `backend/app/scraper/lucky_ca_scraper.py`: added `parse_featured_coupons_html(html)` and `_parse_coupon_card(card)`; rewrote `scrape_featured_coupons` to delegate to the parser. Old `_parse_coupon_item` retained as fallback. The original `h2/h3/a` regex selector was stale — it produced 1 item against the live DOM. ## Captcha / blocker check The HTML contains a `` placeholder element (no challenge served). Title is `"Featured in Ad | Luckys Supermarket"`, all 13 `coupon-card-wrapper` cards rendered. **Not a wall.** The spike script's heuristic flagged the keyword; META.md documents the false positive. ## Schema impact (grocery_item) - Survives contact with reality. Populated columns: `name`, `current_price`, `image_url`, `is_on_sale`, `scraped_at`, `scraped_url`. - `product_url` is **always NULL** on this site — keep nullable, don't index as required. - Parser also produces `description` (long offer text). Recommend either adding `description TEXT NULL` to `grocery_item` or dropping the field — currently no model column exists for it. - 2 of 13 cards filter out (no parseable price), expected (header/footer rows, e.g. "$35 minimum order"). Not a regression. ## Blockers None. Phase R2-A clears the gate. Recommend a tiny migration adding `grocery_item.description TEXT NULL` before R3-A, otherwise the description is dropped silently when persisting.