SchemeAI is a Next.js (App Router) application that helps people in India find government schemes, scholarships and welfare programmes they may qualify for. A user fills in a short profile. A deterministic, rule-based eligibility engine checks that profile against a curated reference catalogue of schemes. Every scheme record links back to an official government source and carries transcribed guideline excerpts as evidence.
This document covers how the system fits together. It is based on the current source tree.
The application has five main parts:
- A single-page client (
src/app/page.tsxrendersAppShell). It switches views from client state instead of using URL routes. - JSON API route handlers under
src/app/api. - Domain modules in
src/lib: the eligibility engine, scheme catalogue, auth, live source checks, the TinyFish client and serializers. - A PostgreSQL database accessed through Prisma (
prisma/schema.prisma). - External services:
- official government websites (live HTTP checks)
- the optional TinyFish browser-agent API (live extraction from official pages)
- an optional LLM reached through
z-ai-web-dev-sdkfor the assistant
flowchart LR
subgraph Browser
UI["AppShell + views"]
Store["Zustand store (localStorage)"]
end
subgraph NextServer["Next.js server"]
API["API route handlers /api/*"]
subgraph Lib["src/lib"]
Elig["eligibility.ts"]
Cat["schemes catalogue"]
Auth["auth.ts"]
Live["live-check.ts"]
TF["tinyfish.ts"]
Ser["serialize.ts"]
DBC["db.ts (Prisma client)"]
end
Asst["/api/assistant"]
end
PG[("PostgreSQL")]
Gov["Official .gov.in sites"]
TinyFish["TinyFish agent API (optional)"]
LLM["LLM via z-ai-web-dev-sdk (optional)"]
UI --> Store
UI -->|"fetch JSON"| API
API --> Elig
API --> Auth
API --> Live
API --> TF
API --> Ser
API --> DBC
Asst --> DBC
Asst --> LLM
Cat -->|"seed via /api/setup or scripts"| DBC
DBC --> PG
Live -->|"HEAD / GET"| Gov
Live --> TF
TF -->|"POST /v1/automation/run"| TinyFish
TinyFish -->|"reads public pages"| Gov
- The engine is rule-based and explains every result.
eligibility.tsnever calls an LLM. Every verdict traces back to a stored rule and a profile field. - Missing data is reported, not guessed. If a profile field is absent, the rule's status is
UNKNOWN, not a guess. BPL/SECC status (isBpl) is never collected from the profile and always resolves toUNKNOWN. - Verification status reflects real checks only. Schemes start as
REFERENCE. They move toLIVE_CHECKEDonly after a real HTTP check of their source domain succeeds or a TinyFish refresh of their official page returns data. - Live data is stored as evidence, not merged. TinyFish extractions go to
FetchJob.resultJsonandLIVE_FETCHevidence rows with the run ID; curated scheme fields are never overwritten. - Integrations degrade cleanly. Without
TINYFISH_API_KEYthe integration reportsNOT_CONFIGUREDand only direct checks run. When the LLM call fails, the assistant falls back to a deterministic, retrieval-based answer.
src/app/page.tsx renders one component, AppShell. Navigation runs on a view value in the Zustand store (src/lib/store.ts), not on Next.js routes. ViewRouter in app-shell.tsx switches on view.name.
View (view.name) |
Component | Purpose | API calls |
|---|---|---|---|
home |
home.tsx |
Landing page, category tiles, recent schemes, live source verification section (the run button is shown to admins; other visitors see a note pointing to the scheme-page Verify button) | GET /api/schemes, POST /api/discovery (admin) |
discover |
wizard.tsx |
4-step profile wizard (basics, education, household, additional) | none (writes profile to store) |
results |
results.tsx |
Ranked match results for the stored profile | POST /api/schemes/match |
browse |
browse.tsx |
Search, filter and paginate the catalogue | GET /api/schemes |
scheme |
scheme-detail.tsx |
Scheme detail, rule-by-rule evaluation, evidence, document checklist, Verify button (source check plus TinyFish refresh, showing the extracted details) | GET /api/schemes/:id, GET /api/sources, POST /api/sources/verify (with schemeId) |
compare |
compare.tsx |
Side-by-side comparison of up to 3 schemes | POST /api/schemes/compare |
dashboard |
dashboard.tsx |
Saved and recent schemes, recommendations, application tracker | GET /api/schemes/:id, POST /api/schemes/match |
admin |
admin.tsx |
System metrics, source registry, per-source checks, Run TinyFish discovery, recent jobs. The nav link is shown only to admins | GET /api/admin/stats, POST /api/sources/verify, POST /api/discovery |
auth |
auth-view.tsx |
Sign in and register | /api/auth/* |
Components used across views:
assistant.tsx: the floating "Ask SchemeAI" widget (POST /api/assistant).bits.tsx: status, verification and category badges;SchemeCard; loading and empty states.i18n.tsx: an English/Hindi dictionary andI18nProvider.
The schemeai-store key in localStorage holds the persisted Zustand state:
profile,hasProfilesavedIds,recentIds,compareIds(max 3)trackerandchecklistsuserandlastMatches
The app works for guests. Saved schemes and tracker status are kept locally. For signed-in users they are also sent to /api/saved and /api/tracker. On mount, AppShell calls GET /api/auth/me and merges the server's saved IDs and tracker into local state.
The hero panel on the home page (DEMO_PROFILE, DEMO_MATCHES in home.tsx) contains static example content. It is not computed.
All handlers are in src/app/api. They return JSON and validate request bodies with Zod where a body is accepted.
| Route | Method(s) | Auth | Description |
|---|---|---|---|
/api/schemes |
GET | none | List with case-insensitive q, category, state, level (national or state), sort (relevance, name, category, recent), page, perPage (max 48) |
/api/schemes/[id] |
GET | none | Detail by id or slug. An optional ?profile=<url-encoded JSON> adds a match evaluation |
/api/schemes/match |
POST | none | Evaluates every scheme against a Zod-validated profile. Returns results sorted by relevanceScore plus a status summary |
/api/schemes/compare |
POST | none | 1 to 3 scheme IDs, with optional profile evaluation |
/api/assistant |
POST | none | Retrieval-grounded Q&A (see section 7.3) |
/api/sources |
GET | none | Government source registry with last real check state and TinyFish status |
/api/sources/verify |
POST | none with schemeId, otherwise admin |
Real HTTP check of one source; with schemeId, also a TinyFish refresh of that scheme (6 h cache). maxDuration 180 s |
/api/discovery |
POST, GET | POST admin, GET none | POST checks up to 5 sources with a direct check and, when configured, a TinyFish extraction in parallel (2 TinyFish runs at a time). maxDuration 300 s. GET returns the last 30 fetch jobs |
/api/auth/register |
POST | none | Creates a user (password at least 8 characters), starts a session, writes an AuditLog row |
/api/auth/login |
POST | none | Verifies the password and starts a session |
/api/auth/logout |
POST | session | Deletes the session row and cookie |
/api/auth/me |
GET | session | Current user, saved scheme IDs, tracker and stored profile |
/api/saved |
GET, POST, DELETE | session | Saved schemes for the signed-in user |
/api/tracker |
GET, PUT | session | Self-reported application status per scheme |
/api/admin/stats |
GET | admin | Counts, source list, recent jobs, TinyFish status. 401 without a session, 403 for non-admins |
/api/health |
GET | none | DB connectivity plus scheme and source counts. Returns 503 when degraded |
/api/setup |
POST | SETUP_SECRET |
One-time seed. Needs ?setup=1 and the x-setup-secret header |
| Module | Responsibility |
|---|---|
eligibility.ts |
Rule evaluation, OR-group logic, scheme-level status, relevance scoring, bilingual explanations, STATUS_META display mapping |
schemes/ |
The static reference catalogue (33 schemes, 22 sources) and the shared seed types, education levels, states and categories |
auth.ts |
scrypt password hashing, DB-backed session tokens (SHA-256 hashed at rest), httpOnly cookie schemeai_session with a 14-day TTL, getSessionUser, requireAdmin |
live-check.ts |
performLiveCheck (HEAD then GET, 12 s timeout, <title> extraction), verifySourceAndRecord (persists a FetchJob, updates the GovernmentSource row, promotes linked schemes) and refreshSchemeWithTinyFish (SCHEME_REFRESH job, LIVE_FETCH evidence, 6 h reuse of successful runs) |
tinyfish.ts |
TinyFish config and status (NOT_CONFIGURED or READY); tinyFishRun (POST /v1/automation/run, X-API-Key, 90 s agent limit, 120 s client timeout, COMPLETED / BLOCKED handling); goal prompts tinyFishExtractSourcePage and tinyFishExtractSchemePage; compactTinyFishResult for storage |
serialize.ts |
Converts Prisma rows to summary or detail JSON. Parses documentsJson and processJson |
types.ts |
Client-safe types that mirror the serializer output |
store.ts |
Client Zustand store (see 2.1) |
i18n.tsx |
English/Hindi dictionary and React context |
db.ts |
Prisma client singleton. Uses POSTGRES_PRISMA_URL if set, otherwise DATABASE_URL |
Prisma uses the postgresql provider (prisma/schema.prisma). JSON payloads are stored as String columns and parsed in application code:
- rule
value documentsJson,processJsonresultJson,checklistJson
Enumerations are also plain strings, with allowed values listed in schema comments. The data model is in section 4.
| Integration | Where | Behaviour |
|---|---|---|
| Official government sites | live-check.ts |
Real read-only HEAD and GET requests to each registered GovernmentSource.domain. No login, form or CAPTCHA automation |
| TinyFish | tinyfish.ts, live-check.ts, /api/discovery, /api/sources/verify |
Used only when TINYFISH_API_KEY is set. Sends POST {TINYFISH_API_URL}/v1/automation/run (default base https://agent.tinyfish.ai) with { url, goal, browser_profile: "lite", agent_config }. Results are stored in FetchJob.resultJson and, for scheme refreshes, as LIVE_FETCH evidence. Details in TINYFISH.md |
| LLM | /api/assistant |
Loads z-ai-web-dev-sdk dynamically and calls ZAI.create() then chat.completions.create. If anything fails or the answer is empty, the response is built deterministically (mode: "DETERMINISTIC") |
.
├── prisma/
│ └── schema.prisma # PostgreSQL data model (users, schemes, rules, sources, jobs)
├── scripts/
│ ├── seed.ts # Idempotent seed of sources + schemes + rules + evidence (npm run db:seed)
│ ├── create-admin.ts # Creates the demo admin user (npm run db:seed:admin)
│ └── copy-standalone.js # Copies static/public into .next/standalone (only for standalone output builds)
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout, fonts, metadata, Toaster
│ │ ├── page.tsx # Renders <AppShell/>, the whole UI
│ │ ├── globals.css # Tailwind v4 styles and theme tokens
│ │ └── api/
│ │ ├── schemes/route.ts # List / search / filter / paginate
│ │ ├── schemes/[id]/route.ts # Detail (+ optional profile match)
│ │ ├── schemes/match/route.ts # Run eligibility engine over all schemes
│ │ ├── schemes/compare/route.ts # Compare up to 3 schemes
│ │ ├── assistant/route.ts # Retrieval-grounded Q&A with LLM fallback
│ │ ├── sources/route.ts # Source registry
│ │ ├── sources/verify/route.ts # Live check of one source
│ │ ├── discovery/route.ts # Batch live discovery + job history
│ │ ├── auth/{register,login,logout,me}/route.ts # Session auth
│ │ ├── saved/route.ts # Saved schemes (signed-in)
│ │ ├── tracker/route.ts # Application tracker (signed-in)
│ │ ├── admin/stats/route.ts # Admin metrics
│ │ ├── health/route.ts # Health check
│ │ └── setup/route.ts # Secret-protected one-time seed
│ ├── components/
│ │ ├── schemeai/ # App views (see section 2.1)
│ │ └── ui/ # shadcn/ui primitives
│ ├── hooks/ # use-mobile, use-toast
│ └── lib/
│ ├── eligibility.ts # Eligibility engine
│ ├── auth.ts # Password hashing + sessions
│ ├── db.ts # Prisma client singleton
│ ├── live-check.ts # Real HTTP source verification + TinyFish scheme refresh
│ ├── tinyfish.ts # TinyFish API client and goal prompts
│ ├── serialize.ts # Prisma row to API JSON
│ ├── store.ts # Zustand client store
│ ├── i18n.tsx # English/Hindi dictionary
│ ├── types.ts # Client-side API types
│ └── schemes/
│ ├── index.ts # ALL_SEED_SCHEMES, ALL_SEED_SOURCES
│ ├── types.ts # SeedScheme, SeedRule, SeedSource, reference lists
│ └── data/
│ ├── scholarships.ts # 9 scholarship/education schemes
│ ├── welfare.ts # 16 welfare/health/housing/insurance schemes
│ ├── livelihood.ts # 8 agriculture/skills/entrepreneurship schemes
│ └── sources.ts # 22 official government sources
├── tests/
│ └── eligibility.test.ts # Assertion script for the engine (npm test)
├── next.config.ts # Next config (ignoreBuildErrors, no strict mode)
├── vercel.json # Vercel build config, region bom1
├── .env.example # Environment variable template
├── download/ # README screenshots
└── DEPLOY-VERCEL.md # Step-by-step Vercel + Neon deployment guide
This diagram is generated from prisma/schema.prisma.
erDiagram
User ||--o{ Session : has
User ||--o| UserProfile : has
User ||--o{ SavedScheme : saves
User ||--o{ ApplicationTracker : tracks
GovernmentSource |o--o{ Scheme : "publishes"
GovernmentSource |o--o{ FetchJob : "checked by"
Scheme ||--o{ SchemeEligibilityRule : "has rules"
Scheme ||--o{ SourceEvidence : "has evidence"
Scheme ||--o{ SavedScheme : "saved as"
Scheme ||--o{ ApplicationTracker : "tracked as"
User {
String id PK
String email UK
String name
String passwordHash
String role
DateTime createdAt
DateTime updatedAt
}
Session {
String id PK
String token UK
String userId FK
DateTime expiresAt
DateTime createdAt
}
UserProfile {
String id PK
String userId FK
Int age
String state
String district
String gender
String educationLevel
String employmentStatus
String occupation
Int annualIncome
String residence
String socialCategory
Boolean isFarmer
Boolean hasDisability
Int disabilityPercent
Boolean isMinority
Boolean isWidow
Boolean consentGiven
DateTime updatedAt
}
GovernmentSource {
String id PK
String name UK
String domain UK
String jurisdiction
String department
String accessMethod
String accessStatus
Boolean automationSupported
String reliability
String notes
DateTime lastCheckedAt
String lastCheckStatus
String lastCheckTitle
DateTime lastSuccessAt
DateTime lastFailureAt
}
Scheme {
String id PK
String slug UK
String name
String nameHi
String shortDescription
String shortDescriptionHi
String description
String category
String ministry
String department
String jurisdiction
String state
String beneficiaryType
String benefits
String benefitAmount
String applicationUrl
String infoUrl
String applicationWindow
String applicationDeadline
String documentsJson
String processJson
String keywords
String verificationStatus
String extractionMethod
String confidence
DateTime dataCompiledAt
DateTime lastLiveCheckedAt
String sourceId FK
}
SchemeEligibilityRule {
String id PK
String schemeId FK
String attribute
String operator
String value
Boolean required
String label
String labelHi
String ruleGroup
}
SourceEvidence {
String id PK
String schemeId FK
String sourceUrl
String excerpt
String evidenceType
String note
DateTime retrievedAt
}
FetchJob {
String id PK
String type
String mode
String status
String sourceId FK
String schemeId
String targetUrl
String resultJson
String error
DateTime startedAt
DateTime completedAt
}
SavedScheme {
String id PK
String userId FK
String schemeId FK
DateTime createdAt
}
ApplicationTracker {
String id PK
String userId FK
String schemeId FK
String status
String notes
String checklistJson
DateTime updatedAt
}
AuditLog {
String id PK
String actor
String action
String entity
String detail
DateTime createdAt
}
Notes:
SavedSchemeandApplicationTrackerare unique on(userId, schemeId). User-owned rows cascade on user delete. Rule, evidence, saved and tracker rows cascade on scheme delete.FetchJob.schemeIdis a plain column with no Prisma relation. It is set bySCHEME_REFRESHjobs.AuditLogis standalone. Today it is written only on user registration.- Status values stored as strings:
Scheme.verificationStatus:REFERENCE,LIVE_CHECKED,STALEFetchJob.type:SOURCE_VERIFY,LIVE_DISCOVERY,SCHEME_REFRESH;FetchJob.mode:DIRECT_FETCH,TINYFISHFetchJob.status:QUEUED,RUNNING,SUCCEEDED,FAILED,SKIPPED_NOT_CONFIGUREDSourceEvidence.evidenceType:GUIDELINE_EXCERPT(seeded),LIVE_FETCH(TinyFish scheme refresh, at most one row per scheme)ApplicationTracker.status:INTERESTED,PREPARING_DOCUMENTS,APPLIED_EXTERNALLY,UNDER_REVIEW,COMPLETED
- Several values defined in the schema are never written by the code:
STALESKIPPED_NOT_CONFIGUREDandQUEUEDTINYFISH_LIVEandDIRECT_FETCHextraction methods (every scheme keepsSEED_REFERENCE; a TinyFish refresh adds evidence but does not change this field)
Source: src/lib/eligibility.ts. Callers: /api/schemes/match, /api/schemes/[id] and /api/schemes/compare.
Each SchemeEligibilityRule has these fields:
attribute: a profile field, orisBploperatorvalue: JSON-encodedrequired- bilingual labels
- optional
ruleGroup
evaluateRule returns MET, FAILED or UNKNOWN, together with English and Hindi explanations, the user's value and a requirement text.
| Operator | Semantics |
|---|---|
MIN / MAX |
Numeric comparison user >= value or user <= value. Non-numeric values give UNKNOWN |
EQ |
String equality |
IN / NOT_IN |
Membership in a JSON array |
TRUE / FALSE |
Boolean flag must be true or must be false |
| other | UNKNOWN ("unsupported operator") |
Two special cases:
- A missing profile value always gives
UNKNOWN. isBplalways givesUNKNOWN, because BPL/SECC listing must be checked against official records.
groupEvaluations buckets rules by ruleGroup. A rule without a group is its own bucket.
- A group's status is
METif any member isMET,FAILEDif every member isFAILED, andUNKNOWNotherwise. - Groups are ANDed together. Only buckets that contain at least one
requiredrule decide the scheme status. Optional rules only add reasons or "to verify" items.
The scheme status is decided as follows:
| Condition | Status |
|---|---|
| No rules at all | UNABLE_TO_DETERMINE (score 20, band EXPLORE) |
Any required bucket FAILED |
NOT_ELIGIBLE |
All required buckets MET |
LIKELY_ELIGIBLE |
Some required bucket UNKNOWN |
POSSIBLY_ELIGIBLE |
| Otherwise | UNABLE_TO_DETERMINE |
The relevance score is used for ranking. The UI does not present it as a probability of eligibility. Points are added as follows, and the total is capped at 100:
| Component | Points |
|---|---|
| Status base | Likely 45, Possibly 25, Unable 8, Not eligible 4 |
| Jurisdiction | +20 if the scheme state equals the profile state; +10 for national schemes (state null) |
| Category affinity | +15 when one of these holds: Agriculture with farmer; Scholarship with student; Skill Development with unemployed or age 45 or under; Entrepreneurship with a business, vendor or artisan occupation; Girl Child & Women with female; Social Security with age 60 or over |
| Info completeness | round(evaluable rules / total rules × 15) |
The score maps to a band: 68 or more is HIGH_RELEVANCE, 42 or more is POTENTIAL_MATCH, and anything lower is EXPLORE.
SchemeMatchResult contains:
reasons: explanations fromMETbuckets, plus a state-residency note when it appliestoVerify: explanations fromUNKNOWNbuckets- the full per-rule
ruleslist, from which the UI showsFAILEDitems infoCompleteness
All text comes in both English and Hindi. Separately from the engine, each scheme's SourceEvidence rows (guideline excerpts transcribed at compile time) and sourceMeta (last real check) are returned by GET /api/schemes/:id and shown next to the evaluation.
flowchart TD
A["Profile + scheme rules"] --> B{"Any rules?"}
B -- No --> U0["UNABLE_TO_DETERMINE, score 20"]
B -- Yes --> C["evaluateRule for each rule"]
C --> C1{"attribute is isBpl or value missing?"}
C1 -- Yes --> R1["UNKNOWN"]
C1 -- No --> C2["Apply operator MIN, MAX, EQ, IN, NOT_IN, TRUE, FALSE"]
C2 --> R2["MET or FAILED"]
R1 --> G["Group by ruleGroup: OR within group"]
R2 --> G
G --> H["Keep buckets with a required rule"]
H --> I{"Any bucket FAILED?"}
I -- Yes --> NE["NOT_ELIGIBLE"]
I -- No --> J{"All buckets MET?"}
J -- Yes --> LE["LIKELY_ELIGIBLE"]
J -- No --> PE["POSSIBLY_ELIGIBLE"]
NE --> S["Score: base + jurisdiction + affinity + completeness"]
LE --> S
PE --> S
S --> BAND["Band: HIGH_RELEVANCE, POTENTIAL_MATCH or EXPLORE"]
BAND --> OUT["reasons, toVerify, rules, infoCompleteness"]
sequenceDiagram
participant W as Wizard view
participant S as Zustand store
participant R as Results view
participant API as Match API route
participant DB as PostgreSQL
W->>S: setProfile(draft)
S->>R: go results
R->>API: POST profile
API->>API: Zod validate profile
API->>DB: findMany schemes with rules and source
API->>API: evaluateScheme for each scheme
API-->>R: results sorted by score plus summary
The tests for the engine are in tests/eligibility.test.ts, a plain assertion script run with npm test (tsx tests/eligibility.test.ts).
The reference catalogue lives in TypeScript under src/lib/schemes/data. It is the single source of truth for seeding. Each SeedScheme includes:
- descriptive fields
- documents and process steps
rulesevidenceexcerptssourceName, which links it to aSeedSource
The catalogue has 33 schemes. 31 are national and 2 are state schemes (Madhya Pradesh and Uttar Pradesh).
Seeding is done by scripts/seed.ts or /api/setup. Both:
- upsert sources by
domain - upsert schemes by
slug(new schemes use the slug as theirid) - replace each scheme's rules and
GUIDELINE_EXCERPTevidence (LIVE_FETCHevidence is kept)
Source check state (accessStatus, lastChecked*) is never touched by seeding.
stateDiagram-v2
[*] --> REFERENCE : seeded
REFERENCE --> LIVE_CHECKED : source HTTP check succeeds
REFERENCE --> LIVE_CHECKED : TinyFish scheme refresh succeeds
LIVE_CHECKED --> LIVE_CHECKED : later checks or refreshes
A failed check sets the source's accessStatus to RESTRICTED but does not downgrade schemes. Nothing in the code moves a scheme to STALE.
The flow, implemented in auth.ts:
- Register or login creates a random 32-byte token.
- The SHA-256 hash of the token is stored in
Session.token. - The raw token goes into the
schemeai_sessioncookie (httpOnly,sameSite=lax,securein production, 14 days). getSessionUserlooks up the hashed token and deletes expired sessions.
Passwords are stored as salt:scrypt(64). They are compared with timingSafeEqual. next-auth is listed as a dependency but is not used.
- Single source (
POST /api/sources/verify,verifySourceAndRecord), admin only unless aschemeIdis sent:- creates a
FetchJob(SOURCE_VERIFY,DIRECT_FETCH) - runs
performLiveCheck - updates the job and the source
- on success, sets linked schemes to
LIVE_CHECKED
- creates a
- Scheme refresh (same route with
schemeId, open to everyone, TinyFishREADYonly), run in parallel with the source check byrefreshSchemeWithTinyFish:- reuses the last
SUCCEEDEDSCHEME_REFRESHjob if it finished within 6 hours - otherwise creates a
SCHEME_REFRESH/TINYFISHjob and runstinyFishExtractSchemePageagainst the scheme'sinfoUrl - on success, replaces the scheme's
LIVE_FETCHevidence and sets it toLIVE_CHECKED
- reuses the last
- Batch discovery (
POST /api/discovery, admin only): takes up to 5 sources, either the givensourceIdsor the least-recently-checked ones. For each source it runs the direct HTTP check and, when TinyFish isREADY,tinyFishExtractSourcePagein parallel (at most 2 sources at a time). The job succeeds if either check succeeds; a TinyFish failure never hides the direct-check result. The extraction (scheme names, announcements, deadlines) is stored inFetchJob.resultJsonand returned to the admin UI.
/api/assistant works in three steps:
- Retrieval. Loads all schemes and scores each one by keyword overlap with the question, plus a large boost for a context
schemeId. It keeps the top 5. - Prompting. Builds a grounded context (benefits, rule labels, official URL) and a strict system prompt: answer only from context, cite sources, optionally reply in Hindi.
- Answer. Tries the LLM through
z-ai-web-dev-sdk. If that fails or the answer is empty, it returns a deterministic bulleted list of the retrieved schemes with their source URLs.
The response includes mode (AI_GROUNDED or DETERMINISTIC), cited schemes and a disclaimer.
Variables, from .env.example and their uses in code. The assistant has no variable of its own; its LLM access is configured by z-ai-web-dev-sdk itself.
| Variable | Required | Used by | Purpose |
|---|---|---|---|
DATABASE_URL |
Yes | prisma/schema.prisma, db.ts |
PostgreSQL connection string (pooled Neon URL recommended) |
POSTGRES_PRISMA_URL |
No | db.ts |
If set, used at runtime instead of DATABASE_URL (Vercel Postgres convention). Not in .env.example |
SETUP_SECRET |
For /api/setup |
/api/setup |
Shared secret checked against the x-setup-secret header |
TINYFISH_API_KEY |
No | tinyfish.ts |
Enables the TinyFish integration (READY) |
TINYFISH_API_URL |
No | tinyfish.ts |
API base URL (default https://agent.tinyfish.ai) |
NODE_ENV |
Set by Next | auth.ts, db.ts |
Secure cookies in production. Prisma singleton caching outside production |
Build and runtime configuration:
next.config.ts:typescript.ignoreBuildErrors: true,reactStrictMode: false. Nooutputmode is set.vercel.json: frameworknextjs,npm run build, regionbom1(Mumbai).package.json(nameschemeai):buildrunsprisma generate && next build, andpostinstallrunsprisma generate. Seeds run withtsx.npm testruns the engine tests andnpm run typecheckrunstsc --noEmit.- Route segment config:
/api/sources/verifyexportsmaxDuration = 180and/api/discoveryexportsmaxDuration = 300, so TinyFish runs are not cut off on serverless hosts whose plan allows those limits.
The documented target (DEPLOY-VERCEL.md) is Vercel serverless functions in the bom1 region, backed by a Neon PostgreSQL database.
flowchart LR
Dev["Developer"] -->|"git push"| GH["GitHub repo"]
GH -->|"auto build: prisma generate, next build"| V["Vercel project, region bom1"]
User["Browser"] -->|"HTTPS"| V
V -->|"Prisma, pooled URL"| Neon[("Neon PostgreSQL")]
V -->|"live checks"| Gov["Official .gov.in sites"]
V -.->|"optional"| TF["TinyFish API (agent.tinyfish.ai)"]
TF -.-> Gov
V -.->|"optional"| LLM["LLM provider via SDK"]
Op["Operator"] -->|"POST /api/setup?setup=1 with x-setup-secret"| V
Op -.->|"or: prisma db push + npm run db:seed"| Neon
Two options:
- Local CLI against the production URL:
npx prisma db push, thennpm run db:seed, thennpm run db:seed:admin. - HTTP:
POST /api/setup?setup=1with the headerx-setup-secret: <SETUP_SECRET>. This upserts sources, schemes, rules and evidence, and creates the demo admin account. It does not create tables, so the schema must already exist (viaprisma db push).
Afterwards, GET /api/health reports DB connectivity and the scheme and source counts. The build never needs a database connection, because only prisma generate runs at build time.
| Area | Technology |
|---|---|
| Framework | Next.js 16 (App Router, route handlers), React 19 |
| Language | TypeScript 5 |
| Styling | Tailwind CSS v4, tw-animate-css, shadcn/ui (Radix primitives), lucide-react icons |
| Client state | Zustand 5 with persist (localStorage) |
| Validation | Zod 4 |
| ORM / DB | Prisma 6, PostgreSQL (Neon recommended) |
| Auth | Custom: Node crypto scrypt + DB sessions + httpOnly cookie |
| i18n | Custom English/Hindi dictionary (i18n.tsx) |
| LLM (optional) | z-ai-web-dev-sdk chat completions |
| Browser automation (optional) | TinyFish agent HTTP API |
| Fonts | next/font/google: Plus Jakarta Sans, Inter |
| Hosting | Vercel (bom1) |
| Tooling | ESLint 9 (eslint-config-next), tsx for scripts |
package.json also lists several dependencies that the application code under src/app, src/lib and src/components/schemeai does not import, for example next-auth, next-intl, @tanstack/react-query, @mdxeditor/editor and @dnd-kit/*. They are inherited from the project template.
These follow directly from the current code:
- No rate limiting. No endpoint is rate limited. Scheme refreshes (
POST /api/sources/verifywithschemeId) are open to everyone; TinyFish usage per scheme is bounded only by the 6-hour reuse of successful runs, and failed runs are not cached. - Source-level promotion is coarse. A successful direct check of a source's domain root marks every scheme linked to that source
LIVE_CHECKED, although the check does not read scheme content. - Demo admin credentials.
/api/setupandscripts/create-admin.tscreateadmin@schemeai.in/Admin@1234, and the sign-in view has a button that submits them. Change or remove that account on public deployments. - Type errors do not fail the build.
next.config.tssetstypescript.ignoreBuildErrors: true;npm run typecheckis the check. - Assistant provider is fixed. The LLM path uses
z-ai-web-dev-sdkwith SDK defaults; there is no provider or model setting in the repo. - Profiles stay in the browser. The
UserProfiletable is read by/api/auth/mebut never written. Document checklists are also local only, andApplicationTracker.checklistJsonis never written. - Never-checked sources are picked last. Discovery orders by
lastCheckedAtascending, and PostgreSQL sorts nulls last in ascending order. - The
CONTAINSoperator is not implemented. It is listed in the schema comment but falls through toUNKNOWNin the engine. The seed data does not use it.