Anleitung · Data Integrity Monitor ← zurück zum Dashboard

HubSpot Data Integrity Monitor

Ein leichtgewichtiges Dashboard, das PKI-B2C-Deals live aus HubSpot abruft und auf Daten-Inkonsistenzen prüft.

Was macht das Tool?

Das Tool zieht sich bei jedem Klick auf „Prüfung starten" alle Deals der Pipeline B2C | Weiterbildung gefördert | Asynchron sowie deren Maßnahmebausteine live aus der HubSpot-API und schickt sie durch eine Reihe von Validatoren. Jeder Validator produziert eine Liste von Auffälligkeiten („Issues"), die im Frontend gruppiert, gefiltert und durchgearbeitet werden können.

Scope v1: Nur PKI-B2C (asynchron). Andere Pipelines (PM, IT-Sales, PKI-B2B, Job-Vermittlung) werden bewusst ignoriert. Das Tool liest Daten von HubSpot, Close.com und dem Course-Calculation-Service. Schreibzugriff auf HubSpot gibt es nur über zwei explizite Pfade: die Triage-Drawer („Ausgewählte übernehmen") und die Bulk-Fix-Skripte im src/scripts/-Ordner.

Die Checks

Es gibt aktuell sechs Validatoren. Jeder produziert Issues mit Deal-Name, eventuell Baustein-Name, Feld und Befund-Text sowie Deep-Links nach HubSpot und Close.

Deal-Pflichtfelder Fehler

Prüft für jeden PKI-B2C-Deal, ob eine konfigurierte Liste von Pflichtfeldern befüllt ist. Leere/null-Werte erzeugen pro Feld ein Issue.

Aktuelle Pflichtfeld-Liste:

Start-abhängige Pflichtfelder: Felder wie n360_learning_id werden nur geflaggt, wenn das Vertrags-Startdatum heute oder in der Vergangenheit liegt. Deals, die noch nicht gestartet sind oder kein Startdatum haben, werden für solche Felder nicht als unvollständig gewertet.

Erweitern: Neues Feld in src/config/fields.jsDEAL_REQUIRED_FIELDS eintragen, Server neustarten. Kein Code-Change an Validatoren nötig.

Maßnahmebaustein-Pflichtfelder Fehler

Analog zu oben, aber für jeden einzelnen Maßnahmebaustein des Deals. Issues werden dem übergeordneten Deal zugeordnet, zeigen aber welcher Baustein (z.B. „#3 KI-03") betroffen ist.

Geprüfte Felder: amount, duration_ue, start_date_modul, end_date_modul, status_360_learning_, mapping_id_360, modular_part_order, internal_id.

Deal ↔ Maßnahmebaustein Konsistenz Warnung

Prüft strukturelle Beziehungen zwischen Deal und seinen Bausteinen:

  1. Deal hat mindestens einen verknüpften Baustein.
  2. Jeder Baustein hat ein Startdatum und ein Enddatum.
  3. Start < Ende pro Baustein.
  4. Baustein-Zeitraum liegt im Deal-Vertrags-Zeitraum (startdate_according_to_contract bis enddate_according_to_contract). Toleranz aktuell: 0 Tage.

Enrollment-Status (360 Learning) Warnung

Prüft pro Baustein, ob der 360-Learning-Status zum Startdatum passt:

Die Warnung trägt zusätzlich eine Sub-Kategorie (z.B. „soll Freigeschaltet · ist Archiviert"), nach der im Dashboard gefiltert werden kann.

Kalender-Date-Vergleich: der Check vergleicht auf Kalendertag-Ebene (YYYY-MM-DD-String), nicht auf Timestamp — dadurch gibt's keine Zeitzonen-Fehler an Modul-Startzeiten.

Deal-Zeitraum-Abdeckung Warnung

Prüft, ob die Maßnahmebausteine den Vertrags-Zeitraum des Deals lückenlos und exakt abdecken:

  1. Erster Baustein-Start = Deal-Start: startdate_according_to_contract muss exakt dem Startdatum des ersten Bausteins entsprechen.
  2. Letzter Baustein-Ende = Deal-Ende: enddate_according_to_contract muss exakt dem Enddatum des letzten Bausteins entsprechen.
  3. Lücken-Check: Zwischen aufeinanderfolgenden Bausteinen darf die Pause maximal 14 Tage betragen. Ein typisches PKI-B2C-Modul ist deutlich länger — eine größere Lücke deutet fast immer auf einen fehlenden Modulbaustein in der Mitte hin.
  4. Überlappungs-Check: Keine zwei Bausteine desselben Deals dürfen sich zeitlich überschneiden. Überschneidet sich z.B. das Enddatum von KI-05 mit dem Startdatum von KI-06, wird das geflaggt.

Archivierte Bausteine (Status Archiviert) werden für diese Prüfung ignoriert — es zählt die aktuell gültige Modul-Reihe.

Die Sub-Kategorien („Deal-Start ≠ erster Baustein", „Deal-Ende ≠ letzter Baustein", „Lücke N Tage", „Überlappung N Tage") lassen sich im Dashboard einzeln filtern.

Doppelte Modul-Bausteine Warnung

Findet Deals, an denen dieselbe Modul-Kennung (internal_id) mehrfach als Maßnahmebaustein hängt — typischer Legacy-Fall: ein alter Workflow hat bei Startdatum-Änderungen einen neuen Baustein angelegt und den alten nur über den Status Archiviert markiert, statt ihn zu entfernen. Der Teilnehmer hat dann technisch z.B. zwei KI-05-Bausteine.

Warum wichtig: ohne diesen Check fällt das in der Triage nur dann auf, wenn beide Instanzen Abweichungen haben. Hat die aktive Instanz bereits saubere Daten, würde die archivierte Zwillings-Instanz sonst übersehen.

Die Sub-Kategorie zeigt die Häufigkeit (z.B. „2× KI-05") zum Filtern.

Die Oberfläche

Summary-Kacheln

Oben: Gesamt-Issues · Deals betroffen · Bausteine geprüft · Checks ausgeführt. „Deals ausgeschlossen" zählt die Test-Deals, die aus dem Lauf entfernt wurden.

Filter-Toolbar

Issue-Zeile — Aktionen rechts

Test-Deals

Test-Deals werden bei jedem nächsten „Prüfung starten" komplett übersprungen (Deal und alle seine Maßnahmebausteine). Nützlich für interne Test-Teilnehmer, die nicht mit echten Daten gemischt werden sollen.

Triage

Die Triage-Drawer ist das Hauptwerkzeug, um Auffälligkeiten an einem Deal in einem Rutsch zu bereinigen. Klick auf „Triage" an einer Issue-Zeile öffnet einen Slide-Over mit allen Daten, die der Deal berührt — und einem Apply-Button, der ausgewählte Korrekturen direkt nach HubSpot schreibt.

Was die Triage zusammenzieht

  1. HubSpot-Deal + alle Maßnahmebausteine mit aktuellen Werten.
  2. Close.com: über die Close-Lead-ID am Deal werden die Custom-Activities „Teilnehmervertrag" (Normalfall + Sonderfall) geholt. Die neueste Published-Activity gilt als autoritativ — sie liefert das Start-Datum und die Modul-Liste, die der Teilnehmer vertraglich gebucht hat.
  3. Course-Calculation-Service: mit Start-Datum + Modul-Liste aus Close als Inputs berechnet er Enddatum, Amount, Urlaubstage, pro Modul Start/Ende/Dauer/Amount.

Dieser Calc-Output mit Close-Inputs ist die Soll-Welt. Alles was in HubSpot davon abweicht, wird als Vorschlag (Suggestion) aufgelistet.

Warnung bei Startdatum-Konflikt

Weicht das HubSpot-startdate_according_to_contract vom Close-Vertrags-Startdatum ab, erscheint oben in der Drawer eine rote Warnung. Dann zuerst klären, welches Datum stimmt — bevor die berechneten Folgewerte (Amount, Urlaubstage, Modul-Daten) blind übernommen werden.

Vorschläge (Suggestions)

Jeder Vorschlag ist eine einzelne Checkbox-Zeile mit:

Vorschläge werden gruppiert: eine Gruppe für Deal-Ebene (Amount, Vertrags-Start/-Ende, Urlaubstage), dann eine Gruppe pro Maßnahmebaustein-Instanz mit Header wie:

Modul KI-05 MB 424316164320 Freigeschaltet

Der MB-ID-Link öffnet den Baustein direkt in HubSpot. Die Status-Pill zeigt den aktuellen status_360_learning_-Wert — grün bei „Freigeschaltet", rot bei „Archiviert", neutral sonst. Bei Deals mit doppelten Modulen (siehe Check „Doppelte Modul-Bausteine") erscheinen beide Instanzen als getrennte Gruppen, sodass klar ist, welche MB-ID bearbeitet wird.

Apply-Flow

Die gewünschten Checkboxen auswählen und auf „Ausgewählte übernehmen" klicken. Ablauf:

  1. Der Server holt die Triage-Daten frisch noch einmal, damit zwischenzeitlich in HubSpot geänderte Werte nicht überschrieben werden. Vorschläge, bei denen current und proposed inzwischen gleich sind, werden als „nicht mehr aktuell" markiert und übersprungen.
  2. Die verbleibenden Writes werden pro Objekt gebündelt und via HubSpot Batch-API geschrieben: Deal-Properties zusammen, MB-Properties zusammen.
  3. Pro Vorschlag kommt ✓ oder ✗ mit Fehlermeldung zurück. Nach erfolgreichem Apply lädt die Drawer sich selbst neu — angewandte Vorschläge verschwinden, verbleibende werden klarer sichtbar.
Fehlt ein HubSpot-Scope (crm.objects.deals.write oder crm.objects.custom.write), antwortet der Server mit 403 und der Drawer zeigt rot den exakten fehlenden Scope. In der Private App → Scopes nachtragen → Token rotieren → neues Token in .env.

Calc-Vergleich (kompaktes Panel)

Kleinere, read-only Variante der Triage: ein Klick auf „Calc" in einer Issue-Zeile öffnet ein Slide-Over, das den Deal durch den Course-Calculation-Service schickt und die Ergebnisse mit den HubSpot-Werten vergleicht. Kein Close-Abruf, kein Apply-Button — nützlich, wenn man nur schnell schauen will, was rechnerisch sein sollte.

Inputs an den Calc-Service

Was verglichen wird

FeldHubSpotCalcToleranz
Vertrags-Startstartdate_according_to_contractstart_dateexakt
Vertrags-Endeenddate_according_to_contractend_dateexakt
Amountamounttotal_cost±0,01 €
Urlaubstage gesamtanzahl_urlaubstage_insgesamt_b2c_asynchronholiday_countexakt
Urlaubstage-Listeurlaubstage_liste__b2c_asynchronholidays_stringSet-Match (Reihenfolge egal)
pro Modul: Start / Endestart_date_modul / end_date_modulstart_date / end_dateexakt
pro Modul: Amountamountamount±0,01 €
pro Modul: UEsduration_ueuesexakt

Duplikate: hat ein Deal mehrere Bausteine mit der gleichen internal_id (z.B. einer archiviert, einer aktiv), werden beide angezeigt und gegen den gleichen Calc-Modul-Wert verglichen.

Modul-Set: separate Warnung wenn Module fehlen (in Calc aber nicht in HubSpot) oder überzählig sind (in HubSpot aber nicht in Calc-Output).

Tastatur-Shortcuts

Datenquelle & Auth

Die Anwendung nutzt eine HubSpot Private App. Das Token liegt in der lokalen .env (gitignored). Alle API-Calls laufen serverseitig (Token verlässt nie das Backend).

Login: Sind AUTH_USERNAME und AUTH_PASSWORD in .env gesetzt, ist /api/* hinter einer eigenen Login-Seite (/login) geschützt. Die Credentials werden im Browser-localStorage gehalten und bei jedem Request manuell als Authorization: Basic-Header mitgeschickt — der native Browser-Auth-Dialog erscheint bewusst nicht. Leere Werte deaktivieren die Auth (nur für lokale Entwicklung empfohlen).

HubSpot-Scopes: Lesen funktioniert mit den .read-Scopes (crm.objects.deals.read, crm.objects.custom.read, crm.schemas.*). Für den Triage-Apply-Flow zusätzlich crm.objects.deals.write und crm.objects.custom.write nötig — ohne sie bleibt Lesen + Calc-Vergleich nutzbar, nur „Ausgewählte übernehmen" scheitert mit 403.

Rate-Limiting: Ein Wrapper im Client fügt zwischen Calls einen Mindestabstand von ~110 ms ein und retry'd automatisch bei 429-Responses.

Skripte (src/scripts/)

Erweitern / Anpassen

Troubleshooting