Node.js API (public gateway)
Core reconciliation engine
Service Architecture
Two-service split: Node.js handles the public edge, Python runs all intelligence. Inter-service calls are cluster-local HTTP.
CLIENT
CA / Finance Team
Uploads purchase register (Excel) + GSTR-2B (JSON). Receives reconciliation report with ITC risk scores.
Human-in-the-Loop
│ │ │
PUBLIC EDGE, Fastify Gateway
greyquill-api
Fastify + TypeScript. 6 endpoints: health, ai-status, document CRUD, COS upload/download. Graceful 503 when DB/COS not configured.
Node.js 22 · Fastify · Zod
│ │ │
INTERNAL, Python AI Engine
greyquill-ai
FastAPI + 8 specialist modules. Invoice extraction via watsonx, GSTR parsing, validation, normalization, 5-pass reconciliation, ITC risk scoring.
Python 3.13 · FastAPI · uv
│ │ │
BACKING SERVICES
watsonx.ai
mistral-small-3-1-24b for invoice field extraction. Region: us-south.
Granite / Mistral
IBM COS
Object storage for invoices, GSTR JSON, purchase registers. Bucket provisioned per environment.
S3-compatible
PostgreSQL
Documents table (UUID PK, jsonb metadata). Managed Postgres in the deployment region.
Local dev · v1
Module Explorer
Click a tab to browse a module. Click any file card to see its role, key functions, inputs and outputs.
API Gateway
6 files
watsonx
3 files
GSTR Parser
7 files
Excel Parser
5 files
Validator
7 files
Normalizer
3 files
Reconciliation
12 files
ITC Risk
3 files
End-to-End Pipeline
How a purchase register + GSTR-2B JSON become a risk-scored reconciliation report.
Upload
/api/storage/upload
→
Parse
excel_parser / gstr_parser
→
Validate
invoice_validator
→
Normalize
invoice_normalizer
→
Reconcile
reconciliation.engine
→
Score Risk
itc_risk.scorer
→
Report
ReconciliationReport
5-Pass Reconciliation Strategy
Sequential matching with descending strictness. Each pass operates only on entries not yet matched by earlier passes.
Pass 1
IRN_EXACT
Invoice Reference Number (e-invoice). Cryptographically-signed, no ambiguity possible.
1.00confidence
Pass 2
EXACT_MATCH
GSTIN + normalized invoice # + value within tolerance. Standard invoice path.
0.95confidence
Pass 3
FUZZY_INVOICE
GSTIN exact + fuzzy invoice # (0.85 similarity via rapidfuzz). OCR / formatting drift.
0.80confidence
Pass 4
AMOUNT_DATE
GSTIN + amount (±2%) + date (±7 days). Invoice # unrecognisable. Review required.
0.65confidence
Pass 5
LOOSE_MATCH
GSTIN + amount (±1%) only. Last resort. Always flagged for manual review.
0.45confidence
Auto-Resolver Rules
- Rounding diff ≤ Rs 1 + proportional taxes → MATCHED
- Date-only diff ≤ 3 days + exact values → MATCHED
- Invoice # format-only diff → MATCHED
Confidence Adjustments
- +0.05 for exact value match
- +0.03 for exact date match
- ±0.03 for vendor compliance grade (A/B/C)
- -0.05 per month for cross-period matches
Live Deployment
Both services run on IBM Code Engine, built straight from source with buildpacks. No Dockerfiles to maintain.
PlatformIBM Code Engine, serverless containers
TopologyPublic API gateway, cluster-local AI service
BuildBuildpacks from source, no Dockerfiles
ResidencyDeployed to the region you choose
CredentialsHeld in platform secrets, never in the image
Modelwatsonx.ai, for invoice field extraction
What's done
- Phases 0-8: repo, API, Python AI, service connectivity, local DB, COS, watsonx
- Phases 10-15: all 8 AI modules with 162 tests, 88% coverage
- Phase 9: both services deployed to Code Engine, running
Remaining
- Managed Postgres, provisioned in the deployment region
- Wire endpoints, connect AI modules into `/extract`, `/reconcile` handlers
- UI, upload + dashboard for CA reviewers
OCR Pipeline Performance
End-to-end watsonx OCR extraction takes ~35-40 seconds. The LLM does NOT see the image, it only sees the text that OCR already produced.
Step 1
Upload to COS
~1-2s
Step 2
COS Connection
~1-2scached
Step 3
watsonx OCR
~20-30sbottleneck
Step 4
Poll until done
3sinterval
Step 5
Read from COS
~1-2s
Step 6
LLM Extract
~5-10sMistral 24B
Step 7
Post-process
<1mspure Python
LLM Model Comparison (Step 6)
- mistral-small-3.1-24b (current), 24B, ~5-10s. Good at structured JSON extraction
- meta-llama/llama-3-3-70b, 70B, ~10-20s. Better reasoning, but 2x slower
- ibm/granite-3-8b, 8B, ~2-3s. Faster, but may miss edge cases
Why Model Size Matters Less
- OCR is the bottleneck, if OCR garbles a digit, no LLM can fix it
- tables_json cross-referencing catches digit errors better than a bigger model
- Per-field confidence flags uncertain values for human review in the UI