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:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
'name': 'Fusion Accounting — Bank Reconciliation',
|
||||
'version': '19.0.1.0.0',
|
||||
'version': '19.0.1.0.1',
|
||||
'category': 'Accounting/Accounting',
|
||||
'sequence': 28,
|
||||
'summary': 'Native V19 bank reconciliation widget with AI confidence scoring + behavioural learning.',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import fusion_reconcile_pattern
|
||||
from . import fusion_reconcile_precedent
|
||||
|
||||
@@ -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.',
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
"""Per-historical-decision reconciliation memory.
|
||||
|
||||
One row per past reconciliation. Holds the full feature vector + outcome,
|
||||
used by precedent_lookup for K-nearest-neighbour search when scoring a
|
||||
new bank line.
|
||||
"""
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class FusionReconcilePrecedent(models.Model):
|
||||
_name = "fusion.reconcile.precedent"
|
||||
_description = "Historical bank reconciliation decision (memory)"
|
||||
_order = "reconciled_at desc, id desc"
|
||||
|
||||
company_id = fields.Many2one('res.company', required=True, index=True,
|
||||
default=lambda self: self.env.company)
|
||||
partner_id = fields.Many2one('res.partner', index=True)
|
||||
|
||||
# Bank line features (the "input")
|
||||
amount = fields.Monetary(currency_field='currency_id')
|
||||
currency_id = fields.Many2one('res.currency')
|
||||
date = fields.Date()
|
||||
memo_tokens = fields.Char(
|
||||
help="Comma-separated normalized memo tokens (output of memo_tokenizer)")
|
||||
journal_id = fields.Many2one('account.journal')
|
||||
|
||||
# Outcome (the "decision made")
|
||||
matched_move_line_count = fields.Integer(
|
||||
help="1 = exact, 2-3 = consolidation, etc.")
|
||||
matched_account_ids = fields.Char(
|
||||
help="Comma-separated account.account IDs that were matched against")
|
||||
matched_invoice_ages_days = fields.Char(
|
||||
help="Comma-separated days-old at reconcile time, e.g. '12, 45, 78'")
|
||||
write_off_amount = fields.Float()
|
||||
write_off_account_id = fields.Many2one('account.account')
|
||||
exchange_diff = fields.Boolean()
|
||||
|
||||
# Provenance
|
||||
reconciler_user_id = fields.Many2one('res.users')
|
||||
reconciled_at = fields.Datetime()
|
||||
source = fields.Selection([
|
||||
('historical_bootstrap', 'Imported from history'),
|
||||
('manual', 'Manual reconcile via fusion'),
|
||||
('ai_accepted', 'AI suggestion accepted'),
|
||||
('auto_rule', 'account.reconcile.model auto-fired'),
|
||||
], required=True)
|
||||
|
||||
# No uniqueness constraint — multiple reconciles can share features
|
||||
@@ -1 +1,5 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_fusion_reconcile_pattern_user,pattern user,model_fusion_reconcile_pattern,fusion_accounting_core.group_fusion_accounting_user,1,0,0,0
|
||||
access_fusion_reconcile_pattern_admin,pattern admin,model_fusion_reconcile_pattern,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
|
||||
access_fusion_reconcile_precedent_user,precedent user,model_fusion_reconcile_precedent,fusion_accounting_core.group_fusion_accounting_user,1,0,0,0
|
||||
access_fusion_reconcile_precedent_admin,precedent admin,model_fusion_reconcile_precedent,fusion_accounting_core.group_fusion_accounting_admin,1,1,1,1
|
||||
|
||||
|
Reference in New Issue
Block a user