app.bettersight.io/backend/templates
JasonFraser e0de2df6bf Phase 5 + 6: automation, content-hash change detection, semantic trip matching
Phase 5 — Automation:
- Daily scrape cron, daily digest email, and Monday brief n8n workflows
- price_history_repository.py + price diffing/is_new detection wired
  into analysis_service's refresh path
- Numeric unread-count badge on the topbar bell (was a plain dot)

Change detection architecture correction:
- CLAUDE.md's Firecrawl Monitor design doesn't match the real
  self-hosted product: no /v1/monitor in v1, the real /v2/monitor is
  cron-scheduled and credit-metered (Supabase-gated), and self-hosted
  Firecrawl is a 3-service stack (api/worker/playwright-service), not
  the single container originally spec'd
- Replaced with content-hash comparison computed during the scrape
  itself (core/scraper.py's compute_content_hash/normalise_html,
  analysis_service's detect_change()) — no external dependency, and
  the refresh path now skips LLM extraction entirely when a scrape
  finds no content diff
- Fixed a real bug in the first pass of this migration: change_detected
  was set and then immediately reset within the same function call,
  making it unobservable to the CompetitorsPage badge, the Apps Script
  sidebar, and the n8n cron filter. It now stays visible until the
  next scrape confirms nothing further changed.
- Added a composite index on scrape_runs(competitor_id, content_hash,
  started_at) backing the per-competitor recency lookup
- Docker Compose's firecrawl service replaced with the real
  firecrawl-api/firecrawl-worker/firecrawl-playwright stack, gated
  behind an opt-in profile
- Added CORS (absent from the original spec entirely) so the
  browser-based dashboard can reach the API cross-origin

Phase 6 — Semantic trip matching:
- client_trips_repository.py — this collection existed in schema but
  nothing ever populated it; comparable-trip matching could never
  produce a real match without it
- embedding_service.save_client_trip() upserts a client_trips record
  from every /research submission's trip-intent description, with
  stale-embedding invalidation when destination/duration change
- /internal/embed-products now embeds client_trips as well as
  competitor products; /internal/match-comparable reads both sides
  from PocketBase instead of expecting client_trips in the request
  body, so it's callable unattended by n8n
- comparable_matches upserts on (client_product, competitor_product)
  instead of always creating — first_matched/last_matched only make
  sense if repeat weekly matches update in place, and a PM's dismissed
  match now survives re-matching
- embedding_and_matching_cron.json — Sunday night n8n workflow

249 tests passing, 96% coverage.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 11:23:23 -04:00
..
emails Phase 5 + 6: automation, content-hash change detection, semantic trip matching 2026-07-02 11:23:23 -04:00