Skip to content
Open
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
.idea
venv
.venv
*.db
*.db
*.egg-info/
src/inputs/*_template.pdf
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
9 changes: 4 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ RUN apt-get update && apt-get install -y \
curl \
&& rm -rf /var/lib/apt/lists/*

# Copy and install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Install Python dependencies after the source is present so pip can install the package itself.
RUN pip install --no-cache-dir -r requirements.txt -r requirements-ai.txt

# Set Python path so imports work correctly
ENV PYTHONPATH=/app/src
ENV PYTHONPATH=/app

# Keep container running for interactive use
CMD ["tail", "-f", "/dev/null"]
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pull-model:
docker compose exec ollama ollama pull mistral

test:
docker compose exec app python3 -m pytest src/test/
docker compose exec app python3 -m pytest -q tests/

clean:
docker compose down -v
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ FireForm is a centralized "report once, file everywhere" system.

The result is hours of time saved per shift, per firefighter.

## Local Setup

FireForm targets Python 3.11. The repository includes a `.python-version` file to make that explicit.

For local development, install the package in editable mode with the dev dependencies:
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
python -m api.db.init_db
pytest -q
```

For the full PDF template and Ollama-backed workflow, install the optional AI extras as well:
```bash
pip install -r requirements-ai.txt
```

### ✨ Key Features
- **Agnostic:** Works with any department's existing fillable PDF forms.
- **AI-Powered:** Uses open-source, locally-run LLMs (Mistral) to extract data from natural language. No data ever needs to leave the local machine.
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ services:
- .:/app
environment:
- PYTHONUNBUFFERED=1
- PYTHONPATH=/app/src
- PYTHONPATH=/app
- OLLAMA_HOST=http://ollama:11434
networks:
- fireform-network
Expand Down
15 changes: 14 additions & 1 deletion docs/db.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ This guide explains how to set up, initialize, and manage the FireForm database.
> [!IMPORTANT]
> Ensure you have installed all dependencies before proceeding:
> ```bash
> pip install -r requirements.txt
> pip install -r requirements-dev.txt
> ```
>
> Install the optional AI/PDF extras only if you want template creation and the Ollama-backed fill flow:
> ```bash
> pip install -r requirements-ai.txt
> ```

## Database Setup
Expand All @@ -32,6 +37,14 @@ uvicorn api.main:app --reload
If successful, you will see:
`INFO: Uvicorn running on http://127.0.0.1:8000`

## Running Tests

The package is installed in editable mode by `requirements-dev.txt`, so tests can be run from the project root without setting `PYTHONPATH` manually:

```bash
pytest -q
```

## Testing Endpoints

1. Open your browser and go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs).
Expand Down
21 changes: 21 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "fireform"
version = "0.1.0"
description = "FireForm backend and PDF processing pipeline"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi",
"pdfrw",
"pydantic",
"requests",
"sqlmodel",
"uvicorn",
]

[tool.setuptools.packages.find]
include = ["api*", "src*"]
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
testpaths = tests
3 changes: 3 additions & 0 deletions requirements-ai.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
commonforms
numpy<2
ollama
3 changes: 3 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-e .
httpx
pytest
13 changes: 1 addition & 12 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1 @@
requests
pdfrw
flask
commonforms
fastapi
uvicorn
pydantic
sqlmodel
pytest
httpx
numpy<2
ollama
.
9 changes: 8 additions & 1 deletion src/file_manipulator.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
from src.filler import Filler
from src.llm import LLM
from commonforms import prepare_form


class FileManipulator:
Expand All @@ -13,6 +12,14 @@ def create_template(self, pdf_path: str):
"""
By using commonforms, we create an editable .pdf template and we store it.
"""
try:
from commonforms import prepare_form
except ImportError as exc:
raise RuntimeError(
"Template creation requires optional AI dependencies. "
"Install them with `pip install -r requirements-ai.txt`."
) from exc

template_path = pdf_path[:-4] + "_template.pdf"
prepare_form(pdf_path, template_path)
return template_path
Expand Down
39 changes: 21 additions & 18 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,37 @@

from api.main import app
from api.deps import get_db
from api.db.models import Template, FormSubmission

# In-memory SQLite database for tests
TEST_DATABASE_URL = "sqlite://"

engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
@pytest.fixture
def engine():
engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
yield engine
SQLModel.metadata.drop_all(engine)


def override_get_db():
@pytest.fixture
def db_session(engine):
with Session(engine) as session:
yield session


# Apply dependency override
app.dependency_overrides[get_db] = override_get_db

@pytest.fixture
def client(engine):
def override_get_db():
with Session(engine) as session:
yield session

@pytest.fixture(scope="session", autouse=True)
def create_test_db():
SQLModel.metadata.create_all(engine)
yield
SQLModel.metadata.drop_all(engine)
app.dependency_overrides[get_db] = override_get_db

with TestClient(app) as test_client:
yield test_client

@pytest.fixture
def client():
return TestClient(app)
app.dependency_overrides.clear()
46 changes: 26 additions & 20 deletions tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
def test_submit_form(client):
pass
# First create a template
# form_payload = {
# "template_id": 3,
# "input_text": "Hi. The employee's name is John Doe. His job title is managing director. His department supervisor is Jane Doe. His phone number is 123456. His email is jdoe@ucsc.edu. The signature is <Mamañema>, and the date is 01/02/2005",
# }
from api.db.models import Template
from src.controller import Controller

# template_res = client.post("/templates/", json=template_payload)
# template_id = template_res.json()["id"]

# # Submit a form
# form_payload = {
# "template_id": template_id,
# "data": {"rating": 5, "comment": "Great service"},
# }
def test_submit_form(client, db_session, monkeypatch):
template = Template(
name="Template 1",
fields={"Incident": "string"},
pdf_path="src/inputs/file_template.pdf",
)
db_session.add(template)
db_session.commit()
db_session.refresh(template)

# response = client.post("/forms/", json=form_payload)
monkeypatch.setattr(
Controller,
"fill_form",
lambda self, user_input, fields, pdf_form_path: "src/outputs/generated.pdf",
)

# assert response.status_code == 200
response = client.post(
"/forms/fill",
json={
"template_id": template.id,
"input_text": "Incident details",
},
)

# data = response.json()
# assert data["id"] is not None
# assert data["template_id"] == template_id
# assert data["data"] == form_payload["data"]
assert response.status_code == 200
assert response.json()["template_id"] == template.id
assert response.json()["output_pdf_path"] == "src/outputs/generated.pdf"
14 changes: 13 additions & 1 deletion tests/test_templates.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
def test_create_template(client):
import sys
import types


def test_create_template(client, monkeypatch):
monkeypatch.setitem(
sys.modules,
"commonforms",
types.SimpleNamespace(prepare_form=lambda src, dest: dest),
)

payload = {
"name": "Template 1",
"pdf_path": "src/inputs/file.pdf",
Expand All @@ -16,3 +26,5 @@ def test_create_template(client):
response = client.post("/templates/create", json=payload)

assert response.status_code == 200
assert response.json()["name"] == payload["name"]
assert response.json()["pdf_path"].endswith("_template.pdf")