Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions contracts/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ paths:
$ref: "path/templates.yaml#/template_by_id"
/api/v1/templates/{template_id}/fields:
$ref: "path/templates.yaml#/template_fields"
/api/v1/templates/pdf:
$ref: "path/templates.yaml#/templates_pdf"

# ── Layer 7: System ────────────────────────────────────────────
/api/v1/health:
Expand Down
163 changes: 151 additions & 12 deletions contracts/path/templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# POST /api/v1/templates
# PUT /api/v1/templates/{template_id}
# GET /api/v1/templates/{template_id}/fields
# POST /api/v1/templates/pdf

templates:
get:
Expand All @@ -13,7 +14,7 @@ templates:
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.
rules, and mapping from the FireForm incident schema.
tags:
- templates
responses:
Expand Down Expand Up @@ -61,7 +62,7 @@ templates:
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.
rules, and how each maps from the FireForm incident schema.
tags:
- templates
requestBody:
Expand All @@ -75,13 +76,24 @@ templates:
display_name: "Texas State Fire Marshal Incident Report"
jurisdiction: "US-TX"
agency_type: "fire_department"
pdf_template_ref: "templates/state_texas.pdf"
fields:
- field_name: "incident_number"
field_type: "string"
required: true
max_length: 20
description: "State-assigned incident number"
canonical_mapping: "report_metadata.incident_number"
incident_mapping: "report_metadata.incident_number"
layout:
page: 0
x: 188.33
y: 621.33
width: 127.33
height: 28.67
font: "Helvetica"
font_size: 10
color: "#000000"
align: "left"
- field_name: "fire_cause"
field_type: "enum"
required: true
Expand All @@ -90,10 +102,26 @@ templates:
- "natural"
- "intentional"
- "undetermined"
canonical_mapping: "fire.cause_category"
field_mappings_from_canonical:
"report_metadata.incident_number": "incident_number"
"fire.cause_category": "fire_cause"
incident_mapping: "fire.cause_category"
layout:
page: 0
x: 188.33
y: 560.0
width: 200.0
height: 18.0
- field_name: "report_footer"
field_type: "string"
required: false
static_text: "Generated by FireForm"
incident_mapping: null
layout:
page: 0
x: 72.0
y: 40.0
width: 300.0
height: 12.0
font_size: 8
align: "center"
responses:
"201":
description: Template created
Expand All @@ -120,8 +148,8 @@ template_by_id:
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.
required/optional status, validation rules, and the mapping from the FireForm
incident schema fields. Includes the source standard reference where applicable.
tags:
- templates
parameters:
Expand Down Expand Up @@ -201,7 +229,7 @@ template_fields:
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
status, validation rules, and incident-schema mapping. Useful for building
validation checklists and for the validate endpoint to determine requirements.
tags:
- templates
Expand Down Expand Up @@ -253,15 +281,126 @@ template_fields:
field_type: "enum"
required: true
description: "Primary incident type code"
canonical_mapping: "incident.types[0].neris_code"
incident_mapping: "incident.types[0].neris_code"
layout:
page: 0
x: 120.0
y: 680.0
width: 160.0
height: 18.0
- field_name: "incident_date"
field_type: "date"
required: true
description: "Date of incident"
canonical_mapping: "incident.start_datetime"
incident_mapping: "incident.start_datetime"
layout:
page: 0
x: 120.0
y: 655.0
width: 120.0
height: 18.0
"404":
description: Template not found
content:
application/json:
schema:
$ref: "../schemas/common.yaml#/ErrorResponse"

templates_pdf:
post:
operationId: uploadTemplatePdf
summary: Upload a blank PDF for a template before defining its fields
description: |
Uploads the blank agency PDF that a template's field coordinates will be
written onto, and returns a `pdf_template_ref` plus the page geometry.

This is the first step of the template-authoring flow. The visual editor
renders the returned pages, the user draws a box per field, and the box
coordinates become each field's `layout`. The resulting `pdf_template_ref`
is then sent in the `POST /api/v1/templates` body. Upload precedes create
because the layout coordinates only have meaning relative to this PDF.

