# 10 - RouteTrack Pi — Shift Mode (SQLite + Flask API + Dashboard Controls)

#### **Date:** December 25, 2025  
**Category:** Raspberry Pi / GPS / SQLite / Flask / Leaflet  
**Backlink:** [09 – RouteTrack Pi — Dashboard Autostart (Gunicorn + systemd)](https://docs.natenetworks.com/books/06-raspberry-pi-python-linux-tips/page/09-routetrack-pi-dashboard-autostart-gunicorn-systemd)

---

## Project Goal

This phase introduces **Shift Mode** to RouteTrack.

Shift Mode allows RouteTrack to track work sessions independently of calendar days, which is essential for a portable, vehicle-mounted system that:

- Is powered down frequently
- Moves between locations
- Does not follow strict midnight-to-midnight boundaries
- Needs accurate per-shift metrics (hours, stops, time-on-site)

The dashboard now supports a simple workflow: **Start Shift → Drive → End Shift**

---

## Why Shift Mode Matters

Daily summaries work well for reporting, but they don’t match real-world truck usage:

- Overnight work can cross calendar boundaries
- Reboots/power loss interrupt sessions
- Short test runs clutter daily totals

Shift Mode solves this by creating a clean “session boundary” that the user controls.

---

## Database Changes

### New `shifts` Table

A new SQLite table stores shift metadata independently of GPS data.

**Table:** `shifts`

```sql
<span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> shifts <span class="token punctuation">(</span>
  id <span class="token keyword">INTEGER</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> AUTOINCREMENT<span class="token punctuation">,</span>
  start_ts <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span>
  end_ts <span class="token keyword">TEXT</span><span class="token punctuation">,</span>
  note <span class="token keyword">TEXT</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">CREATE</span> <span class="token keyword">INDEX</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> idx_shifts_start_ts
  <span class="token keyword">ON</span> shifts<span class="token punctuation">(</span>start_ts<span class="token punctuation">)</span><span class="token punctuation">;</span>

```

**Design Notes:**

- `start_ts` and `end_ts` stored as UTC ISO-8601 strings
- `end_ts` stays `NULL` while a shift is active
- Only one active shift allowed at a time
- Lightweight, isolated table to minimize lock contention

---

## Applying the Schema Safely

Because GPS logging writes constantly to SQLite, stop the logger before applying schema changes.

```bash
sudo systemctl stop routetrack-logger.service
sqlite3 /opt/routetrack/data/routetrack.sqlite < /opt/routetrack/config/schema.sql
sqlite3 /opt/routetrack/data/routetrack.sqlite ".tables"
sudo systemctl start routetrack-logger.service

```

Expected tables:

```
daily_summary  gps_points  shifts  stop_events

```

---

## Flask API Enhancements (Full `app.py`)

Shift Mode is implemented via additional Flask API endpoints.

### New Endpoints

<table id="bkmrk-method-endpoint-purp"><thead><tr><th>Method</th><th>Endpoint</th><th>Purpose</th></tr></thead><tbody><tr><td>GET</td><td>`/api/shift/active`</td><td>Returns the active shift (if any)</td></tr><tr><td>POST</td><td>`/api/shift/start`</td><td>Starts a new shift</td></tr><tr><td>POST</td><td>`/api/shift/end`</td><td>Ends the active shift</td></tr><tr><td>GET</td><td>`/api/shift/summary`</td><td>Returns live stats for the active shift</td></tr></tbody></table>

### Replace `/opt/routetrack/web/app.py`

Edit:

```bash
sudo nano /opt/routetrack/web/app.py

```

Paste the full file:

```python
<span class="token comment">#!/usr/bin/env python3</span>
<span class="token triple-quoted-string string">"""
RouteTrack Local Dashboard (Flask)
----------------------------------

Provides:
- Web UI page (Leaflet map)
- JSON API endpoints:
  - /api/summary/<date>
  - /api/points/<date>
  - /api/stops/<date>

Shift Mode endpoints:
  - GET  /api/shift/active
  - POST /api/shift/start
  - POST /api/shift/end
  - GET  /api/shift/summary

Notes:
- This dashboard is READ-ONLY for GPS-derived tables:
    gps_points, stop_events, daily_summary
- Shift Mode DOES write to SQLite, but only into the "shifts" table.
  This avoids lock contention with the logger and keeps writes minimal.
"""</span>

<span class="token keyword">import</span> sqlite3
<span class="token keyword">from</span> datetime <span class="token keyword">import</span> datetime<span class="token punctuation">,</span> timezone

<span class="token keyword">from</span> flask <span class="token keyword">import</span> Flask<span class="token punctuation">,</span> jsonify<span class="token punctuation">,</span> render_template<span class="token punctuation">,</span> request

DB_PATH <span class="token operator">=</span> <span class="token string">"/opt/routetrack/data/routetrack.sqlite"</span>

app <span class="token operator">=</span> Flask<span class="token punctuation">(</span>__name__<span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Open SQLite connection with Row output so we can jsonify results
    via dict(row).
    """</span>
    conn <span class="token operator">=</span> sqlite3<span class="token punctuation">.</span>connect<span class="token punctuation">(</span>DB_PATH<span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>row_factory <span class="token operator">=</span> sqlite3<span class="token punctuation">.</span>Row
    <span class="token keyword">return</span> conn


<span class="token keyword">def</span> <span class="token function">utc_now_iso</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Return current UTC timestamp in ISO-8601 format (no microseconds).
    Example: 2025-12-25T16:05:00+00:00
    """</span>
    <span class="token keyword">return</span> datetime<span class="token punctuation">.</span>now<span class="token punctuation">(</span>timezone<span class="token punctuation">.</span>utc<span class="token punctuation">)</span><span class="token punctuation">.</span>replace<span class="token punctuation">(</span>microsecond<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">.</span>isoformat<span class="token punctuation">(</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">index</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Serve the dashboard HTML page (Leaflet UI)."""</span>
    <span class="token keyword">return</span> render_template<span class="token punctuation">(</span><span class="token string">"index.html"</span><span class="token punctuation">)</span>


<span class="token comment"># ============================================================</span>
<span class="token comment"># Existing Daily Views (READ-ONLY)</span>
<span class="token comment"># ============================================================</span>

<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/summary/<day>"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_summary</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Return the daily_summary row for YYYY-MM-DD."""</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"SELECT * FROM daily_summary WHERE date = ?"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>day<span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> <span class="token keyword">not</span> row<span class="token punctuation">:</span>
        <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"No summary for this date"</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">404</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/points/<day>"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_points</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Return route points for a given day as a list of [lat, lon]
    suitable for drawing a Leaflet polyline.
    """</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    start <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>day<span class="token punctuation">}</span></span><span class="token string">T00:00:00Z"</span></span>
    end <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>day<span class="token punctuation">}</span></span><span class="token string">T23:59:59Z"</span></span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT ts, lat, lon
        FROM gps_points
        WHERE ts >= ? AND ts <= ?
          AND mode = 3
          AND lat IS NOT NULL
          AND lon IS NOT NULL
        ORDER BY ts
    """</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>start<span class="token punctuation">,</span> end<span class="token punctuation">)</span><span class="token punctuation">)</span>

    rows <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchall<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">[</span>r<span class="token punctuation">[</span><span class="token string">"lat"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> r<span class="token punctuation">[</span><span class="token string">"lon"</span><span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token keyword">for</span> r <span class="token keyword">in</span> rows<span class="token punctuation">]</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/stops/<day>"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_stops</span><span class="token punctuation">(</span>day<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Return stop events that START on a given day.
    """</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    start <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>day<span class="token punctuation">}</span></span><span class="token string">T00:00:00Z"</span></span>
    end <span class="token operator">=</span> <span class="token string-interpolation"><span class="token string">f"</span><span class="token interpolation"><span class="token punctuation">{</span>day<span class="token punctuation">}</span></span><span class="token string">T23:59:59Z"</span></span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT start_ts, end_ts, duration_seconds, lat, lon
        FROM stop_events
        WHERE start_ts >= ? AND start_ts <= ?
        ORDER BY start_ts
    """</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>start<span class="token punctuation">,</span> end<span class="token punctuation">)</span><span class="token punctuation">)</span>

    rows <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchall<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token builtin">dict</span><span class="token punctuation">(</span>r<span class="token punctuation">)</span> <span class="token keyword">for</span> r <span class="token keyword">in</span> rows<span class="token punctuation">]</span><span class="token punctuation">)</span>


<span class="token comment"># ============================================================</span>
<span class="token comment"># Shift Mode (writes only to shifts table)</span>
<span class="token comment"># ============================================================</span>

<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/shift/active"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_shift_active</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Returns the currently active shift (where end_ts is NULL),
    or {"active": false} if none exists.
    """</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT *
        FROM shifts
        WHERE end_ts IS NULL
        ORDER BY id DESC
        LIMIT 1
    """</span><span class="token punctuation">)</span>
    row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> <span class="token keyword">not</span> row<span class="token punctuation">:</span>
        <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"active"</span><span class="token punctuation">:</span> <span class="token boolean">False</span><span class="token punctuation">}</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/shift/start"</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"POST"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_shift_start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Start a new shift.
    Prevents multiple active shifts at once.

    Optional JSON body:
      {"note": "optional note here"}
    """</span>
    note <span class="token operator">=</span> <span class="token string">""</span>
    <span class="token keyword">try</span><span class="token punctuation">:</span>
        payload <span class="token operator">=</span> request<span class="token punctuation">.</span>get_json<span class="token punctuation">(</span>silent<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
        note <span class="token operator">=</span> payload<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">"note"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token keyword">or</span> <span class="token string">""</span>
    <span class="token keyword">except</span> Exception<span class="token punctuation">:</span>
        note <span class="token operator">=</span> <span class="token string">""</span>

    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token comment"># Block starting a shift if one is already active</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"SELECT id FROM shifts WHERE end_ts IS NULL LIMIT 1"</span><span class="token punctuation">)</span>
    <span class="token keyword">if</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
        conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"A shift is already active."</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">409</span>

    start_ts <span class="token operator">=</span> utc_now_iso<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span>
        <span class="token string">"INSERT INTO shifts (start_ts, note) VALUES (?, ?)"</span><span class="token punctuation">,</span>
        <span class="token punctuation">(</span>start_ts<span class="token punctuation">,</span> note<span class="token punctuation">)</span>
    <span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>commit<span class="token punctuation">(</span><span class="token punctuation">)</span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"SELECT * FROM shifts WHERE id = last_insert_rowid()"</span><span class="token punctuation">)</span>
    row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">(</span>row<span class="token punctuation">)</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/shift/end"</span><span class="token punctuation">,</span> methods<span class="token operator">=</span><span class="token punctuation">[</span><span class="token string">"POST"</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_shift_end</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    End the currently active shift by setting end_ts.
    """</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT *
        FROM shifts
        WHERE end_ts IS NULL
        ORDER BY id DESC
        LIMIT 1
    """</span><span class="token punctuation">)</span>
    row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> <span class="token keyword">not</span> row<span class="token punctuation">:</span>
        conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"No active shift."</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">404</span>

    end_ts <span class="token operator">=</span> utc_now_iso<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"UPDATE shifts SET end_ts = ? WHERE id = ?"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>end_ts<span class="token punctuation">,</span> row<span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>commit<span class="token punctuation">(</span><span class="token punctuation">)</span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token string">"SELECT * FROM shifts WHERE id = ?"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>row<span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">,</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
    updated <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>
    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token builtin">dict</span><span class="token punctuation">(</span>updated<span class="token punctuation">)</span><span class="token punctuation">)</span>


<span class="token decorator annotation punctuation">@app<span class="token punctuation">.</span>route</span><span class="token punctuation">(</span><span class="token string">"/api/shift/summary"</span><span class="token punctuation">)</span>
<span class="token keyword">def</span> <span class="token function">api_shift_summary</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""
    Returns a lightweight summary for the ACTIVE shift window.

    Current output:
      - shift window (start -> now)
      - number of gps points in that window (mode=3)
      - stop count + stopped seconds for stop_events inside window

    NOTE:
      This does not compute miles yet. That comes next.
    """</span>
    conn <span class="token operator">=</span> db<span class="token punctuation">(</span><span class="token punctuation">)</span>
    cur <span class="token operator">=</span> conn<span class="token punctuation">.</span>cursor<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token comment"># Find active shift</span>
    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT *
        FROM shifts
        WHERE end_ts IS NULL
        ORDER BY id DESC
        LIMIT 1
    """</span><span class="token punctuation">)</span>
    shift <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">if</span> <span class="token keyword">not</span> shift<span class="token punctuation">:</span>
        conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span><span class="token string">"error"</span><span class="token punctuation">:</span> <span class="token string">"No active shift."</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">404</span>

    start_ts <span class="token operator">=</span> shift<span class="token punctuation">[</span><span class="token string">"start_ts"</span><span class="token punctuation">]</span>
    end_ts <span class="token operator">=</span> utc_now_iso<span class="token punctuation">(</span><span class="token punctuation">)</span>  <span class="token comment"># "now" for active shift</span>

    <span class="token comment"># gps_points stores timestamps with trailing "Z"</span>
    <span class="token comment"># shifts stores timestamps with "+00:00"</span>
    <span class="token comment"># Convert bounds for gps_points query</span>
    start_bound <span class="token operator">=</span> start_ts<span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">"+00:00"</span><span class="token punctuation">,</span> <span class="token string">"Z"</span><span class="token punctuation">)</span>
    end_bound <span class="token operator">=</span> end_ts<span class="token punctuation">.</span>replace<span class="token punctuation">(</span><span class="token string">"+00:00"</span><span class="token punctuation">,</span> <span class="token string">"Z"</span><span class="token punctuation">)</span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT COUNT(*) as point_count
        FROM gps_points
        WHERE ts >= ? AND ts <= ?
          AND mode = 3
          AND lat IS NOT NULL
          AND lon IS NOT NULL
    """</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>start_bound<span class="token punctuation">,</span> end_bound<span class="token punctuation">)</span><span class="token punctuation">)</span>
    point_row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>

    cur<span class="token punctuation">.</span>execute<span class="token punctuation">(</span><span class="token triple-quoted-string string">"""
        SELECT COUNT(*) as stop_count,
               COALESCE(SUM(duration_seconds), 0) as stopped_s
        FROM stop_events
        WHERE start_ts >= ? AND end_ts <= ?
    """</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>start_ts<span class="token punctuation">,</span> end_ts<span class="token punctuation">)</span><span class="token punctuation">)</span>
    stop_row <span class="token operator">=</span> cur<span class="token punctuation">.</span>fetchone<span class="token punctuation">(</span><span class="token punctuation">)</span>

    conn<span class="token punctuation">.</span>close<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">return</span> jsonify<span class="token punctuation">(</span><span class="token punctuation">{</span>
        <span class="token string">"shift_id"</span><span class="token punctuation">:</span> shift<span class="token punctuation">[</span><span class="token string">"id"</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
        <span class="token string">"start_ts"</span><span class="token punctuation">:</span> start_ts<span class="token punctuation">,</span>
        <span class="token string">"end_ts"</span><span class="token punctuation">:</span> end_ts<span class="token punctuation">,</span>
        <span class="token string">"points"</span><span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">(</span>point_row<span class="token punctuation">[</span><span class="token string">"point_count"</span><span class="token punctuation">]</span> <span class="token keyword">or</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string">"stop_count"</span><span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">(</span>stop_row<span class="token punctuation">[</span><span class="token string">"stop_count"</span><span class="token punctuation">]</span> <span class="token keyword">or</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string">"stopped_time_seconds"</span><span class="token punctuation">:</span> <span class="token builtin">int</span><span class="token punctuation">(</span>stop_row<span class="token punctuation">[</span><span class="token string">"stopped_s"</span><span class="token punctuation">]</span> <span class="token keyword">or</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
        <span class="token string">"note"</span><span class="token punctuation">:</span> shift<span class="token punctuation">[</span><span class="token string">"note"</span><span class="token punctuation">]</span> <span class="token keyword">or</span> <span class="token string">""</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span>


<span class="token keyword">if</span> __name__ <span class="token operator">==</span> <span class="token string">"__main__"</span><span class="token punctuation">:</span>
    <span class="token comment"># Local dev run (manual)</span>
    app<span class="token punctuation">.</span>run<span class="token punctuation">(</span>host<span class="token operator">=</span><span class="token string">"0.0.0.0"</span><span class="token punctuation">,</span> port<span class="token operator">=</span><span class="token number">5000</span><span class="token punctuation">,</span> debug<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>

```

Make executable:

```bash
sudo chmod +x /opt/routetrack/web/app.py

```

Restart dashboard service:

```bash
sudo systemctl restart routetrack-dashboard.service

```

---

## Dashboard UI Enhancements (Full `index.html`)

The dashboard top bar now includes Shift controls:

- Start Shift
- End Shift
- Refresh Shift
- Shift status pill

A new **Active Shift** section shows live stats and refreshes every 30 seconds.

### Replace `/opt/routetrack/web/templates/index.html`

Edit:

```bash
sudo nano /opt/routetrack/web/templates/index.html

```

Paste the full file:

```html
<span class="token doctype"><span class="token punctuation"><!</span><span class="token doctype-tag">doctype</span> <span class="token name">html</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>html</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>head</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">charset</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>utf-8<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>RouteTrack Dashboard<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>meta</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>viewport<span class="token punctuation">"</span></span> <span class="token attr-name">content</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>width=device-width, initial-scale=1<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>

  <span class="token comment"><!-- Leaflet (CDN) --></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span>
    <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span>
    <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://unpkg.com/leaflet@1.9.4/dist/leaflet.css<span class="token punctuation">"</span></span>
  <span class="token punctuation">/></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://unpkg.com/leaflet@1.9.4/dist/leaflet.js<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>style</span><span class="token punctuation">></span></span><span class="token style"><span class="token language-css">
    <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 0<span class="token punctuation">;</span> <span class="token property">font-family</span><span class="token punctuation">:</span> Arial<span class="token punctuation">,</span> sans-serif<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">#topbar</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> #111<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token property">display</span><span class="token punctuation">:</span> flex<span class="token punctuation">;</span> <span class="token property">gap</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token property">align-items</span><span class="token punctuation">:</span> center<span class="token punctuation">;</span> <span class="token property">flex-wrap</span><span class="token punctuation">:</span> wrap<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">#topbar button</span> <span class="token punctuation">{</span> <span class="token property">cursor</span><span class="token punctuation">:</span> pointer<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">#map</span> <span class="token punctuation">{</span> <span class="token property">height</span><span class="token punctuation">:</span> 70vh<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">#stats</span> <span class="token punctuation">{</span> <span class="token property">padding</span><span class="token punctuation">:</span> 10px<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">.row</span> <span class="token punctuation">{</span> <span class="token property">margin</span><span class="token punctuation">:</span> 6px 0<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">code</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> #eee<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 2px 4px<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 4px<span class="token punctuation">;</span> <span class="token punctuation">}</span>
    <span class="token selector">.pill</span> <span class="token punctuation">{</span> <span class="token property">display</span><span class="token punctuation">:</span> inline-block<span class="token punctuation">;</span> <span class="token property">padding</span><span class="token punctuation">:</span> 2px 8px<span class="token punctuation">;</span> <span class="token property">border-radius</span><span class="token punctuation">:</span> 999px<span class="token punctuation">;</span> <span class="token property">font-size</span><span class="token punctuation">:</span> 12px<span class="token punctuation">;</span> <span class="token property">background</span><span class="token punctuation">:</span> #333<span class="token punctuation">;</span> <span class="token property">color</span><span class="token punctuation">:</span> #fff<span class="token punctuation">;</span> <span class="token punctuation">}</span>
  </span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>style</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>head</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>body</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>topbar<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>RouteTrack<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> — Local Dashboard

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><span class="token punctuation">></span></span>|<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><span class="token punctuation">></span></span>Date:<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>input</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>day<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>date<span class="token punctuation">"</span></span> <span class="token punctuation">/></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loadAll()<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Load Day<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span><span class="token punctuation">></span></span>|<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>startShift()<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Start Shift<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>endShift()<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>End Shift<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>button</span> <span class="token attr-name">onclick</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>loadShift()<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Refresh Shift<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>button</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>span</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>shiftStatus<span class="token punctuation">"</span></span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>pill<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Shift: Unknown<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>span</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>map<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>

  <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stats<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h3</span><span class="token punctuation">></span></span>Active Shift<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>shift<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h3</span><span class="token punctuation">></span></span>Daily Summary<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>summary<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>

    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h3</span><span class="token punctuation">></span></span>Stops<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h3</span><span class="token punctuation">></span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>stops<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>script</span><span class="token punctuation">></span></span>
  // Default date = today (browser local time)
  const dayInput = document.getElementById("day");
  dayInput.valueAsDate = new Date();

  const shiftDiv = document.getElementById("shift");
  const shiftStatus = document.getElementById("shiftStatus");

  const map = L.map("map").setView([38.7153, -89.94], 13);

  L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
    maxZoom: 19,
    attribution: "<span class="token entity named-entity" title="©">&copy;</span> OpenStreetMap contributors"
  }).addTo(map);

  let routeLine = null;
  let stopMarkers = [];

  // -----------------------------
  // Day-Based Views
  // -----------------------------
  async function loadAll() {
    const day = dayInput.value;
    await loadRoute(day);
    await loadStops(day);
    await loadSummary(day);
  }

  async function loadRoute(day) {
    const res = await fetch(`/api/points/${day}`);
    const pts = await res.json();

    if (routeLine) map.removeLayer(routeLine);
    if (!pts.length) return;

    routeLine = L.polyline(pts, { weight: 4 }).addTo(map);
    map.fitBounds(routeLine.getBounds());
  }

  async function loadStops(day) {
    stopMarkers.forEach(m => map.removeLayer(m));
    stopMarkers = [];

    const res = await fetch(`/api/stops/${day}`);
    const stops = await res.json();

    const stopsDiv = document.getElementById("stops");
    stopsDiv.innerHTML = "";

    if (!Array.isArray(stops) || !stops.length) {
      stopsDiv.innerHTML = "<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>row<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>No stops found.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>";
      return;
    }

    stops.forEach(s => {
      const durMin = Math.round(s.duration_seconds / 60);
      stopsDiv.innerHTML += `<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
        Stop: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${s.start_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span> → <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${s.end_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span>
        (${durMin} min)
      <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>`;

      if (s.lat && s.lon) {
        const m = L.marker([s.lat, s.lon]).addTo(map)
          .bindPopup(`Stop (${durMin} min)<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>br</span><span class="token punctuation">></span></span>${s.start_ts}`);
        stopMarkers.push(m);
      }
    });
  }

  async function loadSummary(day) {
    const summaryDiv = document.getElementById("summary");
    summaryDiv.innerHTML = "";

    const res = await fetch(`/api/summary/${day}`);
    const data = await res.json();

    if (data.error) {
      summaryDiv.innerHTML = `<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>No summary for ${day}. Run processor first.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>`;
      return;
    }

    summaryDiv.innerHTML = `
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Start: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${data.start_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>End: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${data.end_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Distance: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>${data.total_distance_miles}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> miles<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Moving: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>${Math.round(data.moving_time_seconds/60)}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> minutes<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Stopped: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>${Math.round(data.stopped_time_seconds/60)}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> minutes<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>Stops: <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>${data.stop_count}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
    `;
  }

  // -----------------------------
  // Shift Mode
  // -----------------------------
  async function startShift() {
    const res = await fetch("/api/shift/start", {
      method: "POST",
      headers: {"Content-Type": "application/json"},
      body: JSON.stringify({ note: "" })
    });

    const data = await res.json();
    if (!res.ok) {
      alert(data.error || "Failed to start shift");
      return;
    }
    await loadShift();
  }

  async function endShift() {
    const res = await fetch("/api/shift/end", { method: "POST" });
    const data = await res.json();
    if (!res.ok) {
      alert(data.error || "Failed to end shift");
      return;
    }
    await loadShift();
  }

  async function loadShift() {
    shiftDiv.innerHTML = "";

    const activeRes = await fetch("/api/shift/active");
    const active = await activeRes.json();

    if (!active || active.active === false || !active.id) {
      shiftStatus.textContent = "Shift: Inactive";
      shiftDiv.innerHTML = "<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>row<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>No active shift. Click <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Start Shift<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> to begin.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>";
      return;
    }

    shiftStatus.textContent = `Shift: ACTIVE (#${active.id})`;

    const sumRes = await fetch("/api/shift/summary");
    const s = await sumRes.json();

    if (s.error) {
      shiftDiv.innerHTML = `<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">'</span>row<span class="token punctuation">'</span></span><span class="token punctuation">></span></span>${s.error}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>`;
      return;
    }

    shiftDiv.innerHTML = `
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Shift ID:<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> ${s.shift_id}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Start (UTC):<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${s.start_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Now (UTC):<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>code</span><span class="token punctuation">></span></span>${s.end_ts}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>code</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Stops (inside window):<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> ${s.stop_count}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>Stopped Minutes:<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> ${Math.round(s.stopped_time_seconds / 60)}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>row<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>strong</span><span class="token punctuation">></span></span>GPS Points (mode=3):<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>strong</span><span class="token punctuation">></span></span> ${s.points}<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
    `;
  }

  // Auto-load on page open
  loadAll();
  loadShift();

  // Auto-refresh active shift every 30s (handy for truck use)
  setInterval(loadShift, 30000);
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>script</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>body</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>html</span><span class="token punctuation">></span></span>

```

Restart dashboard:

```bash
sudo systemctl restart routetrack-dashboard.service

```

---

## Validation &amp; Testing

Before starting a shift:

```bash
curl http://localhost:5000/api/shift/active

```

Expected:

```json
{"active":false}

```

```bash
curl http://localhost:5000/api/shift/summary

```

Expected:

```json
{"error":"No active shift."}

```

Dashboard validation:

- Start Shift creates an active session
- Refresh Shift updates live metrics
- End Shift closes session cleanly
- Status pill returns to “Shift: Inactive”

---

## Result

RouteTrack now supports:

- Continuous GPS logging
- Stop detection + daily summaries
- Local dashboard (Flask + Leaflet)
- **Shift Mode with user-controlled Start/End**
- Live shift summary panel in the UI

This moves RouteTrack closer to a true **truck-ready route tracking + session logging system**.

---

## Next Steps

Now that Shift Mode works end-to-end, next upgrades will add:

1. **Shift mileage + moving time**
    
    
    - Apply Haversine logic inside shift window
2. **Persist final shift totals**
    
    
    - Save a shift summary row when ending a shift
3. **Shift history**
    
    
    - List past shifts and export (CSV/GeoJSON)
4. Optional: offline map tiles