KOP Integration
Renewa One handles three different deal types based on who is the customer’s primary contractor: Renewa itself, a Renewa-partnered Handwerker, or a customer-chosen external Handwerker. The two non-EB types are collectively referred to as KOP in conversation and in UI labels — in code, the vocabulary was renamed to contractor in May 2026 (PR#1690, the P2 contractor-engagement rebuild).
This page is the canonical reference for what those terms mean, why we use them, and how the rename actually landed.
The three engagement types
engagement_type | Customer’s primary contractor | Who initiated that contractor | Renewa’s role | Value flow to Renewa |
|---|---|---|---|---|
energy_consulting (spoken: “EB”) | Renewa | Renewa-direct sale | Executes the work | Direct sales revenue |
internal_contractor (spoken: “Provi-KOP”) | Renewa-partnered Handwerker | Renewa (introduces the partner to the customer) | Coordinates, oversees | Commission on the Handwerker’s invoices to the customer |
external_contractor (spoken: “Nicht-Provi-KOP”) | Customer-chosen Handwerker | The customer (brings their own Handwerker) | Tracks for FUE compliance only | None (FUE = Fachunternehmererklärung; the contractor may become a Renewa partner later, but until then no value flow) |
The discriminator is projects.engagement_type (Postgres pgEnum, default energy_consulting), denormalized onto workflow_packages.engagement_type so packages without a project_id (manual external_contractor entries) still carry it. It drives lifecycle, billing, dunning, and which workflow template the package instantiates from (template.engagementType must match the project’s).
What “KOP” actually means here
KOP historically stands for Kooperationspartner (cooperation partner). That meaning fits internal_contractor — Renewa really does have a cooperation/commission agreement with that Handwerker. But it does not fit external_contractor — there’s no agreement with that contractor; the customer just brought them in.
That acronym mismatch is exactly why the code-level rename happened (names track current domain scope). The split today:
- Code identifiers are uniformly
contractor: enum values, tables (contractor_customer_inquiries), services (ContractorHandoverService,ContractorCustomerInquiryService), routes (/api/workflow/contractor-engagement/), components (frontend/src/components/workflow/contractor/). - Display strings keep “KOP” intentionally — the Renewa team verbally says “KOP”, “Provi-KOP”, “Nicht-Provi-KOP” all day, and HubSpot pipelines, sales materials, and the UI labels match that vocabulary. i18n keys like
"KOP-Übergabe","Intern (KOP)","Provi-KOP","Nicht-Provi-KOP"live infrontend/src/locales/de/workflow.json.
How a deal becomes a package
HubSpot Deal (Won)
│
▼ pipeline_id lookup (env-driven map, fail-fast on unknown pipelines)
│
├── HUBSPOT_PIPELINE_ENERGY_CONSULTING → engagement_type = 'energy_consulting'
├── HUBSPOT_PIPELINE_INTERNAL_CONTRACTOR → engagement_type = 'internal_contractor'
└── HUBSPOT_PIPELINE_EXTERNAL_CONTRACTOR → engagement_type = 'external_contractor'
│
▼ workflow_packages row created with status='active' (single-step handover, no draft)
│
├── (energy_consulting) Quote-line-items bestimmen Workflow-Template-Selection (matchTemplates())
├── (internal_contractor) commission fields required at handover; rate from
│ projects.commission_rate (HubSpot deal "provision")
└── (external_contractor) project_id may be NULL (manual entry); FUE-only
The pipeline-ID → engagement_type mapping lives in env vars (backend/src/lib/hubspot-pipelines.ts), one per environment — not a config table. An unknown pipeline raises UnknownHubspotPipelineError: a HubSpot deal from an unmapped pipeline is not persisted as a workflow package, and there is no silent default to energy_consulting.
There is no contractor metadata side-table: the planned kop_package_metadata never survived review. The assigned contractor relationship flows through the HubSpot association infrastructure (getAssociationsByRenewaName('contractor', …); the “Zugewiesener KOP AP” sync landed in PR#1891).
Lifecycle differences
All three types share the workflow_packages family (workflow_packages, workflow_package_phases, workflow_package_phase_tasks); phases are template-driven per engagement type rather than a hardcoded Planung → Umsetzung → Fertig sequence. Handover is single-step since PR#1855: packages are created active directly (the draft status value remains in the enum for legacy reads only).
| Aspect | energy_consulting | internal_contractor | external_contractor |
|---|---|---|---|
| Trade-start signal | Bauarbeitsvertrag-driven tasks | Planning signal / manual | Customer portal inquiry “Gewerk gestartet am DATUM” via contractor_customer_inquiries (trade_start_check, responses started / not_yet_started). The recurring scheduler asking the customer is deferred — the inquiry/response flow exists in ContractorCustomerInquiryService |
| Tranches / billing | Quote-line-item-driven via billing | Tranche tasks (workflow_package_phase_tasks with billing metadata) surfaced in ContractorCommissionInvoicesTable; commission rate from projects.commission_rate, effective rate via effectiveCommissionRate() | None — contractor invoices the customer directly (0% to Renewa) |
| Renewa billing involvement | Direct invoicing (billingInvoices, dealType EB) | Receives Provi-Rechnung from partner (billingInvoices, dealType KOP) | None |
| FUE tracking | n/a | Optional (depends on contract) | Mandatory (Renewa documents the external Handwerker’s work for funding compliance) |
Where to look in the code
| What | Where |
|---|---|
The engagement_type enum | renewa-one/backend/src/db/schema.ts → engagementTypeEnum (on projects, denormalized to workflowPackages) |
| Pipeline-ID lookup | renewa-one/backend/src/lib/hubspot-pipelines.ts (pipelineToEngagementType(), env-driven map) |
| Package creation logic | renewa-one/backend/src/lib/workflow/workflow-package-factory.ts (branches on engagementType) |
| Handover / tranche listing | renewa-one/backend/src/services/workflow-package/contractor-handover-service.ts (+ WorkflowPackageHandoverService for project-sourced candidates) |
| Customer inquiry service | renewa-one/backend/src/services/workflow-package/contractor-customer-inquiry-service.ts (table contractor_customer_inquiries) |
| Routes | renewa-one/backend/src/routes/workflow/contractor-engagement/ + consolidated /api/workflow/packages/* |
| Service-container keys | contractorHandover, contractorCustomerInquiry (create-services.ts) |
external_contractor workspace UI | renewa-one/frontend/src/components/workflow/contractor/ExternalContractorWorkspace.tsx |
| Commission table UI | renewa-one/frontend/src/components/workflow/contractor/ContractorCommissionInvoicesTable.tsx |
| Commission rate logic | renewa-one/backend/src/lib/contractor-commission.ts (effectiveCommissionRate()) |
| Specs | 2026-05-09-contractor-engagement-rebuild-design.md (P2, current); 2026-04-24-v3-kop-integration-design.md (V3 reference, retained as-is) |
The rename shipped: KOP → Contractor
The “KOP-as-Renewa-internal-acronym” debt described in earlier versions of this page was paid down in PR#1690 (P2 contractor-engagement rebuild). What actually shipped, versus the migration plan once sketched here:
| Planned | As-built |
|---|---|
internal_kop → partner_contractor, external_kop → customer_contractor | eb → energy_consulting, internal_kop → internal_contractor, external_kop → external_contractor — and the discriminator moved from workflow_packages.package_type to projects.engagement_type |
kop_package_metadata → contractor_package_metadata | Table dropped entirely; contractor linkage via association infrastructure (no contractor_id FK — I#1675’s pattern was rejected) |
kop_customer_inquiries → contractor_customer_inquiries | As planned (incl. contractor_inquiry_type enum) |
KopHandoverService → ContractorHandoverService | As planned; later slimmed to tranche-listing by the handover redesign (PR#1855) |
hubspot_pipeline_mappings config table updated | Config table replaced by env vars (HUBSPOT_PIPELINE_*) |
| Wiki page renamed to “Contractor Integration” + glossary anchor | Page kept under “KOP Integration” — KOP remains the spoken/UI term, so this name is still the search anchor |
UI display strings deliberately still say “KOP” (team vocabulary; user-facing terminology unchanged). The rule applied was names track current domain scope: code identifiers renamed, coined spoken terms stay.
Workflow v4 status
The package model is evolving under the Workflow v4 umbrella (I#1933):
- PR-1 (data layer foundation) merged 2026-06-11 (PR#1942): 5-axis weighted Kanban scoring (LIFO / deadline / priority / revenue / customer score), 3-level effective-deadline resolution (task > phase > package), phase
dueDatestamping from template average durations,PATCH /api/workflow/packages/:iddeadline changes journaled toactivity_logs, andprojects.customer_scoresnapshotted onto packages at handover. - Siblings pending: I#1934 (Admin Kanban UI), I#1935 (Wochenplan).
- No new table layer — same
workflow_packagesfamily with evolved semantics (vestigial V3 columns dropped per the purify-workflow-packages spec, 2026-05-15).
Related
Workflow Engine(TBD — the broader workflow-package model)- HubSpot Integration — pipeline configuration + sync flow
- Financial Calculations — commission rates,
billingInvoices, settlement FUE(TBD — Fachunternehmererklärung tracking forexternal_contractor)