Every insurance and billing partner we integrate with — Egeko today, Noventi/azh next, MIP and ZHP X3 after, and now a direct GKV-DTA route — should plug into the same socket. Right now each one brings its own wiring, and the "vendor-neutral" base module is named after a vendor.
CR #1197 — Noventi/azh §302 billing — is the deliverable. This refactor is the thing that stops it becoming integration #3 with its own private SFTP client, and it is scoped to serve that, not the other way round. If the refactor ever looks like it is competing with #1197 for time, the refactor loses.
The wider ECE programme sits under CR #1173, with #1174 (settings consolidation) and #1271–1276 (MIP and inbound test cases) as siblings. Not in scope: eKV via AZH's KVCheck — we already do eKV.
22 Sep — CR #1319 arrived and, honestly, was expected. A Lovable-generated spec and module for §302 that puts two routes in Release 1: azh (the above) and direct GKV-DTA — EDIFACT ESOL/AUF per TA1 v21→v22, per-payer encryption, DAVASO, Dakota, KOTR. The generated module integrates with zero existing models, does not install on Odoo 19, and once fixed runs no billing process — 46 of its 142 documented claims are true, and those are field names. It does not change this architecture: direct DTA is a second plugin on the same contract, and it is Release 2. How it lands: 07. The module measured: 08.
Egeko live · azh · MIP · ZHP X3 · direct DTA (#1319, Release 2)
SOAP · SFTP · REST · file-drop (Dakota) — MIP's md5 key may add a fifth
non-test lines, 10 of 14 files
all of it lives in one plugin
Today's shape is inverted: the base carries one vendor's vocabulary — its cron, its code list, its field labels — while the plugin carries all the reusable networking. Add a provider and you edit shared code. Add five and you get five retry policies, five credential conventions, and five ways of logging patient data.
Three concrete things are waiting on it: azh §302 billing (the next ticket, needs SFTP that does not exist anywhere in the repo), MIP and ZHP X3 (committed direction, no dates), and a VDMS Excel/XML import that will want the same transports from a completely different part of the tree.
There is also a licence angle, and it is bigger than these two modules. Across all 104 manifests: 9 modules still carry "author": "baotnp" — san_claim, san_flat_rate, san_followup_order, san_industry_fsm, san_prescription_extract, san_provider_base, san_provider_egeko, san_purchase, san_sale — and 17 are OPL-1. This refactor clears two of the nine; it does not finish the 2027 job.
Within these two, the Python exposure is two manifest lines. The real exposure is 8 MB of a vendor's copyrighted PDFs plus a competitor's captured production XML sitting in the repo, which the rewrite is the natural moment to stop shipping.
A driver knows nothing.
Not the domain. Not the provider. Not where the password came from.
It moves bytes to a destination and reports what happened. The moment a SoapDriver learns what an eKV is, we have rebuilt today's problem with more files and a nicer diagram.
depends
new depends on a driver
credentials, passed in
⇢ rest driver used
All numbers are wc -l / AST counts.
wc -l or an AST count taken on 2026-09-19 — see the file map and symbol map for the per-file and per-method verdicts behind each box. Grey is untouched, and most of the graph is grey: 3 654 of 8 588 lines are tests and frozen migrations that nothing in this plan edits.Counting consumers changes which protocol is "normal". REST has 3–4 (azhIndex, MIP pending confirmation, the VDMS connector, probably the Excel/XML import). SFTP has 2. SOAP has exactly one, and it is the module we are rewriting.
So Egeko is the outlier, and the contract must not be shaped around it. WSDL type factories, positional arg0/arg1/arg2, a live zeep object graph, German comma decimals, four status buckets, zeep's private _value_1 — none of that generalises to anything else we will ever integrate. Abstracting from the strangest implementation in the set produces an interface that fits one caller badly and every other caller worse.
Shape the contract on request / response / status / retry, and let the SOAP plugin do its own awkward translation on its own side. That is the plugin's job, and it is the only place that should know what a WSDL is.
Building a generic driver for a single caller is speculative generality, and normally I would argue against it. Two reasons it still earns its place, neither of which is reuse:
1. It is the test seam. Constructing EgekoRequest today fetches a WSDL, which is why every test mocks the entire class and why zero tests exercise payload assembly. Without a seam, a misspelled wire key is caught by nothing.
2. The code already exists. It is ~200 working lines; moving them is close to free.
The viable alternative is to leave the SOAP code inside the rewritten Egeko module, behind the same contract. That is a defensible call — you lose the test seam, which is the part worth fighting for, not the driver.
Considered in detail and not doing it. It was 4–6 days for zero behaviour change, and it was the only work in the programme that could delete production records if the ir_model_data migration went wrong. The eKV domain stays inside maptara_san_provider_base. Reasoning kept in R1 §3a so nobody re-proposes it from scratch in six months.
base, not maptara_basemaptara_base is 12 124 lines and pulls in sale, account, product, portal, l10n_din5008. A module that opens an SSH session has no business dragging the accounting stack behind it. And maptara_san_base is a hidden compatibility shim — not a home for anything.
Not in the driver. It takes host, user and key as arguments; the caller owns storage — maptara.service.provider for ECE, vdms.backend for VDMS. The moment a driver grows a credential model it has an opinion about who it is talking to, and it stops being reusable.
Baseline the 112-test harness. Strip the vendor vocabulary out of the base. Fix the ACL that currently gives every internal user delete rights on every stored credential. Delete the model nothing reads. Then declare the driver contract — ~200 lines, purely additive, no migration.
1 448 lines, one 263-line method mixing ORM reads, VAT arithmetic, document downloads and wire vocabulary, with zero tests on payload assembly. Write the characterisation tests first, then rebuild it on the contract as payload-mapping only.
This is the one figure that does not compress. Being fast does not help when nothing tells you the bytes changed.
The SFTP driver (net new — paramiko appears nowhere in the repo today), plus claim-batch → submission wiring. Ships with a local-directory dry-run from day one, because azhDirekt has no test environment and that dry run is the only pre-production test that will ever exist for it.
MIP (protocol pending confirmation) and ZHP X3 as they arrive · the Excel/XML import as a separate module glued to VDMS · the licence and dependency sweep across the remaining 7 baotnp modules.
Release 2 — direct GKV-DTA (CR #1319). A second plugin, provider_dta, on the unchanged contract: EDIFACT ESOL/AUF per TA1 v21 and v22, per-payer certificate encryption, DAVASO pre-check, Dakota file-drop + mailbox returns, KOTR payer-file import, six IK kinds, IK-Umlenkung, bundling engine, P302 parameter set. Every line greenfield. Not costed here — it is a release, not a phase.
Phase 0 is small and carries almost no migration risk — the only data-touching parts are a cron rename and deleting an unread model, both verifiable against the production-read database now available. After it, every later phase plugs in rather than negotiating.
Named here so nobody has to ask twice, and so they do not creep back in as "while we are in there".
| Not doing | Because |
|---|---|
| KVCheck (eKV via AZH) | We already do eKV, across 11 modules. Buying it a second time from a second vendor is not a plan. |
| A GraphQL driver | No provider in scope uses it. Drivers built for hypothetical providers are maintenance you pay for and never use. If one appears, it is a new class next to the others — that is the point of the contract. |
| Bulk import through the provider contract | Per-order exchange and bulk catalogue ingestion are different domains with different lifecycles. The Excel/XML importer is a parser, not a transport; it belongs in the VDMS extension that owns that data. |
| Rewriting the 112 base tests | Their docstrings carry real incident dates and row counts. They are the only thing making this refactor verifiable, and they are worth more than our opinion of their style. |
| The eKV module split | Dropped, not deferred. 4–6 days for zero behaviour change, and the only work in the programme that could delete production records via a wrong ir_model_data migration. The eKV domain stays in maptara_san_provider_base. |
| Shaping the contract around SOAP | SOAP has one consumer; REST has three or four. Egeko's WSDL type factory, positional arg0/arg1/arg2 and comma decimals generalise to nothing. Design on request/response/status/retry and let the SOAP plugin translate on its own side. |
A new provider ships without editing shared code. New module, selection_add, implement the contract, pick a driver. Zero lines changed in maptara_san_provider_base.
A grep for vendor names in the base returns nothing. No egeko, no X01, no Egekonummer, no cron named after a company.
A misspelled wire key turns the suite red. Today it does not — which is why the Egeko rewrite starts with tests and not with code.
The VDMS tree can open an SFTP session without depending on anything in the provider or eKV tree. If it cannot, the drivers are in the wrong place.
A payer-initiated Direktauftrag arrives in Maptara without a human going to look for it, and arriving twice creates one record, not two. Ticket #1274.
The direct-DTA plugin ships with zero edits to the contract. If provider_dta needs a new operation on maptara.provider.driver, the contract was shaped around azh rather than around the problem. Ticket #1319.
Next: scope and subtask breakdown per phase. This page is the frame for it; the numbers and evidence behind every claim are in
the programme report,
R1,
and the four surveys under .agent-work/provider-refactor/.
Internal planning document. Nothing described here has been implemented.