# A — `maptara_san_provider_base` anatomy

Root: `/home/liviuxyz/workspace/OdooProjects/maptara-demo/maptara_san_provider_base`
Produced: 2026-09-18. Read-only survey. Every claim carries file:line.

## 1. File inventory

Baseline **confirmed exactly**: 23 `.py`, 4658 lines. Plus 4 `.xml` / 486 lines, 1 `.js` / 91 lines, 1 `.csv` / 13 lines, 1 `.po` / 665 lines.

| File | Lines | Purpose |
|---|---|---|
| `__init__.py` | 4 | Imports `post_init_hook_egeko`, `models`, `wizard` |
| `__manifest__.py` | 50 | Manifest; carries `post_init_hook_egeko` (:18) |
| `hooks.py` | 74 | Creates the "Egeko: Poll Cost Estimate Statuses" cron, idempotently |
| `models/__init__.py` | 4 | — |
| `models/maptara_provider.py` | 819 | 2 mapping models + `maptara.service.provider` extension + `maptara.provider.activity` (routing, chatter, ZIP export) |
| `models/maptara_provider_ref_burn.py` | 121 | Spent-KV-Nr ledger written on its own cursor |
| `models/sale_order.py` | 508 | `sale.order` fields, warning centre, send/resend buttons, poll cron body |
| `wizard/__init__.py` | 3 | — |
| `wizard/maptara_send_ekv_wizard.py` | 784 | Send eKV wizard + document line model; ref allocation, upload, warnings |
| `wizard/maptara_confirm_send_ekv_wizard.py` | 19 | "Already sent — are you sure?" confirm step |
| `migrations/19.0.0.3/pre-migrate.py` | 53 | `company_id` (required, one) → `company_ids` (many, optional) |
| `migrations/19.0.0.7/post-migrate.py` | 57 | Recompute stored eKV warnings after predicate narrowed |
| `migrations/19.0.0.8/post-migrate.py` | 75 | Rewrite stored LEGS advice text |
| `migrations/19.0.0.14/post-migrate.py` | 80 | Backfill `send_by_default` from `EKV_DEFAULT_TYPE_CODES` |
| `tests/__init__.py` | 10 | — |
| `tests/test_provider_routing.py` | 277 | Unroutable activities |
| `tests/test_provider_company.py` | 251 | Branch/credential coverage |
| `tests/test_ekv_warnings.py` | 298 | LEGS warning centre |
| `tests/test_send_ekv_image_warning.py` | 113 | Empty-appendix banner |
| `tests/test_send_ekv_mdm_source.py` | 237 | MDM-sourced wizard lines |
| `tests/test_ekv_media_type.py` | 282 | typeCode mapping + `send_by_default` |
| `tests/test_ekv_internal_ref.py` | 428 | KV-Nr allocation + burn ledger |
| `tests/test_send_chatter.py` | 111 | Send-outcome chatter |

XML: `views/maptara_provider_views.xml` 218 (provider form/list/search, activity form/list/search, action, menu), `wizard/maptara_send_ekv_wizard_views.xml` 154, `views/sale_order_views.xml` 95, `wizard/maptara_confirm_send_ekv_wizard_views.xml` 19.

## 2. Models — 9 classes, 70 field definitions

| Model | file:line | Fields | Generic? |
|---|---|---|---|
| `maptara.provider.document.type` (`_name`) | `models/maptara_provider.py:64` | 4 (`:67,:71,:75,:79`) | **Generic.** "a document type maps to a provider code" is exactly the driver-layer concept. |
| `maptara.provider.uom` (`_name`) | `models/maptara_provider.py:133` | 3 (`:136,:140,:144`) | **Generic**, and inert — nothing in this module reads `uom_ids` at all. |
| `maptara.service.provider` (`_inherit`) | `models/maptara_provider.py:157` | +11 (`:159`–`:187`) | **Mostly generic** (credentials, branches, routing). Base model is 25 lines in `maptara_base/models/maptara_service_provider.py:7` with 3 fields. |
| `maptara.provider.activity` (`_name`) | `models/maptara_provider.py:461` | 17 (`:476`–`:550`) | **Half generic.** "an exchange happened" is generic; the `document_type` selection at `:499` is a five-value eKV state machine (`cost_estimate`, `cost_estimate_status`, `cost_estimate_documents`, `resend_cost_estimate`, `message`) and `request_data`/`response_data` are named "XML File" (`:531`, `:538`). A SFTP or REST driver stores neither. |
| `maptara.provider.ref.burn` (`_name`) | `models/maptara_provider_ref_burn.py:37` | 5 (`:56`–`:67`) | **Egeko-shaped by label, generic by mechanism.** Field label is literally `'eGeKo KV-Nr'` (`:63`). The separate-cursor idempotency-token ledger is reusable; the name is not. |
| `sale.order` (`_inherit`) | `models/sale_order.py:13` | +6 (`:15`–`:52`) | **eKV-shaped.** `cost_estimate_completed` (`:27`) hardcodes the three-activity Egeko lifecycle at `:212-216`. |
| `maptara.send.ekv.wizard` (`_name`) | `wizard/maptara_send_ekv_wizard.py:13` | 13 (`:17`–`:154`) | **Egeko-shaped.** See §3. |
| `maptara.send.ekv.line.wizard` (`_name`) | `wizard/maptara_send_ekv_wizard.py:667` | 10 (`:670`–`:752`) | **Generic-ish** — a two-store file picker. Nothing Egeko in it. |
| `maptara.confirm.send.ekv.wizard` (`_name`) | `wizard/maptara_confirm_send_ekv_wizard.py:9` | 1 (`:12`) | Generic, trivial. |

## 3. EGEKO LEAKAGE

**169 lines across the module match `egeko|eGeKo|Egekonummer|optadata|X01|A01|Sonstige`. 106 of them are outside `tests/`. 10 of the 14 non-test `.py` files contain at least one.** Clean non-test files: `models/__init__.py`, `wizard/__init__.py`, `wizard/maptara_confirm_send_ekv_wizard.py`, `migrations/19.0.0.8/post-migrate.py`.

### 3a. Structural — breaks if you rename Egeko

| Evidence | file:line |
|---|---|
| `"post_init_hook": "post_init_hook_egeko"` | `__manifest__.py:18` |
| `from .hooks import post_init_hook_egeko` | `__init__.py:2` |
| `def post_init_hook_egeko(env)` | `hooks.py:11` |
| `def _ensure_egeko_cron(env)` | `hooks.py:25` |
| Cron record named `'Egeko: Poll Cost Estimate Statuses'`, created unconditionally on install of **base** | `hooks.py:54` |
| `sale.order._auto_init` re-runs `_ensure_egeko_cron` on every `-u` | `models/sale_order.py:328-330` |
| `def action_new_egeko_code` — public button name, bound from XML | `wizard/maptara_send_ekv_wizard.py:498`, `wizard/maptara_send_ekv_wizard_views.xml:44` |

The cron is the sharpest one: install base with zero provider modules and you still get a 15-minute job named after a vendor, pointing at `model._cron_process_cost_estimates()` (`hooks.py:57`).

### 3b. Egeko vocabulary baked into base UI strings

| Evidence | file:line |
|---|---|
| Field label `'eGeKo KV-Nr'` | `wizard/maptara_send_ekv_wizard.py:39`, `models/maptara_provider_ref_burn.py:63` |
| Field label `'Let eGeKo assign the KV-Nr'` | `wizard/maptara_send_ekv_wizard.py:51` |
| Warning text hardcodes `"sent as X01 (Sonstige)"` and `"A01 for a prescription"` — opta data's code list, in base | `wizard/maptara_send_ekv_wizard.py:177-180` |
| Chatter headline `"eKV NOT accepted — no Egekonummer returned"` | `models/maptara_provider.py:739` |
| Chatter rows `"eGeKo KV-Nr"` / `"Egekonummer"` | `models/maptara_provider.py:744-745` |
| Chatter footer names the *eGeKo web viewer* | `models/maptara_provider.py:761-763` |
| `"Provider returned no Egekonummer; the request was not accepted."` | `models/maptara_provider.py:776` |
| XML column labels `eGeKo KV-Nr` / `Egekonummer` — 6 in provider views (`:18,:19,:163,:164,:182,:183`), 2 in sale order views (`:68,:69`) | `views/*.xml` |

Note the irony at `models/maptara_provider.py:722-726`: the base module's chatter logic explicitly documents and works around a bug in the *egeko transport's* `LogPlugin.ingress()`. The base knows how the driver behaves.

### 3c. Egeko-only assumptions that are not name-shaped

- `models/sale_order.py:333-380` — `_cron_process_cost_estimates` walks `insurance_state in ('waiting','full','partial','rejected')`. That state vocabulary belongs to `maptara_san_sale/models/sale_order.py:82`, but the *poll-then-fetch-documents* sequencing at `:418-502` is the Egeko three-call protocol, in base, with 17 log lines prefixed `"Egeko cron:"`.
- `models/sale_order.py:351` — comment: "Everything in here talks to a remote SOAP service". Base is asserting the transport.
- `models/maptara_provider.py:499-507` — activity `document_type` selection is the Egeko operation list.
- `wizard/maptara_send_ekv_wizard.py:434-453` — `_get_ref_family` is built around the German refusal text `"Die Leistungserbringer KV-Nr. ... ist vom Kunden '402921484' bereits in einem anderen Vorgang vergeben."` The `base`, `base-1`, `base-2` suffix scheme (`:475-496`) is an Egeko-collision workaround living in base.
- `views/sale_order_views.xml:25` — a comment in base's XML cites `maptara_san_provider_egeko/models/maptara_provider.py:211` by file and line. Base documents itself against the plugin.
- `models/maptara_provider.py:22-24` — `EKV_APPENDIX_MIMETYPES` is 4 MIME types with a comment admitting it lives here rather than in egeko only so the two sides agree. That is a driver capability declaration wearing a constant's clothes.
- Migrations `19.0.0.3/pre-migrate.py:7,19`, `19.0.0.7/post-migrate.py:8`, `19.0.0.14/post-migrate.py:20` all reason about Egeko and MIP by name.

## 4. Transport / protocol code — **there is none**

This is the good news and it is unambiguous. Complete import list of non-test, non-ORM imports in the module:

```
models/maptara_provider.py:3   from contextlib import contextmanager
models/maptara_provider.py:5   from markupsafe import Markup
models/maptara_provider.py:10  import base64
models/maptara_provider.py:12  import io
models/maptara_provider.py:13  import zipfile
wizard/maptara_send_ekv_wizard.py:7  import re
```

Zero `requests`, zero `zeep`/`suds`, zero `lxml`/`etree`/`xml.*`, zero `paramiko`/`ftplib`, zero `ssl`. Verified by grep across all `.py`. SOAP appears only in **comments** (`models/sale_order.py:351`, `models/maptara_provider.py:724`) and in **test mocks** (`tests/test_send_chatter.py:80,95`).

The only binary handling is `action_export` at `models/maptara_provider.py:554-579` — zips `request_data` + `response_data` into `export_zip_data`. That is an audit-log exporter, not a transfer.

Credentials/endpoint config: `username`/`password`/`client_key` on the provider (`models/maptara_provider.py:163-165`), plus an unused-by-base `url` Char on the activity (`models/maptara_provider.py:518`, labelled "Provider API URL"). The actual endpoint configuration lives in the plugin — `maptara_san_provider_egeko/models/res_config_settings.py` (130 lines) and `egeko_request.py` (575 lines).

**Verdict: transport is already fully isolated.** All 3930 lines of protocol work sit in `maptara_san_provider_egeko`. The split problem is not "extract the transport"; it is "extract the eKV domain from the provider domain".

## 5. Extension points that already exist — keep these

1. **Suffix dispatch.** `_provider_method(operation)` at `models/maptara_provider.py:599-611` does `getattr(self, f'_{operation}_{provider_type}', None)`. `_require_provider_method` (`:613-642`) raises three distinguishable errors; `_log_unroutable` (`:644-671`) is the non-raising background variant. Six operations route through it: `send_cost_estimate` (`:680`), `resend_cost_estimate` (`:687`), `_get_state_cost_estimate` (`:779`), `_get_cost_estimate` (`:796`), `_get_cost_estimate_document` (`:806`), `_send_message` (`:816`). The egeko module supplies all six at `maptara_san_provider_egeko/models/maptara_provider.py:444,468,506,592,626,673`.
2. **Same pattern on credentials.** `action_test_connection` (`models/maptara_provider.py:404-438`) does `getattr(self, f'_test_connection_{provider_type}')`; egeko implements `_test_connection_egeko` at `maptara_san_provider_egeko/models/maptara_provider.py:76`.
3. **Empty selection field.** `provider_type = fields.Selection([], ...)` at `models/maptara_provider.py:162`; egeko adds via `selection_add=[('egeko','Egeko')]` at `maptara_san_provider_egeko/models/maptara_provider.py:72-73`. This is the registry.
4. **`_get_ekv_position_lines`** (`models/sale_order.py:72-98`) — documented override seam; egeko narrows it.
5. **`_get_ekv_warning_lines`** (`models/sale_order.py:100-180`) — documented super()-then-append seam.
6. **`_media_type_mapping` / `media_type_code` / `media_type_default`** (`models/maptara_provider.py:192-235`) — single read point for the code table; base deliberately returns `False` rather than a fallback code (`:196-198`).
7. **`_selection_context`** (`wizard/maptara_send_ekv_wizard.py:400-410`) — the wizard→driver handoff is two context keys, `selected_document_ids` and `selected_mdm_attachment_ids`. This is already the driver interface, it is just undeclared.
8. **`_chatter_on_send` contextmanager** (`models/maptara_provider.py:702-717`) — provider-agnostic outcome logging wrapped around every routed send.

