<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: LankaSolve</title>
    <description>The latest articles on DEV Community by LankaSolve (@lankasolve_fbf99f83536698).</description>
    <link>https://dev.to/lankasolve_fbf99f83536698</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4212142%2F8ef8ea53-4719-4a70-bc1a-809f07d77ab4.png</url>
      <title>DEV Community: LankaSolve</title>
      <link>https://dev.to/lankasolve_fbf99f83536698</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lankasolve_fbf99f83536698"/>
    <language>en</language>
    <item>
      <title>I put every government fee on my site in a CSV, with an expiry date</title>
      <dc:creator>LankaSolve</dc:creator>
      <pubDate>Wed, 28 Oct 2026 05:02:20 +0000</pubDate>
      <link>https://dev.to/lankasolve_fbf99f83536698/i-put-every-government-fee-on-my-site-in-a-csv-with-an-expiry-date-3fnj</link>
      <guid>https://dev.to/lankasolve_fbf99f83536698/i-put-every-government-fee-on-my-site-in-a-csv-with-an-expiry-date-3fnj</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://lankasolve.lk/" rel="noopener noreferrer"&gt;LankaSolve&lt;/a&gt;, 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."&lt;/p&gt;

&lt;p&gt;Here's what worked.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Every fact is a row with a source and a next-check date
&lt;/h4&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;authority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;source&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;last&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;verified&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;next&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;check&lt;/span&gt;&lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="k"&gt;due&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;volatility&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;notes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;next_check_due&lt;/code&gt; column is the part that matters. A fact with no expiry date quietly turns into a guess.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Volatility tiers decide how often to re-check
&lt;/h4&gt;

&lt;p&gt;Not every fact goes stale at the same speed, so every row has a tier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GREEN&lt;/strong&gt;: maths and constants (a perch is 25.29 m²). Never re-checked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;YELLOW&lt;/strong&gt;: official values that change now and then (fees, tax rates). Re-checked every few months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RED&lt;/strong&gt;: 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.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Write down what the government &lt;em&gt;doesn't&lt;/em&gt; publish
&lt;/h4&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Calculators are plain functions, not components
&lt;/h4&gt;

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

&lt;p&gt;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 (&lt;code&gt;node --test&lt;/code&gt;). There's no framework, and no test that checks the same maths a second time through the UI.&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Static everything
&lt;/h4&gt;

&lt;p&gt;It's &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt; building plain HTML to Cloudflare Pages, with &lt;a href="https://pagefind.app/" rel="noopener noreferrer"&gt;Pagefind&lt;/a&gt; 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.&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Make the receipts public
&lt;/h4&gt;

&lt;p&gt;All of this would be invisible if the sources stayed in a CSV in the repo, so there's a &lt;a href="https://lankasolve.lk/verified-sources/" rel="noopener noreferrer"&gt;Verified Sources&lt;/a&gt; page listing what was checked, where, and when. It's the page I'd point to if anyone asked "why should I trust this?"&lt;/p&gt;

&lt;h4&gt;
  
  
  What I'd tell anyone building something similar
&lt;/h4&gt;

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

&lt;p&gt;If you want to see it in practice, the &lt;a href="https://lankasolve.lk/calculators/driving-exam-calculator/" rel="noopener noreferrer"&gt;driving licence practice test&lt;/a&gt; is the most self-contained part. It's in English, Sinhala and Tamil, and every answer has its source.&lt;/p&gt;

</description>
      <category>astro</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>opendata</category>
    </item>
  </channel>
</rss>
