<?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: Xayen Universe</title>
    <description>The latest articles on DEV Community by Xayen Universe (@xayenuniverse).</description>
    <link>https://dev.to/xayenuniverse</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%2F4224848%2F156edde6-5b4a-4049-b007-23bb0ec31e6e.png</url>
      <title>DEV Community: Xayen Universe</title>
      <link>https://dev.to/xayenuniverse</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xayenuniverse"/>
    <language>en</language>
    <item>
      <title>I Built an SDK Because I Was Desperate</title>
      <dc:creator>Xayen Universe</dc:creator>
      <pubDate>Tue, 03 Nov 2026 19:41:27 +0000</pubDate>
      <link>https://dev.to/xayenuniverse/i-built-an-sdk-because-i-was-desperate-5flb</link>
      <guid>https://dev.to/xayenuniverse/i-built-an-sdk-because-i-was-desperate-5flb</guid>
      <description>&lt;p&gt;This is not a story about a great idea I had in the shower. It's a story about&lt;br&gt;
being hungry, tired, and slightly greedy, and deciding that the fastest path out&lt;br&gt;
of that was to build something for a bank.&lt;/p&gt;

&lt;p&gt;I'm writing it down because most "how I built X" posts skip the embarrassing&lt;br&gt;
part. They start with "I noticed a gap in the market" and end with "and then it&lt;br&gt;
was a huge success." That's not what happened here. What happened is: I was&lt;br&gt;
desperate, I built an SDK, it mostly broke, I fixed it, and then I wrote about&lt;br&gt;
it because that's how I process things.&lt;/p&gt;

&lt;p&gt;Here's the honest version.&lt;/p&gt;




&lt;h2&gt;
  
  
  The foolish idea
&lt;/h2&gt;

&lt;p&gt;I'd been looking at ALATPay's developer documentation for a different reason —&lt;br&gt;
I wanted to integrate their payment gateway into something I was building. The&lt;br&gt;
docs were fine. The JavaScript SDK was fine. But there was no Java SDK.&lt;/p&gt;

&lt;p&gt;My first thought was the wrong one, and I want to be specific about why it was&lt;br&gt;
wrong.&lt;/p&gt;

&lt;p&gt;I thought: &lt;strong&gt;"If I build the Java SDK, maybe they'll pay me."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not "maybe they'll hire me." Not "maybe it'll be a good portfolio piece." I&lt;br&gt;
genuinely thought a bank would notice a random developer's unrequested&lt;br&gt;
open-source project and decide to compensate them for it. That is not how banks&lt;br&gt;
work. I knew that at the time, in the abstract. I chose not to know it in the&lt;br&gt;
specific, because I was hungry and tired and looking for something to be true&lt;br&gt;
that wasn't.&lt;/p&gt;

&lt;p&gt;The SDK took five days to build. The compensation has, so far, been zero&lt;br&gt;
naira. The portfolio piece has, so far, been worth more than I expected.&lt;/p&gt;

&lt;p&gt;If you're reading this because you're thinking about building something for a&lt;br&gt;
company hoping they'll pay you for it: &lt;strong&gt;don't do that.&lt;/strong&gt; Build it because you&lt;br&gt;
need it, or because it's a portfolio piece, or because you want to learn the&lt;br&gt;
API. But don't build it for the money. The money is not coming.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "everything broke" actually means
&lt;/h2&gt;

&lt;p&gt;I want to describe this carefully because "everything broke" is what I told&lt;br&gt;
a friend at the time, and it's accurate but not useful.&lt;/p&gt;

&lt;p&gt;What actually happened is: &lt;strong&gt;everything broke at a specific moment, and then&lt;br&gt;
one thing at a time after that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first version didn't work at all. The authentication was wrong. ALATPay&lt;br&gt;
uses three headers — &lt;code&gt;Authorization&lt;/code&gt;, &lt;code&gt;Business-Id&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;Ocp-Apim-Subscription-Key&lt;/code&gt; — and I was sending two of them. The third one&lt;br&gt;
(&lt;code&gt;Ocp-Apim-Subscription-Key&lt;/code&gt;) is an Azure API Management convention that I&lt;br&gt;
hadn't encountered before. I sent it under the wrong name, or not at all,&lt;br&gt;
depending on the hour of the night.&lt;/p&gt;

&lt;p&gt;I got 401s for a full day. Every 401 response told me the same thing — "invalid&lt;br&gt;
subscription key" — and every time I read it, I thought "that can't be right, I&lt;br&gt;
put the key in." I read it as a bug in their system, not a bug in my code. It&lt;br&gt;
was a bug in my code.&lt;/p&gt;

&lt;p&gt;The second version authenticated correctly but couldn't parse the response. The&lt;br&gt;
list of transactions in ALATPay's response has more fields than I'd modelled.&lt;br&gt;
My Jackson classes were missing &lt;code&gt;feeAmount&lt;/code&gt; and &lt;code&gt;settlementId&lt;/code&gt; and a dozen other&lt;br&gt;
things. Jackson doesn't crash when it sees unknown fields by default — it&lt;br&gt;
ignores them — but it does crash when it sees a field it &lt;em&gt;thinks&lt;/em&gt; it knows, with&lt;br&gt;
a type that doesn't match. Which was happening, because I'd guessed at the&lt;br&gt;
types.&lt;/p&gt;

&lt;p&gt;The third version worked. Then the tests failed.&lt;/p&gt;

&lt;p&gt;This is the part that took longest, and it's the part I want to talk about&lt;br&gt;
because it's not in any tutorial.&lt;/p&gt;




&lt;h2&gt;
  
  
  The test problem nobody warns you about
&lt;/h2&gt;

&lt;p&gt;When you're building an SDK, you can't test it against the real API. Not&lt;br&gt;
comfortably. Every test call costs you a real request against a real merchant&lt;br&gt;
account, and if you're testing in production you're going to trip rate limits&lt;br&gt;
and log a lot of nonsense in someone's audit trail.&lt;/p&gt;

&lt;p&gt;So you use a fake server. In Java, that's usually &lt;code&gt;MockWebServer&lt;/code&gt; — it spins up&lt;br&gt;
a local HTTP server that you can tell to respond with whatever you want.&lt;/p&gt;

&lt;p&gt;The problem I hit is that &lt;strong&gt;MockWebServer is a different server from ALATPay.&lt;/strong&gt;&lt;br&gt;
It doesn't authenticate, it doesn't rate-limit, it doesn't have a schema. You&lt;br&gt;
tell it what to respond, and it responds. That's the whole point. But it means&lt;br&gt;
that a test that passes against MockWebServer can still fail against ALATPay,&lt;br&gt;
because the test is verifying my &lt;em&gt;assumptions&lt;/em&gt; about ALATPay, not ALATPay.&lt;/p&gt;

&lt;p&gt;The first test I wrote checked that my SDK could fetch a list of transactions.&lt;br&gt;
It passed. Then I ran the same code against the real API and got a 404. My test&lt;br&gt;
had been testing whether Jackson could parse a JSON string I'd written, not&lt;br&gt;
whether my SDK could talk to a real server. Which is a real thing to test, but&lt;br&gt;
it's not the thing I thought I was testing.&lt;/p&gt;

&lt;p&gt;The fix was to be honest about what the tests verify. The README now says:&lt;br&gt;
&lt;em&gt;"All tests use MockWebServer — no real API credentials required to run them."&lt;/em&gt;&lt;br&gt;
Which is true. It's not the same as "the SDK works against ALATPay" and I&lt;br&gt;
learned not to imply that it was.&lt;/p&gt;




&lt;h2&gt;
  
  
  The five days between "shipped" and "actually shipped"
&lt;/h2&gt;

&lt;p&gt;The commit history tells this part better than I can.&lt;/p&gt;

&lt;p&gt;My initial commit was on October 11th. Then, five days later, on October 16th,&lt;br&gt;
the last commit message is: &lt;em&gt;"URL-encode user input in query and path&lt;br&gt;
segments."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's the whole story of the intermediate five days. I shipped. I told myself&lt;br&gt;
it was done. Then I kept finding things.&lt;/p&gt;

&lt;p&gt;The URL-encoding thing is a good example because it's the kind of bug that&lt;br&gt;
doesn't show up in testing. If you pass a transaction ID with a space in it —&lt;br&gt;
or a slash, or an ampersand — the URL is malformed and the server returns&lt;br&gt;
nonsense. My tests used IDs that were safe. Real transaction IDs are not always&lt;br&gt;
safe. Five days later I noticed and fixed it.&lt;/p&gt;

