Skip to content
Draft
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
55 changes: 55 additions & 0 deletions alembic/versions/003_form_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""form_templates — contract Layer 6 template registry table.

Revision ID: 003
Revises: 002
Create Date: 2026-06-26

Distinct from the legacy `template` table (int PK + uploaded PDF). This is the
standards registry keyed by `form_type`, holding incident-schema field definitions
plus their visual layout. The `fields` JSON column (list of TemplateField objects,
each with a nested `layout`) uses sa.JSON for consistency with migrations 001/002
and SQLite test-harness compatibility.

`form_type` is a plain VARCHAR (not a Postgres ENUM): custom jurisdictions are
registered here and are not part of the built-in FormType enum.
"""

from collections.abc import Sequence

from alembic import op
import sqlalchemy as sa
import sqlmodel

revision: str = "003"
down_revision: str | None = "002"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None


def upgrade() -> None:
op.create_table(
"form_templates",
sa.Column("template_id", sa.Uuid(), primary_key=True),
sa.Column("form_type", sqlmodel.sql.sqltypes.AutoString, nullable=False),
sa.Column("display_name", sqlmodel.sql.sqltypes.AutoString, nullable=False),
sa.Column("jurisdiction", sqlmodel.sql.sqltypes.AutoString, nullable=True),
sa.Column("agency_type", sqlmodel.sql.sqltypes.AutoString, nullable=True),
sa.Column("fields", sa.JSON, nullable=False),
sa.Column("source_standard", sqlmodel.sql.sqltypes.AutoString, nullable=True),
sa.Column("pdf_template_ref", sqlmodel.sql.sqltypes.AutoString, nullable=True),
sa.Column("version", sqlmodel.sql.sqltypes.AutoString, nullable=False),
sa.Column("status", sqlmodel.sql.sqltypes.AutoString, nullable=False),
sa.Column("created_at", sa.DateTime, nullable=False),
sa.Column("updated_at", sa.DateTime, nullable=False),
)
op.create_index(
"ix_form_templates_form_type",
"form_templates",
["form_type"],
unique=True,
)


def downgrade() -> None:
op.drop_index("ix_form_templates_form_type", table_name="form_templates")
op.drop_table("form_templates")
4 changes: 2 additions & 2 deletions app/api/router.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from fastapi import APIRouter

from app.api.routes import forms, input, jobs, system, templates, weather, zipcode
from app.api.routes import forms, form_templates, input, jobs, system, weather, zipcode
from app.core.config import API_PREFIX

api_router = APIRouter()
api_router.include_router(templates.router, prefix=API_PREFIX)
api_router.include_router(form_templates.router, prefix=API_PREFIX)
api_router.include_router(forms.router, prefix=API_PREFIX)
api_router.include_router(system.router, prefix=API_PREFIX)
api_router.include_router(jobs.router, prefix=API_PREFIX)
Expand Down
4 changes: 2 additions & 2 deletions app/api/routes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from . import templates, forms
from . import form_templates, forms

__all__ = ["templates", "forms"]
__all__ = ["form_templates", "forms"]
56 changes: 56 additions & 0 deletions app/api/routes/form_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""Contract Layer 6 template registry endpoints (contracts/path/templates.yaml).

Serves the form-template registry at /api/v1/templates, backed by the
UUID-keyed `FormTemplate` model. Handlers are thin — business logic lives in
app/services/form_templates.py. The legacy prototype template routes (upload /
create / make-fillable / preview / delete) were removed in the contract
migration; the legacy int-PK `Template` model survives only as the lookup target
of the fill pipeline (forms.py / jobs.py / tasks/fill.py).
"""

from uuid import UUID

from fastapi import APIRouter, Depends, Query
from sqlmodel import Session

from app.api.deps import get_db
from app.api.schemas.templates import (
CreateTemplateRequest,
TemplateDetail,
TemplateFieldsResponse,
TemplateSummary,
)
from app.services import form_templates as service

router = APIRouter(prefix="/templates", tags=["templates"])


@router.get("", response_model=list[TemplateSummary])
def list_templates(db: Session = Depends(get_db)):
return service.list_templates(db)


@router.post("", response_model=TemplateDetail, status_code=201)
def create_template(body: CreateTemplateRequest, db: Session = Depends(get_db)):
return service.create_template(db, body)


@router.get("/{template_id}", response_model=TemplateDetail)
def get_template(template_id: UUID, db: Session = Depends(get_db)):
return service.get_template(db, template_id)


@router.put("/{template_id}", response_model=TemplateDetail)
def replace_template(
template_id: UUID, body: CreateTemplateRequest, db: Session = Depends(get_db)
):
return service.replace_template(db, template_id, body)


@router.get("/{template_id}/fields", response_model=TemplateFieldsResponse)
def get_template_fields(
template_id: UUID,
required_only: bool = Query(False, description="Return only required fields"),
db: Session = Depends(get_db),
):
return service.get_template_fields(db, template_id, required_only)
240 changes: 0 additions & 240 deletions app/api/routes/templates.py

This file was deleted.

Loading
Loading