feat(fusion_accounting_reports): fusion.report.commentary cache model

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 15:31:22 -04:00
parent 17053b1603
commit 22b277c6b8
6 changed files with 100 additions and 1 deletions

View File

@@ -1,6 +1,6 @@
{
'name': 'Fusion Accounting Reports',
'version': '19.0.1.0.11',
'version': '19.0.1.0.12',
'category': 'Accounting/Accounting',
'summary': 'AI-augmented financial reports (P&L, balance sheet, trial balance, GL).',
'description': """

View File

@@ -1,2 +1,3 @@
from . import fusion_report
from . import fusion_report_engine
from . import fusion_report_commentary

View File

@@ -0,0 +1,43 @@
"""Cached AI-generated commentary for a report run.
One row per (report, period_from, period_to, comparison_mode, company).
Refreshed on demand or via cron when the underlying data has changed."""
from odoo import _, api, fields, models
class FusionReportCommentary(models.Model):
_name = "fusion.report.commentary"
_description = "AI-Generated Report Commentary Cache"
_order = "generated_at desc"
report_id = fields.Many2one('fusion.report', required=True, ondelete='cascade')
company_id = fields.Many2one('res.company', required=True,
default=lambda self: self.env.company)
period_from = fields.Date(required=True)
period_to = fields.Date(required=True)
comparison_mode = fields.Selection([
('none', 'None'),
('previous_period', 'Previous Period'),
('previous_year', 'Previous Year'),
], default='none', required=True)
summary = fields.Text()
highlights = fields.Json() # list of strings
concerns = fields.Json() # list of strings
next_actions = fields.Json() # list of strings
generated_at = fields.Datetime(default=fields.Datetime.now, required=True)
generated_by = fields.Selection([
('on_demand', 'On Demand'),
('cron', 'Cron'),
('templated', 'Templated Fallback'),
], default='on_demand', required=True)
provider = fields.Char(help="LLM provider used (e.g. 'openai', 'claude', 'local'). "
"Empty for templated fallback.")
_unique_period = models.Constraint(
'UNIQUE(report_id, company_id, period_from, period_to, comparison_mode)',
'Only one commentary cache row per report+period+mode.',
)

View File

@@ -1,3 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_fusion_report_user,fusion.report.user,model_fusion_report,base.group_user,1,0,0,0
access_fusion_report_admin,fusion.report.admin,model_fusion_report,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
access_fusion_report_commentary,fusion.report.commentary,model_fusion_report_commentary,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_fusion_report_user fusion.report.user model_fusion_report base.group_user 1 0 0 0
3 access_fusion_report_admin fusion.report.admin model_fusion_report fusion_accounting_core.group_fusion_accounting_admin 1 1 1 1
4 access_fusion_report_commentary fusion.report.commentary model_fusion_report_commentary base.group_user 1 1 1 0

View File

@@ -8,3 +8,4 @@ from . import test_seeded_reports
from . import test_anomaly_detection
from . import test_commentary_prompt
from . import test_commentary_generator
from . import test_fusion_report_commentary

View File

@@ -0,0 +1,53 @@
"""Tests for fusion.report.commentary cache model."""
from datetime import date
from odoo.tests.common import TransactionCase, tagged
@tagged('post_install', '-at_install')
class TestFusionReportCommentary(TransactionCase):
def setUp(self):
super().setUp()
self.report = self.env.ref('fusion_accounting_reports.report_pnl')
def test_create_minimal(self):
c = self.env['fusion.report.commentary'].create({
'report_id': self.report.id,
'period_from': date(2026, 4, 1),
'period_to': date(2026, 4, 30),
'summary': 'Test summary.',
'highlights': ['point 1', 'point 2'],
})
self.assertEqual(c.summary, 'Test summary.')
self.assertEqual(c.highlights, ['point 1', 'point 2'])
self.assertEqual(c.generated_by, 'on_demand')
def test_uniqueness_per_period(self):
self.env['fusion.report.commentary'].create({
'report_id': self.report.id,
'period_from': date(2026, 4, 1),
'period_to': date(2026, 4, 30),
'comparison_mode': 'none',
})
with self.assertRaises(Exception):
self.env['fusion.report.commentary'].create({
'report_id': self.report.id,
'period_from': date(2026, 4, 1),
'period_to': date(2026, 4, 30),
'comparison_mode': 'none',
})
def test_different_comparison_modes_can_coexist(self):
for mode in ['none', 'previous_period', 'previous_year']:
self.env['fusion.report.commentary'].create({
'report_id': self.report.id,
'period_from': date(2026, 5, 1),
'period_to': date(2026, 5, 31),
'comparison_mode': mode,
})
count = self.env['fusion.report.commentary'].search_count([
('report_id', '=', self.report.id),
('period_from', '=', date(2026, 5, 1)),
])
self.assertEqual(count, 3)