Tag: cemac

  • OCR for supplier invoices in CEMAC: what works and what does not

    OCR for supplier invoices is one of the most-requested modules by Cameroonian SMEs switching to SynkriaOps. The need is clear: reduce manual entry from 3-4 minutes per invoice to 30 seconds of visual validation.

    But generalist “Anglo-Saxon” OCR performs poorly on Cameroonian invoices. Here is what we learned to calibrate.

    The photo quality problem

    First field observation: 80% of invoices are photographed with a smartphone, not scanned. Consequences:

    • Inconsistent lighting — often near a window, frequent backlight.
    • Tilt — invoice rarely flat, sometimes hand-held.
    • Reflections — on the glossy paper of hotel chains for example.
    • Folds — especially on delivery slips collected in batch.

    The OCR must perform perspective correction + denoising + deskew preprocessing before attempting text extraction. Without this, the error rate on numbers explodes.

    The NIU format problem

    The NIU (Unique Identification Number) is the Cameroonian equivalent of the French SIRET: 13 alphanumeric characters (e.g., M101212345678A). It identifies a taxpayer and must appear on every invoice.

    A generalist OCR looks for an EU VAT (FR12345678901), a US EIN (12-3456789), or a French SIRET (123 456 789 00012). None of these patterns matches a Cameroonian NIU.

    SynkriaOps uses a dedicated regex and a plausibility score (the first letter is generally M or P depending on status). Without this explicit extraction, tax audits become painful (the NIU is mandatory in the “supplier identity” field of an accounting entry).

    The VAT at 19.25% problem

    Cameroonian VAT stands at 19.25% (since 2005). Many OCRs pre-trained on French or German content calibrate their heuristics on 20% or 19%, and reject a VAT line at 19.25 as “probably not a VAT amount”.

    Required adjustment: widen the VAT plausibility window to common CEMAC rates. Table:

    CountryStandard rateReduced rates
    Cameroon19.25%0% (exemptions)
    Gabon18%5% and 10%
    Chad18%
    Congo18.9%5% and 8%
    Central African Republic19%5%
    Equatorial Guinea15%

    Without this table, OCR “smooths” the VAT and loses precision on rounding (see next pitfall).

    The bilingual labels problem

    In Cameroon, many invoices are in primarily French but with English mentions (“Invoice”, “Tax”, “Subtotal”) or pidgin on wholesale delivery slips. The OCR must recognize all of:

    • Montant HTSubtotalNet amount
    • Taxe sur la valeur ajoutéeVATSales tax
    • Total TTCGrand totalAmount due

    Typical error: taking Subtotal for the gross amount and Total for the net amount (full meaning inversion in some US-imported formats).

    What SynkriaOps does — and does NOT do

    What SynkriaOps OCR does:

    • Perspective + denoising + deskew preprocessing (pdfjs-dist + OpenCV)
    • Text extraction per block with coordinates
    • Dedicated patterns: NIU CM/GA/CG, VAT 19.25/18/15, XAF currency
    • Confidence score per field
    • Mandatory human validation before accounting entry (no auto-validation)

    What it does NOT:

    • Auto-validation of invoices > 1 million XAF (configurable threshold)
    • Proprietary learning on each tenant’s data (privacy by design — we do not aggregate data across SMEs)
    • Advanced fraud detection (forged signature, fake NIU) — that is the human accountant’s role

    Field measurement

    On 600 supplier invoices from the Douala pilot SME (see community testimonial), net extraction rate is 100% (every expected field is extracted), with human validation maintained at 100% during the first 6 months — this is a deliberate discipline, not a technical limit.

    Effective human corrections represent 1.8% of fields over the period — typically a debit/credit inverted on a poorly-calibrated credit note or a mis-weighted rounding line.


    Technical details: apps/api/src/modules/ocr/. The service uses @anthropic-ai/sdk 0.78.0 for the final LLM layer (structured extraction), backed by in-house image preprocessing. The full pattern is documented with E2E tests test/ocr-realtime.e2e-spec.ts and test/pdf-ocr-import.e2e-spec.ts.

  • Booking Mobile Money flows in CEMAC: MTN, Orange, 581 transit sub-accounts

    In Cameroon, Mobile Money now weighs heavily in an SME’s cash flow. Many businesses receive and disburse daily via MTN MoMo or Orange Money — yet the booking remains artisanal: flows often land in bulk in account 512 (bank) or worse, are recorded as cash 53.

    Here is how to properly structure your MoMo accounts in a SYSCOHADA chart.

    Why it is NOT a bank account

    Natural temptation: create a 512### sub-account “Mobile Money MTN” and dump movements into it. Wrong.

    The 512 account designates a bank current account in the strict sense: account holder = the company, opened at a regulated banking institution, monthly statement, IBAN. Your MoMo wallet is none of that.

    The MoMo wallet is an electronic money deposit held by a payment institution (MTN MFS Africa, Orange Money), not a bank. From an accounting standpoint, it is an internal transfer pending or liquidity transit account, closer to 581 or 580.

    The proposed pattern

    Here is the structure we apply in the SMEs we support:

    581001 — Internal transfers — MTN MoMo CM (XAF)
    581002 — Internal transfers — Orange Money CM (XAF)
    
    For each operator, as a sub-account:
    58100110 — MoMo MTN — Main merchant wallet (number 67xxxxxxx)
    58100120 — MoMo MTN — Secondary merchant wallet if needed

    Account 581 is a transit account on the liability side of the SYSCOHADA chart. It records flows awaiting reclassification.

    The typical accounting flow

    Customer payment via MoMo

    1. Customer pays 50,000 XAF via MTN MoMo to the merchant wallet.
    2. Webhook notification received (cf. apps/api/src/modules/mobile-money/).
    3. Automatic entry:
      • Debit 581001 (MoMo wallet) — 50,000
      • Credit 411xxxxxx (Customer) — 50,000
    4. The wallet is not the bank. The balance is latent.

    Transfer MoMo → bank

    When the SME transfers the wallet balance to its BICEC or Afriland account:

    1. MTN fees applied (typically 1% of transfer).
    2. Entry:
      • Debit 512100 (BICEC) — 49,500
      • Debit 627100 (Banking services) — 500
      • Credit 581001 (MoMo wallet) — 50,000
    3. Account 581001 is now at zero.

    Pitfalls to avoid

    Pitfall 1 — No automatic reconciliation

    If the SME has 80 MoMo receipts per month and no automatic reconciliation, each MoMo→bank transfer becomes a puzzle: which of the 80 transactions corresponds to this 247,500 XAF payment?

    Solution: import the MoMo wallet statement (or direct webhook integration)

    • reconcile 581 at the operation level.

    Pitfall 2 — Confusing internal transfers with customer flows

    If a salesperson receives a personal payment on their Pro MoMo and then transfers it to the company, it is not a direct customer receipt — it is two distinct operations: (a) salesperson advance, (b) company reimbursement. To be booked separately, otherwise the real customer’s 411 ledger is wrong.

    Pitfall 3 — MoMo fees booked under 512

    Mobile Money operator fees are not bank fees in the classical sense. Account 6271 (Bank fees) is usable but unclear. Prefer a dedicated analytics tag (“Mobile payment services”) to rationally arbitrate operator vs bank cost.

    Webhook and integrity

    SynkriaOps receives MoMo notifications via HMAC-SHA256 webhook + AES-256-GCM secret encryption (cf. apps/api/src/modules/mobile-money/services/webhook-signature.service.ts). The sender IP is filtered by CIDR allowlist to prevent external replays.

    The automatic 581↔411 reconciliation triggers on receipt of an authenticated notification — the entry is booked without human intervention but traceable (audit log + chained hash).


    Updated with the actual SynkriaOps code state as of 2026-05-08. On the collection side, MTN CM and Orange CM are currently being brought into service: the secure infrastructure is built, the connection with the operators is not yet active. Flow booking (581 sub-accounts) and statement import, however, are operational.