Page dimensions are returned in PDF points (1/72 inch, origin bottom-left)
so the editor can map canvas positions to the same coordinate system the
`layout` fields use.
tags:
- templates
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- pdf_file
properties:
pdf_file:
type: string
format: binary
description: Blank fillable or flat PDF form. Max 50MB.
responses:
"201":
description: PDF stored and ready to be referenced by a template
content:
application/json:
schema:
type: object
required:
- pdf_template_ref
- original_filename
- page_count
- pages
properties:
pdf_template_ref:
type: string
description: Opaque reference to the stored PDF, passed back in
the template body as pdf_template_ref
original_filename:
type: string
page_count:
type: integer
pages:
type: array
description: Per-page geometry in PDF points, index 0 = first page
items:
type: object
required:
- page
- width
- height
properties:
page:
type: integer
width:
type: number
height:
type: number
example:
pdf_template_ref: "templates/uploads/550e8400-e29b-41d4-a716-446655440080.pdf"
original_filename: "texas_sfm_incident.pdf"
page_count: 2
pages:
- page: 0
width: 612.0
height: 792.0
- page: 1
width: 612.0
height: 792.0
"400":
description: Missing file or malformed multipart request
content:
application/json:
schema:
$ref: "../schemas/common.yaml#/ErrorResponse"
"413":
description: PDF exceeds the maximum allowed size
content:
application/json:
schema:
$ref: "../schemas/common.yaml#/ErrorResponse"
"415":
description: Uploaded file is not a valid PDF
content:
application/json:
schema:
$ref: "../schemas/common.yaml#/ErrorResponse"
87 changes: 73 additions & 14 deletions contracts/schemas/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ TemplateSummary:
type: string
format: uuid
form_type:
$ref: "../schemas/enums.yaml#/FormType"
type: string
description: Unique form type identifier (built-in or custom jurisdiction)
display_name:
type: string
jurisdiction:
type: string
nullable: true
description: Jurisdiction code (e.g. "US-Federal", "US-CA", "US-GA")
agency_type:
type: string
nullable: true
version:
type: string
last_updated:
Expand All @@ -34,9 +37,7 @@ CreateTemplateRequest:
required:
- form_type
- display_name
- jurisdiction
- fields
- field_mappings_from_canonical
properties:
form_type:
type: string
Expand All @@ -45,27 +46,24 @@ CreateTemplateRequest:
type: string
jurisdiction:
type: string
nullable: true
description: Jurisdiction code. Optional — the visual editor may register a
template before jurisdiction is assigned.
agency_type:
type: string
nullable: true
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
description: Reference to the PDF template file the layout coordinates apply to

Template:
allOf:
Expand Down Expand Up @@ -136,9 +134,70 @@ TemplateField:
type: string
nullable: true
description: Valid values for enum fields
canonical_mapping:
incident_mapping:
type: string
description: JSON path in canonical FireForm schema this field maps from
nullable: true
description: |
JSON path in the FireForm incident schema this field pulls its value from
(e.g. "fire.cause_category"). Null for a static field. Exactly one of
incident_mapping or static_text is expected.
static_text:
type: string
nullable: true
description: |
Fixed text drawn into the field instead of a mapped value. Null for a
data-mapped field. Mutually exclusive with incident_mapping.
default_value:
nullable: true
description: Default value if canonical field is null
description: Default value if the mapped incident field is null
layout:
nullable: true
description: Visual placement of the field on the PDF. Null for fields with
no fixed position.
allOf:
- $ref: "#/TemplateFieldLayout"

TemplateFieldLayout:
type: object
description: |
Visual placement of a field on the PDF page. Coordinates are in PDF points
with the origin at the bottom-left of the page, so the field box runs from
start = (x, y) to end = (x + width, y + height). The editor is responsible
for converting rendered-canvas pixels to PDF points before saving.
required:
- page
- x
- y
- width
- height
properties:
page:
type: integer
description: Zero-based page index
x:
type: number
description: Lower-left X of the box, in PDF points
y:
type: number
description: Lower-left Y of the box, in PDF points (origin bottom-left)
width:
type: number
height:
type: number
font:
type: string
default: Helvetica
font_size:
type: number
default: 10
color:
type: string
default: "#000000"
description: Hex color, e.g. "#000000"
align:
type: string
enum:
- left
- center
- right
default: left
Loading