From 923c119f6f0f92fdf8356429fa021fb368cf8983 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 30 Mar 2026 00:58:50 +0530 Subject: [PATCH 01/33] Optimize fill_form by removing redundant get_template calls --- api/routes/forms.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/api/routes/forms.py b/api/routes/forms.py index f3430ed..b318520 100644 --- a/api/routes/forms.py +++ b/api/routes/forms.py @@ -11,13 +11,17 @@ @router.post("/fill", response_model=FormFillResponse) def fill_form(form: FormFill, db: Session = Depends(get_db)): - if not get_template(db, form.template_id): - raise AppError("Template not found", status_code=404) - fetched_template = get_template(db, form.template_id) + if not fetched_template: + raise AppError("Template not found", status_code=404) + controller = Controller() - path = controller.fill_form(user_input=form.input_text, fields=fetched_template.fields, pdf_form_path=fetched_template.pdf_path) + path = controller.fill_form( + user_input=form.input_text, + fields=fetched_template.fields, + pdf_form_path=fetched_template.pdf_path + ) submission = FormSubmission(**form.model_dump(), output_pdf_path=path) return create_form(db, submission) From 12697bfdf2231bcbe8745b6cbbb45a189db60a5f Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 30 Mar 2026 01:17:27 +0530 Subject: [PATCH 02/33] validate-input-text --- api/routes/forms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/routes/forms.py b/api/routes/forms.py index b318520..30ce798 100644 --- a/api/routes/forms.py +++ b/api/routes/forms.py @@ -11,18 +11,17 @@ @router.post("/fill", response_model=FormFillResponse) def fill_form(form: FormFill, db: Session = Depends(get_db)): + if not form.input_text.strip(): + raise AppError("Input text cannot be empty", status_code=400) fetched_template = get_template(db, form.template_id) - if not fetched_template: raise AppError("Template not found", status_code=404) - controller = Controller() path = controller.fill_form( user_input=form.input_text, fields=fetched_template.fields, pdf_form_path=fetched_template.pdf_path ) - submission = FormSubmission(**form.model_dump(), output_pdf_path=path) return create_form(db, submission) From 2f479ca6329851486348c39cf3aa4aba131bdbd8 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 30 Mar 2026 02:31:11 +0530 Subject: [PATCH 03/33] refactor: move input_text validation to schema level --- api/routes/forms.py | 2 -- api/schemas/forms.py | 8 +++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/api/routes/forms.py b/api/routes/forms.py index 30ce798..5e18013 100644 --- a/api/routes/forms.py +++ b/api/routes/forms.py @@ -11,8 +11,6 @@ @router.post("/fill", response_model=FormFillResponse) def fill_form(form: FormFill, db: Session = Depends(get_db)): - if not form.input_text.strip(): - raise AppError("Input text cannot be empty", status_code=400) fetched_template = get_template(db, form.template_id) if not fetched_template: raise AppError("Template not found", status_code=404) diff --git a/api/schemas/forms.py b/api/schemas/forms.py index 3cce650..bf6957e 100644 --- a/api/schemas/forms.py +++ b/api/schemas/forms.py @@ -1,9 +1,15 @@ -from pydantic import BaseModel +from pydantic import BaseModel, field_validator class FormFill(BaseModel): template_id: int input_text: str + @field_validator("input_text") + def validate_input_text(cls, value): + if not value or not value.strip(): + raise ValueError("Input text cannot be empty") + return value + class FormFillResponse(BaseModel): id: int From 1138766bf3ae89591eeb2ab6981f4483f58d68bb Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Tue, 31 Mar 2026 03:59:38 +0530 Subject: [PATCH 04/33] Add centralized error handling using FastAPI exception handlers --- api/main.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/api/main.py b/api/main.py index d0b8c79..cec8000 100644 --- a/api/main.py +++ b/api/main.py @@ -1,7 +1,56 @@ from fastapi import FastAPI +from api.routes import templates, forms +from fastapi import Request +from fastapi.responses import JSONResponse +from fastapi.exceptions import RequestValidationError +from starlette.exceptions import HTTPException as StarletteHTTPException + from api.routes import templates, forms app = FastAPI() + +@app.exception_handler(StarletteHTTPException) +async def http_exception_handler(request: Request, exc: StarletteHTTPException): + return JSONResponse( + status_code=exc.status_code, + content={ + "error": { + "type": "HTTPException", + "message": exc.detail, + "details": {} + } + }, + ) + + +@app.exception_handler(RequestValidationError) +async def validation_exception_handler(request: Request, exc: RequestValidationError): + return JSONResponse( + status_code=422, + content={ + "error": { + "type": "ValidationError", + "message": "Invalid request data", + "details": exc.errors(), + } + }, + ) + + +@app.exception_handler(Exception) +async def general_exception_handler(request: Request, exc: Exception): + return JSONResponse( + status_code=500, + content={ + "error": { + "type": "InternalServerError", + "message": str(exc), + "details": {} + } + }, + ) + + app.include_router(templates.router) app.include_router(forms.router) \ No newline at end of file From c4d59ac242aca9af372765d5c72f931ae69ac449 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Tue, 31 Mar 2026 04:13:00 +0530 Subject: [PATCH 05/33] add-request-id --- api/main.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/main.py b/api/main.py index cec8000..7111bf7 100644 --- a/api/main.py +++ b/api/main.py @@ -1,3 +1,5 @@ +import uuid +from starlette.middleware.base import BaseHTTPMiddleware from fastapi import FastAPI from api.routes import templates, forms from fastapi import Request @@ -5,10 +7,19 @@ from fastapi.exceptions import RequestValidationError from starlette.exceptions import HTTPException as StarletteHTTPException -from api.routes import templates, forms - app = FastAPI() +class RequestIDMiddleware(BaseHTTPMiddleware): + async def dispatch(self, request: Request, call_next): + request_id = str(uuid.uuid4()) + request.state.request_id = request_id + response = await call_next(request) + response.headers["X-Request-ID"] = request_id + return response + + +app.add_middleware(RequestIDMiddleware) + @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request: Request, exc: StarletteHTTPException): From dad800ac15719f39ece66b6ac259da2ea3401d6f Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Wed, 1 Apr 2026 18:17:29 +0530 Subject: [PATCH 06/33] feat: add request ID middleware and structured exception handling --- api/main.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/api/main.py b/api/main.py index 7111bf7..62ba6a6 100644 --- a/api/main.py +++ b/api/main.py @@ -34,21 +34,33 @@ async def http_exception_handler(request: Request, exc: StarletteHTTPException): }, ) - @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): + formatted_errors = [] + + for err in exc.errors(): + loc = err.get("loc", []) + field = loc[-1] if loc else "unknown" + issue = err.get("msg", "Invalid value") + expected = err.get("type", "") + + formatted_errors.append({ + "field": field, + "issue": issue, + "expected": expected + }) + return JSONResponse( status_code=422, content={ "error": { "type": "ValidationError", "message": "Invalid request data", - "details": exc.errors(), + "details": formatted_errors, } }, ) - @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): return JSONResponse( From 9bdb2b0e77a6a7e286719d440a42bd25320a9a52 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Sat, 4 Apr 2026 00:43:11 +0530 Subject: [PATCH 07/33] Add prompt engineering layer for improved LLM extraction --- api/services/prompt_builder.py | 40 ++++++++++++++++++++++++++++++++++ src/llm.py | 16 +++++++++++--- 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 api/services/prompt_builder.py diff --git a/api/services/prompt_builder.py b/api/services/prompt_builder.py new file mode 100644 index 0000000..faa265f --- /dev/null +++ b/api/services/prompt_builder.py @@ -0,0 +1,40 @@ +def build_extraction_prompt(input_text: str) -> str: + return f""" +You are an AI system that extracts structured information from incident reports. + +Extract the following fields: +- name +- location +- date (YYYY-MM-DD if possible) +- incident_type +- description + +Return ONLY valid JSON. Do not include any extra text. + +Format: +{{ + "name": "", + "location": "", + "date": "", + "incident_type": "", + "description": "" +}} + +Example: + +Input: +Fire reported near Central Park on Jan 5 involving a vehicle. + +Output: +{{ + "name": "", + "location": "Central Park", + "date": "2024-01-05", + "incident_type": "fire", + "description": "Fire involving a vehicle" +}} + +Now extract from: + +{input_text} +""" \ No newline at end of file diff --git a/src/llm.py b/src/llm.py index 70937f9..8f8a848 100644 --- a/src/llm.py +++ b/src/llm.py @@ -1,7 +1,7 @@ import json import os import requests - +from api.services.prompt_builder import build_extraction_prompt class LLM: def __init__(self, transcript_text=None, target_fields=None, json=None): @@ -47,16 +47,26 @@ def build_prompt(self, current_field): def main_loop(self): # self.type_check_all() for field in self._target_fields.keys(): - prompt = self.build_prompt(field) # print(prompt) # ollama_url = "http://localhost:11434/api/generate" ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") ollama_url = f"{ollama_host}/api/generate" + base_prompt = build_extraction_prompt(self._transcript_text) + + prompt = f""" + {base_prompt} + + Focus specifically on extracting the value for this field: + {field} + + Return only the extracted value as a plain string. Do not return JSON. + """ + print("\n[DEBUG] Generated Prompt:\n", prompt) payload = { "model": "mistral", "prompt": prompt, - "stream": False, # don't really know why --> look into this later. + "stream": False, # streaming disabled; using single response mode } try: From e763a723c3e71b06acc09c03cfc95fc9113378cc Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Sat, 4 Apr 2026 01:10:22 +0530 Subject: [PATCH 08/33] llm-extraction-final --- src/llm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llm.py b/src/llm.py index 8f8a848..66004e0 100644 --- a/src/llm.py +++ b/src/llm.py @@ -62,7 +62,7 @@ def main_loop(self): Return only the extracted value as a plain string. Do not return JSON. """ - print("\n[DEBUG] Generated Prompt:\n", prompt) + payload = { "model": "mistral", "prompt": prompt, From 68d69cc0b5666dadfd3a51b09e9fa752ccbbfb00 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Sat, 11 Apr 2026 01:38:39 +0530 Subject: [PATCH 09/33] enhance: improve prompt consistency and reduce hallucinated LLM outputs --- api/services/prompt_builder.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/api/services/prompt_builder.py b/api/services/prompt_builder.py index faa265f..843c7cd 100644 --- a/api/services/prompt_builder.py +++ b/api/services/prompt_builder.py @@ -1,6 +1,13 @@ def build_extraction_prompt(input_text: str) -> str: return f""" You are an AI system that extracts structured information from incident reports. +Your task is to extract ONLY information explicitly present in the input text. + +STRICT RULES: +- Do NOT infer or guess missing information +- If a field is not clearly mentioned, return an empty string "" +- Do NOT add any extra fields beyond those specified +- Do NOT modify or reinterpret values Extract the following fields: - name @@ -9,8 +16,8 @@ def build_extraction_prompt(input_text: str) -> str: - incident_type - description -Return ONLY valid JSON. Do not include any extra text. - +Return ONLY valid JSON. Do not include any extra text, explanation, or formatting outside JSON. +The output MUST be a valid JSON object and parsable by json.loads(). Format: {{ "name": "", @@ -34,7 +41,22 @@ def build_extraction_prompt(input_text: str) -> str: "description": "Fire involving a vehicle" }} -Now extract from: +Negative Example (DO NOT DO THIS): + +Incorrect Output: +(This output is incorrect because it includes inferred/assumed values) +{{ + "location": "Central Park (assumed)", + "date": "2024-01-05" +}} + +Correct Output: +{{ + "location": "Central Park", + "date": "" +}} + +Now extract strictly from the following input (follow all rules above): {input_text} """ \ No newline at end of file From ceea4de2e75c2217ddb7020f3477b621dc269cac Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Wed, 15 Apr 2026 23:22:17 +0530 Subject: [PATCH 10/33] feat: add fallback handling for unreliable LLM outputs --- src/llm.py | 60 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/llm.py b/src/llm.py index 66004e0..025dede 100644 --- a/src/llm.py +++ b/src/llm.py @@ -3,6 +3,32 @@ import requests from api.services.prompt_builder import build_extraction_prompt +def safe_extract_value(response: str): + if not response: + return None + + response = response.strip() + + + response = response.replace('"', '').replace("'", "") + + + if ":" in response: + response = response.split(":")[-1].strip() + + + response = response.split("\n")[0] + + + if response.lower() in ["-1", "none", "null", "not found"]: + return None + + + if len(response) > 200: + return None + + return response + class LLM: def __init__(self, transcript_text=None, target_fields=None, json=None): if json is None: @@ -82,7 +108,8 @@ def main_loop(self): # parse response json_data = response.json() - parsed_response = json_data["response"] + raw_response = json_data["response"] + parsed_response = safe_extract_value(raw_response) # print(parsed_response) self.add_response_to_json(field, parsed_response) @@ -94,17 +121,18 @@ def main_loop(self): return self def add_response_to_json(self, field, value): - """ - this method adds the following value under the specified field, - or under a new field if the field doesn't exist, to the json dict - """ - value = value.strip().replace('"', "") + value = value.strip().replace('"', "") if value else None parsed_value = None - if value != "-1": + if value: parsed_value = value + else: + parsed_value = { + "value": None, + "requires_review": True + } - if ";" in value: + if value and ";" in value: parsed_value = self.handle_plural_values(value) if field in self._json.keys(): @@ -114,30 +142,20 @@ def add_response_to_json(self, field, value): return + def handle_plural_values(self, plural_value): """ - This method handles plural values. - Takes in strings of the form 'value1; value2; value3; ...; valueN' - returns a list with the respective values -> [value1, value2, value3, ..., valueN] + This method handles plural values. """ if ";" not in plural_value: raise ValueError( f"Value is not plural, doesn't have ; separator, Value: {plural_value}" ) - print( - f"\t[LOG]: Formating plural values for JSON, [For input {plural_value}]..." - ) values = plural_value.split(";") - # Remove trailing leading whitespace for i in range(len(values)): - current = i + 1 - if current < len(values): - clean_value = values[current].lstrip() - values[current] = clean_value - - print(f"\t[LOG]: Resulting formatted list of values: {values}") + values[i] = values[i].strip() return values From 8396a3a0404809bca29e413983f8d671b965c3c7 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Fri, 17 Apr 2026 19:41:42 +0530 Subject: [PATCH 11/33] feat: add basic schema validation layer for extraction outputs --- src/llm.py | 8 +++++++- src/main.py | 3 ++- src/validation.py | 29 +++++++++++++++++++++++++++++ temp_outfile.pdf | Bin 0 -> 74397 bytes 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/validation.py create mode 100644 temp_outfile.pdf diff --git a/src/llm.py b/src/llm.py index 025dede..ab4cbca 100644 --- a/src/llm.py +++ b/src/llm.py @@ -2,6 +2,7 @@ import os import requests from api.services.prompt_builder import build_extraction_prompt +from src.validation import validate_extraction def safe_extract_value(response: str): if not response: @@ -160,4 +161,9 @@ def handle_plural_values(self, plural_value): return values def get_data(self): - return self._json + validated_data, errors = validate_extraction(self._json) + + if errors: + print("[Validation Warning]", errors) + + return validated_data diff --git a/src/main.py b/src/main.py index 5bb632b..b2266ea 100644 --- a/src/main.py +++ b/src/main.py @@ -2,7 +2,8 @@ # from backend import Fill from commonforms import prepare_form from pypdf import PdfReader -from controller import Controller +from typing import Union +from src.controller import Controller def input_fields(num_fields: int): fields = [] diff --git a/src/validation.py b/src/validation.py new file mode 100644 index 0000000..894ac33 --- /dev/null +++ b/src/validation.py @@ -0,0 +1,29 @@ +from pydantic import BaseModel, ValidationError +from typing import Optional + + +class ExtractionSchema(BaseModel): + name: Optional[str] + location: Optional[str] + date: Optional[str] + incident_type: Optional[str] + description: Optional[str] + + class Config: + extra = "allow" + + +def validate_extraction(data: dict): + try: + validated = ExtractionSchema(**data) + return validated.dict(), None + except ValidationError as e: + formatted_errors = [] + + for err in e.errors(): + formatted_errors.append({ + "field": err.get("loc", ["unknown"])[0], + "issue": err.get("msg", "Invalid value") + }) + + return data, formatted_errors \ No newline at end of file diff --git a/temp_outfile.pdf b/temp_outfile.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f0821686fc7e65c5fe45ff6741f0157af3dd9309 GIT binary patch literal 74397 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 literal 0 HcmV?d00001 From 39b36db8ccad66ebbf95316ee528b8a803264295 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Sun, 19 Apr 2026 03:32:33 +0530 Subject: [PATCH 12/33] fix: update PR template test configuration fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace generic Firmware/Hardware/SDK fields with Python version, Docker/Compose version, and OS — relevant to this project. Fixes #458 --- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index cb28132..f7aa899 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -21,9 +21,9 @@ Please describe the tests that you ran to verify your changes. Provide instructi - [ ] Test B **Test Configuration**: -* Firmware version: -* Hardware: -* SDK: +* Python version: +* Docker/Compose version: +* OS: ## Checklist: From 70a8e383ea70ea9c805e2e7056d701904d7392e3 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 18:43:35 -0700 Subject: [PATCH 13/33] ci: add GitHub Actions for testing, linting, and Docker build --- .github/workflows/docker-build.yml | 18 +++++++++++++++++ .github/workflows/lint.yml | 29 ++++++++++++++++++++++++++++ .github/workflows/tests.yml | 31 ++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..39bd9d8 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,18 @@ +name: Docker Build + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + run: | + docker build -t fireform:test . \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..93d41fb --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: Lint + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + continue-on-error: true + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install linter + run: | + python -m pip install --upgrade pip + pip install ruff + + - name: Run linter + run: | + ruff check src/ --output-format=github \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..57ae5d7 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,31 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + + - name: Run tests + env: + PYTHONPATH: src + run: | + python -m pytest src/test/ -v --tb=short --ignore=src/test/test_model.py \ No newline at end of file From 4e381c22b4f34692bbe34ffd42d2e0d0cc74df36 Mon Sep 17 00:00:00 2001 From: Marc Verges Date: Wed, 11 Mar 2026 12:06:16 -0700 Subject: [PATCH 14/33] fix: :adhesive_bandage: issue #34 done --- requirements.txt | 1 + src/main.py | 81 ++++++++---------------------------------------- 2 files changed, 14 insertions(+), 68 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9e4c0b3..8e39eb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ httpx numpy<2 ollama python-multipart +pypdf diff --git a/src/main.py b/src/main.py index b2266ea..c6921b8 100644 --- a/src/main.py +++ b/src/main.py @@ -1,84 +1,29 @@ -import os -# from backend import Fill -from commonforms import prepare_form +from commonforms import prepare_form from pypdf import PdfReader from typing import Union from src.controller import Controller -def input_fields(num_fields: int): - fields = [] - for i in range(num_fields): - field = input(f"Enter description for field {i + 1}: ") - fields.append(field) - return fields - -def run_pdf_fill_process(user_input: str, definitions: list, pdf_form_path: Union[str, os.PathLike]): - """ - This function is called by the frontend server. - It receives the raw data, runs the PDF filling logic, - and returns the path to the newly created file. - """ - - print("[1] Received request from frontend.") - print(f"[2] PDF template path: {pdf_form_path}") - - # Normalize Path/PathLike to a plain string for downstream code - pdf_form_path = os.fspath(pdf_form_path) - - if not os.path.exists(pdf_form_path): - print(f"Error: PDF template not found at {pdf_form_path}") - return None # Or raise an exception - - print("[3] Starting extraction and PDF filling process...") - try: - output_name = Fill.fill_form( - user_input=user_input, - definitions=definitions, - pdf_form=pdf_form_path - ) - - print("\n----------------------------------") - print(f"✅ Process Complete.") - print(f"Output saved to: {output_name}") - - return output_name - - except Exception as e: - print(f"An error occurred during PDF generation: {e}") - # Re-raise the exception so the frontend can handle it - raise e - - -# if __name__ == "__main__": -# file = "./src/inputs/file.pdf" -# user_input = "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 , and the date is 01/02/2005" -# fields = ["Employee's name", "Employee's job title", "Employee's department supervisor", "Employee's phone number", "Employee's email", "Signature", "Date"] -# prepared_pdf = "temp_outfile.pdf" -# prepare_form(file, prepared_pdf) - -# reader = PdfReader(prepared_pdf) -# fields = reader.get_fields() -# if(fields): -# num_fields = len(fields) -# else: -# num_fields = 0 -# #fields = input_fields(num_fields) # Uncomment to edit fields - -# run_pdf_fill_process(user_input, fields, file) - if __name__ == "__main__": file = "./src/inputs/file.pdf" user_input = "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 , and the date is 01/02/2005" - fields = ["Employee's name", "Employee's job title", "Employee's department supervisor", "Employee's phone number", "Employee's email", "Signature", "Date"] + fields = [ + "Employee's name", + "Employee's job title", + "Employee's department supervisor", + "Employee's phone number", + "Employee's email", + "Signature", + "Date", + ] prepared_pdf = "temp_outfile.pdf" prepare_form(file, prepared_pdf) - + reader = PdfReader(prepared_pdf) fields = reader.get_fields() - if(fields): + if fields: num_fields = len(fields) else: num_fields = 0 - + controller = Controller() controller.fill_form(user_input, fields, file) From 6f40a8cfb31d0b834e55ee8a8f6d824177f3892f Mon Sep 17 00:00:00 2001 From: Marc Verges Date: Wed, 11 Mar 2026 12:28:24 -0700 Subject: [PATCH 15/33] fix: :bug: improve gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7fa2022..2f5f8ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .idea venv .venv -*.db \ No newline at end of file +*.db +.vscode \ No newline at end of file From 4d47685a6c5d2d3a29bb924552a7ffc2519c534d Mon Sep 17 00:00:00 2001 From: Marc Verges Date: Wed, 11 Mar 2026 14:53:51 -0700 Subject: [PATCH 16/33] docs: :memo: architecture.md --- docs/architecture.md | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/architecture.md diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..44b9bb5 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,61 @@ +# FireForm Architecture + +FireForm is a system designed to automate the process of filling out PDF forms using information extracted from transcriptions (e.g., voice recordings) via an LLM. + +## Pipeline Description + +The FireForm pipeline follows a structured flow from receiving raw user input to generating a finalized, filled PDF document. + +### High-Level Flow + +1. **Template Preparation**: A standard PDF is processed to ensure it has interactive form fields. +2. **Input Acquisition**: The system receives a transcript (text) and a list of target fields to extract. +3. **LLM Extraction**: An LLM (via Ollama) processes the transcript to find values for each target field. +4. **Form Filling**: extracted values are mapped and written into the PDF template. +5. **Output Generation**: A new, filled PDF is saved with a timestamped filename. + +### Component Interaction + +```mermaid +sequenceDiagram + participant U as User/Frontend + participant FM as FileManipulator + participant LLM as LLM Module + participant O as Ollama (Mistral) + participant F as Filler + participant CF as CommonForms/pypdf + + U->>FM: fill_form(transcript, fields, pdf_path) + FM->>LLM: Set target_fields & transcript + FM->>F: fill_form(pdf_path, llm) + + activate F + F->>LLM: main_loop() + + activate LLM + loop For each field + LLM->>LLM: build_prompt(field) + LLM->>O: POST /api/generate + O-->>LLM: Extracted Value + LLM->>LLM: add_response_to_json(field, value) + end + LLM-->>F: Resulting JSON Data + deactivate LLM + + F->>CF: Read PDF Template + F->>F: Sort Annotations (Visual Order) + F->>CF: Write values to Widgets + F->>CF: Write Filled PDF + F-->>FM: Path to filled PDF + deactivate F + + FM-->>U: Final PDF Path +``` + +### Key Components + +- **`FileManipulator`**: The high-level orchestrator that manages the overall process. +- **`LLM`**: Handles prompt engineering and communication with the local Ollama instance (typically using the Mistral model). It extracts structured data from unstructured text. +- **`Filler`**: Handles the low-level PDF manipulation, ensuring that extracted data is placed into the correct form fields based on their visual order. +- **`commonforms`**: A dependency used to prepare standard PDFs for filling. +- **`pdfrw` / `pypdf`**: Libraries used for reading and writing PDF metadata and form field values. From 08de8c6075821935213acda2e8944dd6d112daec Mon Sep 17 00:00:00 2001 From: Marc Verges Date: Wed, 11 Mar 2026 14:54:44 -0700 Subject: [PATCH 17/33] refactor: :recycle: duplicate query to db --- api/routes/forms.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/routes/forms.py b/api/routes/forms.py index 5e18013..6546546 100644 --- a/api/routes/forms.py +++ b/api/routes/forms.py @@ -9,18 +9,18 @@ router = APIRouter(prefix="/forms", tags=["forms"]) + @router.post("/fill", response_model=FormFillResponse) def fill_form(form: FormFill, db: Session = Depends(get_db)): fetched_template = get_template(db, form.template_id) if not fetched_template: raise AppError("Template not found", status_code=404) - controller = Controller() - path = controller.fill_form( - user_input=form.input_text, - fields=fetched_template.fields, - pdf_form_path=fetched_template.pdf_path - ) - submission = FormSubmission(**form.model_dump(), output_pdf_path=path) - return create_form(db, submission) - + controller = Controller() +path = controller.fill_form( + user_input=form.input_text, + fields=fetched_template.fields, + pdf_form_path=fetched_template.pdf_path +) +submission = FormSubmission(**form.model_dump(), output_pdf_path=path) +return create_form(db, submission) From 3f13854039a4da60ff98cf8d83e163d1d4c32ae0 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 19:19:40 -0700 Subject: [PATCH 18/33] refactor: :zap: remove possible other latent tasks of ollama --- src/file_manipulator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/file_manipulator.py b/src/file_manipulator.py index b7815cc..3719359 100644 --- a/src/file_manipulator.py +++ b/src/file_manipulator.py @@ -14,6 +14,10 @@ def create_template(self, pdf_path: str): By using commonforms, we create an editable .pdf template and we store it. """ template_path = pdf_path[:-4] + "_template.pdf" + + os.system("taskkill /F /IM ollama.exe >nul 2>&1") + print("Cleared existing Ollama instances. Starting fresh...") + prepare_form(pdf_path, template_path) return template_path From 93d9c70d4200e42b8e68545b34014e7de334d470 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 19:43:41 -0700 Subject: [PATCH 19/33] fix: :adhesive_bandage: issue #8 done --- src/filler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filler.py b/src/filler.py index e31e535..7f738c2 100644 --- a/src/filler.py +++ b/src/filler.py @@ -29,13 +29,13 @@ def fill_form(self, pdf_form: str, llm: LLM): pdf = PdfReader(pdf_form) # Loop through pages + i = 0 for page in pdf.pages: if page.Annots: sorted_annots = sorted( page.Annots, key=lambda a: (-float(a.Rect[1]), float(a.Rect[0])) ) - i = 0 for annot in sorted_annots: if annot.Subtype == "/Widget" and annot.T: if i < len(answers_list): From 4d70868c92f24e809df899b88f4ed491a136e2a4 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 21 Feb 2026 17:31:40 +0530 Subject: [PATCH 20/33] security: restrict Ollama port exposure to localhost --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d8064b6..074d0df 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,7 +3,7 @@ services: image: ollama/ollama:latest container_name: fireform-ollama ports: - - "11434:11434" + - "127.0.0.1:11434:11434" volumes: - ollama_data:/root/.ollama networks: From 626c7455aa7bee00132bd07edc19ec8dbd83a39a Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 20:03:12 -0700 Subject: [PATCH 21/33] feat: :sparkles: addded a new template when the db is created --- api/db/init_db.py | 40 +++++++++++++++++++++++++++- api/main.py | 16 ++++++++++- api/routes/__init__.py | 1 + src/inputs/file_template.pdf | Bin 0 -> 74414 bytes src/inputs/file_template_manual.pdf | Bin 0 -> 65350 bytes 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 src/inputs/file_template.pdf create mode 100644 src/inputs/file_template_manual.pdf diff --git a/api/db/init_db.py b/api/db/init_db.py index 9ad27ea..ea77cb6 100644 --- a/api/db/init_db.py +++ b/api/db/init_db.py @@ -1,9 +1,47 @@ -from sqlmodel import SQLModel +import json +import datetime +from sqlmodel import SQLModel, Session, select from api.db.database import engine from api.db import models +from api.db.models import Template + +def seed_db(): + with Session(engine) as session: + # Check if we already have templates + statement = select(Template) + try: + results = session.exec(statement).first() + except Exception: + # Table might not exist yet if called at a weird time + results = None + + if not results: + print("Seeding database with default template...") + fields = { + "Employee's name": "string", + "Employee's job title": "string", + "Employee's department supervisor": "string", + "Employee's phone number": "string", + "Employee's email": "string", + "Signature": "string", + "Date": "string" + } + + # Using ID 2 as agreed to avoid any ID 1 corruption + default_template = Template( + id=2, + name="Manual Test Template", + fields=fields, + pdf_path="src/inputs/file_template_manual.pdf", + created_at=datetime.datetime.now() + ) + session.add(default_template) + session.commit() + print("Database seeded successfully.") def init_db(): SQLModel.metadata.create_all(engine) + seed_db() if __name__ == "__main__": init_db() \ No newline at end of file diff --git a/api/main.py b/api/main.py index 7d81ef6..fce7f03 100644 --- a/api/main.py +++ b/api/main.py @@ -1,11 +1,25 @@ + import os from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from api.routes import forms, templates +from contextlib import asynccontextmanager +from fastapi import FastAPI +from api.routes import templates, forms +from api.db.init_db import init_db + + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup: Initialize the database and seed it if necessary + print("Initializing database...") + init_db() + yield + # Shutdown logic goes here if needed -app = FastAPI() +app = FastAPI(lifespan=lifespan) default_origins = "http://127.0.0.1:5173" allowed_origins = [ diff --git a/api/routes/__init__.py b/api/routes/__init__.py index e69de29..e8fe8f4 100644 --- a/api/routes/__init__.py +++ b/api/routes/__init__.py @@ -0,0 +1 @@ +from . import templates, forms diff --git a/src/inputs/file_template.pdf b/src/inputs/file_template.pdf new file mode 100644 index 0000000000000000000000000000000000000000..67af4c9527e3e006eec8bc25f0c968ff85104980 GIT binary patch literal 74414 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 literal 0 HcmV?d00001 diff --git a/src/inputs/file_template_manual.pdf b/src/inputs/file_template_manual.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2ebae13d6b120accada94e5bc0a2e105ac682afa GIT binary patch literal 65350 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 SOoFCZ%od9#5P%<1+y4OtI*RiE literal 0 HcmV?d00001 From 03fd3bc4f456cf0d287d8079a8ecc7a00852cfb3 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 20:11:44 -0700 Subject: [PATCH 22/33] fix: :adhesive_bandage: issue #20 done --- src/llm.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/llm.py b/src/llm.py index ab4cbca..18017ce 100644 --- a/src/llm.py +++ b/src/llm.py @@ -3,6 +3,7 @@ import requests from api.services.prompt_builder import build_extraction_prompt from src.validation import validate_extraction +from requests.exceptions import Timeout, RequestException def safe_extract_value(response: str): if not response: @@ -72,6 +73,9 @@ def build_prompt(self, current_field): return prompt def main_loop(self): + timeout = 30 + max_retries = 3 + # self.type_check_all() for field in self._target_fields.keys(): # print(prompt) @@ -96,9 +100,18 @@ def main_loop(self): "stream": False, # streaming disabled; using single response mode } + json_data = None try: - response = requests.post(ollama_url, json=payload) - response.raise_for_status() + for attempt in range(max_retries): + try: + response = requests.post(ollama_url, json=payload, timeout=timeout) + response.raise_for_status() + json_data = response.json() + break + except Timeout: + print(f"Ollama request timed out (attempt {attempt+1})") + except RequestException as e: + print(f"Ollama request failed: {e}") except requests.exceptions.ConnectionError: raise ConnectionError( f"Could not connect to Ollama at {ollama_url}. " @@ -107,12 +120,15 @@ def main_loop(self): except requests.exceptions.HTTPError as e: raise RuntimeError(f"Ollama returned an error: {e}") - # parse response - json_data = response.json() - raw_response = json_data["response"] - parsed_response = safe_extract_value(raw_response) - # print(parsed_response) - self.add_response_to_json(field, parsed_response) + + if json_data is None: + raise RuntimeError("Failed to get response from Ollama after retries.") + else: + # parse response + parsed_response = json_data["response"] + # print(parsed_response) + self.add_response_to_json(field, parsed_response) + print("----------------------------------") print("\t[LOG] Resulting JSON created from the input text:") From cdb67e70e33742da053df5bcd19ca2c8f71f91a9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 22 Feb 2026 09:35:45 +0530 Subject: [PATCH 23/33] fix: logic error in handle_plural_values skipping first item --- src/backend.py | 171 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/backend.py diff --git a/src/backend.py b/src/backend.py new file mode 100644 index 0000000..84d937c --- /dev/null +++ b/src/backend.py @@ -0,0 +1,171 @@ +import json +import os +import requests +from json_manager import JsonManager +from input_manager import InputManager +from pdfrw import PdfReader, PdfWriter + + + +class textToJSON(): + def __init__(self, transcript_text, target_fields, json={}): + self.__transcript_text = transcript_text # str + self.__target_fields = target_fields # List, contains the template field. + self.__json = json # dictionary + self.type_check_all() + self.main_loop() + + + def type_check_all(self): + if type(self.__transcript_text) != str: + raise TypeError(f"ERROR in textToJSON() ->\ + Transcript must be text. Input:\n\ttranscript_text: {self.__transcript_text}") + elif type(self.__target_fields) != list: + raise TypeError(f"ERROR in textToJSON() ->\ + Target fields must be a list. Input:\n\ttarget_fields: {self.__target_fields}") + + + def build_prompt(self, current_field): + """ + This method is in charge of the prompt engineering. It creates a specific prompt for each target field. + @params: current_field -> represents the current element of the json that is being prompted. + """ + prompt = f""" + SYSTEM PROMPT: + You are an AI assistant designed to help fillout json files with information extracted from transcribed voice recordings. + You will receive the transcription, and the name of the JSON field whose value you have to identify in the context. Return + only a single string containing the identified value for the JSON field. + If the field name is plural, and you identify more than one possible value in the text, return both separated by a ";". + If you don't identify the value in the provided text, return "-1". + --- + DATA: + Target JSON field to find in text: {current_field} + + TEXT: {self.__transcript_text} + """ + + return prompt + + def main_loop(self): #FUTURE -> Refactor this to its own class + for field in self.__target_fields: + prompt = self.build_prompt(field) + # print(prompt) + # ollama_url = "http://localhost:11434/api/generate" + ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") + ollama_url = f"{ollama_host}/api/generate" + + payload = { + "model": "mistral", + "prompt": prompt, + "stream": False # don't really know why --> look into this later. + } + + response = requests.post(ollama_url, json=payload) + + # parse response + json_data = response.json() + parsed_response = json_data['response'] + # print(parsed_response) + self.add_response_to_json(field, parsed_response) + + print("----------------------------------") + print("\t[LOG] Resulting JSON created from the input text:") + print(json.dumps(self.__json, indent=2)) + print("--------- extracted data ---------") + + return None + + def add_response_to_json(self, field, value): + """ + this method adds the following value under the specified field, + or under a new field if the field doesn't exist, to the json dict + """ + value = value.strip().replace('"', '') + parsed_value = None + plural = False + + if value != "-1": + parsed_value = value + + if ";" in value: + parsed_value = self.handle_plural_values(value) + plural = True + + + if field in self.__json.keys(): + self.__json[field].append(parsed_value) + else: + self.__json[field] = parsed_value + + return + + def handle_plural_values(self, plural_value): + """ + This method handles plural values. + Takes in strings of the form 'value1; value2; value3; ...; valueN' + returns a list with the respective values -> [value1, value2, value3, ..., valueN] + """ + if ";" not in plural_value: + raise ValueError(f"Value is not plural, doesn't have ; separator, Value: {plural_value}") + + print(f"\t[LOG]: Formating plural values for JSON, [For input {plural_value}]...") + values = plural_value.split(";") + + # Remove trailing leading whitespace + for i in range(len(values)): + values[i] = values[i].lstrip() + + print(f"\t[LOG]: Resulting formatted list of values: {values}") + + return values + + + def get_data(self): + return self.__json + +class Fill(): + def __init__(self): + pass + + def fill_form(user_input: str, definitions: list, pdf_form: str): + """ + Fill a PDF form with values from user_input using testToJSON. + Fields are filled in the visual order (top-to-bottom, left-to-right). + """ + + output_pdf = pdf_form[:-4] + "_filled.pdf" + + # Generate dictionary of answers from your original function + t2j = textToJSON(user_input, definitions) + textbox_answers = t2j.get_data() # This is a dictionary + + answers_list = list(textbox_answers.values()) + + # Read PDF + pdf = PdfReader(pdf_form) + + # Loop through pages + for page in pdf.pages: + if page.Annots: + sorted_annots = sorted( + page.Annots, + key=lambda a: (-float(a.Rect[1]), float(a.Rect[0])) + ) + + i = 0 + for annot in sorted_annots: + if annot.Subtype == '/Widget' and annot.T: + field_name = annot.T[1:-1] + + if i < len(answers_list): + annot.V = f'{answers_list[i]}' + annot.AP = None + i += 1 + else: + # Stop if we run out of answers + break + + PdfWriter().write(output_pdf, pdf) + + # Your main.py expects this function to return the path + return output_pdf From d3d430df9aff7b01e4dacd3e1ee6840be6fae9d6 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 21:01:59 -0700 Subject: [PATCH 24/33] fix: :adhesive_bandage: issue #82 done --- api/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/main.py b/api/main.py index fce7f03..8d4a5f7 100644 --- a/api/main.py +++ b/api/main.py @@ -9,6 +9,7 @@ from fastapi import FastAPI from api.routes import templates, forms from api.db.init_db import init_db +from api.errors.handlers import register_exception_handlers @asynccontextmanager @@ -35,6 +36,7 @@ async def lifespan(app: FastAPI): allow_methods=["*"], allow_headers=["*"], ) +register_exception_handlers(app) app.include_router(templates.router) app.include_router(forms.router) From 4c1327664c618cf78d2cbbbd98f45a0919d780f4 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Wed, 15 Apr 2026 21:39:20 -0700 Subject: [PATCH 25/33] feat: :sparkles: Add real-time console progress logging during LLM extraction #132 --- src/llm.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/llm.py b/src/llm.py index 18017ce..24af2d3 100644 --- a/src/llm.py +++ b/src/llm.py @@ -77,7 +77,9 @@ def main_loop(self): max_retries = 3 # self.type_check_all() - for field in self._target_fields.keys(): + total_fields = len(self._target_fields) + for i, field in enumerate(self._target_fields.keys(), 1): + prompt = self.build_prompt(field) # print(prompt) # ollama_url = "http://localhost:11434/api/generate" ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") @@ -128,6 +130,7 @@ def main_loop(self): parsed_response = json_data["response"] # print(parsed_response) self.add_response_to_json(field, parsed_response) + print(f"[{i}/{total_fields}] Extracted data for field '{field}' successfully.") print("----------------------------------") From 29182ec2ac60cf948cf11c1643ee65a75b10d3c8 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 18:25:58 -0700 Subject: [PATCH 26/33] fix: :adhesive_bandage: docker fix --- Dockerfile | 19 +++- Makefile | 19 +++- docker-compose.yml | 12 +- docs/DEPLOYMENT.md | 240 ++++++++++++++++++++++++++++++++++++++++ src/file_manipulator.py | 4 +- 5 files changed, 282 insertions(+), 12 deletions(-) create mode 100644 docs/DEPLOYMENT.md diff --git a/Dockerfile b/Dockerfile index 833fcc3..60392a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,15 @@ FROM python:3.11-slim WORKDIR /app # Install system dependencies +# Fixes #275 #191 #184 — libGL and libglib2 required by faster-whisper / OpenCV +# Fixes #53 — libxcb1 missing from python:3.11-slim base image +# ffmpeg required by faster-whisper for audio processing RUN apt-get update && apt-get install -y \ curl \ + ffmpeg \ + libgl1 \ + libglib2.0-0 \ + libxcb1 \ && rm -rf /var/lib/apt/lists/* # Copy and install Python dependencies @@ -15,8 +22,12 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . -# Set Python path so imports work correctly -ENV PYTHONPATH=/app/src +# Fix #118 #116 — PYTHONPATH must be /app (project root), not /app/src +# All imports use api.*, src.* which require the root to be on the path +ENV PYTHONPATH=/app -# Keep container running for interactive use -CMD ["tail", "-f", "/dev/null"] +# Expose FastAPI port +EXPOSE 8000 + +# Start the FastAPI server (not tail -f /dev/null which does nothing) +CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/Makefile b/Makefile index 567bff5..f01d3ea 100644 --- a/Makefile +++ b/Makefile @@ -26,9 +26,15 @@ help: @echo "make clean - Remove containers" @echo "make super-clean - [CAUTION] Use carefully. Cleans up ALL stopped containers, networks, build cache..." -fireform: build up - @echo "Launching interactive shell in the app container..." - docker compose exec app /bin/bash +# Fix #382 — pull-model is now part of the main setup flow +# Mistral is pulled automatically before you need it +fireform: build up pull-model + @echo "" + @echo "✅ FireForm is ready!" + @echo " API: http://localhost:8000" + @echo " API Docs: http://localhost:8000/docs" + @echo "" + @echo "Run 'make logs' to view live logs, 'make down' to stop." build: docker compose build @@ -54,14 +60,19 @@ logs-frontend: shell: docker compose exec app /bin/bash +# Start the FastAPI server inside the running container +run: + docker compose exec app uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload + exec: docker compose exec app python3 src/main.py pull-model: docker compose exec ollama ollama pull mistral +# Fix — correct test directory (was src/test/ which doesn't exist) test: - docker compose exec app python3 -m pytest src/test/ + docker compose exec app python3 -m pytest tests/ -v clean: docker compose down -v diff --git a/docker-compose.yml b/docker-compose.yml index 074d0df..a25789d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,10 +9,10 @@ services: networks: - fireform-network healthcheck: - test: ["CMD-SHELL", "ollama ps"] + test: ["CMD-SHELL", "ollama list || exit 1"] interval: 10s timeout: 5s - retries: 3 + retries: 5 start_period: 30s app: @@ -24,14 +24,20 @@ services: ollama: condition: service_healthy command: /bin/sh -c "python3 -m api.db.init_db && python3 -m uvicorn api.main:app --host 0.0.0.0 --port 8000" + # Fix #224 — expose port so API is reachable at http://localhost:8000 + ports: + - "8000:8000" volumes: - .:/app ports: - "8000:8000" environment: - PYTHONUNBUFFERED=1 - - PYTHONPATH=/app/src + - CUDA_VISIBLE_DEVICES="" + # Fix #118 #116 — correct PYTHONPATH to project root + - PYTHONPATH=/app - OLLAMA_HOST=http://ollama:11434 + - OLLAMA_TIMEOUT=300 networks: - fireform-network diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..69d0602 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,240 @@ +# FireForm — Deployment Guide + +This guide covers deploying FireForm using Docker. By the end, you will have the full +FireForm stack (FastAPI server + Ollama AI engine) running on any machine with a single command. + +--- + +## Prerequisites + +| Requirement | Version | Install | +|------------|---------|---------| +| Docker Desktop | 26.x or newer | [docker.com/get-started](https://www.docker.com/get-started/) | +| WSL2 (Windows only) | latest | `wsl --update` in PowerShell as Admin | +| Git | any | [git-scm.com](https://git-scm.com/) | +| Disk space | ~6GB free | For Docker image + Mistral model (~4GB) | +| RAM | 8GB minimum | 16GB recommended for smooth LLM inference | + +--- + +## Quick Start (Recommended) + +```bash +# 1. Clone the repository +git clone https://github.com/fireform-core/FireForm +cd FireForm + +# 2. Build and start everything +docker compose build +docker compose up -d + +# 3. Pull the AI model (one-time, ~4GB download) +docker compose exec ollama ollama pull mistral + +# 4. Open FireForm +# API + Swagger docs: http://localhost:8000/docs +# Web interface: http://localhost:8000 +``` + +--- + +## What Runs Inside Docker + +| Container | Purpose | Port | +|-----------|---------|------| +| `fireform-app` | FastAPI server — handles all API routes, PDF filling, Data Lake | `8000` | +| `fireform-ollama` | Ollama AI engine — serves Mistral for LLM extraction and semantic mapping | `11434` | + +The two containers communicate internally over `fireform-network`. You only interact with port `8000`. + +--- + +## Make Commands Reference + +```bash +make fireform # Build + start + pull Mistral model (full setup) +make build # Build Docker images only +make up # Start all containers (background) +make down # Stop all containers +make logs # View live logs from all containers +make logs-app # View live logs from FastAPI app only +make logs-ollama # View live logs from Ollama only +make shell # Open bash shell inside the app container +make run # Start the FastAPI server inside the running container +make pull-model # Pull the Mistral model into Ollama +make test # Run the full test suite inside the container +make clean # Stop containers and remove volumes +make super-clean # [CAUTION] Remove all containers, networks, and build cache +make help # Show all commands with descriptions +``` + +--- + +## Environment Variables + +Set these in `docker-compose.yml` under `app > environment`, or pass via `.env` file: + +| Variable | Default | Description | +|----------|---------|-------------| +| `OLLAMA_HOST` | `http://ollama:11434` | URL of the Ollama service (internal Docker network) | +| `OLLAMA_TIMEOUT` | `300` | Seconds to wait for LLM response before timeout | +| `FIREFORM_MODEL` | `mistral` | LLM model to use (e.g. `mistral`, `llama3`, `llava`) | +| `PYTHONUNBUFFERED` | `1` | Ensures real-time log output | +| `PYTHONPATH` | `/app` | Project root — required for all `api.*` and `src.*` imports | + +### Using a Different Model + +```bash +# In docker-compose.yml, change: +- FIREFORM_MODEL=mistral +# To any Ollama-supported model: +- FIREFORM_MODEL=llama3 + +# Then pull the new model: +docker compose exec ollama ollama pull llama3 +``` + +--- + +## Verifying the Deployment + +### 1. Check containers are running +```bash +docker ps +``` +Expected output: +``` +CONTAINER ID IMAGE PORTS NAMES +xxxxxxxxxxxx fireform-app 0.0.0.0:8000->8000/tcp fireform-app +xxxxxxxxxxxx ollama/ollama:latest 0.0.0.0:11434->11434/tcp fireform-ollama +``` + +### 2. Check app started correctly +```bash +docker compose logs app +``` +Look for: +``` +INFO: Application startup complete. +INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) +``` + +### 3. Test the API +```bash +curl http://localhost:8000/ +``` +Or open **http://localhost:8000/docs** in your browser — Swagger UI should load. + +### 4. Run the test suite +```bash +make test +``` +All 83+ tests should pass. + +--- + +## Troubleshooting + +### Container exits immediately / `Application startup complete` never appears +```bash +docker compose logs app +``` +Read the error. Common causes: + +| Error message | Cause | Fix | +|---------------|-------|-----| +| `ModuleNotFoundError: No module named 'X'` | Missing package in `requirements.txt` | Add the package and rebuild | +| `RuntimeError: Form data requires python-multipart` | `python-multipart` missing | Already fixed in this PR | +| `cannot import name 'X' from 'api.routes'` | Wrong `PYTHONPATH` | Ensure `PYTHONPATH=/app` in `docker-compose.yml` | + +### Port 8000 already in use +```bash +# Find what's using port 8000 +netstat -ano | findstr :8000 # Windows +lsof -i :8000 # Mac/Linux + +# Or change the port in docker-compose.yml: +ports: + - "8080:8000" # Use localhost:8080 instead +``` + +### Ollama container unhealthy / never starts +The `ollama/ollama` image does not include system utilities like `curl` or `wget`. +Do not add a `healthcheck` that depends on these. The app uses `OLLAMA_TIMEOUT=300` +to wait for Ollama to be ready at the application level. + +### LLM calls fail / timeout +```bash +# Verify Mistral is pulled +docker compose exec ollama ollama list +# Should show: mistral:latest + +# If not, pull it: +docker compose exec ollama ollama pull mistral +``` + +### Make command not found (Windows PowerShell) +`make` is not available in Windows PowerShell by default. +Use **Git Bash** or run the underlying `docker compose` commands directly: +```bash +# Instead of: make fireform +docker compose build +docker compose up -d +docker compose exec ollama ollama pull mistral +``` + +--- + +## Production Deployment (Station Intranet) + +For deployment on a Linux station server: + +```bash +# Clone and configure +git clone https://github.com/fireform-core/FireForm +cd FireForm + +# Start services +docker compose up -d +docker compose exec ollama ollama pull mistral + +# FireForm is now accessible on the station intranet at: +# http://:8000 +``` + +**HTTPS Note:** Service Workers (PWA offline mode) require HTTPS on non-localhost connections. +Most fire and police departments operate their own intranet HTTPS/SSL infrastructure — +point your department's reverse proxy (nginx/Apache) to port 8000 to enable PWA installation +on field devices without requiring cloud services or app store distribution. + +--- + +## Architecture Overview + +``` +Field Devices (PWA) Station Server (Docker) +──────────────────── ────────────────────────────────── +Officer Mobile ──────────► FastAPI [:8000] +Station Desktop ──────────► ↓ LLM extraction +Field Tablet ──────────► Ollama/Mistral [:11434] + ↓ Structured JSON + Master Data Lake (SQLite/PostgreSQL) + ↓ AI Semantic Mapping + PDF Filler (PyMuPDF) + ↓ + Filled PDF → Officer download +``` + +--- + +## Known Limitations + +- **SQLite** is used by default (single-file database). For multi-station production use, + migrate to PostgreSQL by updating `SQLMODEL_DATABASE_URL` in the environment. +- **Model download (~4GB)** is required on first run. Subsequent starts use the cached model. +- **CPU inference** is used by default. On machines with NVIDIA GPUs, Ollama automatically + uses CUDA for faster inference — no configuration required. + +--- + + diff --git a/src/file_manipulator.py b/src/file_manipulator.py index 3719359..e499c89 100644 --- a/src/file_manipulator.py +++ b/src/file_manipulator.py @@ -1,7 +1,6 @@ import os from src.filler import Filler from src.llm import LLM -from commonforms import prepare_form class FileManipulator: @@ -13,11 +12,14 @@ def create_template(self, pdf_path: str): """ By using commonforms, we create an editable .pdf template and we store it. """ + # Lazy import + from commonforms import prepare_form template_path = pdf_path[:-4] + "_template.pdf" os.system("taskkill /F /IM ollama.exe >nul 2>&1") print("Cleared existing Ollama instances. Starting fresh...") + prepare_form(pdf_path, template_path) return template_path From 74b14d11767198dccbd8e00562bc55d68a6d63e0 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 18:32:41 -0700 Subject: [PATCH 27/33] refactor: :recycle: dockerfile code cleanup --- Dockerfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 60392a3..282eb25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,8 @@ FROM python:3.11-slim WORKDIR /app # Install system dependencies -# Fixes #275 #191 #184 — libGL and libglib2 required by faster-whisper / OpenCV -# Fixes #53 — libxcb1 missing from python:3.11-slim base image -# ffmpeg required by faster-whisper for audio processing RUN apt-get update && apt-get install -y \ curl \ - ffmpeg \ libgl1 \ libglib2.0-0 \ libxcb1 \ @@ -22,7 +18,6 @@ RUN pip install --no-cache-dir -r requirements.txt # Copy application code COPY . . -# Fix #118 #116 — PYTHONPATH must be /app (project root), not /app/src # All imports use api.*, src.* which require the root to be on the path ENV PYTHONPATH=/app From 9a768c3df05bd2ae20518e90951338d026874966 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 19:18:17 -0700 Subject: [PATCH 28/33] docs: :memo: wiki pages start --- docs/DEPLOYMENT.md | 240 ------------------- docs/db.md | 48 ---- docs/docker.md | 70 ------ docs/architecture.md => wiki/ARCHITECTURE.md | 8 +- wiki/CONTRIBUTING.md | 35 +++ wiki/DATABASE.md | 37 +++ wiki/DEPLOYMENT.md | 48 ++++ wiki/DOCKER.md | 31 +++ wiki/HOME.md | 32 +++ wiki/_SIDEBAR.md | 16 ++ 10 files changed, 203 insertions(+), 362 deletions(-) delete mode 100644 docs/DEPLOYMENT.md delete mode 100644 docs/db.md delete mode 100644 docs/docker.md rename docs/architecture.md => wiki/ARCHITECTURE.md (84%) create mode 100644 wiki/CONTRIBUTING.md create mode 100644 wiki/DATABASE.md create mode 100644 wiki/DEPLOYMENT.md create mode 100644 wiki/DOCKER.md create mode 100644 wiki/HOME.md create mode 100644 wiki/_SIDEBAR.md diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md deleted file mode 100644 index 69d0602..0000000 --- a/docs/DEPLOYMENT.md +++ /dev/null @@ -1,240 +0,0 @@ -# FireForm — Deployment Guide - -This guide covers deploying FireForm using Docker. By the end, you will have the full -FireForm stack (FastAPI server + Ollama AI engine) running on any machine with a single command. - ---- - -## Prerequisites - -| Requirement | Version | Install | -|------------|---------|---------| -| Docker Desktop | 26.x or newer | [docker.com/get-started](https://www.docker.com/get-started/) | -| WSL2 (Windows only) | latest | `wsl --update` in PowerShell as Admin | -| Git | any | [git-scm.com](https://git-scm.com/) | -| Disk space | ~6GB free | For Docker image + Mistral model (~4GB) | -| RAM | 8GB minimum | 16GB recommended for smooth LLM inference | - ---- - -## Quick Start (Recommended) - -```bash -# 1. Clone the repository -git clone https://github.com/fireform-core/FireForm -cd FireForm - -# 2. Build and start everything -docker compose build -docker compose up -d - -# 3. Pull the AI model (one-time, ~4GB download) -docker compose exec ollama ollama pull mistral - -# 4. Open FireForm -# API + Swagger docs: http://localhost:8000/docs -# Web interface: http://localhost:8000 -``` - ---- - -## What Runs Inside Docker - -| Container | Purpose | Port | -|-----------|---------|------| -| `fireform-app` | FastAPI server — handles all API routes, PDF filling, Data Lake | `8000` | -| `fireform-ollama` | Ollama AI engine — serves Mistral for LLM extraction and semantic mapping | `11434` | - -The two containers communicate internally over `fireform-network`. You only interact with port `8000`. - ---- - -## Make Commands Reference - -```bash -make fireform # Build + start + pull Mistral model (full setup) -make build # Build Docker images only -make up # Start all containers (background) -make down # Stop all containers -make logs # View live logs from all containers -make logs-app # View live logs from FastAPI app only -make logs-ollama # View live logs from Ollama only -make shell # Open bash shell inside the app container -make run # Start the FastAPI server inside the running container -make pull-model # Pull the Mistral model into Ollama -make test # Run the full test suite inside the container -make clean # Stop containers and remove volumes -make super-clean # [CAUTION] Remove all containers, networks, and build cache -make help # Show all commands with descriptions -``` - ---- - -## Environment Variables - -Set these in `docker-compose.yml` under `app > environment`, or pass via `.env` file: - -| Variable | Default | Description | -|----------|---------|-------------| -| `OLLAMA_HOST` | `http://ollama:11434` | URL of the Ollama service (internal Docker network) | -| `OLLAMA_TIMEOUT` | `300` | Seconds to wait for LLM response before timeout | -| `FIREFORM_MODEL` | `mistral` | LLM model to use (e.g. `mistral`, `llama3`, `llava`) | -| `PYTHONUNBUFFERED` | `1` | Ensures real-time log output | -| `PYTHONPATH` | `/app` | Project root — required for all `api.*` and `src.*` imports | - -### Using a Different Model - -```bash -# In docker-compose.yml, change: -- FIREFORM_MODEL=mistral -# To any Ollama-supported model: -- FIREFORM_MODEL=llama3 - -# Then pull the new model: -docker compose exec ollama ollama pull llama3 -``` - ---- - -## Verifying the Deployment - -### 1. Check containers are running -```bash -docker ps -``` -Expected output: -``` -CONTAINER ID IMAGE PORTS NAMES -xxxxxxxxxxxx fireform-app 0.0.0.0:8000->8000/tcp fireform-app -xxxxxxxxxxxx ollama/ollama:latest 0.0.0.0:11434->11434/tcp fireform-ollama -``` - -### 2. Check app started correctly -```bash -docker compose logs app -``` -Look for: -``` -INFO: Application startup complete. -INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -``` - -### 3. Test the API -```bash -curl http://localhost:8000/ -``` -Or open **http://localhost:8000/docs** in your browser — Swagger UI should load. - -### 4. Run the test suite -```bash -make test -``` -All 83+ tests should pass. - ---- - -## Troubleshooting - -### Container exits immediately / `Application startup complete` never appears -```bash -docker compose logs app -``` -Read the error. Common causes: - -| Error message | Cause | Fix | -|---------------|-------|-----| -| `ModuleNotFoundError: No module named 'X'` | Missing package in `requirements.txt` | Add the package and rebuild | -| `RuntimeError: Form data requires python-multipart` | `python-multipart` missing | Already fixed in this PR | -| `cannot import name 'X' from 'api.routes'` | Wrong `PYTHONPATH` | Ensure `PYTHONPATH=/app` in `docker-compose.yml` | - -### Port 8000 already in use -```bash -# Find what's using port 8000 -netstat -ano | findstr :8000 # Windows -lsof -i :8000 # Mac/Linux - -# Or change the port in docker-compose.yml: -ports: - - "8080:8000" # Use localhost:8080 instead -``` - -### Ollama container unhealthy / never starts -The `ollama/ollama` image does not include system utilities like `curl` or `wget`. -Do not add a `healthcheck` that depends on these. The app uses `OLLAMA_TIMEOUT=300` -to wait for Ollama to be ready at the application level. - -### LLM calls fail / timeout -```bash -# Verify Mistral is pulled -docker compose exec ollama ollama list -# Should show: mistral:latest - -# If not, pull it: -docker compose exec ollama ollama pull mistral -``` - -### Make command not found (Windows PowerShell) -`make` is not available in Windows PowerShell by default. -Use **Git Bash** or run the underlying `docker compose` commands directly: -```bash -# Instead of: make fireform -docker compose build -docker compose up -d -docker compose exec ollama ollama pull mistral -``` - ---- - -## Production Deployment (Station Intranet) - -For deployment on a Linux station server: - -```bash -# Clone and configure -git clone https://github.com/fireform-core/FireForm -cd FireForm - -# Start services -docker compose up -d -docker compose exec ollama ollama pull mistral - -# FireForm is now accessible on the station intranet at: -# http://:8000 -``` - -**HTTPS Note:** Service Workers (PWA offline mode) require HTTPS on non-localhost connections. -Most fire and police departments operate their own intranet HTTPS/SSL infrastructure — -point your department's reverse proxy (nginx/Apache) to port 8000 to enable PWA installation -on field devices without requiring cloud services or app store distribution. - ---- - -## Architecture Overview - -``` -Field Devices (PWA) Station Server (Docker) -──────────────────── ────────────────────────────────── -Officer Mobile ──────────► FastAPI [:8000] -Station Desktop ──────────► ↓ LLM extraction -Field Tablet ──────────► Ollama/Mistral [:11434] - ↓ Structured JSON - Master Data Lake (SQLite/PostgreSQL) - ↓ AI Semantic Mapping - PDF Filler (PyMuPDF) - ↓ - Filled PDF → Officer download -``` - ---- - -## Known Limitations - -- **SQLite** is used by default (single-file database). For multi-station production use, - migrate to PostgreSQL by updating `SQLMODEL_DATABASE_URL` in the environment. -- **Model download (~4GB)** is required on first run. Subsequent starts use the cached model. -- **CPU inference** is used by default. On machines with NVIDIA GPUs, Ollama automatically - uses CUDA for faster inference — no configuration required. - ---- - - diff --git a/docs/db.md b/docs/db.md deleted file mode 100644 index 4d702be..0000000 --- a/docs/db.md +++ /dev/null @@ -1,48 +0,0 @@ -# Database and API Management Guide - -This guide explains how to set up, initialize, and manage the FireForm database. - -## Prerequisites - -> [!IMPORTANT] -> Ensure you have installed all dependencies before proceeding: -> ```bash -> pip install -r requirements.txt -> ``` - -## Database Setup - -To create the database file and initialize the tables, run the following command from the project root: - -```bash -python -m api.db.init_db -``` - -> [!TIP] -> After running this, you should see a `.db` file in the root of the project. If you don't see it, it means the database was not successfully created. - -## Running the API - -Once the database is initialized, start the FastAPI server: - -```bash -uvicorn api.main:app --reload -``` - -If successful, you will see: -`INFO: Uvicorn running on http://127.0.0.1:8000` - -## Testing Endpoints - -1. Open your browser and go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). -2. Use the **Swagger UI** to test endpoints like `POST /templates/create`. -3. Click **"Try it out"**, fill in the data, and click **"Execute"** to see the response. - -## Database Visualization - -> [!NOTE] -> The database file is excluded from Git to avoid conflicts between developers. - -To visualize the database: -1. Install the **SQLite3 Editor** extension in VS Code. -2. Open the `.db` file directly. diff --git a/docs/docker.md b/docs/docker.md deleted file mode 100644 index f95c819..0000000 --- a/docs/docker.md +++ /dev/null @@ -1,70 +0,0 @@ -# Docker documentation for FireForm - -## Setup -We will be using 3 containers: -1. `fireform-app` -> Runs the FastAPI server on `http://127.0.0.1:8000`. -2. `fireform-frontend` -> Serves the frontend on `http://127.0.0.1:5173`. -3. `ollama/ollama:latest` -> Runs Ollama for LLM calls. - -### Initial configuration steps -For this I provided a script that can be run to automate the setup. -This script builds both containers and starts them. - -You will have to make the script executable, this can be done in linux systems with: -```bash -chmod +x container-init.sh -``` -The it can be run with: -```bash -./container-init.sh -``` -- NOTE: This pulls ollama and mistral, so it's normal for it to take a long time to finish. Don't interrupt it. - -## Dependencies -- **Docker Engine** (20.10+) - [Installation Guide](https://docs.docker.com/engine/install/) -- **Docker Compose** (2.0+) - Included with Docker Desktop or install separately -- **Make** - For running development commands -- **Git** - For version control - -## Configuration files -The files involved in this are: -- Dockerfile -- Makefile -- docker-compose.yml -- .dockerignore (like gitignore but for the containers) -- container-init.sh - -The makefile is set up so that you don't need to learn how to properly use docker, just use the __available commands:__ -``` -make build # Build Docker images -make up # Start all containers -make down # Stop all containers -make logs # View logs from all containers -make shell # Open bash shell in app container -make exec # Run main.py in container -make pull-model # Pull Mistral model into Ollama -make clean # Remove all containers and volumes -``` -* You can see this list at any time by running `make help`. - -## Running the full stack - -```bash -make build -make up -``` - -Then open: -- Frontend: `http://127.0.0.1:5173` -- API docs: `http://127.0.0.1:8000/docs` - -If this is your first run, pull the model once: - -```bash -make pull-model -``` - -## Debugging -For debugging with LLMs it's really useful to attach the logs. -* You can obtain the logs using `make logs` or `docker compose logs`. -* A common problem is when you already have something running in port 11434. As ollama runs in that port, we need it free. You can check what's running on that port with `sudo lsof -i :11434`. diff --git a/docs/architecture.md b/wiki/ARCHITECTURE.md similarity index 84% rename from docs/architecture.md rename to wiki/ARCHITECTURE.md index 44b9bb5..e14ee27 100644 --- a/docs/architecture.md +++ b/wiki/ARCHITECTURE.md @@ -1,6 +1,6 @@ # FireForm Architecture -FireForm is a system designed to automate the process of filling out PDF forms using information extracted from transcriptions (e.g., voice recordings) via an LLM. +FireForm is a system designed to automate the process of filling out PDF forms using information extracted from transcriptions via an LLM. ## Pipeline Description @@ -11,7 +11,7 @@ The FireForm pipeline follows a structured flow from receiving raw user input to 1. **Template Preparation**: A standard PDF is processed to ensure it has interactive form fields. 2. **Input Acquisition**: The system receives a transcript (text) and a list of target fields to extract. 3. **LLM Extraction**: An LLM (via Ollama) processes the transcript to find values for each target field. -4. **Form Filling**: extracted values are mapped and written into the PDF template. +4. **Form Filling**: Extracted values are mapped and written into the PDF template. 5. **Output Generation**: A new, filled PDF is saved with a timestamped filename. ### Component Interaction @@ -55,7 +55,7 @@ sequenceDiagram ### Key Components - **`FileManipulator`**: The high-level orchestrator that manages the overall process. -- **`LLM`**: Handles prompt engineering and communication with the local Ollama instance (typically using the Mistral model). It extracts structured data from unstructured text. -- **`Filler`**: Handles the low-level PDF manipulation, ensuring that extracted data is placed into the correct form fields based on their visual order. +- **`LLM`**: Handles prompt engineering and communication with the local Ollama instance. It extracts structured data from unstructured text. +- **`Filler`**: Handles the low-level PDF manipulation, ensuring that extracted data is placed into the correct form fields. - **`commonforms`**: A dependency used to prepare standard PDFs for filling. - **`pdfrw` / `pypdf`**: Libraries used for reading and writing PDF metadata and form field values. diff --git a/wiki/CONTRIBUTING.md b/wiki/CONTRIBUTING.md new file mode 100644 index 0000000..bcc8dbf --- /dev/null +++ b/wiki/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# Contributing to FireForm + +Thank you for your interest in contributing! FireForm is a Digital Public Good (DPG) designed to help emergency responders. + +## 🌈 Code of Conduct + +All contributors are expected to uphold the [FireForm Code of Conduct](CODE_OF_CONDUCT.md). + +## 🚀 How Can I Contribute? + +### Reporting Bugs + +Check the [issues list](https://github.com/fireform-core/FireForm/issues) before reporting. If it's a new bug, please include: +- A clear title. +- Steps to reproduce. +- Expected vs. actual behavior. + +### Suggesting Enhancements + +Open an issue with: +- A clear title. +- A detailed description of the feature. +- Rationale for why it's useful. + +### Pull Requests + +1. Fork the repo and create your branch from `main`. +2. Add tests if you've added new features. +3. Ensure the test suite passes. +4. Make sure your code lints. +5. Open the pull request! + +## 🛠️ Local Development Setup + +FireForm uses Docker for development. See the [[DOCKER]] page for more details. diff --git a/wiki/DATABASE.md b/wiki/DATABASE.md new file mode 100644 index 0000000..f749b49 --- /dev/null +++ b/wiki/DATABASE.md @@ -0,0 +1,37 @@ +# Database and API Management + +This guide explains how to set up, initialize, and manage the FireForm database. + +## Database Setup + +To create the database file and initialize the tables, run the following command from the project root: + +```bash +python -m api.db.init_db +``` + +> [!TIP] +> After running this, you should see a `.db` file in the root of the project. + +## Running the API + +Once the database is initialized, start the FastAPI server: + +```bash +uvicorn api.main:app --reload +``` + +If successful, you will see: `INFO: Uvicorn running on http://127.0.0.1:8000` + +## Testing Endpoints + +1. Open your browser and go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). +2. Use the **Swagger UI** to test endpoints like `POST /templates/create`. +3. Click **"Try it out"**, fill in the data, and click **"Execute"**. + +## Database Visualization + +The database file is excluded from Git to avoid conflicts. To visualize the database: + +1. Install the **SQLite3 Editor** extension in VS Code. +2. Open the `.db` file directly. diff --git a/wiki/DEPLOYMENT.md b/wiki/DEPLOYMENT.md new file mode 100644 index 0000000..0d2ee57 --- /dev/null +++ b/wiki/DEPLOYMENT.md @@ -0,0 +1,48 @@ +# Deployment Guide + +This guide covers deploying FireForm using Docker. By the end, you will have the full FireForm stack (FastAPI server + Ollama AI engine) running on any machine with a single command. + +## Prerequisites + +| Requirement | Version | Install | +| :--- | :--- | :--- | +| Docker Desktop | 26.x or newer | [docker.com/get-started](https://www.docker.com/get-started/) | +| WSL2 (Windows) | Latest | `wsl --update` | +| Git | Any | [git-scm.com](https://git-scm.com/) | +| Disk space | ~6GB free | For Docker image + Mistral model | +| RAM | 8GB min | 16GB recommended | + +## Quick Start (Recommended) + +```bash +# 1. Clone the repository +git clone https://github.com/fireform-core/FireForm +cd FireForm + +# 2. Build and start everything +docker compose build +docker compose up -d + +# 3. Pull the AI model (one-time, ~4GB download) +docker compose exec ollama ollama pull mistral + +# 4. Open FireForm +# API + Swagger docs: http://localhost:8000/docs +# Web interface: http://localhost:8000 +``` + +## Production Deployment (Station Intranet) + +For deployment on a Linux station server: + +1. **Start services:** `docker compose up -d` +2. **Pull model:** `docker compose exec ollama ollama pull mistral` +3. **Access:** FireForm is now accessible at `http://:8000` + +**HTTPS Note:** Service Workers (PWA offline mode) require HTTPS on non-localhost connections. Point your department's reverse proxy (nginx/Apache) to port 8000 to enable PWA installation. + +## Known Limitations + +- **SQLite** is used by default. For multi-station production use, migrate to PostgreSQL. +- **Model download (~4GB)** is required on first run. +- **CPU inference** is used by default, but Ollama will use CUDA if an NVIDIA GPU is detected. diff --git a/wiki/DOCKER.md b/wiki/DOCKER.md new file mode 100644 index 0000000..c45447b --- /dev/null +++ b/wiki/DOCKER.md @@ -0,0 +1,31 @@ +# Docker Documentation + +FireForm uses Docker to ensure a consistent environment across different machines. + +## Container Stack + +We use three main containers: +1. **`fireform-app`**: Runs the FastAPI server on `http://localhost:8000`. +2. **`fireform-frontend`**: Serves the frontend on `http://localhost:5173`. +3. **`fireform-ollama`**: Runs Ollama for LLM calls. + +## Makefile Commands + +The `Makefile` simplifies common Docker tasks. You can run these commands from the project root: + +| Command | Description | +| :--- | :--- | +| `make build` | Build Docker images | +| `make up` | Start all containers in the background | +| `make down` | Stop all containers | +| `make logs` | View logs from all containers | +| `make shell` | Open a bash shell in the app container | +| `make pull-model` | Pull the Mistral model into Ollama | +| `make clean` | Remove all containers and volumes | +| `make help` | Show all available commands | + +## Troubleshooting + +- **Logs:** Use `make logs` to see what's happening inside the containers. +- **Port Conflicts:** If port 11434 (Ollama) or 8000 (App) is already in use, you may need to stop the conflicting process or change the mapping in `docker-compose.yml`. +- **Model not found:** If LLM calls fail, ensure you've run `make pull-model`. diff --git a/wiki/HOME.md b/wiki/HOME.md new file mode 100644 index 0000000..3991cd5 --- /dev/null +++ b/wiki/HOME.md @@ -0,0 +1,32 @@ +# Welcome to the FireForm Wiki! + +FireForm is the 1st Place Winner of the Reboot the Earth hackathon, hosted by the United Nations (UN) and UC Santa Cruz (UCSC). + +It is an open-source, agnostic system built to solve administrative overhead for first responders. FireForm is a Digital Public Good (DPG) designed to help departments like Cal Fire save hundreds of hours by eliminating redundant paperwork. + +## 🚀 Navigation + +- [[ARCHITECTURE]]: High-level system design and component interaction. +- [[DEPLOYMENT]]: Step-by-step guide to getting FireForm running. +- [[DATABASE]]: Information on database setup and management. +- [[DOCKER]]: Detailed guide on using Docker and Docker Compose. +- [[CONTRIBUTING]]: How you can help improve FireForm. + +## 🚩 The Problem + +First responders, like firefighters, are often required to report a single incident to multiple different agencies. Each agency has its own unique forms and templates. This forces firefighters to spend hours at the end of their shift filling out the same information over and over. + +## 💡 The Solution + +FireForm is a centralized **"report once, file everywhere"** system. + +1. **Single Input:** A firefighter records a single voice memo or fills out one "master" text field. +2. **AI Extraction:** The transcription is sent to an open-source LLM (via Ollama) which extracts all key information into structured JSON. +3. **Template Filling:** FireForm uses this JSON to automatically fill every required PDF template. + +### ✨ Key Features + +- **Agnostic:** Works with any department's existing fillable PDF forms. +- **AI-Powered:** Uses open-source, locally-run LLMs (Mistral). No data leaves the local machine. +- **Single Point of Entry:** Eliminates redundant data entry entirely. +- **Open-Source (DPG):** Built 100% with open-source tools. diff --git a/wiki/_SIDEBAR.md b/wiki/_SIDEBAR.md new file mode 100644 index 0000000..782a471 --- /dev/null +++ b/wiki/_SIDEBAR.md @@ -0,0 +1,16 @@ +### Navigation + +- [[HOME]] +- [[ARCHITECTURE]] +- [[DEPLOYMENT]] +- [[DATABASE]] +- [[DOCKER]] +- [[CONTRIBUTING]] + +*** + +### Links + +- [GitHub Repo](https://github.com/fireform-core/FireForm) +- [Issues](https://github.com/fireform-core/FireForm/issues) +- [License](https://github.com/fireform-core/FireForm/blob/main/LICENSE) From e89c5ac71f81dc214388ef65243e4545cfb09455 Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 19:22:27 -0700 Subject: [PATCH 29/33] fix: :memo: bug in doc name --- wiki/_SIDEBAR.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wiki/_SIDEBAR.md b/wiki/_SIDEBAR.md index 782a471..ebb081a 100644 --- a/wiki/_SIDEBAR.md +++ b/wiki/_SIDEBAR.md @@ -1,6 +1,6 @@ ### Navigation -- [[HOME]] +- [[Home]] - [[ARCHITECTURE]] - [[DEPLOYMENT]] - [[DATABASE]] From d7deb3bbc9c4f659c2fe184beb44e9d6aa2886ad Mon Sep 17 00:00:00 2001 From: marcvergees Date: Sat, 18 Apr 2026 19:25:51 -0700 Subject: [PATCH 30/33] docs: :memo: move documentation to wiki pages --- wiki/ARCHITECTURE.md | 61 -------------------------------------------- wiki/CONTRIBUTING.md | 35 ------------------------- wiki/DATABASE.md | 37 --------------------------- wiki/DEPLOYMENT.md | 48 ---------------------------------- wiki/DOCKER.md | 31 ---------------------- wiki/HOME.md | 32 ----------------------- wiki/_SIDEBAR.md | 16 ------------ 7 files changed, 260 deletions(-) delete mode 100644 wiki/ARCHITECTURE.md delete mode 100644 wiki/CONTRIBUTING.md delete mode 100644 wiki/DATABASE.md delete mode 100644 wiki/DEPLOYMENT.md delete mode 100644 wiki/DOCKER.md delete mode 100644 wiki/HOME.md delete mode 100644 wiki/_SIDEBAR.md diff --git a/wiki/ARCHITECTURE.md b/wiki/ARCHITECTURE.md deleted file mode 100644 index e14ee27..0000000 --- a/wiki/ARCHITECTURE.md +++ /dev/null @@ -1,61 +0,0 @@ -# FireForm Architecture - -FireForm is a system designed to automate the process of filling out PDF forms using information extracted from transcriptions via an LLM. - -## Pipeline Description - -The FireForm pipeline follows a structured flow from receiving raw user input to generating a finalized, filled PDF document. - -### High-Level Flow - -1. **Template Preparation**: A standard PDF is processed to ensure it has interactive form fields. -2. **Input Acquisition**: The system receives a transcript (text) and a list of target fields to extract. -3. **LLM Extraction**: An LLM (via Ollama) processes the transcript to find values for each target field. -4. **Form Filling**: Extracted values are mapped and written into the PDF template. -5. **Output Generation**: A new, filled PDF is saved with a timestamped filename. - -### Component Interaction - -```mermaid -sequenceDiagram - participant U as User/Frontend - participant FM as FileManipulator - participant LLM as LLM Module - participant O as Ollama (Mistral) - participant F as Filler - participant CF as CommonForms/pypdf - - U->>FM: fill_form(transcript, fields, pdf_path) - FM->>LLM: Set target_fields & transcript - FM->>F: fill_form(pdf_path, llm) - - activate F - F->>LLM: main_loop() - - activate LLM - loop For each field - LLM->>LLM: build_prompt(field) - LLM->>O: POST /api/generate - O-->>LLM: Extracted Value - LLM->>LLM: add_response_to_json(field, value) - end - LLM-->>F: Resulting JSON Data - deactivate LLM - - F->>CF: Read PDF Template - F->>F: Sort Annotations (Visual Order) - F->>CF: Write values to Widgets - F->>CF: Write Filled PDF - F-->>FM: Path to filled PDF - deactivate F - - FM-->>U: Final PDF Path -``` - -### Key Components - -- **`FileManipulator`**: The high-level orchestrator that manages the overall process. -- **`LLM`**: Handles prompt engineering and communication with the local Ollama instance. It extracts structured data from unstructured text. -- **`Filler`**: Handles the low-level PDF manipulation, ensuring that extracted data is placed into the correct form fields. -- **`commonforms`**: A dependency used to prepare standard PDFs for filling. -- **`pdfrw` / `pypdf`**: Libraries used for reading and writing PDF metadata and form field values. diff --git a/wiki/CONTRIBUTING.md b/wiki/CONTRIBUTING.md deleted file mode 100644 index bcc8dbf..0000000 --- a/wiki/CONTRIBUTING.md +++ /dev/null @@ -1,35 +0,0 @@ -# Contributing to FireForm - -Thank you for your interest in contributing! FireForm is a Digital Public Good (DPG) designed to help emergency responders. - -## 🌈 Code of Conduct - -All contributors are expected to uphold the [FireForm Code of Conduct](CODE_OF_CONDUCT.md). - -## 🚀 How Can I Contribute? - -### Reporting Bugs - -Check the [issues list](https://github.com/fireform-core/FireForm/issues) before reporting. If it's a new bug, please include: -- A clear title. -- Steps to reproduce. -- Expected vs. actual behavior. - -### Suggesting Enhancements - -Open an issue with: -- A clear title. -- A detailed description of the feature. -- Rationale for why it's useful. - -### Pull Requests - -1. Fork the repo and create your branch from `main`. -2. Add tests if you've added new features. -3. Ensure the test suite passes. -4. Make sure your code lints. -5. Open the pull request! - -## 🛠️ Local Development Setup - -FireForm uses Docker for development. See the [[DOCKER]] page for more details. diff --git a/wiki/DATABASE.md b/wiki/DATABASE.md deleted file mode 100644 index f749b49..0000000 --- a/wiki/DATABASE.md +++ /dev/null @@ -1,37 +0,0 @@ -# Database and API Management - -This guide explains how to set up, initialize, and manage the FireForm database. - -## Database Setup - -To create the database file and initialize the tables, run the following command from the project root: - -```bash -python -m api.db.init_db -``` - -> [!TIP] -> After running this, you should see a `.db` file in the root of the project. - -## Running the API - -Once the database is initialized, start the FastAPI server: - -```bash -uvicorn api.main:app --reload -``` - -If successful, you will see: `INFO: Uvicorn running on http://127.0.0.1:8000` - -## Testing Endpoints - -1. Open your browser and go to [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs). -2. Use the **Swagger UI** to test endpoints like `POST /templates/create`. -3. Click **"Try it out"**, fill in the data, and click **"Execute"**. - -## Database Visualization - -The database file is excluded from Git to avoid conflicts. To visualize the database: - -1. Install the **SQLite3 Editor** extension in VS Code. -2. Open the `.db` file directly. diff --git a/wiki/DEPLOYMENT.md b/wiki/DEPLOYMENT.md deleted file mode 100644 index 0d2ee57..0000000 --- a/wiki/DEPLOYMENT.md +++ /dev/null @@ -1,48 +0,0 @@ -# Deployment Guide - -This guide covers deploying FireForm using Docker. By the end, you will have the full FireForm stack (FastAPI server + Ollama AI engine) running on any machine with a single command. - -## Prerequisites - -| Requirement | Version | Install | -| :--- | :--- | :--- | -| Docker Desktop | 26.x or newer | [docker.com/get-started](https://www.docker.com/get-started/) | -| WSL2 (Windows) | Latest | `wsl --update` | -| Git | Any | [git-scm.com](https://git-scm.com/) | -| Disk space | ~6GB free | For Docker image + Mistral model | -| RAM | 8GB min | 16GB recommended | - -## Quick Start (Recommended) - -```bash -# 1. Clone the repository -git clone https://github.com/fireform-core/FireForm -cd FireForm - -# 2. Build and start everything -docker compose build -docker compose up -d - -# 3. Pull the AI model (one-time, ~4GB download) -docker compose exec ollama ollama pull mistral - -# 4. Open FireForm -# API + Swagger docs: http://localhost:8000/docs -# Web interface: http://localhost:8000 -``` - -## Production Deployment (Station Intranet) - -For deployment on a Linux station server: - -1. **Start services:** `docker compose up -d` -2. **Pull model:** `docker compose exec ollama ollama pull mistral` -3. **Access:** FireForm is now accessible at `http://:8000` - -**HTTPS Note:** Service Workers (PWA offline mode) require HTTPS on non-localhost connections. Point your department's reverse proxy (nginx/Apache) to port 8000 to enable PWA installation. - -## Known Limitations - -- **SQLite** is used by default. For multi-station production use, migrate to PostgreSQL. -- **Model download (~4GB)** is required on first run. -- **CPU inference** is used by default, but Ollama will use CUDA if an NVIDIA GPU is detected. diff --git a/wiki/DOCKER.md b/wiki/DOCKER.md deleted file mode 100644 index c45447b..0000000 --- a/wiki/DOCKER.md +++ /dev/null @@ -1,31 +0,0 @@ -# Docker Documentation - -FireForm uses Docker to ensure a consistent environment across different machines. - -## Container Stack - -We use three main containers: -1. **`fireform-app`**: Runs the FastAPI server on `http://localhost:8000`. -2. **`fireform-frontend`**: Serves the frontend on `http://localhost:5173`. -3. **`fireform-ollama`**: Runs Ollama for LLM calls. - -## Makefile Commands - -The `Makefile` simplifies common Docker tasks. You can run these commands from the project root: - -| Command | Description | -| :--- | :--- | -| `make build` | Build Docker images | -| `make up` | Start all containers in the background | -| `make down` | Stop all containers | -| `make logs` | View logs from all containers | -| `make shell` | Open a bash shell in the app container | -| `make pull-model` | Pull the Mistral model into Ollama | -| `make clean` | Remove all containers and volumes | -| `make help` | Show all available commands | - -## Troubleshooting - -- **Logs:** Use `make logs` to see what's happening inside the containers. -- **Port Conflicts:** If port 11434 (Ollama) or 8000 (App) is already in use, you may need to stop the conflicting process or change the mapping in `docker-compose.yml`. -- **Model not found:** If LLM calls fail, ensure you've run `make pull-model`. diff --git a/wiki/HOME.md b/wiki/HOME.md deleted file mode 100644 index 3991cd5..0000000 --- a/wiki/HOME.md +++ /dev/null @@ -1,32 +0,0 @@ -# Welcome to the FireForm Wiki! - -FireForm is the 1st Place Winner of the Reboot the Earth hackathon, hosted by the United Nations (UN) and UC Santa Cruz (UCSC). - -It is an open-source, agnostic system built to solve administrative overhead for first responders. FireForm is a Digital Public Good (DPG) designed to help departments like Cal Fire save hundreds of hours by eliminating redundant paperwork. - -## 🚀 Navigation - -- [[ARCHITECTURE]]: High-level system design and component interaction. -- [[DEPLOYMENT]]: Step-by-step guide to getting FireForm running. -- [[DATABASE]]: Information on database setup and management. -- [[DOCKER]]: Detailed guide on using Docker and Docker Compose. -- [[CONTRIBUTING]]: How you can help improve FireForm. - -## 🚩 The Problem - -First responders, like firefighters, are often required to report a single incident to multiple different agencies. Each agency has its own unique forms and templates. This forces firefighters to spend hours at the end of their shift filling out the same information over and over. - -## 💡 The Solution - -FireForm is a centralized **"report once, file everywhere"** system. - -1. **Single Input:** A firefighter records a single voice memo or fills out one "master" text field. -2. **AI Extraction:** The transcription is sent to an open-source LLM (via Ollama) which extracts all key information into structured JSON. -3. **Template Filling:** FireForm uses this JSON to automatically fill every required PDF template. - -### ✨ Key Features - -- **Agnostic:** Works with any department's existing fillable PDF forms. -- **AI-Powered:** Uses open-source, locally-run LLMs (Mistral). No data leaves the local machine. -- **Single Point of Entry:** Eliminates redundant data entry entirely. -- **Open-Source (DPG):** Built 100% with open-source tools. diff --git a/wiki/_SIDEBAR.md b/wiki/_SIDEBAR.md deleted file mode 100644 index ebb081a..0000000 --- a/wiki/_SIDEBAR.md +++ /dev/null @@ -1,16 +0,0 @@ -### Navigation - -- [[Home]] -- [[ARCHITECTURE]] -- [[DEPLOYMENT]] -- [[DATABASE]] -- [[DOCKER]] -- [[CONTRIBUTING]] - -*** - -### Links - -- [GitHub Repo](https://github.com/fireform-core/FireForm) -- [Issues](https://github.com/fireform-core/FireForm/issues) -- [License](https://github.com/fireform-core/FireForm/blob/main/LICENSE) From 49dd4cf0f423818d9b21405eee8c370d345f9aff Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 20 Apr 2026 02:07:55 +0530 Subject: [PATCH 31/33] refactor: extract LLM client from main loop --- src/llm.py | 5 ++--- src/llm_client.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 src/llm_client.py diff --git a/src/llm.py b/src/llm.py index 24af2d3..97a9589 100644 --- a/src/llm.py +++ b/src/llm.py @@ -4,6 +4,7 @@ from api.services.prompt_builder import build_extraction_prompt from src.validation import validate_extraction from requests.exceptions import Timeout, RequestException +from src.llm_client import call_llm def safe_extract_value(response: str): if not response: @@ -106,9 +107,7 @@ def main_loop(self): try: for attempt in range(max_retries): try: - response = requests.post(ollama_url, json=payload, timeout=timeout) - response.raise_for_status() - json_data = response.json() + json_data = call_llm(prompt, timeout=timeout, retries=max_retries) break except Timeout: print(f"Ollama request timed out (attempt {attempt+1})") diff --git a/src/llm_client.py b/src/llm_client.py new file mode 100644 index 0000000..66a0d2d --- /dev/null +++ b/src/llm_client.py @@ -0,0 +1,28 @@ +import requests +from requests.exceptions import Timeout, RequestException + + +def call_llm(prompt: str, timeout: int = 30, retries: int = 2): + payload = { + "model": "mistral", + "prompt": prompt, + "stream": False + } + + url = "http://localhost:11434/api/generate" + + for attempt in range(retries + 1): + try: + response = requests.post(url, json=payload, timeout=timeout) + response.raise_for_status() + return response.json() + + except Timeout: + if attempt == retries: + raise RuntimeError("LLM request timed out") + + except RequestException as e: + if attempt == retries: + raise RuntimeError(f"LLM request failed: {e}") + + return None \ No newline at end of file From d71518a1bbfff4d9d992ae79bc53add776254c83 Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 20 Apr 2026 03:08:14 +0530 Subject: [PATCH 32/33] refactor(llm): extract field-level prompt building to service layer --- api/services/prompt_builder.py | 23 ++++++++++++++++++++++ src/llm.py | 36 ++++------------------------------ 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/api/services/prompt_builder.py b/api/services/prompt_builder.py index 843c7cd..d4b20bd 100644 --- a/api/services/prompt_builder.py +++ b/api/services/prompt_builder.py @@ -59,4 +59,27 @@ def build_extraction_prompt(input_text: str) -> str: Now extract strictly from the following input (follow all rules above): {input_text} +""" + +def build_field_prompt(transcript_text: str, current_field: str) -> str: + return f""" +SYSTEM PROMPT: +You are an AI assistant designed to help fill out JSON fields with information extracted from transcribed text. + +You will receive: +- a transcript +- a target field name + +Return ONLY the value for that field. + +Rules: +- If multiple values exist → separate with ";" +- If no value found → return "-1" +- Do NOT add explanation + +DATA: +Target JSON field: {current_field} + +TEXT: +{transcript_text} """ \ No newline at end of file diff --git a/src/llm.py b/src/llm.py index 48780a9..872b924 100644 --- a/src/llm.py +++ b/src/llm.py @@ -1,7 +1,7 @@ import json import os import requests -from api.services.prompt_builder import build_extraction_prompt +from api.services.prompt_builder import build_field_prompt from src.validation import validate_extraction from requests.exceptions import Timeout, RequestException from src.llm_client import call_llm @@ -52,26 +52,7 @@ def type_check_all(self): Target fields must be a list. Input:\n\ttarget_fields: {self._target_fields}" ) - def build_prompt(self, current_field): - """ - This method is in charge of the prompt engineering. It creates a specific prompt for each target field. - @params: current_field -> represents the current element of the json that is being prompted. - """ - prompt = f""" - SYSTEM PROMPT: - You are an AI assistant designed to help fillout json files with information extracted from transcribed voice recordings. - You will receive the transcription, and the name of the JSON field whose value you have to identify in the context. Return - only a single string containing the identified value for the JSON field. - If the field name is plural, and you identify more than one possible value in the text, return both separated by a ";". - If you don't identify the value in the provided text, return "-1". - --- - DATA: - Target JSON field to find in text: {current_field} - - TEXT: {self._transcript_text} - """ - - return prompt + def main_loop(self): timeout = 30 @@ -80,22 +61,13 @@ def main_loop(self): # self.type_check_all() total_fields = len(self._target_fields) for i, field in enumerate(self._target_fields.keys(), 1): - prompt = self.build_prompt(field) + prompt = build_field_prompt(self._transcript_text, field) # print(prompt) # ollama_url = "http://localhost:11434/api/generate" ollama_host = os.getenv("OLLAMA_HOST", "http://localhost:11434").rstrip("/") ollama_url = f"{ollama_host}/api/generate" - base_prompt = build_extraction_prompt(self._transcript_text) - - prompt = f""" - {base_prompt} - - Focus specifically on extracting the value for this field: - {field} - - Return only the extracted value as a plain string. Do not return JSON. - """ + payload = { "model": "mistral", From 2104e708208e195b7c8ca99c5aa10a1636cf2eee Mon Sep 17 00:00:00 2001 From: Lochit Vinay Date: Mon, 20 Apr 2026 03:38:51 +0530 Subject: [PATCH 33/33] refactor(llm): separate validation step from extraction pipeline --- src/file_manipulator.py | 5 ++- src/filler.py | 94 ++++++++++++++++++++++------------------- src/llm.py | 10 +---- 3 files changed, 56 insertions(+), 53 deletions(-) diff --git a/src/file_manipulator.py b/src/file_manipulator.py index e499c89..498c31d 100644 --- a/src/file_manipulator.py +++ b/src/file_manipulator.py @@ -37,7 +37,10 @@ def fill_form(self, user_input: str, fields: list, pdf_form_path: str): print("[3] Starting extraction and PDF filling process...") try: - self.llm._target_fields = fields + if isinstance(fields, dict): + self.llm._target_fields = list(fields.keys()) + else: + self.llm._target_fields = fields self.llm._transcript_text = user_input output_name = self.filler.fill_form(pdf_form=pdf_form_path, llm=self.llm) diff --git a/src/filler.py b/src/filler.py index 7f738c2..21c4b81 100644 --- a/src/filler.py +++ b/src/filler.py @@ -1,52 +1,58 @@ from pdfrw import PdfReader, PdfWriter from src.llm import LLM from datetime import datetime - +from src.validation import validate_extraction class Filler: def __init__(self): pass - def fill_form(self, pdf_form: str, llm: LLM): - """ - Fill a PDF form with values from user_input using LLM. - Fields are filled in the visual order (top-to-bottom, left-to-right). - """ - output_pdf = ( - pdf_form[:-4] - + "_" - + datetime.now().strftime("%Y%m%d_%H%M%S") - + "_filled.pdf" - ) - - # Generate dictionary of answers from your original function - t2j = llm.main_loop() - textbox_answers = t2j.get_data() # This is a dictionary - - answers_list = list(textbox_answers.values()) - - # Read PDF - pdf = PdfReader(pdf_form) - - # Loop through pages - i = 0 - for page in pdf.pages: - if page.Annots: - sorted_annots = sorted( - page.Annots, key=lambda a: (-float(a.Rect[1]), float(a.Rect[0])) - ) - - for annot in sorted_annots: - if annot.Subtype == "/Widget" and annot.T: - if i < len(answers_list): - annot.V = f"{answers_list[i]}" - annot.AP = None - i += 1 - else: - # Stop if we run out of answers - break - - PdfWriter().write(output_pdf, pdf) - - # Your main.py expects this function to return the path - return output_pdf +def fill_form(self, pdf_form: str, llm: LLM): + """ + Fill a PDF form with values from user_input using LLM. + Fields are filled in the visual order (top-to-bottom, left-to-right). + """ + output_pdf = ( + pdf_form[:-4] + + "_" + + datetime.now().strftime("%Y%m%d_%H%M%S") + + "_filled.pdf" + ) + + # Generate dictionary of answers + t2j = llm.main_loop() + raw_data = t2j.get_data() + + # Validation step (separate concern ✅) + validated_data, errors = validate_extraction(raw_data) + + if errors: + print("[Validation Warning]", errors) + + textbox_answers = validated_data + answers_list = list(textbox_answers.values()) + + # Read PDF + pdf = PdfReader(pdf_form) + + # Loop through pages + i = 0 + for page in pdf.pages: + if page.Annots: + sorted_annots = sorted( + page.Annots, + key=lambda a: (-float(a.Rect[1]), float(a.Rect[0])) + ) + + for annot in sorted_annots: + if annot.Subtype == "/Widget" and annot.T: + if i < len(answers_list): + annot.V = f"{answers_list[i]}" + annot.AP = None + i += 1 + else: + break + + PdfWriter().write(output_pdf, pdf) + + return output_pdf diff --git a/src/llm.py b/src/llm.py index 872b924..5bec8ca 100644 --- a/src/llm.py +++ b/src/llm.py @@ -2,7 +2,6 @@ import os import requests from api.services.prompt_builder import build_field_prompt -from src.validation import validate_extraction from requests.exceptions import Timeout, RequestException from src.llm_client import call_llm @@ -60,7 +59,7 @@ def main_loop(self): # self.type_check_all() total_fields = len(self._target_fields) - for i, field in enumerate(self._target_fields.keys(), 1): + for i, field in enumerate(self._target_fields, 1): prompt = build_field_prompt(self._transcript_text, field) # print(prompt) # ollama_url = "http://localhost:11434/api/generate" @@ -152,9 +151,4 @@ def handle_plural_values(self, plural_value): return values def get_data(self): - validated_data, errors = validate_extraction(self._json) - - if errors: - print("[Validation Warning]", errors) - - return validated_data + return self._json