intermediate
Genehmigung
Bedingungen
Compliance

Genehmigungs-Konditionsberichte

Wie permit_conditions-Zeilen das taegliche Compliance-Scoreboard antreiben -- Flotten-Cap, Equity-Zonen-Einsatz, Beschwerde-SLA, EOD-Trip-Bericht und Parkstellplatzauslastung.

Levy Fleets TeamMay 18, 202613 min read

Genehmigungs-Konditionsberichte

Die meisten Stadtgenehmigungen enthalten durchsetzbare Bedingungen -- Flotten-Cap, Equity-Zonen-Einsatz-Prozentsatz, Beschwerde-SLA, Tagesende-Trip-Bericht, Parkstellplatzauslastung. Levy speichert jede als Zeile in permit_conditions, wertet sie planmaessig aus und rendert die Pass/Fail-Tendenz sowohl im Betreiber-Dashboard als auch im Stadt-Portal.

Die fuenf Bedingungstypen

condition_typeWas geprueft wirdKadenz
fleet_capCOUNT(aktive Fahrzeuge in Zustaendigkeit) <= CapTaeglich
equity_zone_pct% der eingesetzten Flotte in Equity-Geografien >= SchwellwertStuendlich
complaint_slaresponded_at jeder Beschwerde innerhalb N Stunden nach created_atTaeglich
trip_report_eodEine Trip-CSV wird taeglich um 23:59 Ortszeit der Zustaendigkeit erstelltTaeglich
corral_utilizationStuendliche Momentaufnahme der Parkstellplatz-BelegungStuendlich

Jede Zeile in permit_conditions traegt die jurisdiction_id, den condition_type und eine config-JSONB, die je Typ variiert.

Bedingung konfigurieren

Im Betreiber-Dashboard unter /dashboard/compliance/{jurisdiction-id} -> Genehmigungsbedingungen-Tab auf Bedingung hinzufuegen klicken.

fleet_cap

{
  "cap": 250,
  "vehicle_types": ["scooter"],
  "exclude_statuses": ["maintenance", "removed"]
}

Der Evaluator zaehlt Fahrzeuge, bei denen:

  • subaccount_id zum Subaccount der Zustaendigkeit passt
  • last_lat/last_lng innerhalb mds_jurisdictions.geometry liegt (oder NULL mit last_seen in den letzten 24h -- grosszuegig fuer Fahrzeuge, die gerade ihr GPS verloren haben)
  • status NOT IN config.exclude_statuses
  • model.vehicle_type IN config.vehicle_types (falls gesetzt)

Pass = Anzahl <= Cap. Fail = Anzahl > Cap. Das Dashboard-Scoreboard zeigt die aktuelle Anzahl und das Cap nebeneinander.

equity_zone_pct

{
  "threshold_pct": 20,
  "equity_geography_ids": ["geo-1", "geo-2"],
  "min_vehicles_required": 5
}

Pass = eingesetzt-in-Equity / total-eingesetzt >= threshold_pct / 100. Der min_vehicles_required ist ein Mindestwert -- falls weniger Fahrzeuge ueberhaupt eingesetzt sind, wird die Bedingung fuer die Stunde als n/a markiert statt fehlgeschlagen (duenne Frueh-Morgen-Einsaetze).

Equity-Geografien stammen entweder aus dem Policy-Feed der Stadt (Regeln mit rule_type = 'equity_zone') oder einer manuell konfigurierten Liste (wenn die Stadt sie nicht veroeffentlicht -- haeufig bei kleineren Staedten). Die manuelle Liste lebt in derselben permit_conditions.config.

complaint_sla

{
  "max_response_hours": 24,
  "complaint_sources": ["311", "in-app", "email"]
}

Der Evaluator joinst complaints, um Zeilen zu finden, bei denen responded_at IS NULL AND created_at < now() - interval '<hours>' (offen und ueberfaellig) oder responded_at - created_at > interval '<hours>' (geschlossen aber zu spaet). Pass = keine spaeten Beschwerden im Fenster.

trip_report_eod

{
  "format": "csv",
  "delivery": "email",
  "recipient_email": "permits@city.gov"
}

Ein geplanter Bericht laeuft um 23:59 Ortszeit der Zustaendigkeit und emittiert eine Trip-CSV fuer den Tag. Pass = der Bericht wurde erfolgreich zugestellt. Fail = E-Mail abgewiesen oder die Berichtszeile materialisierte sich nicht.

corral_utilization

{
  "min_utilization_pct": 10,
  "max_utilization_pct": 90,
  "exclude_corral_ids": []
}

