Scoring framework adoption — services/scoring/normalization.py gains general clamp() + weighted_score(); compute_ipm_compliance_score and compute_organic_pest_score route through them instead of inline min/max/round math.
Report framework split — the 4,955-line report_generator.py monolith becomes a ~280-line CLI facade over services/export/reports/ (per-domain modules: core, wellbeing, state, commons, operations, strategy) plus shared context/snapshot components (common, context). generate_pest_management now routes to pest_management.get_pest_management_report — single owner of the analysis instead of re-querying views. All 27 consumer import sites keep working via the facade re-export.
Shared HTTP client — new services/common/http.py with timeout + retry/backoff; 10 ingestion files stop doing raw requests.* and use the client.
Crop NOI single owner — the Python and TS implementations were actually different formulas (location/period vs crop-cycle). New migration 353_crop_cycle_noi_view.sql makes the view the single owner of the crop-cycle formula; the TS Directus hook now reads the view.
Verification
305 report-generator tests + 531 sprint-wide tests pass; test_migration green
TS hooks: tsc clean, 52 vitest tests pass
All changed files compile; zero new ruff issues vs HEAD
CLI surface intact (94 report types in --type choices)
Notes
Two tests that patched monolith internals (test_state_of_kokonut, test_comprehensive_status) now patch the split module namespaces.
Pest report keeps its public-safe view queries, but they moved into the pest_management domain module.
Dedup sprint item 2/4 targets:
- scoring: generalize services/scoring/normalization with clamp()/weighted_score()
and route compute_ipm_compliance_score + compute_organic_pest_score through it
instead of inline min/max/round math
- reports: split the 4955-line report_generator.py monolith into
services/export/reports/ per-domain modules (core, wellbeing, state, commons,
operations, strategy) + shared context/snapshot components (common, context);
report_generator.py is now a thin CLI facade re-exporting the public API.
generate_pest_management now routes to pest_management.get_pest_management_report
(single owner of the analysis) instead of re-querying views
- http: new services/common/http.py client with timeout/retry/backoff; 10
ingestion files stop doing raw requests.* and use the client
- crop NOI: new schemas/postgres/353_crop_cycle_noi_view.sql becomes the single
owner of the crop-cycle NOI formula; the TS Directus hook reads the view and
the Python side documents the ownership decision
Tests updated to patch the split module namespaces (state/commons) rather than
the facade. 305 report tests + 531 sprint-wide + 52 TS hook tests pass; zero
new ruff issues vs HEAD.
🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
sprint2 migrated post_clickhouse_rows to the shared services.common.http
client, so patching the module-level requests.post no longer intercepts.
The three HTTP-path tests were issuing real requests to clickhouse:8123,
failing CI with connection refused. Patch http.post on the shared client
instead; native-insert test unchanged.
Generated with Codebuff 🤖
Co-Authored-By: Codebuff <noreply@codebuff.com>
Summary
Four dedup/de-monolith targets:
Verification
Notes