&lt;p&gt;There's a version of this story where I say "I shipped the SDK in one weekend,&lt;br&gt;
here's the link." That version is true in the sense that the repo existed after&lt;br&gt;
the weekend. It's not true in the sense that the SDK was finished, because the&lt;br&gt;
SDK wasn't finished. It was &lt;em&gt;started&lt;/em&gt;, and it kept being started for five more&lt;br&gt;
days.&lt;/p&gt;

&lt;p&gt;I think the "shipped in a weekend" framing is a lie that hurts the people who&lt;br&gt;
read it. It makes them feel bad about the fact that their project is still&lt;br&gt;
breaking after a week. Their project is supposed to be still breaking after a&lt;br&gt;
week. Yours was too.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the SDK actually doesn't do
&lt;/h2&gt;

&lt;p&gt;The README is honest about this, and I want to be honest about it here too:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bank transfer is stubbed.&lt;/strong&gt; The endpoint for generating virtual accounts&lt;br&gt;
isn't in the public API documentation, so my &lt;code&gt;BankTransferService&lt;/code&gt; throws&lt;br&gt;
&lt;code&gt;UnsupportedOperationException&lt;/code&gt; and tells you to check the docs. It's a&lt;br&gt;
placeholder. It doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USSD and static wallets are "planned."&lt;/strong&gt; Planned means not built. I have&lt;br&gt;
notes on what they'd look like. I haven't written them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SDK isn't on Maven Central.&lt;/strong&gt; You can't add it as a dependency yet. You&lt;br&gt;
have to clone it and build it locally. That's a real friction point, and I&lt;br&gt;
haven't paid the cost of getting a publishing account set up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need your own credentials.&lt;/strong&gt; The SDK doesn't come with test keys, and it&lt;br&gt;
doesn't have a demo mode. To run it against anything, you need a merchant&lt;br&gt;
account with ALATPay, three credentials from their dashboard, and the patience&lt;br&gt;
to get them. Without those, the SDK is a very well-documented way to throw&lt;br&gt;
&lt;code&gt;AlatPayAuthException&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That last point is the one that limits the SDK most in practice, and it's the&lt;br&gt;
one I'd change first if I were doing it again. A version of this with a&lt;br&gt;
sandbox credential flow — or with a mock mode that returns fixture data without&lt;br&gt;
touching the network — would be more useful than the version I shipped.&lt;/p&gt;

&lt;p&gt;I didn't build that because I didn't know how to. That's the honest reason.&lt;br&gt;
Not "I chose not to because of scope." I didn't know how.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it was actually worth
&lt;/h2&gt;

&lt;p&gt;The SDK has, at this point, been read by fewer than a hundred people. It has,&lt;br&gt;
as far as I know, zero users. Nobody has opened a pull request. Nobody has sent&lt;br&gt;
me an issue.&lt;/p&gt;

&lt;p&gt;By the metrics that matter to a venture capitalist, it's a failure.&lt;/p&gt;

&lt;p&gt;By the metrics that matter to me, it's not, because of three things that&lt;br&gt;
happened after I built it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I learned what an SDK actually is.&lt;/strong&gt; Not "a wrapper around an API" in the&lt;br&gt;
abstract, but the specific mechanics: a client object, a config object, typed&lt;br&gt;
exceptions, a shared HTTP layer, tests that mock the server. I can read any&lt;br&gt;
other SDK now and understand what it's doing, because I built one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I learned how banks think about security.&lt;/strong&gt; The three-header scheme on&lt;br&gt;
ALATPay isn't decorative. It's a specific model of what can go wrong and who's&lt;br&gt;
responsible for what. Building a client for it meant I had to understand it,&lt;br&gt;
and understanding it taught me more about payments infrastructure than reading&lt;br&gt;
a blog post would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I got a piece of code I can point at.&lt;/strong&gt; When I talk to people about what I&lt;br&gt;
do now — when I say I build SDKs — I have a URL. That URL is a thing I made,&lt;br&gt;
and it exists, and it has tests, and it has a license, and somebody can clone&lt;br&gt;
it and run it. That's worth more than the compensation I was imagining when I&lt;br&gt;
started.&lt;/p&gt;

&lt;p&gt;The compensation did not arrive. The thing I actually got was better.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're going to build one anyway
&lt;/h2&gt;

&lt;p&gt;Build it for yourself, or for the portfolio, or because you want to learn the&lt;br&gt;
API. Not because you're hoping a company will notice.&lt;/p&gt;

&lt;p&gt;Here are the four things I'd tell anyone building their first SDK:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Get the auth headers right on day one.&lt;/strong&gt; Before you build anything else,&lt;br&gt;
build a single request that authenticates correctly. If the API has three&lt;br&gt;
headers, test all three before you write the client. The 401 loop I spent a&lt;br&gt;
day in is the most avoidable part of this entire story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Model the response types conservatively.&lt;/strong&gt; Use &lt;code&gt;@JsonIgnoreProperties(ignoreUnknown = true)&lt;/code&gt; on every response class. Don't guess at field types. Test against a real response before you decide a field is a &lt;code&gt;long&lt;/code&gt; instead of a &lt;code&gt;String&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Be honest about what your tests test.&lt;/strong&gt; MockWebServer is for verifying your own logic, not the remote server. Say that in the README.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Ship it before it's done, and then keep shipping it.&lt;/strong&gt; The five-day gap between my first commit and my last is the realistic version. Nobody builds a working SDK in a weekend. If you tell yourself you have to, you'll never start.&lt;/p&gt;




&lt;h2&gt;
  
  
  The link
&lt;/h2&gt;

&lt;p&gt;The SDK is at&lt;br&gt;
&lt;a href="https://github.com/Agenda-X-PR/alatpay-java-sdk" rel="noopener noreferrer"&gt;github.com/Agenda-X-PR/alatpay-java-sdk&lt;/a&gt;.&lt;br&gt;
It's MIT licensed. It works for transaction listing, single transaction&lt;br&gt;
lookup, and settlements. It doesn't work for bank transfers yet, because ALATPay&lt;br&gt;
hasn't published the endpoint, and I'm not going to pretend I can guess at it.&lt;/p&gt;

&lt;p&gt;If you're a Java developer integrating ALATPay, use it. If it saves you the&lt;br&gt;
401 loop I spent a day in, it's done its job.&lt;/p&gt;

&lt;p&gt;If you're a developer thinking about building something for a company hoping&lt;br&gt;
they'll pay you — read the first section of this post again, and then don't&lt;br&gt;
do that.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>beginners</category>
      <category>devops</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Reconciliation Tool I Almost Didn't Finish</title>
      <dc:creator>Xayen Universe</dc:creator>
      <pubDate>Tue, 03 Nov 2026 18:44:39 +0000</pubDate>
      <link>https://dev.to/xayenuniverse/the-reconciliation-tool-i-almost-didnt-finish-5a5i</link>
      <guid>https://dev.to/xayenuniverse/the-reconciliation-tool-i-almost-didnt-finish-5a5i</guid>
      <description>&lt;p&gt;I want to tell you about the night I decided to build this. It wasn't when I&lt;br&gt;
sat down to write code — it was three hours into an Excel reconciliation for a&lt;br&gt;
client, at 11pm, when I noticed the sum of a column was off by ₦0.02 and spent&lt;br&gt;
45 minutes finding out why.&lt;/p&gt;

&lt;p&gt;The answer: floating-point arithmetic. Excel was storing ₦5,000.00 as&lt;br&gt;
&lt;code&gt;5000.0000000000003&lt;/code&gt; because of the way it handles decimals, and my VLOOKUP was&lt;br&gt;
failing because the two sides of the match had slightly different underlying&lt;br&gt;
values. The ₦0.02 wasn't real money. It was the way Excel represented the&lt;br&gt;
money.&lt;/p&gt;

&lt;p&gt;I closed the spreadsheet, opened a blank Python file, and wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;decimal&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the first line of &lt;code&gt;reconcile&lt;/code&gt;. It's been the most important line I&lt;br&gt;
wrote all week.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first decision I made, and why it stuck
&lt;/h2&gt;

&lt;p&gt;I knew within five minutes of starting that the tool needed &lt;code&gt;Decimal&lt;/code&gt;, not&lt;br&gt;
&lt;code&gt;float&lt;/code&gt;. Not because I'm disciplined about types — I'm not. But because I'd just&lt;br&gt;
spent 45 minutes of my life on a bug that existed only because float arithmetic&lt;br&gt;
rounds in ways a spreadsheet hides.&lt;/p&gt;

&lt;p&gt;Money in &lt;code&gt;float&lt;/code&gt; is a lie. &lt;code&gt;0.1 + 0.2 = 0.30000000000000004&lt;/code&gt; in float math. For&lt;br&gt;
a chart, that doesn't matter. For reconciliation, it's the entire bug class I&lt;br&gt;
was trying to escape. &lt;code&gt;Decimal&lt;/code&gt; doesn't have the problem. &lt;code&gt;Decimal&lt;/code&gt; stores&lt;br&gt;
5000.00 as exactly 5000.00.&lt;/p&gt;

&lt;p&gt;I made that decision in the first five minutes and never reconsidered it. That's&lt;br&gt;
rare for me. Normally I'd write the code with &lt;code&gt;float&lt;/code&gt;, hit the bug, and rewrite.&lt;br&gt;
This time I'd already hit the bug — in Excel, at 11pm — and I knew what the fix&lt;br&gt;
was going to look like.&lt;/p&gt;

&lt;p&gt;The lesson I didn't fully articulate at the time: the first decision sets the&lt;br&gt;
tone for the whole project. Getting the type right on Day 1 meant I never had a&lt;br&gt;
rounding bug on Days 2, 3, 4, or 5. The bug was pre-empted, not debugged.&lt;/p&gt;




&lt;h2&gt;
  
  
  The second decision: three sections, not one number
&lt;/h2&gt;

&lt;p&gt;My first instinct for the report was a single line: "N transactions matched, M&lt;br&gt;
didn't." That would be easier to write. It would also be useless.&lt;/p&gt;

&lt;p&gt;The question I wanted the tool to answer wasn't "did everything match" — it was&lt;br&gt;
"what didn't match, and what should I do about it?" A count of 47 matches&lt;br&gt;
doesn't tell you the three things that need fixing.&lt;/p&gt;

&lt;p&gt;So the report became three sections. &lt;strong&gt;Matched&lt;/strong&gt; so you can verify the good&lt;br&gt;
ones. &lt;strong&gt;Unmatched processor rows&lt;/strong&gt; — the transactions the processor says&lt;br&gt;
succeeded but that aren't in the bank. Those are the ones you chase. &lt;strong&gt;Unmatched&lt;br&gt;
bank rows&lt;/strong&gt; — bank entries with no matching processor transaction. Those are the&lt;br&gt;
ones you investigate. Are they refunds? Chargebacks? A different client paying&lt;br&gt;
into the same account?&lt;/p&gt;

&lt;p&gt;That decision added about 30 lines of output-formatting code on Day 2. Worth&lt;br&gt;
every line.&lt;/p&gt;

&lt;p&gt;The instinct I had to resist was "make it as simple as possible." A&lt;br&gt;
single-number report would have been simpler to build and harder to use. The&lt;br&gt;
simplest thing the &lt;em&gt;user&lt;/em&gt; needs is not the simplest thing the &lt;em&gt;code&lt;/em&gt; can produce.&lt;/p&gt;




&lt;h2&gt;
  
  
  The first thing that went wrong
&lt;/h2&gt;

&lt;p&gt;On Day 2, I wrote the matcher. The exact-match version. The one that compares&lt;br&gt;
&lt;code&gt;reference&lt;/code&gt; column to &lt;code&gt;reference&lt;/code&gt; column, amount column to amount column.&lt;/p&gt;

&lt;p&gt;I ran it on the fixtures and got zero matches. All rows unmatched on both&lt;br&gt;
sides.&lt;/p&gt;

&lt;p&gt;The reason: the processor's reference is &lt;code&gt;ORD-001&lt;/code&gt;. The bank's description is&lt;br&gt;
&lt;code&gt;TRF/ORD-001/PAYSTACK&lt;/code&gt;. Different formats. The column I thought I was comparing&lt;br&gt;
was actually a free-text description that &lt;em&gt;contained&lt;/em&gt; the reference somewhere&lt;br&gt;
inside it.&lt;/p&gt;

&lt;p&gt;This is the kind of bug that doesn't show up in a mental model. I &lt;em&gt;knew&lt;/em&gt;&lt;br&gt;
references wouldn't be exact matches — that's why I'd built in fuzzy matching&lt;br&gt;
for Day 3. But I wrote the exact-match version first anyway, because Day 2's job&lt;br&gt;
was the simplest possible thing. Simplest possible thing, it turned out, matched&lt;br&gt;
zero rows.&lt;/p&gt;

&lt;p&gt;The fix was a regex on Day 3. &lt;code&gt;[A-Z]+-\d+&lt;/code&gt;, extract &lt;code&gt;ORD-001&lt;/code&gt; from&lt;br&gt;
&lt;code&gt;TRF/ORD-001/PAYSTACK&lt;/code&gt;, case-insensitive comparison. Ten lines of code.&lt;/p&gt;

&lt;p&gt;Should this have been on Day 2? Probably. I could have designed the fixture&lt;br&gt;
files to have exact-matching references and saved normalization for Day 3. But&lt;br&gt;
the fixtures I picked reflected what real bank statements actually look like,&lt;br&gt;
and the real problem was always going to need extraction.&lt;/p&gt;

&lt;p&gt;The bug you find on Day 2 is often the bug you should have designed for on Day&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;That's not a failure. That's what the process looks like.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The thing I almost shipped but didn't
&lt;/h2&gt;

&lt;p&gt;There was a moment on Day 3, after the fuzzy matching worked, where I thought:&lt;br&gt;
"Let me add a &lt;code&gt;--verbose&lt;/code&gt; flag to the CLI." The output was one line per match,&lt;br&gt;
and I wanted a flag that showed more detail for each row.&lt;/p&gt;

&lt;p&gt;I sat with it for five minutes and killed it.&lt;/p&gt;

&lt;p&gt;The reason: the output already showed everything. Adding &lt;code&gt;--verbose&lt;/code&gt; would let&lt;br&gt;
the user &lt;em&gt;choose&lt;/em&gt; to see less. But the tool's entire purpose was showing the&lt;br&gt;
full picture. A user who runs &lt;code&gt;reconcile&lt;/code&gt; wants to see the three sections. A&lt;br&gt;
user who wants one line of output wants a different tool.&lt;/p&gt;

&lt;p&gt;This is the difference between "a feature I could add" and "a feature the&lt;br&gt;
product needs." I default to adding. This time I didn't.&lt;/p&gt;

&lt;p&gt;The rule I'd carry forward: if you're adding a flag to hide what's already&lt;br&gt;
there, you're probably building a different tool than the one you set out to&lt;br&gt;
build.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5-day build, honestly
&lt;/h2&gt;

&lt;p&gt;Here's what actually happened, not the polished version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 1 (2 hours):&lt;/strong&gt; models.py, cli.py, print row counts. The CLI reported "4&lt;br&gt;
processor rows, 3 bank rows" and I closed the laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 2 (3 hours):&lt;/strong&gt; matcher.py with exact match, two tests. Got zero matches&lt;br&gt;
first, realized the reference-extraction problem, added a regex that probably&lt;br&gt;
belonged on Day 3. Tests passed eventually. Went to sleep at 1am.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 3 (3 hours):&lt;/strong&gt; fuzzy matching — amount tolerance, date window,&lt;br&gt;
case-insensitive reference — plus 7 more tests. 9 tests total. Felt like the&lt;br&gt;
hard part was done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 4 (1.5 hours):&lt;/strong&gt; README, LICENSE, CI, push. Most of this day was&lt;br&gt;
writing, not coding. Also spent 30 minutes fighting a Python PATH issue when&lt;br&gt;
&lt;code&gt;reconcile.exe&lt;/code&gt; wasn't found. The fix was &lt;code&gt;python -m reconcile&lt;/code&gt; instead of&lt;br&gt;
&lt;code&gt;reconcile&lt;/code&gt;. Six minutes of work, 30 minutes of not realizing it was six&lt;br&gt;
minutes of work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Day 5 (2.5 hours):&lt;/strong&gt; reconcile-demo. FastAPI wrapper, HTML form, Render&lt;br&gt;
deploy. First deploy failed. Second deploy worked. Total: 12 hours, five&lt;br&gt;
calendar days, most of it in 2-hour sessions.&lt;/p&gt;

&lt;p&gt;The honest part: every single day had a "this isn't working" moment. Day 2's&lt;br&gt;
was the zero-match. Day 3's was a test failing because the date math was off by&lt;br&gt;
one day — weekends don't post the same way weekdays do, and I hadn't thought&lt;br&gt;
about that. Day 4's was the PATH issue. Day 5's was &lt;code&gt;ModuleNotFoundError&lt;/code&gt; for&lt;br&gt;
the &lt;code&gt;reconcile&lt;/code&gt; package because the temp files were being read incorrectly.&lt;/p&gt;

&lt;p&gt;Every day, something didn't work on the first try.&lt;/p&gt;

&lt;p&gt;The reason I finished anyway: each of those moments was a specific error message&lt;br&gt;
pointing at a specific line. None of them were "I don't know where to start."&lt;br&gt;
They were all "this one thing is wrong." And each one was fixable in under 20&lt;br&gt;
minutes once I stopped trying variations of the same command and actually read&lt;br&gt;
what the error was saying.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's still not done, and why
&lt;/h2&gt;

&lt;p&gt;Things I deliberately cut because they weren't part of the finish line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No support for Xero or QuickBooks exports.&lt;/strong&gt; Only the two CSV formats in the
README.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CLI flags for tolerances.&lt;/strong&gt; Change &lt;code&gt;FuzzyMatchConfig&lt;/code&gt; in code if you need
different tolerances.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No JSON output.&lt;/strong&gt; Text only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No CI test for the &lt;code&gt;pip install&lt;/code&gt;ed command.&lt;/strong&gt; Works locally; not tested on
CI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No partial refunds, chargebacks, or multi-currency settlement.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one is in the README's "Limitations" section. The point of that section&lt;br&gt;
isn't to apologize — it's to make the boundary visible. Anyone using the tool&lt;br&gt;
knows exactly what it does and doesn't do.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;One thing. I'd write the fixture files &lt;em&gt;before&lt;/em&gt; the matcher.&lt;/p&gt;

&lt;p&gt;I wrote &lt;code&gt;models.py&lt;/code&gt; first, then the matcher, then the fixtures. Then on Day 2 I&lt;br&gt;
discovered the fixtures needed a specific format the matcher didn't expect. If&lt;br&gt;
the fixtures had come first, they'd have shown me the reference-extraction&lt;br&gt;
problem before I wrote code that assumed it away. The matcher would have been&lt;br&gt;
written correctly on the first try.&lt;/p&gt;

&lt;p&gt;The rule I'll carry forward: write the test data before the code that uses it.&lt;br&gt;
The data tells you what the code needs to handle, and it's cheaper to change the&lt;br&gt;
data than to change the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  If you're doing this in Excel right now
&lt;/h2&gt;

&lt;p&gt;Everything above is about a specific 12-hour project. The part that matters to&lt;br&gt;
you is whether Excel is the right tool for your situation.&lt;/p&gt;

&lt;p&gt;The honest answer: &lt;strong&gt;for fewer than 20 transactions a month with fewer than 3&lt;br&gt;
clients, Excel is fine.&lt;/strong&gt; The manual process takes 20 minutes and the&lt;br&gt;
discrepancies are rare enough that a tool is not the bottleneck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For 20+ transactions a month with 3+ clients, no bookkeeper, and no&lt;br&gt;
spreadsheet discipline — Excel is losing money for you.&lt;/strong&gt; Not visibly. Not in a&lt;br&gt;
way you can point to a line and say "that's what Excel cost me." But every&lt;br&gt;
₦15,000 missed payment that goes uncaught is real, and the same missed payment&lt;br&gt;
every month for a year is ₦180,000 that went to a bank's reconciliation queue and&lt;br&gt;
never came back.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://reconcile-demo.onrender.com/" rel="noopener noreferrer"&gt;try the tool in the browser&lt;/a&gt;&lt;br&gt;
without installing anything. Upload any two CSVs and see what it does. If it&lt;br&gt;
doesn't help, you've lost nothing. If it does, the source is at&lt;br&gt;
&lt;a href="https://github.com/Agenda-X-PR/reconcile" rel="noopener noreferrer"&gt;github.com/Agenda-X-PR/reconcile&lt;/a&gt;&lt;br&gt;
and you can run it locally.&lt;/p&gt;

&lt;p&gt;Either way, you now know why your Excel is lying to you.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>python</category>
      <category>opensource</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
