Reconciliation Workbench

Run persistence, per-entry action layer with versioning and rollback, ITC re-scoring on corrections, reminder system, and vendor intimation CSV export, the CA's working surface on top of the reconciliation engine.

← Architecture Overview Reconciliation Engine End-to-End Data Flow Invoice Matching Logic
How It Works

The engine produces ephemeral results. The workbench adds persistence, structured decision-making, and audit trail on top.

Workbench Pipeline

Run Reconciliation
AI engine runs 7 matching passes + auto-resolve + ITC risk scoring. Results displayed in UI (ephemeral).
Save This Run
Creates reconciliation_runs header with summary counts + ITC totals. Bulk-inserts all entries into reconciliation_results with entry snapshots, risk scores, and source document links.
Expand Entry Row
Side-by-side comparison of Books vs GSTR-2B records. Action buttons vary by match_category.
Take Action
Creates versioned entry_actions row. Updates user_action_status. For corrections: calls AI /risk to re-score ITC. For reminders: inserts into reminders table.
Export / Review
Bulk export vendor intimation CSV for entries flagged intimate_vendor. Dismiss reminders from sidebar panel.
Run Persistence

Every reconciliation result can be saved as a named run for comparison, audit, and structured follow-up.

1

Save Run

POST /api/sessions/:id/runs, persists the current engine output with document links and risk scores.

API + DB

What gets saved

  • Run header: label, timestamp, summary counts (matched, mismatched, missing, etc.)
  • ITC totals: claimable, at-risk, blocked amounts
  • Per-entry: full engine snapshot as JSONB, entry_key for stable joins
  • Source links: books_document_id and gstr2b_document_id for "View Source File"
  • Risk scores: risk_score, risk_priority, risk_sla_days, risk_factors[]

Performance: bulk fetch pattern

  • Source document linking requires matching entries to parsed_invoices rows
  • Naive approach: 2 DB queries per entry (N+1 problem, 400 queries for 200 entries)
  • Actual: 2 bulk queries total, results stored in Map<string, {id, document_id}>
  • O(1) lookup per entry via GSTIN|invoice_number_norm composite key
▼ ▼ ▼
2

Load & Manage Runs

Saved runs appear in an accordion. Load, rename, delete, or compare across runs.

Operations

  • GET /api/sessions/:id/runs, list with summary counts
  • GET /api/sessions/:id/runs/:runId, full entries + current actions
  • PATCH, rename run label (inline edit)
  • DELETE, cascade deletes results, actions, and reminders

UI behaviour

  • Accordion at top of Reconciliation page when saved runs exist
  • Click a run → loads entries from DB (not re-running the engine)
  • Status badges on each row reflect user_action_status
  • Expand any row to see the action panel
Per-Entry Actions

Each entry row expands to show a side-by-side comparison and category-specific action buttons.

MISMATCH
Books and GSTR-2B records exist but field values disagree. The CA must decide the correct value.
Enter Correction · Accept Difference · Dispute · Ignore
MISSING_IN_GSTR2B
Invoice exists in books but not in vendor's GSTR-2B filing. ITC cannot be claimed until vendor files.
Flag for Vendor Intimation · Accept Risk · Ignore
MISSING_IN_BOOKS
Invoice appears in GSTR-2B but not in the company's purchase register. Possible unclaimed credit.
Request Document · Mark Verified · Set Reminder · Ignore

RunActionPanel Component

Inline expansion below each entry row. Two-column layout with Books (left) and GSTR-2B (right) records.

React

Left/Right comparison

  • Invoice Number, Date, Value, GSTIN displayed side-by-side
  • Mismatched values highlighted in red
  • "View Source File" link on each side (opens the uploaded document)
  • Risk score badge and priority indicator

Correction flow

  • Numeric input for corrected value
  • "Apply & Re-score" button triggers POST /action with enter_correction
  • Server calls AI /risk endpoint with corrected entry values
  • Updated risk score, priority, SLA returned and displayed immediately
  • Non-fatal errors (rescore failure) returned as warnings[] in response
Versioned Actions & Rollback

Every action creates a new version. Previous versions are preserved for audit and rollback.

Example: Action History for Entry INV-2026/0412

