148 lines
6.5 KiB
Markdown
148 lines
6.5 KiB
Markdown
# 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
|