Files
mechanic-pwa/docs/superpowers/STATE.md
T

13 KiB
Raw Blame History

Premium Mechanic PWA — Implementation State

Last updated: 2026-05-17 (end of session 2 — Phase B + Phase C 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 f0d7e83backend/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)
C C1: GET /me endpoint DONE commit a0bdcaa + signature fix 1e5b822 — appended to backend/app/api/mechanic.py; returns MeOut from require_mechanic user; permissions is ["admin", "mechanic:inspect"] for admins else ["mechanic:inspect"]. Test fixtures (client, override_mechanic, _fake_user) in backend/tests/test_mechanic_endpoints.py use FastAPI TestClient + app.dependency_overrides per Option A — no real DB / no async. 6 endpoint tests.
C C2: GET /vehicles list with q+limit search DONE commit 7be8783 — new backend/app/api/mechanic_service.py with search_vehicles_with_last_inspection (single aggregated subquery + outerjoin to avoid N+1 for last completed inspection per vehicle); endpoint in mechanic.py; new VehicleListResponse schema; 8 tests (6 endpoint + 2 service-stmt) with _FakeSession.execute() returning canned rows. Search hits plate (CarV2.number) + VIN only.
C C3: GET /vehicles/{id} detail + bound /vehicles limit DONE commit 9109383 + style fix 90b8c66 — new VehicleDetail(VehicleSummary) + InspectionSummary(_Base) schemas; service get_vehicle_by_id + recent_mechanic_inspections; endpoint raises 404 when missing, last_inspection_at derived from first completed inspection in the list; applied Query(20, ge=1, le=200) bounds to C2's /vehicles limit param. 7 new tests (5 detail + 2 bounds). Service is now FastAPI-free (404 raise lives in endpoint, matching automations.py convention).

Branch state: feat/mechanic-mvp at 90b8c66. Full stack: be0553e (A4) → d2113a0 (B1) → 2739cc8 + 2fb978d (B3) → f0d7e83 (B4) → a0bdcaa + 1e5b822 (C1) → 7be8783 (C2) → 9109383 + 90b8c66 (C3). All pushed to origin (Gitea).

Test count on branch: 83 mechanic tests pass (38 models + 15 schemas + 9 deps + 21 endpoints across C1/C2/C3) in ~2s, no real DB needed.

Live mechanic API surface (4 routes):

  • GET /api/v1/mechanic/healthz — liveness
  • GET /api/v1/mechanic/me — current user + permissions
  • GET /api/v1/mechanic/vehicles?q=&limit= — list with search (1≤limit≤200)
  • GET /api/v1/mechanic/vehicles/{vehicle_id} — detail with recent inspections

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, TimestampMixinapp.models.base
  • get_dbapp.db (file: app/db/__init__.py)
  • get_current_userapp.auth.deps
  • Userapp.models.user
  • Auth flow: OAuth2 password + Bearer JWT (not PIN as plan originally said)
  • DB init: Base.metadata.create_all at startup (not Alembic) + inline ALTER TABLE ADD COLUMN IF NOT EXISTS in app/main.py lifespan
  • 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 (model MechanicInspection)
  • mechanic_inspection_photos (model MechanicInspectionPhoto)
  • mechanic_damage_markers (model MechanicDamageMarker)

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 CF implementers, copy this override map into their context so they don't follow the stale subpackage / dept paths from the plan.

Phase C testing-infrastructure decision (RESOLVED — Option A in force)

Phase C runs against FastAPI TestClient (sync) + app.dependency_overrides[require_mechanic] and app.dependency_overrides[get_db] to inject fake user / fake session. Fake session is a SimpleNamespace-flavored _FakeSession class in tests/test_mechanic_endpoints.py that supports .execute(stmt) (returns _FakeResult seeded with rows) and .get(model, pk) (returns seeded object or None).

No conftest.py, no pytest-asyncio, no real DB. SQL correctness is deliberately deferred to Phase J e2e.

If Phase D/E/F needs a more complex query shape (e.g. .scalars().one() / .first()), extend _FakeResult.scalars() accordingly — currently it returns self and supports only .all(). See test_mechanic_endpoints.py:_FakeResult docstring.

Next task

Task D1 — inspections CRUD foundation (create + list)

(From docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md Phase D.)

Working dir: c:\NewProject\taxi-dashboard (branch feat/mechanic-mvp, HEAD 90b8c66)

⚠️ Heads up — Phase D introduces write operations + the carry-over markers logic, which is qualitatively different from Phase C's read-only endpoints:

  • POST/PATCH endpoints need request-body validation (Pydantic schemas exist; verify they match the plan's body shapes)
  • Carry-over: when a new inspection is created for a vehicle, copy unresolved markers from the previous inspection (prev_inspection_id self-reference). Needs careful design.
  • Idempotency / race conditions: two mechanics opening the same vehicle simultaneously
  • Real persistence with _FakeSession: tests need to verify the right add() / flush() / refresh() calls happened. The current _FakeSession only mocks read operations — needs extension for write paths (add(obj), flush(), refresh(obj) to seed an id).

Recommendation for the next session: before dispatching the D1 implementer, decide:

  1. Whether the carry-over copy happens inside the create endpoint (simpler, atomic) or as a separate background step (e.g. Celery).
  2. How _FakeSession.add/flush/refresh should behave for write-path tests (auto-assign id? track inserted objects?).
  3. Whether Phase D is the right place to introduce a tiny real-DB conftest after all (re-evaluate Option C from earlier).

These three decisions are NOT trivial and warrant a brainstorming pass at the start of the next session before any implementer is dispatched.

Read the D1 task definition in the plan before deciding. Also check what other relevant Pydantic schemas exist that we built in B3 (InspectionCreate, InspectionPatch, MarkerCreate, MarkerPatch).

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 C полностью завершён (C1–C3, последний коммит 90b8c66, 83/83 тестов зелёных).
Следующая фаза — Phase D (inspections CRUD с carry-over markers) из плана
PremiumDriverApp/docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md.

⚠️ Перед спавном D1 implementer-subagent сделай brainstorming pass по трём решениям
из секции "Next task" в STATE.md: (1) carry-over inline-vs-async, (2) _FakeSession
write-path extension, (3) пересмотр Option C для real-DB conftest в Phase D.

Используй superpowers:brainstorming, потом superpowers:subagent-driven-development.

That prompt is the single entry point for the next session — Claude will read STATE.md, ground itself, brainstorm the three Phase D architecture decisions, then dispatch the D1 implementer.

Remaining roadmap (Phase 1 MVP)

  • Phase B (4 tasks) done — module skeleton, models, schemas, auth deps
  • Phase C (3 tasks) done — GET /me, GET /vehicles, GET /vehicles/{id}
  • Phase D (3 tasks): inspections CRUD with carry-over markers logic ← NEXT
  • 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

≈ 28 tasks left until MVP live on mechanic.pptaxi.ru.

Known external dependencies

  • MinIO pp-inspections bucket — 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-mvp branches in both repos
  • All new tables use mechanic_ prefix
  • Don't touch existing app/api/inspections.py or app/models/inspection.py (vendor-imported)
  • All new mechanic source lives in flat files under backend/app/api/ with mechanic_* naming (mechanic.py, mechanic_schemas.py, mechanic_deps.py). No app/mechanic/ subpackage — see "Plan-vs-reality overrides" table above.
  • Router prefix is /v1/mechanic; /api is added by include_router(..., prefix="/api") in main.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_mechanic allows admins + members of any active Department with dept_type='svc'. Use it as the default auth dep for all mechanic endpoints.