Internal · symbol-level plan

Every class, field and method

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.

Tests, migrations and tools/ excluded — they are not being changed. · file map · goal · R1
Verdicts: keep untouched de-brand vendor strings out, logic same contract becomes a typed driver operation → driver moves to a transport module rewrite fix delete
Classes
15

9 in base, 6 in egeko

Field definitions
82

70 base + 12 egeko

Methods
105

77 base + 28 egeko

Largest single method
263

generate_document_template

maptara_san_provider_base — 9 classes · 70 fields · 77 methods maptara.provider.activity 17f · 12m 6 ops + dispatch trio → THE CONTRACT _post_send_outcome 59 — de-brand action_export · chatter maptara.service.provider 11f · 12m name/active ↓ ACL + groups= branches keep 2 constraints 40 ln each · media-type table · _for_company provider_type = Selection([]) — this IS the registry sale.order  (_inherit) 6f · 12m _process_single… 127 — de-brand 2 public actions — frozen warning centre 81 ln · cron 48 · _auto_init moves with the cron Egeko's three-call protocol lives here — that is the leak 6 more send wizard 13f·30m line wizard · confirm ref.burn · doc.type uom · 0 methods maptara_san_provider_egeko — 6 classes · 12 fields · 28 methods maptara.provider.activity  (_inherit) — 1f · 10m generate_document_template — 263 lines ORM + VAT + MDM downloads + wire vocabulary, no intermediate representation poll_state 76 — FROZEN fetch_docs 46 resend 37 send 23 · BUG Four status buckets and de_price precision: preserve verbatim, 32 tests guard them EgekoRequest + LogPlugin 0f · 12m ~200 lines → maptara_driver_soap LogPlugin writes status='sent' into the ORM — stop that __init__ 36 · no Transport timeout · zero retries _value_1 leaks zeep internals into business code 3 more res.config.settings — 10f · 4m endpoint pattern → promoted to drivers maptara.service.provider — 1f · 1m sale.order — 0f · 1m, cleanest file here Read the sizes, not the count. 15 classes sounds like a lot of surface. It is not: two classes carry 22 of the 105 methods, and one single method — 263 lines — is 6.9% of all runtime code in both modules and has zero payload tests. Blue = becomes the contract · red = rewritten · amber = de-branded · green = lifted to a driver or frozen verbatim · grey = untouched.
Every class in both modules, at a glance. Bar widths inside each card are proportional to the work in that area, not to line count. The one thing worth taking away: the surface looks broad and is actually concentrated — generate_document_template alone outweighs most of the classes on this page put together.

The 15 biggest methods — this is the refactor

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 egeko/maptara_provider:180
263
_process_single_order_estimate base/sale_order:382
127
_get_ekv_warning_lines base/sale_order:100
81
_get_state_cost_estimate_egeko egeko/maptara_provider:506
76
_post_send_outcome base/maptara_provider:719
59
_send_ekv base/wizard:542
59
action_send_insurance base/sale_order:248
50
_cron_process_cost_estimates base/sale_order:333
48
_get_cost_estimate_document_egeko egeko/maptara_provider:626
46
_check_credentials_name_their_partner base/maptara_provider:312
40
_check_one_provider_per_type_and_branch base/maptara_provider:354
40
get_response_documents_by_egeko_id egeko/egeko_request:364
39
_resend_cost_estimate_egeko egeko/maptara_provider:468
37
EgekoRequest.__init__ egeko/egeko_request:245
36
action_test_connection base/maptara_provider:404
35
One method is 6.9% of all runtime code in both modules

generate_document_template263 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.

maptara_san_provider_base — 9 classes, 70 fields, 77 methods

maptara.service.provider _inherit · 11 fields · 12 methods · maptara_provider.py:157

SymbolLnVerdictNote
name, activemove downTo maptara_base. The only things 15 Codexa modules need from this layer. Never renamecodexa_api/provider_site.py:20 is a non-sudo FK.
provider_type162keepSelection([]) — this is the registry. Plugins join with selection_add. Odoo has nothing better.
username, password, client_key163-165fixPlain Char, no groups=. Add it. Model ACL keeps read, loses write/create/unlink.
uom_idsdeleteRead by nothing in either module.
action_test_connection404contract35 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_partner312keep40 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_branch354keep40 ln. Related to CR #1174's one-config-per-type question.
media_type_code / _media_type_mapping / media_type_default192-235keepSingle 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_nos238-303keepBranch/credential scoping. Genuinely generic. 18 tests.

