---
title: "Weekly Report — 2026-W18"
type: report
period: 2026-W18
generated: 2026-04-27
tags: [report, weekly]
---

# Weekly Report — 2026-W18

## Summary
Week 18 was dominated by building and integrating the **AI Cost Tracker** — a new shared Oliver platform service for centralised AI spend tracking. The tracker was scaffolded, deployed to optical-dev, and successfully connected to two consumer projects (video-accessibility, sandbox-notebookllamalm-nextjs) in a single day. Significant parallel work was done on video-accessibility: a Pydantic v2 serialisation bug was fixed, and a multi-phase RBAC/invitation system with SaaS-style tenant isolation was architected and partially implemented.

## Projects Worked On

| Project | Days Active | Key Work Done |
|---|---|---|
| **ai-cost-tracker** | 1 | Built FastAPI+MongoDB+Celery service; fixed rollup field names; switched analytics from nightly rollups to live `usage_events`; redesigned dashboard (dynamic dropdowns, sub-cent cost display, `source_app` pivot) |
| **video-accessibility** | 1 | Fixed Pydantic v2 `Field(alias="_id")` bug causing `undefined` client IDs; added `PATCH /jobs/{id}` endpoint + Cost Tracker Project ID UI field; scaffolded Client→Team→Project RBAC + PM role; MS SSO provisioning fix; multi-phase invitation system with Redis membership cache; Mailgun emailer |
| **sandbox-notebookllamalm-nextjs** | 1 | Integrated cost tracker (preflight + record); fixed ContextVar propagation through `asyncio.wait_for`; verified smoke test end-to-end |

## Key Decisions Made

- **`source_app` as project identifier** — `project_id` is never populated in `usage_events`; all analytics now pivot on `source_app` string
- **Drop Python `ContextVar` for user context** — unreliable across `asyncio.wait_for` task boundaries; always pass `user_external_id` as explicit function parameter
- **`preflight()` is fail-open** (`fail_open=True`) — cost-tracker outage must never block AI pipelines; returns `allow=true` when service unreachable
- **`record()` never raises** — SQLite outbox with 30s flusher + 10 retries + `dead` status; background error, not pipeline error
- **Alias mapping in pricing_engine** — keep `"google"` as provider string in client code; `pricing_engine.py` maps it to `vertex_ai-language-models` internally
- **Analytics reads live `usage_events`** — switched from nightly `usage_rollups` (invisible until 01:00 UTC) to real-time collection reads
- **`dev` branch for SaaS refactor** — video-accessibility tenant architecture (Org→members→teams→projects) goes to `dev`, not `main`
- **Redis membership cache** for RBAC (`cached_memberships.py`) — cache bumped on every membership write, avoiding per-request DB queries
- **Mailgun over SendGrid** — reuses existing `httpx` dependency; identical method signatures

## Technical Insights

- **`asyncio.wait_for` does NOT propagate Python ContextVars** — any context set before `wait_for` is invisible inside the nested coroutine; always pass values explicitly
- **`docker compose restart` does NOT re-read `env_file`** — must use `docker compose up -d --force-recreate` after `.env` changes
- **`.env` files on optical can be silently wiped** on `git pull` or Docker rebuild; verify after every deploy: `grep COST_TRACKER .env` + `docker inspect <container>`
- **Pydantic v2 `Field(alias="_id")` gotcha** — serialises the JSON key as `_id`, not `id`; frontend receives `undefined` on `.id` access; fix with explicit `_from_doc` helpers that rename `_id → id` and remove the alias from the response model
- **LiteLLM pricing key for Gemini is `vertex_ai-language-models`**, not `google` — always verify exact provider key in `model_prices` collection after a LiteLLM sync
- **`toFixed(2)` silently zeroes sub-cent costs** — use dynamic precision for cost display (e.g. `toPrecision(4)` or threshold-based formatting)
- **ElevenLabs returns no usage metadata** — character count must be captured via `len(text)` before the API call
- **Google TTS bills stripped chars for SSML** — use `len(synthesis_input.text)`, not `len(ssml)`, to avoid over-counting
- **When making FastAPI helpers async**, audit every call site — there were 10 spread across multiple route files in video-accessibility
- **`docker compose build --no-cache`** required when source code is baked into the image (not volume-mounted)
- **`git pull --rebase` fails with unstaged changes** — `git stash` first

## Knowledge Base Growth

10 new wiki articles created:

| Article | Location | Topic |
|---|---|---|
| Homelab Services Map | `homelab/homelab-services-map.md` | Full network/container reference |
| AI Cost Tracker — Pricing Sources | `tech-patterns/cost-tracker-pricing-sources.md` | 3-layer pricing pipeline (override → yaml → litellm) |
| AI Cost Tracker — Billing Units per Provider | `tech-patterns/cost-tracker-providers.md` | Gemini/ElevenLabs/GCS TTS extraction patterns |
| AI Cost Tracker — Integration Guide | `tech-patterns/cost-tracker-integration.md` | Step-by-step project onboarding (v3, ContextVar pitfall, docker inspect) |
| AI Cost Tracker — Project Card | `projects-overview/ai-cost-tracker.md` | Repo, stack, status, consumers |
| AI Cost Tracker — Architecture | `architecture/ai-cost-tracker.md` | Service design, live URLs, data model |
| Sync HTTP + SQLite Outbox Pattern | `concepts/sync-with-outbox.md` | Resilient record() without blocking pipelines |
| LiteLLM as Pricing Source | `concepts/litellm-pricing-source.md` | Why LiteLLM, sync schedule, provider key format |
| Preflight + Record Pattern | `concepts/preflight-record-pattern.md` | Universal 3-step pattern for all AI calls |
| Lazy User Mirror | `concepts/lazy-user-mirror.md` | On-first-use user provisioning in cost-tracker |

## Action Items Carried Forward

| Item | Project | Priority |
|---|---|---|
| Push `ai-cost-tracker` to Bitbucket: `cd /tmp/ai-cost-tracker && git push origin main` | ai-cost-tracker | High |
| Monitor dashboard — confirm video-accessibility events appearing after today's fixes | ai-cost-tracker / video-accessibility | High |
| Run migration on optical-web-1: `docker compose exec -T api python migrate.py up` (adds `project_manager` enum + `pm_client_ids`) | video-accessibility | High |
| Complete + commit Phase 4 frontend reorg (new routes, Settings IA, org switcher in Sidebar) | video-accessibility | Medium |
| Commit `frontend/src/hooks/useAccessibleVideoEdit.ts` unstaged changes | video-accessibility | Medium |
| Add `**/__pycache__/` to `.gitignore` | video-accessibility | Low |
| Add outbox depth metric to monitoring alerts for `dead` status rows | ai-cost-tracker | Low |
| Phase 2: track Whisper `audio_duration_seconds` and approximate from Cloud Run billing | ai-cost-tracker | Backlog |
| Check if video-accessibility MongoDB has historical AI call logs for back-fill | ai-cost-tracker | Backlog |
| Add deploy checklist to wiki: verify .env → docker inspect → smoke test | homelab/ops | Low |
| Grafana: change admin password from default `admin:admin` | homelab | Security |

## Stats
- Sessions: 9
- Daily logs: 1 day (2026-04-27)
- Wiki articles updated/created: 10
- Flush errors: 6 (FLUSH_ERROR throughout the day — investigate memory compiler reliability)