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.
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:
- Amount (
amount) - Anzahl Urlaubstage | B2C Asynchron (
anzahl_urlaubstage_insgesamt_b2c_asynchron) - Urlaubstage-Liste | B2C Asynchron (
urlaubstage_liste__b2c_asynchron) - Startdatum laut Vertrag (
startdate_according_to_contract) - Enddatum laut Vertrag (
enddate_according_to_contract) - Close Lead ID (
close_contact_id_of_linked_student) - 360 Learning ID (
n360_learning_id) — start-abhängig, siehe Hinweis unten - Maßnahmennummer (
os_measure_number) - Closer User ID (
sales_mitarbeiter__user_id_) - Kundennummer Kostenträger (
student_cost_bearer_customer_id) - Teilnehmer E-Mail Franklin (
student_fi_mail) - BG Count (
won_bg_count) - Won Date Sales (
won_date_sales)
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.js → DEAL_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:
- Deal hat mindestens einen verknüpften Baustein.
- Jeder Baustein hat ein Startdatum und ein Enddatum.
- Start < Ende pro Baustein.
- Baustein-Zeitraum liegt im Deal-Vertrags-Zeitraum (
startdate_according_to_contractbisenddate_according_to_contract). Toleranz aktuell: 0 Tage.
Enrollment-Status (360 Learning) Warnung
Prüft pro Baustein, ob der 360-Learning-Status zum Startdatum passt:
- Ist das Startdatum erreicht (heute ≥ Start), muss der Status
Freigeschaltetsein. - Ist das Startdatum noch in der Zukunft (heute < Start), muss er
Nicht freigeschaltetsein. - Alles andere (inkl.
Archiviert,Jetzt freischaltenoder leer) erzeugt eine Warnung.
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:
- Erster Baustein-Start = Deal-Start:
startdate_according_to_contractmuss exakt dem Startdatum des ersten Bausteins entsprechen. - Letzter Baustein-Ende = Deal-Ende:
enddate_according_to_contractmuss exakt dem Enddatum des letzten Bausteins entsprechen. - 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.
- Ü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
- Check — Chips mit Counts pro Validator, toggle.
- Severity — Fehler / Warnung, toggle.
- Sub-Status — erscheint nur, wenn mindestens ein Check-Filter aktiv ist (sonst mischen sich Sub-Kategorien verschiedener Check-Typen und die Liste wird unübersichtlich). Zeigt dann z.B. bei „Enrollment-Status" die „soll X · ist Y"-Chips, bei „Doppelte Modul-Bausteine" die Häufigkeit.
- Suche — Freitext über Deal-Name, Baustein-Name, Feld und Message.
- Feld — Multi-Select-Dropdown aller unique Felder mit Counts; mehrere gleichzeitig aktivierbar.
- Sortierung — nach Check · Deal · Feld · Severity.
- Die Counts an den Chips passen sich an die anderen aktiven Filter an.
Issue-Zeile — Aktionen rechts
- Triage — öffnet Drawer mit kompletter Fix-Übersicht (Close-Vertrag + Calc-Vergleich + Checkbox-Apply, siehe unten).
- Calc — kompaktes Panel mit reinem Calc-Service-Vergleich ohne Write-Flow.
- HubSpot ↗ — springt direkt zum Datensatz (Deal oder Baustein, je nach Issue).
- Close ↗ — öffnet den verknüpften Close-Lead (nur wenn
close_contact_id_of_linked_studentam Deal gesetzt ist). - Test-Deal — markiert den Deal als „ignorieren bei nächster Prüfung".
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.
- Hardcoded: zentrale, team-weite Test-Deals in
src/config/testDeals.js(im Repo eingecheckt). - Browser-local: individuelle Einträge werden im
localStoragedes Browsers gespeichert — sie bleiben lokal, werden nicht mit anderen geteilt und nicht auf dem Server persistiert. - Markieren: Button „Test-Deal" an einer Issue-Zeile. Deal verschwindet sofort aus der aktuellen Ansicht.
- Verwalten: Button „Test-Deals" oben im Header öffnet das Panel mit Hardcoded-Liste (read-only) + eigenen Einträgen inkl. „entfernen"-Button.
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
- HubSpot-Deal + alle Maßnahmebausteine mit aktuellen Werten.
- 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.
- 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:
- Feld-Label (z.B. „KI-05 · Startdatum")
current → proposed— aktueller HubSpot-Wert und der Soll-Wert- Source-Badge (
close-vertragodercalc-authoritativeoderenrollment-rule) + Begründung
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:
- Der Server holt die Triage-Daten frisch noch einmal, damit zwischenzeitlich in HubSpot geänderte Werte nicht überschrieben werden. Vorschläge, bei denen
currentundproposedinzwischen gleich sind, werden als „nicht mehr aktuell" markiert und übersprungen. - Die verbleibenden Writes werden pro Objekt gebündelt und via HubSpot Batch-API geschrieben: Deal-Properties zusammen, MB-Properties zusammen.
- 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.
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
start_date= Deal-Feldstartdate_according_to_contract.modules= alle uniqueinternal_id-Werte der MBs (archiviert + aktiv, dedupliziert).
Was verglichen wird
| Feld | HubSpot | Calc | Toleranz |
|---|---|---|---|
| Vertrags-Start | startdate_according_to_contract | start_date | exakt |
| Vertrags-Ende | enddate_according_to_contract | end_date | exakt |
| Amount | amount | total_cost | ±0,01 € |
| Urlaubstage gesamt | anzahl_urlaubstage_insgesamt_b2c_asynchron | holiday_count | exakt |
| Urlaubstage-Liste | urlaubstage_liste__b2c_asynchron | holidays_string | Set-Match (Reihenfolge egal) |
| pro Modul: Start / Ende | start_date_modul / end_date_modul | start_date / end_date | exakt |
| pro Modul: Amount | amount | amount | ±0,01 € |
| pro Modul: UEs | duration_ue | ues | exakt |
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
- Enter — startet die Prüfung (außerhalb von Input-Feldern).
- Esc — schließt Calc- oder Triage-Panel.
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/)
npm run introspect— listet Pipelines, Custom-Object-Schemas und Deal-Properties; Basis für die Config.npm run introspect-close— Close-Custom-Activity-Types + Custom-Fields.npm run dump-deal [dealId]— druckt alle Properties eines Deals + seiner MBs.node src/scripts/analyzeArchived.js— klassifiziert archivierte MBs (orphan vs. has-replacement).node src/scripts/fixArchivedStatuses.js— Bulk-Fix für Archiv-Status-Anomalien (Dry-Run Default).node src/scripts/reportRecentChanges.js [minutes=30]— Audit-Report: welche Deals + MBs wurden in den letzten N Minuten geändert, welche Felder von alt → neu. Praktisch direkt nach einer Triage-Apply-Serie zum Gegenlesen.
Erweitern / Anpassen
- Neues Pflichtfeld überwachen:
src/config/fields.js→DEAL_REQUIRED_FIELDSoderMB_REQUIRED_FIELDSergänzen → Server restart. Start-abhängige Felder zusätzlich im SetSTART_DEPENDENT_FIELDSinsrc/validators/dealRequiredFields.jseintragen. - Neuen Check hinzufügen: Datei in
src/validators/anlegen (exportiert{ id, name, description, severity, run(dataset) }), insrc/validators/index.jsregistrieren. - Weitere Pipeline (z.B. PKI B2B): Pipeline-ID in
src/config/fields.jspflegen; falls parallel zu PKI-B2C, Fetch-Layer um Multi-Pipeline-Support erweitern.
Troubleshooting
- „HUBSPOT_PRIVATE_APP_TOKEN is missing" —
.envfehlt oder leer. Kopiere.env.example→.env. - 414 Request-URI Too Large — beim Abrufen aller 672 Deal-Properties via GET. Wird intern durch Batch-Read (POST) gelöst.
- Calc-Panel zeigt Fehler — prüfe
COURSE_CALCULATION_SERVICE_URLundCOURSE_CALCULATION_SERVICE_API_KEYin.env. Der Fehler-Body wird angezeigt. - Triage-Drawer: „HubSpot-Scope fehlt" — Private App um
crm.objects.deals.writebzw.crm.objects.custom.writeergänzen, Token rotieren,.envaktualisieren, Server neustarten. - Triage-Drawer: „Keine Close-Anbindung möglich" —
close_contact_id_of_linked_studentam Deal ist leer oder enthält keinelead_…-ID. Close-Section bleibt leer, Calc fällt auf HubSpot-Inputs zurück. - Verdächtig viele Enrollment-Warnungen — oft ein Hinweis auf einen versehentlich getriggerten Archive-Workflow in HubSpot. Bulk-Fix via
node src/scripts/fixArchivedStatuses.js(Dry-Run Default).