Zum Inhalt

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.