Status: deprecated. The background contract preload and its Redis cache described under Status quo are scheduled for removal. Do not build new features on them. New code loads contract data on demand, as described under Target architecture. This page documents the current state (verified against the code on 2026-10-12) for the team taking over KBP, and the migration plan in Migration roadmap.
KBP has always loaded full commodity contracts from EnWiCore through the legacy client
interface (VertraegeFuerKundeHolen service consumer). That interface returns the contract list
and then fetches tariffs, prices, meters, products and IMS data per contract. For customers
with many contracts (for example housing companies, Wohnungswirtschaft), one load takes from
several seconds up to minutes.
To hide that latency, KBP starts a full load in the background as soon as an agent opens a customer, stores the result in Redis and lets every feature read from that cache. Features that need contract details either wait for the preload or fall back to overview data.
| Building block | Layer | Role |
|---|---|---|
App.vue → contractsStore.preloadContracts() |
Frontend | Fires the preload once LoadCustomerInfo has succeeded and the Vertraege API entity is accessible |
contractsStore.contractsPreloadFinished / contractsPreloadSuccessful |
Frontend (Pinia, features/vertraege/store/pinia.ts) |
Preload state that gates UI in several features (see Frontend gating) |
GET api/vertraege/preload → VertraegeController.ContractsPreload |
Web | Feature Vertraege_Preload; sends GetCustomerCommodityContractsRequest { FetchFromRemoteSystem = true } unless a load is already running |
GetCustomerCommodityContractsCommand |
Web (Commands/, legacy MediatR) |
Orchestrator: coordinates loading flags, waits for a running preload, reads/writes the Redis contract cache, triggers the full EnWiCore load |
CacheManager (ICacheManager) |
Web (Core/CacheManager.cs) |
Redis access for contracts, loading flags, contract annotations and account balances |
GetCustomerContractsQuery → ContractRepository.GetCustomerContractsAsync |
Application → Infrastructure.Data | The query every feature uses. Default includeDetails: true routes commodity contracts through GetCustomerCommodityContractsRequest, i.e. through the preload cache |
GetCustomerCommodityContractsRequest (Infrastructure) → VertraegeFuerKundeHolenCommand |
Infrastructure (legacy service consumer) | Full EnWiCore load with per-contract tariff, meter, product and IMS calls |
GetContractAnnotationsCommand |
Web | Loads contract annotations (Vertragshinweise) in parallel to the preload and caches them in Redis |
EnwicoreContractRepository, EmobilityContractRepository (derive from Cacheable) |
Infrastructure.Data | Already use the new lightweight endpoints, but also cache their results in Redis for 60 minutes |


Any GetCustomerContractsQuery with includeDetails: true (the default) ends in the orchestrator.
What happens there depends on the Redis flags and the request options:

All keys are scoped by {agent e-mail}_{tenant}_{default reference id}.
| Key suffix | Content | TTL |
|---|---|---|
_contracts_{contractId} |
Full Contract entity (written in chunks of 10) |
60 min |
_contracts_loading |
A full load is in progress | 60 min |
_contracts_loaded |
A full load has completed | 60 min |
_contracts_successfully_pre_loaded |
Marker for the multi-tab workaround (bug 827678); deliberately not reset by InvalidateCache |
10 s |
_contract_annotations* |
Contract annotations and their loading flags | 60 min |
_financial_account_balance* |
APA account balances (customer and per contract) | 5 min |
Cacheable entries |
EnWiCore contract overviews, e-mobility contracts | 60 min |
Write-back. When an agent changes contract data in KBP, the change is sent to EnWiCore. The cached contracts in Redis are not deleted and reloaded afterwards. Instead, KBP copies the new values into the cached contracts itself, so that other features see the change without a new full load:
| Command | What it writes into the cached contracts |
|---|---|
UpdateAddressCommand |
Name and address of the deviating correspondence recipient, for contracts where the EnWiCore update succeeded |
UpdateBankAccountDataCommand |
IBAN and account holder, for contracts where the EnWiCore update succeeded |
EmailAdresseAendernCommand |
E-mail address of the deviating correspondence recipient |
MobilfunknummerAendernCommand |
Mobile phone number |
VertragAktualisierenCommand |
Replaces one contract with a fresh copy loaded from EnWiCore |
GetCustomerContractCommand |
Not a write: when it loads single contracts from EnWiCore, it adds them to the cache or replaces cached ones, but only if the new data is complete (bug 645291) |
Consequences:
Invalidation. None of the upstream systems KBP consumes (EnWiCore, APA and others) send cache headers or change notifications. KBP therefore never knows whether cached data is still current. It relies on fixed TTLs and on clearing the cache at points where a change is likely:
EntryPointController.LoadCustomerInfo clears the customer’s cache on every session start.KontodatenController deletes all cached balances of the customer
whenever api/kontodaten is called with a contract number. The frontend makes that call after
every change to APA data in KBP. As a side effect, the cache is also cleared when an agent only
switches to another contract in Kontodaten. If the frontend stops making that call, KBP shows
stale balances without any error.Changes made outside KBP (directly in EnWiCore, APA or another system) become visible only when the TTL expires or the cache is cleared at one of these points.
The contract overview endpoint already delivers these fields per contract (ContractOverview):
contract number, sector, contract type, status / active flag, start date, delivery address and
current tariff name. Everything else needs the full load. The tables below list, per use case, only
the fields that are not in the overview — these are the reason the use case depends on the
preload.
Claims management builds lists of selectable contracts for a task. It loads all contracts of the customer with details and filters them, because eligibility depends on detail data.
| Use case (agent view) | Code | Detail data needed (not in overview) | Why |
|---|---|---|---|
| Create debt collection task (Inkassoauftrag): contract selection with balances | GetInkassoAuftragVertraegeQuery |
Meters | Only contracts with at least one meter and active delivery are offered. Then one APA balance call per eligible contract (VertragssaldenLadenCommand) |
| Create disconnection task (Sperrauftrag): contract selection | GetSperrauftragVertraegeQuery, SperrauftragVertragDto |
Meters, network operator, market location | Filter: not water, meter present, delivery active. The list shows market location, network operator and meter number per contract |
| Create research task: contract selection | GetResearchTaskContractsQuery |
Meters | Filter: meter present, delivery active |
| Forced-access lawsuits and legal files (Zutrittsklagen, Akte) | ZutrittsklagenLadenCommand |
Contract notes (ForcedAccess, File) |
Marks every contract that is under a forced-access lawsuit or has a legal file |
| Disconnection process version (V1/V2) | SperrProcessVersionResolver |
— (sector only) | Uses the full query today, but the overview is sufficient |
| Task lists, triggering events, delimitation tasks, avoidance agreements | InkassoController, SperrInkassoController, SperrenEntsperrenController, ContractDelimitationTaskController, AvoidanceAgreementController |
— (contract number, type, status) | Use the full query today, but the overview is sufficient. Two controllers block on it via TasksUtils.RunSync |
Kontodaten combines APA account data with EnWiCore contract data. The cross-contract overview takes its contract list from APA (balances per contract); the contract selection is built from EnWiCore contracts. The UI waits for the preload before contracts can be opened.
| Use case (agent view) | Code | Detail data needed (not in overview) | Why |
|---|---|---|---|
| Contract selection (Vertragsauswahl) on the account data page | GetKontodatenQuery |
Payment type (direct debit / self-payer), next billing date (RotationEnd), judicial dunning procedure, business-customer flag, products |
Shown per contract in the selection. Products only serve to detect incomplete cached contracts, which are then reloaded in batches of 50 |
| Warning “customer handled by legal department” | GetKontodatenQuery |
Contract notes (ForcedAccess) of all contracts |
Customer-level warning derived from any contract |
| Cross-contract account overview (Kundenkontoübersicht) | GetKundenkontoUebersichtQuery |
Tariffs | Flexible-tariff check per contract, only if the customer has at most MaxAnzahlVertraegeFuerStandardApaDatenabruf contracts |
| Payments and receipts: contract pre-selection | ZahlungenUndBelegeVertragsvorauswahl.vue |
— | Needs only contract numbers, but the button stays disabled until the preload succeeded |
| Account balances | VertragssaldenLadenCommand, GetCustomerFinancialAccountOverviewCommand |
— | APA data, cached in Redis for 5 minutes; not part of the contract preload |
| Component | Flag | Effect |
|---|---|---|
kontodaten/views/UebergreifendeUebersicht.vue |
contractsPreloadFinished, contractsPreloadSuccessful |
Reloads the account overview when the preload finishes; contract rows cannot be opened until the preload succeeded |
kontodaten/views/UebergreifendeZahlungenUndBelege.vue |
contractsPreloadFinished |
Shows a loading state and hides the contract selection until the preload finishes |
kontodaten/components/kundenkonto/ZahlungenUndBelegeVertragsvorauswahl.vue |
contractsPreloadSuccessful |
Contract pre-selection button disabled, with tooltip |
auftragserfassung/views/Auftragserfassung.vue |
contractsPreloadSuccessful |
Loads existing-customer data (Bestandskundendaten) only after a successful preload |
shared/master-data-cleanup/store/useLoadingStates.ts |
contractsPreloadFinished |
Master data cleanup dialog waits for the preload |
shared/components/SectorIcon.vue |
contractsPreloadSuccessful |
Shows “unknown business area” instead of “unknown contract type” while not loaded |
kontodaten/views/Kontodaten.vue, vertraege/views/VertragDetails.vue |
contractsPreloadSuccessful |
Declared but not used for rendering |
Tariff change offers (GetTariffChangeOffersCommand: meters, tariffs, market location), warnings
(GetWarnhinweiseQuery), payment data (GetPaymentDataQuery: bank account), owner management
(OwnerController, UpdateOwnerCommand: market location), permissions, PIN phone numbers,
contact and address data, paper option cancellation and order capture. Overview-only pages
(contract list, contract count, relocations, NBA, tariff change entry, self-service) already use
GetCustomerContractOverviewsQuery and do not depend on the preload.
reloadDetailsFromSourceIfMissing: false the orchestrator returns
overview data while the preload is still running. Detail fields (tariffs, meters, notes) are then
empty without any error.contracts_successfully_pre_loaded flag forces a reload (bug
827678).Cacheable repositories can cache an upstream failure as “no contracts”
for 60 minutes (bug audit 2026-09-08, finding H-05).Contract entity carries tariffs, prices, meters, notes,
products and documents for all features; every feature pays for all of it.TasksUtils.RunSync around contract loading in two claims-management
controllers.The target removes the preload, the orchestrator and the Redis contract cache. Each feature calls the query that returns exactly what it needs:
getContractOverviewsByCustomerId (optional current tariff name) via
GetCustomerContractOverviewsQuery. The endpoint supports paging (limit, offset), but KBP
currently requests all contracts in one call (limit = int.MaxValue).ContractDetailsNew entity (final name to be decided).Contract + ContractDetails shape.

