# Field-to-Model: Correction Data Pipeline

> **Status:** Discussion draft — no implementation started, no code changes made.
> **Scope:** Mobile app → dedicated ingestion server. Retraining itself is out of scope (see [Out of scope](#out-of-scope)).

## Why this exists

The app already lets a harvester correct the AI's grading — change a class, remove a false detection, or draw a box around a bunch the model missed. That correction is never destructive: the AI's original call is kept, and the human's edit sits alongside it (see the `history` / `corrections` tables in `lib/services/database_helper.dart`).

That pairing — *what the model said* next to *what was actually true* — is exactly the labeled data needed to train the next generation of the model. Today it's trapped in a local SQLite database on each harvester's phone and goes nowhere. This document proposes a pipeline to collect it centrally, without adding a single step to the harvester's workflow.

## Agreed principles

Three calls have already been made in discussion; everything downstream follows from them.

1. **A dedicated, standalone ingestion server.** Not bolted onto `server-desktop` or any operational service. If it's slow or down, nothing a harvester relies on in the field is affected — the data simply queues on-device until it's reachable again.
2. **Fully silent, background sync.** No "sync now" button, no progress bar, no prompt. The harvester's job is to scan and correct; getting that data to us is entirely the app's problem, running only when connectivity allows.
3. **The app tracks its own upload state.** Every record knows locally whether it's been sent. No handshake with the server is needed to know what's still pending — a local flag is the source of truth, which makes the whole thing resumable after any interruption.

## System architecture

The phone is the only place data is created. Everything after that is designed so a slow or offline server never blocks a harvester from scanning.

```mermaid
flowchart LR
  H(["Harvester"]) --> P["Phone: scan + correct"]
  P --> L[("Local SQLite\nscans + corrections\n+ upload state")]
  L -- "background sync\nWi-Fi / connectivity trigger" --> S["Dedicated Ingestion Server"]
  S --> R[("Raw Store\nimages + AI result + correction")]
  R --> T["Retraining Pipeline\n(offline, periodic — future work)"]
  T --> M["New Model Version"]
  M -.-> P
```

The dashed link back to the phone is deliberately out of scope for this document — how a new model version reaches devices is a separate rollout question for later.

## Sync flow, step by step

The harvester only ever sees the left-most lane. Everything else happens whenever the phone next has connectivity, with no user-visible state.

```mermaid
sequenceDiagram
  participant H as Harvester
  participant App as App
  participant DB as Local DB
  participant Sync as Background Sync
  participant Srv as Ingestion Server

  H->>App: Scan bunch, correct a grade
  App->>DB: Save record (uploadedAt = none)
  Note right of DB: Sits queued.<br/>Nothing for the harvester to do.
  loop Whenever the OS wakes it
    Sync->>DB: Which records are unsynced?
    alt connectivity available
      Sync->>Srv: Upload batch (image + AI result + correction)
      Srv-->>Sync: Acknowledged
      Sync->>DB: Mark uploadedAt = now
    else no connectivity
      Sync->>Sync: Do nothing, try again next wake
    end
  end
```

One edge case worth naming: a harvester can correct a scan *after* it already uploaded — reviewing a batch a few days later, say. So "synced" needs to track the correction separately from the original scan, or a late correction silently never makes it off the device.

## What actually gets sent

Per scan, the payload pairs the model's original call with whatever the harvester changed it to — that pairing is the entire point of the exercise.

| Field | Where it comes from | Why it matters for training |
|---|---|---|
| `image` | Camera capture | The actual training input |
| `ai_detections[]` | On-device inference | The model's original boxes, classes, confidence — what it got right or wrong |
| `human_correction[]` | Harvester review | The corrected boxes/classes, or a removed false positive — the actual label |
| `model_version` | App build | Which model produced the AI call, so improvement can be measured generation over generation |
| `captured_at` | App | Chronological + seasonal context for the dataset |
| `device_id` | App | Coarse debugging / dedup, not tied to a personal identity |
| `uploaded_at` | Sync engine, local only | Never sent — this is the idempotency marker that makes sync resumable |

## Open decisions

Three things still need a call before this can move from brainstorm to plan.

| Question | Leaning | Trade-off | |
|---|---|---|---|
| Wi-Fi only, or any connection? | Wi-Fi only to start | Slower data arrival, but no surprise mobile-data cost for a harvester who never asked for this | **Open** |
| Upload every scan, or only corrected ones? | Every reviewed scan | More storage, but "the AI was right, no correction needed" is itself a useful training signal, not noise | **Open** |
| Silent upload, or an opt-in toggle? | Depends on device ownership | Opt-in adds friction and drop-off; silent upload only sits right if these are company-managed devices | **Open** |
| Dedicated, standalone server | — | Keeps ingestion downtime from ever touching the app's core detection features | **Decided** |
| Fully background, no harvester-facing sync UI | — | Zero added burden on the harvester's workflow | **Decided** |

## Risks & mitigations

- **Ingestion server is down or slow.** No effect on scanning or correcting — records simply stay queued locally and upload on the next successful sync. This is the entire reason it's a separate server.
- **Bandwidth cost on a harvester's personal data plan.** Addressed by the Wi-Fi-only decision above, once confirmed.
- **A correction made after the scan already synced gets missed.** Track correction-sync state separately from scan-sync state, so an edit made days later still gets picked up on the next background pass.
- **Raw storage grows indefinitely.** Not a phone-side concern, but worth flagging for the server design — periodic archival into a proper training dataset, rather than an ever-growing inbox.

## Out of scope

The app's responsibility ends at **reliably getting corrected data to the server**. Aggregating uploads into a training dataset, running retraining, evaluating a new model, and distributing it back to devices are separate initiatives — worth planning, but deliberately not decided here.

---

*Companion presentation version (with rendered diagrams) prepared as a shareable artifact for stakeholder review.*
