Civic Tech · Meet the Builders

From PR to Citizen to "There Must Be a Better Way": Building MerlionOS

🛠️ Google Tech Stack & Infrastructure
Google Gemini 2.5 Flash Gemini Multimodal Vision gemini-embedding-001 (RAG) Gemini 3.1 Flash-Lite Google Search Grounding Google Cloud BigQuery

A few months ago, I became a Singapore citizen. On paper, it was a milestone; in practice, it was an administrative explosion. As a Permanent Resident, my civic digital footprint was fairly routine: filing taxes via IRAS, renewing my Re-entry Permit with ICA, occasionally checking my CPF balances, booking a polyclinic appointment through HealthHub, and using OneMap to check bus arrival times. But the citizenship journey changes everything.

Suddenly, the process is far more active. First, you check your application status and make appointments on the ICA portal for your new identity card and passport. Next, you complete the mandatory Singapore Citizen Journey onboarding. And then, a whole new world of civic admin opens up—tracking SkillsFuture credits, redeeming CDC vouchers on RedeemSG, and keeping up with HDB BTO launches.

When I sat down to list all the agency sites I needed to access to fulfill my new duties, stay informed, and handle daily life, the count came to 19 separate statutory board and national service portals. Juggling that many bookmarks and browser tabs every week—plus checking SMRT’s Twitter/X feed each morning to see if my MRT line was running smoothly—felt like an endless obstacle course.

I wanted a clean, searchable index that securely verified the links to those essential portals: ICA, ELD, IRAS, CPF, RedeemSG, SP Group, SkillsFuture, Workforce Singapore, MOM, MOH, HDB, MOE, LTA, NEA, Gov.sg, SG Journey, OneMap, HealthHub, and ActiveSG.

MerlionOS 19 Portals Initial Prototype
📸 Initial MerlionOS Prototype: The 19 essential Singapore statutory portals index.

So I began by collecting the portals I needed to fulfill my Singaporean duties, stay informed, and enjoy the country’s privileges—all in one place.

The real story of MerlionOS isn't just the destination; it is the journey of how it got built, chunk by chunk, from a local script to a hardened agentic platform.

🎨 From External Links to a Local Dashboard

It started as a weekend hackathon project for the Hack2skill × Google Cloud GenAI Academy APAC Cohort 2. Rather than jumping straight into complex AI agent loops, I wanted to solve my own immediate frustration: building a clean, trustworthy index for the 19 essential Singapore government portals I found myself needing as a new citizen.

In my first iteration, I built a customizable drag-and-drop grid, fetching agency logos directly from official URLs to keep them authentic and up-to-date. But a directory of links only solved half my problem—what I really needed was personal situational awareness before stepping out the door. That realization drove me to build SG Hub, a real-time civic command center embedded right inside my dashboard.

I was tired of opening half a dozen separate apps and websites just to get through my routine. Before heading out or planning my day, I often found myself checking SMRT's Twitter/X feed just to see if my MRT line was delayed, looking up hyper-local rain alerts and regional PSI air quality readings, scrolling through 7 official government Telegram channels for public announcements, and monitoring 15 different community deal channels for local savings. On top of that, keeping track of upcoming HDB BTO launch tables meant manually refreshing the HDB newsroom. With SG Hub, I brought all of these scattered daily feeds together into a single, real-time civic command center right on my dashboard.

MerlionOS SG Hub real-time civic dashboard
📸 SG Hub — the real-time civic command center: live MRT/LRT line status, official LTA/SMRT service advisories, and government broadcasts consolidated into a single dashboard.

📊 Streaming Economic Insights with Google Cloud BigQuery

Beyond daily situational awareness, I wanted to understand broader macro trends affecting citizens and job seekers in Singapore. To track economic signals alongside civic services, I plugged into Google Cloud BigQuery to analyze public data.gov.sg datasets—streaming job market vacancy counts, year-over-year sector growth trends, and Ministry of Manpower (MOM) retrenchment statistics. By combining BigQuery's fast analytical queries with live API fallbacks, I could monitor top affected industries and workforce shifts directly inside MerlionOS.

MerlionOS Job Market Analysis panel powered by Google Cloud BigQuery
📸 Streaming Economic Insights: the Job Market Analysis panel—live MOM job-vacancy counts (11,700 open roles), median starting salary, top in-demand skills, year-over-year sector growth, and quarterly retrenchment risk—powered by Google Cloud BigQuery over data.gov.sg datasets.

💬 Breathing Life into the Chatbot