v3
enter_correction, Corrected books value to ₹5,06,467. Risk: LOW (12)
Current
v2
accept_difference, Accepted mismatch. Risk unchanged: HIGH (68)
Superseded · Rollback
v1
ignored, Initial triage, no action taken.
Superseded · Rollback

Create action (transactional)

  • Wrapped in db.begin() for atomicity
  • Step 1: Read current version number
  • Step 2: Set is_current=false, superseded_at=NOW() on all current actions
  • Step 3: Insert new action with version=prev+1, is_current=true
  • Unique constraint: only one is_current=true per result_id

Rollback (transactional)

  • Also wrapped in db.begin()
  • Step 1: Find most recent non-current version
  • Step 2: Supersede the current action
  • Step 3: Restore selected version to is_current=true
  • Action endpoint updates user_action_status on the result row
ITC Re-scoring on Correction

When a CA enters a corrected value that resolves a mismatch, the system re-scores the entry's ITC risk in real time.

Re-score Flow

Enter Correction
CA enters corrected books or GSTR-2B value. Stored in corrected_books_value / corrected_gstr2b_value.
Build Modified Entry
Server overlays corrected values onto the raw_entry_snapshot. If corrected values match within ₹1, sets match_category: MATCHED and clears mismatch_types.
Call AI /risk
Sends modified entry to POST http://AI_SERVICE/risk. Existing ITC risk scorer evaluates with new values.
Update Result
New risk_score, risk_priority, risk_sla_days, risk_factors[] written to the result row. UI updates immediately.
Failure Handling
If the AI service is unavailable, the action still succeeds but the response includes a warnings[] array: "Risk re-scoring failed, scores may be stale".
Reminder System

CAs can set follow-up reminders on specific entries. Reminders surface via a sidebar badge and a dedicated panel.

Creating reminders

  • Triggered by "Set Reminder" action on MISSING_IN_BOOKS entries
  • Due date computed from app_settings.reminders (frequency + time)
  • Stored in reminders table with links to session, run, and result
  • Three reminder types: missing_in_books_verify, missing_in_gstr2b_followup, mismatch_review

Viewing & managing

  • Sidebar badge: orange dot + count on Reconciliation nav link
  • Polls GET /api/reminders/count every 60 seconds
  • RemindersPanel: lists pending reminders with invoice details
  • Each reminder has a "Dismiss" button → PATCH /api/reminders/:id

Settings (Tenant Profile)

  • Frequency: Daily / Weekly / On Due Date
  • Reminder Time: configurable (default 10:00)
  • Stored via PUT /api/settings/reminders in app_settings table
  • Key-value store with JSONB values and UPSERT semantics

Error handling

  • Fetch errors display an error message (not stuck on loading)
  • useEffect cleanup with cancelled flag prevents state updates after unmount
  • If reminder creation fails during an action, the action still succeeds with a warning
Vendor Intimation Export

Bulk export a CSV of entries flagged for vendor follow-up.

CSV Export, GET /api/sessions/:id/runs/:runId/export

Filter
Query param ?filter=intimate_vendor selects entries with user_action_status = 'intimate_vendor'. Default: all MISSING_IN_GSTR2B entries.
Columns
GSTIN, Supplier Name, Invoice Number, Invoice Date, Books Value, GSTR-2B Value, Difference, Period
Format
Content-Type: text/csv with Content-Disposition: attachment. Values with commas/quotes are properly escaped using RFC 4180 double-quote rules.
Database Schema

Three new migrations (009 to 011) add five tables/extensions to the existing schema.

reconciliation_runs

id: UUID PK
session_id: UUID FK → reconciliation_sessions
label: TEXT              # "Run 1", user-renameable
run_at: TIMESTAMPTZ
total_books: INT
total_gstr2b: INT
matched: INT
mismatched: INT
missing_in_gstr2b: INT
missing_in_books: INT
probable_matches: INT
auto_resolved: INT
total_itc_claimable: NUMERIC(15,2)
total_itc_at_risk: NUMERIC(15,2)
total_itc_blocked: NUMERIC(15,2)
# INDEX: session_id

reconciliation_results (added columns)

