|
| 1 | +# NeoReports — Implementation Plan (v1) |
| 2 | + |
| 3 | +Small, independent PRs, in order. Each one closes with green tests and closes one acceptance criterion (AC-n) from `docs/MVP-Spec.md`. Check the box when done. |
| 4 | + |
| 5 | +## PR 0 — Repository bootstrap |
| 6 | +- [x] `global.json`, `build/Directory.Build.props`, `build/Directory.Packages.props`, `.editorconfig`, `.gitignore`. |
| 7 | +- [x] `NeoReports.sln` with solution folders mirroring `src/ tests/ benchmarks/ samples/`. |
| 8 | +- [x] Minimal CI (`dotnet build` + `dotnet test` + `dotnet format --verify-no-changes`). |
| 9 | +- **Acceptance:** `dotnet build` and `dotnet test` pass on an empty repo. |
| 10 | + |
| 11 | +## PR 1 — NeoReports.Abstractions |
| 12 | +- [x] Typed-only types and interfaces per D9 (already skeletoned in `src/NeoReports.Abstractions/`). |
| 13 | +- [x] English XML docs on everything public. |
| 14 | +- **Acceptance:** compiles multi-target (net8/net9), no dependencies beyond `Logging.Abstractions`. |
| 15 | +- **Depends on:** PR 0. |
| 16 | + |
| 17 | +## PR 2 — NeoReports.Core: builder + batch pipeline |
| 18 | +- [x] Generic fluent builder `ReportBuilder<TRow>` (`From`/`Filter`/`Columns`/`Column`/`To`/`UploadTo`/`Retry`/`OnFailure`; mapping via `From(source, map)` — see D12). |
| 19 | +- [x] `IReportRegistry` + `AddReport<TRow>(...)` (DI). |
| 20 | +- [x] `ReportRunner`/pipeline: batch loop, `TypedBatchReader` (adapts streaming → batches), `T → object?[]` projection at the writer edge. |
| 21 | +- [x] Polly v8 integration (`ResiliencePipeline`) on the batch read. |
| 22 | +- [x] `IFailureStrategy`: `AbortReport`, `SkipBatchAndLog`; threshold (consecutive/total/ratio) via `AbortIf` (see D11). |
| 23 | +- **Acceptance:** AC-1, AC-11, AC-12, AC-13, AC-14. Pipeline tested with an in-memory fake source. ✅ 13 green tests. |
| 24 | +- **Depends on:** PR 1. |
| 25 | + |
| 26 | +## PR 3 — Sources.Sql + Formats.Csv + Destinations.Local (first end-to-end) |
| 27 | +- [x] `Source.Sql(...).Keyset(key, pageSize)` — `IBatchSource<T>`, connection per page, `string?` cursor, parameterized parameters (auto-binds only what the query references). |
| 28 | +- [x] `Format.Csv(...)` — non-generic writer (delimiter, encoding, header via `DisplayName`, culture/format formatting, RFC 4180 escaping, CRLF, UTF-8 without BOM). |
| 29 | +- [x] `Destination.Local(pathTemplate)` — `{name}/{date[:fmt]}/{ext}` tokens + parameters; atomic publish (temp + move). |
| 30 | +- [x] Sample `01-sql-to-csv-local`. |
| 31 | +- **Acceptance:** AC-2, AC-4, AC-7. Reference report runs end-to-end to CSV+Local. SQL tested with Testcontainers. ✅ 26 green tests (13 Core + 4 CSV + 6 Local + 3 SQL/E2E). |
| 32 | +- **Depends on:** PR 2. |
| 33 | + |
| 34 | +## PR 4 — Formats.Xlsx + Destinations.S3 |
| 35 | +- [x] `Format.Xlsx(...)` with ClosedXML (sheet, auto-filter, native types; per-column format/date). Memory grows with rows — see D14. |
| 36 | +- [x] Multi-output in a single pass (CSV + XLSX reading the source once) — proven in the E2E `Csv_and_xlsx_are_generated_reading_the_source_once`. |
| 37 | +- [x] `Destination.S3(bucket, keyTemplate)` — all-or-nothing upload via `PutObject` (no partial object on failure) — see D15. |
| 38 | +- [x] Sample `02-sql-to-xlsx-s3`. |
| 39 | +- **Acceptance:** AC-5, AC-6, AC-8. ✅ Cumulative green tests: 34 (13 Core + 4 CSV + 6 Local + 4 Xlsx + 3 S3 + 4 SQL/E2E). |
| 40 | +- **Depends on:** PR 3. |
| 41 | + |
| 42 | +## PR 5 — Constant memory (validation) |
| 43 | +- [x] `NeoReports.Benchmarks` with `MemoryDiagnoser`: synthetic source (lazy, page by page) of 100k and 1M rows → CSV/XLSX. |
| 44 | +- [x] No buffering tweak needed: per-row allocation already constant (~446 B/row @100k vs ~461 B/row @1M — linear, not super-linear). |
| 45 | +- **Acceptance:** AC-3 (~constant allocation). ✅ proven. CSV is streaming; XLSX grows with volume by ClosedXML design (D14). |
| 46 | +- **Depends on:** PR 4. |
| 47 | + |
| 48 | +## PR 6 — Jobs: single worker |
| 49 | +- [x] `IJobStore` (InMemory) + `ICheckpointStore` (no-op) + `InMemoryJobScheduler` in the base package `NeoReports.Jobs` (see D18). |
| 50 | +- [x] `Jobs.Hangfire` single-server: `HangfireJobScheduler` + invoker reusing `ReportJobWorker`; params via JSON; DI. SQL storage configured by the host (see D19). |
| 51 | +- [x] Cooperative cancellation (per-job CTS / Hangfire `CancellationToken`); idempotent restart (per-job temp + upload only at the end, inherited from the pipeline). |
| 52 | +- **Acceptance:** AC-15, AC-16; status `queued→running→completed`. ✅ 16 green tests. |
| 53 | +- **Depends on:** PR 2. |
| 54 | + |
| 55 | +## PR 7 — Integrations.AspNetCore: trigger endpoints |
| 56 | +- [x] `MapNeoReports("/api")` (Minimal API): `run` (async 202+jobId / `?mode=sync` stream), `GET /reports`, `GET /jobs/{id}`, `POST /jobs/{id}/cancel`, `GET /jobs/{id}/download` (multi-output → zip). |
| 57 | +- [x] Validation: sync rejects multi-output (`400`); auth inherited from the host (optional `RequireAuthorization`). Artifact store in Core; status as string — see D20. |
| 58 | +- [x] Sample `03-async-job-hangfire` (Hangfire single-server, in-memory storage). |
| 59 | +- **Acceptance:** AC-9, AC-10. **Demonstrable MVP.** ✅ 8 integration tests via TestServer. |
| 60 | +- **Depends on:** PR 6, PR 4. |
| 61 | + |
| 62 | +## PR 8 — OSS release polish |
| 63 | +- [ ] README, LICENSE (MIT), CHANGELOG, NuGet packaging (symbols/snupkg), per-package README. |
| 64 | +- **Acceptance:** `dotnet pack` produces all packages; samples documented. |
| 65 | +- **Depends on:** PR 7. |
| 66 | + |
| 67 | +--- |
| 68 | + |
| 69 | +## Post-MVP (do not start before validating with users) |
| 70 | + |
| 71 | +Likely order once there's traction: dynamic path (config + JsonLogic) → **Blazor UI from the Claude Design handoff** → variants/pipeline → multi-worker + mid-job resume → remaining sources/formats/destinations. Reminder: any UI work always starts from the Claude Design deliverables, never from invented design. |
0 commit comments