diff --git a/CLAUDE.md b/CLAUDE.md index fd2c10c..e019023 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -30,6 +30,7 @@ 20. [Known Constraints & Hard Rules](#20-known-constraints--hard-rules) 21. [Change Philosophy](#21-change-philosophy) 22. [Object Storage Migration (R2)](#22-object-storage-migration-r2) +23. [Photo Capture-Time / Geo Overlay](#23-photo-capture-time--geo-overlay) --- @@ -163,6 +164,7 @@ part of the tree — see §7. Device registration on the API side lives in | `REDIS_URL` | Optional. When set, Flask-Limiter uses Redis for shared rate-limit counters across Gunicorn workers. | | `GROQ_API_KEY` | Optional. When set, enables the AI chatbot at `/support/chat`. Absent → chat input disabled; customers see a "Submit to Support" fallback only. | | `GROQ_MODEL` | Optional. Groq model ID. Defaults to `llama-3.3-70b-versatile`. | +| `PHOTO_STAMP_ENABLED` | Optional, default `true`. Burns a capture-time + geo overlay into photos uploaded via `POST /api/v1/photos/upload`. Set `false` to store raw uploads. | ### Email SSL Auto-Detection @@ -577,7 +579,7 @@ The last eight styles (`SummaryTitle` through `TableCell`) were added for the fa | `POST /api/v1/inspections` | jwt_required | Create inspection; idempotent via `mobile_local_id` | | `PATCH /api/v1/inspections/` | jwt_required | Update inspection (draft → completed) | | `POST /api/v1/issues` | jwt_required | Create issue; idempotent via `mobile_local_id`; accepts `result_photos` list stored in `mobile_photo_paths` | -| `POST /api/v1/photos/upload` | jwt_required | Multipart photo upload; returns `server_path` | +| `POST /api/v1/photos/upload` | jwt_required | Multipart photo upload; returns `server_path`, `stamped`, `captured_at`, `capture_source`. Optional form fields `captured_at` (ISO-8601), `latitude`, `longitude` drive the burned-in timestamp/geo overlay — see §23. | ### Phase C Endpoints @@ -1351,6 +1353,7 @@ timeout = 30 | 78 | **`PATCH /api/v1/issues//handler` allows the inspector on purpose — do NOT align it to the web form's admin/director/PM restriction** | The iPad lets the assigned inspector set "Handled By" from the field, scoped via `get_inspector_scope()` (403 if the issue's facility isn't contracted). This is a deliberate divergence from the web form. `_issue_payload()` must keep returning all handler fields (`handler_type`, `handler_label`, `facility_handler_*`, `vendor_*`, `internal_handler_name`, `internal_handler_contact`) or the iPad's "Handled By" panel silently blanks — same failure mode as rule 40. | | 79 | **`auditor` = `project_manager` access + issue management, minus delete — keep the two decorators distinct** | Auditor is added to `@project_manager_required` (PM baseline) and to every `project_manager` role check in routes/templates. Its *extra* issue powers (verify/bulk-verify/verification-queue) go through the separate `@issue_manager_required` (admin/director/auditor). Issue **delete** stays `@supervisor_required` — never add auditor there. When adding a new PM-level gate, include `auditor`; when adding a director-only or delete-level gate, do not. The three issue **delete** template gates (spaced `['admin', 'director']` in `issues/list.html` + `issues/view.html`) are deliberately left without auditor. Auditor is also in the `_ALLOWED_ROLES` set of every `app/api/*` module — a **new** API blueprint's `_ALLOWED_ROLES` must include `auditor` for PM parity. | | 80 | **Assignee dropdowns are `director`/`inspector`/`auditor` (admin removed, auditor added)** | The issue/inspection assignee `