Data Quality Reports

Summary

The data quality system detects anomalies in a partner’s active lot and programme data via lib/data-quality.ts. Admins can view a live summary, preview an HTML report, export a CSV, and create a persisted QualityReport record. Reports are editable while in draft status and can be sent to the partner’s contact email via Resend (with up to 3 retry attempts). Once sent, a report is immutable.

State Diagram

stateDiagram-v2
    state "Vérification en direct" as LiveCheck
    state "Rapport brouillon" as Draft
    state "Rapport envoyé" as Sent
    [*] --> LiveCheck: admin views summary, preview or CSV
    LiveCheck --> [*]: ephemeral — no DB record
    [*] --> Draft: admin creates report draft
    Draft --> Draft: admin edits report
    Draft --> Sent: admin sends report
    Sent --> [*]: immutable

Steps

1. Compute Anomalies (Actor: system)

computePartnerAnomalies(partnerId) from lib/data-quality.ts queries all active lots for the partner and identifies:

  • Lots missing price (and not priceMissing: true)
  • Lots with price errors
  • Lots with prixSurDemande
  • Lots missing surface
  • Lots missing numeroLot
  • Programmes without photos
  • Programmes without an address

Returns { summary: PartnerAnomaliesSummary, details: AnomalyDetail[], programmesWithoutPhotos: string[], programmesWithoutAddress: string[], lotsPriceSurDemande: ..., ... }.

2. Live Quality Summary (Actor: admin)

GET /admin/partners/:id/quality-summary — returns the summary object only (no detail rows). Fast overview for the admin dashboard.

Outcome: { data: summary } with counts per anomaly type

3. HTML Preview (Actor: admin)

GET /admin/partners/:id/quality-reports/preview — renders a full HTML report via renderReportHtml() and returns it as text/html. Not persisted. Allows admin to review the email content before creating a draft.

Outcome: Full HTML email preview in browser

4. CSV Export (Actor: admin)

GET /admin/partners/:id/quality-reports/csv — serializes anomalies to RFC 4180 CSV with UTF-8 BOM. Filename pattern: quality-report-{partner-name}-{date}.csv. Streamed as a file download.

Outcome: CSV file with all anomaly details

5. Create Report Draft (Actor: admin)

POST /admin/partners/:id/quality-reports:

  • Runs computePartnerAnomalies().
  • Returns 400 NO_ANOMALIES if there are no anomalies to report (nothing to send).
  • Creates QualityReport with status: "draft", subject, body (HTML), summary, details.

Outcome: QualityReport record in DB with status draft

6. List Report History (Actor: admin)

GET /admin/partners/:id/quality-reports — returns all quality reports for the partner, ordered newest first. body field excluded from list for performance.

7. View Single Report (Actor: admin)

GET /admin/quality-reports/:reportId — full report including body HTML.

8. Edit Draft Report (Actor: admin)

PUT /admin/quality-reports/:reportId with { subject?, body? }:

  • Returns 409 ALREADY_SENT if the report has been sent.
  • body is sanitized via sanitizeHtml() (strips <script> tags and on* attributes).
  • Updates subject and/or body in place.

Outcome: Draft report updated

9. Send Report via Resend (Actor: admin, tools: resend-email)

POST /admin/quality-reports/:reportId/send:

  • Returns 409 ALREADY_SENT if already dispatched.
  • Returns 400 NO_CONTACT_EMAIL if partner has no email on file.
  • Sanitizes body HTML before sending.
  • Calls Resend API (from: "data-quality@sealion.cacio.fr") with up to 3 attempts (delays: 1s, 3s between retries).
  • On success: status → "sent", sentAt, sentTo set on the report.
  • On all attempts failed: 502 EMAIL_SEND_FAILED.

Outcome: Email delivered, report marked sent

Error States

  • Partner not found → 404 PARTNER_NOT_FOUND
  • Report not found → 404 REPORT_NOT_FOUND
  • Creating report with no anomalies → 400 NO_ANOMALIES
  • Editing or sending an already-sent report → 409 ALREADY_SENT
  • Sending when partner has no contact email → 400 NO_CONTACT_EMAIL
  • Resend API failure (all retries exhausted) → 502 EMAIL_SEND_FAILED