One level below the file map. 15 classes, 82 fields, 105 methods across the runtime code of both modules — each with a verdict. Extracted by AST on 2026-09-19, so the line numbers and method sizes are measured, not remembered.
9 in base, 6 in egeko
70 base + 12 egeko
77 base + 28 egeko
generate_document_template
generate_document_template alone outweighs most of the classes on this page put together.Ranked by line count. The top method is 2.1× the next one and 3.2× the third. Everything below the 20-line mark is noise for planning purposes.
generate_document_template — 263 lines, egeko/models/maptara_provider.py:180-442 — reads Odoo ORM fields, does VAT and discount arithmetic, downloads MDM attachments over a different transport, and emits opta data's exact wire vocabulary, with no intermediate representation at any point. It defines four nested functions inside a for loop.
It has zero direct test coverage. test_wire_format.py asserts on positions only; the ~45 keys of the document dict at :335-378 are asserted by nothing.
This single method is the Egeko rewrite. Everything else in R3 is tidying around it.
| Symbol | Ln | Verdict | Note |
|---|---|---|---|
| name, active | — | move down | To maptara_base. The only things 15 Codexa modules need from this layer. Never rename — codexa_api/provider_site.py:20 is a non-sudo FK. |
| provider_type | 162 | keep | Selection([]) — this is the registry. Plugins join with selection_add. Odoo has nothing better. |
| username, password, client_key | 163-165 | fix | Plain Char, no groups=. Add it. Model ACL keeps read, loses write/create/unlink. |
| uom_ids | — | delete | Read by nothing in either module. |
| action_test_connection | 404 | contract | 35 ln. Becomes the typed probe operation. Its three-stage result (config/endpoint/credentials) is currently consumed only by a log line — promote it to a declared return type. |
| _check_credentials_name_their_partner | 312 | keep | 40 ln. Scoped to "rows carrying a secret" to route around the dual-purpose model. Its own docstring says the real fix is elsewhere; that fix is out of scope. |
| _check_one_provider_per_type_and_branch | 354 | keep | 40 ln. Related to CR #1174's one-config-per-type question. |
| media_type_code / _media_type_mapping / media_type_default | 192-235 | keep | Single read point for the code table. Returns False rather than a fallback code, deliberately — 20 tests guard that. |
| _for_company · covers_company · action_expand_branches · _descendant_branches · _compute_company_ik_nos | 238-303 | keep | Branch/credential scoping. Genuinely generic. 18 tests. |
| Symbol | Ln | Verdict | Note |
|---|---|---|---|
| _provider_method | 599 | contract | 13 ln. getattr(self, f'_{op}_{provider_type}') — the accidental registry the whole design already runs on. Becomes a declared AbstractModel. |
| _require_provider_method | 613 | contract | 30 ln. Three distinguishable UserErrors. Keep the distinctions — they are the difference between "no provider", "no plugin" and "plugin can't do this". |
| _log_unroutable | 644 | contract | 28 ln. The non-raising background variant. This is why an azh install would only log, not break — it is also the reason Option A was viable. |
| send_cost_estimate · resend_cost_estimate · _get_state_cost_estimate · _get_cost_estimate · _get_cost_estimate_document · _send_message | 680-820 | contract | 6 methods, 5–16 ln each. These six become the typed operations. Only _get_state_cost_estimate has a consumed return value — truthy iff the insurance decision moved. |
| _post_send_outcome | 719 | de-brand | 59 ln. Contains "eKV NOT accepted — no Egekonummer returned", the eGeKo web-viewer footer, and the sent→error downgrade that compensates for a bug in the plugin's logging layer. Fix the cause, delete the compensation. |
| _chatter_on_send | 703 | keep | 15 ln contextmanager. Provider-agnostic already. |
| action_export | 554 | keep | 26 ln. Zips request+response. The only binary handling in the entire base module. |
| request_data / response_data | 531,538 | relabel | Labelled "XML File". A §302 flat file and a JSON body have to live here too. |
| document_type | 499 | de-brand | A five-value Selection that is precisely Egeko's operation list. Move the values to the plugin via selection_add. |
| url, data | 518,520 | check | Unread by base. Confirm the plugin writes them or delete. |
| Symbol | Ln | Verdict | Note |
|---|---|---|---|
| _process_single_order_estimate | 382 | de-brand | 127 ln — the second-biggest method in the programme. It is Egeko's poll-then-fetch-details-then-fetch-documents three-call protocol, implemented in the vendor-neutral base, with 17 log lines prefixed "Egeko cron:". |
| _cron_process_cost_estimates | 333 | de-brand | 48 ln. Walks insurance_state in ('waiting','full','partial','rejected') — that vocabulary belongs to maptara_san_sale, not here. |
| _auto_init | 318 | de-brand | 14 ln. Re-creates the Egeko-named cron on every -u. Deleting that cron is not a supported state. Moves with the cron, or the base keeps resurrecting a job it no longer owns. |
| _get_ekv_warning_lines | 100 | keep | 81 ln. Documented super()-then-append seam. 13 tests, including "never blocks" and "survives empty recordsets". |
| action_send_insurance · action_resend_insurance | 248,299 | keep | 50 + 18 ln. Public API — called by maptara_pos_sale_glue/models/maptara_pos_visit.py:389,391. Signature is frozen. |
| _get_ekv_position_lines | 72 | keep | 27 ln. Documented override seam; the plugin narrows it. Single predicate for both payload and warning banner — 5 tests assert they agree. |
| ece_provider_id · ece_provider_locked · cost_estimate_completed · provider_activity_ids | 15-52 | keep | 4 of the 6 public fields. cost_estimate_completed hardcodes the three-activity lifecycle at :212-216 — leave it, it is behaviour not vocabulary. |
| Class | Shape | Verdict | Note |
|---|---|---|---|
| maptara.send.ekv.wizard | 13f · 30m | de-brand | The most method-dense class in either module. _send_ekv 59 ln (:542), _materialise_uploads 36 (:339), action_new_egeko_code 32 (:498, rename — it is bound from XML), _get_ref_family 22 (:432, built around opta data's German refusal string), _generate_internal_ref 22 (:475). |
| maptara.provider.ref.burn | 5f · 2m | labels only | burn is 35 ln (:87) on its own cursor so a spent token survives a rollback. Excellent. Rename the 'eGeKo KV-Nr' label at :63 and touch nothing else — a lost row means a reissued KV-Nr. |
| maptara.provider.document.type | 4f · 3m | keep | "A document type maps to a provider code" is exactly the driver-layer concept. Already generic. |
| maptara.send.ekv.line.wizard | 10f · 5m | keep | Two-store file picker with an _exactly_one_source SQL CHECK. Nothing vendor-shaped in it. |
| maptara.confirm.send.ekv.wizard | 1f · 1m | fix | action_confirm is 4 lines and one of them bypasses _materialise_uploads. Smallest method in the module, and a real bug. |
| maptara.provider.uom | 3f · 0m | delete | Zero methods. A model whose entire job is appearing in a form. ACL'd, viewed, read by nothing. |
| Symbol | Ln | Verdict | Note |
|---|---|---|---|
| generate_document_template | 180 | rewrite | 263 ln. The whole job. Four nested functions defined inside a for loop. Preserve exactly: de_decimal/de_price per-field precision, net and gross both post-discount, additionalFee = Mehrkosten not discount, positions sorted create_date desc, and dhpIdentNumer — opta data's typo, which must stay misspelled. |
| _get_state_cost_estimate_egeko | 506 | semantics frozen | 76 ln. The four status buckets. Only STATUS_DECIDED may write insurance_state. Encodes two named regressions: code 41 used to write rejected; code -1 was re-polled forever. 15 tests. Port the tests before the code. |
| _get_cost_estimate_document_egeko | 626 | rewrite | 46 ln. Contains the "empty response marks error, never deletes, or the cron loops forever" invariant at :656-670 — which has no test. |
| _resend_cost_estimate_egeko | 468 | rewrite | 37 ln. del internPrescriptionId — absent ≠ empty string. Asserted nowhere. |
| _send_cost_estimate_egeko | 444 | fix | 23 ln. Writes insurance_state = 'waiting' at :459-466 even when the send failed, so an unaccepted order polls an empty ref forever. |
| _ekv_doctor_source | 142 | keep | 37 ln. Doctor precedence: supply window → case → prescription. Untested but self-contained. |
| _egeko_media_type_code · _post_egeko_state · _get_cost_estimate_egeko · _send_message_egeko | 112-673 | rewrite | 28 / 8 / 33 / 15 ln. Straightforward once the DTO exists. |
| Symbol | Ln | Verdict | Note |
|---|---|---|---|
| EgekoRequest.__init__ | 245 | → driver | 36 ln. Session, cached Transport, Client, type factory. Bug to fix in transit: timeout reaches Client() at :274 but not Transport() at :273. |
| _web_authenticated | 282 | → driver | 22 ln. Collapses every login failure to False — the module's own docstring calls this useless, which is why a second honest login path exists in probe_egeko_credentials. Two login paths; keep one. |
| get_response_documents_by_egeko_id | 364 | rewrite | 39 ln. Dual-mode dict-or-object unwrapping reaching for zeep's private _value_1, with except Exception: decoded_content = None turning a corrupt attachment into an empty one, silently. |
| send_document_template_san · resend_document_template_san | 306,405 | rewrite | 30 ln each. Blind setattr loop over an unvalidated dict — a typo'd key raises inside zeep, not at the call site. |
| get_document_state_by_egeko_id · get_document_template_san_by_egeko_id · send_message_by_egeko_id | 338-437 | keep | 11 ln each. Thin wrappers. All positional arg0/arg1/arg2 — the WSDL has no named parameters, and the driver must support that. |
| LogPlugin.egress / ingress / marshalled / __init__ | 205-234 | → driver | 11 / 12 / 2 / 3 ln. Two problems to fix on the way. ingress writes status='sent' into the ORM from inside the SOAP stack — base documents this as a bug and compensates downstream. And marshalled calls envelope.prune(), so a logging plugin silently alters the wire payload. The driver returns an audit record; it must not write one. |
| Class | Shape | Verdict | Note |
|---|---|---|---|
| res.config.settings | 10f · 4m | → driver | 9 of the 10 fields are computed helpers around one stored egeko_url. The pattern — env var beats DB param, never default — is promoted to the driver layer for everyone. _onchange_egeko_url_preset (24 ln, :101) must stay an onchange, not a button: a button reloads the settings record and silently discards the assignment. |
| maptara.service.provider | 1f · 1m | contract | provider_type selection_add, plus _test_connection_egeko (16 ln, :76) → the probe operation. |
| sale.order | 0f · 1m | keep | 29-line override of _get_ekv_position_lines. The cleanest thing in the module. |
Every operation below is outbound: we send, we poll, we fetch what we asked for. The ECE test plan under CR #1173 requires flows where the payer starts the conversation:
#1274 Direktauftrag — the payer creates a delivery order on the platform; it must be pulled into Maptara. #1276 Versorgungsanfrage — the payer asks whether we can supply, and requests an estimate. #1275 — messages arrive from the insurer, not just to them.
These are written against the egeko test platform with Kostenträger credentials, so this is not MIP-only scope. Nothing in the current design pulls payer-initiated work into Maptara, and a contract that cannot express it will be reopened the moment R3 starts.
A new provider implements these and declares what it supports; it edits nothing shared.
| Operation | Today's name | Return | Notes |
|---|---|---|---|
| probe | _test_connection_<type> | {ok, stage, url, message} | stage ∈ config / endpoint / credentials. Never raises, creates no Vorgang — the only operation safe to fire at production. |
| send_estimate | _send_cost_estimate_<type> | — | Attachments become explicit arguments, not the two smuggled context keys. |
| resend_estimate | _resend_cost_estimate_<type> | — | |
| poll_state | _get_state_cost_estimate_<type> | bool | The only return value anyone consumes — truthy iff the insurance decision moved. |
| fetch_decision | _get_cost_estimate_<type> | — | |
| fetch_documents | _get_cost_estimate_document_<type> | — | Must keep "empty response marks error, never deletes" or the cron loops forever. |
| send_message | _send_message_<type>(rec, subj, msg) | bool | The only one with extra arguments today. |
| fetch_orders | — does not exist — | list | NEW. Direktauftrag: the payer created a delivery order. Ticket #1274. |
| fetch_requests | — does not exist — | list | NEW. Versorgungsanfrage: the payer asks whether we can supply. Ticket #1276. |
| fetch_messages | — does not exist — | list | NEW. Inbound half of #1275. _send_message already covers the outbound half. |
Outbound operations act on a record we already have — an activity row, an order, a known external_ref. Inbound operations have no local record yet: they poll a queue, discover work, and must create something. That means idempotency (don't import the same Direktauftrag twice), a cursor or acknowledgement, and a decision about what Odoo object a Versorgungsanfrage even becomes.
None of that is driver concern — it is contract and plugin concern. But the contract has to have a shape for it, or every provider invents its own polling loop, which is the exact failure this programme exists to prevent.
Open question for a human: does a Versorgungsanfrage become a sale.order, a maptara.case, or something new? That is a product decision and it blocks the inbound design, not the outbound one — so it does not hold up Phase 0 or R2's outbound half.
Today you discover a provider cannot do something by calling it and getting None back from getattr. A declared contract lets a plugin say so up front: does it support send_message? which MIME types (currently a module-level constant in the base, admitting in a comment that it lives there only so both sides agree)? does it poll or push? does it batch?
That is the difference between an interface and a naming convention, and it is roughly 200 lines.
Extracted by ast over the runtime .py of both modules, 2026-09-19, excluding tests/, migrations/, tools/ and __pycache__. Method sizes are end_lineno - lineno + 1. The 70 base field definitions cross-check against survey A's independent count.
Internal planning document. Nothing here has been implemented.