Messages und Queues
Reference · Stand: 2026-09-22
Transport ist RabbitMQ über das Paket deutsche-dienstrad/dd-rabbitmq
(DistributingMessageConsumer routet eingehende Messages anhand des
Queue-Namens auf die in config/rabbitmq.php registrierten Consumer).
Verbindung per Env: RABBIT_MQ_HOST, RABBIT_MQ_PORT, RABBIT_MQ_USER,
RABBIT_MQ_PASSWORD, RABBIT_MQ_VHOST, RABBIT_MQ_SSL.
Konsumierte Queues
kpi-message → KpiMessageConsumer
Periodische Händler-KPI-Lieferung des Portals. Wird versioniert in
kpi_values gespeichert (KpiIngestService).
{
"supplierId": 123,
"supplierName": "Fahrrad Müller GmbH",
"periodStart": "2026-08-01",
"periodEnd": "2026-08-31",
"schemaVersion": 1,
"kpis": [
{ "key": "cost_rate", "value": 4.2 },
{ "key": "submission_rate_service_dealer", "value": 150.0 },
{ "key": "submission_rate_delivering_dealer", "value": 120.0 },
{ "key": "correction_rate", "value": 0.8 }
]
}
Versionierungsregeln (KpiIngestService), je (supplier, kpi_key):
| Eingehende Periode | Verhalten |
|---|---|
| gleich der aktuellen | Wert in place aktualisieren |
| neuer als die aktuelle | aktuelle Zeile abschließen (superseded_at), neue als aktuell anlegen |
| älter als die aktuelle | als bereits abgelöste historische Zeile ablegen (Redelivery-sicher) |
| noch kein Wert vorhanden | als aktuell anlegen |
Unbekannte keys werden gespeichert, aber nicht bewertet, solange keine Regel
existiert.
evaluation-request → EvaluationRequestConsumer
Bewertungsanfrage für einen konkreten Fall. Servicefall-KPIs im Payload,
Händler-KPIs kommen aus dem Bestand. Löst den Job EvaluateSupplier aus.
{
"supplierId": 123,
"caseId": 456,
"caseType": "technical_service",
"kpis": [
{ "key": "wear_cost", "value": 180.5 },
{ "key": "service_cost_rate", "value": 6.0 },
{ "key": "ordinary_wear_violated", "value": 1 }
]
}
caseType ist enum-validiert (ScoreRequestCaseType); aktuell existiert nur
technical_service. ordinary_wear_violated: 1 = gewöhnlicher Verschleiß
eingehalten, 0 = nicht eingehalten (Name ist historisch invertiert, siehe
KPIs und Grenzwerte).
Publizierte Queues
evaluation-result (aus EvaluateSupplier)
{
"supplierId": 123,
"caseId": 456,
"caseType": "technical_service",
"status": "completed",
"haendlerCluster": "inconspicuous",
"servicefallCluster": "critical",
"haendlerKpiBands": {
"cost_rate": "inconspicuous",
"submission_rate_service_dealer": "inconspicuous",
"submission_rate_delivering_dealer": "inconspicuous",
"correction_rate": "inconspicuous"
},
"servicefallKpiBands": {
"wear_cost": "critical",
"service_cost_rate": "inconspicuous",
"ordinary_wear_violated": "inconspicuous"
},
"auditIntensityPercent": 50,
"evaluatedAt": "2026-09-22T10:15:00+00:00"
}
Bei status: "incomplete" sind Cluster, Bänder und auditIntensityPercent
null.
Enum-Werte: status ∈ completed | incomplete; Bänder/Cluster ∈
inconspicuous | critical | risky.
Portal-Seite (Monolith)
Das Gegenstück lebt im Monolith unter api/app/Modules/TechnicalServices/Cluster/
und spricht exakt die oben dokumentierten Queues:
| Richtung | Queue | Absender/Empfänger im Portal |
|---|---|---|
| Portal publiziert | kpi-message |
SupplierKpiPublishService via Command technical-services:publish-cluster-kpis (monatlich am 1., 03:00, pro aktivem Händler) |
| Portal publiziert | evaluation-request |
SupplierScoreService::request() — setzt den Servicefall transaktional auf ScoreRequested, bevor die Message rausgeht |
| Portal konsumiert | evaluation-result |
SupplierScoreResponseHandler — speichert das Ergebnis als ClusterEvaluation, entscheidet dann per Lottery mit auditIntensityPercent über manuelle Prüfung vs. Auto-Freigabe (incomplete → immer manuell) |
Herkunft der Servicefall-KPI-Werte im Portal: wear_cost = Brutto-Summe des
Falls, service_cost_rate = Verschleißkosten / UVP des Vertrags-Produkts
(productListPrice, gerundet auf eine Nachkommastelle), ordinary_wear_violated
= 1 wenn alle Verschleißteil-Limits (max_contract_exchanges) über den
Vertrag eingehalten sind, sonst 0.