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
50 lines
1.9 KiB
Python
50 lines
1.9 KiB
Python
"""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
|