# Advanced Reports — Implementation Plan (Phase 1) **Version:** 1.0 **Date:** July 9, 2026 **Status:** Draft — derived from the approved planning set **Related Documents:** `ADVANCED_REPORTS_FEASIBILITY.md`, `ADVANCED_REPORTS_FUNCTIONAL.md`, `ADVANCED_REPORTS_NON_FUNCTIONAL.md`, `ADVANCED_REPORTS_API.md`, `ADVANCED_REPORTS_PROPOSAL.md` --- ## 1. Approach Build the Advanced Application Report on the existing report pipeline: the server computes analytics, renders map images, and writes a datasource; the client Stimulsoft viewer renders and exports. One analytics engine feeds every page (FR-5.4); content options are applied by shaping the datasource, not by swapping templates. Guiding principles: - **Pure-function analytics core** — testable without HTTP, Mongo, or Puppeteer (NFR-6.3). - **Legacy untouched** — no shared-code change may alter `preAppReport`/loadsheet behaviour (NFR-5.1). - **`ApplicationDetail` is read once per report** (single pass, streaming cursor, projected fields only — NFR-1.2/1.4). - **One Chromium instance** per report for all captures (NFR-1.3). ## 2. Deliverables map | # | Deliverable | New/Changed files | |---|---|---| | D1 | Analytics engine | `helpers/report_util.js` (new) | | D2 | Endpoint + datasource builder | `controllers/advanced_report.js` (new), `routes/job.js` (route), `model/job.js` (report-contents options persistence) | | D3 | Map page variants + multi-capture | `public/sprayMap.html` (variants), `helpers/web_util.js` (browser reuse) | | D4 | Template + validation | `reports/app_advanced.mrt` (authored in the Stimulsoft designer), `scripts/validate_advanced_report_template.js` (new) | | D5 | Frontend wiring | client repo: Report Settings "Report Contents" panel, advanced-report option, viewer call | | D6 | Tests + offline harness | `tests/` fixtures + unit/integration tests, offline Stimulsoft harness | | D7 | Rollout | backfill verification, template deploy to `REPORT_DIR`, release notes | Sequencing: **D1 → D2 → (D3 ∥ D4) → D5 → D6 → D7.** D3 and D4 are independent once D2 fixes the datasource shape. D6 grows alongside every deliverable (D1 unit tests land with D1). ## 3. D1 — Analytics engine (`helpers/report_util.js`) Pure functions over point arrays; no I/O. 1. **Line segmentation** — group `ApplicationDetail` points by `llnum`; `sprayStat == 3` marks line start, spray-on = `sprayStat ∈ {1, 3}` (pattern: `getSprayOnSegments`, `controllers/job.js:604`). 2. **Zone assignment** — point-in-polygon (turf) of each line's points against `job.sprayAreas`; majority wins for straddling lines (FR-5.2). 3. **Per-line stats** — start time, spray time, length (`geoUtil.distance()` — **km**), avg speed, area = length × swath, app rate, avg |xTrack|, turn time (gap to next line; pattern: turn-time loop `workers/job_worker.js:1486`). 4. **Zone roll-ups** — sprayed area, coverage %, volume, flight/spray time, avg turn time, avg height, avg XT, avg flow rate (mean `lminApp` — completely new calculation, degrade when flat 0). 5. **Mission totals** — sums/weighted averages of zone values (must equal page-1 figures exactly, NFR-3.3); ferry time/distance = flight − spray. 6. **Planned areas** — turf area from polygon geometry (`sprayAreas[].properties.area` absent in live data); coverage capped at 100.0% display. 7. **Weather** — reuse `jobUtil.getDataWeatherInfo(fileIds)` (`helpers/job_util.js:325`); manual `job.weatherInfo` override. Unit-test fixtures (with D1): typical multi-zone job, no-flow-controller job (`lminApp` = 0), SatLoc-style job (no xTrack/turn data), unsprayed zone, single-zone job, boundary-straddling line. ## 4. D2 — Endpoint + datasource (`controllers/advanced_report.js`) 1. `POST /preAdvancedReport` in `routes/job.js`, same auth middleware as `preAppReport` (NFR-4.1). Returns `{ rid, path, c }` (FR-1.1). 2. Controller flow (mirrors `preAppReport_post`): load job with populated refs → persist Report Settings incl. Report Contents selections (`job.rptOp` pattern, FR-7.5) → stream `ApplicationDetail` by the job's `fileId`s with field projection (NFR-1.2) → run D1 engine → render maps via D3 → write `rptDS.json` → select template. 3. Datasource shape (all display values pre-localized strings, FR-1.3): - `mission` — header/info block, KPI tiles, statistics, generation date. - `zones[]` — per-zone info + stats + map image refs; empty-state zones carry `–` placeholder values (FR-4.6); filtered per Report Contents options. - `lines[]` nested per zone — flight-line table rows; single `–` placeholder row for unsprayed zones; omitted when Flight Line Statistics is off. - `products[]`, `weather` (suppressed when unavailable), `coverageCards[]` (all zones, always). 4. Template selection: `app_advanced_.mrt` else `app_advanced.mrt`; applicator id sanitized to hex ObjectId (NFR-4.2). 5. Structure the generation function so a worker can call it without the HTTP layer (NFR-2.3); a simple in-process counter limits it to max 2 concurrent generations (NFR-2.2). 6. Per-phase pino logging: query, data aggregation, each capture, datasource write (NFR-7.1). ## 5. D3 — Maps (`public/sprayMap.html` variants + `helpers/web_util.js`) 1. Extend `web_util` to open one Chromium instance and capture multiple pages/states per report (NFR-1.3). 2. Mission map variant: fitBounds over all zones; numbered markers + zone/field names + acreage labels; spray/ferry layers; legend/scale/north arrow. **Mode switch** (FR-2.3.3): compute zone pixel footprint at fitted zoom — below threshold (~25 px) render locator badges (`divIcon`, zone numbers) instead of polygons (FR-2.3.2). 3. Zone detail variant: per-zone fitBounds, boundary + spray lines + dashed ferry lines, neighbouring zones faded into the background; unsprayed zones render boundary + ferry only. 4. Background toggle: when *Hide Map Background* is selected (FR-7.4), all variants skip the satellite tile layer and render on the plain dark-green background used in the mockups (a fixed CSS background on the map container) — no tile downloads during capture. 4. Thumbnails: crop from the single all-zones capture when single-viewport; per-zone captures when dispersed; **skipped entirely above 12 zones** (FR-3.5) or when zone pages are excluded (Report Contents). 5. Zone capture failure → placeholder image + log, report continues; mission map failure → request fails (NFR-3.1). ## 6. D4 — Template (`reports/app_advanced.mrt`, authored in the Stimulsoft designer) 1. Three page designs authored manually in the embedded Stimulsoft designer: Mission Overview, Mission Coverage (grid ≤12 zones / compact table >12), Zone Detail (master band per zone, flight-line table as StiPanel-wrapped child band). 2. Validation script `scripts/validate_advanced_report_template.js` (NFR-5.2/6.1), run after every designer save and before deploy. Checks: no empty `{}` collections in the `.mrt` JSON; `GlobalizationStrings` for en-US / pt-PT / es-ES with non-empty Items targeting existing components; unique component names; every band's `DataSourceName` / `MasterComponent` / `DataRelationName` resolves against the Dictionary; DataBands nested inside DataBands are StiPanel-wrapped; every `{table.column}` expression references a declared Dictionary column. 3. Section suppression via empty datasets; dash placeholder rows come from the datasource, not template logic. 4. Committed `.mrt` is the source of truth (NFR-6.1). Footer `Created ` + `page/totalPages`; header band per page type. ## 7. D5 — Frontend (client repo) 1. Report Settings dialog: add right-side **Report Contents** panel — Include All Zone Detail (default on), nested Sprayed Zones Only (default off), Include Flight Line Statistics (default on), Hide Map Background (default off), info tooltips (FR-7.4); restore last selections per job. 2. Advanced Report as a report option alongside the legacy report; on Preview call `preAdvancedReport` and hand `{rid, path}` to the existing viewer unchanged. ## 8. D6 — Testing & verification 1. D1 unit tests over fixtures (all FR-8 degradation rows covered, NFR-3.4). 2. Cross-page consistency test: mission totals ≡ zone roll-ups (NFR-3.3). 3. Offline Stimulsoft harness (file:// + `stimulsoft.reports.pack.js`) loading the real `.mrt` + generated `rptDS.json` — reproduces viewer load/localize/render without the app (NFR-6.2); Trial watermark acceptable in tests. 4. Integration run against a live-like multi-zone job; visual check of all three page types, both map modes, >12-zone compact layout, both unit systems, all three cultures. 5. Performance measurement against NFR-1.1 (~35 s per 10 zones; ~15 s typical 3-zone job) with per-phase timings from NFR-7.1 logs. ## 9. D7 — Rollout 1. Verify production aggregate coverage (`avgXtError`, `avgSpraySpeed`, `totalFlightLength`) on recent Applications; re-run `scripts/migrate_applications.js` only if gaps found (NFR-8.1). 2. Deploy `app_advanced*.mrt` to the environment's `REPORT_DIR` (may be outside this repo — NFR-6.4). 3. Release notes: flow-rate fields require a flow controller; SatLoc-sourced applications omit XT/turn statistics (NFR-8.2). ## 10. Open items - **F-OQ-1 Page orientation** (portrait-only vs landscape variant) — blocks D4 template freeze; portrait assumed until the PO decides. - **F-OQ-2 Compact coverage layout threshold** (more than 12 vs 12-and-above; threshold value) — affects D3 thumbnail logic and the D4 coverage page; "more than 12" assumed until the PO decides. - **Regeneration reuse/caching** for repeat downloads of unchanged reports — not in Phase 1 scope; candidate optimization if NFR-1.1 budgets prove tight in practice (D2's worker-ready structure keeps the door open).