From 1ef21b187373f72891640908da63f355413acd90 Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Tue, 17 Mar 2026 11:14:40 +0530 Subject: [PATCH 1/8] Improve LLM request reliability by adding timeout and minor fixes --- src/llm.py | 8 ++++---- src/main.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/llm.py b/src/llm.py index 70937f9..bb0712f 100644 --- a/src/llm.py +++ b/src/llm.py @@ -45,7 +45,7 @@ def build_prompt(self, current_field): return prompt def main_loop(self): - # self.type_check_all() + self.type_check_all() for field in self._target_fields.keys(): prompt = self.build_prompt(field) # print(prompt) @@ -54,13 +54,13 @@ def main_loop(self): ollama_url = f"{ollama_host}/api/generate" payload = { - "model": "mistral", + "model" : os.getenv("OLLAMA_MODEL", "mistral"), "prompt": prompt, "stream": False, # don't really know why --> look into this later. } try: - response = requests.post(ollama_url, json=payload) + response = requests.post(ollama_url, json=payload, timeout=30) response.raise_for_status() except requests.exceptions.ConnectionError: raise ConnectionError( @@ -72,7 +72,7 @@ def main_loop(self): # parse response json_data = response.json() - parsed_response = json_data["response"] + parsed_response = json_data.get("response", "") # print(parsed_response) self.add_response_to_json(field, parsed_response) diff --git a/src/main.py b/src/main.py index 5bb632b..addb764 100644 --- a/src/main.py +++ b/src/main.py @@ -2,6 +2,7 @@ # from backend import Fill from commonforms import prepare_form from pypdf import PdfReader +from typing import Union from controller import Controller def input_fields(num_fields: int): From 6070da0204a3cfef7a5a76b5cabee736b18e009e Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Wed, 18 Mar 2026 21:44:52 +0530 Subject: [PATCH 2/8] feat: add incident timeline extraction for structured emergency reports --- src/controller.py | 55 +++++++++- src/django | 1 + src/inputs/file_template.pdf | Bin 0 -> 74397 bytes src/test/test_controller_timeline.py | 94 ++++++++++++++++ src/timeline_extractor.py | 157 +++++++++++++++++++++++++++ 5 files changed, 302 insertions(+), 5 deletions(-) create mode 160000 src/django create mode 100644 src/inputs/file_template.pdf create mode 100644 src/test/test_controller_timeline.py create mode 100644 src/timeline_extractor.py diff --git a/src/controller.py b/src/controller.py index d31ec9c..8873b16 100644 --- a/src/controller.py +++ b/src/controller.py @@ -1,11 +1,56 @@ +from typing import List, Dict, Any + from src.file_manipulator import FileManipulator +from src.timeline_extractor import TimelineExtractor + class Controller: - def __init__(self): + """ + Controller layer for FireForm. + + Responsible for orchestrating the processing pipeline: + - Receiving user input + - Extracting timeline information + - Passing processed data to FileManipulator + """ + + def __init__(self) -> None: self.file_manipulator = FileManipulator() + self.timeline_extractor = TimelineExtractor() + + def fill_form( + self, + user_input: str, + fields: List[str], + pdf_form_path: str + ) -> Dict[str, Any]: + """ + Process the user input and fill the PDF form. + + Steps: + 1. Extract timeline events from the incident narrative + 2. Pass the original data to FileManipulator for form filling + 3. Attach timeline data to the result for downstream use + """ + + # Extract timeline from incident text + timeline = self.timeline_extractor.extract_timeline(user_input) + + # Call existing FireForm pipeline + result = self.file_manipulator.fill_form( + user_input, + fields, + pdf_form_path + ) + + # Attach timeline to the result if possible + if isinstance(result, dict): + result["timeline"] = timeline + + return result - def fill_form(self, user_input: str, fields: list, pdf_form_path: str): - return self.file_manipulator.fill_form(user_input, fields, pdf_form_path) - - def create_template(self, pdf_path: str): + def create_template(self, pdf_path: str) -> Dict[str, Any]: + """ + Generate a template from the provided PDF form. + """ return self.file_manipulator.create_template(pdf_path) \ No newline at end of file diff --git a/src/django b/src/django new file mode 160000 index 0000000..3abf898 --- /dev/null +++ b/src/django @@ -0,0 +1 @@ +Subproject commit 3abf89887993140d28676f26420ee0d46a617f51 diff --git a/src/inputs/file_template.pdf b/src/inputs/file_template.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f0821686fc7e65c5fe45ff6741f0157af3dd9309 GIT binary patch literal 74397 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 literal 0 HcmV?d00001 diff --git a/src/test/test_controller_timeline.py b/src/test/test_controller_timeline.py new file mode 100644 index 0000000..b6a31d9 --- /dev/null +++ b/src/test/test_controller_timeline.py @@ -0,0 +1,94 @@ +import pytest +from api.main import app +from unittest.mock import MagicMock +from src.controller import Controller + + +class TestControllerTimeline: + """ + Test suite for verifying timeline extraction integration + within the FireForm controller pipeline. + """ + + @pytest.fixture + def controller(self): + """ + Provides a Controller instance with mocked FileManipulator. + """ + controller = Controller() + controller.file_manipulator = MagicMock() + + # Simulate file_manipulator returning a valid result dict + controller.file_manipulator.fill_form.return_value = { + "status": "success", + "filled_pdf": "output.pdf" + } + + return controller + + def test_timeline_extraction_integration(self, controller): + """ + Ensure timeline data is added to controller output. + """ + + incident_text = ( + "Engine 12 arrived at 3:10 PM. " + "Fire contained at 3:25 PM." + ) + + result = controller.fill_form( + user_input=incident_text, + fields=[], + pdf_form_path="dummy.pdf" + ) + + assert isinstance(result, dict) + assert "timeline" in result + assert len(result["timeline"]) == 2 + assert result["timeline"][0]["time"] == "15:10" + assert result["timeline"][1]["time"] == "15:25" + + def test_no_timeline_when_no_times(self, controller): + """ + Ensure timeline is empty when no timestamps exist. + """ + + incident_text = "Firefighters responded quickly to the incident." + + result = controller.fill_form( + user_input=incident_text, + fields=[], + pdf_form_path="dummy.pdf" + ) + + assert "timeline" in result + assert result["timeline"] == [] + + def test_controller_pipeline_still_calls_file_manipulator(self, controller): + """ + Ensure existing pipeline behavior is preserved. + """ + + incident_text = "Engine arrived at 3:10 PM." + + controller.fill_form( + user_input=incident_text, + fields=["name", "location"], + pdf_form_path="incident_form.pdf" + ) + + controller.file_manipulator.fill_form.assert_called_once() + + def test_invalid_input_handling(self, controller): + """ + Ensure controller handles invalid input gracefully. + """ + + result = controller.fill_form( + user_input="", + fields=[], + pdf_form_path="dummy.pdf" + ) + + assert isinstance(result, dict) + assert "timeline" in result \ No newline at end of file diff --git a/src/timeline_extractor.py b/src/timeline_extractor.py new file mode 100644 index 0000000..5afad21 --- /dev/null +++ b/src/timeline_extractor.py @@ -0,0 +1,157 @@ +""" +timeline_extractor.py + +Incident Timeline Extraction Module for FireForm. + +This module extracts chronological events from incident narratives +and returns structured timeline data. + +Author: FireForm Contributor +""" + +import re +import logging +from datetime import datetime +from dataclasses import dataclass +from typing import List, Optional + + +logger = logging.getLogger(__name__) + + +# Precompiled regex patterns +TIME_PATTERN = re.compile( + r"\b(\d{1,2}:\d{2}\s?(?:AM|PM|am|pm)?|\d{1,2}:\d{2})\b" +) + +SENTENCE_SPLIT_PATTERN = re.compile(r"[.!?\n]+") + + +@dataclass +class TimelineEvent: + """ + Data model representing a timeline event. + """ + event: str + time: str + + +class TimelineExtractor: + """ + Extracts chronological timeline events from incident narratives. + """ + + def __init__(self) -> None: + self.time_pattern = TIME_PATTERN + + def normalize_time(self, time_str: str) -> Optional[str]: + """ + Normalize time string into 24-hour HH:MM format. + """ + time_str = time_str.strip() + + formats = [ + "%I:%M %p", + "%I:%M%p", + "%H:%M", + ] + + for fmt in formats: + try: + parsed = datetime.strptime(time_str, fmt) + return parsed.strftime("%H:%M") + except ValueError: + continue + + logger.warning(f"Unable to normalize time: {time_str}") + return None + + def split_sentences(self, text: str) -> List[str]: + """ + Split narrative into sentences. + """ + sentences = SENTENCE_SPLIT_PATTERN.split(text) + return [s.strip() for s in sentences if s.strip()] + + def extract_events(self, text: str) -> List[TimelineEvent]: + """ + Extract timeline events from narrative text. + """ + events: List[TimelineEvent] = [] + + sentences = self.split_sentences(text) + + for sentence in sentences: + + matches = self.time_pattern.findall(sentence) + + if not matches: + continue + + for time_match in matches: + + normalized = self.normalize_time(time_match) + + if not normalized: + continue + + event_text = sentence.replace(time_match, "").strip() + + event_text = re.sub(r"\s+", " ", event_text) + + if not event_text: + continue + + events.append( + TimelineEvent( + event=event_text, + time=normalized + ) + ) + + return events + + def sort_events(self, events: List[TimelineEvent]) -> List[TimelineEvent]: + """ + Sort events chronologically. + """ + + def parse_time(event: TimelineEvent): + try: + return datetime.strptime(event.time, "%H:%M") + except Exception: + return datetime.min + + return sorted(events, key=parse_time) + + def extract_timeline(self, text: str) -> List[dict]: + """ + Main public API. + + Returns structured timeline data. + """ + + if not text or not isinstance(text, str): + logger.warning("Invalid input provided to timeline extractor.") + return [] + + try: + + events = self.extract_events(text) + + if not events: + return [] + + sorted_events = self.sort_events(events) + + return [ + { + "event": e.event, + "time": e.time + } + for e in sorted_events + ] + + except Exception as exc: + logger.error("Timeline extraction failed.", exc_info=exc) + return [] \ No newline at end of file From f8ac7f15e7a9477eb58192556298a6bc94b86fb9 Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Wed, 18 Mar 2026 22:23:22 +0530 Subject: [PATCH 3/8] fix: remove duplicate methods in controller and clean integration --- src/controller.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/controller.py b/src/controller.py index 8873b16..b8abee4 100644 --- a/src/controller.py +++ b/src/controller.py @@ -24,33 +24,22 @@ def fill_form( fields: List[str], pdf_form_path: str ) -> Dict[str, Any]: - """ - Process the user input and fill the PDF form. - Steps: - 1. Extract timeline events from the incident narrative - 2. Pass the original data to FileManipulator for form filling - 3. Attach timeline data to the result for downstream use - """ - - # Extract timeline from incident text + # Extract timeline timeline = self.timeline_extractor.extract_timeline(user_input) - # Call existing FireForm pipeline + # Run original FireForm pipeline result = self.file_manipulator.fill_form( user_input, fields, pdf_form_path ) - # Attach timeline to the result if possible + # Attach timeline if isinstance(result, dict): result["timeline"] = timeline return result def create_template(self, pdf_path: str) -> Dict[str, Any]: - """ - Generate a template from the provided PDF form. - """ return self.file_manipulator.create_template(pdf_path) \ No newline at end of file From a73e90dd86a929cc6caa977c214c48e10beb8f81 Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Wed, 18 Mar 2026 22:40:04 +0530 Subject: [PATCH 4/8] fix: remove invalid api import from controller timeline tests --- src/test/test_controller_timeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/test_controller_timeline.py b/src/test/test_controller_timeline.py index b6a31d9..680141c 100644 --- a/src/test/test_controller_timeline.py +++ b/src/test/test_controller_timeline.py @@ -1,5 +1,4 @@ import pytest -from api.main import app from unittest.mock import MagicMock from src.controller import Controller From a18f6f62fd4fd0f81288f2e9d391d098cd09c1d4 Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Wed, 18 Mar 2026 22:44:57 +0530 Subject: [PATCH 5/8] fix: remove duplicate fill_form method overriding timeline logic --- src/controller.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/controller.py b/src/controller.py index b8abee4..d325953 100644 --- a/src/controller.py +++ b/src/controller.py @@ -1,5 +1,4 @@ from typing import List, Dict, Any - from src.file_manipulator import FileManipulator from src.timeline_extractor import TimelineExtractor @@ -7,11 +6,7 @@ class Controller: """ Controller layer for FireForm. - - Responsible for orchestrating the processing pipeline: - - Receiving user input - - Extracting timeline information - - Passing processed data to FileManipulator + Responsible for orchestrating the processing pipeline. """ def __init__(self) -> None: @@ -25,17 +20,14 @@ def fill_form( pdf_form_path: str ) -> Dict[str, Any]: - # Extract timeline timeline = self.timeline_extractor.extract_timeline(user_input) - # Run original FireForm pipeline result = self.file_manipulator.fill_form( user_input, fields, pdf_form_path ) - # Attach timeline if isinstance(result, dict): result["timeline"] = timeline From a4beaf21636e158a71adbec38079427598825545 Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Thu, 19 Mar 2026 11:47:09 +0530 Subject: [PATCH 6/8] feat: add AI-powered incident similarity search with FAISS and embeddings --- api/main.py | 3 +- api/similarity_api.py | 15 +++++++++ faiss_index.bin | Bin 0 -> 4653 bytes incidents.pkl | Bin 0 -> 128 bytes pytest.ini | 3 ++ src/__init__.py | 0 src/controller.py | 14 +++++++++ src/incident_similarity.py | 63 +++++++++++++++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 api/similarity_api.py create mode 100644 faiss_index.bin create mode 100644 incidents.pkl create mode 100644 pytest.ini delete mode 100644 src/__init__.py create mode 100644 src/incident_similarity.py diff --git a/api/main.py b/api/main.py index d0b8c79..ed814fe 100644 --- a/api/main.py +++ b/api/main.py @@ -1,7 +1,8 @@ from fastapi import FastAPI from api.routes import templates, forms +from api.similarity_api import router as similarity_router app = FastAPI() - +app.include_router(similarity_router) app.include_router(templates.router) app.include_router(forms.router) \ No newline at end of file diff --git a/api/similarity_api.py b/api/similarity_api.py new file mode 100644 index 0000000..f83b30b --- /dev/null +++ b/api/similarity_api.py @@ -0,0 +1,15 @@ +from fastapi import APIRouter +from src.incident_similarity import IncidentSimilarity + +router = APIRouter() + +similarity_engine = IncidentSimilarity() + + +@router.get("/similar-incidents") +def get_similar_incidents(query: str, top_k: int = 3): + results = similarity_engine.search(query, top_k) + return { + "query": query, + "results": results + } \ No newline at end of file diff --git a/faiss_index.bin b/faiss_index.bin new file mode 100644 index 0000000000000000000000000000000000000000..deac8575ec6f8ea42409071970f06a54b771c8e9 GIT binary patch literal 4653 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 WcfRok?Taxd(TL^9EklntV)#D{4ZlhN literal 0 HcmV?d00001 diff --git a/incidents.pkl b/incidents.pkl new file mode 100644 index 0000000000000000000000000000000000000000..de7dac4bfdec7f208f1639b37e5e541d641efbb3 GIT binary patch literal 128 zcmXAhu@S;B3;-z@L>C$=mf$WcP*Dg)!QvDvC))|>7$BwD7Xn}Z|7-s)yFb@@T$>9z z2P=u87iW+-nNAY#K!FD#)Ku5Q86A*FI*u7;$pMClzP2e;+7uYCFdCWkJ# literal 0 HcmV?d00001 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..c7b23ec --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +pythonpath = . +testpaths = tests diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/controller.py b/src/controller.py index d325953..19f684b 100644 --- a/src/controller.py +++ b/src/controller.py @@ -1,6 +1,10 @@ from typing import List, Dict, Any from src.file_manipulator import FileManipulator from src.timeline_extractor import TimelineExtractor +from src.incident_similarity import IncidentSimilarity + +# Initialize globally (important) +similarity_engine = IncidentSimilarity() class Controller: @@ -20,16 +24,26 @@ def fill_form( pdf_form_path: str ) -> Dict[str, Any]: + # 1. Extract timeline timeline = self.timeline_extractor.extract_timeline(user_input) + # 2. Perform similarity search BEFORE adding new incident + similar_cases = similarity_engine.search(user_input) + + # 3. Fill form result = self.file_manipulator.fill_form( user_input, fields, pdf_form_path ) + # 4. Store new incident AFTER search + similarity_engine.add_incident(user_input) + + # 5. Attach results if isinstance(result, dict): result["timeline"] = timeline + result["similar_incidents"] = similar_cases return result diff --git a/src/incident_similarity.py b/src/incident_similarity.py new file mode 100644 index 0000000..cfcb9a6 --- /dev/null +++ b/src/incident_similarity.py @@ -0,0 +1,63 @@ +from sentence_transformers import SentenceTransformer +import faiss +import numpy as np +import os +import pickle + + +class IncidentSimilarity: + def __init__(self, index_path="faiss_index.bin", data_path="incidents.pkl"): + self.model = SentenceTransformer("all-MiniLM-L6-v2") + self.dimension = 384 + + self.index_path = index_path + self.data_path = data_path + + self.index = faiss.IndexFlatL2(self.dimension) + self.incidents = [] + + self._load() + + def add_incident(self, text: str): + embedding = self.model.encode([text]) + embedding = np.array(embedding).astype("float32") + + self.index.add(embedding) + self.incidents.append(text) + + self._save() + + def search(self, query: str, top_k: int = 3): + if len(self.incidents) == 0: + return [] + + query_embedding = self.model.encode([query]) + query_embedding = np.array(query_embedding).astype("float32") + + distances, indices = self.index.search(query_embedding, top_k) + + results = [] + + for i, idx in enumerate(indices[0]): + if idx < len(self.incidents): + distance = distances[0][i] + similarity_score = 1 / (1 + distance) + results.append({ + "incident": self.incidents[idx], + "score": round(similarity_score, 4) + }) + + return results + + def _save(self): + faiss.write_index(self.index, self.index_path) + with open(self.data_path, "wb") as f: + pickle.dump(self.incidents, f) + + def _load(self): + if os.path.exists(self.index_path): + self.index = faiss.read_index(self.index_path) + + if os.path.exists(self.data_path): + with open(self.data_path, "rb") as f: + self.incidents = pickle.load(f) \ No newline at end of file From 0d6269b4e4605472e6604f1242db2c661213ef9c Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Thu, 19 Mar 2026 11:52:02 +0530 Subject: [PATCH 7/8] chore: remove generated files from tracking --- faiss_index.bin | Bin 4653 -> 0 bytes incidents.pkl | Bin 128 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 faiss_index.bin delete mode 100644 incidents.pkl diff --git a/faiss_index.bin b/faiss_index.bin deleted file mode 100644 index deac8575ec6f8ea42409071970f06a54b771c8e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4653 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 WcfRok?Taxd(TL^9EklntV)#D{4ZlhN diff --git a/incidents.pkl b/incidents.pkl deleted file mode 100644 index de7dac4bfdec7f208f1639b37e5e541d641efbb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 128 zcmXAhu@S;B3;-z@L>C$=mf$WcP*Dg)!QvDvC))|>7$BwD7Xn}Z|7-s)yFb@@T$>9z z2P=u87iW+-nNAY#K!FD#)Ku5Q86A*FI*u7;$pMClzP2e;+7uYCFdCWkJ# From 6a2035baba11caf7d16afb12c3f2aafd0196fdfe Mon Sep 17 00:00:00 2001 From: Mahendra Reddy Date: Thu, 19 Mar 2026 11:53:28 +0530 Subject: [PATCH 8/8] chore: update gitignore for generated files --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7fa2022..3970c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ .idea venv .venv -*.db \ No newline at end of file +*.db +faiss_index.bin +incidents.pkl \ No newline at end of file