Skip to content

Repository files navigation

Layak logo

Layak

The Agentic AI Concierge for Malaysian Social-Assistance Schemes

Three uploads. One website. Six autonomous steps. Zero hallucinated rules.


Grand Champion - Project 2030 Open Category

Node.js pnpm Next.js React FastAPI Gemini Frontend on Vercel Backend on Render MIT License

Aligned with UN Sustainable Development Goals

SDG 1 - No Poverty SDG 10 - Reduced Inequalities SDG 16 - Peace, Justice & Strong Institutions


Layak functional diagram

Live Demo · Pitch Deck · Demo Video


🏆 Grand Champion - Open Category · Project 2030: MyAI Future Hackathon Project 2030: MyAI Future Hackathon · Track 2 - Citizens First (GovTech & Digital Services) · Open Category · GDGoC UTM × Google Cloud · Build with AI Initiative (BWAI) Built by Team T010NG


✨ At a Glance

RM 13,808
annual relief surfaced for our reference user, Aisyah
~60s
median end-to-end latency, upload → draft packets
5 + 2 + 1
upside + subsidy-credit info cards + 1 contribution flagged
0
hallucinated rules - every number cites a source PDF

🎬 Demo Video

layak-demo-vid_1.mp4

▶ Also on YouTube


🖼 Screenshots

Landing page

Landing: every Malaysian scheme you qualify for, in one upload

Signed-in dashboard

Dashboard: application drafts and recent activity

Upload intake

Upload intake: sample, upload, or manual entry

Scheme library

Scheme library: every scheme Layak reasons over

Manual entry form

Manual entry: privacy-first path, no docs required

Results page

Results: ranked schemes, cited sources, strategy advisories

Cik Lay chatbot

Cik Lay: grounded chatbot, multilingual (en / ms / zh)

What-if scenario sliders

What-If: live partial rerun under 2 s, no re-upload

Admin discovery queue

Admin: agentic scheme discovery + human-in-the-loop moderation


🧠 What Layak Does

Malaysia's aid landscape is fragmented - 167 social-assistance schemes spread across 17 ministries and agencies. Citizens rarely know what they qualify for because the effort to search and apply for each one isn't worth it.

Layak collapses that into a single guided flow. A user uploads documents (or uses the privacy-first manual-entry path), and the agent returns:

  • 🥇 Ranked Schemes ordered by estimated annual RM upside.
  • 💬 Plain-Language Reasons they appear to qualify.
  • 🔗 Source-Linked Provenance for every rule-backed claim.
  • 📄 Draft Application Packets for manual submission.
  • ⚠️ Required Contributions surfaced separately so the headline upside stays honest.
  • 🤖 Conversational Concierge - a grounded chatbot on the results page so non-technical users (e.g. aunties and uncles) can ask follow-up questions about their evaluation in English, Bahasa Malaysia, or Mandarin.

🏗 Feature Matrix

Feature What It Means
📥 Dual Intake Document upload for IC / payslip / utility, or a manual form for users who'd rather not upload anything.
Visible 6-Step Agent Extract → Classify → Match → Optimize Strategy → Compute Upside → Generate, streamed over SSE so the citizen watches the work happen. Steps 4 + 5 run concurrently via asyncio.gather.
Grounded Retrieval Local embedding RAG grounds citations over the scheme PDF corpus; deterministic rule modules still decide eligibility and amounts, with cached citations as fail-open fallback.
🧮 Live Arithmetic Annual upside computed via Gemini Code Execution, not LLM narration.
🖨 Draft Packet Generation WeasyPrint renders pre-filled application PDFs for each matched scheme, all watermarked DRAFT - NOT SUBMITTED.
👤 Accounts & History Firebase Auth (Google + Guest), Firestore-backed evaluation history, free-tier quota, and an upgrade waitlist.
🔐 PDPA-Aligned Explicit consent on sign-up, JSON export, and hard-delete endpoints. Delete any evaluation or your whole account anytime.
🎭 Demo-Ready Fixtures Five synthetic personas (Aisyah, Farhan, Hashim, Meiling, Ravi) for stable judging walkthroughs.
🤖 Per-Evaluation Chatbot A floating panel on every completed results page - grounded on that eval doc + local RAG retrieval, multilingual (en/ms/zh), with a five-layer guardrail stack (system-prompt language lock, safety filters, input validator, RAG grounding, citation-drift detector).