maptara.provider.activity _name · 17 fields · 12 methods · maptara_provider.py:461

SymbolLnVerdictNote
_provider_method599contract13 ln. getattr(self, f'_{op}_{provider_type}') — the accidental registry the whole design already runs on. Becomes a declared AbstractModel.
_require_provider_method613contract30 ln. Three distinguishable UserErrors. Keep the distinctions — they are the difference between "no provider", "no plugin" and "plugin can't do this".
_log_unroutable644contract28 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_message680-820contract6 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_outcome719de-brand59 ln. Contains "eKV NOT accepted — no Egekonummer returned", the eGeKo web-viewer footer, and the senterror downgrade that compensates for a bug in the plugin's logging layer. Fix the cause, delete the compensation.
_chatter_on_send703keep15 ln contextmanager. Provider-agnostic already.
action_export554keep26 ln. Zips request+response. The only binary handling in the entire base module.
request_data / response_data531,538relabelLabelled "XML File". A §302 flat file and a JSON body have to live here too.
document_type499de-brandA five-value Selection that is precisely Egeko's operation list. Move the values to the plugin via selection_add.
url, data518,520checkUnread by base. Confirm the plugin writes them or delete.

sale.order _inherit · 6 fields · 12 methods · sale_order.py:13

SymbolLnVerdictNote
_process_single_order_estimate382de-brand127 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_estimates333de-brand48 ln. Walks insurance_state in ('waiting','full','partial','rejected') — that vocabulary belongs to maptara_san_sale, not here.
_auto_init318de-brand14 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_lines100keep81 ln. Documented super()-then-append seam. 13 tests, including "never blocks" and "survives empty recordsets".
action_send_insurance · action_resend_insurance248,299keep50 + 18 ln. Public API — called by maptara_pos_sale_glue/models/maptara_pos_visit.py:389,391. Signature is frozen.
_get_ekv_position_lines72keep27 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_ids15-52keep4 of the 6 public fields. cost_estimate_completed hardcodes the three-activity lifecycle at :212-216 — leave it, it is behaviour not vocabulary.

Smaller classes

ClassShapeVerdictNote
maptara.send.ekv.wizard13f · 30mde-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.burn5f · 2mlabels 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.type4f · 3mkeep "A document type maps to a provider code" is exactly the driver-layer concept. Already generic.
maptara.send.ekv.line.wizard10f · 5mkeep Two-store file picker with an _exactly_one_source SQL CHECK. Nothing vendor-shaped in it.
maptara.confirm.send.ekv.wizard1f · 1mfix action_confirm is 4 lines and one of them bypasses _materialise_uploads. Smallest method in the module, and a real bug.
maptara.provider.uom3f · 0mdelete Zero methods. A model whose entire job is appearing in a form. ACL'd, viewed, read by nothing.

maptara_san_provider_egeko — 6 classes, 12 fields, 28 methods

maptara.provider.activity _inherit · 1 field · 10 methods · maptara_provider.py

SymbolLnVerdictNote
generate_document_template180rewrite 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_egeko506semantics 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_egeko626rewrite 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_egeko468rewrite 37 ln. del internPrescriptionId — absent ≠ empty string. Asserted nowhere.
_send_cost_estimate_egeko444fix 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_source142keep 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_egeko112-673rewrite 28 / 8 / 33 / 15 ln. Straightforward once the DTO exists.

EgekoRequest & LogPlugin plain Python · 12 methods · egeko_request.py

SymbolLnVerdictNote
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_authenticated282→ 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_id364rewrite 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_san306,405rewrite 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_id338-437keep 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.

Rest of egeko

ClassShapeVerdictNote
res.config.settings10f · 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.provider1f · 1mcontract provider_type selection_add, plus _test_connection_egeko (16 ln, :76) → the probe operation.
sale.order0f · 1mkeep 29-line override of _get_ekv_position_lines. The cleanest thing in the module.

The contract — seven outbound operations, and at least three inbound

Correction 2026-09-19 — reading tickets #1271–1276 broke the seven-method design

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.

OperationToday's nameReturnNotes
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>boolThe 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)boolThe only one with extra arguments today.
fetch_orders— does not exist —listNEW. Direktauftrag: the payer created a delivery order. Ticket #1274.
fetch_requests— does not exist —listNEW. Versorgungsanfrage: the payer asks whether we can supply. Ticket #1276.
fetch_messages— does not exist —listNEW. Inbound half of #1275. _send_message already covers the outbound half.
Why inbound is not just "seven more of the same"

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.

Plus capability flags — the thing the getattr registry cannot express

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.