docs(state): Phase B complete (B1-B4), Phase C test-infra fork documented
This commit is contained in:
+68
-28
@@ -1,6 +1,6 @@
|
|||||||
# Premium Mechanic PWA — Implementation State
|
# Premium Mechanic PWA — Implementation State
|
||||||
|
|
||||||
> Last updated: 2026-05-16 (end of session 1)
|
> Last updated: 2026-05-16 (end of session 2 — Phase B done)
|
||||||
|
|
||||||
## What is done
|
## What is done
|
||||||
|
|
||||||
@@ -11,6 +11,14 @@
|
|||||||
| A | A2: Caddy fragment for `mechanic.pptaxi.ru` | ✅ DONE | `ops/Caddyfile.fragment` |
|
| 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 | 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` |
|
| 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
|
## Where things live
|
||||||
|
|
||||||
@@ -43,30 +51,60 @@ Existing `app/api/inspections.py` + model `CarInspection` (table `car_inspection
|
|||||||
- `mechanic_inspection_photos` (model `MechanicInspectionPhoto`)
|
- `mechanic_inspection_photos` (model `MechanicInspectionPhoto`)
|
||||||
- `mechanic_damage_markers` (model `MechanicDamageMarker`)
|
- `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 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
|
## Next task
|
||||||
|
|
||||||
### **Task B1 — Mechanic module skeleton with healthz endpoint**
|
### **Task C1 — `GET /me` endpoint**
|
||||||
|
|
||||||
(From `docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md` Phase B.)
|
(From `docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md` Phase C.)
|
||||||
|
|
||||||
**Working dir:** `c:\NewProject\taxi-dashboard` (branch `feat/mechanic-mvp` already checked out)
|
**Working dir:** `c:\NewProject\taxi-dashboard` (branch `feat/mechanic-mvp`, HEAD `f0d7e83`)
|
||||||
|
|
||||||
**What to do:**
|
**The endpoint** itself is trivial:
|
||||||
1. Create new file `backend/app/api/mechanic.py` (not a subpackage — TaxiDashboard uses flat `app/api/*.py` files):
|
```python
|
||||||
```python
|
@router.get("/me", response_model=MeOut)
|
||||||
from fastapi import APIRouter
|
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,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/v1/mechanic", tags=["mechanic"])
|
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`.
|
||||||
|
|
||||||
@router.get("/healthz")
|
**⚠️ 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.
|
||||||
async def healthz() -> dict[str, str]:
|
|
||||||
return {"status": "ok"}
|
|
||||||
```
|
|
||||||
2. Register in `backend/app/main.py` — add `mechanic` to the `from app.api import (...)` tuple and call `app.include_router(mechanic.router)` where the other includes live.
|
|
||||||
3. Test: bring up the dev backend, hit `GET /api/v1/mechanic/healthz`, expect `{"status":"ok"}`.
|
|
||||||
4. Commit + push.
|
|
||||||
|
|
||||||
Plan calls it Task B1 (see `Phase B` section in the plan).
|
## 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
|
## How to resume in a new session
|
||||||
|
|
||||||
@@ -75,22 +113,21 @@ In a new Claude Code chat in `c:\NewProject\`, paste this prompt:
|
|||||||
```
|
```
|
||||||
Продолжаем проект Premium Mechanic PWA.
|
Продолжаем проект Premium Mechanic PWA.
|
||||||
|
|
||||||
Контекст:
|
Прочитай PremiumDriverApp/docs/superpowers/STATE.md — там полное состояние и следующая задача.
|
||||||
- Spec: PremiumDriverApp/docs/superpowers/specs/2026-05-16-premium-mechanic-design.md
|
|
||||||
- План: PremiumDriverApp/docs/superpowers/plans/2026-05-16-premium-mechanic-phase1.md
|
|
||||||
- Backend layout: PremiumDriverApp/docs/backend-layout-notes.md
|
|
||||||
- Текущее состояние: PremiumDriverApp/docs/superpowers/STATE.md
|
|
||||||
|
|
||||||
Phase A полностью завершён (A1–A4, последний коммит be0553e). Следующая задача — Task B1 (mechanic module skeleton + healthz endpoint) из плана.
|
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).
|
||||||
|
|
||||||
Используй subagent-driven-development skill. Прочитай STATE.md, спавни первого implementer-subagent на Task B1.
|
Используй 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, and continue dispatching subagents from Task B1 onward.
|
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)
|
## Remaining roadmap (Phase 1 MVP)
|
||||||
|
|
||||||
- **Phase B** (4 tasks): module skeleton, models registered, schemas, auth deps
|
- ~~**Phase B** (4 tasks)~~ ✅ done — module skeleton, models, schemas, auth deps
|
||||||
- **Phase C** (3 tasks): GET /me, GET /vehicles, GET /vehicles/{id}
|
- **Phase C** (3 tasks): GET /me, GET /vehicles, GET /vehicles/{id}
|
||||||
- **Phase D** (3 tasks): inspections CRUD with carry-over markers logic
|
- **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 E** (5 tasks): MinIO storage layer + presigned upload/confirm + 302 read + celery thumbnail
|
||||||
@@ -100,7 +137,7 @@ That prompt is the **single entry point** for the next session — Claude will r
|
|||||||
- **Phase I** (6 tasks): CameraCapture + PhotoSlot + VehicleScheme + InspectionEditor + InspectionReview
|
- **Phase I** (6 tasks): CameraCapture + PhotoSlot + VehicleScheme + InspectionEditor + InspectionReview
|
||||||
- **Phase J** (4 tasks): e2e test + deploy backend + deploy frontend + smoke test
|
- **Phase J** (4 tasks): e2e test + deploy backend + deploy frontend + smoke test
|
||||||
|
|
||||||
≈ 35 tasks left until MVP live on `mechanic.pptaxi.ru`.
|
≈ 31 tasks left until MVP live on `mechanic.pptaxi.ru`.
|
||||||
|
|
||||||
## Known external dependencies
|
## Known external dependencies
|
||||||
|
|
||||||
@@ -114,4 +151,7 @@ That prompt is the **single entry point** for the next session — Claude will r
|
|||||||
- All new code in `feat/mechanic-mvp` branches in both repos
|
- All new code in `feat/mechanic-mvp` branches in both repos
|
||||||
- All new tables use `mechanic_` prefix
|
- All new tables use `mechanic_` prefix
|
||||||
- Don't touch existing `app/api/inspections.py` or `app/models/inspection.py` (vendor-imported)
|
- Don't touch existing `app/api/inspections.py` or `app/models/inspection.py` (vendor-imported)
|
||||||
- Backend tests are synchronous metadata-style for now (no async fixtures exist yet); when API endpoint tests start in Phase C, they'll need a real DB session — implementer-subagent should propose minimal `conftest.py` additions if needed.
|
- 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user