CogniQuote is a six-node processing pipeline that accepts insurance application PDFs and returns a risk assessment with a calculated premium and a routing decision.
Manual insurance underwriting is slow and inconsistent. Underwriters read multi-page application forms, cross-reference policy manuals, identify PII that must not leave the organisation, calculate premiums from actuarial tables, and route applications to the right queue. An LLM alone cannot do this reliably -- it hallucinates numbers, mishandles privacy, and has no mechanism to run deterministic formulas. CogniQuote splits the work between an LLM (for unstructured extraction) and deterministic code (for premium calculation and routing), with a privacy layer sitting between them so that PII never reaches the cloud model.
The pipeline is a linear six-node state machine built on LangGraph and executed inside a FastAPI BackgroundTask. Each node reads from and writes to a typed shared state dictionary.
PDF Upload
|
v
[Node 1: OCR] -----> raw_text
| (AWS Textract, or mock text in dev)
v
[Node 2: Privacy] -> anonymized_text + token_map
| (spaCy + Presidio -- runs fully offline)
v
[Node 3: RAG] -----> retrieved_context
| (pgvector similarity search over policy manual chunks)
v
[Node 4: LLM] -----> risk_assessment (JSON)
| (Groq Llama 3.3-70B with structured output)
v
[Node 5: De-anon] -> restored_assessment
| (PII tokens swapped back to original values)
v
[Node 6: Calculate] -> calculated_premium + routing_decision
| (deterministic Python, no LLM)
v
Response (stored in PostgreSQL, polled by frontend)
The privacy shield (Node 2) pseudonymises names, emails, phone numbers, locations, credit card
numbers, and IBANs before the text leaves the server. The LLM (Node 4) receives only placeholder
tokens like <PERSON_1>. After the LLM returns its structured result, Node 5 restores the original
values in the JSON payload. This means the cloud LLM never sees real PII.
The risk calculator (Node 6) is a set of hardcoded rules. It looks up a base premium and a risk
multiplier from a policy-type table, applies the LLM's risk score to the formula
base * (1 + risk_score * multiplier), then picks a routing bucket:
- risk_score <= 40: INSTANT_QUOTE
- risk_score <= 70: RISK_SUMMARY
- risk_score > 70 or unknown policy type: HUMAN_REVIEW
- Mock mode for local development. Setting
MOCK_MODE=truein the environment bypasses AWS Textract and uses a hardcoded sample insurance form. No AWS credentials needed for local work. - Structured LLM output. The LLM is forced to return a Pydantic-validated JSON schema via
LangChain's
with_structured_output(). The risk_score is constrained to 0--100, the recommendation is aLiteraltype, and the whole object is validated before it enters the deterministic stage. - Selective retries. Both the OCR service and the LLM service use tenacity with exponential backoff, but the LLM retry logic explicitly skips non-transient errors (rate limiting, auth failures, token limit errors) and only retries on 5xx server errors or connection failures.
- Input truncation. Application text is capped at 4,000 characters and RAG context at 5,000 characters before being sent to the LLM, keeping token usage within Groq free-tier limits.
- Lazy vector store initialisation. The PGVector connection is created on first use rather than at import time, so the API starts even if the database container is still booting.
- Single-page frontend with polling. The React dashboard uploads a file, receives a job_id
immediately, then polls
/api/v1/job-status/{job_id}every two seconds until the pipeline completes. There is no WebSocket or SSE -- this keeps the architecture simple.
- FastAPI (Python 3.11)
- LangGraph, LangChain Core
- Groq API (Llama 3.3-70B-Versatile)
- spaCy (en_core_web_lg) + Microsoft Presidio
- AWS Textract (OCR, with mock fallback)
- HuggingFace sentence-transformers (all-MiniLM-L6-v2)
- PostgreSQL with pgvector
- SQLAlchemy ORM
- tenacity (retry logic)
- Docker, Docker Compose
- Next.js 16, React 19, TypeScript
- Tailwind CSS v4, Framer Motion
- Axios
CogniQuote_Project/
cogniquote_backend/
app/
agent/
graph.py # LangGraph pipeline definition
state.py # TypedDict for pipeline state
db/
models.py # SQLAlchemy Document & Assessment models
session.py # Database connection and session factory
services/
llm.py # Groq LLM integration with structured output
ocr.py # AWS Textract integration with mock mode
privacy.py # PII detection and pseudonymisation
storage.py # Database CRUD helpers
main.py # FastAPI application and endpoints
policy_manuals/ # HDFC ERGO policy PDFs for RAG ingestion
scripts/
ingest_policies.py # PDF chunking and vector store population
docker-compose.yml
Dockerfile
requirements.txt
rules.md
.env.example
cogniquote_frontend/
app/
layout.tsx # Root layout with metadata
page.tsx # Underwriter dashboard (single page)
globals.css
package.json
tsconfig.json
next.config.ts
.gitignore
- Docker and Docker Compose
- Node.js 18 or later
- A Groq API key (for LLM inference)
- AWS credentials (optional -- only needed for live Textract)
- Copy the environment template and fill in your keys:
cd cogniquote_backend
cp .env.example .env
Required variables:
GROQ_API_KEY-- your Groq API keyMOCK_MODE-- set totruefor local development (bypasses AWS Textract)AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEY-- only needed ifMOCK_MODE=falseAWS_S3_BUCKET-- only needed ifMOCK_MODE=falseDATABASE_URL-- defaults to the Docker Compose service; override if needed
- Start the API and the pgvector database:
docker-compose up --build -d
The API will be available at http://localhost:8000. The database runs on port 5432.
- (First run only) Ingest the policy manuals into the vector store:
docker-compose exec api python scripts/ingest_policies.py
This loads the PDFs from policy_manuals/, chunks them, embeds them with all-MiniLM-L6-v2, and
stores the vectors in PostgreSQL.
cd cogniquote_frontend
npm install
npm run dev
The dashboard will be available at http://localhost:3000.
Upload an insurance document for processing. Returns immediately with a job_id.
Request: multipart/form-data with a file field (PDF, PNG, JPG, or TIFF).
Response (200):
{
"job_id": "a1b2c3d4-...",
"status": "PROCESSING"
}Poll for the result of a processing job.
Response while processing:
{
"job_id": "a1b2c3d4-...",
"status": "PROCESSING",
"filename": "application.pdf"
}Response when complete:
{
"job_id": "a1b2c3d4-...",
"status": "COMPLETED",
"filename": "application.pdf",
"anonymized_text": "INSURANCE APPLICATION FORM...\nApplicant Name: <PERSON_1>...",
"pipeline_trace": [
"ocr_node: extracted text",
"privacy_node: PII pseudonymized",
"rag_retrieval_node: retrieved 3 chunks",
"llm_node: risk assessed",
"de_anonymize_node: PII restored",
"calculate_risk_node: premium 15750.00, route INSTANT_QUOTE"
],
"assessment": {
"policy_type": "Comprehensive Motor Insurance",
"vehicle_value": 1250000,
"claim_history_summary": "1 claim in last 3 years...",
"risk_score": 25,
"recommendation": "APPROVE",
"reasoning": "...",
"calculated_premium": 15750.0,
"routing_decision": "INSTANT_QUOTE"
}
}Returns all past assessments, ordered by most recent first.
{
"status": "success",
"count": 5,
"results": [...]
}- The six-node pipeline runs end-to-end: OCR, PII pseudonymisation, RAG retrieval, LLM analysis, PII restoration, and deterministic routing.
- Mock mode allows local development and testing without AWS credentials.
- The vector store is populated from seven HDFC ERGO policy manuals covering motor, health, critical illness, personal accident, and wellbeing products.
- The policy-type rules in
POLICY_RULES_DB(in graph.py) are hardcoded for three policy types plus a default fallback. Adding a new policy type requires editing the dictionary and restarting. - The frontend polls for results. There is no push notification or real-time socket.
- The LLM prompt is tuned for motor insurance applications. Other lines of business (e.g. health insurance forms) may produce lower-quality assessments without prompt adjustments.
- Input truncation at 4,000 characters means very long application forms (30+ pages) may lose tail content. This is a trade-off for staying within Groq free-tier token limits.
- There is no authentication layer on the API or the frontend.
MIT