Permit-Condition Reports
Most city permits include enforceable conditions — fleet cap, equity-zone deployment percentage, complaint SLA, end-of-day trip report, parking corral utilization. Levy stores each as a row in permit_conditions, evaluates them on a schedule, and renders the pass/fail trend in both the operator dashboard and the city portal.
The five condition types
condition_type | What it checks | Cadence |
|---|---|---|
fleet_cap | COUNT(active vehicles in jurisdiction) <= cap | Daily |
equity_zone_pct | % of deployed fleet inside equity geographies >= threshold | Hourly |
complaint_sla | Each complaint's responded_at within N hours of created_at | Daily |
trip_report_eod | A trip CSV is generated daily at 23:59 jurisdiction-local time | Daily |
corral_utilization | Hourly snapshot of parking corral occupancy | Hourly |
Each row in permit_conditions carries the jurisdiction_id, condition_type, and a config JSONB that varies by type.
Configuring a condition
From the operator dashboard at /dashboard/compliance/{jurisdiction-id} -> Permit conditions tab, click Add condition.
fleet_cap
{
"cap": 250,
"vehicle_types": ["scooter"],
"exclude_statuses": ["maintenance", "removed"]
}
The evaluator counts vehicles where:
subaccount_idmatches the jurisdiction's subaccountlast_lat/last_lngis insidemds_jurisdictions.geometry(or NULL withlast_seenin the last 24h — generous to vehicles that just lost GPS)status NOT IN config.exclude_statusesmodel.vehicle_type IN config.vehicle_types(if set)
Pass = count <= cap. Fail = count > cap. The dashboard scoreboard shows the current count and the cap side by side.
equity_zone_pct
{
"threshold_pct": 20,
"equity_geography_ids": ["geo-1", "geo-2"],
"min_vehicles_required": 5
}
Pass = deployed-in-equity / total-deployed >= threshold_pct / 100. The min_vehicles_required is a floor — if there are fewer than that deployed at all, the condition is marked n/a for the hour rather than failed (sparse early-morning deployments).
Equity geographies come from either the city's Policy feed (rules with rule_type = 'equity_zone') or a manually configured list (when the city doesn't publish them — common with smaller cities). The manual list lives in the same permit_conditions.config.
complaint_sla
{
"max_response_hours": 24,
"complaint_sources": ["311", "in-app", "email"]
}
The evaluator joins complaints to find rows where responded_at IS NULL AND created_at < now() - interval '<hours>' (open and overdue) or responded_at - created_at > interval '<hours>' (closed but late). Pass = zero late complaints in the window.
trip_report_eod
{
"format": "csv",
"delivery": "email",
"recipient_email": "permits@city.gov"
}
A scheduled report runs at 23:59 jurisdiction-local time and emits a trip CSV for the day. Pass = the report was successfully delivered. Fail = email bounced or the report row didn't materialize.
corral_utilization
{
"min_utilization_pct": 10,
"max_utilization_pct": 90,
"exclude_corral_ids": []
}
Hourly snapshot of each parking corral's occupancy. Pass = all corrals between the configured floor and ceiling. Fail = at least one corral outside the band. The condition is used by cities that want to ensure parking is actually being used (low end) and that operators aren't over-concentrating (high end).
The scoreboard
Operators see the scoreboard at /dashboard/compliance/{jurisdiction-id}. Cities see it inside the city portal at /city/{slug}. The shape is the same:
| Condition | Current value | Threshold | Status (today) | 30-day pass rate |
|---|---|---|---|---|
| Fleet cap | 247 vehicles | 250 | Pass | 100% |
| Equity zone % | 18% | >= 20% | Fail | 88% |
| Complaint SLA | 2 open over 24h | 0 | Fail | 92% |
| Trip report EOD | Delivered | n/a | Pass | 100% |
| Corral utilization | 6 of 6 in band | All in band | Pass | 95% |
Failing conditions are highlighted in red. The 30-day pass rate is computed from the city_compliance_reports history.
The reporter library
src/lib/compliance/permit-reporter.ts exposes per-condition evaluators. Each takes the jurisdiction + the relevant permit_conditions.config and returns:
type ConditionResult = {
condition_id: string;
passed: boolean;
current_value: number | string;
threshold: number | string;
measured_at: string; // RFC3339
details: Record<string, unknown>;
};
The aggregate report at digest time:
const report = await buildComplianceReport({
jurisdictionId,
period: 'daily', // or '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, ... }
This shape is what the digest email renders, what the compliance-report API serves, and what gets persisted to city_compliance_reports.payload.
The compliance-report API
City contacts pull formal reports from:
GET /api/city/{slug}/compliance-report?period=monthly&date=2026-05
Returns the same JSON shape as the digest plus a pdf_url when period=monthly (we render a one-page PDF for monthly reports — useful for permit renewals).
Every fetched report is persisted to city_compliance_reports — the audit trail of every report the city has actually retrieved. If a city auditor asks "did you send a report for January?", the row in city_compliance_reports is the answer.
When conditions fail
A failing condition does not auto-pause your fleet. It surfaces:
- A red row on the scoreboard
- A line in today's digest email
- A Sentry breadcrumb tagged
compliance.condition.failed
What you do about it depends on the condition. For fleet_cap overage, deploy fewer vehicles. For equity_zone_pct, re-distribute. For complaint_sla, respond to the open complaints. The system doesn't make operational decisions for you.
The exception is fleet_cap, where the dashboard offers a "Pause new ride starts in jurisdiction" toggle. This is a soft constraint — existing rides continue, no new rides start in the jurisdiction until the cap is back under threshold. The toggle is operator-controlled; we don't activate it automatically because false-positive auto-pauses would be much worse than a transient fleet_cap fail.
Custom conditions
If your permit includes a condition that doesn't fit the five built-in types (e.g., "must achieve 90% multilingual signage"), add a row with condition_type = 'custom' and a manual passed field. The evaluator returns whatever you set; cities still see it on the scoreboard. This is the escape hatch for permits that have one weird clause.
What's next
- Digest Emails and Cadence — when these results land in the city's inbox.
- City Portal & Magic-Link Auth — where the city contact views the scoreboard.