# Columns added to existing table by migration 009:
run_id: UUID FK → reconciliation_runs
entry_key: TEXT            # "GSTIN|inv_num_norm"
raw_entry_snapshot: JSONB  # full engine entry dict
books_document_id: UUID FK → documents
gstr2b_document_id: UUID FK → documents
user_action_status: TEXT DEFAULT 'pending'
corrected_books_value: NUMERIC(15,2)
corrected_gstr2b_value: NUMERIC(15,2)
# INDEX: run_id, (run_id, user_action_status), entry_key

entry_actions

id: UUID PK
result_id: UUID FK → reconciliation_results
run_id: UUID FK → reconciliation_runs
entry_key: TEXT NOT NULL
action_type: TEXT NOT NULL
corrected_value: NUMERIC(15,2)
correction_field: TEXT       # 'books_value' or 'gstr2b_value'
notes: TEXT
# Versioning:
version: INT DEFAULT 1
is_current: BOOLEAN DEFAULT true
superseded_at: TIMESTAMPTZ
acted_at: TIMESTAMPTZ DEFAULT NOW()
# UNIQUE: (result_id) WHERE is_current = true

reminders

id: UUID PK
session_id: UUID FK → reconciliation_sessions
run_id: UUID FK → reconciliation_runs
result_id: UUID FK → reconciliation_results
entry_key: TEXT NOT NULL
supplier_gstin: TEXT
invoice_number: TEXT
reminder_type: TEXT NOT NULL
status: TEXT DEFAULT 'pending'  # pending|dismissed|snoozed
notes: TEXT
due_at: TIMESTAMPTZ
dismissed_at: TIMESTAMPTZ
# INDEX: (session_id, status), (status, due_at)

app_settings

key: TEXT PK
value: JSONB NOT NULL
updated_at: TIMESTAMPTZ DEFAULT NOW()

# Seeded with:
# key='reminders', value='{"frequency":"daily","time":"10:00"}'
# UPSERT via ON CONFLICT (key) DO UPDATE
API Routes

15 new endpoints added to server.ts.

MethodRoutePurpose
POST/api/sessions/:id/runsSave current reconciliation as a named run
GET/api/sessions/:id/runsList saved runs with summary counts
GET/api/sessions/:id/runs/:runIdLoad full run: entries + current actions
PATCH/api/sessions/:id/runs/:runIdRename run label
DELETE/api/sessions/:id/runs/:runIdDelete run (cascades to results, actions, reminders)
POST/api/runs/:runId/entries/:resultId/actionCreate versioned action on an entry
GET/api/runs/:runId/entries/:resultId/historyFetch all action versions for an entry
POST/api/runs/:runId/entries/:resultId/rollbackRollback to previous action version
GET/api/sessions/:id/runs/:runId/exportDownload vendor intimation CSV
GET/api/settingsGet all app settings
PUT/api/settings/:keyUpsert a setting value
GET/api/remindersList pending reminders (filterable by session_id)
GET/api/reminders/countPending reminder count (for sidebar badge)
PATCH/api/reminders/:idDismiss or snooze a reminder
File Map

All files involved in the workbench feature, grouped by layer.

a database migration
Creates reconciliation_runs table + extends reconciliation_results
a database migration
Creates entry_actions table with versioning columns
a database migration
Creates app_settings + reminders tables, seeds defaults
recon-run.repo.ts
Run CRUD: create, findBySession, findById, updateLabel, deleteRun
recon-result.repo.ts
Result bulk insert, findByRun, updateActionStatus, updateRiskScores
entry-action.repo.ts
Versioned action create (transactional), findByResult, rollback
settings.repo.ts
Key-value settings: get, set (UPSERT), getAll
reminder.repo.ts
Reminder CRUD: create, findPending, updateStatus, getPendingCount
server.ts
15 new route handlers for runs, actions, export, settings, reminders
api.ts
14 new API client functions matching the new endpoints
Reconciliation.tsx
Major overhaul: save/load runs, expandable rows, bulk export bar
RunActionPanel.tsx
Inline action panel: side-by-side comparison, action buttons, history
RemindersPanel.tsx
Pending reminders list with dismiss and error handling
TenantProfile.tsx
Added reminder settings section (frequency + time)
Sidebar.tsx
Orange badge on Reconciliation nav link for pending reminders