# SHOTTRACK — Master Project Plan

**Created:** 2026-09-12  
**Last Updated:** 2026-09-12  
**Plan Version:** 1.0  

---

## Checkpoint / Resume System

Each phase has a STATUS field:
- `NOT STARTED` — nothing done yet
- `IN PROGRESS` — partially implemented
- `COMPLETE` — all acceptance criteria met, tests passing
- `BLOCKED` — cannot proceed; dependency listed

**Current Checkpoint:** Phase 3 COMPLETE → Phase 4 NOT STARTED  
**Next action:** Begin Phase 4 — OCR Worker Pipeline.

---

## Project Overview

ShotTrack is a multi-tenant SaaS PWA for plastic injection-molding factories. Helpers (machine operators) photograph a machine's digital display; the system OCRs the shot counter and auto-calculates payroll. No manual entry. No "start/end session" concept — consecutive readings define production segments automatically.

**Zero paid dependency policy:** Every library must be free/open-source. WhatsApp Business API is the only optional paid integration (off by default).

---

## Architecture Decisions (ADRs)

| # | Decision | Rationale |
|---|---|---|
| ADR-01 | Next.js 14 App Router + TypeScript | SSR for SEO-irrelevant admin panels; server actions eliminate API boilerplate for simple mutations |
| ADR-02 | Prisma + PostgreSQL | Strong typing, migrations, Decimal type for money |
| ADR-03 | Auth.js v5 with credentials provider | No third-party auth cost; JWT sessions; role in token |
| ADR-04 | pg-boss for OCR job queue | Postgres-backed; no Redis; at-least-once delivery; survives restarts |
| ADR-05 | Python OCR worker (separate process) | EasyOCR/Tesseract are Python-first; keeps Node process clean |
| ADR-06 | Money as Prisma Decimal, never JS float | Floating-point rounding is a financial bug |
| ADR-07 | Ledger immutability | LedgerTransaction rows are NEVER updated or deleted; corrections = new ADJUSTMENT rows |
| ADR-08 | Rate snapshot at calculation time | Rate/cavity/product copied onto segment at creation; never recalculated from current values |
| ADR-09 | Display Profile as data, not code | Admin draws field regions in UI; no deployment needed to add new machine type |
| ADR-10 | Idempotency-Key on every state-mutating endpoint | Safe retry on network failure; prevents duplicate ledger entries |
| ADR-11 | factoryId on every table (except platform-level entities) | Complete tenant isolation enforced at query level, not just route level |
| ADR-12 | bcryptjs (not bcrypt) | Pure JS, no native binary; simpler Docker setup |
| ADR-13 | Tailwind CSS + Radix UI primitives | No paid component library; accessible by default |
| ADR-14 | EasyOCR as primary OCR engine | Apache 2.0 license; pure Python; no binary install; better on proportional fonts |
| ADR-15 | Tesseract (secondary) for seven-segment fonts | Machine 1 cycle time font misread by EasyOCR; Tesseract + ssd traineddata required |
| ADR-16 | No "session" concept | Helpers photo any time; system pairs consecutive readings into segments automatically |
| ADR-17 | Work Hub allow-list only (not fully public) | Factories control which job workers see their requirements; privacy for capacity info |

---

## Tech Stack (Exact Versions)

| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 14.2.30 |
| Language | TypeScript | 5.6.3 |
| Auth | next-auth (Auth.js v5) | 5.0.0-beta.25 |
| ORM | Prisma | 5.22.0 |
| DB | PostgreSQL | 16 (Docker) |
| Job Queue | pg-boss | 9.0.3 |
| Validation | Zod | 3.24.2 |
| Forms | react-hook-form + @hookform/resolvers | 7.53.2 + 3.9.1 |
| Styling | Tailwind CSS | 3.4.15 |
| UI Primitives | Radix UI | various |
| Password | bcryptjs | 2.4.3 |
| OCR Primary | EasyOCR | 1.7.2 |
| OCR Secondary | Tesseract | to be installed |
| Image Processing | OpenCV (Python) | 5.0.0 |
| OCR Runtime | Python | 3.13.7 |
| Containerization | Docker + Docker Compose | — |

---

## User Roles

| Role | Scope | Access |
|---|---|---|
| SUPER_ADMIN | Platform-wide | Create/suspend factories, view all data |
| ADMIN | Single factory | All factory management, payroll, settings |
| HELPER | Single factory | Capture readings on assigned machines |
| JOB_WORKER | Cross-factory | Log daily job production, view own ledger |

---

## Phase Summary Table

| Phase | Name | Status | Blocks |
|---|---|---|---|
| 0 | OCR Feasibility Spike | **COMPLETE (CONDITIONAL GO)** | — |
| 1 | Project Foundation & Auth | NOT STARTED | All phases |
| 2 | Admin Core — Machines, Helpers, Products | **COMPLETE** | Phases 3,4,5 |
| 3 | Helper Reading Capture (PWA Camera) | **COMPLETE** | Phase 4 |
| 4 | OCR Worker Pipeline | NOT STARTED | Phase 5 |
| 5 | Production Segments & Earning Calculation | NOT STARTED | Phases 6,7 |
| 6 | Display Profile Builder (Region Editor) | NOT STARTED | Phase 4 accuracy |
| 7 | Payroll, Ledger & Payments | NOT STARTED | Phase 8 |
| 8 | Job Worker Module | NOT STARTED | Phase 9 |
| 9 | Work Hub Marketplace | NOT STARTED | — |
| 10 | OCR Hardening & Accuracy Validation | NOT STARTED | Phase 4 confidence |
| 11 | WhatsApp Notifications (optional) | NOT STARTED | — |
| 12 | Reporting & Analytics | NOT STARTED | — |

---

## PHASE 0 — OCR Feasibility Spike

**Status:** COMPLETE — CONDITIONAL GO  
**Location:** `E:\wamp64\www\shottrack-ocr-spike\`

### What was done
- Ran EasyOCR on 2 real machine photos (INJKon colour HMI + unknown-brand monochrome HMI)
- Tested 6 preprocessing variants: Otsu (A), Adaptive (B), CLAHE (C), Inverted (D), Sharpened (E), Denoised (F)
- Identified OCR accuracy per field, root causes of failures, parser bugs
- Produced `PHASE0_REPORT.md` — formal feasibility report

### Key findings
| Field | Machine 1 | Machine 2 |
|---|---|---|
| Counter | PASS — `00035` at 99.9% conf | FAIL (parser bug, not OCR) — `04947Cntl` in raw output at 68% |
| Date | FAIL (fused with time) | PASS with region crop |
| Time | PASS | PARTIAL (2s drift, acceptable) |
| Cycle time | FAIL (seven-segment font) | PARTIAL (no decimal) |

### Verdict
Counter values ARE readable by EasyOCR. Machine 2 failure was a parser regex bug (`\b` boundary on fused `04947Cntl`), not an OCR failure. Processing time 228s is spike artefact; production estimate with region crops = ~35s.

### Phase 0 files
| File | Description |
|---|---|
| `ocr_spike.py` | v1 — raw OCR diagnosis |
| `ocr_spike_v2.py` | v2 — improved parser, region crops, ground truth |
| `ocr_spike_report.json` | v1 raw EasyOCR output |
| `ocr_spike_v2_report.json` | v2 accuracy results |
| `PHASE0_REPORT.md` | Formal Phase 0 report |

### Pre-Phase 2 blockers (from Phase 0)
- [ ] Fix counter parser: remove `\b` from fused-label regex → `CNT_FUSED = re.compile(r"(?i)(cnt|count|pcs|shot|parts|total)")`
- [ ] Factory Admin confirms which counter is the payment counter on Machine 1 (`00035 Cnt` or `983.8` at bottom)
- [ ] Verify extracted values against actual machine readout

### Pre-Phase 6 blockers
- [ ] Install Tesseract (UB-Mannheim Windows installer)
- [ ] Download ssd seven-segment traineddata

### Pre-Phase 10 blockers
- [ ] Capture 20–30 additional photos per machine (varied angle, lighting, distance, time)
- [ ] Re-run spike with full fixture set to measure true accuracy

---

## PHASE 1 — Project Foundation & Auth

**Status:** IN PROGRESS (partial — 4 files exist, no src/ yet)  
**Target directory:** `E:\wamp64\www\shottrack\`

### What already exists
| File | State |
|---|---|
| `package.json` | Complete — all dependencies specified |
| `tsconfig.json` | Complete — strict mode, `@/*` path alias |
| `next.config.ts` | Complete — minimal config |
| `prisma/schema.prisma` | Complete — full 30+ table schema with all enums |

### What remains to build

#### 1a — Configuration files
- [ ] `tailwind.config.ts` — shadcn/ui CSS variable theme
- [ ] `postcss.config.mjs`
- [ ] `.env.example`
- [ ] `docker-compose.yml` — db + web services
- [ ] `.eslintrc.json`
- [ ] `Dockerfile` (for docker-compose web service)

#### 1b — Library layer (`src/lib/`)
- [ ] `src/lib/db.ts` — Prisma singleton (global cache for dev hot-reload)
- [ ] `src/lib/auth.ts` — Auth.js v5 credentials provider; role + factoryId in JWT
- [ ] `src/lib/utils.ts` — `cn()` helper (clsx + tailwind-merge)
- [ ] `src/lib/validations/auth.ts` — Zod loginSchema
- [ ] `src/types/next-auth.d.ts` — Session type augmentation (role, factoryId)

#### 1c — Middleware
- [ ] `src/middleware.ts` — Route protection by role; redirect to /login if unauthenticated
  - `/platform/*` → SUPER_ADMIN only
  - `/admin/*` → ADMIN only
  - `/helper/*` → HELPER only
  - `/jobworker/*` → JOB_WORKER only

#### 1d — App shell
- [ ] `src/app/globals.css` — Tailwind directives + shadcn/ui CSS variables
- [ ] `src/app/layout.tsx` — Root layout; Inter font; metadata
- [ ] `src/app/page.tsx` — Root redirect by role (uses `auth()` server-side)

#### 1e — Login
- [ ] `src/app/login/page.tsx` — Email + password form
- [ ] `src/app/login/actions.ts` — Server action calling `signIn()`; redirects by role on success

#### 1f — Platform panel (SUPER_ADMIN)
- [ ] `src/app/(platform)/layout.tsx` — Sidebar: Dashboard, Factories, Settings
- [ ] `src/app/(platform)/platform/page.tsx` — Dashboard: factory counts by subscription status
- [ ] `src/app/(platform)/platform/factories/page.tsx` — Factory list table
- [ ] `src/app/(platform)/platform/factories/new/page.tsx` — Create factory form + server action
- [ ] `src/app/(platform)/platform/factories/[id]/page.tsx` — Factory detail; suspend/reactivate buttons

#### 1g — Platform API routes
- [ ] `src/app/api/platform/factories/route.ts` — GET list, POST create (with Idempotency-Key + Zod)
- [ ] `src/app/api/platform/factories/[id]/route.ts` — GET detail, PATCH update status

#### 1h — Role dashboard stubs
- [ ] `src/app/(admin)/layout.tsx` — Admin sidebar
- [ ] `src/app/(admin)/admin/page.tsx` — Admin dashboard (factory name, machine/helper/reading counts)
- [ ] `src/app/(helper)/layout.tsx` — Helper layout (mobile-first)
- [ ] `src/app/(helper)/helper/page.tsx` — Helper dashboard stub
- [ ] `src/app/(jobworker)/layout.tsx` — Job worker layout
- [ ] `src/app/(jobworker)/jobworker/page.tsx` — Job worker dashboard stub

#### 1i — Seed script
- [ ] `prisma/seed.ts` — Idempotent seed: SUPER_ADMIN, 1 demo factory, ADMIN, 2 HELPERs, 2 machines, 1 mold

### Acceptance criteria
- [ ] `npm run dev` starts without errors
- [ ] `npm run build` produces no TypeScript errors
- [ ] Login page renders; correct credentials redirect to correct dashboard by role
- [ ] Wrong credentials show error message
- [ ] Unauthenticated access to `/platform` redirects to `/login`
- [ ] SUPER_ADMIN cannot access `/admin`; ADMIN cannot access `/platform`
- [ ] Factory create form creates factory + ADMIN user in one transaction
- [ ] Suspend/reactivate toggles `subscriptionStatus` and is reflected in list
- [ ] POST to factory API with duplicate Idempotency-Key returns 200 (not 409 or 500)
- [ ] `prisma/seed.ts` runs idempotently twice without errors

### Testing requirements
- Manual browser test: login flow for each of 4 roles
- Manual browser test: Platform factory CRUD
- TypeScript: `npm run build` must pass
- DB: `npx prisma validate` must pass

### Dependencies
- Node 20+ (confirmed: 20.12.2)
- Docker 25+ (confirmed: 25.0.3)
- PostgreSQL 16 (via Docker)

### Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Auth.js v5 beta breaking changes | MEDIUM | HIGH | Pin to 5.0.0-beta.25; test login flow immediately |
| Prisma adapter incompatibility with credentials | LOW | HIGH | Credentials provider doesn't use adapter for sessions — JWT strategy only |
| Windows path issues in Docker volumes | MEDIUM | MEDIUM | Use named volume not bind mount |

---

## PHASE 2 — Admin Core (Machines, Helpers, Products, Molds)

**Status:** NOT STARTED  
**Depends on:** Phase 1 complete

### Features to build

#### 2a — Machine management
- Machine list, create, edit, detail pages (Admin only, scoped to `factoryId`)
- Machine status toggle (ACTIVE / MAINTENANCE / RETIRED)
- Machine `[id]/readings` — readings history per machine

#### 2b — Helper management
- Helper list, create, edit, deactivate (Admin only)
- Each helper linked to a User account (email + initial password)
- Helper code (short identifier, unique within factory)
- Assignment: helper is not tied to a single machine — any helper can read any machine

#### 2c — Product & Mold management
- Product list, create, edit, deactivate
- Mold list, create (with cavity count), edit, deactivate
- Both scoped to `factoryId`

#### 2d — Rate configuration
- Per-helper or per-machine-mold-product combination rate (`ratePerPart`)
- Rate is set at time of segment creation (copied as snapshot, never recalculated)

#### 2e — API routes (all with factoryId scoping + Idempotency-Key)
- `/api/admin/machines/` — CRUD
- `/api/admin/helpers/` — CRUD + user account creation
- `/api/admin/products/` — CRUD
- `/api/admin/molds/` — CRUD

### Acceptance criteria
- [ ] Admin can create/edit/deactivate machines, helpers, products, molds
- [ ] All queries filter by `factoryId` from session — no cross-tenant data leakage
- [ ] Creating a helper auto-creates a linked User with HELPER role
- [ ] Deactivated machines/helpers are hidden from active lists but preserved in history
- [ ] Duplicate Idempotency-Key on POST returns same created record, no duplicate rows

### Testing requirements
- Manual: Create machine M1 in Factory A; verify ADMIN of Factory B cannot see M1
- Manual: Create helper, log in as helper, verify dashboard shows correct factory context
- TypeScript build must pass

### Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Rate configuration complexity | MEDIUM | Start with simple per-helper rate; per-product/mold rate in Phase 5 refinement |

---

## PHASE 3 — Helper Reading Capture (PWA Camera)

**Status:** NOT STARTED  
**Depends on:** Phase 2 complete

### Features to build

#### 3a — Camera interface (mobile-first PWA)
- `src/app/(helper)/helper/read/page.tsx` — Take reading page
- Camera access via `getUserMedia()` (client component)
- Machine selector (dropdown of active machines in factory)
- "Capture" button → preview photo → "Submit" or "Retake"
- Photo compressed client-side before upload (max 1600px wide, quality 0.85)
- `capturedAt` timestamp recorded at moment of capture (client time)

#### 3b — Photo upload
- Upload to local filesystem (Phase 3) or S3-compatible store (Phase 10+)
- API route: `POST /api/helper/readings` — multipart form (photo + machineId + capturedAt)
- Server creates `Reading` record with `OCRStatus = PENDING`
- Enqueues OCR job via pg-boss
- Returns `readingId` immediately; helper sees "Processing..." status

#### 3c — Reading history
- Helper can see last 10 readings with status (PENDING / APPROVED / REJECTED)
- Shows counter value (once OCR complete), date, time, machine name

#### 3d — Framed capture guide (Section 21 of spec)
- Overlay grid/frame on camera preview to help center the display
- Text hint: "Hold camera steady, align display in frame"

### Acceptance criteria
- [ ] Helper can take a photo from mobile browser; photo is uploaded; reading record created
- [ ] `capturedAt` is the client-side capture time (not server receive time)
- [ ] `OCRStatus` starts as PENDING; reading appears in helper's history immediately
- [ ] Photo is stored in `public/uploads/readings/` (or configured storage path)
- [ ] API validates machineId belongs to helper's factory (tenant check)
- [ ] Idempotency-Key prevents duplicate readings on retry

### Testing requirements
- Test on mobile Chrome: camera opens, photo taken, upload succeeds
- Test on desktop: file picker fallback works
- Test network retry: same Idempotency-Key → same readingId returned, no duplicate DB row

### Risks
| Risk | Impact | Mitigation |
|---|---|---|
| `getUserMedia` HTTPS requirement | HIGH | Use Next.js dev HTTPS or test on local network via IP |
| Large photo files slow upload | MEDIUM | Client-side resize before upload |
| Offline/poor signal in factory | MEDIUM | Retry with Idempotency-Key; show "Uploading..." with spinner |

---

## PHASE 4 — OCR Worker Pipeline

**Status:** NOT STARTED  
**Depends on:** Phase 3 (reading records + photo files) + Phase 0 fixes

### Features to build

#### 4a — pg-boss job queue setup
- `src/lib/queue.ts` — pg-boss singleton (one worker process, not Next.js request handler)
- `ocr-worker/` directory — standalone Node.js/Python bridge process
- `ocr-worker/index.ts` — subscribes to `ocr-jobs` queue; spawns Python on each job
- Job payload: `{ readingId, photoPath, machineId, factoryId }`

#### 4b — Python OCR script (`ocr-worker/ocr_engine.py`)
Based on Phase 0 findings:
- **Counter extraction** (3-strategy approach):
  1. Fused box: `CNT_FUSED = re.compile(r"(?i)(cnt|count|pcs|shot|parts|total)")` — strip label, keep leading digits
  2. Adjacent boxes: digit box within ±2 positions of label-only box
  3. Standalone 4–6 digit box not near non-counter words (blocklist: Scrw, Mold, RPM, Kwh, Run Hours)
- **Date extraction**: strict 6-digit DDMMYY, or region crop if Display Profile available
- **Time extraction**: HH:MM:SS pattern with separator tolerance
- **Cycle time**: proportional font via EasyOCR; seven-segment via Tesseract+ssd (Phase 6+)
- **Preprocessing variants**: D_inverted for dark-bg displays; original colour for colour HMIs
- **Multi-variant voting**: agree on most-common value; ratio = confidence boost
- **Confidence thresholds**:
  - Counter ≥ 0.85 → AUTO_APPROVED
  - Counter 0.50–0.84 → PENDING_REVIEW
  - Counter < 0.50 → FAILED (triggers admin review flag)

#### 4c — Result writing
- Worker writes `OCRResult` row with rawBoxes, extractedFields, engineUsed, processingMs
- Updates `Reading`: counterValue, machineDate, machineTime, cycleTimeSec, ocrStatus, ocrConfidence, confidenceLevel
- Reading auto-approved if counter confidence ≥ 0.85 AND overall ≥ 0.80
- Triggers segment creation (Phase 5) on auto-approve

#### 4d — Admin OCR review UI
- Admin can see PENDING_REVIEW readings with photo + OCR result
- Can approve (with or without editing extracted value), reject, or request retake

### Acceptance criteria
- [ ] Submitting a reading enqueues an OCR job
- [ ] Worker processes job: Python runs, result saved, reading status updated
- [ ] Counter ≥ 85% confidence → reading auto-approved, segment triggered
- [ ] Counter 50–84% → reading status = PENDING_REVIEW, admin notified
- [ ] Counter < 50% → reading status = FAILED
- [ ] Worker handles Python crash gracefully (job retried via pg-boss)
- [ ] OCR result (raw boxes JSON) stored in `ocr_results` table for audit
- [ ] Admin can view photo alongside OCR boxes in review UI

### Testing requirements
- Test with known photo: verify correct counter value extracted
- Test worker crash recovery: kill Python mid-job; verify pg-boss retries
- Test confidence thresholds: verify correct status transitions
- Manual review: admin approves/rejects a PENDING_REVIEW reading

### Risks
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Python process startup time | HIGH | MEDIUM | Keep Python process warm (long-running worker, not spawned per-job) |
| EasyOCR model download on first run | MEDIUM | LOW | Pre-download in Dockerfile |
| Counter parser word-boundary bug (Phase 0 finding) | KNOWN | HIGH | Fix CNT_FUSED regex before Phase 4 starts |
| False positives on complex HMI backgrounds | MEDIUM | HIGH | NON_COUNTER_WORDS blocklist; region crops from Display Profile (Phase 6) |

---

## PHASE 5 — Production Segments & Earning Calculation

**Status:** NOT STARTED  
**Depends on:** Phase 4 (auto-approved readings)

### Core business logic

#### Segment auto-creation rule
When Reading N is approved on Machine M:
1. Find the most recent previously approved Reading on Machine M (Reading N-1)
2. Create a `ProductionSegment`:
   - `startReadingId` = N-1, `endReadingId` = N
   - `shotsProduced` = N.counterValue − N-1.counterValue
   - If shots < 0 → counter reset detected → flag for admin review (void segment, admin resolves)
   - `partsProduced` = shotsProduced × cavitySnapshot
   - `earnedAmount` = partsProduced × rateSnapshot (Decimal arithmetic only)
   - `rateSnapshot`, `cavitySnapshot`, `productSnapshot` copied at creation, never updated
3. Segment status = OPEN initially; auto-approved if OCR confidence was HIGH

#### 5a — Segment service
- `src/lib/segments.ts` — `createSegmentFromReading(readingId)` — called by OCR worker on auto-approve
- Transaction: segment creation + ledger entry in a single DB transaction
- Idempotency: check for existing segment with same startReadingId before creating

#### 5b — Ledger entry creation
- On segment approval: create `LedgerTransaction` (type = EARNING)
- `balanceAfter` = previous balance + earnedAmount
- `LedgerTransaction` rows are NEVER modified or deleted

#### 5c — Admin segment review UI
- List of segments by machine/helper/date range
- Approve, dispute, void (with reason) — each action creates an ADJUSTMENT ledger entry if needed
- Shows: machine, helper, start/end readings, shots, parts, earned amount, status

#### 5d — Counter reset handling
- If N.counterValue < N-1.counterValue → likely counter reset or machine change
- Auto-void that segment + flag for admin review
- Admin can override: specify manual shots count

### Acceptance criteria
- [ ] Approving Reading N auto-creates segment (N-1 → N) with correct shot count
- [ ] `partsProduced` = shots × cavity count (from mold snapshot)
- [ ] `earnedAmount` = parts × rate — computed with Decimal arithmetic, no float
- [ ] Ledger EARNING transaction created atomically with segment approval
- [ ] Counter reset (counterValue drops) → segment auto-voided, flagged for admin
- [ ] Duplicate segment creation blocked by idempotency check
- [ ] Admin can approve/dispute/void segments from UI

### Testing requirements
- Unit test: segment calculation with known inputs (shots × cavity × rate = expected earnedAmount)
- Unit test: counter reset detection
- Integration test: reading approval → segment created → ledger entry exists
- Manual: approve 3 consecutive readings; verify 2 segments with correct values

### Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Race condition: two readings approved simultaneously | HIGH | DB transaction + unique constraint on (startReadingId) |
| Counter reset misdetected | MEDIUM | Flag threshold configurable per machine |
| Decimal precision loss | HIGH | Always use Prisma Decimal, never Number for money |

---

## PHASE 6 — Display Profile Builder (Region Editor)

**Status:** NOT STARTED  
**Depends on:** Phase 2 (machine management)  
**Pre-requisite:** Tesseract installed (for seven-segment support)

### Features to build

#### 6a — Calibration photo upload
- Admin uploads a reference photo of the machine display
- Stored as `CalibrationSample`

#### 6b — Interactive region editor
- Canvas overlay on the reference photo
- Admin draws bounding boxes for: Counter, Date, Time, Cycle Time
- Each box stored as normalized coordinates (x, y, w, h) in `OCRRegion`
- Per-region settings: labelAliases, preprocessVariant, ocrEngine, confidenceThreshold

#### 6c — Test run
- "Test OCR on this photo" button
- Sends calibration photo through OCR with the new Display Profile
- Shows extracted values alongside expected values
- Admin approves or adjusts regions

#### 6d — Production use
- When OCR worker processes a reading, it loads the machine's Display Profile
- Crops each region before passing to OCR engine
- Much faster (crop ~200×50px instead of 1600×1200) and more accurate

### Acceptance criteria
- [ ] Admin can draw regions on a machine photo via canvas UI
- [ ] Regions saved as normalized coordinates (0.0–1.0)
- [ ] "Test" button runs OCR on calibration photo and shows result
- [ ] OCR worker uses Display Profile regions if available
- [ ] Processing time with Display Profile < 35s (vs 228s full-image spike)
- [ ] Seven-segment cycle time handled by Tesseract+ssd on region crop

### Testing requirements
- Manual: Draw regions for Machine 1 (INJKon); run test; verify counter reads `00035`
- Manual: Draw regions for Machine 2; run test; verify counter reads `04947`
- Performance: measure processing time with regions vs. without

### Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Canvas API incompatibility on older Android | MEDIUM | Use progressive enhancement; fallback to coordinate input |
| Region coordinates drift between photos | MEDIUM | Normalized 0.0–1.0 reduces impact; framed capture guide helps |

---

## PHASE 7 — Payroll, Ledger & Payments

**Status:** NOT STARTED  
**Depends on:** Phase 5 (segments + ledger transactions)

### Features to build

#### 7a — Helper payroll panel (Admin view)
- List helpers with: total earned this period, total paid, outstanding balance
- Date range filter
- Per-helper ledger: all EARNING + PAYMENT + ADJUSTMENT transactions
- Print-friendly payslip

#### 7b — Payment recording
- Admin records cash payment: `POST /api/admin/payments`
- Creates `Payment` record + `LedgerTransaction` (type = PAYMENT)
- Amount deducted from balance
- Idempotency-Key prevents duplicate payment records

#### 7c — Advance recording
- Admin records advance: `LedgerTransaction` (type = ADVANCE, negative balance effect)
- Deducted from next payroll

#### 7d — Adjustment recording
- Admin can create manual ADJUSTMENT ledger entry (positive or negative) with note
- Never modifies existing entries

#### 7e — Helper's own ledger view
- Helper can see their own transaction history and current balance
- Read-only; no payment actions

### Acceptance criteria
- [ ] Admin sees correct outstanding balance per helper (sum of EARNING - PAYMENT - ADVANCE)
- [ ] Payment recording creates both Payment row and LedgerTransaction atomically
- [ ] Duplicate Idempotency-Key on payment → returns existing payment, no duplicate ledger entry
- [ ] Helper can view own ledger but cannot see other helpers' data
- [ ] Print-friendly payslip renders correctly

### Testing requirements
- Unit test: balance calculation across EARNING + PAYMENT + ADVANCE + ADJUSTMENT entries
- Manual: record payment; verify balance decreases by exact amount
- Manual: record same payment with same Idempotency-Key twice; verify single deduction

### Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Balance calculation off due to float | HIGH | Always sum Decimal; never cast to Number |
| Concurrent payment recording | MEDIUM | DB transaction; idempotency key |

---

## PHASE 8 — Job Worker Module

**Status:** NOT STARTED  
**Depends on:** Phase 7 (ledger foundation)

### Features to build

#### 8a — Job creation (Admin)
- Admin creates Job: title, machine, mold, product, ratePerPart, targetQuantity
- Job assigned to a JobWorker

#### 8b — Daily production logging (Job Worker)
- Job worker logs daily output: date, partsProduced
- System calculates earnedAmount = partsProduced × rateSnapshot
- Creates `JobDailyProduction` + EARNING ledger entry (on admin approval)

#### 8c — Job worker ledger
- Same ledger structure as helpers: EARNING, PAYMENT, ADVANCE, ADJUSTMENT
- Separate from helper ledger; payeeType = JOB_WORKER

#### 8d — Admin approval
- Admin reviews daily production submissions
- Approve → ledger entry created; Reject → no ledger entry

### Acceptance criteria
- [ ] Job worker can log daily production
- [ ] Admin approves; `earnedAmount` calculated correctly with rate snapshot
- [ ] Job worker ledger balance updates correctly
- [ ] Job worker cannot see other job workers' ledger data

---

## PHASE 9 — Work Hub Marketplace

**Status:** NOT STARTED  
**Depends on:** Phase 8 (job worker accounts exist)

### Features to build

#### 9a — Requirements posting (Admin)
- Factory posts a WorkHubRequirement: skills needed, quantity, rate, deadline
- `isPublic` flag controls visibility; `allowListIds` for specific factories
- Not visible to all by default (privacy of production capacity)

#### 9b — Offer submission (Job Worker)
- Job worker browses requirements they're allowed to see (allow-list)
- Submits WorkHubOffer with cover note + proposed rate

#### 9c — Offer acceptance (Admin)
- Admin accepts one offer (concurrency-safe: DB transaction checks only one accepted per requirement)
- Accepted offer → creates Job linking the job worker to the factory
- Other pending offers → auto-rejected on acceptance

#### 9d — Factory capability profile
- Factory fills in capability profile (machine types, specialisms, capacity)
- Used by marketplace to match requirements with capable factories

### Acceptance criteria
- [ ] Requirement with `isPublic=false` not visible to job workers not on allowList
- [ ] Accepting an offer is idempotent; concurrent accepts only succeed for one
- [ ] Accepted offer creates a Job; other offers marked REJECTED atomically

---

## PHASE 10 — OCR Hardening & Accuracy Validation

**Status:** NOT STARTED  
**Depends on:** Phase 4 (OCR pipeline running); Phase 0 blockers resolved  
**Trigger:** After 20–30 real photos captured per machine

### Features to build

#### 10a — Calibration sample collection
- `CalibrationSample` records link photo + ground truth + OCR result
- Admin marks samples as validated (ground truth confirmed against physical display)

#### 10b — Accuracy benchmark runner
- Script re-runs OCR on all validated calibration samples
- Reports per-field accuracy, per-variant accuracy, per-machine accuracy
- Target: counter accuracy ≥ 95% across all validated samples

#### 10c — Tesseract seven-segment integration
- Integrate Tesseract + ssd traineddata for Machine 1 cycle time
- Region crop the cycle time area before passing to Tesseract

#### 10d — Counter parser fix (from Phase 0)
- Remove `\b` from CNT_FUSED regex: `re.compile(r"(?i)(cnt|count|pcs|shot|parts|total)")`
- Verified by calibration sample re-run

#### 10e — Confidence threshold tuning
- Adjust auto-approve / review / retake thresholds based on real-world accuracy data
- Machine-specific threshold overrides in Display Profile

### Acceptance criteria
- [ ] Counter accuracy ≥ 95% on validated calibration sample set (20+ photos per machine)
- [ ] Seven-segment cycle time readable via Tesseract+ssd on Machine 1
- [ ] CNT_FUSED parser fix: Machine 2 counter (`04947Cntl`) correctly extracts `04947`
- [ ] Benchmark script runnable without manual steps

---

## PHASE 11 — WhatsApp Notifications (Optional)

**Status:** NOT STARTED  
**Depends on:** WhatsApp Business API credentials (paid, off by default)

### Events to notify
- Helper: reading submitted, segment approved
- Admin: reading needs review, low balance alert, payment processed
- Job Worker: job assigned, offer accepted

### Architecture
- `WhatsAppMessageLog` records queued/sent messages
- Worker processes queue; calls WhatsApp Business API
- Feature flag in SystemSetting: `whatsapp_enabled = "false"` by default

---

## PHASE 12 — Reporting & Analytics

**Status:** NOT STARTED  
**Depends on:** Phases 5, 7, 8 complete

### Features
- Factory-level production report: shots/day by machine
- Helper earnings report: period earnings, payment history
- Machine utilization: operating hours, average cycle time
- Raw material consumption tracking
- Export: CSV, print-friendly

---

## Global Risks

| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| OCR accuracy insufficient on real factory photos | MEDIUM | CRITICAL | Phase 0 CONDITIONAL GO; Phase 10 validates at scale; admin review catches misses |
| Ledger balance discrepancy | LOW | CRITICAL | Immutable ledger; Decimal arithmetic; idempotency on all writes |
| Cross-tenant data leak | LOW | CRITICAL | factoryId on every query from session; middleware route protection |
| Seven-segment font on Machine 1 | HIGH | MEDIUM | Non-payment field; Tesseract+ssd in Phase 10; does not block Phase 1–9 |
| Auth.js v5 beta instability | MEDIUM | HIGH | Pin exact version; test after every npm install |
| Large photo files in production | MEDIUM | MEDIUM | Client-side resize to 1600px max; consider object storage in Phase 10+ |
| pg-boss job loss on DB restart | LOW | MEDIUM | pg-boss at-least-once delivery; idempotent OCR handler |
| Windows dev / Linux prod path differences | MEDIUM | MEDIUM | Docker normalizes paths; use forward slashes in code |

---

## Implementation Order (Recommended)

```
Phase 1 → Phase 2 → Phase 3 → Phase 4 → Phase 5 → Phase 6 → Phase 7 → Phase 8 → Phase 9 → Phase 10 → Phase 11 → Phase 12
```

**Critical path:** 1 → 2 → 3 → 4 → 5 → 7 (core earning loop)  
**OCR accuracy path:** Phase 0 fixes → Phase 4 → Phase 6 → Phase 10  
**Work Hub path:** Phase 8 → Phase 9

---

## Feature Checklist (Summary)

### Phase 1
- [ ] Next.js 14 App Router project scaffold
- [ ] Full Prisma schema (30+ tables) — schema EXISTS, needs `npm install` + `db:push`
- [ ] Auth.js v5 credentials provider (4 roles)
- [ ] Tenant isolation middleware
- [ ] Platform panel: factory CRUD, suspend/reactivate
- [ ] Role-based dashboard routing
- [ ] Docker Compose (db + web)
- [ ] Seed script

### Phase 2
- [ ] Machine CRUD (Admin)
- [ ] Helper CRUD + user account creation (Admin)
- [ ] Product & Mold CRUD (Admin)
- [ ] Rate configuration

### Phase 3
- [ ] PWA camera interface (Helper)
- [ ] Photo upload + Reading record creation
- [ ] Reading history (Helper)
- [ ] Framed capture guide

### Phase 4
- [ ] pg-boss queue setup
- [ ] Python OCR worker (EasyOCR, 6 variants, counter/date/time extraction)
- [ ] Counter parser fix (remove `\b` boundary bug)
- [ ] Auto-approve / review / fail thresholds
- [ ] Admin OCR review UI

### Phase 5
- [ ] Segment auto-creation from approved readings
- [ ] Shot/parts/earned calculation (Decimal)
- [ ] Ledger EARNING entry creation
- [ ] Counter reset detection
- [ ] Admin segment review UI

### Phase 6
- [ ] Calibration photo upload
- [ ] Interactive region editor (canvas)
- [ ] Test OCR on calibration photo
- [ ] OCR worker uses Display Profile regions

### Phase 7
- [ ] Helper payroll panel (Admin)
- [ ] Payment recording (Admin)
- [ ] Advance recording
- [ ] Adjustment entry
- [ ] Helper ledger view

### Phase 8
- [ ] Job creation (Admin)
- [ ] Daily production logging (Job Worker)
- [ ] Admin approval → EARNING ledger
- [ ] Job Worker ledger

### Phase 9
- [ ] WorkHub requirement posting
- [ ] Offer submission
- [ ] Concurrency-safe offer acceptance
- [ ] Factory capability profile

### Phase 10
- [ ] Calibration sample benchmark runner
- [ ] Tesseract seven-segment integration
- [ ] CNT_FUSED parser fix verified
- [ ] Confidence threshold tuning

### Phase 11
- [ ] WhatsApp notification events
- [ ] Feature flag (off by default)

### Phase 12
- [ ] Production reports
- [ ] Earnings reports
- [ ] CSV export

---

## Current Starting Point (Phase 1 — Remaining Work)

**Files that already exist in `E:\wamp64\www\shottrack\`:**

| File | Status |
|---|---|
| `package.json` | ✅ Complete |
| `tsconfig.json` | ✅ Complete |
| `next.config.ts` | ✅ Complete |
| `prisma/schema.prisma` | ✅ Complete (30+ tables, all enums) |

**Files that need to be created to complete Phase 1:**
- `tailwind.config.ts`, `postcss.config.mjs`
- `.env.example`, `docker-compose.yml`
- `src/lib/db.ts`, `src/lib/auth.ts`, `src/lib/utils.ts`
- `src/lib/validations/auth.ts`
- `src/types/next-auth.d.ts`
- `src/middleware.ts`
- `src/app/globals.css`, `src/app/layout.tsx`, `src/app/page.tsx`
- `src/app/login/page.tsx`, `src/app/login/actions.ts`
- `src/app/(platform)/layout.tsx` + 4 platform pages
- `src/app/api/platform/factories/route.ts` + `[id]/route.ts`
- `src/app/(admin)/layout.tsx` + admin dashboard stub
- `src/app/(helper)/layout.tsx` + helper dashboard stub
- `src/app/(jobworker)/layout.tsx` + jobworker dashboard stub
- `prisma/seed.ts`

**First command after plan approval:**
```bash
cd E:/wamp64/www/shottrack && npm install
```
Then write all remaining Phase 1 files.

---

*ShotTrack Master Project Plan — v1.0 — 2026-09-12*
