diff --git a/contracts/openapi.yaml b/contracts/openapi.yaml index 781c221..c6c0c1f 100644 --- a/contracts/openapi.yaml +++ b/contracts/openapi.yaml @@ -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: diff --git a/contracts/path/templates.yaml b/contracts/path/templates.yaml index dab9dca..c2621ca 100644 --- a/contracts/path/templates.yaml +++ b/contracts/path/templates.yaml @@ -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: @@ -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: @@ -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: @@ -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 @@ -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 @@ -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: @@ -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 @@ -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" diff --git a/contracts/schemas/template.yaml b/contracts/schemas/template.yaml index 45c71f7..f9ce921 100644 --- a/contracts/schemas/template.yaml +++ b/contracts/schemas/template.yaml @@ -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: @@ -34,9 +37,7 @@ CreateTemplateRequest: required: - form_type - display_name - - jurisdiction - fields - - field_mappings_from_canonical properties: form_type: type: string @@ -45,19 +46,16 @@ 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 @@ -65,7 +63,7 @@ CreateTemplateRequest: 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: @@ -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