A directory of links is useful, but the ultimate vision was an assistant that could explain the rules and guide citizens through complex processes. In Singapore, nearly every statutory board operates its own isolated chatbot widget on its own website—forcing citizens to jump from portal to portal, repeating context to different agency bots. I wanted to eliminate that fragmentation with a single, unified civic Co-Pilot powered by FastAPI.

I powered the core engine with Google Gemini 2.5 Flash to leverage its native parallel tool-calling—so a single question could fan out to several statutory-board tools at once and come back as one synthesized answer, instead of the citizen hopping between each agency's own chatbot.

To ensure the assistant stays reliable under heavy traffic and prevent service disruptions during peak usage, I built a resilient fallback router: if a 429 rate limit is hit, the engine silently falls back to Gemini 3.1 Flash-Lite with Google Search Grounding, maintaining 100% uptime without sacrificing accurate web-grounded responses.

MerlionOS Gemini Co-Pilot answering a citizenship onboarding question
📸 Co-Pilot Assistant in action: a unified civic chat answering multi-step questions—here, post-citizenship onboarding—with guidance grounded in official sources like the Singapore Journey Portal, plus an Operations Trace tab for live tool logs.

🛡️ Hardening Security: Protecting Civic Trust

When building a tool that connects citizens to government portals, security and trust are paramount. A single malicious redirect or XSS vulnerability could ruin user confidence, so I built defense-in-depth security directly into the core architecture.

On the client side, I implemented strict XSS sanitization via a custom safeURL utility that filters out dangerous protocols (javascript:, data:, vbscript:) and escapes quotes to prevent HTML breakouts. On the backend, my BeautifulSoup scraper follows redirect chains but enforces domain verification—ensuring final landing pages strictly resolve to .gov.sg or trusted public domains like healthhub.sg, wsg.sg, and cdc.gov.sg.

To safeguard users against phishing, I engineered a strict Anti-Phishing Link Policy: only official .gov.sg domains and approved trusted portals render as clickable links in chat responses. Arbitrary web search results, external blogs, or forum references are rendered as plain non-clickable text with their domain visible, so the assistant never trains users to click unverified links from a chat window. Crucially, any authentication or login URLs (singpass, login, auth, corppass) are strictly non-clickable—even on .gov.sg hosts—and carry a security shield marker advising users to open official portals directly.

MerlionOS Co-Pilot refusing to open a SingPass login link
📸 Security Guardrails in Action: asked to "take me to the SingPass login page," the Co-Pilot refuses to hand over a login link—instead advising the user to open singpass.gov.sg themselves and never follow login links from a chat assistant.

🏆 Shortlisted in the Top 101

I submitted my initial prototype to the Hack2skill × Google Cloud GenAI Academy APAC Cohort 2 hackathon, and was thrilled when MerlionOS was shortlisted in the top 101 projects out of 1,500+ submissions.

Being shortlisted was the ultimate motivation boost. Receiving $100 in Google Cloud Platform (GCP) credits gave me the perfect opportunity to scale beyond a simple prototype and engineer MerlionOS into a comprehensive, high-fidelity platform.

🏗️ MerlionOS System Architecture
flowchart TD
    User["👤 Citizen User"] -->|Browser / Mobile| FE["💻 Web Frontend & Dashboard"]
    FE -->|Preferences| LocalStorage["💾 LocalStorage"]
    FE -->|FastAPI SSE / REST| API["🚀 FastAPI Backend (Google Cloud Run)"]

    API -->|Parallel Tool Calling| Gemini["🤖 Google Gemini 2.5 Flash"]
    API -->|RAG Vector Search| Embed["📚 gemini-embedding-001"]
    Gemini -->|429 Rate Limit Fallback| FlashLite["⚡ Gemini 3.1 Flash-Lite + Search Grounding"]

    API -->|Economic Analytics| BQ["📊 Google Cloud BigQuery"]
    API -->|x-api-key| DataGov["🌐 Data.gov.sg (NEA, LTA)"]
    API -->|safeURL Domain Check| GovPortals["🏛️ 93 Statutory Portals"]
    API -->|Rule-Based Analysis| WhyEngines["🎯 Why Explanation Engines"]
      

🚀 Scaling Up: From 19 to 93 Portals & Preference Controls

With GCP credits in hand and momentum building, I decided to research just how many Singapore government portals actually existed. To my surprise, a thorough audit revealed a staggering total of 93 statutory board and agency portals—ranging from health (HPB) and monetary policy (MAS) to urban planning (URA), defense (OneNS), emergency services (SCDF), and the Judiciary.