**No `models.AbstractModel` anywhere in the module.** Grep returns zero. The extension mechanism is stringly-typed `getattr`, not an abstract interface — which works and is well-tested, but gives no place to declare capabilities (does this driver support `send_message`? MIME types? async polling?).

## 6. The eKV flow, end to end

```
sale.order.action_send_insurance                 models/sale_order.py:248
  ├─ _check_ece_provider_company                 models/sale_order.py:222
  ├─ validates patient / insurance fields        models/sale_order.py:263-284
  └─ opens maptara.send.ekv.wizard               models/sale_order.py:287-297

maptara.send.ekv.wizard.default_get              wizard/...:196
  └─ _prepare_document_lines                     wizard/...:233
       ├─ documents.document via claim_request    wizard/...:236-244
       ├─ _mdm_attachments (MDM store, direct)    wizard/...:274-316
       └─ _ticked_by_default -> provider.media_type_default   wizard/...:251

action_send                                      wizard/...:321
  ├─ _materialise_uploads                        wizard/...:339
  ├─ _validate_document_selection                wizard/...:382   (never blocks)
  └─ _send_ekv(selected)                         wizard/...:542
       ├─ existing activities? -> maptara.confirm.send.ekv.wizard   wizard/...:550-558
       ├─ ref: let_provider_assign_ref / typed / _generate_internal_ref   wizard/...:565-569
       ├─ _create_activity                       wizard/...:531
       ├─ activity.with_context(**_selection_context).send_cost_estimate()   wizard/...:572-573
       │    └─ activity.send_cost_estimate       models/maptara_provider.py:680
       │         ├─ _chatter_on_send             models/maptara_provider.py:702
       │         └─ _require_provider_method('send_cost_estimate')
       │              -> _send_cost_estimate_egeko   (egeko module:444)
       └─ on exception: ref.burn(...) on separate cursor, then re-raise   wizard/...:588-595
```

**`maptara_confirm_send_ekv_wizard`** sits between `_send_ekv`'s duplicate check and the actual send: `action_confirm` (`wizard/maptara_confirm_send_ekv_wizard.py:17-20`) browses the parent wizard from `context['active_id']`, re-derives the selection itself, and calls `_send_ekv` **directly with `confirm_send=True`** — bypassing `action_send`, therefore bypassing `_materialise_uploads` and `_validate_document_selection`. This is stated as known in the base's own comments (`wizard/maptara_send_ekv_wizard.py:105-109`, `:388-394`).

Interpreting the response is **entirely** in the plugin — base never parses anything. Base only reads `external_ref` after the fact (`models/maptara_provider.py:733`).

Return path: `hooks.py` cron → `sale.order._cron_process_cost_estimates` (`models/sale_order.py:333`) → `_process_single_order_estimate` (`:382`) → `_get_state_cost_estimate` → `_get_cost_estimate` → `_get_cost_estimate_document`, each routed through `_provider_method`.

## 7. Licence / authorship

- `"author": "baotnp"` — `__manifest__.py:44`. **1 occurrence.**
- `"license": 'OPL-1'` — `__manifest__.py:49`. **1 occurrence.**
- `'website': "#"` — `__manifest__.py:45`.
- **Zero** per-file copyright headers, zero SPDX identifiers, zero occurrences of "baotnp" outside the manifest. Grep over all `.py`/`.xml`/`.js` returns exactly the two manifest lines.
- Sibling `maptara_san_provider_egeko/__manifest__.py` carries the identical `"author": "baotnp"` / `"license": 'OPL-1'` pair.

OPL-1 is Odoo's proprietary licence. Any generic driver layer intended for reuse or third-party plugins inherits that constraint; changing it requires knowing what baotnp's relationship to the code is, which cannot be determined from the repo. **UNKNOWN** — would need the contract or a CLA.

