feat(fusion_accounting_bank_rec): pattern + precedent models for behavioural learning

Adds the foundation for AI confidence scoring:
- fusion.reconcile.pattern: per-(company, partner) aggregate profile
  (volume, cadence, preferred matching strategy, memo signature,
  write-off habits) — recomputed nightly from precedents.
- fusion.reconcile.precedent: per-historical-decision memory holding
  full feature vector + outcome, used by precedent_lookup for KNN
  scoring of new bank lines.

Includes ACL rows for fusion accounting user (read) and admin (CRUD)
groups. Manifest bumped to 19.0.1.0.1.

Note: switched the pattern uniqueness rule from the deprecated
_sql_constraints attribute to models.Constraint (Odoo 19 native API)
so the unique(company_id, partner_id) is actually enforced at the
PG level — _sql_constraints is silently ignored in 19.

Made-with: Cursor
This commit is contained in:
gsinghpal
2026-04-19 10:17:29 -04:00
parent 3dc74e3987
commit 6e945dea95
5 changed files with 111 additions and 1 deletions

View File

@@ -0,0 +1,55 @@
"""Per-partner bank reconciliation pattern aggregate.
One row per (company_id, partner_id). Continuously summarises HOW this
partner gets reconciled. Recomputed nightly via cron from the precedent
table. Used as a feature input to confidence_scoring.
"""
from odoo import fields, models
class FusionReconcilePattern(models.Model):
_name = "fusion.reconcile.pattern"
_description = "Per-partner bank reconciliation pattern aggregate"
_rec_name = "partner_id"
company_id = fields.Many2one('res.company', required=True, index=True,
default=lambda self: self.env.company)
partner_id = fields.Many2one('res.partner', required=True, index=True)
# Volume + cadence
reconcile_count = fields.Integer(default=0,
help="Total past reconciles for this partner")
typical_amount_range = fields.Char(
help="e.g. '$1,200 $2,400 (median $1,847.50)'")
typical_cadence_days = fields.Float(
help="Mean inter-reconcile days")
typical_day_of_month = fields.Char(
help="e.g. '1st, 15th'")
# Matching strategy used historically
pref_strategy = fields.Selection([
('exact_amount', 'Exact-amount-first'),
('fifo', 'FIFO oldest-due-first'),
('multi_invoice', 'Multi-invoice consolidation'),
('cherry_pick', 'Cherry-pick specific invoices'),
])
pref_account_id = fields.Many2one('account.account',
help="Most-used target account")
# Memo signature
common_memo_tokens = fields.Char(
help="Comma-separated tokens that appear in ≥30% of past reconciles")
# Tax + write-off habits
common_writeoff_account_id = fields.Many2one('account.account')
common_writeoff_tax_id = fields.Many2one('account.tax')
typical_writeoff_amount = fields.Float(
help="e.g. 0.05 for rounding diffs")
last_refreshed_at = fields.Datetime()
_uniq_company_partner = models.Constraint(
'unique(company_id, partner_id)',
'One pattern row per (company, partner) — already exists.',
)