feat(n8n): add scheduled quality-scan workflow

The original docs described two n8n workflows but the repository only ever
shipped one (return-processing); the sketched second workflow (knowledge
sync) depends on RAGcore, which isn't connected here, so it stays deferred.

Add POST /api/v1/integrations/n8n/scheduled-scan (X-Service-Token
protected, same pattern as the return callback), calling the same
run_scan() the manual "Run quality scan" UI action uses and recording a
service-actor data_quality_scan_run audit event. run_scan() already only
creates an issue for a condition without one open, so overlapping triggers
do no duplicate domain work.

n8n/mobilityops-scheduled-quality-scan.json (hourly schedule + manual test
trigger, both feeding the same HTTP call) ships "active": false so it can't
fire anywhere until deliberately published. Verified live against the
local n8n instance via the Manual test trigger: full green execution, and
the resulting data_quality_scan_run audit event (actor_type=service,
actor_label="n8n scheduled scan") confirms the real round trip, not just a
contract test. deploy/unraid/setup-scheduled-scan.sh mirrors the existing
return-workflow publish script for the shared Unraid n8n.
This commit is contained in:
NuklearRabbit
2026-08-02 06:59:17 +02:00
parent ec8f809497
commit e115031a57
9 changed files with 306 additions and 8 deletions
+6 -4
View File
@@ -299,18 +299,20 @@ def _scan_odometer_regressions(db: Session, scan: ScanResult) -> None:
break
def run_scan(db: Session, *, actor: CurrentUser | None = None) -> ScanResult:
def run_scan(
db: Session, *, actor_label: str | None = None, actor_type: str = "user"
) -> ScanResult:
scan = ScanResult()
_scan_duplicate_customers(db, scan)
_scan_missing_required_fields(db, scan)
_scan_odometer_regressions(db, scan)
_scan_booking_overlaps(db, scan)
_scan_vehicle_status_conflicts(db, scan)
if actor is not None:
if actor_label is not None:
record_audit_event(
db,
actor_type="user",
actor_label=actor.display_name,
actor_type=actor_type,
actor_label=actor_label,
action="data_quality_scan_run",
entity_type="system",
metadata={"created": scan.created},