## 8. Test coverage

**8 test files, 112 `def test_` methods, ~182 `self.assert*` call lines** (line-counted, so multi-line asserts undercount). All 8 classes are `@tagged('post_install', '-at_install')`; `test_ekv_internal_ref.py` has 3 separate tagged classes (`:35`, `:184`, `:321`).

| File | tests | asserts | What it actually asserts |
|---|---|---|---|
| `test_ekv_internal_ref.py` | 23 | 26 | KV-Nr never reissued: family scoping not order scoping, numeric not lexicographic suffix sort, burn ledger survives rollback, `action_new_egeko_code` twice gives two numbers, empty-ref escape hatch |
| `test_ekv_media_type.py` | 20 | 25 | `media_type_code` returns `False` (not `'X01'`) when unmapped; `send_by_default` seeding; per-provider isolation; re-tick on provider change |
| `test_ekv_warnings.py` | 13 | 32 | LEGS warning fires, counts correctly, never blocks, survives empty recordsets (guards against the `TypeError`-in-a-stored-compute failure) |
| `test_provider_company.py` | 18 | 26 | Group-wide vs branch-locked credential shapes; `_for_company` precedence; both `@api.constrains`; `action_expand_branches` incl. `test_a_removed_branch_stays_removed` |
| `test_provider_routing.py` | 17 | 27 | Unroutable activities raise on foreground paths and log-not-raise on background; `ondelete='restrict'`; the nine dead stage-test rows |
| `test_send_ekv_mdm_source.py` | 11 | 21 | MDM attachments reach wizard lines without being copied; the `_exactly_one_source` SQL CHECK (uses `psycopg2.IntegrityError`) |
| `test_send_ekv_image_warning.py` | 6 | 9 | Both empty-appendix states produce distinct banner texts; negative case |
| `test_send_chatter.py` | 4 | 16 | Chatter posts on success, failure and exception; `sent`→`error` downgrade when no `external_ref` |

The suite tests the *routing and allocation* logic hard and the *transport* not at all — correctly, since there is none here. Egeko-specific counterparts live in `maptara_san_provider_egeko/tests/` (7 files, incl. `test_wire_format.py` 256 and `test_connection_probe.py` 258). Test docstrings cite real incident dates and row counts throughout, which makes this suite unusually good regression insurance for a refactor — **do not rewrite the tests as part of the split.**

---

## Parking Lot

- **`maptara.provider.uom` is dead weight.** Defined at `models/maptara_provider.py:133`, exposed in the form at `views/maptara_provider_views.xml:93-97`, has ACL rows (`security/ir.model.access.csv:12-13`) — and `uom_ids` is read by nothing in either module. Grep for `uom_ids` outside the definition and the view returns zero hits. *Impact: free deletion, or free generic-layer citizen. Confidence: high.*
- **`maptara.service.provider` is two models in one trench coat.** Base (`maptara_base/models/maptara_service_provider.py:7`) defines it as the Leistungserbringer; this module bolts ECE credentials on. The constraint at `models/maptara_provider.py:311-351` is scoped to "rows carrying a secret" purely to route around that, and its own docstring says "The real fix is to stop the model being dual-purpose". *Impact: the split is the natural moment to do it. Confidence: high — documented in-code.*
- **`maptara.provider.activity.url` and `.data` appear unused by base.** `:518` and `:520`; no reads in this module. Possibly written by the egeko module. *Impact: unknown until checked in the plugin. Confidence: medium — the plugin was not grepped for writes.*
- **The confirm wizard skips upload materialisation.** `wizard/maptara_confirm_send_ekv_wizard.py:19-20` re-filters lines and calls `_send_ekv` directly, so a file sitting in `upload_attachment_ids` on the resend-confirm path never becomes a document. The base comments acknowledge the bypass for validation (`wizard/maptara_send_ekv_wizard.py:105-109`) but do not mention uploads. *Impact: possible silently-dropped attachment on the confirm path. Confidence: medium — not traced to a reproducer, and the confirm path is only reachable for orders with prior activity.*
- **`hooks.py` creates a cron with `noupdate: True` and `_auto_init` recreates it on every upgrade.** `models/sale_order.py:328-330`. Idempotent by `env.ref`, so probably fine, but it means a user who deletes the cron gets it back on the next `-u` with no way to opt out. *Impact: low. Confidence: high.*
