|
| 1 | +# Savant: CurrencySelectionAdvisor (id 9 · family 0x62 · lane L12) |
| 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:31`). Induction -> `QueryStrategy::CamWide`. Style Analytical inherited |
| 8 | +> from 0x62 SMBAccounting. (This is the only L12 savant on a named `ReasoningKind` rather than `Other`.) |
| 9 | +
|
| 10 | +## What it decides (AXIS-B core) |
| 11 | +Decide **which currencies the tenant should enable** (activate on `res.currency`), given the geography of |
| 12 | +its partners and transactions. The hard constraints are deterministic (you cannot deactivate a currency |
| 13 | +still used as some company's `currency_id`; activating any second currency auto-adds the |
| 14 | +`group_multi_currency` toggle — L12 R6); the ambiguous core is the *forward-looking recommendation*: from |
| 15 | +the spread of partner countries, invoice/bill currencies seen, and bank-journal currencies, induce which |
| 16 | +not-yet-enabled currencies are worth turning on. Output is a ranked set of currencies to enable with NARS |
| 17 | +`(frequency, confidence)`; woa-rs surfaces it as a suggestion the tenant confirms (Iron Rule 7), never an |
| 18 | +auto-activation. |
| 19 | + |
| 20 | +## Deterministic guard (AXIS-A — stays in woa-rs) |
| 21 | +The closed-form rules stay in woa-rs (`res_currency.py:L83-106`, L12 R6): the active-currency count > 1 ⇒ |
| 22 | +add `group_multi_currency`; **cannot deactivate** a currency still referenced as a company `currency_id` |
| 23 | +(hard block). The rounding write-protection (cannot raise rounding / set 0 once `_has_accounting_entries`, |
| 24 | +`account/res_currency.py:L26-41`, L12 R7) and the company-tree currency root-delegation |
| 25 | +(`_get_company_root_delegated_field_names()=['currency_id']`, branches inherit root currency, |
| 26 | +`res_company.py:L341-418`, L12 R9) are deterministic. The savant is invoked only for the *which-to-enable* |
| 27 | +recommendation, never for the activate/deactivate guard itself. |
| 28 | + |
| 29 | +## Slot 1 — Evidence (Arrow EvidenceRef) |
| 30 | +The geography signal `EvidenceRef { table: "tenant.currency_geography", schema_fingerprint, rows }` |
| 31 | +(one row per observed currency / country bucket): |
| 32 | + |
| 33 | +| column | dtype | signal | |
| 34 | +|---|---|---| |
| 35 | +| `currency_id` | `Int64` | a currency seen in evidence (or a candidate to enable) | |
| 36 | +| `currency_code` | `Utf8` | ISO code (e.g. `USD`, `CHF`, `GBP`) — the recommendation unit | |
| 37 | +| `is_active` | `Boolean` | whether already enabled (the reasoner recommends among `is_active=false`) | |
| 38 | +| `partner_country_count` | `Int64` | how many partners are in the country/zone using this currency — the primary geography signal | |
| 39 | +| `invoice_currency_count` | `Int64` | how many invoices/bills already carry this currency (latent demand even if not enabled) | |
| 40 | +| `bank_journal_count` | `Int64` | how many bank journals are denominated in this currency | |
| 41 | +| `is_company_currency` | `Boolean` | whether some company uses it as `currency_id` (cannot be disabled — AXIS-A hard fact, L12 R6) | |
| 42 | +| `last_seen_date` | `Date32`/nullable | recency of the most recent transaction in this currency (recent demand weighs more) | |
| 43 | + |
| 44 | +## Slot 2 — Odoo field → signal map (cite L-doc file:lines) |
| 45 | +- `group_multi_currency` auto-toggle when active count > 1; cannot deactivate a currency used as a company `currency_id` <- `L12-MULTICOMPANY-CURRENCY.md:40-42` (R6; `res_currency.py:L83-106`). |
| 46 | +- rounding write-protection once `_has_accounting_entries` (currency in use by any AML) <- `L12-MULTICOMPANY-CURRENCY.md:44-45` (R7; `account/res_currency.py:L26-41`). |
| 47 | +- company-tree currency root-delegation (branches inherit root currency; multi-currency = different ROOT companies) <- `L12-MULTICOMPANY-CURRENCY.md:51-52` (R9; `res_company.py:L96-104, L341-418`). |
| 48 | +- `_get_rates` company/root-scoped rate availability (a currency is only useful if rates exist for it) <- `L12-MULTICOMPANY-CURRENCY.md:31-32` (R3; `res_currency.py:L120-139`). |
| 49 | +- delegation tuple `name=CurrencySelectionAdvisor family=0x62 reasoning=NextBestAction inference=Induction semiring=NarsTruth style=Analytical — suggest which currencies to enable based on partner/transaction geography` <- `L12-MULTICOMPANY-CURRENCY.md:40-42` (R6 savant seed) and `SAVANTS.md:62`. |
| 50 | + |
| 51 | +## Slot 3 — Property-level alignment |
| 52 | +**N/A — class-level pivots only; no `owl:equivalentProperty` defined.** `odoo_alignment.rs` holds only |
| 53 | +class-level `owl:equivalentClass` rows and **zero** property IRIs (`odoo_alignment.rs:14, 60-68`). |
| 54 | +`res.currency -> fibo:Currency` is an L-doc-proposed class-level row (`L12-MULTICOMPANY-CURRENCY.md:15`) |
| 55 | +and is not yet among the realized seed rows in `odoo_alignment.rs` (which seed partner/account/move/ |
| 56 | +product/uom/hr). The which-currency-to-enable recommendation traverses no property IRI and crosses no |
| 57 | +SKR/ZUGFeRD seam — partner country and transaction-currency counts are scalar features, not an ontology |
| 58 | +traversal. Never invent IRIs. |
| 59 | + |
| 60 | +## Slot 4 — AXIS-B decision in evidence terms |
| 61 | +Let E = the per-currency geography rows (slot 1), restricted to candidates with `is_active = false`. |
| 62 | + |
| 63 | +-> Conclusion C = `RecommendEnableCurrencies({currency_id…})` — a ranked set — emitted with NARS |
| 64 | +`(frequency, confidence)` where: |
| 65 | +- **frequency** of "enable currency X" rises with: a high `partner_country_count` in X's zone, existing |
| 66 | + `invoice_currency_count`/`bank_journal_count` in X (latent demand the tenant is already transacting), |
| 67 | + and recent `last_seen_date`. |
| 68 | +- **frequency** is near-certain (and the recommendation moot) for any currency with `is_company_currency` |
| 69 | + or already `is_active`; those are AXIS-A facts, not recommendations. |
| 70 | +- **confidence** is the NARS weight from the volume of corroborating evidence (many partners AND many |
| 71 | + invoices in X ⇒ high; a single stray foreign invoice ⇒ low); a thin history keeps confidence low even |
| 72 | + at high frequency. Capped by phi-1. |
| 73 | + |
| 74 | +Discriminating features (ranked): `partner_country_count` >> `invoice_currency_count` > |
| 75 | +`bank_journal_count` > `last_seen_date` recency. Induction here is "tenants with this geography footprint |
| 76 | +have historically benefited from enabling these currencies." Enabling the first additional currency also |
| 77 | +flips `group_multi_currency` (AXIS-A side-effect, L12 R6) — the savant flags that consequence but the |
| 78 | +guard performs it. |
| 79 | + |
| 80 | +## Parity / GoBD notes |
| 81 | +Pure configuration advice — no posting, no GoBD Festschreibung interaction. Suggestion-only (Iron Rule 7): |
| 82 | +the tenant confirms each activation; the deterministic guards then enforce the irreversibility edges |
| 83 | +(cannot disable an in-use currency, L12 R6; cannot retroactively coarsen rounding once entries exist, |
| 84 | +L12 R7). The one parity caution: enabling a currency commits the tenant to maintaining its rate table |
| 85 | +(L12 R3 `_get_rates` falls back to 1.0 when no rate exists, which would silently mis-convert) — so the |
| 86 | +recommendation should pair an enable suggestion with a "rates required" note rather than imply the currency |
| 87 | +is immediately safe to transact. |
0 commit comments