Expanding the index fourfold meant the interface needed a major UX overhaul. Showing 93 portals all at once would overwhelm anyone, so I built personal preference settings—allowing users to hover over any card to hide it with a quick eye icon toggle. I also built a Manage Portals control panel at the top of the grid with live text search, visible/hidden filters, multi-select checkboxes, and bulk add-or-hide actions, persisting every user's custom layout directly in localStorage. This turned a massive directory into a clean, personalized civic workspace.

MerlionOS Manage Portals Control Panel showing 93 visible portals
📸 Manage Portals Control Panel: live text search, visible/hidden filters across all 93 portals, bulk hide/show actions, and per-user localStorage persistence.

🔍 Intent-Based Search & Plain-English Glossary

Navigating public services often fails because citizens don't know official government terminology. To bridge this gap, I built an Intent-Based Search Bar at the top of the portal grid. Instead of requiring users to know exact agency names, it maps plain-language phrasing—like "change shop address", "top up CPF", or "renew passport"—against a curated dictionary of everyday synonyms, complete with quick-task chips. If a search query matches a live SG Hub dashboard tab (e.g., "COE premium"), the search bar intelligently surfaces a direct link straight to that live panel.

MerlionOS Intent-Based Search with quick-task chips
📸 Intent-Based Search: plain-language queries like "renew passport" instantly resolve to the right agency, backed by one-tap quick-task chips above the portal grid.

To demystify complex policy jargon, I also created an interactive Plain-English Glossary. Any of 26 common civic terms—such as CPF, SRS, COE, BTO, EHG, PSI, or SSOC—rendered anywhere inside the app gets a subtle dashed underline. Hovering on desktop or tapping on mobile displays a clear, one-sentence explanation in a tooltip. A background MutationObserver continuously scans the DOM so newly loaded panel content is annotated automatically.

MerlionOS Plain-English Glossary tooltip explaining the acronym LTA
📸 Plain-English Glossary: civic acronyms pick up a subtle dashed underline anywhere in the app—hover on desktop or tap on mobile and a one-line explanation appears, here defining LTA as the Land Transport Authority.

🧑‍🤝‍🧑 Tailoring the Experience: Life-Stage Personas

To make 93 portals and multiple live dashboards feel immediately relevant to different users, I introduced life-stage Demo Personas (New Citizen, Young Family, Fresh Graduate, Retiree)—requiring no real Singpass or identity data.

Selecting a persona tailors the experience across three surfaces simultaneously: the Gemini Co-Pilot receives life-stage context so guidance is prioritized for that person, the portal grid surfaces a "Personalized for X" banner highlighting the most relevant agencies, and SG Hub displays recommended dashboard views—all persisted deterministically in localStorage.

MerlionOS Life-Stage Demo Personas selector
📸 Life-Stage Personas: a one-click "Try as" selector (New Citizen, Young Family, Fresh Graduate, Retiree) re-tailors the Co-Pilot, portal grid, and SG Hub—requiring no Singpass or real identity data.
MerlionOS SG Portals tailored for the New Citizen persona
📸 Persona applied — SG Portals: choosing New Citizen adds a "Personalized for New citizen" banner with jump-links to the agencies that matter most at that life-stage (ICA, SG Journey, CPF, IRAS, HDB, SkillsFuture).
MerlionOS SG Hub recommended dashboards for the New Citizen persona
📸 Persona applied — SG Hub: the same choice surfaces a "Recommended dashboards" strip, steering a new citizen straight to the live panels most relevant to them.

🚕 Daily Mobility & Border Safety

Once the portal grid was under control, I turned my attention back to daily mobility friction. Essential metrics like latest COE bidding premiums across all 5 categories and upcoming HDB BTO launch schedules are usually buried deep inside press releases, PDFs, or separate portal sub-pages, making them hard to track at a glance. I brought live COE premiums directly into the main dashboard alongside an islandwide LTA Taxi Availability map rendered on interactive Leaflet.js paired with an opt-in "Around You" 2km geolocation lookup. Instead of blindly booking expensive ride-hailing services during peak surge hours, surfacing the live 2km taxi radius directly on action buttons lets citizens instantly gauge local taxi density and find cheaper, non-surge transit options.

To support cross-border commuters and daily safety, I integrated live ICA checkpoint delay advisories for Woodlands and Tuas congestion directly from the official ICA Newsroom, and added real-time PUB flood advisory alerts rendered as priority warning banners at the top of the feed.

