Tracker v2.11 — System Architecture

Subsystems, communication pathways & feedback loops · CHEMA Twilight Operations
CHEMA Twilight Operations 4,600+ lines · 7 tabs · 3 data sources Built 2026-05
01 Master Map 02 Data Ingestion 03 State Computation 04 KPI Fallback Chains 05 DOP Module 06 Timeline Module 07 Planning Inputs 08 On-Floor Feedback Loop 09 v2.11 Fixes
01

Master Architecture Map

The tracker is a single-file browser application (~4,600 lines). It has no server; all computation runs client-side. Five subsystems communicate through a shared in-memory DATA store and a persistent IndexedDB (IDB). User inputs flow from planning fields and file uploads into parsers, which populate DATA, which drives every rendered UI surface.
Full system — data flow left → right, feedback right → left
FILE SOURCES PARSERS DATA STORE COMPUTE LAYER UI SURFACES COORDINATOR SOR Export TMS · -T/-D/-N variants SOR-US-CHEMA-2026-* Hub Summary iGate · per-belt rows Hub Summary (N).xlsx Employee Summary iGate · per-employee rows Employee Summary (N).xlsx CURE Export Cube utilization · dest rows CURE-*.xlsx Planning Inputs hub-plan-pph-input hub-plan-vol-input dop-start / dop-end → localStorage persist IndexedDB sort_history · hub_snapshots TDB · cure_history parseSORStaffing() Summary sheet all fields rows · empTotals · areaHours parseHubSummary() Belt rows · excludedRows normalizeBelt() per row parseEmpSummary() Per-employee scan rows pkgs · hours · PPH parseCURE() Dest rows · highTfcs · gaps dateRange · utilization DATA.sor rows · empTotals · areaHours summary.planned/actual sortDate · ts DATA.hub rows · excludedRows · ts Belt keyed by normalizeBelt() DATA.emp rows · ts Employee-level scan stats DATA.cure rows · highTfcsRows · gapRows dateRange PLAN OVERRIDES _hubPlanPPH() → number _hubPlanVol() → number TIMELINE.snaps Keyed by file idx N {hub, emp, sor, ts} renderHubKpiGrid() 8 KPI tiles · fallback chains netVol / PPH / paidDay / smalls _dopState() Aggregates all metrics → state obj → render fns renderIGateScanMetrics() Per-belt PPH · color bands _igatePerUserRows() renderTimelineCharts() 3 Chart.js charts · snapshot series _activateLatestSnapshot() renderVolumeReconciliation() SOR vs iGate cross-check Belt-level variance table _buildSnapshotRecord() Flatten DATA → IDB record TDB.put('sort_history', …) Hub Tab — KPI Grid 8 tiles · stripe color · sub text DOP Module Pace · Scenarios · Back-Solve iGate Scan Metrics Belt table · PPH bands Timeline Tab 3 charts · snapshot table Vol Reconciliation SOR ↔ iGate delta table History Tab Sort-over-sort IDB records Area / Employee Tabs Zone breakdown · emp table renderAreaKpiGrid() Coordinator reads & acts INPUTS Plan PPH · Plan Vol Sort window UPLOADS SOR · Hub · Emp periodic mid-sort DECISIONS Staff up/cut Pace corrections SAVE TO IDB Post-sort · History feeds next-day planning ← coordinator inputs Plan PPH · adjusts sort window · uploads next snapshot
SOR / TMS data path
iGate Hub path
iGate Employee path
CURE path
Planning input path
DATA store output
Rendered UI → coordinator
IDB persistence
02

Data Ingestion Pipeline

