11 KiB
Premium Mechanic PWA — Implementation State
Last updated: 2026-05-16 (end of session 2 — Phase B done)
What is done
| Phase | Task | Status | Artifact |
|---|---|---|---|
| 0 | Git infrastructure (Gitea + repos + feature branches) | ✅ DONE | repos exist, ssh keys configured |
| A | A1: Recon TaxiDashboard backend structure | ✅ DONE | docs/backend-layout-notes.md |
| A | A2: Caddy fragment for mechanic.pptaxi.ru |
✅ DONE | ops/Caddyfile.fragment |
| A | A3: MinIO CORS on pp-inspections bucket |
✅ DONE | ops/set-minio-cors.sh, applied, preflight 204 |
| A | A4: SQLAlchemy models (MechanicInspection, MechanicInspectionPhoto, MechanicDamageMarker) |
✅ DONE | commit be0553e on feat/mechanic-mvp |
| B | B1: Mechanic module skeleton + /healthz |
✅ DONE | commit d2113a0 — flat backend/app/api/mechanic.py with APIRouter(prefix="/v1/mechanic"), registered in app/main.py with prefix="/api" → final URL /api/v1/mechanic/healthz |
| B | B2: SQLAlchemy models registered + tests | ✅ DONE | already shipped in A4 (be0553e) — 38 metadata tests in backend/tests/test_mechanic_models.py, no extra B2 commit |
| B | B3: Pydantic schemas | ✅ DONE | commit 2739cc8 + fix 2fb978d — 14 schemas + 6 Literals in backend/app/api/mechanic_schemas.py, 15 behavioral tests in backend/tests/test_mechanic_schemas.py; fix added = None defaults to Optional[X] fields and tightened MarkerOut.damage_type/severity to DamageType/Severity Literals |
| B | B4: Auth + DB session dependencies | ✅ DONE | commit f0d7e83 — backend/app/api/mechanic_deps.py re-exports get_db+get_current_user, adds require_mechanic guard (admin OR member of any active dept_type='svc' department); 9 tests in backend/tests/test_mechanic_deps.py use fake-DB stubs (no real session needed) |
Branch state: feat/mechanic-mvp at f0d7e83. Stack: be0553e (A4) → d2113a0 (B1) → 2739cc8 (B3) → 2fb978d (B3-fix) → f0d7e83 (B4). All pushed to origin (Gitea).
Test count on branch: 62 mechanic tests pass (38 models + 15 schemas + 9 deps) in ~0.8s, no real DB needed.
Where things live
Repos (Gitea: http://100.64.0.9:3001/tremble7681)
| Repo | Local clone | Working branch |
|---|---|---|
mechanic-pwa |
c:\NewProject\PremiumDriverApp\ |
feat/mechanic-mvp |
taxi-dashboard |
c:\NewProject\taxi-dashboard\ |
feat/mechanic-mvp |
taxi-dashboard clone has two remotes: origin → Gitea, vds → SSH to /opt/sites/taxi-dashboard/ on 100.64.0.12 (for pulling back-and-forth with prod).
Reference paths (real, verified — see docs/backend-layout-notes.md)
Base,TimestampMixin→app.models.baseget_db→app.db(file:app/db/__init__.py)get_current_user→app.auth.depsUser→app.models.user- Auth flow: OAuth2 password + Bearer JWT (not PIN as plan originally said)
- DB init:
Base.metadata.create_allat startup (not Alembic) + inlineALTER TABLE ADD COLUMN IF NOT EXISTSinapp/main.pylifespan - Backend Docker container:
taxi-backend-green/taxi-backend-blue(blue/green) - MinIO container:
taxi-minio - Models registered in:
app/models/__init__.py(imports +__all__)
Naming clash decision
Existing app/api/inspections.py + model CarInspection (table car_inspections) hold vendor-imported read-only data from NaughtySoft. Our new tables use the mechanic_ prefix to coexist:
mechanic_inspections(modelMechanicInspection)mechanic_inspection_photos(modelMechanicInspectionPhoto)mechanic_damage_markers(modelMechanicDamageMarker)
Plan-vs-reality overrides applied in Phase B (Authoritative)
Three places where the original plan (docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md) was overridden during execution because Phase A1 recon showed the codebase doesn't match the plan's assumptions:
| Plan said | Actual implementation | Why |
|---|---|---|
backend/app/mechanic/router.py (subpackage) |
backend/app/api/mechanic.py (flat) |
TaxiDashboard uses flat app/api/*.py files; no subpackages |
backend/app/mechanic/schemas.py (subpackage) |
backend/app/api/mechanic_schemas.py (flat sibling) |
Same convention |
backend/app/mechanic/deps.py (subpackage) |
backend/app/api/mechanic_deps.py (flat sibling) |
Same convention |
Router prefix /api/v1/mechanic |
Router prefix /v1/mechanic, with app.include_router(..., prefix="/api") in main.py |
Matches existing convention (inspections.py → /v2/inspections + include with /api). Final URL is identical: /api/v1/mechanic/... |
require_mechanic checks user.dept.lower() in (...) |
Queries UserDepartment ⨝ Department WHERE dept_type='svc' AND is_active, admin shortcuts before DB hit |
User has no dept attribute; departments are M2M via user_departments. DEPT_TYPES=['ops','svc','adm'], svc = service-type (мастерская) |
When dispatching Phase C–F implementers, copy this override map into their context so they don't follow the stale subpackage / dept paths from the plan.
Next task
Task C1 — GET /me endpoint
(From docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md Phase C.)
Working dir: c:\NewProject\taxi-dashboard (branch feat/mechanic-mvp, HEAD f0d7e83)
The endpoint itself is trivial:
@router.get("/me", response_model=MeOut)
async def me(user: User = Depends(require_mechanic)) -> MeOut:
perms = ["mechanic:inspect"]
if user.is_admin:
perms.insert(0, "admin")
return MeOut(
id=user.id,
name=user.full_name or user.username or f"user#{user.id}",
email=user.email,
permissions=perms,
)
Add to backend/app/api/mechanic.py (the existing router from B1). Import require_mechanic from app.api.mechanic_deps and MeOut from app.api.mechanic_schemas.
⚠️ Open architecture decision before C1 (must resolve, not punt) — see "Phase C testing-infrastructure decision" section below. The plan's tests for C1 assume httpx.AsyncClient, pytest-asyncio, conftest.py with client + mechanic_user_token fixtures, and a real test DB. None of that exists. Don't let the C1 implementer freelance — decide first, then dispatch.
Phase C testing-infrastructure decision (UNRESOLVED — answer this before Task C1)
Current backend/tests/ contains zero fixtures: no conftest.py, no async client, no JWT minting, no test DB. Phases A and B got by with pure-unit and metadata tests. Phase C is the first phase that needs endpoint-level tests, which forces a choice:
| Option | Scope | Pros | Cons |
|---|---|---|---|
| A. Dependency-override mocks (recommended) | ~30 lines: override get_current_user / require_mechanic / get_db on the FastAPI app, fake db.execute results in each test. Sync TestClient. |
Tiny, fast, no DB, matches the TDD London School preference in CLAUDE.md. Already proven in test_mechanic_deps.py. |
Won't catch real-DB query bugs; defers integration testing to Phase J e2e. |
| B. Real DB fixture infrastructure | ~250–400 lines: conftest.py with async SQLite (aiosqlite) or testcontainers Postgres, Base.metadata.create_all, seeded user+department+M2M, JWT mint helper, pytest-asyncio + httpx.AsyncClient. |
Catches real-query bugs, e2e-ish from day one. | Big tangent, slows every PR, async fixture lifecycle is fiddly. |
| C. Hybrid | Start with A; add a tiny "DB smoke" subset (Option B) only for endpoints with non-trivial query logic. | Best of both, incrementally. | Two paradigms to maintain. |
Recommendation: A, with the understanding that Phase J adds end-to-end against a real running backend (the plan's existing Phase J e2e + smoke test tasks already cover the real-DB path).
If A is chosen, the controller's prompt for the C1 implementer should explicitly tell them: "use FastAPI TestClient (sync) + app.dependency_overrides[require_mechanic] to inject a fake user; don't add a conftest, don't add a real DB, don't introduce pytest-asyncio."
How to resume in a new session
In a new Claude Code chat in c:\NewProject\, paste this prompt:
Продолжаем проект Premium Mechanic PWA.
Прочитай PremiumDriverApp/docs/superpowers/STATE.md — там полное состояние и следующая задача.
Phase B полностью завершён (B1–B4, последний коммит f0d7e83). Следующая —
Task C1 (GET /me) из плана PremiumDriverApp/docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md,
НО сначала надо разрешить "Phase C testing-infrastructure decision" в STATE.md (Option A / B / C).
Используй superpowers:subagent-driven-development skill. Реши test-infra вопрос, потом
спавни первого implementer-subagent на C1 со специфической инструкцией про тестовый подход.
That prompt is the single entry point for the next session — Claude will read STATE.md, ground itself, resolve the test-infrastructure fork, and continue dispatching subagents from Task C1 onward.
Remaining roadmap (Phase 1 MVP)
Phase B (4 tasks)✅ done — module skeleton, models, schemas, auth deps- Phase C (3 tasks): GET /me, GET /vehicles, GET /vehicles/{id}
- Phase D (3 tasks): inspections CRUD with carry-over markers logic
- Phase E (5 tasks): MinIO storage layer + presigned upload/confirm + 302 read + celery thumbnail
- Phase F (2 tasks): markers create/update/delete
- Phase G (5 tasks): Vite + React + Tailwind + shadcn + PWA scaffold
- Phase H (3 tasks): Login + Home + VehicleCard pages
- Phase I (6 tasks): CameraCapture + PhotoSlot + VehicleScheme + InspectionEditor + InspectionReview
- Phase J (4 tasks): e2e test + deploy backend + deploy frontend + smoke test
≈ 31 tasks left until MVP live on mechanic.pptaxi.ru.
Known external dependencies
- MinIO
pp-inspectionsbucket — CORS already configured, presigned uploads will work - Caddy on 100.64.0.12 — fragment ready, will be wired during Phase J deploy
- Docker blue/green deployment on VDS — backend re-deployment will need a touch of
docker compose(Phase J) - Frontend deployment target:
/opt/sites/mechanic-pwa/dist/on VDS (Phase J)
Conventions reminder
- All new code in
feat/mechanic-mvpbranches in both repos - All new tables use
mechanic_prefix - Don't touch existing
app/api/inspections.pyorapp/models/inspection.py(vendor-imported) - All new mechanic source lives in flat files under
backend/app/api/withmechanic_*naming (mechanic.py,mechanic_schemas.py,mechanic_deps.py). Noapp/mechanic/subpackage — see "Plan-vs-reality overrides" table above. - Router prefix is
/v1/mechanic;/apiis added byinclude_router(..., prefix="/api")inmain.py. Final URLs always/api/v1/mechanic/*. - Phase C+ endpoint tests: pending the test-infrastructure decision (Option A / B / C above). Do NOT introduce
pytest-asyncio,conftest.py, or a real DB without first resolving that decision. require_mechanicallows admins + members of any activeDepartmentwithdept_type='svc'. Use it as the default auth dep for all mechanic endpoints.