🌏 Why It Matters - SDG Alignment

Layak is built around a simple product stance: citizens should not have to portal-hop just to discover what they are already entitled to. That stance maps directly to three UN Sustainable Development Goals:

SDG Goal How Layak Contributes
No Poverty Surfacing unclaimed subsidies (STR 2026, JKM Warga Emas, BKK) and tax reliefs directly increases the disposable income of low- and middle-income households.
Reduced Inequalities The manual-entry path works on a data-capped mid-range Android - the same experience the self-employed gig worker gets as the salaried urban professional.
Peace, Justice & Strong Institutions Every output carries a citation to a gazetted PDF, every draft is clearly labelled, and nothing is submitted on the citizen's behalf. Public-sector AI with receipts.

🏛 Architecture

Layak is a two-service app: a Next.js 16 frontend on Vercel and a FastAPI + ADK-Python backend on Render. The backend runs a RootAgent (gemini-3.1-flash-lite) that orchestrates six FunctionTools as a SequentialAgent. The two middle-pipeline Gemini calls - optimize_strategy and compute_upside - dispatch concurrently via asyncio.gather, so wallclock latency collapses from sum to max.

flowchart LR
    Browser[Browser] --> Frontend[Next.js 16 frontend]
    Frontend --> Backend[FastAPI backend]
    Frontend --> Auth[Firebase Auth]
    Backend --> Firestore[Firestore]
    Backend --> Gemini[Gemini Developer API]
    Backend --> Search[Local RAG index]
    Frontend --> Vercel[Vercel hosting]
    Backend --> Render[Render hosting]
Loading
Agent Pipeline - Six Autonomous Steps
flowchart LR
    Intake[Upload or manual entry] --> Extract[1. Extract<br/>Gemini 3.1 Flash-Lite]
    Extract --> Classify[2. Classify<br/>Gemini 3.1 Flash-Lite]
    Classify --> Match[3. Match<br/>19 rule modules + local RAG citations]
    Match --> Optimize[4. Optimize Strategy<br/>Gemini 3.1 Flash-Lite structured output]
    Optimize --> Compute[5. Compute Upside<br/>Gemini 3.1 Flash-Lite + Code Execution]
    Compute --> Generate[6. Generate<br/>WeasyPrint draft PDFs]
    Extract --> SSE[SSE stream to UI]
    Classify --> SSE
    Match --> SSE
    Optimize --> SSE
    Compute --> SSE
    Generate --> SSE --> Results[Results page]
Loading

Steps 4 and 5 are dispatched concurrently after match completes - asyncio.gather(optimize_strategy, compute_upside) collapses their wallclock from sum to max. The SSE wire emits both step_started events first, then both step_result events as the gather resolves, so the frontend stepper stays well-defined.

Authenticated Evaluation Flow
flowchart LR
    User[Signed-in user] --> SignIn[Google OAuth]
    SignIn --> Intake[Start evaluation]
    Intake --> SSE[Streaming pipeline]
    SSE --> Persist[Persist evaluation to Firestore]
    Persist --> Results[Results by id]
    Persist --> History[Evaluation history]
    Results --> Packet[Regenerate packet ZIP]
    User --> Export[User export or delete]
Loading
Conversational Concierge - Per-Evaluation Grounded Chatbot

Cik Lay (Pegawai Skim) fronts a floating chatbot on every completed results page so a low-tech-literacy user can ask follow-up questions about their evaluation in plain English, Bahasa Malaysia, or Mandarin. The bot is hard-constrained to the loaded evaluations/{evalId} doc plus local RAG retrieval over the twenty cached scheme PDFs - it is not a general-purpose chatbot.

