From b4f6c709f3b3e044b37a041a4ac24ef1356849b5 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Tue, 9 Jun 2026 03:46:45 +0530 Subject: [PATCH 1/3] added API contracts --- contracts/openapi.yaml | 98 +++ contracts/path/extraction.yaml | 313 ++++++++ contracts/path/forms.yaml | 348 ++++++++ contracts/path/incidents.yaml | 313 ++++++++ contracts/path/input.yaml | 247 ++++++ contracts/path/jobs.yaml | 79 ++ contracts/path/reporting.yaml | 191 +++++ contracts/path/system.yaml | 152 ++++ contracts/path/templates.yaml | 267 +++++++ contracts/schemas/canonical-incident.yaml | 930 ++++++++++++++++++++++ contracts/schemas/common.yaml | 129 +++ contracts/schemas/enums.yaml | 130 +++ contracts/schemas/extraction-record.yaml | 134 ++++ contracts/schemas/form-record.yaml | 198 +++++ contracts/schemas/incident-record.yaml | 150 ++++ contracts/schemas/input-record.yaml | 138 ++++ contracts/schemas/reporting.yaml | 112 +++ contracts/schemas/system.yaml | 101 +++ contracts/schemas/template.yaml | 144 ++++ 19 files changed, 4174 insertions(+) create mode 100644 contracts/openapi.yaml create mode 100644 contracts/path/extraction.yaml create mode 100644 contracts/path/forms.yaml create mode 100644 contracts/path/incidents.yaml create mode 100644 contracts/path/input.yaml create mode 100644 contracts/path/jobs.yaml create mode 100644 contracts/path/reporting.yaml create mode 100644 contracts/path/system.yaml create mode 100644 contracts/path/templates.yaml create mode 100644 contracts/schemas/canonical-incident.yaml create mode 100644 contracts/schemas/common.yaml create mode 100644 contracts/schemas/enums.yaml create mode 100644 contracts/schemas/extraction-record.yaml create mode 100644 contracts/schemas/form-record.yaml create mode 100644 contracts/schemas/incident-record.yaml create mode 100644 contracts/schemas/input-record.yaml create mode 100644 contracts/schemas/reporting.yaml create mode 100644 contracts/schemas/system.yaml create mode 100644 contracts/schemas/template.yaml diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml new file mode 100644 index 0000000..3b19ea3 --- /dev/null +++ b/contracts/openapi.yaml @@ -0,0 +1,98 @@ +openapi: 3.0.0 + +info: + title: FireForm API + version: 1.0.0 + description: | + Proposed API contract for FireForm as GSoC project 2026 by [chetanr25.in](https://chetanr25.in) + contact: + name: Chetan R + url: https://github.com/chetanr25 + email: chetan250204@gmail.com + +servers: + - url: http://localhost:8080 + description: Local FireForm instance (default) + +tags: + - name: input + description: Submit voice or text incident narratives + - name: extraction + description: AI-powered data extraction from narratives into canonical JSON + - name: forms + description: Generate agency-specific PDF forms from extracted data + - name: incidents + description: Manage incident records linking inputs, extractions, and forms + - name: reporting + description: Aggregate statistics and periodic report generation + - name: templates + description: Form template configuration and management + - name: system + description: Health checks, schema introspection, and system status + - name: jobs + description: Cross-cutting async job status polling + +paths: + # ── Layer 1: Input ────────────────────────────────────────────── + /api/v1/input/voice: + $ref: "path/input.yaml#/voice" + /api/v1/input/text: + $ref: "path/input.yaml#/text" + /api/v1/input/{input_id}: + $ref: "path/input.yaml#/input_by_id" + + # ── Layer 2: AI Extraction ───────────────────────────────────── + /api/v1/extract/{input_id}: + $ref: "path/extraction.yaml#/extract_by_input" + /api/v1/extract/{extract_id}: + $ref: "path/extraction.yaml#/extract_by_id" + /api/v1/extract/{extract_id}/validate: + $ref: "path/extraction.yaml#/validate" + + # ── Layer 3: Form Generation ─────────────────────────────────── + /api/v1/forms/generate/all: + $ref: "path/forms.yaml#/generate_all" + /api/v1/forms/generate/{form_type}: + $ref: "path/forms.yaml#/generate_single" + /api/v1/forms/{form_id}: + $ref: "path/forms.yaml#/form_by_id" + /api/v1/forms/{form_id}/pdf: + $ref: "path/forms.yaml#/form_pdf" + /api/v1/forms/{form_id}/json: + $ref: "path/forms.yaml#/form_json" + /api/v1/forms/batch/{batch_id}: + $ref: "path/forms.yaml#/batch_by_id" + + # ── Layer 4: Incident Management ─────────────────────────────── + /api/v1/incidents: + $ref: "path/incidents.yaml#/incidents" + /api/v1/incidents/{incident_id}: + $ref: "path/incidents.yaml#/incident_by_id" + + # ── Layer 5: Reporting & Analytics ───────────────────────────── + /api/v1/reports/summary: + $ref: "path/reporting.yaml#/summary" + /api/v1/reports/generate: + $ref: "path/reporting.yaml#/generate" + /api/v1/reports/{report_id}: + $ref: "path/reporting.yaml#/report_by_id" + + # ── Layer 6: Templates & Configuration ───────────────────────── + /api/v1/templates: + $ref: "path/templates.yaml#/templates" + /api/v1/templates/{template_id}: + $ref: "path/templates.yaml#/template_by_id" + /api/v1/templates/{template_id}/fields: + $ref: "path/templates.yaml#/template_fields" + + # ── Layer 7: System ──────────────────────────────────────────── + /api/v1/health: + $ref: "path/system.yaml#/health" + /api/v1/schema/incident: + $ref: "path/system.yaml#/schema_incident" + /api/v1/schema/incident/versions: + $ref: "path/system.yaml#/schema_versions" + + # ── Layer 8: Async Jobs ──────────────────────────────────────── + /api/v1/jobs/{job_id}: + $ref: "path/jobs.yaml#/job_by_id" diff --git a/contracts/path/extraction.yaml b/contracts/path/extraction.yaml new file mode 100644 index 0000000..0e11309 --- /dev/null +++ b/contracts/path/extraction.yaml @@ -0,0 +1,313 @@ +# Layer 2 AI Extraction Endpoints +# POST /api/v1/extract/{input_id} +# GET /api/v1/extract/{extract_id} +# PATCH /api/v1/extract/{extract_id} +# POST /api/v1/extract/{extract_id}/validate + +extract_by_input: + post: + operationId: createExtraction + summary: Start AI extraction from input narrative + description: | + Sends the narrative (from a previously submitted input) to the local Ollama + LLM with a structured prompt to extract all incident fields into the canonical + FireForm JSON schema. This is an asynchronous operation the LLM may take + 30–120 seconds. Returns an extract_id and job_id for polling. + tags: + - extraction + parameters: + - name: input_id + in: path + required: true + description: ID of the input record to extract from + schema: + type: string + format: uuid + requestBody: + required: false + content: + application/json: + schema: + $ref: "../schemas/extraction-record.yaml#/ExtractionRequest" + example: + model_override: "llama3:8b" + extraction_hints: + incident_type: "wildland_fire" + state: "CA" + responses: + "202": + description: Extraction job queued successfully + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/AsyncJobResponse" + example: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + job_id: "550e8400-e29b-41d4-a716-446655440098" + job_type: "extraction" + status: "processing" + input_id: "550e8400-e29b-41d4-a716-446655440001" + queued_at: "2024-07-15T14:30:00Z" + estimated_seconds: 60 + poll_url: "/api/v1/extract/550e8400-e29b-41d4-a716-446655440020" + "404": + description: Input ID not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "INPUT_NOT_FOUND" + message: "Input with ID 550e8400-e29b-41d4-a716-446655440099 not found" + "409": + description: Conflict input not ready or extraction already exists + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + examples: + not_ready: + summary: Input still transcribing + value: + error_code: "INPUT_NOT_READY" + message: "Input is in 'transcribing' state. Wait until status is 'ready'." + detail: + current_status: "transcribing" + already_exists: + summary: Extraction already initiated for this input + value: + error_code: "EXTRACTION_EXISTS" + message: "An extraction already exists for this input" + detail: + existing_extract_id: "550e8400-e29b-41d4-a716-446655440020" + "503": + description: Ollama LLM service unavailable + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "LLM_UNAVAILABLE" + message: "Ollama LLM service is not available" + retry_after_seconds: 30 + detail: + ollama_status: "connection_refused" + +extract_by_id: + get: + operationId: getExtraction + summary: Get extraction result by ID + description: | + Returns the full canonical FireForm JSON when extraction is complete, or + the current job status while still processing. When status is "completed", + the response body contains the entire canonical incident schema. When still + processing, includes a retry_after_seconds hint for polling. + tags: + - extraction + parameters: + - name: extract_id + in: path + required: true + description: Unique identifier of the extraction + schema: + type: string + format: uuid + responses: + "200": + description: Extraction record (completed or in-progress) + content: + application/json: + schema: + oneOf: + - $ref: "../schemas/extraction-record.yaml#/ExtractionCompleted" + - $ref: "../schemas/extraction-record.yaml#/ExtractionProcessing" + examples: + completed: + summary: Extraction completed with canonical JSON + value: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + input_id: "550e8400-e29b-41d4-a716-446655440001" + status: "completed" + completed_at: "2024-07-15T14:31:05Z" + canonical_incident: + schema_version: "1.1.0" + schema_name: "fireform_canonical_incident" + extraction_metadata: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + confidence_score: 0.91 + incident: + name: "Bear Creek Wildfire" + processing: + summary: Extraction still in progress + value: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + input_id: "550e8400-e29b-41d4-a716-446655440001" + status: "processing" + started_at: "2024-07-15T14:30:00Z" + retry_after_seconds: 5 + "404": + description: Extraction not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "EXTRACT_NOT_FOUND" + message: "Extraction with ID 550e8400-e29b-41d4-a716-446655440099 not found" + + patch: + operationId: updateExtraction + summary: Manually correct extracted fields + description: | + Allows a responder to correct any field in the canonical JSON after LLM + extraction. Uses JSON Merge Patch (RFC 7396) only send the fields that + changed. The server records an audit trail of all changes vs the original + LLM output and recalculates completeness scores and applicable_forms. + tags: + - extraction + parameters: + - name: extract_id + in: path + required: true + description: Unique identifier of the extraction to update + schema: + type: string + format: uuid + requestBody: + required: true + description: JSON Merge Patch (RFC 7396) only include changed fields + content: + application/merge-patch+json: + schema: + $ref: "../schemas/canonical-incident.yaml#/CanonicalIncident" + example: + fire: + estimated_damage_usd: 250000 + casualties: + total_responder_injuries: 2 + responses: + "200": + description: Extraction updated returns full updated canonical JSON + content: + application/json: + schema: + $ref: "../schemas/extraction-record.yaml#/ExtractionCompleted" + "404": + description: Extraction not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Extraction is locked (already submitted) + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "EXTRACT_LOCKED" + message: "Cannot modify extraction incident report has been submitted" + detail: + report_status: "submitted" + submitted_at: "2024-07-15T18:00:00Z" + "422": + description: Invalid field path or value + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "VALIDATION_ERROR" + message: "Invalid field path or value in patch" + validation_errors: + - field: "fire.cause_certainty" + issue: "Must be one of: confirmed, probable, suspected, undetermined" + value: "maybe" + +validate: + post: + operationId: validateExtraction + summary: Validate extraction against a form's requirements + description: | + Validates the canonical JSON against a specific form type's field requirements. + Returns whether the extraction has all required fields, which recommended + fields are missing, and any warnings. Useful for checking "can I generate + a NERIS report with what I have?" before triggering form generation. + tags: + - extraction + parameters: + - name: extract_id + in: path + required: true + description: Unique identifier of the extraction to validate + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + type: object + required: + - form_type + properties: + form_type: + $ref: "../schemas/enums.yaml#/FormType" + example: + form_type: "neris" + responses: + "200": + description: Validation result + content: + application/json: + schema: + $ref: "../schemas/extraction-record.yaml#/ValidationResult" + example: + valid: true + form_type: "neris" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + missing_required: [] + missing_recommended: + - "fire.detector_present" + - "fire.detector_operated" + warnings: + - "fire.estimated_damage_usd is null NERIS recommends providing damage estimates" + field_coverage_percent: 94 + "404": + description: Extraction not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "422": + description: Unknown form type + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "UNKNOWN_FORM_TYPE" + message: "Form type 'xyz' is not recognized" + detail: + valid_form_types: + - neris + - nemsis_epcr + - nibrs + - nfirs_basic + - nfirs_fire + - nfirs_structure + - nfirs_wildland + - nfirs_ems + - nfirs_hazmat + - nfirs_apparatus + - nfirs_personnel + - nfirs_arson + - nfirs_casualty_civilian + - nfirs_casualty_responder + - cal_fire_ics209 + - osha_301 + - un_ssirs + - state_georgia + - state_california + - state_new_york diff --git a/contracts/path/forms.yaml b/contracts/path/forms.yaml new file mode 100644 index 0000000..b920b41 --- /dev/null +++ b/contracts/path/forms.yaml @@ -0,0 +1,348 @@ +# Layer 3 Form Generation Endpoints +# POST /api/v1/forms/generate/all +# POST /api/v1/forms/generate/{form_type} +# GET /api/v1/forms/{form_id} +# GET /api/v1/forms/{form_id}/pdf +# GET /api/v1/forms/{form_id}/json +# GET /api/v1/forms/batch/{batch_id} + +generate_all: + post: + operationId: generateAllForms + summary: Generate all applicable forms from an extraction + description: | + Triggers batch generation of ALL forms listed in the extraction's + extraction_metadata.applicable_forms. This is an async batch job. + Use skip_incomplete to skip forms that fail validation, or force_partial + to generate forms with blank fields where data is missing. + tags: + - forms + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/GenerateAllRequest" + example: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + options: + skip_incomplete: true + force_partial: false + responses: + "202": + description: Batch form generation job accepted + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/BatchGenerateResponse" + example: + batch_id: "550e8400-e29b-41d4-a716-446655440030" + status: "processing" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + forms_queued: + - "neris" + - "nfirs_basic" + - "nfirs_wildland" + forms_skipped: + - form_type: "nemsis_epcr" + reason: "Missing required fields: ems.patients[0].date_of_birth" + estimated_seconds: 45 + poll_url: "/api/v1/forms/batch/550e8400-e29b-41d4-a716-446655440030" + "404": + description: Extract ID not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Extraction not yet completed + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "EXTRACT_NOT_COMPLETED" + message: "Extraction is still processing. Wait until status is 'completed'." + detail: + current_status: "processing" + "422": + description: No applicable forms found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "NO_APPLICABLE_FORMS" + message: "No forms are applicable for this extraction's incident type" + +generate_single: + post: + operationId: generateSingleForm + summary: Generate one specific form type + description: | + Generates a single agency-specific form from the canonical extraction data. + The form_type path parameter specifies which form to generate. If the extraction + is missing required fields for this form, returns 422 unless force_partial is true. + tags: + - forms + parameters: + - name: form_type + in: path + required: true + description: Type of form to generate + schema: + $ref: "../schemas/enums.yaml#/FormType" + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/GenerateSingleRequest" + example: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + options: + output_format: "pdf" + force_partial: false + responses: + "202": + description: Form generation job accepted + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/FormGenerateResponse" + example: + form_id: "550e8400-e29b-41d4-a716-446655440040" + form_type: "neris" + status: "processing" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + job_id: "550e8400-e29b-41d4-a716-446655440097" + estimated_seconds: 15 + poll_url: "/api/v1/forms/550e8400-e29b-41d4-a716-446655440040" + "404": + description: Extract ID not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "422": + description: Validation failure or form not in applicable list + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "FORM_VALIDATION_FAILED" + message: "Extraction is missing required fields for NERIS form" + detail: + form_type: "neris" + missing_fields: + - "incident.types[0].neris_code" + - "location.coordinates" + validation_errors: + - field: "incident.types[0].neris_code" + issue: "Required field is null" + +form_by_id: + get: + operationId: getForm + summary: Get form metadata and status + description: | + Returns the form record including generation status, associated extract and + incident IDs, and a field_mapping_summary showing how canonical fields were + mapped to the form's agency-specific fields (for audit/transparency). + tags: + - forms + parameters: + - name: form_id + in: path + required: true + description: Unique identifier of the generated form + schema: + type: string + format: uuid + responses: + "200": + description: Form record found + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/FormRecord" + example: + form_id: "550e8400-e29b-41d4-a716-446655440040" + form_type: "neris" + status: "completed" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + incident_id: "550e8400-e29b-41d4-a716-446655440050" + created_at: "2024-07-15T14:32:00Z" + completed_at: "2024-07-15T14:32:12Z" + pdf_ready: true + json_ready: true + field_mapping_summary: + total_form_fields: 85 + fields_filled: 72 + fields_blank: 13 + coverage_percent: 84.7 + "404": + description: Form not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + +form_pdf: + get: + operationId: downloadFormPdf + summary: Download filled PDF form + description: | + Returns the filled PDF binary file for the specified form. If the form + generation is still in progress, returns 202 Accepted with a retry_after + hint. The Content-Disposition header is set for browser download. + tags: + - forms + parameters: + - name: form_id + in: path + required: true + description: Unique identifier of the form to download + schema: + type: string + format: uuid + responses: + "200": + description: Filled PDF file + content: + application/pdf: + schema: + type: string + format: binary + headers: + Content-Disposition: + description: Attachment filename for download + schema: + type: string + example: 'attachment; filename="NERIS_FF-2024-CA-0157.pdf"' + "202": + description: Form generation still in progress + content: + application/json: + schema: + type: object + properties: + message: + type: string + status: + type: string + retry_after_seconds: + type: integer + example: + message: "Form generation is still in progress" + status: "processing" + retry_after_seconds: 5 + "404": + description: Form not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "500": + description: PDF generation failed + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "PDF_GENERATION_FAILED" + message: "Failed to generate PDF for form" + detail: + reason: "Template file corrupted or missing" + +form_json: + get: + operationId: getFormJson + summary: Get form-specific field-mapped JSON + description: | + Returns the form-specific JSON with fields mapped to the agency's expected + format not the canonical FireForm schema, but the actual field names and + structure that the target agency system expects. Useful for future direct + API submission to agency systems. + tags: + - forms + parameters: + - name: form_id + in: path + required: true + description: Unique identifier of the form + schema: + type: string + format: uuid + responses: + "200": + description: Form-specific mapped JSON + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/FormMappedJson" + example: + form_type: "neris" + form_version: "2.0" + form_id: "550e8400-e29b-41d4-a716-446655440040" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + agency_fields: + incident_type: "wildland-fire" + incident_date: "2024-07-10" + alarm_time: "13:52" + acres_burned: 1247 + cause: "natural" + "404": + description: Form not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + +batch_by_id: + get: + operationId: getBatchStatus + summary: Get batch form generation status + description: | + Returns the status of a batch form generation job including progress + for each individual form. Poll this endpoint after POST /forms/generate/all. + tags: + - forms + parameters: + - name: batch_id + in: path + required: true + description: Unique identifier of the batch job + schema: + type: string + format: uuid + responses: + "200": + description: Batch job status + content: + application/json: + schema: + $ref: "../schemas/form-record.yaml#/BatchStatus" + example: + batch_id: "550e8400-e29b-41d4-a716-446655440030" + status: "completed" + total: 3 + completed: 3 + failed: 0 + forms: + - form_id: "550e8400-e29b-41d4-a716-446655440040" + form_type: "neris" + status: "completed" + - form_id: "550e8400-e29b-41d4-a716-446655440041" + form_type: "nfirs_basic" + status: "completed" + - form_id: "550e8400-e29b-41d4-a716-446655440042" + form_type: "nfirs_wildland" + status: "completed" + "404": + description: Batch job not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" diff --git a/contracts/path/incidents.yaml b/contracts/path/incidents.yaml new file mode 100644 index 0000000..810a311 --- /dev/null +++ b/contracts/path/incidents.yaml @@ -0,0 +1,313 @@ +# Layer 4 Incident Management Endpoints +# POST /api/v1/incidents +# GET /api/v1/incidents +# GET /api/v1/incidents/{incident_id} +# PATCH /api/v1/incidents/{incident_id} +# DELETE /api/v1/incidents/{incident_id} + +incidents: + post: + operationId: createIncident + summary: Create a full incident record + description: | + Creates a permanent incident record that links input, extraction, and + generated forms into a single coherent unit. Assigns a permanent incident_id + and stores the complete incident for future retrieval and reporting. + tags: + - incidents + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/CreateIncidentRequest" + example: + extract_id: "550e8400-e29b-41d4-a716-446655440020" + incident_number: "CA-SQF-2024-0421" + tags: + - "wildland" + - "mutual_aid" + - "lightning" + responses: + "201": + description: Incident record created + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/IncidentRecord" + example: + incident_id: "550e8400-e29b-41d4-a716-446655440050" + extract_id: "550e8400-e29b-41d4-a716-446655440020" + incident_number: "CA-SQF-2024-0421" + status: "draft" + forms_generated: + - form_id: "550e8400-e29b-41d4-a716-446655440040" + form_type: "neris" + status: "completed" + tags: + - "wildland" + - "mutual_aid" + - "lightning" + created_at: "2024-07-15T14:35:00Z" + "404": + description: Extract ID not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Duplicate incident number + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "DUPLICATE_INCIDENT_NUMBER" + message: "Incident number CA-SQF-2024-0421 already exists" + detail: + existing_incident_id: "550e8400-e29b-41d4-a716-446655440049" + + get: + operationId: listIncidents + summary: List incidents with filtering + description: | + Returns a paginated list of incident records with optional filtering by + date range, incident type, and status. Supports sorting by date. + Maximum 100 results per page. + tags: + - incidents + parameters: + - name: date_from + in: query + description: Start date filter (inclusive, ISO 8601) + schema: + type: string + format: date + - name: date_to + in: query + description: End date filter (inclusive, ISO 8601) + schema: + type: string + format: date + - name: incident_type + in: query + description: Filter by incident category + schema: + $ref: "../schemas/enums.yaml#/IncidentCategory" + - name: status + in: query + description: Filter by report status + schema: + $ref: "../schemas/enums.yaml#/ReportStatus" + - name: page + in: query + description: Page number (1-based) + schema: + type: integer + minimum: 1 + default: 1 + - name: per_page + in: query + description: Items per page (max 100) + schema: + type: integer + minimum: 1 + maximum: 100 + default: 20 + - name: sort + in: query + description: Sort order + schema: + type: string + enum: + - date_asc + - date_desc + default: date_desc + responses: + "200": + description: Paginated list of incidents + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/IncidentListResponse" + example: + data: + - incident_id: "550e8400-e29b-41d4-a716-446655440050" + incident_number: "CA-SQF-2024-0421" + status: "draft" + incident_name: "Bear Creek Wildfire" + incident_type: "fire" + incident_date: "2024-07-10" + forms_count: 3 + created_at: "2024-07-15T14:35:00Z" + pagination: + total: 42 + page: 1 + per_page: 20 + total_pages: 3 + has_next: true + has_prev: false + "422": + description: Invalid query parameter format + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "VALIDATION_ERROR" + message: "Invalid date format for date_from" + validation_errors: + - field: "date_from" + issue: "Must be ISO 8601 date format (YYYY-MM-DD)" + value: "15/07/2024" + +incident_by_id: + get: + operationId: getIncident + summary: Get full incident record + description: | + Returns the complete incident record including the linked canonical + extraction, all generated forms and their statuses, submission log, + and audit trail. + tags: + - incidents + parameters: + - name: incident_id + in: path + required: true + description: Unique identifier of the incident + schema: + type: string + format: uuid + responses: + "200": + description: Full incident record + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/IncidentRecordFull" + "404": + description: Incident not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + + patch: + operationId: updateIncident + summary: Update incident metadata + description: | + Updates incident metadata such as status, tags, incident number, and notes. + Does NOT allow changing the underlying extraction data use PATCH /extract + for that. Submitted incidents cannot be modified. + tags: + - incidents + parameters: + - name: incident_id + in: path + required: true + description: Unique identifier of the incident to update + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/UpdateIncidentRequest" + example: + status: "approved" + tags: + - "wildland" + - "mutual_aid" + - "lightning" + - "reviewed" + notes: "Reviewed by BC Wilson. Ready for submission." + responses: + "200": + description: Incident updated + content: + application/json: + schema: + $ref: "../schemas/incident-record.yaml#/IncidentRecord" + "404": + description: Incident not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Cannot modify a submitted incident + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "INCIDENT_SUBMITTED" + message: "Cannot modify an incident that has been submitted" + detail: + submitted_at: "2024-07-15T18:00:00Z" + + delete: + operationId: deleteIncident + summary: Soft-delete an incident + description: | + Performs a soft delete sets a deleted_at timestamp but never removes data. + Submitted incidents cannot be deleted. Soft-deleted incidents are excluded + from list queries by default but can be recovered. + tags: + - incidents + parameters: + - name: incident_id + in: path + required: true + description: Unique identifier of the incident to delete + schema: + type: string + format: uuid + responses: + "200": + description: Incident soft-deleted + content: + application/json: + schema: + type: object + properties: + incident_id: + type: string + format: uuid + deleted_at: + type: string + format: date-time + recoverable: + type: boolean + example: + incident_id: "550e8400-e29b-41d4-a716-446655440050" + deleted_at: "2024-07-16T10:00:00Z" + recoverable: true + "404": + description: Incident not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Cannot delete already deleted or submitted + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + examples: + already_deleted: + summary: Incident already soft-deleted + value: + error_code: "ALREADY_DELETED" + message: "Incident has already been deleted" + detail: + deleted_at: "2024-07-16T09:00:00Z" + submitted: + summary: Submitted incidents cannot be deleted + value: + error_code: "INCIDENT_SUBMITTED" + message: "Submitted incidents cannot be deleted" diff --git a/contracts/path/input.yaml b/contracts/path/input.yaml new file mode 100644 index 0000000..a2d6a57 --- /dev/null +++ b/contracts/path/input.yaml @@ -0,0 +1,247 @@ +# Layer 1 Input Endpoints +# POST /api/v1/input/voice, POST /api/v1/input/text, GET /api/v1/input/{input_id} + +voice: + post: + operationId: submitVoiceInput + summary: Submit a voice recording for transcription + description: | + Accepts an audio file (voice memo) from a first responder along with optional + incident metadata. The audio is saved and queued for transcription via Whisper + running on the local Ollama instance. Returns an input_id immediately the + transcription runs asynchronously. Poll GET /api/v1/input/{input_id} for status. + tags: + - input + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: + - audio_file + properties: + audio_file: + type: string + format: binary + description: Audio file in wav, mp3, m4a, ogg, or webm format. Max 500MB. + station_id: + type: string + description: Station identifier (e.g. "STA-045") + responder_badge: + type: string + description: Badge number of the responder submitting the report + incident_date_hint: + type: string + format: date + description: Approximate date of the incident to aid extraction + example: + station_id: "STA-045" + responder_badge: "FD-7842" + incident_date_hint: "2024-07-15" + responses: + "201": + description: Voice input accepted and queued for transcription + content: + application/json: + schema: + $ref: "../schemas/input-record.yaml#/VoiceInputResponse" + example: + input_id: "550e8400-e29b-41d4-a716-446655440001" + status: "queued" + input_type: "voice" + estimated_processing_seconds: 30 + created_at: "2024-07-15T14:25:00Z" + job_id: "550e8400-e29b-41d4-a716-446655440099" + poll_url: "/api/v1/input/550e8400-e29b-41d4-a716-446655440001" + "400": + description: Malformed request (missing audio file or invalid form data) + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "413": + description: Audio file exceeds 500MB limit + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "FILE_TOO_LARGE" + message: "Audio file exceeds maximum size of 500MB" + detail: + max_size_bytes: 524288000 + received_size_bytes: 600000000 + "415": + description: Unsupported audio format + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "UNSUPPORTED_FORMAT" + message: "Audio format not supported. Accepted formats: wav, mp3, m4a, ogg, webm" + detail: + accepted_formats: + - wav + - mp3 + - m4a + - ogg + - webm + "503": + description: Ollama/Whisper service unavailable + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "LLM_UNAVAILABLE" + message: "Ollama transcription service is not available" + retry_after_seconds: 30 + +text: + post: + operationId: submitTextInput + summary: Submit a text narrative for processing + description: | + Accepts a free-text incident narrative from a first responder. This is + synchronous the text is stored immediately and the input is marked as + "ready" for extraction. No transcription step needed. + tags: + - input + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/input-record.yaml#/TextInputRequest" + example: + narrative: > + Responded to a wildfire at Bear Creek Trailhead around 1:45 PM on July 10th. + Lightning strike ignited timber litter in mixed conifer and chaparral. Fire + spread rapidly northeast driven by 15-25 mph SW winds. We deployed 18 engines, + 6 water tenders, and 3 helicopters. 247 personnel total. One firefighter + treated for heat exhaustion, returned to duty July 12. 1247 acres burned across + federal, state, and private land. 47 structures threatened, 3 damaged, none + destroyed. Fire contained July 14, controlled July 15. + station_id: "STA-045" + responder_badge: "FD-7842" + incident_date_hint: "2024-07-10" + responses: + "201": + description: Text input accepted and ready for extraction + content: + application/json: + schema: + $ref: "../schemas/input-record.yaml#/TextInputResponse" + example: + input_id: "550e8400-e29b-41d4-a716-446655440001" + status: "ready" + input_type: "text" + character_count: 612 + word_count: 98 + created_at: "2024-07-15T14:25:00Z" + "400": + description: Malformed JSON request body + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "413": + description: Narrative exceeds 50,000 character limit + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "NARRATIVE_TOO_LONG" + message: "Narrative exceeds maximum length of 50,000 characters" + detail: + max_characters: 50000 + received_characters: 52000 + "422": + description: Validation error (empty or too-short narrative) + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "VALIDATION_ERROR" + message: "Narrative is too short to extract meaningful incident data" + validation_errors: + - field: "narrative" + issue: "Must contain at least 10 words" + value: "Fire happened" + +input_by_id: + get: + operationId: getInput + summary: Get input record by ID + description: | + Returns the full input record including the original text or transcript, + processing status for voice inputs, and metadata. For voice inputs, poll + this endpoint until status transitions from "queued"/"transcribing" to "ready". + tags: + - input + parameters: + - name: input_id + in: path + required: true + description: Unique identifier of the input record + schema: + type: string + format: uuid + responses: + "200": + description: Input record found + content: + application/json: + schema: + $ref: "../schemas/input-record.yaml#/InputRecord" + examples: + voice_ready: + summary: Voice input with completed transcription + value: + input_id: "550e8400-e29b-41d4-a716-446655440001" + input_type: "voice" + status: "ready" + transcript: "Responded to a wildfire at Bear Creek..." + original_filename: "incident_memo.m4a" + audio_duration_seconds: 180 + character_count: 612 + word_count: 98 + station_id: "STA-045" + responder_badge: "FD-7842" + incident_date_hint: "2024-07-10" + created_at: "2024-07-15T14:25:00Z" + updated_at: "2024-07-15T14:25:30Z" + voice_processing: + summary: Voice input still transcribing + value: + input_id: "550e8400-e29b-41d4-a716-446655440001" + input_type: "voice" + status: "transcribing" + transcript: null + created_at: "2024-07-15T14:25:00Z" + updated_at: "2024-07-15T14:25:10Z" + retry_after_seconds: 5 + text_ready: + summary: Text input ready for extraction + value: + input_id: "550e8400-e29b-41d4-a716-446655440002" + input_type: "text" + status: "ready" + transcript: "Responded to a wildfire at Bear Creek..." + character_count: 612 + word_count: 98 + created_at: "2024-07-15T14:25:00Z" + updated_at: "2024-07-15T14:25:00Z" + "404": + description: Input record not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "INPUT_NOT_FOUND" + message: "Input with ID 550e8400-e29b-41d4-a716-446655440099 not found" diff --git a/contracts/path/jobs.yaml b/contracts/path/jobs.yaml new file mode 100644 index 0000000..2ac042f --- /dev/null +++ b/contracts/path/jobs.yaml @@ -0,0 +1,79 @@ +# Layer 8 Async Job Status (Cross-cutting) +# GET /api/v1/jobs/{job_id} + +job_by_id: + get: + operationId: getJobStatus + summary: Get async job status + description: | + Universal job status endpoint for any asynchronous operation in FireForm — + transcription, LLM extraction, form generation, and report generation. + Returns the current status, progress percentage, and a result URL when + the job completes. Poll this endpoint for long-running operations. + tags: + - jobs + parameters: + - name: job_id + in: path + required: true + description: Unique identifier of the async job + schema: + type: string + format: uuid + responses: + "200": + description: Job status + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/Job" + examples: + queued: + summary: Job waiting in queue + value: + job_id: "550e8400-e29b-41d4-a716-446655440098" + job_type: "extraction" + status: "queued" + progress_percent: 0 + created_at: "2024-07-15T14:30:00Z" + updated_at: "2024-07-15T14:30:00Z" + processing: + summary: Job currently processing + value: + job_id: "550e8400-e29b-41d4-a716-446655440098" + job_type: "extraction" + status: "processing" + progress_percent: 45 + created_at: "2024-07-15T14:30:00Z" + updated_at: "2024-07-15T14:30:30Z" + completed: + summary: Job completed successfully + value: + job_id: "550e8400-e29b-41d4-a716-446655440098" + job_type: "extraction" + status: "completed" + progress_percent: 100 + result_url: "/api/v1/extract/550e8400-e29b-41d4-a716-446655440020" + created_at: "2024-07-15T14:30:00Z" + updated_at: "2024-07-15T14:31:05Z" + failed: + summary: Job failed with error + value: + job_id: "550e8400-e29b-41d4-a716-446655440098" + job_type: "extraction" + status: "failed" + progress_percent: 23 + error: + error_code: "LLM_TIMEOUT" + message: "Ollama did not respond within 120 seconds" + created_at: "2024-07-15T14:30:00Z" + updated_at: "2024-07-15T14:32:00Z" + "404": + description: Job not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "JOB_NOT_FOUND" + message: "Job with ID 550e8400-e29b-41d4-a716-446655440099 not found" diff --git a/contracts/path/reporting.yaml b/contracts/path/reporting.yaml new file mode 100644 index 0000000..ad10172 --- /dev/null +++ b/contracts/path/reporting.yaml @@ -0,0 +1,191 @@ +# Layer 5 Reporting & Analytics Endpoints +# GET /api/v1/reports/summary +# POST /api/v1/reports/generate +# GET /api/v1/reports/{report_id} + +summary: + get: + operationId: getReportSummary + summary: Get aggregate incident statistics + description: | + Returns aggregate statistics for a dashboard view total incidents, + breakdown by type and status, forms generated, average completeness + scores, and average processing times. Supports filtering by date range + and grouping by time period or incident type. + tags: + - reporting + parameters: + - name: date_from + in: query + description: Start date (inclusive, ISO 8601) + schema: + type: string + format: date + - name: date_to + in: query + description: End date (inclusive, ISO 8601) + schema: + type: string + format: date + - name: group_by + in: query + description: Group results by time period or incident type + schema: + type: string + enum: + - day + - week + - month + - incident_type + default: month + responses: + "200": + description: Aggregate statistics + content: + application/json: + schema: + $ref: "../schemas/reporting.yaml#/ReportSummary" + example: + date_from: "2024-01-01" + date_to: "2024-07-15" + total_incidents: 157 + by_type: + fire: 42 + ems: 68 + rescue: 12 + hazardous_conditions: 8 + service_call: 15 + good_intent: 7 + false_alarm: 5 + by_status: + draft: 3 + under_review: 2 + approved: 12 + submitted: 140 + forms_generated: 489 + avg_completeness_score: 88.3 + avg_processing_time_seconds: 47.2 + groups: + - period: "2024-07" + incident_count: 23 + forms_generated: 71 + +generate: + post: + operationId: generateReport + summary: Generate a periodic report + description: | + Generates a periodic (monthly, quarterly, or annual) aggregate report + as PDF or JSON. This includes statistics, incident summaries, and + compliance metrics required by agencies like USFA/NERIS. This is an + async operation returns a report_id for polling. + + Note: USFA encourages monthly submission but requires quarterly at minimum. + This endpoint supports generating reports aligned with those cadences. + tags: + - reporting + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/reporting.yaml#/GenerateReportRequest" + example: + period_type: "monthly" + year: 2024 + month: 7 + format: "pdf" + responses: + "202": + description: Report generation job accepted + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/AsyncJobResponse" + example: + job_id: "550e8400-e29b-41d4-a716-446655440096" + job_type: "report_generation" + status: "processing" + estimated_seconds: 30 + poll_url: "/api/v1/reports/550e8400-e29b-41d4-a716-446655440060" + report_id: "550e8400-e29b-41d4-a716-446655440060" + "422": + description: Invalid period or future date + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + examples: + future_period: + summary: Future period requested + value: + error_code: "FUTURE_PERIOD" + message: "Cannot generate a report for a future period" + invalid_quarter: + summary: Invalid quarter value + value: + error_code: "VALIDATION_ERROR" + message: "Invalid quarter value" + validation_errors: + - field: "quarter" + issue: "Must be 1, 2, 3, or 4" + value: 5 + +report_by_id: + get: + operationId: getReport + summary: Retrieve a generated report + description: | + Returns a previously generated periodic report. If the report is still + being generated, returns 202 Accepted with a retry hint. When complete, + returns the report in the originally requested format (PDF binary or JSON). + tags: + - reporting + parameters: + - name: report_id + in: path + required: true + description: Unique identifier of the generated report + schema: + type: string + format: uuid + responses: + "200": + description: Generated report (PDF or JSON depending on original request) + content: + application/pdf: + schema: + type: string + format: binary + application/json: + schema: + $ref: "../schemas/reporting.yaml#/PeriodicReport" + headers: + Content-Disposition: + description: Attachment filename for PDF downloads + schema: + type: string + example: 'attachment; filename="FireForm_Monthly_2024-07.pdf"' + "202": + description: Report still being generated + content: + application/json: + schema: + type: object + properties: + message: + type: string + status: + type: string + retry_after_seconds: + type: integer + example: + message: "Report generation is still in progress" + status: "processing" + retry_after_seconds: 10 + "404": + description: Report not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" diff --git a/contracts/path/system.yaml b/contracts/path/system.yaml new file mode 100644 index 0000000..cd00901 --- /dev/null +++ b/contracts/path/system.yaml @@ -0,0 +1,152 @@ +# Layer 7 System Endpoints +# GET /api/v1/health +# GET /api/v1/schema/incident +# GET /api/v1/schema/incident/versions + +health: + get: + operationId: getHealth + summary: System health check + description: | + Returns the health status of all FireForm components — database, Ollama LLM + (including loaded models, GPU status, and current load), Whisper transcription, + and file storage. Returns 200 even when degraded (so load balancers don't kill + it), 503 only if the system is truly unhealthy and cannot serve any requests. + tags: + - system + responses: + "200": + description: System is healthy or degraded + content: + application/json: + schema: + $ref: "../schemas/system.yaml#/HealthStatus" + examples: + healthy: + summary: All systems operational + value: + status: "healthy" + version: "1.0.0" + uptime_seconds: 86400 + components: + database: + status: "healthy" + response_time_ms: 2 + ollama: + status: "healthy" + response_time_ms: 15 + model_loaded: "llama3:8b" + ollama_version: "0.3.0" + models_available: + - name: "llama3:8b" + size_gb: 4.7 + quantization: "Q4_K_M" + loaded: true + - name: "mistral:7b" + size_gb: 4.1 + quantization: "Q4_0" + loaded: false + current_load: + active_requests: 1 + queued_requests: 0 + whisper: + status: "healthy" + response_time_ms: 10 + storage: + status: "healthy" + disk_free_gb: 120.5 + degraded: + summary: Some components have issues + value: + status: "degraded" + version: "1.0.0" + uptime_seconds: 3600 + components: + database: + status: "healthy" + response_time_ms: 2 + ollama: + status: "degraded" + response_time_ms: 5000 + detail: "High latency model may be loading" + current_load: + active_requests: 3 + queued_requests: 2 + whisper: + status: "unhealthy" + detail: "Whisper model not loaded" + storage: + status: "healthy" + disk_free_gb: 120.5 + "503": + description: System is unhealthy cannot serve requests + content: + application/json: + schema: + $ref: "../schemas/system.yaml#/HealthStatus" + example: + status: "unhealthy" + version: "1.0.0" + uptime_seconds: 10 + components: + database: + status: "unhealthy" + detail: "Connection refused" + ollama: + status: "unhealthy" + detail: "Connection refused" + +schema_incident: + get: + operationId: getIncidentSchema + summary: Get the canonical FireForm JSON Schema + description: | + Returns the full canonical FireForm incident JSON Schema (JSON Schema + draft-07). Clients can use this to validate incident data locally before + submitting. This is a schema-as-API pattern for interoperability. + tags: + - system + responses: + "200": + description: Full JSON Schema document + content: + application/json: + schema: + type: object + description: JSON Schema draft-07 document for the canonical incident model + example: + $schema: "http://json-schema.org/draft-07/schema#" + title: "FireForm Canonical Incident" + type: "object" + properties: + schema_version: + type: "string" + +schema_versions: + get: + operationId: getSchemaVersions + summary: Get schema version history + description: | + Returns the version history of the canonical FireForm incident schema, + including changelogs and whether each version introduced breaking changes. + Useful for clients tracking schema evolution across FireForm updates. + tags: + - system + responses: + "200": + description: Schema version history + content: + application/json: + schema: + type: array + items: + $ref: "../schemas/system.yaml#/SchemaVersion" + example: + - version: "1.1.0" + released_at: "2026-02-01T00:00:00Z" + changelog: "Added NERIS fields replacing legacy NFIRS. Added wildland.aerial_operations. Updated incident.types to include neris_code." + breaking_changes: true + - version: "1.0.0" + released_at: "2024-01-01T00:00:00Z" + changelog: "Initial canonical schema with NFIRS support." + breaking_changes: false diff --git a/contracts/path/templates.yaml b/contracts/path/templates.yaml new file mode 100644 index 0000000..dab9dca --- /dev/null +++ b/contracts/path/templates.yaml @@ -0,0 +1,267 @@ +# Layer 6 Template & Configuration Endpoints +# GET /api/v1/templates +# GET /api/v1/templates/{template_id} +# POST /api/v1/templates +# PUT /api/v1/templates/{template_id} +# GET /api/v1/templates/{template_id}/fields + +templates: + get: + operationId: listTemplates + summary: List all available form templates + description: | + Returns all registered form templates including built-in standard templates + (NERIS, NEMSIS, NIBRS, NFIRS modules, OSHA, etc.) and any custom templates + added for specific jurisdictions. Each template defines the fields, validation + rules, and mapping from the canonical FireForm schema. + tags: + - templates + responses: + "200": + description: List of all templates + content: + application/json: + schema: + type: array + items: + $ref: "../schemas/template.yaml#/TemplateSummary" + example: + - template_id: "550e8400-e29b-41d4-a716-446655440070" + form_type: "neris" + display_name: "NERIS Incident Report" + jurisdiction: "US-Federal" + agency_type: "fire_department" + version: "2.0" + last_updated: "2026-02-01" + field_count: 85 + status: "active" + - template_id: "550e8400-e29b-41d4-a716-446655440071" + form_type: "nemsis_epcr" + display_name: "NEMSIS Electronic Patient Care Report" + jurisdiction: "US-Federal" + agency_type: "ems" + version: "3.5" + last_updated: "2024-01-15" + field_count: 142 + status: "active" + - template_id: "550e8400-e29b-41d4-a716-446655440072" + form_type: "nfirs_basic" + display_name: "NFIRS Basic Module (Legacy)" + jurisdiction: "US-Federal" + agency_type: "fire_department" + version: "5.0" + last_updated: "2015-01-01" + field_count: 65 + status: "legacy" + + post: + operationId: createTemplate + summary: Register a new custom form template + description: | + Registers a new form template for a jurisdiction or agency not yet supported. + This is how FireForm extends to new states, countries, or custom agency forms + without code changes. The template defines all fields, their types, validation + rules, and how each maps from the canonical FireForm schema. + tags: + - templates + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/template.yaml#/CreateTemplateRequest" + example: + form_type: "state_texas" + display_name: "Texas State Fire Marshal Incident Report" + jurisdiction: "US-TX" + agency_type: "fire_department" + fields: + - field_name: "incident_number" + field_type: "string" + required: true + max_length: 20 + description: "State-assigned incident number" + canonical_mapping: "report_metadata.incident_number" + - field_name: "fire_cause" + field_type: "enum" + required: true + allowed_values: + - "accidental" + - "natural" + - "intentional" + - "undetermined" + canonical_mapping: "fire.cause_category" + field_mappings_from_canonical: + "report_metadata.incident_number": "incident_number" + "fire.cause_category": "fire_cause" + responses: + "201": + description: Template created + content: + application/json: + schema: + $ref: "../schemas/template.yaml#/Template" + "409": + description: Template with this form_type already exists + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "422": + description: Invalid template definition + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + +template_by_id: + get: + operationId: getTemplate + summary: Get full template schema + description: | + Returns the complete template definition including all fields, their types, + required/optional status, validation rules, and the mapping from canonical + FireForm schema fields. Includes the source standard reference where applicable. + tags: + - templates + parameters: + - name: template_id + in: path + required: true + description: Unique identifier of the template + schema: + type: string + format: uuid + responses: + "200": + description: Full template definition + content: + application/json: + schema: + $ref: "../schemas/template.yaml#/Template" + "404": + description: Template not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + + put: + operationId: updateTemplate + summary: Update an existing template + description: | + Replaces an existing template definition. Used when an upstream standard + changes (e.g., NERIS schema update). Templates used by submitted incidents + cannot be modified create a new version instead. + tags: + - templates + parameters: + - name: template_id + in: path + required: true + description: Unique identifier of the template to update + schema: + type: string + format: uuid + requestBody: + required: true + content: + application/json: + schema: + $ref: "../schemas/template.yaml#/CreateTemplateRequest" + responses: + "200": + description: Template updated + content: + application/json: + schema: + $ref: "../schemas/template.yaml#/Template" + "404": + description: Template not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + "409": + description: Template in use by submitted incidents cannot modify + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" + example: + error_code: "TEMPLATE_IN_USE" + message: "Template is referenced by submitted incidents. Create a new version instead." + detail: + submitted_incident_count: 15 + recommendation: "POST /api/v1/templates to create a new version" + +template_fields: + get: + operationId: getTemplateFields + summary: Get template field definitions + description: | + Returns just the fields list for a template with type, required/optional + status, validation rules, and canonical schema mapping. Useful for building + validation checklists and for the validate endpoint to determine requirements. + tags: + - templates + parameters: + - name: template_id + in: path + required: true + description: Unique identifier of the template + schema: + type: string + format: uuid + - name: required_only + in: query + description: If true, return only required fields + schema: + type: boolean + default: false + responses: + "200": + description: List of template fields + content: + application/json: + schema: + type: object + properties: + template_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + total_fields: + type: integer + required_fields: + type: integer + optional_fields: + type: integer + fields: + type: array + items: + $ref: "../schemas/template.yaml#/TemplateField" + example: + template_id: "550e8400-e29b-41d4-a716-446655440070" + form_type: "neris" + total_fields: 85 + required_fields: 32 + optional_fields: 53 + fields: + - field_name: "incident_type" + field_type: "enum" + required: true + description: "Primary incident type code" + canonical_mapping: "incident.types[0].neris_code" + - field_name: "incident_date" + field_type: "date" + required: true + description: "Date of incident" + canonical_mapping: "incident.start_datetime" + "404": + description: Template not found + content: + application/json: + schema: + $ref: "../schemas/common.yaml#/ErrorResponse" diff --git a/contracts/schemas/canonical-incident.yaml b/contracts/schemas/canonical-incident.yaml new file mode 100644 index 0000000..a11d1b9 --- /dev/null +++ b/contracts/schemas/canonical-incident.yaml @@ -0,0 +1,930 @@ +# Canonical FireForm Incident Schema +# This is the master superset schema single source of truth for all downstream forms + +CanonicalIncident: + type: object + description: | + The canonical FireForm incident data model. This is the superset schema containing + every field any downstream form could need. Form-specific mappers select only the + relevant fields for each agency template. + properties: + schema_version: + type: string + description: Schema version identifier + example: "1.1.0" + schema_name: + type: string + description: Schema name identifier + enum: + - fireform_canonical_incident + + extraction_metadata: + $ref: "#/ExtractionMetadata" + report_metadata: + $ref: "#/ReportMetadata" + incident: + $ref: "#/Incident" + location: + $ref: "#/Location" + fire: + $ref: "#/Fire" + wildland: + $ref: "#/Wildland" + structure: + $ref: "#/Structure" + casualties: + $ref: "#/Casualties" + ems: + $ref: "#/EMS" + hazmat: + $ref: "#/Hazmat" + arson: + $ref: "#/Arson" + responding_agencies: + $ref: "#/RespondingAgencies" + resources_deployed: + $ref: "#/ResourcesDeployed" + weather: + $ref: "#/Weather" + environmental_impact: + $ref: "#/EnvironmentalImpact" + infrastructure_impact: + $ref: "#/InfrastructureImpact" + near_miss_and_safety: + $ref: "#/NearMissAndSafety" + lessons_learned: + $ref: "#/LessonsLearned" + follow_up: + $ref: "#/FollowUp" + periodic_reporting: + $ref: "#/PeriodicReporting" + attachments: + $ref: "#/Attachments" + +# --- Sub-schemas --- + +ExtractionMetadata: + type: object + properties: + extract_id: + type: string + format: uuid + input_id: + type: string + format: uuid + input_type: + type: string + enum: [voice, text] + extracted_at: + type: string + format: date-time + llm_model: + type: string + example: "llama3:8b" + confidence_score: + type: number + minimum: 0 + maximum: 1 + description: Overall confidence score from the LLM extraction (0.0–1.0) + completeness: + $ref: "#/Completeness" + applicable_forms: + type: array + items: + $ref: "../schemas/enums.yaml#/FormType" + +Completeness: + type: object + description: | + Tells the system which forms can be fully auto-generated vs which need + manual review. Recalculated server-side after every PATCH /extract. + properties: + overall_percent: + type: integer + minimum: 0 + maximum: 100 + missing_fields: + type: array + items: + type: string + description: JSON paths of fields that have no value + low_confidence_fields: + type: array + items: + type: string + description: JSON paths of fields where LLM confidence is low + inferred_fields: + type: array + items: + type: string + description: JSON paths of fields that were inferred (not explicitly stated) + forms_fully_generatable: + type: array + items: + $ref: "../schemas/enums.yaml#/FormType" + forms_needing_review: + type: array + items: + $ref: "../schemas/enums.yaml#/FormType" + forms_missing_data: + type: array + items: + $ref: "../schemas/enums.yaml#/FormType" + +ReportMetadata: + type: object + properties: + report_id: + type: string + example: "FF-2024-CA-0157" + incident_number: + type: string + example: "CA-SQF-2024-0421" + report_date: + type: string + format: date + report_time: + type: string + format: time + report_status: + $ref: "../schemas/enums.yaml#/ReportStatus" + reporting_unit: + $ref: "#/ReportingUnit" + prepared_by: + type: array + items: + $ref: "#/Personnel" + reviewed_by: + type: array + items: + $ref: "#/Reviewer" + submission_log: + type: array + items: + type: object + properties: + form_type: + $ref: "../schemas/enums.yaml#/FormType" + submitted_at: + type: string + format: date-time + submitted_to: + type: string + +ReportingUnit: + type: object + properties: + station_name: + type: string + station_id: + type: string + agency_name: + type: string + agency_id: + type: string + format: uuid + agency_type: + type: string + +Personnel: + type: object + properties: + name: + type: string + badge_number: + type: string + rank: + type: string + role: + type: string + contact_number: + type: string + signature_captured: + type: boolean + +Reviewer: + type: object + properties: + name: + type: string + badge_number: + type: string + rank: + type: string + role: + type: string + reviewed_at: + type: string + format: date-time + approved: + type: boolean + +Incident: + type: object + properties: + name: + type: string + description: Human-readable incident name + types: + type: array + items: + $ref: "#/IncidentType" + start_datetime: + type: string + format: date-time + alarm_datetime: + type: string + format: date-time + first_arrival_datetime: + type: string + format: date-time + containment_datetime: + type: string + format: date-time + nullable: true + controlled_datetime: + type: string + format: date-time + nullable: true + cleared_datetime: + type: string + format: date-time + nullable: true + total_duration_hours: + type: number + nullable: true + narrative: + type: string + description: Free text summary of the incident + raw_transcript: + type: string + description: Original voice or text input verbatim + +IncidentType: + type: object + properties: + primary: + type: boolean + category: + $ref: "../schemas/enums.yaml#/IncidentCategory" + subcategory: + type: string + neris_code: + type: string + description: NERIS incident type code (replaces NFIRS as of Feb 2026) + nfirs_code: + type: string + description: Legacy NFIRS incident type code + +Location: + type: object + properties: + address: + type: string + nullable: true + nearest_landmark: + type: string + nullable: true + nearest_town: + type: string + nullable: true + county: + type: string + nullable: true + state: + type: string + nullable: true + country: + type: string + nullable: true + postal_code: + type: string + nullable: true + coordinates: + $ref: "#/Coordinates" + ignition_point_coordinates: + $ref: "#/CoordinatesBasic" + elevation_range_ft: + type: string + nullable: true + legal_description: + type: string + nullable: true + jurisdiction: + type: object + properties: + federal: + type: boolean + state: + type: boolean + private: + type: boolean + tribal: + type: boolean + property_type: + type: string + nullable: true + property_use: + type: string + nullable: true + dispatch_center: + type: string + nullable: true + +Coordinates: + type: object + properties: + latitude: + type: number + longitude: + type: number + accuracy_meters: + type: number + +CoordinatesBasic: + type: object + properties: + latitude: + type: number + longitude: + type: number + +Fire: + type: object + properties: + cause_category: + type: string + nullable: true + cause_specific: + type: string + nullable: true + cause_certainty: + $ref: "../schemas/enums.yaml#/CauseCertainty" + arson_suspected: + type: boolean + material_first_ignited: + type: string + nullable: true + fuel_types: + type: array + items: + type: string + fire_spread_directions: + type: array + items: + type: string + rate_of_spread: + $ref: "../schemas/enums.yaml#/RateOfSpread" + flame_lengths_ft: + type: string + nullable: true + spotting_distance_miles: + type: number + nullable: true + unusual_behaviors: + type: array + items: + type: string + detector_present: + type: boolean + nullable: true + detector_operated: + type: boolean + nullable: true + suppression_system_present: + type: boolean + nullable: true + suppression_system_operated: + type: boolean + nullable: true + estimated_damage_usd: + type: number + nullable: true + contents_loss_usd: + type: number + nullable: true + +Wildland: + type: object + properties: + is_wildland_incident: + type: boolean + total_acres_burned: + type: number + nullable: true + land_ownership_breakdown: + type: object + properties: + federal_acres: + type: number + state_acres: + type: number + private_acres: + type: number + tribal_acres: + type: number + percent_contained: + type: integer + minimum: 0 + maximum: 100 + fire_lines: + type: object + properties: + primary_line_miles: + type: number + secondary_line_miles: + type: number + dozer_line_miles: + type: number + hand_line_miles: + type: number + aerial_operations: + type: object + properties: + water_drops_gallons: + type: integer + retardant_drops_gallons: + type: integer + total_flight_hours: + type: number + containment_strategies: + type: array + items: + type: string + +Structure: + type: object + properties: + is_structure_involved: + type: boolean + structures_threatened: + type: integer + nullable: true + structures_damaged: + type: integer + nullable: true + structures_destroyed: + type: integer + nullable: true + structures_protected: + type: integer + nullable: true + construction_type: + type: string + nullable: true + stories: + type: integer + nullable: true + area_sqft: + type: number + nullable: true + occupancy_at_time: + type: integer + nullable: true + +Casualties: + type: object + properties: + civilian: + type: array + items: + $ref: "#/CivilianCasualty" + responder: + type: array + items: + $ref: "#/ResponderCasualty" + total_civilian_injuries: + type: integer + total_civilian_fatalities: + type: integer + total_responder_injuries: + type: integer + total_responder_fatalities: + type: integer + +CivilianCasualty: + type: object + properties: + age: + type: integer + nullable: true + sex: + type: string + nullable: true + injury_type: + type: string + severity: + $ref: "../schemas/enums.yaml#/InjurySeverity" + cause: + type: string + nullable: true + location_at_time: + type: string + nullable: true + transported: + type: boolean + hospital: + type: string + nullable: true + +ResponderCasualty: + type: object + properties: + personnel_id: + type: string + agency: + type: string + role: + type: string + injury_type: + type: string + severity: + $ref: "../schemas/enums.yaml#/InjurySeverity" + treatment: + type: string + nullable: true + transported: + type: boolean + hospital: + type: string + nullable: true + return_to_duty_date: + type: string + format: date + nullable: true + osha_recordable: + type: boolean + nfirs_5_required: + type: boolean + +EMS: + type: object + properties: + ems_response_required: + type: boolean + patients: + type: array + items: + $ref: "#/EMSPatient" + total_patients: + type: integer + ems_agency_responded: + type: string + nullable: true + nemsis_report_required: + type: boolean + nemsis_report_ids: + type: array + items: + type: string + +EMSPatient: + type: object + properties: + patient_ref_id: + type: string + age_approx: + type: integer + nullable: true + sex: + type: string + nullable: true + chief_complaint: + type: string + nullable: true + disposition: + type: string + nullable: true + transported: + type: boolean + date_of_birth: + type: string + format: date + nullable: true + nemsis_data_captured: + type: boolean + +Hazmat: + type: object + properties: + involved: + type: boolean + materials: + type: array + items: + type: object + properties: + name: + type: string + un_number: + type: string + nullable: true + quantity: + type: string + nullable: true + epa_reportable_quantity_exceeded: + type: boolean + spill_size_gallons: + type: number + nullable: true + +Arson: + type: object + properties: + suspected: + type: boolean + confirmed: + type: boolean + law_enforcement_notified: + type: boolean + investigation_required: + type: boolean + investigation_agency: + type: string + nullable: true + evidence_collected: + type: boolean + nibrs_report_required: + type: boolean + notes: + type: string + nullable: true + +RespondingAgencies: + type: object + properties: + primary_agency: + type: string + all_agencies: + type: array + items: + $ref: "#/RespondingAgency" + mutual_aid_activated: + type: boolean + mutual_aid_agencies: + type: array + items: + type: string + unified_command: + type: boolean + +RespondingAgency: + type: object + properties: + agency_name: + type: string + agency_type: + type: string + role: + type: string + personnel_count: + type: integer + +ResourcesDeployed: + type: object + properties: + total_personnel: + type: integer + personnel_breakdown: + type: object + properties: + firefighters: + type: integer + crew_supervisors: + type: integer + engineers: + type: integer + incident_command: + type: integer + support_staff: + type: integer + apparatus: + type: array + items: + $ref: "#/Apparatus" + crew_types: + type: array + items: + type: string + +Apparatus: + type: object + properties: + type: + type: string + count: + type: integer + +Weather: + type: object + properties: + on_arrival: + $ref: "#/WeatherReading" + worst_conditions: + $ref: "#/WeatherReadingExtended" + factors_influencing_fire: + type: array + items: + type: string + +WeatherReading: + type: object + properties: + datetime: + type: string + format: date-time + temperature_f: + type: number + relative_humidity_percent: + type: number + wind_speed_mph: + type: number + wind_direction: + type: string + haines_index: + type: integer + nullable: true + +WeatherReadingExtended: + type: object + properties: + datetime: + type: string + format: date-time + temperature_f: + type: number + relative_humidity_percent: + type: number + wind_speed_mph: + type: number + wind_gusts_mph: + type: number + nullable: true + +EnvironmentalImpact: + type: object + properties: + wildlife_habitat_affected_acres: + type: number + nullable: true + watershed_impact: + type: string + nullable: true + soil_erosion_risk: + type: string + nullable: true + sensitive_species_affected: + type: array + items: + type: string + air_quality_impact: + type: string + nullable: true + water_body_affected: + type: boolean + nullable: true + +InfrastructureImpact: + type: object + properties: + items: + type: array + items: + $ref: "#/InfrastructureItem" + +InfrastructureItem: + type: object + properties: + type: + type: string + unit: + type: string + quantity: + type: number + severity: + type: string + +NearMissAndSafety: + type: object + properties: + near_miss_events: + type: array + items: + type: object + properties: + description: + type: string + date: + type: string + format: date + contributing_factors: + type: array + items: + type: string + lessons_learned: + type: string + nullable: true + corrective_action: + type: string + nullable: true + safety_breaches: + type: integer + weather_related_risks: + type: array + items: + type: string + +LessonsLearned: + type: object + properties: + successful_tactics: + type: array + items: + type: string + areas_for_improvement: + type: array + items: + type: string + recommendations: + type: array + items: + type: string + +FollowUp: + type: object + properties: + mop_up: + type: object + properties: + percent_complete: + type: integer + estimated_completion_date: + type: string + format: date + personnel_assigned: + type: integer + rehabilitation: + type: object + properties: + erosion_control_acres: + type: number + reseeding_acres: + type: number + hazard_tree_removal_required: + type: boolean + next_inspection_date: + type: string + format: date + nullable: true + investigation_ongoing: + type: boolean + +PeriodicReporting: + type: object + properties: + contributes_to_monthly_report: + type: boolean + contributes_to_quarterly_report: + type: boolean + contributes_to_annual_report: + type: boolean + neris_submitted: + type: boolean + neris_submitted_at: + type: string + format: date-time + nullable: true + state_submitted: + type: boolean + state_submitted_at: + type: string + format: date-time + nullable: true + +Attachments: + type: object + properties: + maps: + type: boolean + photos_count: + type: integer + weather_charts: + type: boolean + resource_tracking_logs: + type: boolean + incident_action_plans: + type: boolean + attachment_refs: + type: array + items: + type: object + properties: + ref_id: + type: string + format: uuid + filename: + type: string + content_type: + type: string + size_bytes: + type: integer diff --git a/contracts/schemas/common.yaml b/contracts/schemas/common.yaml new file mode 100644 index 0000000..0798f85 --- /dev/null +++ b/contracts/schemas/common.yaml @@ -0,0 +1,129 @@ +# Common schemas used across multiple endpoints + +ErrorResponse: + type: object + required: + - error_code + - message + properties: + error_code: + type: string + description: Machine-readable error code + example: "EXTRACT_NOT_FOUND" + message: + type: string + description: Human-readable error message + example: "Extraction with ID xyz not found" + detail: + type: object + description: Additional context specific to the error type + additionalProperties: true + retry_after_seconds: + type: integer + description: Present on 503 errors seconds to wait before retrying + validation_errors: + type: array + description: Present on 422 errors list of field-level validation issues + items: + $ref: "#/ValidationError" + +ValidationError: + type: object + properties: + field: + type: string + description: JSON path of the invalid field + example: "fire.cause_certainty" + issue: + type: string + description: Description of the validation issue + example: "Must be one of: confirmed, probable, suspected, undetermined" + value: + description: The invalid value that was provided + +Pagination: + type: object + properties: + total: + type: integer + description: Total number of items across all pages + page: + type: integer + description: Current page number (1-based) + per_page: + type: integer + description: Items per page + total_pages: + type: integer + description: Total number of pages + has_next: + type: boolean + description: Whether there is a next page + has_prev: + type: boolean + description: Whether there is a previous page + +AsyncJobResponse: + type: object + required: + - job_id + - status + properties: + job_id: + type: string + format: uuid + description: Unique job identifier for polling + job_type: + type: string + description: Type of async operation + enum: + - transcription + - extraction + - form_generation + - batch_form_generation + - report_generation + status: + $ref: "enums.yaml#/JobStatus" + estimated_seconds: + type: integer + description: Estimated time to completion in seconds + poll_url: + type: string + description: Full URL to poll for job status + +Job: + type: object + required: + - job_id + - job_type + - status + properties: + job_id: + type: string + format: uuid + job_type: + type: string + enum: + - transcription + - extraction + - form_generation + - batch_form_generation + - report_generation + status: + $ref: "enums.yaml#/JobStatus" + progress_percent: + type: integer + minimum: 0 + maximum: 100 + description: Progress percentage (0–100) + result_url: + type: string + description: URL of the completed result (present when status is "completed") + error: + $ref: "#/ErrorResponse" + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time diff --git a/contracts/schemas/enums.yaml b/contracts/schemas/enums.yaml new file mode 100644 index 0000000..b79beda --- /dev/null +++ b/contracts/schemas/enums.yaml @@ -0,0 +1,130 @@ +# Shared enums used across the FireForm API + +InputStatus: + type: string + enum: + - queued + - transcribing + - ready + - failed + description: Status of an input record + +ExtractionStatus: + type: string + enum: + - processing + - completed + - failed + - needs_review + description: Status of an AI extraction job + +FormStatus: + type: string + enum: + - queued + - generating + - completed + - failed + description: Status of a form generation job + +ReportStatus: + type: string + enum: + - draft + - under_review + - approved + - submitted + description: Status of an incident report in the approval workflow + +JobStatus: + type: string + enum: + - queued + - processing + - completed + - failed + description: Status of any async job + +FormType: + type: string + enum: + - neris + - nemsis_epcr + - nibrs + - nfirs_basic + - nfirs_fire + - nfirs_structure + - nfirs_wildland + - nfirs_ems + - nfirs_hazmat + - nfirs_apparatus + - nfirs_personnel + - nfirs_arson + - nfirs_casualty_civilian + - nfirs_casualty_responder + - cal_fire_ics209 + - osha_301 + - un_ssirs + - state_georgia + - state_california + - state_new_york + description: | + Stable string identifier for a form type. Includes the new NERIS standard + (replacing NFIRS as of Feb 2026), legacy NFIRS modules, NEMSIS, NIBRS, + OSHA, state-specific, and international (UN SSIRS) forms. + +IncidentCategory: + type: string + enum: + - fire + - ems + - rescue + - hazardous_conditions + - service_call + - good_intent + - false_alarm + - law_enforcement + description: High-level incident category + +CauseCertainty: + type: string + enum: + - confirmed + - probable + - suspected + - undetermined + description: Certainty level of fire cause determination + +InjurySeverity: + type: string + enum: + - minor + - moderate + - severe + - fatal + description: Severity classification for injuries + +RateOfSpread: + type: string + enum: + - slow + - moderate + - rapid + - extreme + description: Rate of fire spread classification + +PeriodType: + type: string + enum: + - monthly + - quarterly + - annual + description: Reporting period type + +OutputFormat: + type: string + enum: + - pdf + - json + - both + description: Output format for generated forms diff --git a/contracts/schemas/extraction-record.yaml b/contracts/schemas/extraction-record.yaml new file mode 100644 index 0000000..aef7442 --- /dev/null +++ b/contracts/schemas/extraction-record.yaml @@ -0,0 +1,134 @@ +# Extraction-related schemas + +ExtractionRequest: + type: object + properties: + model_override: + type: string + description: Override the default LLM model (e.g. "llama3:70b") + extraction_hints: + type: object + description: Optional hints to improve extraction accuracy + properties: + incident_type: + type: string + description: Hint about the incident type (e.g. "wildland_fire", "structure_fire") + state: + type: string + description: US state code to apply state-specific extraction rules + agency_type: + type: string + description: Agency type hint for form selection + additionalProperties: true + +ExtractionCompleted: + type: object + required: + - extract_id + - input_id + - status + - canonical_incident + properties: + extract_id: + type: string + format: uuid + input_id: + type: string + format: uuid + status: + type: string + enum: + - completed + completed_at: + type: string + format: date-time + model_used: + type: string + description: LLM model that performed the extraction + processing_time_seconds: + type: number + canonical_incident: + $ref: "canonical-incident.yaml#/CanonicalIncident" + corrections: + type: array + description: Audit trail of manual corrections applied via PATCH + items: + type: object + properties: + field_path: + type: string + original_value: {} + corrected_value: {} + corrected_at: + type: string + format: date-time + corrected_by: + type: string + +ExtractionProcessing: + type: object + required: + - extract_id + - input_id + - status + properties: + extract_id: + type: string + format: uuid + input_id: + type: string + format: uuid + status: + type: string + enum: + - processing + - failed + started_at: + type: string + format: date-time + retry_after_seconds: + type: integer + description: Polling hint for clients + error_type: + type: string + nullable: true + description: Present when status is "failed" + error_detail: + type: string + nullable: true + partial_result: + $ref: "canonical-incident.yaml#/CanonicalIncident" + +ValidationResult: + type: object + required: + - valid + - form_type + - extract_id + properties: + valid: + type: boolean + description: Whether all required fields for this form type are present + form_type: + $ref: "enums.yaml#/FormType" + extract_id: + type: string + format: uuid + missing_required: + type: array + items: + type: string + description: JSON paths of required fields that are missing + missing_recommended: + type: array + items: + type: string + description: JSON paths of recommended fields that are missing + warnings: + type: array + items: + type: string + description: Human-readable warnings about data quality + field_coverage_percent: + type: number + description: Percentage of form fields that have values diff --git a/contracts/schemas/form-record.yaml b/contracts/schemas/form-record.yaml new file mode 100644 index 0000000..a83ec41 --- /dev/null +++ b/contracts/schemas/form-record.yaml @@ -0,0 +1,198 @@ +# Form generation related schemas + +GenerateAllRequest: + type: object + required: + - extract_id + properties: + extract_id: + type: string + format: uuid + options: + type: object + properties: + skip_incomplete: + type: boolean + default: true + description: Skip forms that fail validation + force_partial: + type: boolean + default: false + description: Generate forms even with missing fields (leaving blanks) + +GenerateSingleRequest: + type: object + required: + - extract_id + properties: + extract_id: + type: string + format: uuid + options: + type: object + properties: + output_format: + $ref: "../schemas/enums.yaml#/OutputFormat" + force_partial: + type: boolean + default: false + force: + type: boolean + default: false + description: Allow generation even if form_type is not in applicable_forms + +BatchGenerateResponse: + type: object + required: + - batch_id + - status + - extract_id + properties: + batch_id: + type: string + format: uuid + status: + type: string + enum: [processing] + extract_id: + type: string + format: uuid + forms_queued: + type: array + items: + $ref: "../schemas/enums.yaml#/FormType" + forms_skipped: + type: array + items: + type: object + properties: + form_type: + $ref: "../schemas/enums.yaml#/FormType" + reason: + type: string + estimated_seconds: + type: integer + poll_url: + type: string + +FormGenerateResponse: + type: object + required: + - form_id + - form_type + - status + properties: + form_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + status: + type: string + enum: [processing, completed] + extract_id: + type: string + format: uuid + job_id: + type: string + format: uuid + estimated_seconds: + type: integer + poll_url: + type: string + +FormRecord: + type: object + required: + - form_id + - form_type + - status + properties: + form_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + status: + $ref: "../schemas/enums.yaml#/FormStatus" + extract_id: + type: string + format: uuid + incident_id: + type: string + format: uuid + nullable: true + created_at: + type: string + format: date-time + completed_at: + type: string + format: date-time + nullable: true + pdf_ready: + type: boolean + json_ready: + type: boolean + field_mapping_summary: + type: object + properties: + total_form_fields: + type: integer + fields_filled: + type: integer + fields_blank: + type: integer + coverage_percent: + type: number + +FormMappedJson: + type: object + required: + - form_type + - form_id + properties: + form_type: + $ref: "../schemas/enums.yaml#/FormType" + form_version: + type: string + form_id: + type: string + format: uuid + extract_id: + type: string + format: uuid + agency_fields: + type: object + additionalProperties: true + description: Agency-specific field names and values as the target system expects + +BatchStatus: + type: object + required: + - batch_id + - status + properties: + batch_id: + type: string + format: uuid + status: + type: string + enum: [processing, completed, failed] + total: + type: integer + completed: + type: integer + failed: + type: integer + forms: + type: array + items: + type: object + properties: + form_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + status: + $ref: "../schemas/enums.yaml#/FormStatus" diff --git a/contracts/schemas/incident-record.yaml b/contracts/schemas/incident-record.yaml new file mode 100644 index 0000000..932425c --- /dev/null +++ b/contracts/schemas/incident-record.yaml @@ -0,0 +1,150 @@ +# Incident management schemas + +CreateIncidentRequest: + type: object + required: + - extract_id + properties: + extract_id: + type: string + format: uuid + incident_number: + type: string + description: Optional org-assigned incident number + tags: + type: array + items: + type: string + +UpdateIncidentRequest: + type: object + properties: + status: + $ref: "../schemas/enums.yaml#/ReportStatus" + tags: + type: array + items: + type: string + incident_number: + type: string + notes: + type: string + +IncidentRecord: + type: object + required: + - incident_id + - extract_id + - status + properties: + incident_id: + type: string + format: uuid + extract_id: + type: string + format: uuid + incident_number: + type: string + nullable: true + status: + $ref: "../schemas/enums.yaml#/ReportStatus" + incident_name: + type: string + nullable: true + incident_type: + type: string + nullable: true + incident_date: + type: string + format: date + nullable: true + forms_generated: + type: array + items: + type: object + properties: + form_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + status: + $ref: "../schemas/enums.yaml#/FormStatus" + tags: + type: array + items: + type: string + notes: + type: string + nullable: true + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + deleted_at: + type: string + format: date-time + nullable: true + +IncidentRecordFull: + description: Full incident record with linked extraction and forms + allOf: + - $ref: "#/IncidentRecord" + - type: object + properties: + canonical_incident: + $ref: "canonical-incident.yaml#/CanonicalIncident" + forms: + type: array + items: + $ref: "form-record.yaml#/FormRecord" + submission_log: + type: array + items: + type: object + properties: + form_type: + $ref: "../schemas/enums.yaml#/FormType" + submitted_at: + type: string + format: date-time + submitted_to: + type: string + status: + type: string + +IncidentListResponse: + type: object + properties: + data: + type: array + items: + type: object + properties: + incident_id: + type: string + format: uuid + incident_number: + type: string + nullable: true + status: + $ref: "../schemas/enums.yaml#/ReportStatus" + incident_name: + type: string + nullable: true + incident_type: + type: string + nullable: true + incident_date: + type: string + format: date + nullable: true + forms_count: + type: integer + created_at: + type: string + format: date-time + pagination: + $ref: "common.yaml#/Pagination" diff --git a/contracts/schemas/input-record.yaml b/contracts/schemas/input-record.yaml new file mode 100644 index 0000000..65dd205 --- /dev/null +++ b/contracts/schemas/input-record.yaml @@ -0,0 +1,138 @@ +# Input-related schemas + +TextInputRequest: + type: object + required: + - narrative + properties: + narrative: + type: string + description: Free-text incident narrative (10+ words, max 50,000 characters) + minLength: 20 + maxLength: 50000 + station_id: + type: string + description: Station identifier + responder_badge: + type: string + description: Badge number of the reporting responder + incident_date_hint: + type: string + format: date + description: Approximate date of the incident + +VoiceInputResponse: + type: object + required: + - input_id + - status + - input_type + properties: + input_id: + type: string + format: uuid + status: + type: string + enum: + - queued + input_type: + type: string + enum: + - voice + estimated_processing_seconds: + type: integer + created_at: + type: string + format: date-time + job_id: + type: string + format: uuid + description: Job ID for polling transcription status + poll_url: + type: string + description: URL to poll for input status + +TextInputResponse: + type: object + required: + - input_id + - status + - input_type + properties: + input_id: + type: string + format: uuid + status: + type: string + enum: + - ready + input_type: + type: string + enum: + - text + character_count: + type: integer + word_count: + type: integer + created_at: + type: string + format: date-time + +InputRecord: + type: object + required: + - input_id + - input_type + - status + properties: + input_id: + type: string + format: uuid + input_type: + type: string + enum: + - voice + - text + status: + $ref: "enums.yaml#/InputStatus" + transcript: + type: string + nullable: true + description: Transcribed text (for voice) or original narrative (for text). Null while transcribing. + original_filename: + type: string + nullable: true + description: Original audio filename (voice inputs only) + audio_duration_seconds: + type: number + nullable: true + description: Duration of audio in seconds (voice inputs only) + character_count: + type: integer + nullable: true + word_count: + type: integer + nullable: true + station_id: + type: string + nullable: true + responder_badge: + type: string + nullable: true + incident_date_hint: + type: string + format: date + nullable: true + error_detail: + type: string + nullable: true + description: Error message if transcription failed + retry_after_seconds: + type: integer + description: Polling hint present when status is queued or transcribing + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time diff --git a/contracts/schemas/reporting.yaml b/contracts/schemas/reporting.yaml new file mode 100644 index 0000000..f352e70 --- /dev/null +++ b/contracts/schemas/reporting.yaml @@ -0,0 +1,112 @@ +# Reporting & Analytics schemas + +ReportSummary: + type: object + properties: + date_from: + type: string + format: date + date_to: + type: string + format: date + total_incidents: + type: integer + by_type: + type: object + additionalProperties: + type: integer + description: Incident counts grouped by category + by_status: + type: object + additionalProperties: + type: integer + description: Incident counts grouped by report status + forms_generated: + type: integer + avg_completeness_score: + type: number + avg_processing_time_seconds: + type: number + groups: + type: array + items: + type: object + properties: + period: + type: string + description: Period label (e.g. "2024-07" for monthly, "fire" for by type) + incident_count: + type: integer + forms_generated: + type: integer + +GenerateReportRequest: + type: object + required: + - period_type + - year + properties: + period_type: + $ref: "../schemas/enums.yaml#/PeriodType" + year: + type: integer + minimum: 2000 + maximum: 2100 + month: + type: integer + minimum: 1 + maximum: 12 + description: Required when period_type is "monthly" + quarter: + type: integer + minimum: 1 + maximum: 4 + description: Required when period_type is "quarterly" + format: + $ref: "../schemas/enums.yaml#/OutputFormat" + +PeriodicReport: + type: object + properties: + report_id: + type: string + format: uuid + period_type: + $ref: "../schemas/enums.yaml#/PeriodType" + period_label: + type: string + description: Human-readable period (e.g. "July 2024", "Q3 2024", "2024") + generated_at: + type: string + format: date-time + summary: + $ref: "#/ReportSummary" + incidents: + type: array + items: + type: object + properties: + incident_id: + type: string + format: uuid + incident_number: + type: string + incident_date: + type: string + format: date + incident_type: + type: string + status: + type: string + forms_generated: + type: integer + compliance: + type: object + properties: + neris_submission_rate: + type: number + description: Percentage of incidents with NERIS submitted + average_submission_delay_days: + type: number + overdue_incidents: + type: integer diff --git a/contracts/schemas/system.yaml b/contracts/schemas/system.yaml new file mode 100644 index 0000000..8c25693 --- /dev/null +++ b/contracts/schemas/system.yaml @@ -0,0 +1,101 @@ +# System & health check schemas + +HealthStatus: + type: object + required: + - status + - version + properties: + status: + type: string + enum: + - healthy + - degraded + - unhealthy + version: + type: string + description: FireForm API version + uptime_seconds: + type: integer + components: + type: object + properties: + database: + $ref: "#/ComponentHealth" + ollama: + $ref: "#/ComponentHealth" + whisper: + $ref: "#/ComponentHealth" + storage: + $ref: "#/ComponentHealth" + +ComponentHealth: + type: object + required: + - status + properties: + status: + type: string + enum: + - healthy + - degraded + - unhealthy + response_time_ms: + type: integer + nullable: true + detail: + type: string + nullable: true + model_loaded: + type: string + nullable: true + description: Currently loaded model (ollama component) + disk_free_gb: + type: number + nullable: true + description: Free disk space (storage component) + ollama_version: + type: string + nullable: true + description: Ollama server version (ollama component) + models_available: + type: array + nullable: true + description: All models pulled and available on the Ollama server (ollama component) + items: + type: object + properties: + name: + type: string + size_gb: + type: number + quantization: + type: string + nullable: true + loaded: + type: boolean + current_load: + type: object + nullable: true + description: Current processing load (ollama component) + properties: + active_requests: + type: integer + queued_requests: + type: integer + +SchemaVersion: + type: object + required: + - version + - released_at + properties: + version: + type: string + released_at: + type: string + format: date-time + changelog: + type: string + breaking_changes: + type: boolean diff --git a/contracts/schemas/template.yaml b/contracts/schemas/template.yaml new file mode 100644 index 0000000..45c71f7 --- /dev/null +++ b/contracts/schemas/template.yaml @@ -0,0 +1,144 @@ +# Template & Configuration schemas + +TemplateSummary: + type: object + properties: + template_id: + type: string + format: uuid + form_type: + $ref: "../schemas/enums.yaml#/FormType" + display_name: + type: string + jurisdiction: + type: string + description: Jurisdiction code (e.g. "US-Federal", "US-CA", "US-GA") + agency_type: + type: string + version: + type: string + last_updated: + type: string + format: date + field_count: + type: integer + status: + type: string + enum: + - active + - legacy + - draft + +CreateTemplateRequest: + type: object + required: + - form_type + - display_name + - jurisdiction + - fields + - field_mappings_from_canonical + properties: + form_type: + type: string + description: Unique form type identifier + display_name: + type: string + jurisdiction: + type: string + agency_type: + type: string + fields: + type: array + items: + $ref: "#/TemplateField" + field_mappings_from_canonical: + type: object + additionalProperties: + type: string + description: | + Mapping from canonical FireForm JSON paths to this template's field names. + Key = canonical path (e.g. "fire.cause_category"), value = template field name. + source_standard: + type: string + nullable: true + description: Reference to the source standard (e.g. "NERIS v2.0", "NFIRS 5.0") + pdf_template_ref: + type: string + nullable: true + description: Reference to the PDF template file + +Template: + allOf: + - type: object + properties: + template_id: + type: string + format: uuid + version: + type: string + last_updated: + type: string + format: date + field_count: + type: integer + status: + type: string + enum: [active, legacy, draft] + created_at: + type: string + format: date-time + updated_at: + type: string + format: date-time + - $ref: "#/CreateTemplateRequest" + +TemplateField: + type: object + required: + - field_name + - field_type + - required + properties: + field_name: + type: string + description: Field identifier within this template + field_type: + type: string + enum: + - string + - integer + - number + - boolean + - date + - datetime + - time + - enum + - text + - array + description: Data type of the field + required: + type: boolean + description: + type: string + nullable: true + max_length: + type: integer + nullable: true + min_value: + type: number + nullable: true + max_value: + type: number + nullable: true + allowed_values: + type: array + items: + type: string + nullable: true + description: Valid values for enum fields + canonical_mapping: + type: string + description: JSON path in canonical FireForm schema this field maps from + default_value: + nullable: true + description: Default value if canonical field is null From 38253221f5b4795856b410624bb0f93478df71a6 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Tue, 9 Jun 2026 11:41:46 +0530 Subject: [PATCH 2/3] renamed incident-contract --- .../schemas/{canonical-incident.yaml => incident-contract.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/schemas/{canonical-incident.yaml => incident-contract.yaml} (100%) diff --git a/contracts/schemas/canonical-incident.yaml b/contracts/schemas/incident-contract.yaml similarity index 100% rename from contracts/schemas/canonical-incident.yaml rename to contracts/schemas/incident-contract.yaml From 7b354ea39ea790fefa39adbb6cce84de3d5c69f8 Mon Sep 17 00:00:00 2001 From: chetanr25 Date: Tue, 9 Jun 2026 11:54:45 +0530 Subject: [PATCH 3/3] updated incident-contracts in all references --- contracts/path/extraction.yaml | 6 +++--- contracts/schemas/extraction-record.yaml | 8 ++++---- contracts/schemas/incident-contract.yaml | 4 ++-- contracts/schemas/incident-record.yaml | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/path/extraction.yaml b/contracts/path/extraction.yaml index 0e11309..865e20c 100644 --- a/contracts/path/extraction.yaml +++ b/contracts/path/extraction.yaml @@ -129,9 +129,9 @@ extract_by_id: input_id: "550e8400-e29b-41d4-a716-446655440001" status: "completed" completed_at: "2024-07-15T14:31:05Z" - canonical_incident: + incident_contract: schema_version: "1.1.0" - schema_name: "fireform_canonical_incident" + schema_name: "fireform_incident_contract" extraction_metadata: extract_id: "550e8400-e29b-41d4-a716-446655440020" confidence_score: 0.91 @@ -179,7 +179,7 @@ extract_by_id: content: application/merge-patch+json: schema: - $ref: "../schemas/canonical-incident.yaml#/CanonicalIncident" + $ref: "../schemas/incident-contract.yaml#/IncidentContract" example: fire: estimated_damage_usd: 250000 diff --git a/contracts/schemas/extraction-record.yaml b/contracts/schemas/extraction-record.yaml index aef7442..ae0fd48 100644 --- a/contracts/schemas/extraction-record.yaml +++ b/contracts/schemas/extraction-record.yaml @@ -27,7 +27,7 @@ ExtractionCompleted: - extract_id - input_id - status - - canonical_incident + - incident_contract properties: extract_id: type: string @@ -47,8 +47,8 @@ ExtractionCompleted: description: LLM model that performed the extraction processing_time_seconds: type: number - canonical_incident: - $ref: "canonical-incident.yaml#/CanonicalIncident" + incident_contract: + $ref: "incident-contract.yaml#/IncidentContract" corrections: type: array description: Audit trail of manual corrections applied via PATCH @@ -97,7 +97,7 @@ ExtractionProcessing: type: string nullable: true partial_result: - $ref: "canonical-incident.yaml#/CanonicalIncident" + $ref: "incident-contract.yaml#/IncidentContract" ValidationResult: type: object diff --git a/contracts/schemas/incident-contract.yaml b/contracts/schemas/incident-contract.yaml index a11d1b9..1a129df 100644 --- a/contracts/schemas/incident-contract.yaml +++ b/contracts/schemas/incident-contract.yaml @@ -1,7 +1,7 @@ # Canonical FireForm Incident Schema # This is the master superset schema single source of truth for all downstream forms -CanonicalIncident: +IncidentContract: type: object description: | The canonical FireForm incident data model. This is the superset schema containing @@ -16,7 +16,7 @@ CanonicalIncident: type: string description: Schema name identifier enum: - - fireform_canonical_incident + - fireform_incident_contract extraction_metadata: $ref: "#/ExtractionMetadata" diff --git a/contracts/schemas/incident-record.yaml b/contracts/schemas/incident-record.yaml index 932425c..686e7ce 100644 --- a/contracts/schemas/incident-record.yaml +++ b/contracts/schemas/incident-record.yaml @@ -94,8 +94,8 @@ IncidentRecordFull: - $ref: "#/IncidentRecord" - type: object properties: - canonical_incident: - $ref: "canonical-incident.yaml#/CanonicalIncident" + incident_contract: + $ref: "incident-contract.yaml#/IncidentContract" forms: type: array items: