DEV Community

Cover image for I put every government fee on my site in a CSV, with an expiry date
LankaSolve
LankaSolve

Posted on Fully Autonomous

I put every government fee on my site in a CSV, with an expiry date

Most "how to get a passport in [country]" pages go wrong the same way. They were right the day someone wrote them, nobody wrote down where the facts came from, and nothing tells anyone when to check again.

I run LankaSolve, a static site with step-by-step guides and calculators for Sri Lankan government procedures: passports, national ID cards, police clearance, driving licences, vehicle transfers, EPF/ETF and VAT. It's one person, a few hours a week. So the design question was never "how do I write a lot of pages." It was "how do I keep a few hundred government facts true without a team."

Here's what worked.

1. Every fact is a row with a source and a next-check date

The core of the project isn't a component. It's docs/sources/source-registry.csv. Every externally sourced fact (a fee, a form number, a deadline, an office address) is one row:

data_id, category, data_point, value, unit,
source_authority, source_title, source_url, source_date,
last_verified, next_check_due, volatility, notes
Enter fullscreen mode Exit fullscreen mode

The rule is simple: if a Sri Lankan fee, rule or requirement isn't in the registry with a primary source, it doesn't go on the site. Not from memory, and not from a blog that ranks well. There are about 350 rows behind the published pages right now.

The next_check_due column is the part that matters. A fact with no expiry date quietly turns into a guess.

2. Volatility tiers decide how often to re-check

Not every fact goes stale at the same speed, so every row has a tier:

  • GREEN: maths and constants (a perch is 25.29 m²). Never re-checked.
  • YELLOW: official values that change now and then (fees, tax rates). Re-checked every few months.
  • RED: things that change often or need interpretation. These get short re-check dates, and one rule: a RED fact can't become a calculator or an automated decision without a deliberate sign-off.

The RED tier earned its place this year. Sri Lanka Police suspended online police clearance applications in September and restored them from 16 October. While it was suspended, that row was re-checked weekly. The guide carried a dated status banner, and the public sources page carried a timeline. When the notice came out, updating the site meant changing one row and the text that depended on it, not hunting through pages.

3. Write down what the government doesn't publish

The driving licence practice test taught me this. It has 100 questions, and each one cites the regulation or official page its answer comes from, down to the gazette schedule for road signs. The mock exam is 40 questions, with a 30/40 pass mark and 60 minutes.

Those three exam numbers aren't published anywhere by the Department of Motor Traffic. They come from driving schools and exam-prep material. So the data file says that next to the numbers, and so does the page. It's tempting to present a widely repeated number as fact. Labelling it honestly costs one sentence.

The same idea applies to forms. Each guide's checklist links the official form it names. The rule is: link the department's own PDF if one exists. If the form is only generated inside a portal when you submit (some company-registration forms work this way), link the portal. If it's issued ad hoc by a local office with no central version, link nothing. Never guess a PDF URL from a form number.

4. Calculators are plain functions, not components

The EPF/ETF, VAT and loan calculators live in src/calculators/ as pure JavaScript, with no Astro, no DOM and no browser state. A small client-side script in src/ui/ imports the function and wires it to the form.

That keeps testing boring in the good sense. Each calculator gets one test file covering normal values, zero, the boundaries, and one worked real-world example, and it runs with Node's built-in test runner (node --test). There's no framework, and no test that checks the same maths a second time through the UI.

5. Static everything

It's Astro building plain HTML to Cloudflare Pages, with Pagefind for search at build time. Runtime dependencies: Astro and the sitemap integration. There's no CMS, database or server. Guide content lives in data files that a shared guide template renders, so a fee change is a data edit, not a layout edit.

6. Make the receipts public

All of this would be invisible if the sources stayed in a CSV in the repo, so there's a Verified Sources page listing what was checked, where, and when. It's the page I'd point to if anyone asked "why should I trust this?"

What I'd tell anyone building something similar

  • Put a date on every fact, plus a date to check it again.
  • Keep the facts in one boring file, not spread across your templates.
  • Write down the gaps. "This isn't officially published" is useful information.
  • A one-person project lives or dies on maintenance cost, so price every new page in re-checks, not in words.

If you want to see it in practice, the driving licence practice test is the most self-contained part. It's in English, Sinhala and Tamil, and every answer has its source.

Top comments (2)

Collapse
 
posting-dude profile image
Posting Dude •

The next_check_due column is the part i'd steal. Do you have anything that fails the build when a row is past its date, or is it a manual look each week? I'd be tempted to make overdue RED rows block the deploy and overdue YELLOW ones just print a warning, otherwise the column slowly turns into one more thing nobody reads. Also like that the 30/40 pass mark is labelled as not officially published, most sites would just state it as fact.

Collapse
 
lankasolve_fbf99f83536698 profile image
LankaSolve •

Thanks, this is a good question. Right now it's somewhere in between. A test reads the CSV and prints every YELLOW or RED row that's past its next_check_due, but it's warning-only and it runs in the test suite, not the deploy. So it shows up when I run tests, and nothing stops a deploy. Today the count is zero overdue out of ~370 dated rows, though that's partly because I just did a pass.

I like your split. My hesitation about RED rows blocking the deploy is that one stale fee would then hold back an unrelated fix, like a typo or a broken link on another page. The version I'm leaning toward is failing only when the page you're changing depends on an overdue RED row. Until then, the warnings have to be loud enough that I actually read them, which is exactly the failure you described.

And yes on the pass mark. I couldn't find it published anywhere official, so the honest thing is to say that rather than repeat what everyone else repeats.