Rebuilding maptara_san_provider_base and rewriting maptara_san_provider_egeko around explicit transport drivers — so Egeko, MIP, ZHP X3, azhDirekt and azhIndex all plug into the same socket instead of each growing their own.
dev/Noventi-1197-api · worktree /home/liviuxyz/workspace/OdooProjects/maptara-demo-1197-noventi/1. Noventi is the deliverable. CR #1197 drives this programme; the refactor exists to stop azh becoming integration #3 with its own private SFTP client. Where this document reads as though the refactor is the goal, invert it.
2. The contract needs inbound operations. Everything designed below is outbound — we send, we poll, we fetch what we asked for. #1274 Direktauftrag and #1276 Versorgungsanfrage are payer-initiated: the insurer creates work and Maptara must pull it in. #1275 needs inbound messages too. All three are written against the egeko test platform with Kostenträger credentials, so this lands in R3, not some future provider. See the symbol map.
3. MIP has live test credentials and may not be REST — the per-customer md5 interface key points at a signed-request scheme. Confirm before shaping anything around it.
4. It is all one programme. #1173 (ECE) is the parent; #1174 settings and #1271–1276 are siblings. This document previously treated #1174 as a stray.
Everything with a number in it was measured by reading the code on 2026-09-18, and every number is re-derivable — the three underlying surveys are linked in §2 and carry file:line for each claim. The only numbers that are judgement rather than measurement are the dev-day ranges in §9, and they are marked as such.
Nothing here has been implemented. This is a plan awaiting a go/no-go. No source file outside docs/ and .agent-work/ has been touched.
Surveys A, B and C were produced by independent read-only passes over the codebase. Where they disagreed with my prior assumptions, the survey won and the assumption is recorded as corrected in §12. B was then run a second time; B2 logs where the two passes disagree rather than quietly picking a winner — see §12.
Python lines, 23 files. Zero of them do transport.
Python lines, 20 files. 1 448 are the addon; the rest is tests and tools.
Non-test lines in the vendor-neutral module that name the vendor.
Live code refs into the provider layer from the rest of the repo. That is the entire blast radius.
Three things came out of the survey that change the shape of the job.
maptara_san_provider_base contains zero networking code. Its complete non-ORM import list is six lines: contextmanager, Markup, base64, io, zipfile, re. No requests, no zeep, no lxml, no paramiko, no ssl. SOAP appears only in comments and test mocks. All protocol code already lives in the Egeko plugin.
So the job is not "extract the transport layer". It is "extract the vendor out of the vendor-neutral module" — and then give the plugins a transport layer they currently each have to invent.
_provider_method() resolves implementations by getattr(self, f'_{operation}_{provider_type}') (maptara_san_provider_base/models/maptara_provider.py:599-611), with provider_type = fields.Selection([]) as the registry (:162) and each plugin joining via selection_add. Six operations route through it, plus a seventh for connection tests. It works, it is well-tested, and the 17-line comment above it is an incident report explaining why.
It is also a naming convention with no type, no capability declaration and no way to ask "does this driver support attachments?". That is what needs upgrading — not replacing.
3 modules depend on provider_base directly. provider_egeko has zero dependents — it is a leaf. 15 live code references total, in 8 files, 6 modules. Zero external XML view inheritance, zero external ACL rows, zero external action references.
But maptara_patient_connect_hub calls search() on maptara.service.provider with no active domain (controllers/patient_service_view.py:23, patient_patient_view.py:639) and gets the active=True filter purely because provider_base adds the field. Change that field's ownership carelessly and a public patient portal starts listing archived providers. No error, no test, no one finds out.
Three independent read-only passes, each writing its own file. Read them when a number here looks wrong — they carry file:line for every claim and they are the reason this plan is not vibes.
| Survey | Question | Tool calls | Headline |
|---|---|---|---|
| A — base anatomy | What is in provider_base, and how much of it is secretly Egeko? | 22 | 9 models / 70 fields. 169 Egeko-matching lines, 106 outside tests, in 10 of 14 non-test files. No transport at all. |
| B — egeko anatomy | How does it actually talk to Egeko, and what must a rewrite preserve? | 26 | zeep over WSDL, 8 operations, all positional arg0/arg1/arg2. 13–16% of the addon is reusable transport; ~42–46% is Maptara business logic tangled into the payload builder. |
| C — blast radius & prior art | Who breaks, and what can we steal instead of inventing? | 48 | 22-module transitive closure hangs off two column names. zeep is declared in no requirements file. SFTP has zero prior art anywhere in the repo. |
The stated driver for this work is getting out from under baotnp / OPL-1 before the 2027 licensing cost lands. Measured across all 104 manifests:
92 LGPL-3, 17 OPL-1.
repo-wide. Only 2 of them are ours here.
Across both modules. No SPDX, no LICENSE file, no COPYING.
Scope correction (2026-09-19): a repo-wide manifest scan shows 9 modules carry "author": "baotnp", not 2 — also maptara_san_claim, san_flat_rate, san_followup_order, san_industry_fsm, san_prescription_extract, san_purchase and san_sale. Two of those, san_sale and san_claim, are direct dependencies of provider_base. This refactor clears 2 of the 9; the 2027 de-licensing programme is a larger piece of work than this one.
Within the two modules in scope, baotnp appears exactly twice in ~8 600 lines: maptara_san_provider_base/__manifest__.py:44 and maptara_san_provider_egeko/__manifest__.py:25. Git history for the egeko path shows 26 commits, zero of them by baotnp — 13 by Liviu Staniloiu, 6 soniacristea-cappsai, 3+1 Liviuxyz-ctrl, 1 liviustaniloiu-cappsai, 1 Doru Ambrus, 1 bogdanbozga-cappsai.
That reads like inherited manifest boilerplate rather than a record of authorship. Caveat that matters: 26 commits for a 3 930-line module sitting at version 19.0.0.15 strongly suggests earlier history lives in another repo. Do not build a clean-room claim on this until the original repo is checked.
Inside maptara_san_provider_egeko:
| Path | What it is |
|---|---|
tools/fixtures/Egeko.dtd | opta data's DTD, carrying copyright (c)2007 - 2015 by optadata.com on line 5 |
docs/guides/egeko-EKV-DE-TechSpec-LE-soap-V_1_1_6 (1).pdf | 1.19 MB — opta data's technical spec, vendored whole. The filename still has the browser's (1) suffix. |
docs/guides/translated_…_en_translation.pdf | 6.79 MB — a machine translation of that same spec. A derivative work of a third party's document, committed to the repo. |
tools/fixtures/ref-129-send-microsoftbc.xml | A real sendDocumentTemplate captured from Microsoft BC — a third party's production output. |
tools/fixtures/ours-S07001-20260813.xml | Our own capture "verbatim from the odoo log, 2026-08-13 09:31:36" — carrying live-shaped patient payload. |
Swapping OPL-1 to LGPL-3 in two manifests is a five-minute job. Shipping a module that contains a vendor's copyrighted DTD, two copies of their spec, and a competitor's production XML is the part that would actually embarrass us. The rewrite ships without docs/guides/*.pdf and tools/fixtures/; they move to an internal reference repo.
169 lines in provider_base match egeko|eGeKo|Egekonummer|optadata|X01|A01; 106 are outside tests/, spread over 10 of the 14 non-test Python files. The ones that are structural rather than cosmetic:
| Evidence | Location | Why it is structural |
|---|---|---|
"post_init_hook": "post_init_hook_egeko" | __manifest__.py:18 | The base module's install hook is named after one vendor. |
Cron 'Egeko: Poll Cost Estimate Statuses' | hooks.py:54 | Install base with zero plugins and you still get a 15-minute vendor-branded job. |
_auto_init re-creates that cron on every -u | models/sale_order.py:328-330 | Deleting it is not a supported state. |
action_new_egeko_code | wizard/maptara_send_ekv_wizard.py:498 + XML :44 | A public button name, bound from base's own view. |
"sent as X01 (Sonstige)" / "A01 for a prescription" | wizard/maptara_send_ekv_wizard.py:177-180 | opta data's code list hardcoded in base's user-facing warning text. |
Field labels 'eGeKo KV-Nr', 'Let eGeKo assign the KV-Nr' | :39, :51, maptara_provider_ref_burn.py:63, 8 XML columns | The generic idempotency-token ledger is branded. |
Poll-then-fetch-documents sequencing, 17 log lines prefixed "Egeko cron:" | models/sale_order.py:418-502 | The Egeko three-call protocol, implemented in base. |
An XML comment citing maptara_san_provider_egeko/models/maptara_provider.py:211 | views/sale_order_views.xml:25 | Base documents itself against its own plugin, by file and line. |
openspec/changes/consolidate-ece-provider-settings/proposal.md (113 lines) is CR #1174, requested by Nicole Loosen on 2026-07-29. It names the same three reverse dependencies this survey derived independently (:86-87) and proposes, among other things, "Replace per-provider if/else dispatch with an extension contract, so adding MIP or ZHP X3 does not require editing maptara_san_provider_base" (:29-31).
That is this document's §6, written a month and a half earlier. These must be one piece of work, not two.
| Proposal claim | Verified? | Finding |
|---|---|---|
"the Egeko endpoint is a hardcoded module constant… BREAKING for anyone relying on EGEKO_URL (egeko_request.py:17)" |
STALE | That constant no longer exists. :17 is TIMEOUT = 30. There are now three named hosts (:35-37), env-var-beats-DB-param resolution (:39-40, :64-83), a deliberate refusal to default, and a 130-line settings panel with four mutually-exclusive warnings. This bullet is already delivered. |
"security/ir.model.access.csv currently grants base.group_user full read/write/create/unlink on maptara.service.provider, the model holding password and client_key" |
TRUE | Verified verbatim: ir.model.access.csv:9 → …,base.group_user,1,1,1,1. Every internal user can read, edit and delete every ECE credential. The fields are plain fields.Char with no groups=. |
"a database constraint permits exactly one provider configuration of each type system-wide" — _unique_provider_type |
ASSUMED | Not independently re-verified in this pass. If true it blocks multi-Betriebsstätte credentials, which azh needs too (one Kundennummer per location is an open question in CR #1197). |
| "MIP and ZHP X3 are being added" | NEW TO ME | Changes the economics entirely. See below. |
I was going to argue against generalising from two providers, on the grounds that two is not evidence. That argument is dead: the real pipeline is Egeko (live), MIP, ZHP X3 (both per #1174), azhDirekt (SFTP, CR #1197 POC) and azhIndex (SFTP return + REST documents). Five integrations across three transports.
Building the fifth one by copy-pasting the first one is how you end up with five different retry policies, five credential conventions and five ways of logging patient data at INFO. Which, to be fair, is exactly where we are with two.
Four layers, each with one job. The rule that decides every boundary question below: a driver must never learn what an eKV is. The moment a SoapDriver knows about Kostenvoranschläge, we have rebuilt today's problem with more files and a nicer diagram.
EkvDocument DTO is the seam that does not exist today and without which the rewrite is cosmetic.Survey B bucketed all 1 448 addon lines of the Egeko module. Pure transport — WSDL cache, client construction, session lifecycle, the logging plugin — is 185–230 lines, 13–16% (the two passes drew the require_auth and LogPlugin boundaries differently; see B2). A perfect SoapDriver absorbs all of it and then stops.
The other ~85% is generate_document_template and friends: one 263-line method (maptara_provider.py:180-442) that simultaneously reads Odoo ORM fields, does VAT and discount arithmetic, downloads MDM attachments over a different transport, and emits Egeko's exact wire vocabulary — with no intermediate representation at any point.
So: if we build only L3, the "plugin" is the same 687-line file with a new import at the top. The EkvDocument DTO in L1 is not a nice-to-have; it is the thing that makes the other layers mean anything.
Source: 185–230 lines already written and working in egeko_request.py.
Must carry forward: the module-scope InMemoryCache (8 WSDL GETs → 1, measured); env-var-beats-DB-param endpoint resolution; refusal to default.
Must fix on the way: TIMEOUT = 30 is passed to Client() (:274) but not to Transport() (:273), so the ?wsdl GET — which runs on every EgekoRequest construction, i.e. once per record in the cron — sits outside the cap. What it falls back to is UNKNOWN until odoo/tools/zeep.py is read; both survey passes guessed, and differently. Zero retries anywhere. Every error is except Exception → UserError, and no HTTP status code is inspected anywhere in the module, so a SOAP Fault, a DNS failure and a TypeError in our own dict are indistinguishable.
Prior art in this repo: none. paramiko 0 occurrences, pysftp 0, sftp 0 outside docs/. No host-key store, no key management, no credential model for it.
Needed by: azhDirekt out (to_azh/) and azhIndex in (from_azh/) on edx.azh.de:22.
Shape: connect, put, list, get, archive-locally, plus a dry-run that writes to a local directory so the whole §302 pipeline is testable without a vendor account — which matters, because azhDirekt has no test environment at all.
Source: maptara_vdms_connector/services/client.py — a plain Python class, already correct. Token cache with a 60 s refresh margin, 3 retries with 2 ** (attempt-1) backoff on 5xx, one re-login on 401 then give up, 429 fails fast, generator-based pagination with resume.
Also steal: it is the only module in the repo that declares external_dependencies honestly, and it keeps credentials in the ORM behind groups=.
Needed by: azhIndex document fetch (base64 PDFs), and anything MIP/ZHP X3 turn out to be.
zeep is imported by egeko_request.py:2 and :7 and is declared in no requirements file: not requirements.txt, not requirements-custom.txt, not pyproject.toml (dependencies = []), not uv.lock, and the egeko manifest has no external_dependencies key at all. Nine manifests declare 'external_dependencies': {} — an empty dict, which is worse than nothing because it looks deliberate.
Worse: :7 imports InMemoryCache from upstream zeep.cache directly, bypassing Odoo 19's vendored odoo.tools.zeep shim that the very next import uses. If that shim exists to sandbox XXE or network policy, we are half inside it. UNKNOWN what the shim does — 5 minutes of reading before the driver copies the pattern.
Adding paramiko to this situation without declaring it would be a choice.
Everything below is load-bearing and verified. A rewrite that loses any of it is a regression, not a refactor.
That is the whole externally-observed API. Most of the ekv_* names you would expect to find are not fields at all — ekv_position_lines, ekv_lines, ekv_forced_qty, ekv_not_invoiced, ekv_doctor_source, ekv_body_diff, ekv_available are methods, local variables or a CLI script. ekv_ik lives in maptara_san_sale and ekv_state in maptara_san_claim — neither is ours to move.
egeko_request.py:111-176 sorts every Egeko status code into four buckets, and only one of them may write insurance_state:
| Bucket | Codes | Behaviour |
|---|---|---|
STATUS_DECIDED | 2, 3, 4, 5, 6, 501 | The only bucket allowed to write insurance_state. |
STATUS_PENDING | 0, 1, 32 | Silent. Nothing written, nothing posted. Note 0 is a legal code, so the guard is response is None or response == '', never if response:. |
STATUS_FAILED | -1, 42 | Warn + chatter. insurance_state untouched. |
STATUS_INFORMATIONAL | 11, 12, 13, 31, 33, 41, 71 | Posted once. Never acted on. |
| unknown | anything else | Logged with the full known-code list, posted as "not acted on", nothing written. |
This shape exists because of two real incidents, and tests/test_document_state.py is the receipt: code 41 ("delivered after cost approval") used to write rejected; code -1 ("Error") used to sit in the pending bucket and be re-polled forever. 15 tests guard it.
Also load-bearing: egeko_state_code (maptara_provider.py:104-110) exists solely so the 15-minute cron does not post an identical chatter line every 15 minutes until the heat death of the order.
Each of these replaced a specific bug and is guarded by tests. Preserve them by porting the tests first:
de_decimal / de_price emit comma separators, 2 to 4 decimals, per field independently — derived from three opta data reference documents, one of which proves net and gross do not share a precision (2,75 and 3,2725 on the same position). 17 tests.ZeroDivisionError.additionalFee is Mehrkosten, gated on show_additional_payment, computed from _additional_payment_netto() — never the discount percentage.serialNumber never leaks Python False onto the wire.create_date descending, indexed from 1. Newest first. Odd, deliberate, tested.dhpIdentNumer stays misspelled. That is opta data's typo in their WSDL. "Fixing" it breaks the send. 🙃internPrescriptionId rather than blanking it — "no element" ≠ "empty string".No default endpoint, ever. Env var → DB param → refuse with a 12-line error. The reason is documented at egeko_request.py:42-62: a default means a staging DB restored from production files real eKVs with real Krankenkassen. Make this a first-class driver-layer feature, not an Egeko quirk.
The dead-test host as a named constant. EGEKO_URL_DEAD_TEST exists purely so the settings panel can shout at you for picking a host the vendor documents but does not run. Unusual, and correct.
The three-stage connection probe. {ok, stage, url, message} with stage ∈ {config, endpoint, credentials} — a WSDL failure blames the host, not the password. Never raises, creates no Vorgang, safe against production. Specify stage in the new contract; today only a log line consumes it.
The ref-burn ledger on its own cursor. A spent KV-Nr must stay spent even when the surrounding transaction rolls back. Generic mechanism, vendor-branded label — rename it, do not redesign it.
Context smuggling. Attachments reach the driver via two undocumented context keys, selected_document_ids and selected_mdm_attachment_ids. Absent them, the payload silently ships with no appendix. In the new contract these are explicit arguments.
ingress writes status='sent' the moment any response arrives, including a refusal — and base carries a compensating hack that downgrades sent→error when no external_ref came back. Fix the cause; delete the workaround.
test methods, 8 files, ~182 asserts
test methods, 9 files, 1 382 lines = 35% of the module
status buckets, number formats, endpoint resolution, routing
no test ever builds a real SOAP envelope
Every suite patches above the transport — EgekoRequest is mocked at the import site because constructing it fetches a WSDL. So self.factory.document() and setattr(position, field, value) are never exercised, and a misspelled wire key is caught by nothing. Untested entirely: both send paths' insurance_state writes, the approval-detail fetch, the document fetch, the dual-path unmarshalling with its except Exception: decoded_content = None swallow, the session lifecycle, and the LogPlugin.
Meanwhile tools/ekv_body_diff.py — 630 lines that diff a captured payload against opta data's reference XML and exit 1 on a mismatch, explicitly written to gate a pipeline — is wired into nothing.
Therefore R0 below is not optional. Porting payload tests before touching payload code is the difference between a refactor and a rewrite-and-pray.
Two inputs changed the order. A production-read database is now available, so migration work is verifiable. And the estimates in this section were padded: they carried buffer for review cycles and migration verification and presented it as coding time. R0+R1's cheap half is 1.5–2 days, not 6.
The committed sequence is now:
| Order | Work | Days | Why here |
|---|---|---|---|
| 1 | R1 cheap half — test baseline, de-brand, ACL fix, delete dead model | 1.5–2 | Independent, and the ACL is a live credential exposure |
| 2 | R2 — the driver contract | ~2 | Needs no migration at all. The only item that gets more expensive with every provider added. |
| 3 | R4 — SFTP driver | 4–6 | Unblocks azh #1197 |
| 4 | R0 + R3 — payload tests, then the Egeko rewrite | 10–15 | Needs the contract first; no deadline pressure. This is the figure that does not compress. |
| — | R1's module split (Option B), W3, W5 | 5–8 | Deferred. Same cost next quarter as today, and the only work carrying production-data risk. |
~8–10 days to unblock azh, against the 24–37 originally drawn below. The difference is deferred work plus removed padding, not optimism. Full reasoning and the B-vs-R2 SWOT: R1-provider-base-rework.html §3a.
The original sequencing is kept below, because the per-package content and exit criteria still stand — only the order and the day counts moved.
Before touching anything. Wrap tools/ekv_body_diff.py's signature comparison in a TransactionCase and assert a built payload against tools/fixtures/ref-128-send-toilettensitz.xml. Add direct tests for the two send paths' insurance_state writes. Exit criterion: a deliberately misspelled wire key makes the suite red. Today it does not.
maptara_san_provider_base 4–6 d| Action | Target |
|---|---|
move the poll cron and its post_init_hook out of base into the plugin that needs it | hooks.py, __manifest__.py:18, sale_order.py:328-330 |
| move the poll-then-fetch sequencing (Egeko's three-call protocol) out of base's cron body | models/sale_order.py:418-502 |
strip vendor strings from base's user-facing text: X01/A01, Egekonummer, eGeKo KV-Nr, the eGeKo web-viewer footer | wizard :177-180, maptara_provider.py:739-776, 8 XML columns |
rename action_new_egeko_code and the ref-burn ledger's labels to vendor-neutral names (mechanism unchanged) | wizard :498, maptara_provider_ref_burn.py:63 |
move name and active on maptara.service.provider down into maptara_base | frees 15 of 22 modules from caring about this layer at all |
delete maptara.provider.uom — defined, ACL'd, shown in the form, read by nothing | maptara_provider.py:133 |
fix the ACL: base.group_user currently has 1,1,1,1 on the credential model | security/ir.model.access.csv:9 |
The name/active move touches the public patient portal by accident. Before merging: add a test that maptara_patient_connect_hub's two search() calls still exclude archived providers. There is no such test today, which is precisely how this breaks silently.
Upgrade the getattr convention into an AbstractModel with typed operations, capability flags and NotImplementedError defaults — the shape already proven 12 times in this repo by codexa.service, maptara.document.service et al. Keep provider_type + selection_add as the registry; it works and Odoo has no better mechanism for runtime-selected implementations.
Define the EkvDocument DTO here. This is the step people will want to skip. It is the step that decides whether any of the rest is real.
Lift the ~185 transport lines into SoapDriver; rebuild the Egeko plugin as payload-mapping only, on top of the DTO. Fix in passing: Transport timeout, retry policy, distinguishing SOAP Fault from transport failure, DEBUG-gating the payload logging, deleting the cookie log. Exit criterion: the ported R0 payload tests pass unchanged against the new implementation — same bytes on the wire.
Net new. paramiko declared properly, host-key policy decided explicitly, credentials in the ORM behind groups= following the vdms.backend pattern. Ship with a local-directory dry-run mode from day one — azhDirekt has no test environment, so the dry run is the only pre-production test that will ever exist for it.
Generalise VdmsClient rather than writing a third HTTP client. Token cache, backoff, pagination and the honest external_dependencies declaration all come for free.
Flip both manifests off OPL-1/baotnp; move docs/guides/*.pdf and tools/fixtures/ to an internal reference repo; declare zeep and paramiko; delete the empty wizard/ package; resolve the nine 'external_dependencies': {} lies. Gate: confirm the pre-2026 git history of both modules in the original repo before claiming clean-room authorship.
The azh §302 POC (M0 core + M1 printing + M2 azhDirekt out, 20–28 d per the #1197 report) sits on top of R2 and R4. Built before them, it becomes integration #3 with its own private SFTP code, and the driver layer arrives too late to save it.
Found while surveying, not fixed, not part of the requested work. Ranked by what they cost if ignored.
| # | Finding | Evidence | Confidence |
|---|---|---|---|
| 1 | Every internal user can read, edit and delete every ECE credential. base.group_user holds 1,1,1,1 on maptara.service.provider, which stores password and client_key as plain Char with no groups=. |
maptara_san_provider_base/security/ir.model.access.csv:9 | certain |
| 2 | Full patient payload logged at INFO. Every request and response XML — names, birth dates, insurance numbers, diagnosis codes — goes to the Odoo log and any shipper attached to it. The session cookie is logged separately on every login. GDPR-relevant. | egeko_request.py:212, :224, :299 | certain |
| 3 | insurance_state = 'waiting' is written even when the send failed. An order Egeko never accepted enters the 15-minute poll loop with an empty external_ref and polls forever. Base corrects status but not insurance_state. |
maptara_provider.py:459-466, same shape :495-502 | high |
| 4 | The WSDL fetch is not covered by the 30 s timeout, and it happens once per record in the cron. timeout reaches Client() but not Transport(). The fallback value is UNKNOWN — read odoo/tools/zeep.py before quoting one. |
egeko_request.py:273-274 | high mechanics / unknown blast |
| 4b | The manifest lies about its dependencies. models/maptara_provider.py:16 does from ...maptara_base.utils import … — a relative import across an addon boundary — and :426 uses maptara.document.service. Neither module is in depends; both resolve only transitively through base. |
__manifest__.py:21-23 vs maptara_provider.py:16, :426 | certain |
| 5 | Corrupt attachments become empty attachments, silently. base64 decode wrapped in a bare except Exception: decoded_content = None, then written as b''. Nobody is told. |
egeko_request.py:373-397, maptara_provider.py:644 | high |
| 6 | The confirm-resend wizard skips upload materialisation. It calls _send_ekv directly, bypassing _materialise_uploads — a file sitting in upload_attachment_ids on that path never becomes a document. |
wizard/maptara_confirm_send_ekv_wizard.py:19-20 | medium |
| 7 | Liveness ping doubles the request count and may not be side-effect-free. getClosedMessages() is called before every operation inside the session window; it has business meaning at opta data. |
egeko_request.py:288-293 | high mechanics / UNKNOWN effects |
| 8 | Wire archives bypass the document manager. Request/response XML is stored as raw Binary on the activity, despite base already depending on maptara_document_manager_api and maptara.document.attachment having provider_code/provider_ref built for exactly this. No retention policy exists. |
maptara_provider.py:521-550 | high |
| 9 | Diagnostic tooling hardcodes another developer's worktree (maptara-demo-ece-1173) and two container paths. |
tools/ekv_doctor.py:21, :44-45 | high |
| 10 | A 26-line commented-out action_send_ekv override waiting on a hook nobody built. |
maptara_case_mdm_glue/models/sale_order.py:118-144 | certain |
#1 and #2 are a credential-exposure and a patient-data-in-logs finding respectively. Neither blocks the refactor, both are cheap inside R1 and R3 where the files are already open, and neither gets cheaper by waiting. They are listed here rather than smuggled into scope — but they are the two I would take.
| # | Decision | Why it blocks | My recommendation |
|---|---|---|---|
| D1 | Is this one piece of work with CR #1174, or two? | #1174's OpenSpec proposal already proposes the extension contract. Two people designing the same seam is how you get two seams. | Merge them. This document becomes the technical design under #1174's business framing. #1174 is still blocked on an unread spec docx; the engineering half does not have to be. |
| D2 | Does the EkvDocument DTO get built, or do we ship drivers only? |
Drivers alone touch 13–16% of the Egeko module. Without the DTO the "plugin" is the same 687-line file with a new import. | Build it — but with R3, not R2. Revised 2026-09-19: azh is §302 claims, not cost estimates, so it needs a different payload shape entirely. The DTO belongs to the Egeko rewrite that actually consumes it. R2 ships the contract and capability flags only. |
| D3 | Refactor before CR #1197, or build the azh POC first and retrofit? | R2+R4 are prerequisites for azh being a plugin rather than integration #3 with its own private SFTP code. | Refactor first — and it is now ~8–10 days to unblock azh, not 13–20, once the module split is deferred and the padding is removed. |
| D7 | Does the eKV module split (R1 Option B) happen at all? | It is 4–6 days for zero behaviour change, and it is the only work in the programme that can delete production records if the migration is wrong. | Defer. R2's worst case is revising an interface nothing external depends on; B's worst case is a production database missing its cron. Revisit if MIP/ZHP X3 get committed dates (D4). |
| D4 | Do MIP and ZHP X3 have committed timelines? ANSWERED for MIP | They are the strongest argument for the driver layer. | MIP is real and further along than assumed. Tickets #1271–1276 (Nicole Loosen, 2026-08-26) carry working test-platform credentials — Kundennummer, Pseudo-IK, Kostenträger-ID, a per-customer md5 interface key, login and password — plus six written test cases. Only the Software-ID was outstanding, due 2026-08-27. ZHP X3 still has nothing. |
| D5 | Do landmines #1 (credential ACL) and #2 (patient data at INFO) come into scope? | Both are cheap inside work already planned; both stay armed otherwise. | Yes — but it is explicitly your call, not mine to assume. |
| D6 | Where does the git history of both modules actually start? | 26 commits, zero by baotnp, on a module at version 19.0.0.15. Earlier history probably lives in another repo. | Check before R6 claims clean-room authorship. This is a five-minute question with a legal-sized answer. |
Method. Three independent read-only passes over /home/liviuxyz/workspace/OdooProjects/maptara-demo/ on 2026-09-18, 96 tool calls in total, each pass writing its own file with file:line for every claim. Counts in this document were taken from those files; the four spot-checks I ran directly (manifest licence keys across 104 modules, the ACL row, the endpoint constants, MIP/ZHP presence) are stated as verified where they appear. No file outside docs/ and .agent-work/ was modified.
Corrections to my own earlier statements in this workstream:
provider_base". Wrong — there is no transport in provider_base. The job is extracting the vendor out of it, and adding a transport layer that never existed.from zeep.cache". There is 1. The underlying finding — zeep undeclared, one import bypassing Odoo's shim — stands.odoo/tools/zeep.py. The conclusion built on them (a transport-only split touches well under a fifth of the module) survives either reading. Disagreements are logged in B2 rather than reconciled by fiat.Not covered here. MIP and ZHP X3 protocols (no documentation seen — they may not be SOAP, in which case L3 needs a fourth driver). What odoo.tools.zeep actually does. Whether _unique_provider_type is still in force. The pre-2026 git history of either module. Kerstin's §302 Datenmapping docx, which is deliberately Step 2 of the #1197 workstream and untouched here.
Files:
.agent-work/provider-refactor/A-provider-base-anatomy.md
.agent-work/provider-refactor/B-egeko-anatomy.md
.agent-work/provider-refactor/C-blast-radius-and-prior-art.md
Internal engineering planning document. Contains effort estimates and unflattering findings about code we own; not for client distribution.