Stuendliche Momentaufnahme der Belegung jedes Parkstellplatzes. Pass = alle Parkstellplaetze zwischen konfiguriertem Boden und Decke. Fail = mindestens einer ausserhalb des Bands. Die Bedingung wird von Staedten verwendet, die sicherstellen wollen, dass Parken tatsaechlich genutzt wird (niedriges Ende) und dass Betreiber nicht ueberkonzentrieren (hohes Ende).

Das Scoreboard

Betreiber sehen das Scoreboard unter /dashboard/compliance/{jurisdiction-id}. Staedte sehen es im Stadt-Portal unter /city/{slug}. Die Form ist gleich:

BedingungAktueller WertSchwellwertStatus (heute)30-Tage-Pass-Rate
Flotten-Cap247 Fahrzeuge250Pass100%
Equity-Zone %18%>= 20%Fail88%
Beschwerde-SLA2 offen ueber 24h0Fail92%
Trip-Bericht EODZugestelltn/aPass100%
Parkstellplatz-Auslastung6 von 6 im BandAlle im BandPass95%

Fehlgeschlagene Bedingungen sind rot hervorgehoben. Die 30-Tage-Pass-Rate wird aus der city_compliance_reports-Historie berechnet.

Die Reporter-Bibliothek

src/lib/compliance/permit-reporter.ts exponiert Per-Bedingungs-Evaluatoren. Jeder nimmt die Zustaendigkeit + die zugehoerige permit_conditions.config und gibt zurueck:

type ConditionResult = {
  condition_id: string;
  passed: boolean;
  current_value: number | string;
  threshold: number | string;
  measured_at: string; // RFC3339
  details: Record<string, unknown>;
};

Der aggregierte Bericht zur Digest-Zeit:

const report = await buildComplianceReport({
  jurisdictionId,
  period: 'daily', // oder 'weekly' / 'monthly'
  date: '2026-05-18',
});

// report.conditions: ConditionResult[]
// report.summary.passed: number
// report.summary.failed: number
// report.trips: { count, total_distance_km, ... }
// report.complaints: { open, closed, late, ... }
// report.enforcement_events: { speed_limits_applied, locks_issued, ... }

Diese Form ist es, was die Digest-E-Mail rendert, was die Compliance-Report-API liefert und was an city_compliance_reports.payload persistiert wird.

Die Compliance-Report-API

Stadtkontakte holen formelle Berichte unter:

GET /api/city/{slug}/compliance-report?period=monthly&date=2026-05

Gibt dieselbe JSON-Form wie der Digest zurueck, plus einen pdf_url wenn period=monthly (wir rendern ein einseitiges PDF fuer Monatsberichte -- nuetzlich fuer Genehmigungsverlaengerungen).

Jeder abgerufene Bericht wird in city_compliance_reports persistiert -- der Audit-Trail jedes Berichts, den die Stadt tatsaechlich abgerufen hat. Wenn ein Stadt-Auditor fragt "haben Sie einen Bericht fuer Januar gesendet?", ist die Zeile in city_compliance_reports die Antwort.

Wenn Bedingungen fehlschlagen

Eine fehlgeschlagene Bedingung pausiert Ihre Flotte nicht automatisch. Sie wird sichtbar gemacht als:

  • Eine rote Zeile im Scoreboard
  • Eine Zeile im heutigen Digest
  • Ein Sentry-Breadcrumb markiert mit compliance.condition.failed

Was Sie damit tun, haengt von der Bedingung ab. Bei fleet_cap-Ueberschreitung weniger Fahrzeuge einsetzen. Bei equity_zone_pct umverteilen. Bei complaint_sla auf offene Beschwerden reagieren. Das System trifft keine operativen Entscheidungen fuer Sie.

Die Ausnahme ist fleet_cap, wo das Dashboard einen "Neue Fahrtstarts in Zustaendigkeit pausieren"-Schalter anbietet. Das ist ein Soft-Constraint -- bestehende Fahrten laufen weiter, keine neuen Fahrten starten in der Zustaendigkeit, bis der Cap wieder unter Schwellwert liegt. Der Schalter wird vom Betreiber gesteuert; wir aktivieren ihn nicht automatisch, weil Fehlalarme schlimmer waeren als ein voruebergehender fleet_cap-Fehler.

Benutzerdefinierte Bedingungen

Wenn Ihre Genehmigung eine Bedingung enthaelt, die zu keinem der fuenf eingebauten Typen passt (z.B. "muss 90% mehrsprachige Beschilderung erreichen"), fuegen Sie eine Zeile mit condition_type = 'custom' und einem manuellen passed-Feld hinzu. Der Evaluator gibt zurueck, was Sie setzen; Staedte sehen es weiterhin im Scoreboard. Das ist die Notfalloption fuer Genehmigungen mit einer kuriosen Klausel.

Was kommt als naechstes?