[{"data":1,"prerenderedAt":772},["ShallowReactive",2],{"work-index":3},[4,119,212,292,378,475,546,612,679],{"id":5,"title":6,"body":7,"client":96,"confidential":97,"description":88,"extension":98,"featured":97,"meta":99,"navigation":100,"order":101,"path":102,"period":103,"secondary":104,"seo":105,"sitemap":106,"slug":107,"stack":108,"stem":115,"summary":116,"track":117,"__hash__":118},"work/work/remittance-rebuild.md","Rebuilding a remittance platform, then getting it licensed",{"type":8,"value":9,"toc":87},"minimark",[10,15,19,23,26,29,33,52,56,69,73],[11,12,14],"h2",{"id":13},"context","Context",[16,17,18],"p",{},"Fast Pace Transfer is a remittance company. Money comes in from senders abroad and goes out over mobile money and bank rails in Ghana. I joined as Lead Developer in 2020, when the product suite and its API services were already in production and the transaction success rate was below where a payments business can live.",[11,20,22],{"id":21},"the-problem","The problem",[16,24,25],{},"A transfer is a sequence of calls to systems I did not control. A mobile money provider, a bank rail, a compliance check. Any one of them could time out, and a timeout tells you nothing about whether the money moved. Retry, and you risk paying twice. Give up, and you risk paying nobody while the customer's balance says otherwise.",[16,27,28],{},"The codebase treated a transfer as one request that either worked or did not. Real transfers are neither.",[11,30,32],{"id":31},"what-i-did","What I did",[34,35,36,40,43,46,49],"ul",{},[37,38,39],"li",{},"Rebuilt the product suite and API services around idempotency keys. Every money-moving request carries a key. A replay returns the original result instead of a second payout, which turns retries from a risk into the default recovery strategy.",[37,41,42],{},"Modelled each transfer as a saga: an ordered set of steps, each with a compensating action. A provider timeout mid-transfer now ends in a known state with a known next move, instead of a support ticket.",[37,44,45],{},"Introduced Agile delivery, TDD and production monitoring, in that order of resistance. Monitoring was the one that changed the conversation, because the team saw failures before customers reported them.",[37,47,48],{},"Led ISO 27001 certification from inside the engineering team. The controls, the evidence, and the engineering practices the auditor needed to see were built into how we shipped, rather than bolted on before the audit.",[37,50,51],{},"Led the engineering side of Central Bank of Ghana licensing.",[11,53,55],{"id":54},"what-it-cost","What it cost",[34,57,58,61,64,67],{},[37,59,60],{},"Idempotency is a storage and discipline cost. Keys live somewhere, expire at some point, and every caller has to send them. Clients that did not were the long tail of the migration.",[37,62,63],{},"Sagas trade atomicity for explicitness. Every step needs a compensation, every compensation needs a test, and a compensation that fails is its own class of incident.",[37,65,66],{},"Certification pulls engineers into evidence and documentation work that does not ship features.",[37,68],{},[11,70,72],{"id":71},"outcome","Outcome",[34,74,75,78,81,84],{},[37,76,77],{},"Transaction success rate pushed past 95%.",[37,79,80],{},"Team velocity up 50% and production errors down 40% after the Agile, TDD and monitoring rollout.",[37,82,83],{},"ISO 27001 certification achieved.",[37,85,86],{},"Central Bank of Ghana licence achieved.",{"title":88,"searchDepth":89,"depth":89,"links":90},"",2,[91,92,93,94,95],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"Fast Pace Transfer",false,"md",{},true,1,"/work/remittance-rebuild","2020 to 2023","advisory",{"title":6,"description":88},{"loc":102},"remittance-rebuild",[109,110,111,112,113,114],"PHP (Laravel)","Java","Azure","Docker","Redis","MySQL","work/remittance-rebuild","Idempotency keys and saga orchestration across mobile money and bank rails, then ISO 27001 and a central bank licence led from inside the engineering team.","engineering","XNlLMxQA1cVRGu2MY_P0o9QCbcJI1ooNy1n-W6CZ7pE",{"id":120,"title":121,"body":122,"client":194,"confidential":97,"description":88,"extension":98,"featured":100,"meta":195,"navigation":100,"order":89,"path":196,"period":197,"secondary":198,"seo":199,"sitemap":200,"slug":201,"stack":202,"stem":208,"summary":209,"track":210,"__hash__":211},"work/work/scaling-to-thirty.md","What changes between eight engineers and thirty",{"type":8,"value":123,"toc":187},[124,126,129,131,134,137,139,161,163,178,180],[11,125,14],{"id":13},[16,127,128],{},"Turntabl is an engineering consultancy in Accra. Some engineers are embedded inside client teams, including at tier-1 investment banks. Others form separate teams building end-to-end products for other clients. I joined in 2023 as Engineering Team Lead. In July 2026 I became Senior Engineering Manager, with 30+ engineers directly and indirectly across multiple concurrent clients.",[11,130,22],{"id":21},[16,132,133],{},"Eight engineers is one team. One standup, one client cadence, one backlog I could hold in my head, and every pull request could cross my desk if I wanted it to.",[16,135,136],{},"Thirty engineers across several clients is a different job with the same title on the door. Each client sets its own rhythm, its own tooling and its own definition of done. The habits that made me useful at eight, being in every review and every design conversation, are exactly the ones that make me the bottleneck at thirty.",[11,138,32],{"id":31},[34,140,141,144,153,156,159],{},[37,142,143],{},"Wrote things down instead of saying them. Promotion criteria, decision records, delivery plans, kickoff briefings. A conversation scales to the people in the room. A document scales to the people who join next quarter.",[37,145,146,147,152],{},"Authored a ",[148,149,151],"a",{"href":150},"/work/engineering-ladder","five-level career progression framework"," so that promotion stopped depending on which engagement someone happened to be on and who happened to notice.",[37,154,155],{},"Moved quality from my review queue into structure: TDD as a team default, decision records so the reasoning survives the meeting, reviewer-attestation separation of duties where governance required it, and an on-call rotation on the platform team so production ownership was a schedule rather than a personality.",[37,157,158],{},"Chaired the engineering culture committee, so that engineers on different clients still had reasons to be in the same room. Hackathons, demo days, meetups, and the scheduling that makes them happen.",[37,160],{},[11,162,55],{"id":54},[34,164,165,168,176],{},[37,166,167],{},"Most of the job turned out to be written artefacts rather than conversations. I write far more than I code.",[37,169,170,171,175],{},"Distance from the code is real. The honest answer to whether I still build is in the ",[148,172,174],{"href":173},"/work/versioned-data-analytics-replica","analytics replica study",", which I am accountable for and could not have written from the sidelines.",[37,177],{},[11,179,72],{"id":71},[16,181,182,183,186],{},"No single number describes this one. 30+ engineers, directly and indirectly, across multiple concurrent clients. The promotion framework, the decision records and the culture committee are still running. Velocity and defect figures for a single team are in the ",[148,184,185],{"href":102},"remittance study","; the equivalent for a team of teams is  harder to attribute and I would rather leave it blank than reach.",{"title":88,"searchDepth":89,"depth":89,"links":188},[189,190,191,192,193],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"Turntabl",{},"/work/scaling-to-thirty","2023 to present",null,{"title":121,"description":88},{"loc":196},"scaling-to-thirty",[203,204,205,206,207],"Org design","Hiring","Promotion frameworks","ADRs","On-call rotation","work/scaling-to-thirty","What stopped working past a single team, what had to be written down, and how quality holds when I cannot review everything.","leadership","qoUVlf9xgVQcETlbbk6eXQlcc6fG-lZ7zm4NGo0IN5s",{"id":213,"title":214,"body":215,"client":276,"confidential":97,"description":88,"extension":98,"featured":100,"meta":277,"navigation":100,"order":278,"path":279,"period":197,"secondary":210,"seo":280,"sitemap":281,"slug":282,"stack":283,"stem":289,"summary":290,"track":104,"__hash__":291},"work/work/scoping-fixed-price-work.md","Estimating engineering work you then have to deliver",{"type":8,"value":216,"toc":269},[217,219,222,225,227,230,232,249,251,264,266],[11,218,14],{"id":13},[16,220,221],{},"Part of my remit is engineering sales. I scope, price and bid for engineering work, and then I lead the team that delivers it. That second clause is the whole point. The estimate is written by the person who has to live with it.",[16,223,224],{},"This study describes the method. It does not describe any deal. Rates, contract values, budgets and client-attributable hour counts are not mine to publish, and nothing below depends on them.",[11,226,22],{"id":21},[16,228,229],{},"A fixed price on a regulated engagement is a promise about the future made with incomplete information. There are two easy ways to break it. Quote low and overrun quietly, which costs the delivery team. Pad the number until the bid is safe, which costs the bid. A proposal the client can hold you to, and that you can hold yourself to, needs structure underneath the number.",[11,231,32],{"id":31},[34,233,234,237,240,243,246],{},[37,235,236],{},"Work breakdown structures down to per-role hours. Every line carries a role and an hour count in a ledger, so the price is a sum of things someone can argue with individually.",[37,238,239],{},"Requirement compliance matrices. Every requirement identifier gets one of three classifications: comply, comply with clarification, or do not comply. One engagement ran to 237 identifiers. The third column matters because it exists. Saying no to a line before signing is cheaper than discovering it after.",[37,241,242],{},"Scoping bands with stop-and-re-estimate thresholds. Each package carries a band. If effort crosses it, delivery pauses and the estimate is redone with the client in the room. The bid degrades honestly instead of silently.",[37,244,245],{},"An engineering delivery plan, a statement of work and a kickoff briefing produced alongside the commercial proposal, so the team that starts on day one starts from the same document the client signed.",[37,247,248],{},"Milestone-gated payment structures on the commercial side, and reviewer-attestation separation of duties on the delivery side, so the person who signs off on a piece of work is a different person from the one who built it.",[11,250,55],{"id":54},[34,252,253,256,259,262],{},[37,254,255],{},"Time before revenue. A 237-line compliance matrix is days of work with no guarantee of a win.",[37,257,258],{},"The stop-and-re-estimate threshold is uncomfortable to trigger, and it only works if it is written into the contract. A threshold that lives in a spreadsheet is a wish.",[37,260,261],{},"Bid-to-delivery continuity means I cannot blame the estimate. That is the discipline and it is also the exposure.",[37,263],{},[11,265,72],{"id":71},[16,267,268],{},"The practice is the outcome: bids that turn into deliverable plans, teams that start from the document the client signed, and estimates that are answerable because the person who wrote them is the person delivering. There is no figure in this study, by design.",{"title":88,"searchDepth":89,"depth":89,"links":270},[271,272,273,274,275],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"No single engagement",{},3,"/work/scoping-fixed-price-work",{"title":214,"description":88},{"loc":279},"scoping-fixed-price-work",[284,285,286,287,288],"Work breakdown structures","Hour ledgers","Compliance matrices","Statements of work","Milestone gating","work/scoping-fixed-price-work","Work breakdown to per-role hours, requirement compliance matrices, and banded thresholds that stop a bid and re-estimate instead of silently overrunning.","-Fi02Di81Rul6rkXGAsShz6gZBk7l8TUDXFvAwBj9kI",{"id":293,"title":294,"body":295,"client":362,"confidential":100,"description":88,"extension":98,"featured":97,"meta":363,"navigation":100,"order":364,"path":173,"period":365,"secondary":210,"seo":366,"sitemap":367,"slug":368,"stack":369,"stem":375,"summary":376,"track":117,"__hash__":377},"work/work/versioned-data-analytics-replica.md","Making a versioned datastore queryable without losing its history",{"type":8,"value":296,"toc":355},[297,299,302,305,307,310,313,316,318,335,337,347,349,352],[11,298,14],{"id":13},[16,300,301],{},"A platform I lead an engineering team on stores versioned records: not just the current state of something, but every state it has held and every correction since. That history is the point of the system. Anything downstream that loses it is worse than useless, because it looks authoritative and is quietly wrong.",[16,303,304],{},"This study is the analytics workstream, which I am accountable for. The client is current, so it is not named, and neither is the platform.",[11,306,22],{"id":21},[16,308,309],{},"Versioned stores answer two questions at once: what was true about a record at a point in time, and what did we believe about it as of another point in time. Keeping those separate is what makes the data defensible later.",[16,311,312],{},"Queries against the primary store were fast along the paths that had been indexed and impractical everywhere else. People wanted to query on any property without first asking someone to add an index for it.",[16,314,315],{},"That means an analytical replica on a columnar backend. A columnar backend has no cheap UPDATE. The versioning model closes an interval every time a new version of a record arrives, which is an UPDATE by another name. Those two facts had to be reconciled without losing the semantics that make the history trustworthy.",[11,317,32],{"id":31},[34,319,320,323,326,329,332],{},[37,321,322],{},"Modelled the replica as append-only record and closure tables. A new version is an insert. Nothing on the write path updates a row.",[37,324,325],{},"Derived interval closure at read time with an anti-join and a window function instead of maintaining it inline. The close of one interval is the open of the next, and the query works that out rather than the writer.",[37,327,328],{},"Preserved both time dimensions end to end, held record linkage, and kept stable identity across the migration, so a reference in the source is the same reference in the replica. Nested structures are serialised rather than flattened.",[37,330,331],{},"Phased the delivery: historical backfill first, then continuous streamed replication with catch-up after gaps.",[37,333,334],{},"Delivered as merged pull requests with automated test coverage, inside the client's ecosystem and on the client's tooling.",[11,336,55],{"id":54},[34,338,339,342,345],{},[37,340,341],{},"Deferred materialisation. Deriving closure at query time spends at read what it saves at write. The trade is deliberate and it is not free.",[37,343,344],{},"An asynchronous replica is behind its source by definition. Freshness became an explicit opt-in, so callers choose the trade-off knowingly rather than discovering it during a reconciliation.",[37,346],{},[11,348,72],{"id":71},[16,350,351],{},"The replica answers queries on any property without an index request standing in the way, and the history survives the trip.",[16,353,354],{},"No figures here, and no product, platform or client names. They belong to the client, and none of them are what makes the approach worth reading about.",{"title":88,"searchDepth":89,"depth":89,"links":356},[357,358,359,360,361],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"Anonymised, current engagement",{},4,"Current",{"title":294,"description":88},{"loc":173},"versioned-data-analytics-replica",[370,371,372,373,374],"Event streaming","Columnar warehouse","Document store","Kubernetes","Data modelling","work/versioned-data-analytics-replica","An append-only analytical replica that keeps every version of a record intact on a backend with no cheap UPDATE, from backfill to live replication.","_TlGpJL_SZ26tfKE5TF0NPCYX-ffbbaY9g6-Ofh6x7U",{"id":379,"title":380,"body":381,"client":362,"confidential":100,"description":88,"extension":98,"featured":100,"meta":458,"navigation":100,"order":459,"path":460,"period":365,"secondary":104,"seo":461,"sitemap":462,"slug":463,"stack":464,"stem":472,"summary":473,"track":117,"__hash__":474},"work/work/provider-agnostic-wallet-layer.md","Making three custody providers interchangeable behind one wallet interface",{"type":8,"value":382,"toc":451},[383,385,388,391,394,396,399,402,405,408,410,430,432,443,445,448],[11,384,14],{"id":13},[16,386,387],{},"The platform bridges DeFi, CeFi and traditional finance under a regulated wrapper. We deliver backend, platform and security work into it as an engineering vendor, under fixed-price engagements with milestone-gated payment, separation of duties, and acceptance tied to merged pull requests.",[16,389,390],{},"I lead the engagement portfolio. I scope and price the work, then I am accountable for the team that delivers it.",[16,392,393],{},"This study is the wallet and custody workstream.",[11,395,22],{"id":21},[16,397,398],{},"Custody is the part of a crypto platform you cannot get wrong twice.",[16,400,401],{},"The platform needed more than one custody provider: for resilience, for commercial leverage, and because different asset and jurisdiction combinations are served well by different providers.",[16,403,404],{},"Those providers agree on almost nothing that matters. One splits key material three ways and requires all three shares to sign. Another brokers signing through a co-signer service carrying its own activation state. A third exposes a different key hierarchy and fails in a different way. Trust boundaries, key ceremonies, error semantics and operational assumptions all differ.",[16,406,407],{},"Writing product code against any one of them means writing product code against all of them, or rewriting it the day a provider is replaced. The wallet layer had to make them interchangeable without pretending they were the same thing.",[11,409,32],{"id":31},[34,411,412,415,418,421,424,427],{},[37,413,414],{},"Designed the provider-agnostic base layer first, before any adapter existed. The abstraction was specified across a numbered work breakdown, so the boundary was argued on paper before it was argued in code.",[37,416,417],{},"Built adapters for three custody providers against that boundary. Each adapter owns its provider's key ceremony, trust boundary and failure semantics. Nothing above the boundary knows which provider is underneath.",[37,419,420],{},"Held a 3-of-3 MPC custody boundary where the provider's model required it, with RSA-AES hardening on the transport of key material.",[37,422,423],{},"Produced non-activation evidence for a co-signer service, because a regulated audit treats \"this component is not enabled\" as a claim requiring proof.",[37,425,426],{},"Scoped each adapter as its own fixed-price package with its own work breakdown and merge-request packages, so a provider could be added, deferred or dropped without reopening the whole engagement.",[37,428,429],{},"Delivered under separation of duties. No engineer approved their own work, and a senior independent reviewer attested each package.",[11,431,55],{"id":54},[34,433,434,437,440],{},[37,435,436],{},"The abstraction is honest about what it cannot hide. Custody models differ in ways that leak: activation state, signing latency, and what \"the key is unavailable\" actually means. Those surface at the boundary instead of being smoothed over. The interface is less elegant for it and the system is more truthful.",[37,438,439],{},"Building the base layer first delayed the first working adapter. Fixed-price delivery punishes that ordering, because the client sees no wallet at all during the opening package. It was still the right call, and it is why the second and third adapters cost a fraction of the first.",[37,441,442],{},"Each package carried an explicit out-of-scope list, including providers named as excluded and delivered later under a separate package. Naming a gap costs a conversation. Half-integrating a custody provider costs considerably more.",[11,444,72],{"id":71},[16,446,447],{},"Three custody providers sit behind one interface, and product code has never had to know which. Adding a fourth means writing one more adapter.",[16,449,450],{},"No figures here. Hours, rates and finding counts belong to the client, and the platform is current, so it is not named.",{"title":88,"searchDepth":89,"depth":89,"links":452},[453,454,455,456,457],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},{},5,"/work/provider-agnostic-wallet-layer",{"title":380,"description":88},{"loc":460},"provider-agnostic-wallet-layer",[465,466,467,112,468,469,470,471],"Go","Kotlin","Python","MPC custody","OIDC/PKCE","ASVS 5.0","SBOM","work/provider-agnostic-wallet-layer","Three MPC custody models with different trust boundaries and key ceremonies, abstracted so product code never learns which one is underneath.","M3utj1X3A6BtWh0L-ggj0if3J4GwjJMV3_56VvM3-jg",{"id":476,"title":477,"body":478,"client":362,"confidential":100,"description":88,"extension":98,"featured":97,"meta":533,"navigation":100,"order":534,"path":535,"period":365,"secondary":198,"seo":536,"sitemap":537,"slug":538,"stack":539,"stem":543,"summary":544,"track":117,"__hash__":545},"work/work/identity-and-onboarding.md","Onboarding and identity for a regulated crypto platform",{"type":8,"value":479,"toc":526},[480,482,485,487,490,493,495,509,511,521,523],[11,481,14],{"id":13},[16,483,484],{},"A regulated crypto wealthtech platform bridging DeFi, CeFi and TradFi. Regulated means onboarding is a compliance surface, and identity is the door to custody. I led the team that built the secure onboarding and identity layer. The client is current, so it stays unnamed.",[11,486,22],{"id":21},[16,488,489],{},"Invitations are tokens that get emailed, forwarded, clicked twice and clicked late. A single-use invitation token has more states than valid and used, and most implementations discover the others in production.",[16,491,492],{},"Authentication had to go through a third-party identity provider using OIDC with PKCE. KYC verification came from a specialist vendor and had to be visible, as one fact, to every core service that makes a decision on it.",[11,494,32],{"id":31},[34,496,497,500,503,506],{},[37,498,499],{},"Specified the invitation token lifecycle as a state machine before writing it, with every state and transition named, so that clicked twice and clicked after expiry had defined outcomes rather than surprising ones.",[37,501,502],{},"Built an auth gateway in front of the core services, with OIDC/PKCE against a third-party identity provider so that credential handling stays with the provider.",[37,504,505],{},"Integrated a KYC verification vendor across the core services, so verification status is written once and read everywhere.",[37,507,508],{},"Ran the invitation flow through an email provider, with delivery treated as one of the token's states.",[11,510,55],{"id":54},[34,512,513,516,519],{},[37,514,515],{},"A state machine is more design up front than a boolean. It pays for itself the first time a token is forwarded to the wrong inbox.",[37,517,518],{},"Delegated identity means the provider's outage is your outage, and the provider's session model is your session model.",[37,520],{},[11,522,72],{"id":71},[16,524,525],{},"Production-grade onboarding and identity in a regulated product, delivered by the team I led.",{"title":88,"searchDepth":89,"depth":89,"links":527},[528,529,530,531,532],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},{},6,"/work/identity-and-onboarding",{"title":477,"description":88},{"loc":535},"identity-and-onboarding",[540,469,541,542],"Auth gateway","Single-use tokens","KYC integration","work/identity-and-onboarding","A single-use invitation token lifecycle specified as a state machine, an auth gateway with OIDC/PKCE, and KYC verification across core services.","6dqAy0BnIXlOQEENXcuc1vEHsQYbmoLW-NSFNL7J9UA",{"id":547,"title":548,"body":549,"client":194,"confidential":97,"description":88,"extension":98,"featured":97,"meta":602,"navigation":100,"order":603,"path":150,"period":197,"secondary":198,"seo":604,"sitemap":605,"slug":606,"stack":607,"stem":609,"summary":610,"track":210,"__hash__":611},"work/work/engineering-ladder.md","A promotion framework for a consultancy that does not fit product-company ladders",{"type":8,"value":550,"toc":595},[551,553,556,558,561,563,576,578,586,588],[11,552,14],{"id":13},[16,554,555],{},"Turntabl places engineers inside client teams and runs product teams for others. An engineer's work is visible mostly to the client, and the same engineer might be on three engagements in two years. Product-company ladders assume a manager who sees the work every day and a single codebase to be judged against. Here, both assumptions are false.",[11,557,22],{"id":21},[16,559,560],{},"A ladder for this context has to answer three things a product ladder can skip. What counts as evidence when the evidence lives with the client. What progression means when scope is measured in engagements rather than services owned. And where the choice to lead people sits, so that staying technical is a promotion path rather than a plateau.",[11,562,32],{"id":31},[34,564,565,568,571,574],{},[37,566,567],{},"Authored a five-level career progression and promotion framework for company leadership.",[37,569,570],{},"Adapted consulting firm models, where progression tracks engagement scope and client trust, instead of product-company ladders tied to a single codebase.",[37,572,573],{},"Forked the framework into IC and management tracks at L4, so that leading people is a deliberate choice and a senior engineer who does not want reports still has somewhere to go.",[37,575],{},[11,577,55],{"id":54},[34,579,580,583],{},[37,581,582],{},"A framework is a document until leadership uses it in a promotion round.",[37,584,585],{},"Forking at L4 means two sets of expectations to maintain, and two sets of arguments about whether someone is on the right one.",[11,587,72],{"id":71},[16,589,590,591,594],{},"The framework exists and was authored for company leadership. It is one of the artefacts that came out of the ",[148,592,593],{"href":196},"transition to thirty engineers",".",{"title":88,"searchDepth":89,"depth":89,"links":596},[597,598,599,600,601],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},{},7,{"title":548,"description":88},{"loc":150},"engineering-ladder",[203,608],"Career frameworks","work/engineering-ladder","Five levels, forking into IC and management tracks at L4, adapted from consulting firm models for an engagement-based services context.","sbbYoDYlMFV_bwjP1QThC1i_Ad-t2pfgMorMl13qOvs",{"id":613,"title":614,"body":615,"client":666,"confidential":97,"description":88,"extension":98,"featured":97,"meta":667,"navigation":100,"order":668,"path":669,"period":670,"secondary":198,"seo":671,"sitemap":672,"slug":673,"stack":674,"stem":676,"summary":677,"track":117,"__hash__":678},"work/work/reconciliation-automation.md","Automating end-of-day reconciliation",{"type":8,"value":616,"toc":659},[617,619,622,624,627,629,637,639,646,648],[11,618,14],{"id":13},[16,620,621],{},"ReachAfrika ran a reward-advertising and bill-payment platform. Bill payments went out over Nigerian utility-payment APIs. I was a full-stack developer there from 2019 to 2020.",[11,623,22],{"id":21},[16,625,626],{},"At the end of each day, the platform's ledger and the providers' records had to agree, and they did not always. Reconciliation was not automated, so mismatches were found late, by people, after the money had already moved.",[11,628,32],{"id":31},[34,630,631,634],{},[37,632,633],{},"Automated the end-of-day reconciliation: pull each provider's records, match them against the ledger, and surface every mismatch with enough context to act on it.",[37,635,636],{},"Integrated the Nigerian utility-payment APIs the reconciliation ran against.",[11,638,55],{"id":54},[34,640,641,644],{},[37,642,643],{},"Every provider format is its own parser, and every parser is a maintenance commitment.",[37,645],{},[11,647,72],{"id":71},[34,649,650,653,656],{},[37,651,652],{},"Financial errors down over 75%.",[37,654,655],{},"Transaction volume up 20% after the utility-payment integrations.",[37,657,658],{},"Customer acquisition up over 25% on the platform as a whole.",{"title":88,"searchDepth":89,"depth":89,"links":660},[661,662,663,664,665],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"ReachAfrika",{},8,"/work/reconciliation-automation","2019 to 2020",{"title":614,"description":88},{"loc":669},"reconciliation-automation",[109,467,114,113,675],"Linux","work/reconciliation-automation","Financial errors down over 75% after end-of-day reconciliation stopped being a job someone did by hand.","RsNYHayuKSWZQtVMCjoeBfzJskItnP_07eSzwPWwuXk",{"id":680,"title":681,"body":682,"client":756,"confidential":97,"description":88,"extension":98,"featured":97,"meta":757,"navigation":100,"order":758,"path":759,"period":760,"secondary":104,"seo":761,"sitemap":762,"slug":763,"stack":764,"stem":769,"summary":770,"track":117,"__hash__":771},"work/work/fatca-regulatory-reporting.md","Reporting to a foreign regulator out of a core banking system",{"type":8,"value":683,"toc":749},[684,686,689,691,694,697,700,702,716,718,729,731,739],[11,685,14],{"id":13},[16,687,688],{},"First Atlantic Bank is a commercial bank in Accra. I joined in October 2018 as a junior software developer, on the team that builds and maintains the bank's internal applications. It was my first job in a regulated institution, and the first time compliance was something I had to build rather than something another department handled.",[11,690,22],{"id":21},[16,692,693],{},"FATCA obliges financial institutions outside the United States to identify accounts held by US persons and report them to the IRS. Getting it wrong is not a bug report. It is withholding penalties and sanctions against the bank.",[16,695,696],{},"The account data lived in the core banking system. The report the IRS wanted did not, and nobody was going to assemble it by hand every reporting cycle at the accuracy that regime requires.",[16,698,699],{},"Separately, customers had to leave the bank to pay an electricity bill, which meant the bank saw none of that traffic.",[11,701,32],{"id":31},[34,703,704,707,710,713],{},[37,705,706],{},"Built and maintained an application that interfaces with the bank's core banking system, Oracle FlexCube, taking scheduled extracts and assembling the FATCA return for the IRS.",[37,708,709],{},"Put the assembled data behind a dashboard so the compliance team could reconcile it on demand, in their own time, without a developer sitting between them and the numbers they were accountable for.",[37,711,712],{},"Built and maintained a branch module application and a REST API service integrating the Electricity Company of Ghana payments API, so utility payments could be made through the bank.",[37,714,715],{},"Supported and trained bank staff across the applications the development team had built, which is where I learned that an application nobody can operate is an application that does not work.",[11,717,55],{"id":54},[34,719,720,723],{},[37,721,722],{},"The data had to come out of a live core banking system. FlexCube is what the bank runs on all day, so it does not hand over extracts on demand. You take them on a schedule it can tolerate, and everything downstream inherits that cadence, including how current the numbers are when compliance opens the dashboard.",[37,724,725,726,728],{},"Reconciliation stayed manual. Compliance opened the dashboard and worked through the numbers themselves, every cycle. That does not scale: the effort grows with the account book, and it lands on whoever is free when the deadline arrives. A year later at ",[148,727,666],{"href":669}," I built the automated end-of-day reconciliation I had not built here, and financial errors fell by over 75%. That is the one I would go back and change.",[11,730,72],{"id":71},[34,732,733,736],{},[37,734,735],{},"FATCA reporting to the IRS delivered off the core banking system, keeping the bank clear of fines and sanctions.",[37,737,738],{},"Revenue collections and commissions up over 10% after the utility payments integration.",[16,740,741,742,744,745,748],{},"This is the oldest work on the site and the smallest in scope. It is here because the thread starts here: every regulated engagement since, from ISO 27001 and a central bank licence at ",[148,743,96],{"href":102}," to ASVS Level 2 and separation of duties on ",[148,746,747],{"href":460},"custody work",", is the same job at a larger size.",{"title":88,"searchDepth":89,"depth":89,"links":750},[751,752,753,754,755],{"id":13,"depth":89,"text":14},{"id":21,"depth":89,"text":22},{"id":31,"depth":89,"text":32},{"id":54,"depth":89,"text":55},{"id":71,"depth":89,"text":72},"First Atlantic Bank",{},9,"/work/fatca-regulatory-reporting","2018 to 2019",{"title":681,"description":88},{"loc":759},"fatca-regulatory-reporting",[110,765,766,767,768],"C#","Oracle FlexCube","SQL Server","REST APIs","work/fatca-regulatory-reporting","FATCA returns assembled out of the bank's core banking system for the US IRS, plus a utility payments integration that lifted collections over 10%.","ZTg4dI9eMu0tE4oxcFLyN5MHp9W19AcCjEzwdNnlMfw",1789350680585]