graph TD
    User[User message] --> L1[Layer 1 - Input validator<br/>regex prompt-injection guard + length cap]
    L1 --> L2[Layer 2 - System prompt<br/>identity + Rule 0 language lock + eval-context digest + 5 hard rules]
    L2 --> L3[Layer 3 - Per-turn language reinforcement<br/>appended to user message]
    L3 --> L4[Layer 4 - Grounding + safety<br/>Local RAG grounding passages injected into prompt + Gemini built-in safety_settings<br/>BLOCK_LOW_AND_ABOVE on 4 harm categories]
    L4 --> Gemini[Gemini Flash]
    L4 --> Search[Local RAG index]
    Search --> Gemini
    Gemini --> Draft[Draft response]
    Draft --> L5[Layer 5 - Output validator<br/>citation-drift detector drops &#91;scheme:xxx&#93; markers not in eval's qualifying matches]
    L5 --> Panel[Floating chat panel<br/>on results page]
Loading

The stack is explicit end to end: Layer 1, Layer 2, Layer 3, Layer 4, and Layer 5 keep Cik Lay grounded while use-chat.ts keeps the conversation state local by holding the rolling history in the browser and persisting nothing server-side. The eval-context digest carries only ic_last6 for privacy.

Agentic Scheme Discovery + Admin Moderation

A background discovery agent watches a hardcoded allowlist of authoritative government source pages (MOF, JKM, LHDN, KWSP, PERKESO) and surfaces rate or eligibility changes to an admin reviewer before they reach end users. The flow keeps a human in the loop - Layak never auto-publishes scheme changes from the open web.

flowchart TD
    Trigger["Admin clicks<br/>'Run discovery now'"] --> Watcher[Source Watcher<br/>httpx + SHA-256 hash]
    Allowlist["discovery_sources.yaml<br/>6 gazetted URLs"] --> Watcher
    Watcher -->|content changed| Extractor[Gemini 3.1 Flash-Lite<br/>structured-output extractor]
    Watcher -->|unchanged| Skip[Skip]
    Extractor -->|confidence >= 0.5| Queue[Moderation queue<br/>discovered_schemes/Firestore]
    Extractor -->|confidence < 0.5| Drop[Drop]
    Queue --> Review["Admin reviews at<br/>/dashboard/discovery"]
    Review -->|approve - matched| Verified[verified_schemes<br/>Firestore upsert]
    Review -->|approve - new scheme| Manifest[YAML manifest<br/>backend/data/discovered/]
    Review -->|reject| Terminal[Terminal]
    Verified --> Badge["'Source verified DD MMM YYYY'<br/>badge on scheme cards"]
    Manifest --> Engineer[Engineer hand-codes<br/>Pydantic rule module]
Loading

The admin role is gated by a Firebase custom claim seeded from LAYAK_ADMIN_EMAIL_ALLOWLIST. Non-admin users hitting /dashboard/discovery are redirected to /dashboard (and the sidebar tab never renders for them). Public users see only the trust signal - the "Source verified" badge - on every scheme card across the app.

Cross-Scheme Strategy Optimizer + Cik Lay Handoff

After matching, an optimizer agent surfaces cross-scheme coordination opportunities the rule engine can't see - e.g. "Only one filing sibling should claim the RM 1,500 dependent-parent relief; pick whoever's at the highest marginal tax bracket." Each advisory cites the source PDF passage that backs the rule and offers a one-click handoff to Cik Lay for follow-up.

flowchart LR
    Matches[Matched schemes<br/>+ Profile + Classification] --> TripFilter[Trip filter<br/>pure Python]
    Registry["scheme_interactions.yaml<br/>3 hardcoded rules"] --> TripFilter
    TripFilter -->|triggered rules| Optimizer[Gemini 3.1 Flash-Lite<br/>structured output<br/>+ few-shot prompt]
    TripFilter -->|nothing trips| Empty["Empty state<br/>'No conflicts detected'"]
    Optimizer --> Schema[Pydantic validation<br/>+ registry membership check]
    Schema --> Gate{"Confidence<br/>gate"}
    Gate -->|>= 0.8| Full[Full card]
    Gate -->|0.5 – 0.8| Soft[Soft suggestion<br/>+ force-show CTA]
    Gate -->|< 0.5| Suppressed[Suppressed]
    Full --> Section[Strategy section<br/>on results page]
    Soft --> Section
    Section -->|'Ask Cik Lay about this'| Handoff[Chat panel auto-opens<br/>with advisory prefilled<br/>+ advisory in system prompt as DATA]
Loading

Four-layer grounding keeps the optimizer honest: (1) the YAML registry of allowed interaction_id values, (2) the Pydantic schema with mandatory citation and length caps, (3) hand-written few-shot examples that anchor the response shape, and (4) frontend confidence-gated rendering. The Cik Lay handoff injects the advisory into the chat system prompt as DATA - for context only, not instructions so a hostile headline can't redirect the assistant.

What-If Scenarios - Live Partial Rerun

The results page surfaces three sliders (monthly income, children under 18, elderly dependants) that let users explore "what changes if my income drops to RM 2,500" without re-uploading documents. Each slider drag debounces 500 ms then runs a lightweight partial-rerun on the server - skipping OCR + Code Execution + PDF generation, which keeps the round-trip under 2 s end-to-end.

flowchart LR
    Sliders[3 sliders<br/>income / children / elderly] -->|debounce 500ms| Hook[useWhatIf hook<br/>+ AbortController]
    Hook --> POST["POST /api/evaluations/{id}/what-if<br/>5 calls / 60s rate limit"]
    POST --> Override[Apply overrides<br/>clamp + rebuild dependants]
    Override --> Classify[Classify]
    Classify --> Match[Match]
    Match --> Optimize[Optimize Strategy]
    Optimize --> Diff[compute_deltas vs baseline]
    Diff --> Response[WhatIfResponse<br/>matches + strategy + deltas]
    Response --> Chips[Delta chips per scheme card<br/>gained / lost / tier_changed / amount_changed]
    Response --> RefreshedStrategy[Strategy section refreshes]
    ResetAll[Reset all] -->|clear| Hook
Loading

The endpoint is stateless w.r.t. Firestore - sliders are exploratory and dragging them never pollutes the user's evaluation history. The original eval doc remains the durable record; reset reverts the page to baseline.

Two-Tier Reasoning Surface - Watch the Agent Think

The pipeline streams two parallel reasoning registers as it runs. Layperson users see a lay narration card (always visible) with one humanised line per step. Anyone curious about the internals can expand a developer transcript with timestamps, tool names, local RAG retrieval hits with scores, and Gemini Code Execution stdout - the same data a backend engineer would see in logs.

flowchart TD
    Step[Pipeline step completes] --> Narrate[Per-step narrators]
    Narrate --> Tier1["PipelineNarrativeEvent<br/>headline + data point<br/>(en / ms / zh)"]
    Narrate --> Tier2["PipelineTechnicalEvent<br/>timestamp + log lines<br/>(English only)"]
    Tier1 --> SSE[SSE stream]
    Tier2 --> SSE
    SSE --> Lay["Lay narration card<br/>'Read your documents · RM 2,800'<br/>'Matched against 20 schemes · 12 qualifying'"]
    SSE --> Tech["Technical transcript<br/>collapsed by default<br/>show details ▾"]
    Tier1 --> Firestore["evaluations/{id}.narrativeLog"]
    Tier2 --> Firestore2["evaluations/{id}.technicalLog"]
    Firestore --> Replay[Retrospective replay on results page]
    Firestore2 --> Replay
Loading

The technical layer is PII-clean by contract: full IC numbers, names, and addresses never reach the transcript. Only the last six digits of the IC, masked as ******-PB-####, ever surface. The lay narration localises to the user's language; the technical transcript stays English because its audience is developer-grade.


🌀 Google AI Ecosystem

Layak's AI and identity stack runs on first-party Google components — Gemini, Gemini Code Execution, gemini-embedding-001, ADK-Python, Firebase Auth + Firestore — with hosting on Vercel + Render:

Layer Component Role
🧠 Brain · 01 Gemini 3.1 Flash-Lite RootAgent orchestrator + optimize_strategy (structured output) + compute_upside (sandboxed code execution).
🧠 Brain · 02 Gemini 3.1 Flash-Lite Multimodal extract (IC + payslip + utility) and household classify, on the free Gemini Developer API tier.
🧠 Brain · 03 Gemini Code Execution Sandboxed Python on top of Flash-Lite for annual-RM arithmetic - stdout streamed verbatim to the UI.
📚 Context · 04 Local embedding RAG gemini-embedding-001 + numpy cosine search over the twenty cached scheme PDFs. Every rule carries a passage citation.
🎛 Orchestrator · 05 ADK-Python v1.31 First-party GA agent framework. SequentialAgent + FunctionTool.
☁ Lifecycle · 06 Vercel + Render Frontend on Vercel, backend on Render (free tier).
👤 Identity · 07 Firebase Auth Google OAuth + Guest mode. ID-token verification on every dashboard call.
🗄 State · 08 Firestore Evaluation history, discovery moderation queue, verified-scheme cache, user preferences.

🧰 Tech Stack

Category Technology Notes
Frontend Next.js 16 · React 19 · TypeScript 5 · Tailwind CSS 4 · shadcn/ui Public experience, dashboard, evaluation UI
Backend FastAPI · Python 3.12 · Pydantic v2 Intake APIs, orchestration, rules, packet generation
Agent Framework Google ADK for Python v1.31 · SequentialAgent RootAgent orchestration
Models Gemini 3.1 Flash-Lite (free Gemini Developer API) All pipeline steps on Flash-Lite; in-family fallback gemini-2.5-flash-lite
Grounding Local embedding RAG (gemini-embedding-001 + numpy) Source passage retrieval for provenance
Computation Gemini Code Execution Annual upside calculations
Document Output WeasyPrint Draft PDF packet generation
Identity & Data Firebase Auth · Firestore Authenticated flows, saved evaluations, quotas, discovery queue
Cloud Vercel (frontend) · Render (backend) · Firebase Spark (Auth + Firestore) Hosting, runtime secrets, authenticated state
Tooling pnpm · ESLint · Prettier · Husky · lint-staged · ruff Workspace and code quality

🚀 Getting Started

Prerequisites

  • Node.js 24.x
  • pnpm@10.33.0
  • Python 3.12

Install & Configure

pnpm install          # installs every workspace package
cp .env.example .env  # then fill in the Gemini + Firebase values below

Required environment variables:

  • GEMINI_API_KEY (free key from https://aistudio.google.com/apikey)
  • GOOGLE_GENAI_USE_VERTEXAI=FALSE
  • LAYAK_CORS_ORIGINS
  • NEXT_PUBLIC_BACKEND_URL
  • NEXT_PUBLIC_FIREBASE_*
  • FIREBASE_ADMIN_KEY

Run Locally

# one-time - build the committed local RAG index from the scheme PDFs
cd backend && GEMINI_API_KEY=... python -m scripts.build_rag_index --verbose

# in terminal 1 - frontend
pnpm dev                                                # → http://localhost:3000

# in terminal 2 - backend (GEMINI_API_KEY in backend/.env)
cd backend && uvicorn app.main:app --reload --port 8080 # → http://localhost:8080

Useful Commands

pnpm dev         # start frontend (Next.js 16, webpack, port 3000)
pnpm build       # production frontend build
pnpm start       # run production frontend
pnpm run lint    # lint frontend (use `run` - `pnpm lint` hits a built-in)
pnpm format      # prettier --write across the repo

☁ Deployment

The frontend deploys to Vercel (Next.js native, free Hobby tier) and the backend deploys to Render (Docker web service, free tier; cold-starts ~30-60s after idle). Live demo: https://layak.vercel.app.

Deploy Steps

One-time - build the committed RAG index (commit backend/data/rag_index/):

cd backend && GEMINI_API_KEY=... python -m scripts.build_rag_index --verbose

Frontend → Vercel:

  1. Connect the repo on Vercel and set the project root to frontend/.
  2. Set env vars NEXT_PUBLIC_FIREBASE_* and NEXT_PUBLIC_BACKEND_URL.

Backend → Render:

  1. Use the render.yaml blueprint at the repo root to provision the Docker web service.
  2. Set secrets GEMINI_API_KEY, FIREBASE_ADMIN_KEY, and LAYAK_CORS_ORIGINS (plus GOOGLE_GENAI_USE_VERTEXAI=FALSE).

Important

If these URLs or steps drift, the live Vercel / Render configuration and render.yaml are the source of truth - not this README.


🔒 Privacy & Safety

Caution

Layak is a preparation tool, not a submission tool. It never writes to bantuantunai.hasil.gov.my, the LHDN portal, or any other live agency endpoint.

  • 🚫 No Live Submission - Ever. Outputs are drafts. The citizen submits through the official channel.
  • 🧾 No Unverified Claim reaches the UI. If local RAG returns no passage for a rule, the rule drops out of the ranking.
  • 🎭 Synthetic Demo Documents Only. Every MyKad, payslip, and utility bill used in our demo fixtures is fictional and watermarked SYNTHETIC - FOR DEMO ONLY.
  • No Final Legal Determination is claimed. Every explanation uses "you appear to qualify ... the agency confirms on application."
  • 🗑 User-Controlled Deletion — delete any evaluation, cascade-delete on account deletion, JSON export on demand - PDPA 2010-aligned.

🤝 AI Disclosure

This project has utilized AI tooling in the following ways to produce sustainable and maintainable code:

  • Google AI Studio - Prompt engineering and development-workflow design for the app.
  • Google Antigravity IDE - Code scaffolding and generation support.
  • GitHub Copilot - Documentation assistance and Git workflow support.
  • Claude Code - Programming assistance tooling.

All AI-assisted output is reviewed, tested, and integrated by human developers before commit.


👥 Team

Built with ❤️ by Team T010NG, as we strive to TOLONG.

Adam
Adam
@AlaskanTuna
Hao
Hao
@chaosiris
JS
JS
@Doraemon-00

📁 Project Structure

Repository Layout
Layak/
├── assets/                  # README visuals (banner.png + screenshots/)
├── frontend/                # Next.js app - dashboard, marketing, evaluation UI
├── backend/                 # FastAPI app - agent pipeline, rules, routes, PDF generation
│   ├── app/agents/          #   ADK-Python RootAgent + 6 FunctionTools + chat / optimizer prompts
│   ├── app/rules/           #   19 typed Pydantic rule modules (STR, JKM, LHDN, i-Saraan, PERKESO, …)
│   ├── app/routes/          #   FastAPI routes (auth, evaluations, chat, what_if, schemes, admin, user, quota)
│   ├── app/services/        #   local RAG client, rate limit, Firestore wrappers, warmup
│   ├── data/schemes/        #   20 committed gazetted source PDFs (source of truth)
│   ├── data/rag_index/      #   committed local RAG index (vectors.npz + chunks.json)
│   ├── data/discovered/     #   YAML manifests from approved discovery candidates
│   └── scripts/             #   build_rag_index.py builds the committed RAG index
├── render.yaml              # Render backend blueprint
├── LICENSE                  # MIT (+ attribution courtesy clause)
├── package.json             # root workspace orchestrator
├── pnpm-workspace.yaml
└── .env.example

📜 License

Layak is open source under the MIT License - see LICENSE.

You're free to use, fork, study, modify, and redistribute the code for commercial or non-commercial purposes. The only binding condition is attribution: preserve the copyright notice in any derivative work, and (where reasonable) link back to this repository.

Suggested attribution line for forks, papers, blog posts, or derivative products:

Based on Layak by Team T010NG (Adam · Hao · JS) - Grand Champion of Project 2030: MyAI Future Hackathon, Open Category, 2026. https://github.com/AlaskanTuna/Layak

The committed scheme PDFs under backend/data/schemes/ are gazetted Malaysian government documents and remain the property of their respective issuing agencies (MOF, LHDN, JKM, KWSP, PERKESO). They are included here under fair-dealing for research and demonstration of the Layak pipeline; redistribution of those PDFs must respect each original publisher's terms.


🙏 Acknowledgements

  • Project 2030: MyAI Future Hackathon - GDGoC UTM × Google Cloud, for the opportunity and the credits.
  • Ministry of Finance Malaysia - for publishing Budget 2026 as a gazetted, citable primary source.
  • LHDN, JKM, PERKESO, KWSP - for the public explanatory notes and schedules we ground the rule engine on.
  • The open-source community behind Next.js, FastAPI, ADK-Python, shadcn/ui, and WeasyPrint.

Layak · Project 2030 · MyAI Future Hackathon · Track 2 Open · Grand Champion · © 2026 Team T010NG · MIT License

About

Layak is an agent-powered concierge for Malaysian social-assistance schemes, built on NextJS, FastAPI, Google ADK-Python and local embedded RAG.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages