<?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: Matt Rybin</title>
    <description>The latest articles on DEV Community by Matt Rybin (@mattrybin).</description>
    <link>https://dev.to/mattrybin</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3713947%2F418e186f-bda8-42e8-a79b-2bff1c96a983.jpg</url>
      <title>DEV Community: Matt Rybin</title>
      <link>https://dev.to/mattrybin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mattrybin"/>
    <language>en</language>
    <item>
      <title>I Built a Polish ID Number Generator — Here's the Math</title>
      <dc:creator>Matt Rybin</dc:creator>
      <pubDate>Wed, 11 Mar 2026 20:00:56 +0000</pubDate>
      <link>https://dev.to/mattrybin/i-built-a-polish-id-number-generator-heres-the-math-2jhl</link>
      <guid>https://dev.to/mattrybin/i-built-a-polish-id-number-generator-heres-the-math-2jhl</guid>
      <description>&lt;p&gt;I built a free tool that generates valid Polish ID card numbers for software testing. Each number passes the same ICAO 9303 checksum validation used in real passport machine-readable zones worldwide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Exists
&lt;/h2&gt;

&lt;p&gt;Polish ID card numbers (&lt;code&gt;dowód osobisty&lt;/code&gt;) aren't random strings. They have a strict format — three letters, six digits — and the last digit is a checksum. If you're building anything that touches Polish identity data (banks, insurance, government forms, KYC flows), random strings won't pass validation. And you can't use real numbers in test environments because GDPR exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Format
&lt;/h2&gt;

&lt;p&gt;Every Polish ID number looks like this: &lt;code&gt;ABC123456&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ABC&lt;/code&gt; — the series (three uppercase letters)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;12345&lt;/code&gt; — serial digits&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;6&lt;/code&gt; — check digit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not all 26 letters show up in the series. &lt;code&gt;O&lt;/code&gt; and &lt;code&gt;Q&lt;/code&gt; are excluded to avoid confusion with &lt;code&gt;0&lt;/code&gt;. So the valid alphabet is 24 letters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Interesting Part: ICAO 9303 Checksums
&lt;/h2&gt;

&lt;p&gt;The check digit uses the same algorithm found in passport MRZs globally. It's simple and kind of elegant:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Convert each character to a number. Digits stay as-is. Letters map to &lt;code&gt;A=10, B=11, ... Z=35&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Multiply each value by a repeating weight pattern: &lt;code&gt;7, 3, 1, 7, 3, 1, 7, 3, 1...&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sum everything up.&lt;/li&gt;
&lt;li&gt;The check digit is &lt;code&gt;sum mod 10&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In JavaScript, the core logic looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;charValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// digits: face value, letters: A=10..Z=35&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;55&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkDigit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;charValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator picks three random letters from the valid set, generates five random digits, concatenates them, runs the checksum over all eight characters, and appends the result as the ninth character. Every output is structurally valid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deduplication
&lt;/h2&gt;

&lt;p&gt;One small detail I liked solving: the tool keeps a history set of the last 1,000 generated numbers and rerolls if it hits a duplicate. Overkill for a 24×10⁵ keyspace? Probably. But it means you can generate batches of 100 without worrying about collisions in your test fixtures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateIdNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;idHistorySet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;idNum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;The tool runs inside a Phoenix LiveView app. The generation itself is pure client-side JavaScript — no server round-trip needed. The Elixir/Phoenix side handles rendering, i18n (the tool works in both English and Polish), and the colocated hook pattern that wires up the JS behavior to the LiveView DOM.&lt;/p&gt;

&lt;p&gt;Everything runs in the browser. No ID numbers are sent to or stored on any server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;English:&lt;/strong&gt; &lt;a href="https://poland.gg/tools/polish-id-number-generator" rel="noopener noreferrer"&gt;Polish ID Number Generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polish:&lt;/strong&gt; &lt;a href="https://poland.gg/pl/narzedzia/generator-dowodu-osobistego" rel="noopener noreferrer"&gt;Generator numeru dowodu osobistego&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool also supports output formatting (raw &lt;code&gt;ABC123456&lt;/code&gt; vs. spaced &lt;code&gt;ABC 123456&lt;/code&gt;) and batch generation of 1, 5, 10, or 100 numbers at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Generators
&lt;/h2&gt;

&lt;p&gt;Poland.gg has the same kind of generators for PESEL numbers, NIP tax IDs, and mikrorachunek tax payment accounts — if you're building test data for Polish systems, you probably need more than one of these.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm considering adding a companion validator tool that breaks down an existing ID number and shows the checksum math step by step. Could be useful for debugging validation logic. Open to suggestions.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>javascript</category>
      <category>elixir</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I built a lightning-fast statistics platform for Poland</title>
      <dc:creator>Matt Rybin</dc:creator>
      <pubDate>Fri, 16 Jan 2026 05:58:21 +0000</pubDate>
      <link>https://dev.to/mattrybin/i-built-a-lightning-fast-statistics-platform-for-poland-1ldm</link>
      <guid>https://dev.to/mattrybin/i-built-a-lightning-fast-statistics-platform-for-poland-1ldm</guid>
      <description>&lt;p&gt;I needed Polish demographic data for a project and kept running into the same problem. The official statistics site (GUS) was slow, confusing, and the language they use is pretty hard to parse even if you speak Polish. And ChatGPT kept giving me different numbers every time I asked.&lt;/p&gt;

&lt;p&gt;So I thought, why not just build something better?&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actualy does
&lt;/h2&gt;

&lt;p&gt;You click on any region in Poland and you get the stats. Thats it. No loading, no waiting, no confusing navigation. You can go from the whole country down to voivodeship (thats like a province), then to powiat (county), and all the way down to gmina which is like a commune or municipality. Theres over 2,478 gminas and each one has its own page with stats.&lt;/p&gt;

&lt;p&gt;Population, housing, geography, demographics — all pulled from GUS which is the official government statistics office. Same data they have, just actually usable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tech side
&lt;/h2&gt;

&lt;p&gt;I built this in Elixir. Partly because I love the language but mainly because I wanted pages to load &lt;em&gt;instantly&lt;/em&gt;. Not fast, instant. Like you click and its already there. Elixir's concurrency model and the way Phoenix handles requests made this possible. I also built a custom template engine that allow me to create super good text!&lt;/p&gt;

&lt;p&gt;The whole thing runs in both English and Polish. Same data, same interface, just switch the language. No weird google translate stuff, its properly localized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I think this matters
&lt;/h2&gt;

&lt;p&gt;Government data should be accessible to everyone not just researchers who know how to navigate confusing websites. Now you can find the population of any gmina in Poland in like 2 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Whats next
&lt;/h2&gt;

&lt;p&gt;Im planning to add more datasets — economy, education, transport, environment. Eventually I want to build API access so developers can pull this data programatically. And theres some ideas around property predictions based on regional trends but thats further out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it out
&lt;/h2&gt;

&lt;p&gt;The site is live at &lt;a href="https://poland.gg" rel="noopener noreferrer"&gt;poland.gg&lt;/a&gt; and theres a Polish version at &lt;a href="https://poland.gg/pl" rel="noopener noreferrer"&gt;poland.gg/pl&lt;/a&gt;. Its free to use.&lt;/p&gt;

&lt;p&gt;Would love to hear what you think. What data would be useful? Whats missing? Any feedback is super helpful since im still building this out.&lt;/p&gt;

</description>
      <category>elixir</category>
      <category>datascience</category>
      <category>webdev</category>
      <category>ui</category>
    </item>
  </channel>
</rss>
