<?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: Ramsey Opoku Gyimah</title>
    <description>The latest articles on DEV Community by Ramsey Opoku Gyimah (@aimlin9).</description>
    <link>https://dev.to/aimlin9</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%2F3870792%2Fcf9fdcf6-1718-44f9-bf94-209e241bf9e9.jpg</url>
      <title>DEV Community: Ramsey Opoku Gyimah</title>
      <link>https://dev.to/aimlin9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aimlin9"/>
    <language>en</language>
    <item>
      <title>I Built a Tool That Reads My GCB Bank Statement and Tells Me Where My Money Goes</title>
      <dc:creator>Ramsey Opoku Gyimah</dc:creator>
      <pubDate>Sun, 12 Apr 2026 14:59:05 +0000</pubDate>
      <link>https://dev.to/aimlin9/i-built-a-tool-that-reads-my-gcb-bank-statement-and-tells-me-where-my-money-goes-2nc4</link>
      <guid>https://dev.to/aimlin9/i-built-a-tool-that-reads-my-gcb-bank-statement-and-tells-me-where-my-money-goes-2nc4</guid>
      <description>&lt;p&gt;[FinTrack Ghana cover(&lt;a href="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu0jme7eolvyabtbxz83.png" rel="noopener noreferrer"&gt;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu0jme7eolvyabtbxz83.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Let me paint a picture.&lt;/p&gt;

&lt;p&gt;It's the end of the month. You open your GCB banking app. You see a list of transactions — numbers, dates, weird reference codes. You scroll and scroll. You &lt;em&gt;know&lt;/em&gt; you spent too much somewhere, but where? On what? The app doesn't tell you. It just shows you the damage.&lt;/p&gt;

&lt;p&gt;This is the reality for most people in Ghana. Our bank apps are basically digital receipts. No categories. No charts. No "hey, you spent 40% more on food this month." Nothing.&lt;/p&gt;

&lt;p&gt;I got tired of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I decided to build something
&lt;/h2&gt;

&lt;p&gt;I'm a CS student in Accra, and I've been teaching myself Django. I wanted a portfolio project that wasn't just another to-do app or blog — something that solves a problem I actually have. So I asked myself: &lt;em&gt;what if I could upload my bank statement and have a system break down my spending automatically?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's how &lt;strong&gt;FinTrack Ghana&lt;/strong&gt; was born.&lt;/p&gt;

&lt;p&gt;You upload a GCB statement (PDF or CSV), and within seconds the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extracts every transaction&lt;/li&gt;
&lt;li&gt;Figures out what each one is (food? transport? utilities?)&lt;/li&gt;
&lt;li&gt;Shows you pie charts and spending breakdowns&lt;/li&gt;
&lt;li&gt;Asks Google Gemini to give you actual financial advice based on YOUR numbers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not generic advice. Advice like: &lt;em&gt;"Your essentials — food, transport, utilities — are well managed at GHS 90 total. Consider automating GHS 100 into savings each payday."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part nobody warns you about
&lt;/h2&gt;

&lt;p&gt;Parsing bank statements sounds simple until you actually try it.&lt;/p&gt;

&lt;p&gt;GCB gives you a PDF with a table. Sounds easy, right? Just read the table. Except PDF tables aren't really tables — they're text positioned to &lt;em&gt;look&lt;/em&gt; like tables. Every bank formats theirs differently. GCB has columns for Date, Debit, Credit, Balance, and Remarks. Ecobank uses completely different headers. MTN MoMo is its own thing entirely.&lt;/p&gt;

&lt;p&gt;I ended up building a &lt;strong&gt;bank detector&lt;/strong&gt; — a piece of code that reads the filename and the first few lines of the file to figure out which bank it's from:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankDetector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;BANK_SIGNATURES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gcb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ghana commercial bank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ecobank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ecobank&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mtn_momo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mtn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mobile money&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;momo&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_preview&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;content_preview&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bank&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BANK_SIGNATURES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;combined&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keywords&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bank&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple? Yes. But it works. And sometimes simple is exactly what you need.&lt;/p&gt;

&lt;p&gt;For the actual parsing, I used &lt;code&gt;pdfplumber&lt;/code&gt; for PDFs and Python's built-in &lt;code&gt;csv&lt;/code&gt; module for CSV files. The key insight was handling the Debit/Credit split — GCB uses separate columns, so if Debit has a value, money went out. If Credit has a value, money came in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making sense of "ECG PREPAID PURCHASE Ref 078445566778"
&lt;/h2&gt;

&lt;p&gt;Once you have the transactions, you need to categorize them. Nobody wants to see raw bank descriptions. They want to know: &lt;em&gt;was this food? transport? a bill?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built a two-layer system:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First layer: keywords.&lt;/strong&gt; If the description contains "kfc" or "shoprite", it's food. If it says "uber" or "goil", it's transport. This catches about 80% of transactions instantly with 100% accuracy.&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="n"&gt;RULES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;food&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;shoprite&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;kfc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;papaye&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chop bar&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;restaurant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;transport&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uber&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bolt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trotro&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;goil&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fuel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utilities&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ecg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ghana water&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mtn bill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;vodafone&lt;/span&gt;&lt;span class="sh"&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;p&gt;&lt;strong&gt;Second layer: spaCy NLP.&lt;/strong&gt; For transactions that keywords don't catch — like "SUSU COLLECTOR PAYMENT" — I trained a text classifier on 190 labeled examples. All with Ghanaian context. Makola Market purchases. Trotro fares. ECG prepaid top-ups.&lt;/p&gt;

&lt;p&gt;The result? The system correctly categorizes about 90% of transactions automatically. The rest get labeled as "other", and users can manually correct them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that made me smile
&lt;/h2&gt;

&lt;p&gt;When I plugged in Google Gemini and fed it a real month's spending data, the response genuinely surprised me. It opened with "Akwaaba!" — the Twi word for welcome — and then gave specific, thoughtful advice based on my actual numbers.&lt;/p&gt;

&lt;p&gt;Not "save more money." Not "reduce your spending." Actual advice like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your essential expenses for food (GHS 30), transport (GHS 15), and utilities (GHS 45) are impressively low. Consider automating a transfer of GHS 100-150 into a dedicated savings account each payday.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's when I knew this project was worth building.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's under the hood
&lt;/h2&gt;

&lt;p&gt;For the curious, here's what powers everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Python + Django + Django REST Framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth:&lt;/strong&gt; JWT tokens (register, login, password reset)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File parsing:&lt;/strong&gt; pdfplumber for PDFs, csv module for CSVs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NLP:&lt;/strong&gt; spaCy text classifier + keyword matching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI insights:&lt;/strong&gt; Google Gemini 2.5 Flash&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; SQLite for dev, PostgreSQL for production&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests:&lt;/strong&gt; 29 unit tests covering parsers and categorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The whole thing is API-first. I built and tested every endpoint with curl before writing a single line of frontend code. That approach saved me a lot of headaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned (the honest version)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PDF parsing will humble you.&lt;/strong&gt; I thought it'd take a day. It took much longer. Every bank structures their PDFs differently, and "extracting a table" from a PDF is way harder than it sounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with the dumb solution.&lt;/strong&gt; My keyword rules are literally just string matching. No fancy algorithms. But they handle 80% of the work. I only needed spaCy for the edge cases. Don't over-engineer early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build for a niche nobody's serving.&lt;/strong&gt; There are thousands of finance dashboards on GitHub. But how many of them can read a GCB statement? Or know what "trotro" means? That regional specificity is what makes this project different from everything else out there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test with real data early.&lt;/strong&gt; I used my actual bank statement to test the parser. Seeing my own transactions appear — correctly parsed, correctly categorized — was the most satisfying moment in the whole project.&lt;/p&gt;

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

&lt;p&gt;I'm currently building the React frontend — charts, drag-and-drop upload, budget tracking with progress bars. After that, deployment to Railway and Vercel.&lt;/p&gt;

&lt;p&gt;The full code is open source: &lt;strong&gt;&lt;a href="https://github.com/aimlin9/finance-dashboard" rel="noopener noreferrer"&gt;github.com/aimlin9/finance-dashboard&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're building something for your region — whether it's Ghanaian banks, Nigerian fintech, Kenyan mobile money, or anything else — lean into that specificity. It's not a limitation. It's your edge.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Ramsey, a CS student in Accra building portfolio projects and looking for remote internship opportunities. Find me on &lt;a href="https://github.com/aimlin9" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
