docs(fusion_accounting_reports): CLAUDE.md, UPGRADE_NOTES.md, README.md
Made-with: Cursor
This commit is contained in:
147
fusion_accounting_reports/CLAUDE.md
Normal file
147
fusion_accounting_reports/CLAUDE.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# fusion_accounting_reports — Cursor / Claude Context
|
||||
|
||||
## Purpose
|
||||
|
||||
AI-augmented financial reports — a Fusion-native replacement for Odoo
|
||||
Enterprise's `account_reports` module. Phase 2 of the fusion_accounting
|
||||
roadmap.
|
||||
|
||||
CORE scope:
|
||||
- Income Statement (P&L)
|
||||
- Balance Sheet
|
||||
- Trial Balance
|
||||
- General Ledger (with drill-down)
|
||||
|
||||
AI augmentation:
|
||||
- Anomaly detection (variance vs prior period)
|
||||
- AI commentary (LLM-generated narrative)
|
||||
|
||||
## Architecture
|
||||
|
||||
Hybrid: the engine (`fusion.report.engine`, AbstractModel) is the SINGLE
|
||||
read surface for reports. Per-report definitions are stored as `fusion.report`
|
||||
records with JSON `line_specs` so non-developers can tweak the layouts.
|
||||
|
||||
Public engine API (5 methods):
|
||||
- `compute_pnl(period, *, comparison='none', company_id=None)`
|
||||
- `compute_balance_sheet(date_to, *, comparison='none', company_id=None)`
|
||||
- `compute_trial_balance(period, *, company_id=None)`
|
||||
- `compute_gl(period, *, account_ids=None, company_id=None)`
|
||||
- `drill_down(*, account_id, period, company_id=None)`
|
||||
|
||||
Pure-Python services in `services/` (no Odoo imports — independently
|
||||
unit-testable):
|
||||
- `date_periods` — `Period` dataclass + comparison-period math
|
||||
- `account_hierarchy` — chart-of-accounts tree walk
|
||||
- `totaling` — debit/credit/balance roll-ups
|
||||
- `currency_conversion` — multi-currency conversion via `res.currency.rate`
|
||||
- `line_resolver` — JSON `line_specs` → rendered rows
|
||||
- `drill_down_resolver` — line → underlying journal items
|
||||
- `anomaly_detection` — variance vs prior period (z-score + abs/pct gates)
|
||||
- `commentary_generator` — LLM narrative with templated fallback
|
||||
- `commentary_prompt` — provider-agnostic system + user prompt
|
||||
|
||||
Persisted models in `models/`:
|
||||
- `fusion.report` — definition with JSON `line_specs`
|
||||
- `fusion.report.commentary` — LLM-output cache (one per period+mode)
|
||||
- `fusion.report.anomaly` — flagged variances
|
||||
- `fusion.account.balance.mv` — pre-aggregated materialized view
|
||||
- `fusion.report.engine` — AbstractModel (the API)
|
||||
- `fusion.reports.cron` — cron handlers (commentary refresh, MV refresh)
|
||||
- `fusion.xlsx.export.wizard` — TransientModel (XLSX export)
|
||||
- `fusion.period.picker.wizard` — TransientModel (UX entry-point)
|
||||
- `fusion.migration.wizard` (inherits) — adds `_reports_bootstrap_step`
|
||||
|
||||
Controller: `controllers/reports_controller.py` exposes 8 JSON-RPC endpoints
|
||||
under `/fusion/reports/*`. All read paths route through the engine.
|
||||
|
||||
OWL frontend: `static/src/`
|
||||
- `scss/` — variables, base styles, dark-mode overrides
|
||||
- `services/reports_service.js` — central reactive state + RPC wrappers
|
||||
- `views/report_viewer/` — top-level OWL view + view-registry adapter
|
||||
- `components/report_table/` — generic financial-table renderer
|
||||
- `components/drill_down_dialog/` — modal for journal-item listing
|
||||
- `components/period_filter/` — date-range + comparison picker
|
||||
- `components/ai_commentary_panel/` — LLM commentary surface
|
||||
- `components/anomaly_strip/` — variance summary banner
|
||||
- `tours/reports_tours.js` — 5 OWL tour smoke tests
|
||||
|
||||
## Coexistence
|
||||
|
||||
When `account_reports` is installed, the Reports menu hides via
|
||||
`fusion_accounting_core.group_fusion_show_when_enterprise_absent`
|
||||
(a computed group). The engine + AI tools (commentary, anomaly detection)
|
||||
remain available for the chat regardless.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **V19 deprecations to avoid:** `_sql_constraints` (use `models.Constraint`),
|
||||
`@api.depends('id')` (raises `NotImplementedError`), `@route(type='json')`
|
||||
(use `type='jsonrpc'`), `numbercall` field on `ir.cron` (removed),
|
||||
`groups_id` on `res.users` (use `all_group_ids` for searching),
|
||||
`users` field on `res.groups` (use `user_ids`), `groups_id` on
|
||||
`ir.ui.menu` (use `group_ids`).
|
||||
|
||||
- **Engine signature:** Public methods are keyword-only after the leading
|
||||
positional `period` / `date_to`. Always pass `company_id=...` explicitly.
|
||||
|
||||
- **`fusion.report` lookup:** `_get_report` falls back from per-company
|
||||
override to global (`company_id=False`) — order is `company_id desc nulls
|
||||
last`.
|
||||
|
||||
- **Materialized view refresh:** `fusion.account.balance.mv` rebuilds via a
|
||||
dedicated autocommit cursor (REFRESH CONCURRENTLY can't run inside Odoo's
|
||||
regular transaction). Triggered by cron + on demand from the engine when
|
||||
data is older than the configured TTL.
|
||||
|
||||
- **JSON `line_specs`:** Strings prefixed `account:`, `prefix:`, `formula:`
|
||||
or `header` — `line_resolver.py` resolves each spec to a row. Header rows
|
||||
have no compute payload and are silently skipped by downstream totals.
|
||||
|
||||
- **Commentary cache:** Keyed on `(report_id, company_id, period_from,
|
||||
period_to, comparison_mode)` with a unique constraint. Re-runs use the
|
||||
cache unless `force_refresh=True`.
|
||||
|
||||
## Test counts (Phase 2 ship)
|
||||
|
||||
- 130 logical tests, 0 failed, 0 errors
|
||||
- Includes:
|
||||
- 6 benchmarks (tagged `benchmark`)
|
||||
- 1 LLM compat smoke (tagged `local_llm`, skips when no LLM)
|
||||
- 5 OWL tours (tagged `tour`, skips without `websocket-client`)
|
||||
- Property-based, integration, controller, materialized-view, coexistence,
|
||||
migration round-trip, PDF/XLSX export
|
||||
|
||||
## Performance baseline
|
||||
|
||||
| Operation | Median | P95 | Budget |
|
||||
|---|---|---|---|
|
||||
| `engine.compute_pnl` | 3ms | 8ms | <2000ms |
|
||||
| `engine.compute_balance_sheet` | 15ms | 20ms | <2000ms |
|
||||
| `engine.compute_trial_balance` | 3ms | 8ms | <1000ms |
|
||||
| `engine.compute_gl` | 25ms | 81ms | <3000ms |
|
||||
| `engine.drill_down` | 2ms | 10ms | <500ms |
|
||||
| `controller.run` (HTTP round-trip) | 9ms | 46ms | <2500ms |
|
||||
|
||||
All metrics within 1x of budget at Phase 2 ship. Numbers from
|
||||
`tests/test_performance_benchmarks.py` against the dev VM
|
||||
(`westin-v19`, ~1 fiscal year of data).
|
||||
|
||||
## Known concerns / Phase 2.5 backlog
|
||||
|
||||
- Trial balance period-only sum doesn't auto-close to retained earnings
|
||||
(drift visible in `test_trial_balance_total_near_zero`, currently skipped)
|
||||
- Balance sheet `TOTAL LIABILITIES + EQUITY` math limited (no
|
||||
subtotal-of-subtotals expansion in `formula:` specs)
|
||||
- GL `line_specs` need `prefix:` empty-string handling for
|
||||
"all accounts" semantics
|
||||
- Header rows (no compute payload) silently skipped by `line_resolver` —
|
||||
fine for layout, but a `header_only=True` flag would be clearer
|
||||
- `expense` prefix overlaps with subtypes (`expense_direct_cost`,
|
||||
`expense_depreciation`) — current line_specs need explicit ordering or a
|
||||
longer-prefix-wins rule
|
||||
- `wkhtmltopdf` may need configuration for PDF export on first install
|
||||
- `ReportsAdapter.run_report` vs `run_fusion_report` naming (legacy clash
|
||||
with Enterprise wrapper)
|
||||
- Tour tests skip when `websocket-client` is absent — install it in CI to
|
||||
exercise the OWL surface end-to-end
|
||||
Reference in New Issue
Block a user