rairo Claude Opus 4.8 commited on
Commit
ba74ad7
·
1 Parent(s): 4aa562c

docs: add ADR log for cross-session continuity

Browse files

Records the async generation pipeline, iterative image refinement with
model fallback, and country-scoped catalogue grounding decisions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files changed (1) hide show
  1. docs/ADR.md +96 -0
docs/ADR.md ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Architecture Decision Records — NeoFix-API (SozoFix backend)
2
+
3
+ This log records the "why" behind non-obvious backend decisions so future sessions
4
+ keep continuity. Append new records at the bottom. Newest decisions supersede older
5
+ ones only when explicitly marked. Status: Accepted | Superseded | Deprecated.
6
+
7
+ Stack recap: single-file Flask (`main.py`) on a Hugging Face Space (git remote is the
8
+ Space itself; pushing `main` redeploys). Firebase Realtime DB + Storage via Admin SDK.
9
+ Google GenAI (Gemini) for text/image, Deepgram for TTS, Stripe, Resend.
10
+
11
+ ---
12
+
13
+ ## ADR-001 — Asynchronous background generation for repair guides
14
+ **Date:** 2026-07-04 · **Status:** Accepted
15
+
16
+ **Context.** `PUT /api/projects/<id>/approve` was one synchronous Gemini call asking for
17
+ 5 steps with interleaved images, then uploads + TTS, all inside the request. Two failures:
18
+ (1) the newer image model returns fewer inline images than text steps, and the old code
19
+ truncated steps to `min(steps, images)`, so users got fewer than 5 steps; (2) the whole
20
+ job risked the Hugging Face Space HTTP timeout (~60 to 90s). The frontend meanwhile showed
21
+ a fake client-side progress bar that hit 100% regardless of backend state.
22
+
23
+ **Decision.** Split validation from work. `/approve` now validates (auth, credits, ownership,
24
+ re-entrancy guard), sets project `status=generating`, seeds a `projects/{id}/generationProgress`
25
+ node, spawns a `threading.Thread(daemon=True)` running `_run_guide_generation`, and returns
26
+ **202** immediately. A new lightweight `GET /api/projects/<id>/progress` is polled by the client.
27
+ A staleness guard in that endpoint flips a stuck `generating` project to `generation_failed`
28
+ once its heartbeat is older than `GENERATION_STALE_S` (default 240s), covering threads killed
29
+ by a Space restart.
30
+
31
+ **Consequences.** No request-timeout risk. Real, pollable progress. Credits are still deducted
32
+ only on success (moved into the job's finish path). Background threads do not survive a Space
33
+ restart; the staleness guard plus the client retry panel recover the user. `debug`/reloader is
34
+ left on; threads still run.
35
+
36
+ ---
37
+
38
+ ## ADR-002 — True iterative image refinement with model fallback
39
+ **Date:** 2026-07-04 · **Status:** Accepted
40
+
41
+ **Context.** A single multimodal call is unreliable for producing exactly 5 illustrated steps.
42
+ The primary image model (`GENERATION_MODEL`, "Nano Banana 2" = `gemini-3.1-flash-image-preview`)
43
+ is higher latency and sometimes returns text-only turns.
44
+
45
+ **Decision.** Generate in phases inside `_run_guide_generation`:
46
+ - **Phase A (plan):** a fast text-only call on `PLAN_TEXT_MODEL` (`gemini-3.1-flash-lite`) returns
47
+ the tools list and exactly 5 numbered steps. Reuses the existing regex extraction and
48
+ `parse_numbered_steps`.
49
+ - **Phase B (images):** one chat session on `GENERATION_MODEL`, one `send_message` per step so each
50
+ turn yields exactly one image with visual continuity. Each call runs under a timeout
51
+ (`IMAGE_CALL_TIMEOUT_S`, default 28s) with one retry, then falls back to `FALLBACK_IMAGE_MODEL`
52
+ (`gemini-2.5-flash-image`, "nano banana original") for the remaining steps. A step that still
53
+ yields no image keeps its text with an empty `imageUrl`; **steps are never truncated**.
54
+ - **Phase C (upload):** reuses the existing per-step image-upload + Deepgram-TTS parallelism.
55
+
56
+ All model ids and the timeout are environment variables, tunable without a redeploy.
57
+
58
+ **Consequences.** Guaranteed 5 text steps, with a latency safety net. Cost per guide is higher
59
+ than a single call (multiple model turns) but predictable; track it. Fixes the min-truncation bug.
60
+
61
+ ---
62
+
63
+ ## ADR-003 — Country-scoped supplier catalogue grounding
64
+ **Date:** 2026-07-04 (country scoping added 2026-07-05) · **Status:** Accepted
65
+
66
+ **Context.** The tools list was plain strings. We want real supplier prices (anchor partner:
67
+ Electrosales, Harare) shown to users, but only to users in that supplier's market, the way
68
+ Home Depot shows to US users and Builders to SA users. A user in a country with no partner
69
+ should see the generic tools list.
70
+
71
+ **Decision.** New `catalogue/` RTDB root. Each item carries a 2-letter `country` ISO code
72
+ (default `ZW` via `DEFAULT_CATALOGUE_COUNTRY`) plus `supplier`, `city`, keywords, price, etc.
73
+ Admin-only endpoints: CRUD, AI extract from an uploaded image or PDF (Gemini with
74
+ `response_mime_type=application/json`; PDFs go through `types.Part.from_bytes`, no new deps),
75
+ and bulk save after review. `match_tools_to_catalogue` is a cheap token-overlap scorer that
76
+ returns the best in-stock item per tool (carrying its `country`), cached on the project as
77
+ `toolMatches` at generation time and recomputed on demand via `GET /api/projects/<id>/tool-matches`.
78
+ The **client** detects the viewer's country and filters matches; the backend stays country-agnostic.
79
+
80
+ **Rejected alternative.** A per-user `city` field on the user profile (built, then reverted).
81
+ The user wanted automatic locale detection, not a manual setting. `PUT /api/user/profile` is back
82
+ to display-name only.
83
+
84
+ **Consequences.** Onboarding a new market is a data operation: load that supplier's catalogue
85
+ tagged with its country code. No server change per user. The matching scan is O(tools × items),
86
+ fine to roughly 1–2k items; add a keyword index if the catalogue grows large.
87
+
88
+ ---
89
+
90
+ <!-- Template for new records:
91
+ ## ADR-00N — Title
92
+ **Date:** YYYY-MM-DD · **Status:** Accepted
93
+ **Context.** ...
94
+ **Decision.** ...
95
+ **Consequences.** ...
96
+ -->