diff --git a/fusion_accounting_reports/CLAUDE.md b/fusion_accounting_reports/CLAUDE.md new file mode 100644 index 00000000..582f55ed --- /dev/null +++ b/fusion_accounting_reports/CLAUDE.md @@ -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 diff --git a/fusion_accounting_reports/README.md b/fusion_accounting_reports/README.md new file mode 100644 index 00000000..7a5aca51 --- /dev/null +++ b/fusion_accounting_reports/README.md @@ -0,0 +1,103 @@ +# fusion_accounting_reports + +AI-augmented financial reports for Odoo 19 Community — a Fusion-native +replacement for Enterprise's `account_reports` module. + +## What it does + +- **CORE reports**: Income Statement (P&L), Balance Sheet, Trial Balance, + General Ledger (with drill-down to journal items) +- **AI augmentation**: variance-based anomaly detection + LLM-generated + commentary (Claude / GPT / local LM Studio / Ollama) +- **Wizards**: period picker (common presets — MTD, QTD, YTD, last month, + custom range) + XLSX export +- **Coexists** with Enterprise's `account_reports` (Enterprise wins by + default; the Fusion menu appears only when Enterprise is uninstalled — + the engine and AI tools are always available via the AI chat) +- **Multi-currency** aware via `services/currency_conversion.py` +- **Multi-company** aware (per-company `fusion.report` overrides fall back + to global definitions) + +## Quick start + +```bash +# Install +odoo --addons-path=... -i fusion_accounting_reports + +# Open the reports menu (when Enterprise's account_reports is NOT installed) +# Apps → Reports → Open Financial Report +``` + +## Configuration + +### LLM commentary (optional) + +For LM Studio / Ollama (local): + +- `fusion_accounting.openai_base_url` = `http://host.docker.internal:1234/v1` +- `fusion_accounting.openai_model` = your local model name +- `fusion_accounting.openai_api_key` = `lm-studio` (or anything non-empty) +- `fusion_accounting.provider.reports_commentary` = `openai` + +For OpenAI / Anthropic, set the corresponding API keys via the +`fusion_accounting_ai` config screen — `reports_commentary` will route +through whatever provider you choose. + +If no provider is configured, commentary falls back to a deterministic +templated summary (no LLM call). + +### Cron jobs + +Two cron handlers live in `models/fusion_reports_cron.py`: + +- `fusion_reports_commentary_refresh` — daily, regenerates commentary for + the most recently completed period +- `fusion_reports_mv_refresh` — every 15 min, refreshes + `fusion.account.balance.mv` + +## Public engine API + +```python +engine = env['fusion.report.engine'] + +# Income statement +result = engine.compute_pnl(period, comparison='previous_year') + +# Balance sheet (point-in-time) +result = engine.compute_balance_sheet(date(2026, 12, 31)) + +# Trial balance +result = engine.compute_trial_balance(period) + +# General ledger (journal items per account) +result = engine.compute_gl(period, account_ids=[1, 2, 3]) + +# Drill-down (one account, period) +items = engine.drill_down(account_id=1, period=period) +``` + +## JSON-RPC endpoints + +All under `/fusion/reports/`: + +- `POST /fusion/reports/run` — single entry-point (dispatches by `report_type`) +- `POST /fusion/reports/drill_down` — journal items for an account+period +- `POST /fusion/reports/commentary` — fetch/refresh LLM commentary +- `POST /fusion/reports/anomalies` — flagged variances for a period +- `POST /fusion/reports/export_xlsx` — XLSX bytes +- `POST /fusion/reports/export_pdf` — PDF bytes (via wkhtmltopdf) +- `POST /fusion/reports/list_definitions` — available `fusion.report` records +- `POST /fusion/reports/period_presets` — date-range presets for the picker + +## Test counts + +- 130 logical tests, 0 failures, 0 errors +- 6 performance benchmarks (tagged `benchmark`) +- 1 local-LLM compat smoke (tagged `local_llm`, skips without LLM) +- 5 OWL tour tests (tagged `tour`, skips without `websocket-client`) + +## See also + +- `CLAUDE.md` — agent context (architecture, conventions, perf baseline, + Phase 2.5 backlog) +- `UPGRADE_NOTES.md` — V19 anchor + migration strategy diff --git a/fusion_accounting_reports/UPGRADE_NOTES.md b/fusion_accounting_reports/UPGRADE_NOTES.md new file mode 100644 index 00000000..4a512823 --- /dev/null +++ b/fusion_accounting_reports/UPGRADE_NOTES.md @@ -0,0 +1,60 @@ +# fusion_accounting_reports — Upgrade Notes + +## Odoo Version Anchor + +This module targets **Odoo 19.0** (community-base). + +Reference snapshot of Enterprise code mirrored from: +- `account_reports` (Odoo 19.0.x) +- Source: `/Users/gurpreet/Github/RePackaged-Odoo/accounting/account_reports/` + +## Cross-Version Diff Strategy + +When a new Odoo version ships: + +1. Run `check_odoo_diff.sh` (in repo root) against the new Enterprise version +2. Note any breaking changes in `account.move.line` / `account.account` API + surfaces relied on by `services/totaling.py` and + `services/drill_down_resolver.py` +3. For mirrored OWL components, diff Enterprise's new versions against ours + and port material changes (signature renames, new behaviour we want to + inherit) +4. Re-run the full test suite + tour tests + benchmarks against the new Odoo + version +5. Update this file with the new version anchor + any deviations + +## V19 Migration Notes (already applied — Phase 1 lessons) + +These were the bite-points from Phase 1 (`fusion_accounting_bank_rec`); we +preempted them in Phase 2 from day one: + +- `_sql_constraints` → `models.Constraint` (used in `fusion.report`, + `fusion.report.commentary`, `fusion.report.anomaly`) +- `@api.depends('id')` → removed everywhere; computed fields depend on real + field names instead +- `@route(type='json')` → `type='jsonrpc'` (all 8 endpoints) +- `numbercall` field on `ir.cron` → omitted (removed in V19) +- `res.groups.users` → `user_ids` +- `ir.ui.menu.groups_id` → `group_ids` (used in `views/menu_views.xml` and + the two wizard view files for the coexistence-group filter) + +## Engine API Stability + +The 5 public engine methods (`compute_pnl`, `compute_balance_sheet`, +`compute_trial_balance`, `compute_gl`, `drill_down`) are the public contract. +Their signatures are keyword-only after the first positional argument and +will be treated as semver-stable across patch releases. Breaking changes +will bump the minor version (e.g. 19.0.2.x.y). + +## Phase 2 → Phase 2.5 Migration + +If we ship Phase 2.5 (line_spec polish, deferred features, header_only +flag, prefix overlap fix), changes will go in incremental commits. No DB +migration needed — Phase 2 schema is forward-compatible: + +- `fusion.report.line_specs` is a JSON column; the migration path is to + rewrite specs in place +- `fusion.account.balance.mv` can be dropped/re-created freely +- `fusion.report.commentary` is a cache; safe to truncate on upgrade +- `fusion.report.anomaly` records carry Period as date_from/date_to fields; + no schema-level changes anticipated