merge v0.12.0 #51
Reference in New Issue
Block a user
Delete Branch "dev"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The per-unit Gantt chart on /unit/{id} (Phase 1, v0.11.0) was scoped to one unit's deployment timeline. This adds the fleet-wide view as a new entry under /tools. What it shows - 12-month calendar grid styled like the Job Planner (4 months per row, responsive down to single column on mobile). - Each day cell shows up to 4 colored mini-bars — one per project that had ≥1 active UnitAssignment that day, color deterministically hashed from project_id. Days with >4 active projects show "+N". - KPI strip at the top: project count, distinct unit count, total assignment count in the window. - Collapsible project legend: ordered by first-active date (which matches the deployment-history reading order), each row links to the project page, shows the assignment count. Click-a-day side panel - Click any populated day cell → slide-over panel from the right - Groups by project, lists every (unit, location) active that day - Per-deployment: unit link, location link, window dates, active / closed badge, "auto-backfilled" tag for metadata_backfill source - Sources from a new GET /api/admin/deployment-history/day endpoint Navigation - Prev / Next month buttons shift the 12-month window by one month - "Recent" button jumps back to default (12 months ending now) - Default window is 11 months back from current month — operator sees the recent past on first load, not future emptiness Files - backend/services/deployment_history.py — data builder + day-detail helper. Walks UnitAssignment windows, intersects with the 12-month range, computes per-project active-day sets. - backend/routers/deployment_history.py — page route + day-detail JSON endpoint. Wired into main.py. - templates/admin/deployment_history.html — page + side-panel - templates/tools.html — new card linking to the page Phase 3 (deferred): drag-to-resize bars to retroactively adjust assignment windows from inside the calendar; per-unit row view (complement to the project-row view) for "where has unit X been across all jobs"; horizontal scroll for >12-month windows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>The map sidebar that replaced Upcoming Actions on the project overview is now also on the deeper Vibration tab — operators get the same spatial context when they drill into vibration monitoring locations. Refactor - New partial templates/partials/projects/location_map.html. Self-contained: includes the map div + a self-fetch script that pulls coords from /api/projects/{p}/locations-json on load. Accepts: - project_id (required) - map_height (default "320px") - location_type ('vibration' | 'sound' | none = all) - project_dashboard.html: ~150 lines of inline map JS deleted, replaced with {% include 'partials/projects/location_map.html' %}. Identical behavior, less duplication. - projects/detail.html Vibration tab: locations list converted to a 2/3 + 1/3 grid; right column hosts the same map partial filtered to location_type=vibration with a taller 450px viewport. Bidirectional hover-highlight (card ↔ pin) works on both surfaces since the partial registers its own document-level mouseover/mouseout handlers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>The Calendar grid (day-cells with project bars) is great for seeing which projects had activity on a given day, but bad for seeing how long any single deployment lasted. The Gantt view inverts that — one row per project, horizontal bars per assignment window — so an operator can read durations at a glance. Service layer - backend/services/deployment_history.py extends each project's payload with `bars`: a list of {unit_id, location_id, location_name, start, end, is_active, source} for every UnitAssignment clipped to the visible 12-month window. Location names are batch-resolved. Same cost as before since the underlying assignment scan is the same; just additional data in the response. Template - Tab switcher at the top of /tools/deployment-history toggles between Calendar and Gantt views. URL hash (#gantt) preserves the active view across month-nav (Prev / Next / Recent buttons within the Gantt view link to ?...#gantt to stay on the same tab). - Gantt view is a plain SVG with: - Left 220px label gutter: project color dot + truncated name, whole row clickable → opens the project page - Right area: horizontal time axis with month gridlines + labels, "today" dashed orange line, one row per project - One bar per assignment in that row, colored by project, reduced opacity for closed assignments, blue outline for metadata- backfilled assignments, white tip on the right edge of active bars - Hover any bar → tooltip with unit + location + window - Alternating row backgrounds for readability. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Third view on /tools/deployment-history. Where 'Gantt by Project' has one row per project showing that project's deployments, 'Gantt by Unit' inverts it — one row per seismograph, bars colored by the project the unit was deployed to. The natural use case: "where has BE11529 been across all my jobs?" Spotting unit rotation patterns, idle gaps, and concurrent assignments gets immediate visually. Service - deployment_history.get_deployment_history_data() now also returns a `units` array. Each unit dict carries: {id, bars[], first_active, assignment_count, any_active} Each bar has the project_name + project_color baked in so the renderer can paint by job without a second lookup. - Units sorted: currently-active first, then by first_active ascending. UI - Third tab "Gantt by Unit" added next to Calendar / Gantt by Project. - Tab switcher refactored to a small registry (_DH_TABS) so adding more views in the future is a one-line addition. - URL hash sync now supports #gantt and #byunit; nav buttons preserve the active tab across month-paging. - SVG layout: 160px label gutter (smaller than the project Gantt's 220px since unit IDs are short), 32px row height, green dot for units with at least one active deployment. Unit ID is clickable → /unit/{id}; each bar is clickable → /projects/{p}. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>Field-install workflow needs to be fast: arrive on site, snap a photo of the seismograph in place, leave. Project / location classification happens later at a desk. This adds the data model + capture endpoint for that workflow. Data model - New PendingDeployment table. Lifecycle: awaiting → assigned (when promoted to a real UnitAssignment) or → cancelled (operator's mistake). Photos are filesystem files under data/photos/{unit_id}/ with the filename stored on the row. - Migration: backend/migrate_add_pending_deployments.py (idempotent). Endpoints - POST /api/deployments/capture — multipart upload (unit_id, photo, optional note). Refuses non-seismographs. Extracts EXIF GPS (cribbing extract_exif_data from routers/photos.py) and stores the captured "lat,lon" on the row. Saves the photo under data/photos/{unit_id}/install_YYYYMMDD_HHMMSS_<uuid8>.<ext>. Returns the new pending_deployment_id + extracted coords + photo URL for the client to render confirmation. - GET /api/deployments/pending — list by status (default awaiting) - GET /api/deployments/pending/{id} — single row detail - POST /api/deployments/pending/{id}/promote — classify → create UnitAssignment. Body accepts two shapes: assign-to-existing-location OR create-new-location (with new-or-existing project). Sets status=assigned, resulting_assignment_id, promoted_at. - POST /api/deployments/pending/{id}/cancel — abandon with optional reason. All four routes write UnitHistory audit rows (pending_deployment_captured / _promoted / _cancelled). Events from a unit with an unclassified pending deployment land in the unit's "Unattributed" events bucket as usual. Once promoted, the new UnitAssignment's window retroactively attributes them — same mechanism the metadata-backfill tool uses. Seismograph-only for v1. SLM deployments don't follow this pattern and are tracked elsewhere. Capture refuses non-seismograph unit_ids with HTTP 400. UI (commits 2 + 3) lands next. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>UI for the pending-deployment workflow (commits 2 + 3 from the plan, landed together since commit 1 already shipped the full backend). New surfaces - /deploy — mobile-first 3-step wizard. Pick unit → take photo (uses <input capture="environment"> so it opens the phone camera) → add optional note + submit. EXIF GPS auto-extracted on the server. Success page shows the captured coords + links to either "Deploy another" or "View pending hopper." Whole flow is meant to take under 90 seconds on site. - /tools/pending-deployments — the hopper. Filter pills: Awaiting / Assigned / Cancelled. Each card shows photo thumbnail, unit serial link, captured-at timestamp, coordinates, operator note, and status-appropriate actions. - Classify modal on the hopper: two modes — "Assign to existing location" (project + location pickers, scoped to vibration_monitoring) or "Create new location" (with new-or-existing project, plus a "use captured coords" checkbox that writes the pending row's coords onto the new location). Calls /pending/{id}/promote on submit. - Cancel button uses prompt() for the optional reason → POSTs to /pending/{id}/cancel. Backend additions - GET /api/deployments/seismograph-picker — JSON list of non-retired seismograph units for the /deploy unit picker. Annotates each unit with has_pending so the picker can flag units that already have a pending capture waiting. Discovery - New "Field Deploy" + "Pending Deployments" cards on /tools. - Dashboard banner: auto-shows when there are awaiting captures, polled every 30s. Hides when count drops to 0. Click → /tools/ pending-deployments. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>The photo input had `capture="environment"` which forces mobile browsers to open the camera and skip the "Photo Library" / "Choose File" options. Useful when you're literally at the install site, problematic when you took the photo earlier and want to upload it now from your gallery. Removed the attribute. Most mobile browsers now present a chooser ("Take Photo", "Photo Library", "Choose File"). EXIF extraction works identically either way — the server doesn't care whether the file came from the camera or the gallery. Hint copy updated to reflect both options. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>