diff --git a/MEMORY.md b/MEMORY.md new file mode 100644 index 0000000..cf68eac --- /dev/null +++ b/MEMORY.md @@ -0,0 +1,66 @@ +# 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, origin `http://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:3008` over Tailscale; `BIND_IP` from `.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` (`TZ` in `services.py`); timestamps stored UTC-aware. +- Sources: `time_track` (explicit intervals → `accepted` on ingest), `zed` (heartbeat-derived blocks → `suggested`), `manual` (web-created → `accepted`), commits stored as `RawEvent(source="zed_commit")`, displayed per day, never time. +- Unique key `(source, device_id, external_id)`; Zed block `external_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}/status` returns 422. Never let inferred time double-count. +- Accepted `zed_inferred` becomes `kind="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_URL` is built via `SQLAlchemy URL.create` from `POSTGRES_*` 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) and `sync_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}/env` hold 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` (slug `gem360-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:8b` at `http://100.103.83.12:11345` (env `OLLAMA_*` reserved; no Ollama code yet — deterministic parsing first). +- Call import pipeline is upload + text extraction (`pdfplumber`, `pypdf` fallback, `needs_ocr` status) only; provider-specific parser intentionally deferred until a redacted sample PDF is provided. +- Auth: single password (`ADMIN_PASSWORD`) for web, `SYNC_API_TOKEN` bearer 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 `.env` and 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_imports` volume. +- **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-linger` would extend to logout (not enabled — needs user decision). +- Known harmless diagnostics: naive `datetime.now()` warnings in tracker scripts (local-time JSON format is deliberate), FastAPI `Depends`-default style warnings, broad `except` in PDF fallback (intentional), unsorted-import lints in `main.py`; stale "group_*_by_day not found" warnings in `app/main.py` are false positives (functions exist in `services.py`, exercised in smoke tests). +- `zed_time_tracker/` shows ` ?` in `git submodule status` from an untracked `__pycache__/` (regenerated each run; harmless). Note: `time_track` repo 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.html` +- `zed_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) + +1. `python3 -m py_compile app/*.py time_track/time_track.py zed_time_tracker/*.py` +2. `docker compose build` / `config --quiet` with inline env vars (never create a local `.env` in the repo) +3. Full smoke test on `BIND_IP=127.0.0.1` with a hostile password (e.g. containing `@ : # / %`), seed data via sync APIs, exercise login + UI + exports with curl, verify idempotent re-syncs +4. Always end with `docker compose down -v --remove-orphans` and remove temp files (cookies, reports) +5. Commit submodule repos individually, update outer pointers, push all three \ No newline at end of file