Fastwhisper / docs /ARCHITECTURE.md
Mbonea's picture
Close P1 arch-code gaps: loop summary, template-echo Rules, Move now, proof kinds.
57ed4c2
|
Raw
History Blame Contribute Delete
9.45 kB
# Habit Journal — Architecture
Single-user PWA on a Hugging Face Docker Space: **FastAPI** backend + **Vite/Preact** frontend, JSON/JSONL under `DATA_ROOT`, server-owned stats. Optional OpenRouter for coach/reschedule with Rules backup.
Interactive canvas (Cursor): `habit-journal-architecture.canvas.tsx`
---
## Stack at a glance
| Layer | Technology |
|-------|------------|
| Host | Hugging Face Docker Space |
| Server | Uvicorn + FastAPI (`app.main:create_app`) |
| Static UI | Vite → `static/` (PWA shell + service worker) |
| Data | JSON/JSONL under `DATA_ROOT` (default `/data/loop_logger`) |
| Optional LLM | OpenRouter (coach + reschedule); Rules backup if missing |
**One line:** HF Docker → Uvicorn/FastAPI → JSONL stores → Vite/Preact PWA (`credentials: 'include'`) → optional OpenRouter with Rules backup.
---
## System overview
```mermaid
flowchart TB
subgraph clients [Clients]
Browser[Browser PWA]
Agent[Cursor agent]
end
subgraph space [HF Docker Space]
Static[static/ Vite build]
API[FastAPI create_app]
SW[Service Worker shell-only]
end
subgraph data [DATA_ROOT]
Config[config.json]
Entries[entries.jsonl]
Daily[daily.jsonl]
Traces[traces.jsonl]
Sched[schedule/]
end
OR[OpenRouter optional]
Browser --> Static
Browser --> API
Browser --> SW
Agent -->|Bearer AGENT_TOKEN| API
API --> Config
API --> Entries
API --> Daily
API --> Traces
API --> Sched
API -.->|coach / reschedule| OR
```
Notes:
- Browser loads the static shell; **`/api/*` is network-only** (SW must never cache API).
- Agent uses `Authorization: Bearer AGENT_TOKEN` on `/api/agent/*` (session login also works as fallback).
---
## Backend layers
```mermaid
flowchart LR
subgraph edge [HTTP]
RAuth[auth]
REnt[entries]
RDaily[daily]
RPlan[plan]
RAgent[agent]
RCoach[coach]
RStats[stats]
RDbg[debug/export/settings/health]
end
subgraph services [Services]
CoachSvc[CoachService]
Resched[RescheduleService]
Evidence[evidence + stats_math]
SchedMath[schedule_math]
end
subgraph stores [Stores]
EntryStore
DailyStore
ScheduleStore
TraceStore
ConfigStore
end
RAuth --> ConfigStore
REnt --> EntryStore
RDaily --> DailyStore
RPlan --> ScheduleStore
RPlan --> Resched
RAgent --> ScheduleStore
RAgent --> DailyStore
RAgent --> EntryStore
RCoach --> CoachSvc
CoachSvc --> Evidence
CoachSvc --> TraceStore
RStats --> Evidence
Resched --> SchedMath
ScheduleStore --> SchedMath
```
### Module map
| Module | Responsibility |
|--------|----------------|
| `app/routers/*` | HTTP edge; envelope `{ok, data, error}` |
| `app/store_*` | JSONL persistence + file locks |
| `app/stats_math.py`, `app/evidence.py` | Server-owned ranks & `SERVER_PICKS` |
| `app/coach_service.py`, `app/backup_replies.py` | OpenRouter or Rules backup |
| `app/schedule_*` | Plan math, locked EV, reschedule |
| `app/deps.py`, `app/security_passwords.py` | Session + AGENT_TOKEN bearer |
### API surface
| Group | Endpoints |
|-------|-----------|
| Health | `GET /api/health`, `GET /api/ready` |
| Auth | `POST` login / logout / change-password, `GET /api/auth/me` |
| Entries | CRUD `/api/entries` + filters |
| Daily | `PUT`/`GET /api/daily/{day}`, `GET /api/daily?start=&end=` |
| Plan | Plan CRUD, feedback, seed, reschedule, priors, templates, health |
| Agent | `GET /api/agent/context`, `PUT /api/agent/plan/{day}` |
| Coach | `POST /api/coach`, `POST /api/entries/{id}/coach` |
| Stats | `GET /api/stats`, `GET /api/stats/remedies` |
| Debug | Traces, paste bundles |
| Export | Entries/daily JSONL + CSV |
| Settings | `GET /api/settings/status` (no secrets) |
---
## Frontend UI
### Nav IA
`Home | Plan | (+) Log | Daily | Settings`
History remains at `#/history` (reachable from Home “See all”). Center FAB stays **Log** (one tap).
```mermaid
flowchart TB
Login["#/login"] --> AuthGate{session?}
AuthGate -->|yes| Shell[App shell]
Shell --> TabBar
subgraph tabs [Bottom nav]
Home["#/ Home"]
Plan["#/plan Plan"]
FAB["(+) Log"]
Daily["#/daily Daily"]
Settings["#/settings"]
end
TabBar --> Home
TabBar --> Plan
TabBar --> FAB
TabBar --> Daily
TabBar --> Settings
Home --> History["#/history"]
History --> Entry["#/entry/:id"]
Home --> Coach["#/coach"]
Home --> Stats["#/stats"]
Settings --> Remedies["#/remedies"]
FAB --> Log["#/log"]
Log -->|high FSE| ProofSheet[Proof brick sheet]
ProofSheet --> DailyAPI[PUT /api/daily]
Plan --> BlockSheet[Block feedback sheet]
```
### Routes
| Route | Page | Role |
|-------|------|------|
| `#/` | Home | Week score, pending, plan next, indoors/proof nudges |
| `#/plan`, `#/plan/:date` | Plan | Day timeline, seed, reschedule, block feedback |
| `#/log` | Log | Fast entry + high-FSE proof wizard |
| `#/daily` | Daily | 6-pt scoreboard + movement / fantasy / proof fields |
| `#/settings` | Settings | Status, password, export |
| `#/history` | History | Entry list (via Home) |
| `#/entry/:id` | Entry | Detail + coach |
| `#/coach` | Coach | Free-text coach + SERVER_PICKS |
| `#/stats` | Stats | Server-owned `p_helped` / ranks |
| `#/remedies` | Remedies | Remedy leaderboard |
| `#/login` | Login | Password → session cookie |
### UI stack
- Vite + Preact + TypeScript
- Motion (press / sheets)
- lucide-preact icons
- Plus Jakarta Sans, warm journal UI (`#EBE7E0`)
- `credentials: 'include'` on all API calls; `401``#/login`
### Key components
| Component | Role |
|-----------|------|
| `TabBar` | Home \| Plan \| (+) Log \| Daily \| Settings |
| `Timeline` + `BlockSheet` | Plan day + thick feedback |
| `ResolvePendingSheet` | Pending resolve in < 3 taps |
| `Toast` / `Sheet` / `Pressable` | Shared chrome |
| `CoachResult` | Rules \| Model badge |
---
## Auth
```mermaid
sequenceDiagram
participant U as User
participant UI as PWA
participant API as FastAPI
participant Disk as config.json
U->>UI: password
UI->>API: POST /api/auth/login
API->>Disk: verify PBKDF2 hash
API-->>UI: Set-Cookie session
UI->>API: GET /api/* credentials include
Note over API: 401 → redirect #/login
participant A as Agent
A->>API: GET /api/agent/context
Note over A,API: Authorization Bearer AGENT_TOKEN
```
- **Human:** PBKDF2 password in `config.json` → signed session cookie (`sv` + `exp`).
- **Agent:** Bearer `AGENT_TOKEN` on `/api/agent/*` (or session).
- **Envelope:** `{ ok, data, error: { code, message } }`.
- Server owns points, `p_helped`, ranks, `d_hat` — LLM must not invent %.
---
## Coach pipeline
```mermaid
flowchart TB
Req[POST /api/coach] --> Evidence[build_evidence SERVER_PICKS]
Evidence --> Key{OPENROUTER_API_KEY?}
Key -->|no / force_backup| Rules[backup_replies]
Key -->|yes| OR[OpenRouter]
OR -->|parse fail / label at high I| Rules
Rules -->|I ge 7| HighFSE[high_intensity_fse]
OR --> Trace[traces.jsonl]
Rules --> Trace
Trace --> UI[CoachResult Rules or Model]
```
At **intensity ≥ 7**, Rules prefer interruptive remedies (leave room, interrupt walk without cinematic headphones, one proof line) — not “Labeling” alone.
---
## Schedule / Plan
```mermaid
flowchart TB
AgentPull[GET agent/context] --> Caps[capacity_hint + operators]
Caps --> Put[PUT agent/plan or UI seed]
Put --> PlanFile[plan-YYYY-MM-DD.json]
UI[Timeline + BlockSheet] --> FB[POST feedback]
FB --> Feedback[feedback.jsonl]
Feedback --> Priors[priors.json d_hat]
UI --> Resched[POST reschedule]
Resched -->|OR or rules| PlanFile
```
**P1 scope lock:** thin loop only (timeline + thick feedback + priors + capacity cuts). EV formula locked; no PERMA / δ / H in code. See [SCHEDULE.md](SCHEDULE.md).
---
## Domain: loop-break / movement
```mermaid
flowchart LR
Log[Log entry] -->|intensity ge 7| Proof[Proof wizard]
Proof --> DailyRow[Daily proof_brick_*]
DailyRow --> Home[Home banners]
DailyRow --> AgentCtx[agent/context movement]
Walk[Interrupt vs fantasy walk] --> DailyRow
Coach[Coach Rules] -->|intensity ge 7| Interrupt[Interrupt not Labeling]
PlanTpl[fantasy_slot_25 template] --> Plan[Day plan]
```
Daily additive fields include: `stayed_indoors_all_day`, `left_room`, `left_home`, walk splits, headphones / cinematic flags, `proof_brick_*`, fantasy scheduled vs unplanned minutes. **6-pt Daily formula unchanged.**
---
## Persistence layout
Under `DATA_ROOT` (default `/data/loop_logger`):
| Path | Purpose |
|------|---------|
| `config.json` | Password hash, session version |
| `entries.jsonl` | Log entries |
| `daily.jsonl` | Scoreboard + movement / proof / fantasy |
| `traces.jsonl` | Coach debug traces |
| `coach_brief.md` | Coach brief |
| `schedule/plan-YYYY-MM-DD.json` | Versioned day plan |
| `schedule/feedback.jsonl` | Block thick feedback |
| `schedule/priors.json` | `d_hat` / `p_done` / fun priors |
| `schedule/meta.json` | Reschedule cooldown |
Path definitions live in [`app/paths.py`](../app/paths.py).
---
## Related docs
- [BACKEND.md](BACKEND.md) — API / storage / coach / ops
- [FRONTEND.md](FRONTEND.md) — PWA UI contract
- [SCHEDULE.md](SCHEDULE.md) — Plan / agent / P1 scope lock
## Local tooling
- `scripts/pull_handoff_bundle.py` — pull live Space state into a paste bundle (`handoff/` gitignored)
- `.env` — local secrets only (gitignored): `HF_TOKEN`, `HF_SPACE_URL`, `APP_PASSWORD`, `AGENT_TOKEN`