Without a cache, loading details for all contracts on every request is as slow as the preload itself. The features therefore have to change when they need details, not only where they get them from. Three patterns are possible and can be combined:
| Pattern | Idea | Effect on the agent | Dependency |
|---|---|---|---|
| A. Contract pre-selection | Show the overview list first (fast, all contracts). The agent selects the contract(s) to work on. Details are loaded only for the selection | One extra step; detail columns move from the list to a detail or confirmation step. Ineligible contracts can only be rejected after selection | None — KBP only |
| B. Lazy enrichment | Render the overview list immediately and fill detail columns per row in the background, with limited parallelism | No extra step; columns and eligibility appear progressively. Still N calls, so slow for large customers | None — KBP only |
| C. Richer upstream endpoint | EnWiCore adds the needed flags to the overview (e.g. has active meter, forced-access note, payment type) or offers customer-level filters | No change for the agent; fastest | EnWiCore change request |
Pattern A is the default proposal: it removes the dependency on caches and the N calls, and many KBP processes already work on one selected contract. Pattern C is needed where information must be visible before selection, for example legal warnings.

| Use case | Proposal without preload | What changes for the feature |
|---|---|---|
| Debt collection task: contract selection | A: overview list pre-filtered on active commodity contracts; meter check and APA balance only for selected contracts | Balances are no longer shown for all contracts up front. Contracts without a meter appear in the list and are rejected after selection. Check in the spike whether “active” already implies “has meter” — then the filter needs no detail data at all |
| Disconnection task: contract selection | A: same pre-filter plus “not water”; market location, network operator and meter number loaded for the selection | These three columns move from the selection list to the confirmation step |
| Research task: contract selection | A: as debt collection task, without balances | Same as debt collection task |
| Forced-access lawsuits and legal files | C: customer-level indicator from EnWiCore, or B: notes query per contract | Needed before selection (legal risk). With B the indicators appear progressively; with A they would only be visible after selection, which is likely not acceptable |
| Disconnection process version | Overview Sector |
None |
| Task lists, events, delimitation tasks, avoidance agreements | Overview query | None; also removes TasksUtils.RunSync |
Kontodaten already has the natural pre-selection: APA returns the contracts with balances, and most
pages work on one selected contract (/kontodaten/:vertragsnummer).
| Use case | Proposal without preload | What changes for the feature |
|---|---|---|
| Contract selection | APA list + overview fields for every contract; payment type, next billing date, judicial dunning and business-customer flag loaded only for the selected contract | The selection list shows fewer columns; these values move to the header of the selected contract. Check in spike 1423738 whether APA already provides payment type or dunning state |
| Legal-department warning | C: customer-level indicator, or notes query in the background (B) | Same open question as forced-access lawsuits in claims management |
| Flexible-tariff check in the account overview | Tariff query only for the selected contract, or B for small customers as today | Marker appears after selection or progressively instead of with the first render |
| Payments and receipts pre-selection | Overview list | Button enabled immediately |
| UI gating | Remove contractsPreloadFinished / contractsPreloadSuccessful |
Each view shows its own loading state |
Parent: 1341022 — KBP: 2026 Technical maintenance. States as of 2026-10-12.
| Step | Work item | Content | State |
|---|---|---|---|
| 1 | 1264317 | Integrate getContractOverviewsByCustomerId; switch overview pages to it |
Done |
| 2 | 1423721 | Split the monolithic contract-details loading into domain-based queries; introduce combined DTO (CMDM and Kontodaten out of scope) | New |
| 3.1 | 1423731 | Spike: move every feature except CMDM and Kontodaten to the new queries; create one story per feature | In Progress |
| 4.1 / 4.2 | 1423734, 1423735 | Spike and refactoring for CMDM | New |
| 5.1 / 5.2 | 1423738, 1423740 | Spike and refactoring for Kontodaten | New |
| 6 | 1423742 | Verify ContractNew / ContractDetailsNew is the only access path |
New |
| 7 | 1423744 | Remove ContractsPreload and the Redis contract caching, including both Cacheable contract repositories |
New |
| 8 | 1423746 | Delete legacy Contract and ContractDetails entities |
New |
| 9 | 1423748 | Rename ContractNew / ContractDetailsNew to final names |
New |

Step 1 states that preloading stops, but the frontend still triggers the preload on every session
start and the endpoint Vertraege_Preload is active. Removing it is part of step 7; until then the
preload must keep working, because claims management and Kontodaten still depend on it.