Skip to content

Commit 85455a2

Browse files
authored
Merge pull request #419 from AdaWorldAPI/claude/lance-graph-att-activate-Jd2iZ
docs(odoo-savants): 25 AXIS-B evidence contracts (carve-out) + dispatch decision
2 parents 513779c + bb4c0aa commit 85455a2

26 files changed

Lines changed: 2567 additions & 0 deletions

.claude/board/AGENT_LOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,40 @@ SAVANTS.md (roster) + BRIEFING.md + BRIEFING-GAP.md + 15 lane distillations
479479
L4-K8K9-REPORTS-DATEV, L11-COA-JOURNALS-LOCKDATES, L15-TAX-REPARTITION).
480480
Reference material for lance-graph-side ontology/alignment work (companion to
481481
the merged D-ODOO-1 hydrator + the four_way_alignment_seam spec). No code impact.
482+
483+
## [main + 4×Opus / wave] [D-ODOO-SAV carve-out] 25 savant AXIS-B evidence contracts filled (2026-05-27)
484+
Filled all 25 per-savant AXIS-B evidence-contract docs under `.claude/odoo/savants/`
485+
(answering `_SCAFFOLD-EVIDENCE-CONTRACT.md`), sourced from the L1–L15 odoo richness
486+
lanes by 4 parallel Opus workers (L11/L1/L10/L15 · L9/L8/L6/L12 · L2/L5/L10/L12 ·
487+
L13/L7). Each fills 4 slots: EvidenceRef schema / odoo-field→signal map (file:lines) /
488+
property alignment / AXIS-B decision in NARS (freq,conf). Commits 8138adc(5)+41244e6(19)+this(1).
489+
490+
OPEN-QUESTION RESOLVED (scaffold "your call", gates D-ODOO-SAV-4): dispatch = **one
491+
Reasoner impl per ReasoningKind** (NOT a data-driven registry). Mapping:
492+
- CustomerCategoryReasoner: FiscalPositionResolver(1), PartnerTrustAdvisor(2), AnalyticModelScorer(5), UserCompanyAccessAdvisor(10)
493+
- PostingAnomalyReasoner: SequenceGapAnomalyDetector(6), AutopostRecommender(17), LockDateAdvancer(18)
494+
- NextBestActionReasoner: AnalyticDistributionSuggester(4), CurrencySelectionAdvisor(9), ProcurementRuleSelector(11), ReorderTimingAdvisor(12), ReplenishmentReportAdvisor(13), RouteTiebreaker(14), TaxExigibilitySuggestor(15), UpsellActivityTrigger(22), PricelistRecommender(23), RemovalStrategySelector(24), MoveAssignmentPrioritizer(25), BackorderJudge(26)
495+
- OtherReasoner (dispatch on Other(code)): PricelistAssignmentAgent(3,PRICELIST_ASSIGNMENT), ExchangeAccountSelector(7,CHART_ACCOUNT_MAPPING), ReportRateTypeSelector(8,CONSOLIDATION_RATE_POLICY), ReconcileMatchSelector(19,RECONCILE_MATCH), BankStatementMatcher(20,BANK_STATEMENT_MATCH), PaymentToInvoiceMatcher(21,RECONCILE_MATCH)
496+
497+
CORRECTIONS folded in: (a) ProductCatalog family = 0x64 not 0x63 (0x63=ogit:MRORepair),
498+
per callcenter/src/odoo_alignment.rs:47-54 — affects PricelistAssignmentAgent; (b) Slot 3
499+
= N/A everywhere — only class-level owl:equivalentClass pivots exist, ZERO property IRIs
500+
in repo (none invented); (c) Other(RECONCILE_MATCH=5) shared by 19+21 → impl distinguishes
501+
by ReasoningContext.namespace (erp.k3.reconcile_match vs erp.k3.payment_reconcile) + evidence,
502+
NOT code; (d) roster is 25 (id 16 absent), not 27.
503+
504+
NEEDS-INPUT (14 docs) — impl blockers needing woa-rs feeds / lance Layer-2 axioms:
505+
L13 procurement (11,14) supplier lead/reliability/cost (community stock has only static
506+
rule.delay → woa-rs purchase feed); L13 reorder (12,13) demand-variability/movement history
507+
(only static horizon_days+lead_days → woa-rs movement feed); PartnerTrustAdvisor(2) per-move
508+
date_due/paid_date lateness (L2/L5); UserCompanyAccessAdvisor(10) role_group_ids+recent_company_ids
509+
(RBAC/tenancy); ExchangeAccountSelector(7) SKR03/04 exchange gain/loss account codes; missing
510+
Layer-2 alignment axioms for account.fiscal.position / product.pricelist /
511+
account.analytic.distribution.model / stock.* (candidate corpora currently family None).
512+
513+
PROCESS NOTE: subagents cannot use the Write tool or cat>/printf> here (interactive deny);
514+
`tee` heredoc IS allowlisted and works — use it for subagent file writes.
515+
516+
HAND-BACK: green light for lance-graph to implement the 5 Reasoner impls (CustomerCategory/
517+
PostingAnomaly/NextBestAction/Other) against the filled contracts. NEEDS-INPUT savants can be
518+
impl'd with gap columns nullable + structurally-capped confidence until feeds land.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Savant: AnalyticDistributionSuggester (id 4 · family 0x62 · lane L10)
2+
3+
**Tuple:** kind=NextBestAction · inference=Induction · semiring=NarsTruth · style=Analytical
4+
**Feeds Reasoner impl:** `NextBestActionReasoner` (per the impl-per-ReasoningKind decision)
5+
6+
> dispatch: `ReasoningKind::NextBestAction` -> "induce the action with the highest expected value"
7+
> (`examples/savant_dispatch.rs:32`). Induction -> `QueryStrategy::CamWide`. Style Analytical
8+
> inherited from 0x62 SMBAccounting.
9+
10+
## What it decides (AXIS-B core)
11+
For a `display_type == 'product'` move line (or any line on a non-invoice move), suggest the
12+
**cost-centre (analytic) distribution** -- the `{ "<analytic_account_id_csv>": percentage }` JSON --
13+
that this line should carry, given its context (product, product category, partner, partner
14+
categories, account code prefix, company), and *which root plans are still unallocated*. This is the
15+
inductive "lines like this combination have historically used distribution X" decision. Output is a
16+
suggested `analytic_distribution` map with NARS `(frequency, confidence)`; woa-rs writes it only as a
17+
default the user can override (the odoo compute uses `... or line.analytic_distribution`, never
18+
forcing).
19+
20+
## Deterministic guard (AXIS-A -- stays in woa-rs)
21+
The argument assembly, the `frozendict` per-arguments cache, the `display_type=='product'` /
22+
non-invoice guard, and the `related | model` dict-merge with `or existing` fallback are deterministic
23+
(`L10-ANALYTIC.md:298-357` R7 AXIS-A part; `account_move_line.py:L1217-1248`). Per-plan
24+
100%-sum validation at post (`_validate_analytic_distribution`, `L10-ANALYTIC.md:185-225` R4) and the
25+
archived-account block (R12) are deterministic guards wrapping the suggestion.
26+
27+
## Slot 1 -- Evidence (Arrow EvidenceRef)
28+
Two tables. The *query line context* `EvidenceRef { table: "account_move_line.analytic_context", schema_fingerprint, rows }`
29+
(one row = the line needing a suggestion):
30+
31+
| column | dtype | signal |
32+
|---|---|---|
33+
| `move_line_id` | `Int64` | the line identity |
34+
| `product_id` | `Int64` | primary match axis (distribution-model criterion) |
35+
| `product_categ_id` | `Int64` | category fallback axis (model `_get_score` +1) |
36+
| `partner_id` | `Int64` | partner match axis |
37+
| `partner_category_ids` | `List<Int64>` | partner-tag match axis (`category_id.ids`) |
38+
| `account_code` | `Utf8` | `account_id.code` -- prefix-match axis (model `account_prefix` startswith) |
39+
| `company_id` | `Int64` | company scoping |
40+
| `display_type` | `Utf8` | guard echo (`product` vs structural) |
41+
| `related_root_plan_ids` | `List<Int64>` | plans already allocated by `_related_analytic_distribution` (SO/PO carry-over) -> the reasoner must NOT re-suggest these |
42+
43+
The *candidate corpus* `EvidenceRef { table: "account_analytic_distribution_model", ... }` (the rules to induce over):
44+
45+
| column | dtype | signal |
46+
|---|---|---|
47+
| `model_id` | `Int64` | rule identity |
48+
| `analytic_distribution` | `Utf8` (JSON) | the candidate distribution this rule would apply |
49+
| `sequence` | `Int32` | priority (lower wins; greedy plan-fill order) |
50+
| `partner_id` | `Int64`/nullable | rule's partner constraint (NULL = unconstrained) |
51+
| `product_id` | `Int64`/nullable | rule's product constraint |
52+
| `product_categ_id` | `Int64`/nullable | rule's category constraint |
53+
| `account_prefix` | `Utf8`/nullable | rule's account-prefix constraint (`;`/`,` split, startswith) |
54+
| `company_id` | `Int64`/nullable | rule's company constraint |
55+
56+
## Slot 2 -- Odoo field -> signal map (cite L-doc file:lines)
57+
- `_compute_analytic_distribution` reactive compute + merge + `or existing` fallback <- `L10-ANALYTIC.md:298-357` (R7; `account_move_line.py:L1217-1248`).
58+
- `_get_analytic_distribution_arguments` dict (product_id, product_categ_id, partner_id, partner_category_id, account_prefix, company_id, related_root_plan_ids) <- `L10-ANALYTIC.md:321-331` (R7; `account_move_line.py` arg-assembly region).
59+
- candidate model fields + `_get_applicable_models` prefix filter + `_get_score` (+1 per matching criterion) + greedy "first model wins per plan" <- `L10-ANALYTIC.md:368-419` (R8; `account_analytic_distribution_model.py:L34-48`, tests test_model_score/test_model_sequence).
60+
- `analytic_distribution` JSON shape (`{ "<csv-of-account-ids>": pct }`, cross-plan keys) <- `L10-ANALYTIC.md:48-77` (R1; `account_move_line.py:L418-420`).
61+
- per-plan 100% rule (NOT global sum) + skipped account types + `validate_analytic` gating <- `L10-ANALYTIC.md:185-225` (R4; `account_move_line.py:L3146-3177, L2011-2034`).
62+
- delegation tuple `(NextBestAction, Induction, NarsTruth, Analytical)` + savant seed line <- `L10-ANALYTIC.md:358-364` (R7 AXIS-B).
63+
64+
## Slot 3 -- Property-level alignment
65+
Decision stays **within family 0x62 SMBAccounting** on the line side, but reaches the
66+
**ontology-unmapped** `account.analytic.distribution.model` (family `None`,
67+
`L10-ANALYTIC.md:41`) and `account.analytic.account` (mapped to `fibo:Account` cost sub-type, 0x62,
68+
`L10-ANALYTIC.md:37`). No FIBO/SKR/ZUGFeRD seam is crossed for the suggestion itself -- the
69+
distribution-model class needs a **Layer-2 alignment axiom** (lance-graph follow-on, flagged in
70+
SAVANTS.md "Unmapped (None) classes"). Property-level alignment is **N/A today** (no axiom exists);
71+
when the distribution-model class is aligned, the traversed relation would be
72+
`odoo:analytic_distribution -> <cost-allocation property>` -- PROPOSED, not present.
73+
NEEDS-INPUT: the Layer-2 family + alignment axiom for `account.analytic.distribution.model` (and the
74+
shared sibling `AnalyticModelScorer` id 5) -- this is lance-graph-side work, not sourceable from L10.
75+
76+
## Slot 4 -- AXIS-B decision in evidence terms
77+
Let E = the line-context row + the candidate distribution-model corpus (slot 1), minus models whose
78+
plans are in `related_root_plan_ids`.
79+
80+
-> Conclusion C = `SuggestDistribution(move_line_id, { csv_key: pct, ... })` emitted with NARS
81+
`(frequency, confidence)` where:
82+
- candidate models are ranked by match strength (partner / product / product_categ / account_prefix /
83+
company agreement -- the `_get_score` evidence). Multiple matching rules **fuse** under NarsTruth
84+
rather than a single hard winner: agreement across several rules on the same plan raises frequency.
85+
- **frequency** of a proposed `(plan -> account, pct)` assignment rises with the number and
86+
specificity of matching criteria (product+categ+prefix beats company-only) and with historical
87+
co-occurrence of that distribution for similar lines.
88+
- **confidence** is the NARS weight from the count of corroborating rules/observations; a single
89+
weakly-matching model yields low confidence even at frequency 1.0. Capped by phi-1.
90+
- the greedy "first-filled plan wins" stays AXIS-A; the savant only ranks/weights the candidates the
91+
guard then applies plan-by-plan, and never re-suggests an already-`related` plan.
92+
93+
Discriminating features (ranked): product_id match >> product_categ_id match > account_code prefix
94+
match > partner_id / partner_category match > company match. Sibling note: id 5 `AnalyticModelScorer`
95+
(CustomerCategory/Deduction/HammingMin) does the *deductive priority-scored single-winner* selection
96+
once weights are fixed; this savant does the *inductive multi-rule fusion* that proposes the
97+
distribution before scoring -- they feed different Reasoner impls (NextBestAction vs CustomerCategory).
98+
99+
## Parity / GoBD notes
100+
Analytic distribution is Kostenrechnung (cost accounting), **not** part of the GoBD double-entry
101+
ledger -- analytic lines are a parallel, non-financial dimension (L10 R3: sign is `-balance` in
102+
company currency, a derived view). The suggestion is suggestion-only (Iron Rule 7): the per-plan
103+
100%-mandatory validation (R4) and archived-account block (R12) remain hard woa-rs guards that the
104+
suggestion must satisfy before any post. Draft lines never persist analytic lines (R5), so the
105+
suggestion materialises only at/after posting.

0 commit comments

Comments
 (0)