MerlionOS Live Transport & Vehicle Costs (COE) panel with LTA taxi map
📸 Live Mobility Panel: latest COE bidding premiums across all five categories with a 49-exercise premium trend, sitting above a real-time islandwide LTA taxi-availability map — the same Transit & Transport panel also carries SMRT alerts and Woodlands/Tuas ICA checkpoint advisories.

🏠 Unlocking Data Insights: Housing, Wages & Wealth

To help homebuyers navigate Singapore's housing market, I built an HDB Resale Flat Price Tracker powered by BigQuery over 236,000+ transaction rows. Planning housing as a new citizen requires evaluating which neighborhoods hold long-term investment value without depreciating; querying town-by-town median prices and multi-year trend lines helps citizens judge their property budget with confidence. To give buyers future foresight, I integrated Python OLS linear regression models directly into the analytical tools to compute multi-year price forecasts. Furthermore, because buyers are often caught off guard by cumulative interest rates over a 25-year tenure, I built a deterministic HDB Loan & Concessionary Interest Calculator so citizens can calculate exact monthly installments before committing—eliminating post-payment loan surprises.

Understanding career growth in an AI-driven economy requires data transparency. I built the Occupational Wage Explorer using MOM survey data to track which new roles are emerging due to AI, identify which industries have the highest pay raises vs falls, and allow job seekers to look up wages by specific occupations to verify they are being compensated per industry standards.

For personal financial planning, I built an interactive IRAS Tax & SRS Relief Optimizer alongside live scrapers for official IRAS news and filing deadlines. Because details on CPF SA/MA top-ups and SRS contributions are spread across multiple agency pages, this tool calculates exact tax top-ups so citizens get the absolute most out of eligible tax reliefs before filing deadlines hit.

MerlionOS IRAS Tax Due Dates panel with live filing deadlines
📸 IRAS Tax & Wealth: live IRAS filing deadlines (YA 2026) with a "next deadline" countdown and a green "Live" freshness badge—here flagging the 31 Jul GST return due in 6 days—above the upcoming corporate-tax and GST dates.

⚡ Environment Feeds & Acquiring the Data.gov.sg API Key

As I expanded environmental tracking beyond basic PSI readings to include PM2.5 forecasts, 24-hour outlooks, live temperature, humidity, wind direction, and a 5-tier UV Index, the app began making frequent live requests to NEA and data.gov.sg endpoints. Relying on unauthenticated public calls quickly led to API throttling during peak hours.

This friction drove a major infrastructure upgrade: registering an official DATA_GOV_SG_API_KEY (applied via the x-api-key header) across all data.gov.sg requests. This unblocked high-frequency environmental polling, eliminated burst-rate limits, and ensured unthrottled, zero-downtime updates across all weather tiles and government feeds (expanding Telegram broadcasts from 7 to 12 official channels).

MerlionOS Live Weather, PSI, and UV Index Environment Feeds
📸 Live Environment Feeds: NEA 24-hour national PSI reading with current-conditions tiles (temperature, humidity, wind, rainfall, PM2.5, UV Index) and a 2-hour regional forecast.

🎯 Deterministic "Why" Insights & Structured Data

As the feature set matured, the focus shifted from feature expansion to deep architecture hardening and reliability. While AI models are great for conversation, critical financial and economic trends require 100% deterministic accuracy without hallucination.

I built three rule-based "Why" Explanation Engines that analyze existing datasets without extra network calls: cross-referencing Hiring Pressure against multi-year CAGR trends to explain vacancy acceleration vs churn, comparing COE quota and bids round-over-round to explain supply/demand price moves, and comparing flat-type price changes against islandwide figures to flag broad-based price growth versus flat-mix shifts. All three engines stay silent when data is ambiguous, avoiding forced guesses.

Architecturally, I eliminated fragile text-parsing between the backend and dashboard endpoints. Previously, job stats and COE results were computed once as Gemini text blocks and re-parsed via line splits. I refactored the data tools to return structured dictionaries directly consumed by /api/sg-hub/* endpoints, wrapping them in thin formatting helpers for Gemini, ensuring complete decoupling and zero dashboard breakage when chat prompts change.

MerlionOS Deterministic Why explanation engine on the Job Market Analysis panel
📸 Deterministic "Why" Engine: rule-based analysis of live MOM data (via Google Cloud BigQuery) surfaces a plain-English "Why" line—here explaining that net hiring pressure stays strong (6.0×) even as year-on-year vacancy growth decelerates.

📚 Grounding Policy Answers: RAG & gemini-embedding-001

As citizens began asking open-ended policy questions—such as "Should I buy a BTO or resale flat?", "How does CPF LIFE work?", or "Who is required to file income tax?"—I realized a simple link index wasn't enough. To answer these questions accurately without AI hallucinations, I built a Retrieval-Augmented Generation (RAG) knowledge engine (tools/knowledge.py).

I curated a 42-chunk corpus of authoritative Singapore civic facts, each tagged with official government source URLs. Using gemini-embedding-001 (768-dimensional vector embeddings), the engine embeds user queries and document chunks, retrieving relevant facts via pure-Python cosine similarity. Registered as search_knowledge_base in the Co-Pilot tool loop, the assistant retrieves and cites official source links directly instead of relying on parametric memory, degrading gracefully if the embedding API is unavailable.

To handle complex queries, I implemented Multi-Hop Agentic Chaining (up to 3 hops). When a user asks a multi-step question—such as "What tech jobs are in demand right now, what's the median salary, and what SkillsFuture courses can I take?"—Gemini 2.5 Flash executes an initial tool lookup for wage benchmarks, evaluates the returned data, and automatically chains a second tool dispatch for relevant training courses before synthesizing a final answer.

MerlionOS Multi-Hop Step 1: User Question and live tool lookup stream MerlionOS Multi-Hop Step 2: Tech Jobs & Median Wage Answer MerlionOS Multi-Hop Step 3: SkillsFuture Course Answer & Citations MerlionOS Operations Trace Step 1: Tool dispatch log for MOM Job Vacancies MerlionOS Operations Trace Step 2: Tool dispatch log for Occupational Wages MerlionOS Operations Trace Step 3: Tool dispatch log for SkillsFuture Courses
📸 Multi-Hop Agentic Reasoning in Action (6-Grid Trace): Top Row: 1. User Question & Tool Lookup status ➔ 2. Job Market & Tech Salary Answer ➔ 3. SkillsFuture Course Answer & Citations · Bottom Row: The corresponding underlying tool dispatches live in the Operations Trace console (Job Vacancies ➔ Wage Benchmarks ➔ SkillsFuture / RAG retrieval).

Furthermore, I added Multimodal Vision: citizens can attach a photo or screenshot of a document—an IRAS Notice of Assessment, a benefits notice, or a government form—and Gemini’s vision channel reads its content (the figures, transaction types, and deadlines) to explain what it means and cross-reference statutory caps like the S$80,000 relief limit. Crucially, it works on the substance, not the identity: as I cover in the next section, a hard privacy guardrail refuses to read NRIC, FIN, or passport numbers off an uploaded document and prompts the citizen to redact those identifiers first.

Multimodal Step 1: IRAS Notice of Assessment attached via the paperclip button Multimodal Step 2: Gemini reads the reliefs and tax payable off the Notice of Assessment Multimodal Step 3: Operations Trace log for base64 vision parsing and tax tool call
📸 Multimodal Vision Document Analysis (3-Card Row): 1. Attachment: Attaching an IRAS Notice of Assessment via the paperclip button ➔ 2. Vision AI Answer: Gemini reads the Notice of Assessment’s reliefs and the exact tax payable, without needing a name or NRIC, and lays them out ➔ 3. Operations Trace: Live backend log tracing the base64 vision decoding.

🔧 Production Hardening, Testing & Mobile UX Polish

To protect the public demo against quota exhaustion, I introduced in-memory per-IP rate limiting—a sliding window of 20 requests/minute in production, relaxed to 100 for local development—on /api/chat endpoints, safeguarding our shared Gemini free-tier quota.

To maintain complete data transparency, I added real-time data freshness badges across scraper-backed panels (ICA, IRAS, HDB, Telegram)—displaying a green "Live" pill on success or an amber "Showing last known data" pill if falling back to cache, alongside exponential backoff retries for network resilience.

On the code health front, I centralized TTL caching helpers in tools/core.py and set up pyflakes alongside automated Python and JavaScript unit tests in GitHub Actions CI gates—achieving a comprehensive test suite of 142 automated tests (136 Python + 6 JavaScript) spanning RAG vector math, cosine similarity, decision boundaries, and route-level API endpoints.

Finally, I turned to how responses feel. I upgraded chat replies to a real-time Server-Sent Events (SSE) stream (text/event-stream), so answers now arrive token-by-token behind a blinking cursor () that vanishes on completion, and swapped the old bouncing-dot spinner for real-time tool status lines ("Searching the knowledge base", "Reading gov.sg pages"). On mobile, I rounded it out by fixing CSS grid descendant bugs on onboarding banners, surfacing the 2km taxi radius directly on action buttons, and adding tap-to-tooltip chart interactions.

MerlionOS mobile portal grid with intent search, quick-task chips and Manage Portals controls MerlionOS mobile Job Market Analysis with live MOM vacancy stats and a vacancy-trend-by-sector chart MerlionOS mobile Co-Pilot chat answering a passport-renewal question
📸 Mobile Polish & Data Freshness: the responsive layout reflows the portal grid, renders the live Job Market Analysis panel—MOM vacancy stats and a full Vacancy Trend by Sector chart (BigQuery / data.gov.sg) with a green "Live" freshness pill—and keeps the Gemini Co-Pilot conversation fully usable on a phone.

What started as a local script for 19 portals had evolved into a resilient, fast, and secure civic co-pilot, fully containerized and deployed on Google Cloud Run.

🌐 Data-Layer Scaling: WAF-Proof BigQuery ETL & Live Agency Feeds

As traffic grew and dataset sizes expanded, live scrapers faced a major operational hurdle: government data servers like Ministry of Manpower Stats (stats.mom.gov.sg) and HDB (hdb.gov.sg) enforce strict Web Application Firewall (WAF 403) blocks against cloud datacenter IP ranges (such as GCP Cloud Run IP pools). To solve this without sacrificing live updates, I engineered a dual-cloud hosting strategy—running core web containers on Render (which bypasses datacenter IP blocks) while connecting to Google Cloud BigQuery for heavy analytical datasets.

I extended the BigQuery data architecture beyond job vacancies to cover HDB Resale Prices (sg_housing.hdb_resale_prices, indexing 236,000+ transaction rows) and the MOM Occupational Wage Survey (sg_employment.occupational_wages). Rather than requiring clients to download ~20MB CSV workbooks per request, both datasets now resolve through server-side BigQuery aggregate queries (using APPROX_QUANTILES for median price calculations and GROUP BY roll-ups for multi-year trend charts). Each query operates on a resilient 3-tier fallback pipeline (BigQuery → Live API → Committed Seed Snapshot), backed by startup pre-warm threads and multi-hour TTL memoization so first-time visitors never pay a cold-start query penalty.

To deepen real-time situational awareness, I expanded the Gov Updates hub from 12 to 22 official Telegram channels—integrating live broadcasts from the Elections Department, CPF Board, LTA, SkillsFuture, SWDA, NEA, NLB, URA, and GovTech. For statutory boards without Telegram channels, I built dedicated BeautifulSoup scrapers for CDC / RedeemSG Media Releases (scraped from cdc.gov.sg via a strict trusted-domain allowlist) and IRAS News & Updates for tax filing announcements, each rendering real-time green "Live" vs amber "Last Known Cache" data freshness status badges.

Finally, I expanded the automated test suite to 151 automated tests (145 Python + 6 JavaScript) running on every GitHub Actions push, exercising BigQuery fallbacks, WAF-proof parsers, and live scraping boundaries deterministically.

MerlionOS Gov Updates hub: CDC/RedeemSG media-release scraper and official Telegram broadcasts with live freshness badges
📸 WAF-Proof Live Agency Feeds: the expanded Gov Updates hub streams CDC / RedeemSG Media Releases (scraped from the CDC Newsroom) alongside Official Government Broadcasts from 22 Telegram channels (@LTASingapore, @Skills_Workforce_Development and more)—each panel carrying a green "Live" freshness badge and a "Last synced" timestamp, hosted on Render to bypass datacenter WAF 403 blocks.

🛡️ Trust by Design: A Hard PII Guardrail & a Calmer Dashboard

As more citizens leaned on the Co-Pilot for real civic questions, one became unavoidable: what should the system refuse to do? I built the privacy guardrail in two layers. If a citizen types an NRIC, FIN, or passport number straight into the chat, a regex fast-path in tools/security.py catches it and blocks the message before it ever reaches Gemini—a deterministic, hard-coded refusal, not a model that might change its mind. On the vision side, where an uploaded image can't be pattern-matched in advance, the assistant is instructed to refuse to read identity numbers off a document and to ask the citizen to redact them first. Trust in a civic tool is earned by the guardrails citizens cannot accidentally bypass, not just by the features they can access.

MerlionOS PII Guardrail blocking an NRIC input before sending MerlionOS PII Guardrail blocking a Passport Number input before sending MerlionOS PII Guardrail blocking a Luhn-checked Credit Card input before sending
📸 Multi-Pattern PII Security Interception (3-Card Mobile Row): 1. NRIC/FIN Block: Prompts containing Singapore NRICs (S1234567A) stopped at the door ➔ 2. Passport Block: Passport numbers (K1234567A) caught by security filter ➔ 3. Card Block: 16-digit credit cards validated via Luhn checksum and blocked before reaching the AI.

On the dashboard side, SG Hub had grown into a dozen live panels—transit, weather, HDB, jobs, tax, gov updates, community deals—and not everyone wants all of them open at once. So I made every panel collapsible: tap any card's title to fold it away, or use Collapse All and Expand All to reshape the whole dashboard in one move. Someone who only follows their MRT line and the weather can tuck everything else out of sight, and it stays that way on their next visit. For a hub this dense, letting people quiet the parts they don't need was the difference between a wall of data and a dashboard they actually come back to.

🔬 Does It Actually Work? A Quiet Audit Before Submission

After weeks of rapid feature development, the last stretch before submission was about a less glamorous question: do the things I already built actually work? In one case, the honest answer was no. The plain-English glossary—those dashed-underline tooltips that quietly explain jargon like CPF, SRS, BTO, and COE as you read—had switched itself off. A single stray line of code had disabled it since launch: not one term was ever being explained, and nothing on the surface gave it away. I tracked it down, fixed it, and watched dozens of acronyms light up across the dashboard again.

The audit also showed me how many real questions the assistant still couldn't answer well. So I more than doubled its knowledge base—from 42 to 109 grounded, cited answers—covering the things people actually ask about: CPF retirement sums, HDB lease buyback, income-tax objections, the Progressive Wage Model, CareShield Life, GST Vouchers, SimplyGo, and PR and long-term-pass applications. Every answer is tied to an official .gov.sg source, so the Co-Pilot points you to the real page instead of guessing from memory—and I added a check that measures whether it actually retrieves the right source, so quality is something I can prove rather than hope for.

Finally, because the app hands out specific tax figures and CPF top-up amounts, I added plain "not financial, tax, or legal advice" notes to the relief optimizer and the Co-Pilot. It's a small thing, but being upfront about the tool's limits felt as important as any feature.

⚡ The Last Stretch: Faster, Tidier, and Open to Everyone

Some panels simply felt slow. The housing dashboard, for one, waited on three separate government sources before it showed anything. I reworked it to pull them together and to remember recent results, so pages that used to stall now snap open—and when a government feed goes down, the dashboard quietly falls back to the last good data behind a small badge instead of freezing on a spinner or dropping you on an error.

Behind the scenes, the front-end had grown into one enormous file that was getting risky to touch. I split it into eight smaller, self-contained files—one for the glossary, one for the charts, one for the chat, and so on—so I could improve a single feature confident I wasn't quietly breaking three others. I also made the dashboard fully keyboard-navigable, so it isn't a mouse-only tool.

None of this shows up on screen, but it's what lets the app keep getting better without getting more fragile. A growing test suite of 186 automated tests (180 Python + 6 JavaScript, 100% pass) runs on every change, so before anything ships I know I haven't broken a part of a tool people rely on.

🌱 Beyond the Hackathon: Evolving into an Active Platform

The hackathon submission deadline came and went, and the prototype refinement phase was officially over—but the journey was far from done. Building a tool that solved my own everyday citizen friction meant I couldn't just walk away when the competition ended. MerlionOS became an active, evolving open-source civic platform.

Operating a live civic engine in the wild brought two critical questions to the surface: What if upstream government agencies update their HTML layouts or API endpoints without notice? And what if new public datasets or statutory rules become available? Answering these required building a system that could monitor its own health and continuously expand its capabilities.

🔍 The Silent Failures: Building a Daily Health Monitoring Sentinel

Before expanding into new features, the first priority for an active platform was operational health. Building against public government websites comes with an invisible risk: statutory boards silently change their HTML layouts, update press release tables, or alter API schemas without warning. A scraper that worked perfectly yesterday can silently break today, leaving citizens with stale data while the dashboard shows no obvious error.

To catch these silent failures before citizens ever encounter them, I built an automated daily Health Monitoring Sentinel (scripts/healthcheck.py) running via GitHub Actions every morning at 09:30 SGT. It executes nine canary checks across three critical layers of the app:

When a check encounters broken code or stale data, the workflow doesn't just log an error in CI—it automatically opens or updates a data-monitor GitHub Issue and triggers an email alert straight to my inbox. As long as every feed stays healthy and current, the system remains quietly green.

⚡ Expanding Civic Capabilities: PDF Copilot, Localization & Life-Events

With data integrity safeguarded by the daily sentinel, I turned to expanding the platform's core capabilities:

📄 PDF Document Copilot with "Redact & Proceed" PII Protection: As citizens started asking for help analyzing official PDF documents—such as IRAS Notices of Assessment, CPF Account Statements, or HDB Eligibility Letters—blocking every NRIC-bearing document wasn't practical, because almost every official Singapore government letter carries the recipient's NRIC. To solve this safely, I engineered a server-side text extraction engine using pypdf with deterministic redact-and-proceed PII protection (tools/security.py). The server automatically scrubs NRICs, FINs, passports, phone numbers, and credit card numbers to [REDACTED] while preserving assessable income and tax figures—forwarding only the sanitized text to Gemini. Raw file bytes containing personal identifiers never reach the LLM.

🌐 4-Language Singov Localization & Elderly Accessibility Mode: Singapore is officially quadrilingual. I built a 1-click national localization engine (static/js/translations.js) supporting English, Chinese (中文), Malay (Bahasa Melayu), and Tamil (தமிழ்)—standardizing statutory terms against official Lianhe Zaobao (联合早报) and Berita Harian glossaries (e.g. 预购组屋 BTO, 缴税通知单 NOA, 定心与支援套餐 AP). For senior citizens and visually impaired users, I added a 1-click Elderly Accessibility Mode that scales base typography (16–18px), enlarges touch hit targets, and boosts contrast ratios across the dashboard.

🧭 Multi-Agency Life-Events Journeys: Navigating major life milestones—such as buying a first HDB flat, welcoming a baby, or transitioning into retirement—usually requires jumping across 4 to 5 separate statutory portals. I built the Life-Events Journeys module (tools/life_events.py and static/js/journeys.js), guiding citizens step-by-step through chronological milestones: linking HFE letters, CPF grant calculations, Baby Bonus cash gifts, and retirement sum choices in a single unified view.

🚀 The Next Frontier: Building the Civic Intelligence Co-Pilot

Looking back at how MerlionOS evolved, I realized most civic tools hit a ceiling because they stop at simply displaying information. But the real leap in value happens when an assistant moves beyond passive data display—and starts deciding, warning, and acting on a citizen's behalf. No single government agency can do this alone because each statutory board only sees its own isolated policy slice.

The true vision for MerlionOS is a unified Civic Intelligence (CI) Co-Pilot that bridges these agency silos into personal decision-making. Imagine a single eligibility engine that automatically calculates your "money left on the table"—unifying EHG housing grants, CPF accrued-interest rules, Baby Bonus cash gifts, Workfare payouts, and tax reliefs into one personal financial verdict. Instead of leaving citizens to compute grant eligibility across half a dozen websites, the CI Co-Pilot computes exact dollar opportunities based on a citizen's basic profile.

That same intelligence transforms scattered portal links into complete Life-Event Orchestrations—assembling deadlines, grants, calculators, and checklists from HDB, CPF, IRAS, and MOM into a single seamless journey for major milestones like buying a first home, welcoming a baby, transitioning into retirement, or navigating retrenchment. It turns backward-looking data into forward-looking foresight, answering questions like "Can I afford this flat?" or "Is now the right time to bid for a COE?" using real-time price medians, loan limits, and bidding trends.

By repointing automated background event pipelines directly at citizen preferences, the CI Co-Pilot turns rare site visits into an ongoing habit—delivering proactive Telegram alerts when COE premiums drop, Punggol BTO launches open, or MRT lines experience disruptions. Combined with a community ScamShield pattern checker and multilingual accessibility across Singapore's four official languages (English, 中文, Malay, Tamil), the Co-Pilot becomes not just a sidebar chatbot, but the central intelligence engine for navigating Singapore public life.

💡 Key Takeaways & Lessons Learned

Building MerlionOS taught me that civic tech becomes transformative when developers build tools to solve their own everyday friction. Here are the core lessons I took away from this journey:

Building MerlionOS taught me a simple lesson: the best tools are built when you are the first and most critical user of your own code. It's the micro-optimizations—the mobile layout alignments, the stream controllers, and the silent API fallbacks—that turn a demo into an app you trust to use every single day.

Where to find MerlionOS