All data enters through a single drag-drop handler — onFileDrop() — which classifies the file by name pattern and routes it to the right parser. Every parser is a Promise; once resolved, its output is written directly into the DATA global and the relevant render functions are called.
FunctionInput fileOutput to DATAKey fields extracted
parseSORStaffing(file)SOR-US-CHEMA-*.xlsxDATA.sor.rows
DATA.sor.empTotals
DATA.sor.areaHours
DATA.sor.summary
DATA.sor.sortDate
Staffing rows by empId+area; per-area hour totals; Summary sheet planned/actual dict (Volume, PPH, Flow Per Hour, % Smalls, Paid Day, Hours, Sort Start, Sort Down, Sort Span)
parseHubSummary(file)Hub Summary (N).xlsxDATA.hub.rows
DATA.hub.excludedRows
Per-belt: LooseOutbound, BagsLinked, Gross Volume, Bypass BagsLinked, Holdover BagsLinked. Belt names normalized via normalizeBelt(). BUILDING/AUDITS/PD13HVD/INBUILD/TOTAL rows excluded.
parseEmpSummary(file)Employee Summary (N).xlsxDATA.emp.rowsPer-employee: empId, pkgs, hours, derived PPH, Last Scan timestamp. Employee ID taxonomy: 7-digit numeric or 10-char alphanumeric = human; DWS 1–40, return belts 101–104, bulk 401–404 = synthetic (filtered in scan metrics).
parseCURE(file)CURE-*.xlsxDATA.cure.rows
DATA.cure.highTfcsRows
DATA.cure.gapRows
Per-destination: Pkg Cube, Avg Net, Avg Equip Cap → Util % = (Pkg Cube × Avg Net) / Avg Equip Cap × 100. Rows with TFCS ≥ 95% flagged separately.
File ordering contract: The number in the filename suffix (e.g. Hub Summary (19).xlsx) is the snapshot index — higher = later. Base file (no number) is index 0. _parseFilenameIdx(name) extracts this. The Timeline module uses these indices to build a chronological sort without relying on file-system timestamps.
sortDate source: Always derived from the filename via _inferSortDateFromFile() — never from cell B1 which uses =TODAY() and would be unreliable. Pattern: 2026-05-04 from SOR-US-CHEMA-2026-05-04-T.xlsx.
03

State Computation Layer

Two parallel state aggregators exist: renderHubKpiGrid() computes display-ready metrics for the Hub tab KPI tiles, and _dopState() computes an identical metric set used by the DOP module. Both read the same DATA store and both now route planPPH and planVol through the user-input helpers added in v2.11. The duplication is intentional — the KPI grid renders tiles while _dopState() returns a state object for further computation downstream.
FunctionCallsReturns / Side effect
renderHubKpiGrid()_hubPlanPPH(), _hubPlanVol(), _kpiCard(), _kpiCardMag(), _kpiStripeFor(), _kpiFmt()Writes innerHTML of #hub-kpi-grid. 8 tiles: Net Volume, Actual PPH, Paid Day, Cost/Piece, Total Hours, Headcount, Flow/Hour (or Process Rate), % Smalls (with iGate magnitude).
_dopState()_hubPlanPPH(), _hubPlanVol(), _dopSpan(), _igatePerUserRows()Returns state object: {hubLoaded, sorLoaded, empLoaded, netVol, planVol, pph, planPPH, paidDay, planPD, totalHrs, planHrs, hc, planHC, flowHr, planFlow, pSmalls, planSm, sortSpan, planSpan, cppActual, cppPlan, igAvgPPH, igEmps, sortDate}. This object is passed to all DOP render functions.
dopRenderAll(force)_dopState(), _dopRenderPace(), _dopComputePhase(), _dopRenderPhaseChip(), _dopRenderPhaseDetail(), _dopRenderScenarios(), _dopRenderBackSolve(), _dopUpdateChrome(), renderHubKpiGrid()Master DOP refresh. Called on every file upload, every clear, every tab switch to hub, and every 30-second clock tick.
renderIGateScanMetrics()_igatePerUserRows(), _kpiStripeFor()Hub tab iGate table: per-belt gross vol, PPH with color bands (red <70 / amber 70–99 / green 100–149 / blue ≥150).
renderVolumeReconciliation()DATA.sor, DATA.hubCross-check table: SOR reported vs iGate LooseOutbound+BagsLinked per belt, delta column, coverage %.
04

KPI Metric Fallback Chains

Every metric has a priority chain: the authoritative source is tried first; if it returns zero or null (which mid-sort SOR exports do for most fields), the next source is used. This means the tracker shows meaningful numbers at every stage of the sort — not just post-sort when TMS finalizes the SOR.
Priority chain for each KPI metric — top = highest priority
Net Volume Plan PPH Total Hours Flow / Hour % Smalls Paid Day ① SOR actual summary.actual['Volume'] if non-zero → ② iGate Hub sum Σ LooseOutbound rows always available mid-sort ① User input hub-plan-pph-input if empty → ② null (stripe-neutral) SOR planned PPH ignored (per-employee, not Adj Bldg) Auto-suggest on SOR load: if input empty AND SOR planned 70–200 → pre-fill ① SOR actual summary.actual['Hours'] if non-zero → ② Σ areaHours DATA.sor.areaHours dict always present with SOR ① SOR actual summary.actual['Flow Per Hour'] if non-zero → ② Process Rate netVol ÷ paidDay tile label switches to "Process Rate" ① SOR actual summary.actual['% Smalls'] if non-zero → ② iGate SLS rows Σ(SLS01+SLS02+SS) LooseOutbound+BagsLinked ÷ netVol Tile: count (big) + % (secondary) ① SOR actual summary.actual['Paid Day'] if non-zero → ② Derived: totalHrs ÷ headcount computed from areaHours + distinct empIds
05

DOP Module — Pace, Scenarios & Back-Solve

The DOP (Daily Operations Plan) module is the primary operational decision surface. It lives inside the Hub tab and refreshes every 30 seconds via _dopStartTick(). The module's entry point is dopRenderAll(force), which builds a fresh state object and dispatches to four sub-renderers.
DOP module call graph and decision branches
dopRenderAll(force) trigger: upload · clear · tab switch · 30s tick _dopState() aggregates DATA → state object _dopComputePhase(state) Phase 0/1/2/3 · pctOfPlan thresholds _dopRenderPace(state) → #dop-pace Historical guard: sortDate ≠ today? YES → show static completed-sort summary NO → live burn rate · sort-down ETA Vol anomaly: netVol > planVol×1.2 → warn burnNow = netVol / elapsedH _dopRenderPhaseChip() + _dopRenderPhaseDetail() → #dop-phase-chip · #dop-phase-banner Phase 0: pre-induction (<5%) Phase 1: ramp (5–50%) Phase 2: steady (50–85%) Phase 3: close (≥85%) _dopRenderScenarios(state) → #dop-scenarios Current / +1% / +5% / +10% PPH Phase-aware remaining hours Each scenario: projected final vol vs planVol → on-plan / early / late _dopRenderBackSolve(state) → #dop-backsolve Q1: Burn to hit plan? (pkgs/hr) Q2: PPH lift needed? (+Δ PPH) Q3: Staffing implication? All phase-aware · uses state.planPPH _dopStartTick() 30s interval · hub tab active
06

Timeline Module — Intra-Sort Snapshot Pipeline

The Timeline tab accepts a bulk upload of all mid-sort snapshot files at once — any combination of Hub Summary (N), Employee Summary (N), and SOR (N) files. The number suffix N is the snapshot index. The pipeline classifies, parses, chronologically orders, and renders them as three Chart.js charts. The critical v2.11 fix: the SOR snapshot's summary object now carries both the full {planned, actual} dict (for KPI grid use) and the flat timeline fields (paidDayActual, sortDownActual, etc.) in a single merged object.
Timeline pipeline — file drop to Chart.js
1. BATCH DROP 2. CLASSIFY 3. PARSE + MERGE 4. ORDER + ACTIVATE 5. RENDER CHARTS 6. PUSH TO DATA onTlFiles(fileList) onTlDrop() onTlDragOver/Leave() All N files at once no ordering needed _classifyTlFile(name) Returns 'hub'|'emp'|'sor' _parseFilenameIdx(name) Returns integer N Unknown → skipped parseSORStaffing() + _parseSORSummarySheet() v2.11: summary = merge( {planned,actual} + flat fields No longer clobbers planned/actual _interpolateTimestamps() ts from emp LastScan or sortDown actual string _activateLatestSnapshot() Pushes latest → DATA.sor/hub/emp renderTimelineCharts() Chart 1: Net/Gross Vol + PPH Chart 2: Paid Day plan/actual + Sort Down ETA actual/plan Chart 3: Per-belt gross vol top10 v2.11: also copies DATA.sor.summary DATA.sor.sortDate (were missing pre-v2.11) → triggers dopRenderAll() TIMELINE.snaps — keyed by index N snaps[N] = { idx:N, hub:rows[], emp:rows[], sor:{rows,empTotals,areaHours,summary:{planned,actual,...flatFields},sortDate}, ts:Date } sorted by .idx for interpolation; sorted by .ts for chart rendering v2.11 fix: Timeline Chart 2 — Paid Day Y-axis Old: hard max:5 clipped any planned paid day > 5 hrs · New: suggestedMax = min(8, max(5, ceil(maxValue × 1.15))) _pdMax computed from [...paidDaySeries, ...paidDayPlanned].filter(v => v != null && v > 0) before chart init suggestedMax (not max) allows data points above the suggestion to extend the axis dynamically
07

Planning Input Subsystem (v2.11)

Before v2.11 every planPPH reference read directly from SOR's planned['PPH'] — a per-employee target (~217) rather than the building-level Adjusted Building PPH (~119). This corrupted every stripe color, every Cost/Piece plan, every DOP scenario, and every back-solve target. The v2.11 planning input subsystem intercepts all three derivation sites.

Input fields

hub-plan-pph-input — number field, Adj Bldg PPH. Placeholder "e.g. 119". Persists to localStorage['hub_plan_pph'].

hub-plan-vol-input — number field, Plan Volume override. Persists to localStorage['hub_plan_vol'].

Both live above the KPI grid inside the Hub tab. oninput calls hubPlanInputChanged() → re-renders grid + DOP.

Helper functions

_hubPlanPPH() — reads input value; returns number or null.

_hubPlanVol() — reads input value; returns number or null.

_loadHubPlanInputs() — called at DOMContentLoaded, restores localStorage values into inputs.

_suggestHubPlanFromSOR() — on SOR load: if input is empty AND SOR planned PPH is 70–200, pre-fills the input.

Affected callsites

All three state-building functions now route through _hubPlanPPH() for planPPH:

renderHubKpiGrid() line 1789
_dopState() line 3551
_buildSnapshotRecord() line 2172

_hubPlanVol() similarly overrides planVol at all three sites.

08

On-Floor Coordinator Feedback Loop

The tracker is designed around a four-hour sort window (Twilight). The coordinator's interaction has three phases: pre-sort setup, intra-sort monitoring, and post-sort capture. Each phase drives a different subsystem.
Temporal feedback loop — sort day timeline
17:30 Start 19:30 21:00 End SORT START SORT DOWN PRE-SORT RAMP (Ph.1) STEADY (Ph.2) CLOSE (Ph.3) CAPTURE PRE-SORT SETUP Enter Plan PPH (Adj Bldg ~119) Enter Plan Volume Set sort window Upload SOR (planned fields) → KPI stripe colors initialized → DOP back-solve targets set INTRA-SORT MONITORING Upload Hub Summary (N) + Employee Summary (N) + SOR (N) snapshots periodically Hub KPI: Net Vol, PPH vs plan (stripe), Process Rate (mid-sort), % Smalls (iGate derived) DOP Pace: burnNow pkgs/hr vs burnRequired · Sort-down ETA · Progress bars DOP Scenarios: if we push +5% PPH → projected final vol vs plan iGate Scan Metrics: belt-level PPH bands → where are scans slow? CLOSE + CAPTURE Upload final SOR (all actuals) % Smalls switches to SOR final Timeline charts show full arc DOP: historical guard activates Save End of Sort → IDB record → feeds History + Trends tabs ⬆ Staff up if burnNow < burnReq ⬇ Cut if PPH above plan early ◉ Watch % Smalls deviation Coordinator reads tiles → makes staffing/pacing call → uploads next snapshot → loop repeats History IDB → Trends tab → informs tomorrow's Plan PPH and staffing estimate
09

v2.11 Fix Summary

#BugRoot causeFix (function + line)
1 Flow/Hour KPI tile showed — during intra-sort even though netVol and paidDay were both populated Guard +actual['Flow Per Hour'] > 0 correctly suppressed SOR's mid-sort 0, but no fallback existed for any sort date, not just today renderHubKpiGrid(): when SOR actual = 0, derive _processRate = netVol / paidDay. Tile label switches to "Process Rate · Vol ÷ Paid Day".
2 Timeline batch upload: after activating the latest snapshot, Hub KPI tiles showed old/empty values for Flow/Hour, % Smalls, Sort Span Two bugs: (a) onTlFiles wrote summary: summ where summ was the 4-field flat object from _parseSORSummarySheet, overwriting the full {planned,actual} dict from parseSORStaffing. (b) _activateLatestSnapshot copied rows/hours/empTotals but not DATA.sor.summary or DATA.sor.sortDate. onTlFiles(): merge both shapes — summary = {planned, actual, ...summ}. _activateLatestSnapshot(): add DATA.sor.summary = latest.sor.summary and DATA.sor.sortDate = latest.sor.sortDate.
3 Back-Solve showed fabricated projections ("305,619 projected · burn 76k pkgs/hr") when old sort data was loaded _dopRenderPace computed elapsedH from wall-clock time. Old sort data: wall-clock is past sort end → status = 'post' → elapsedH = span.spanH = 4hburnNow = 305k/4 = 76k pkgs/hr. _dopRenderPace(): check DATA.sor.sortDate !== today → if true, show static historical summary banner and return early. Also: volume anomaly warning if netVol > planVol × 1.2.
4 Timeline Chart 2 Y-axis clipped Paid Day values > 5 hrs (observed 6.01 planned) Hard-coded max:5 on the Paid Day Y-axis in renderTimelineCharts(). Compute _pdMax = min(8, max(5, ceil(maxValue × 1.15))) from both actual and planned series. Replace max:5 with suggestedMax: _pdMax.
5 Plan PPH showed 217.6 (per-employee target from SOR) instead of ~119 (Adj Bldg PPH) All three state-builders read planned['PPH'] directly from SOR Summary, which stores the individual-employee rate, not the building-adjusted rate. New HTML inputs hub-plan-pph-input + hub-plan-vol-input. Helpers _hubPlanPPH() / _hubPlanVol() inserted at all three planPPH / planVol derivation sites. localStorage persist + DOMContentLoaded restore + SOR auto-suggest.
6 % Smalls showed — mid-sort (SOR actual = 0 until TMS finalizes) No fallback when SOR actual % Smalls = 0. Filter DATA.hub.rows for belts in {SLS01,SLS02,SS}, sum LooseOutbound+BagsLinked, divide by netVol. Use _kpiCardMag to show absolute count (big) above percentage (secondary).
Removed in v2.11: Sort Span KPI tile removed from Hub tab. The value is still computed by _dopState() for DOP use, and appears in the Volume Reconciliation table. The tile was redundant alongside the DOP pace panel.
Tracker v2.11 Architecture · CHEMA Twilight Operations Generated 2026-05-14