6.4 KiB
6.4 KiB
Project memory — time_track_server
Handoff notes for agent conversations. Verify claims against current code and Git state before relying on them. Last updated: 2026-09-14.
Current focus
The combined-timesheet server is deployed and in daily use. Real data flows from both local trackers via systemd timers. Remaining feature work: mobile-provider PDF call parser (blocked on a redacted sample PDF), optional Ollama enrichment (design only so far).
Architecture
- Outer repo (this one): FastAPI server (
app/) + Docker Compose deployment + two Git submodules.time_track/— Waybar tracker (submodule, originhttp://100.103.83.12:3003/fegger/waybar_time_track.git)zed_time_tracker/— Zed hours tracker (submodule, origin.../fegger/zed_time_tracker.git)
- Server:
app/main.py(routes),app/services.py(ingest/derivation/grouping),app/models.py(RawEvent,TimeEntry,CallImport),app/templates/(Jinja + Pico CSS),app/database.py. - Access:
http://100.103.83.12:3008over Tailscale;BIND_IPfrom.env; Postgres internal-only; secrets only via.env(never commit.env). - Deploy on server:
git pull && docker compose up --build -d(submodules first: commit each submodule, push, then update pointers in outer repo, push all three). - Backup:
docker compose exec -T postgres pg_dump -U time_track time_track > backup.sql.
Data semantics (must preserve)
- Timezone: day grouping, display, and monthly reports use
Europe/Vienna(TZinservices.py); timestamps stored UTC-aware. - Sources:
time_track(explicit intervals →acceptedon ingest),zed(heartbeat-derived blocks →suggested),manual(web-created →accepted), commits stored asRawEvent(source="zed_commit"), displayed per day, never time. - Unique key
(source, device_id, external_id); Zed blockexternal_id=stable_id(device, project_slug, start, end)— heartbeat re-derivation must skip existing IDs so user accept/reject decisions stick (fixed 500 bug on 2026-09-14). - Overlap rule (user decision 2026-09-14): suggested entries overlapping accepted time are evidence only — "Accept day" skips them, UI hides Accept,
POST /entries/{id}/statusreturns 422. Never let inferred time double-count. - Accepted
zed_inferredbecomeskind="manual"(durable, survives re-derivation). - Rejected entries are excluded from exports; suggested time always reported separately from accepted.
- Monthly export
GET /export/monthly?month=YYYY-MM&format=md|csv; full calendar month, Vienna dates, month-boundary-safe. DATABASE_URLis built viaSQLAlchemy URL.createfromPOSTGRES_*env parts — never go back to string interpolation (passwords with@ : / # %broke it).
Client sync (this workstation, verified working)
zed_time_tracker/sync.py(per project, offset-based state in.zed-hours/sync-state.json, strips absolute paths) andsync_all.py(multi-project wrapper reading~/.config/zed-hours-sync/{env,projects.txt}).time_track/time_track.py sync(re-sends all finished entries; server dedupes by entry UUID).- systemd user timers on this workstation (
framework):zed-hours-sync.timer+time-track-sync.timer, every 15 min,Persistent=true; env files~/.config/{zed-hours-sync,time-track-sync}/envhold real tokens (mode 600 — never print or commit them). Both verified syncing successfully on 2026-09-14 (time_track: 15 entries; zed: 0 new, state up to date). - Only project configured in
projects.txt:/home/fegger/Code/ixsol/gem360-git(sluggem360-git).
Decisions & conventions
- Single user; no rates/invoicing — duration reporting only; call logs are mobile-provider call-detail records (logs only, no audio).
- Zed model choice for optional enrichment:
qwen3:8bathttp://100.103.83.12:11345(envOLLAMA_*reserved; no Ollama code yet — deterministic parsing first). - Call import pipeline is upload + text extraction (
pdfplumber,pypdffallback,needs_ocrstatus) only; provider-specific parser intentionally deferred until a redacted sample PDF is provided. - Auth: single password (
ADMIN_PASSWORD) for web,SYNC_API_TOKENbearer for sync APIs; unauthenticated browser requests redirect to/login(303), APIs return 401. - Commits: repo hygiene — commit only when asked (user has approved commit+push each iteration); keep
.envand tokens out of everything.
Open items / blockers
- Call-log PDF parser: needs a redacted mobile-provider sample statement (columns/layout/duration format) before implementation. Upload UI already collects files to the
call_importsvolume. - Ollama enrichment: planned worker step for suggesting task/project from provider labels; output must be suggestion-only, strict JSON (Pydantic), minimal PII sent.
- No HTTPS (Tailscale encryption only); add reverse proxy/Tailscale Serve if browser HTTPS is wanted.
- User timers run only while the desktop session is active;
loginctl enable-lingerwould extend to logout (not enabled — needs user decision). - Known harmless diagnostics: naive
datetime.now()warnings in tracker scripts (local-time JSON format is deliberate), FastAPIDepends-default style warnings, broadexceptin PDF fallback (intentional), unsorted-import lints inmain.py; stale "group_*_by_day not found" warnings inapp/main.pyare false positives (functions exist inservices.py, exercised in smoke tests). zed_time_tracker/shows?ingit submodule statusfrom an untracked__pycache__/(regenerated each run; harmless). Note:time_trackrepo has a tracked__pycache__/time_track.cpython-314.pyc— do not delete it.
Files that matter now
app/main.py,app/services.py,app/models.py,app/schemas.py,app/templates/index.htmlzed_time_tracker/sync.py,zed_time_tracker/sync_all.py,time_track/time_track.py(sync section ~L267)docker-compose.yml,.env.example,README.md
Validation workflow (proven)
python3 -m py_compile app/*.py time_track/time_track.py zed_time_tracker/*.pydocker compose build/config --quietwith inline env vars (never create a local.envin the repo)- Full smoke test on
BIND_IP=127.0.0.1with a hostile password (e.g. containing@ : # / %), seed data via sync APIs, exercise login + UI + exports with curl, verify idempotent re-syncs - Always end with
docker compose down -v --remove-orphansand remove temp files (cookies, reports) - Commit submodule repos individually, update outer pointers, push all three