[{"data":1,"prerenderedAt":6440},["ShallowReactive",2],{"blog-recent":3},[4,411,782,1840,1999,2262,2518,2766,2884,3105,3569,3782,4006,4206,4408,4976,5126,5269,5436,5527,6144,6295,6407],{"id":5,"title":6,"body":7,"date":396,"description":397,"extension":398,"featured":399,"meta":400,"navigation":401,"part":402,"path":403,"seo":404,"series":402,"stem":405,"tags":406,"tldr":409,"__hash__":410},"blog\u002Fblog\u002Fstop-calling-that-excel-file-a-database.md","Stop Calling That Excel File a Database",{"type":8,"value":9,"toc":382},"minimark",[10,14,27,34,37,43,46,51,54,60,76,91,95,98,103,106,109,115,121,125,128,131,136,141,145,148,155,164,173,177,180,186,196,202,211,221,225,228,250,253,260,284,287,293,303,309,313,320,341,344,356,362,365,368],[11,12,13],"p",{},"For the past few weeks I've been helping small companies automate their workflows and analyse their data. Credit modelling here, data pipelines there, the usual. And I keep running into the same thing.",[11,15,16,17,21,22,26],{},"These aren't struggling startups. They have flowing income. Real clients. Real revenue. Some of them are ",[18,19,20],"em",{},"crushing it"," on paper. But ask them where their data lives and they'll point you to a share drive full of Excel files with names like ",[23,24,25],"code",{},"client_data_v3_final_FINAL(2).xlsx",".",[11,28,29,30],{},"No single point of truth. No unified system. Just spreadsheets. Everywhere. ",[31,32,33],"del",{},"Sound familiar?",[11,35,36],{},"And I get it. Excel is comfortable. You open it, you type stuff, you make a chart, you feel productive. But here's the thing:",[11,38,39],{},[40,41,42],"strong",{},"Your Excel file is not a database.",[11,44,45],{},"It's not even close. And the fact that you've been using it as one is the reason your \"client_data_v3_final_FINAL(2).xlsx\" exists in the first place.",[47,48,50],"h2",{"id":49},"what-a-database-actually-is","What a Database Actually Is",[11,52,53],{},"Think of it this way. A spreadsheet is a whiteboard. You can write anything anywhere, erase it, overwrite it, and nobody's stopping you. It's flexible. It's fast. And it's completely untrustworthy.",[11,55,56,57,26],{},"A database is a filing cabinet with rules. Every drawer has a label. Every folder has a slot. You can't put a client record in the claims folder. You can't file the same client twice. Two people can access the cabinet at the same time without losing anything. And if someone tries to file something wrong, the cabinet says ",[18,58,59],{},"no",[11,61,62,63,66,67,70,71],{},"That's the whole difference. A spreadsheet ",[18,64,65],{},"trusts"," you. A database ",[18,68,69],{},"protects"," you. ",[31,72,73],{},[40,74,75],{},"And based on what I've seen, you need protecting.",[11,77,78,79,82,83,86,87,90],{},"The technical terms for this are ",[40,80,81],{},"constraints"," (rules about what data is allowed), ",[40,84,85],{},"relationships"," (how different pieces of data connect to each other), and ",[40,88,89],{},"concurrent access"," (multiple people using the same data without breaking things). You don't need to remember those words. Just know that a database has them and Excel doesn't.",[47,92,94],{"id":93},"the-three-you-should-know","The Three You Should Know",[11,96,97],{},"There are hundreds of databases out there, but three dominate. Each has a purpose. Knowing which one to reach for is half the battle.",[99,100,102],"h3",{"id":101},"sqlite-the-one-that-lives-in-your-pocket","SQLite: The One That Lives in Your Pocket",[11,104,105],{},"SQLite is a database that lives inside a single file. No server to set up. No configuration. No IT department needed. You create a file on your computer, point a program at it, and it just works.",[11,107,108],{},"It's already everywhere — your phone uses it, your browser uses it, your operating system uses it. You just never noticed because it works quietly in the background.",[11,110,111,114],{},[40,112,113],{},"Use it when:"," You're building a small tool, a personal project, or anything where one or two people need to work with structured data. If you're a developer building a CLI tool or an analyst with a modest laptop SQLite is probably your answer.",[11,116,117,120],{},[40,118,119],{},"Skip it when:"," You've got dozens of people hitting the database at the same time from different servers. SQLite is a single-writer — great for most things, but not for high-traffic applications.",[99,122,124],{"id":123},"mysql-the-one-that-runs-the-internet","MySQL: The One That Runs the Internet",[11,126,127],{},"MySQL is the workhorse. It's the database behind WordPress, which powers about 40% of the web. If you've ever visited a website, you've probably interacted with MySQL without knowing it.",[11,129,130],{},"It's a proper client-server database — meaning you set it up on a machine (or in the cloud), and multiple people and applications connect to it over a network. It handles thousands of simultaneous users, supports backups and replication, and has been battle-tested for over 30 years.",[11,132,133,135],{},[40,134,113],{}," You're building a web application, an internal tool, or anything where multiple users need to read and write data at the same time. MySQL is the safe default. Hosting is cheap, the tooling is mature, and it's good enough for 99% of use cases.",[11,137,138,140],{},[40,139,119],{}," You need advanced analytical features or complex data types. MySQL can do a lot, but when you start pushing into heavy analytics or flexible data structures, you want Postgres.",[99,142,144],{"id":143},"postgres-the-one-that-does-everything","Postgres: The One That Does Everything",[11,146,147],{},"PostgreSQL is the Swiss Army knife of databases. It's open-source, it's powerful, and it has features that the others simply don't.",[11,149,150,151,154],{},"It handles relational data (tables with relationships), but it also handles ",[18,152,153],{},"flexible"," data — think JSON documents stored right alongside your structured tables. It supports complex queries, full-text search, custom data types, and basically anything you can throw at it.",[11,156,157,159,160,163],{},[40,158,113],{}," You're building something complex. Financial systems, analytics platforms, credit modelling, anything where the data relationships are non-trivial. If you're doing analytical work (",[31,161,162],{},"which I've been doing a lot of lately","), Postgres is where you want your data.",[11,165,166,168,169,172],{},[40,167,119],{}," You just need something simple. If you're building a todo app or a personal tool, SQLite is lighter and easier. Postgres is overkill for small projects — but it's never the ",[18,170,171],{},"wrong"," choice.",[47,174,176],{"id":175},"why-excel-fails","Why Excel Fails",[11,178,179],{},"Let me count the ways.",[11,181,182,185],{},[40,183,184],{},"No concurrency."," Two people open the same Excel file? One gets read-only. You email it around? Now you've got five copies and nobody knows which is current. A database handles this transparently. Hundreds of users, simultaneous reads and writes, consistent data every time.",[11,187,188,191,192,195],{},[40,189,190],{},"No constraints."," Type a negative number into a premium field? Excel doesn't care. Type \"asdf\" into a date field? Excel will ",[18,193,194],{},"silently convert it to a date"," (December 30, 1900, because of course it will). A database rejects invalid data at the point of entry. Your data stays clean.",[11,197,198,201],{},[40,199,200],{},"No relationships."," Linking clients to policies to claims in Excel means VLOOKUP. And INDEX\u002FMATCH. And circular references that break when someone inserts a row. In a database, you define the connections once and the database maintains them forever. Client deleted? The database tells you there are outstanding policies first.",[11,203,204,207,208,26],{},[40,205,206],{},"No audit trail."," Who changed that number? When? Why? Excel doesn't know. A database with proper logging can tell you exactly who modified what and when. For financial services, this isn't optional. It's a ",[18,209,210],{},"regulatory requirement",[11,212,213,216,217,220],{},[40,214,215],{},"No scale."," Try loading 100,000 rows of client data in Excel. ",[31,218,219],{},"Go ahead, I'll wait."," Now try doing a lookup across that. Now try doing it while someone else is editing the file. A database handles millions of rows without breaking a sweat.",[47,222,224],{"id":223},"the-single-point-of-truth-problem","The Single Point of Truth Problem",[11,226,227],{},"Here's what I keep seeing. A company has:",[229,230,231,235,238,241,244,247],"ul",{},[232,233,234],"li",{},"A client list in one Excel file",[232,236,237],{},"Policy data in another",[232,239,240],{},"Claims data in a third",[232,242,243],{},"Financial summaries manually compiled from all three",[232,245,246],{},"Fees calculated manually from all three",[232,248,249],{},"A person whose entire job is to reconcile these files every month",[11,251,252],{},"That person is a human doing what a database does, except slower, with errors, and without an audit trail.",[11,254,255,256,259],{},"The \"single source of truth\" isn't a philosophy. It's a ",[18,257,258],{},"technical requirement",". You need one place where the data lives, with rules about what can go in, and a system that enforces those rules. That's a database. Not a share drive. Not a Google Sheet. Not a collection of Excel files named with increasingly desperate version numbers.",[11,261,262,263,270,271,276,277,280,281,26],{},"When I built ",[264,265,269],"a",{"href":266,"rel":267},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Fv-star",[268],"nofollow","v-star",", the whole point was a single engine that processes policies correctly. When I built ",[264,272,275],{"href":273,"rel":274},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Ffees",[268],"fees"," ",[31,278,279],{},"you wont see it cause its IP",", the first thing I set up was a PostgreSQL database with proper schema, foreign keys, and audit triggers. Not because I'm fancy. Because ",[18,282,283],{},"that's how you make sure the numbers are right",[11,285,286],{},"The companies I've been helping lately — they have good data. Real data. Data that could drive better decisions, optimise pricing, identify risk patterns. But it's locked in spreadsheets. Fragmented. Inconsistent. And every time someone compiles a report, they're making judgment calls about which version of the truth to use.",[11,288,289,290],{},"That's not a people problem. That's an infrastructure problem. And the fix is simple: ",[40,291,292],{},"put it in a database.",[11,294,295,296,299,300],{},"And for those of you who ",[18,297,298],{},"know"," this and are screaming into the void — I see you. Worse off are the people who work in an office with a database but, not knowingly, IT won't give them read access for their analysis in Excel because they think you're taking their jobs. Bro. I just want to streamline this person's workflow, get paid, and leave. That's it. That's the whole motivation. ",[31,301,302],{},"But no, gotta protect the sacred SQL Server from the actuary with a Python script.",[304,305],"img",{"src":306,"alt":307,"width":308},"https:\u002F\u002Fi1.sndcdn.com\u002Fartworks-MrGMX4hO0XXPablj-hCuNgw-t1080x1080.jpg","Trust me bro",400,[47,310,312],{"id":311},"what-id-tell-that-small-company","What I'd Tell That Small Company",[11,314,315,316,319],{},"If you're running a business on Excel files and you know it's a mess — and you ",[18,317,318],{},"do"," know, you just haven't had time to fix it — here's the minimum viable fix:",[321,322,323,329,335],"ol",{},[232,324,325,328],{},[40,326,327],{},"Pick SQLite"," if you're a small operation with one or two people accessing data. It's a single file, zero setup, and it'll handle more than you think.",[232,330,331,334],{},[40,332,333],{},"Pick MySQL"," if you've got a web application or multiple users hitting the data concurrently. Managed MySQL on any cloud provider costs almost nothing.",[232,336,337,340],{},[40,338,339],{},"Pick Postgres"," if you're doing anything analytical, financial, or complex. The flexibility alone is worth the switch.",[11,342,343],{},"Then move your data. One table at a time. Clients first. Then matters\u002Fpolicies. Then claims. Then financials. Build the relationships. Add the constraints. Write the queries.",[11,345,346,347,352,353],{},"Don't let people tell you getting a database is hard. You can run one in a ",[264,348,351],{"href":349,"rel":350},"https:\u002F\u002Fwww.docker.com\u002F",[268],"Docker"," container in about 30 seconds. The issue is a lot of us aren't technically inclined in these things. ",[31,354,355],{},"Maybe I should get a Data Protection certificate.",[11,357,358,359,26],{},"It's not glamorous work. But it's the foundation that everything else sits on. And until you do it, every report you compile, every analysis you run, every decision you make is sitting on a foundation of ",[31,360,361],{},"cell references and prayer",[11,363,364],{},"Your Excel file is not a database. Your business deserves better.",[366,367],"hr",{},[11,369,370],{},[18,371,372,373,377,378,381],{},"P.S. — If you're still using Excel for reserving\u002Fsimulations, I have ",[264,374,376],{"href":375},"\u002Fblog\u002Fwhygolang","some thoughts",". And if you're building financial systems, ",[264,379,269],{"href":266,"rel":380},[268]," processes a million policies in under 300ms. Just saying.",{"title":383,"searchDepth":384,"depth":384,"links":385},"",2,[386,387,393,394,395],{"id":49,"depth":384,"text":50},{"id":93,"depth":384,"text":94,"children":388},[389,391,392],{"id":101,"depth":390,"text":102},3,{"id":123,"depth":390,"text":124},{"id":143,"depth":390,"text":144},{"id":175,"depth":384,"text":176},{"id":223,"depth":384,"text":224},{"id":311,"depth":384,"text":312},"2026-07-10","For the past few weeks I've been helping small companies automate workflows and analyse data. Most of them have flowing income, real clients, real revenue — and not a single source of truth. Just spreadsheets duct-taped together.","md",false,{},true,null,"\u002Fblog\u002Fstop-calling-that-excel-file-a-database",{"title":6,"description":397},"blog\u002Fstop-calling-that-excel-file-a-database",[407,408],"software","actuarial","Small companies with real revenue are running their entire operations on Excel files passed around via email. Here's what a database actually is, why SQLite\u002FMySQL\u002FPostgres exist, and why your business is one corrupted file away from disaster.","Kt9eU8uBP8LcNdjR8yzC7vY2JoFeL4vVrc3ka3T3i-Q",{"id":412,"title":413,"body":414,"date":772,"description":773,"extension":398,"featured":401,"meta":774,"navigation":401,"part":402,"path":775,"seo":776,"series":402,"stem":777,"tags":778,"tldr":780,"__hash__":781},"blog\u002Fblog\u002Finto-the-larp.md","Into the LARP: Why I Treat Life Like a Game",{"type":8,"value":415,"toc":763},[416,422,427,433,440,445,448,455,458,462,468,475,481,484,488,491,501,507,513,519,529,539,543,546,549,556,567,574,578,584,591,602,609,612,622,626,629,636,646,649,656,660,663,670,673,679,683,730,732,735,738,741,746,748],[11,417,418,419,26],{},"There's this scene in ",[40,420,421],{},"The Dark Knight",[11,423,424],{},[18,425,426],{},"Joker: (holding a knife inside Gambol's mouth) Wanna know how I got these scars? My father was...a drinker. And a fiend. And one night he goes off crazier than usual. Mommy gets the kitchen knife to defend herself. He doesn't like that. Not. One. Bit. So - me watching - he takes the knife to her, laughing while he does it! Turns to me, and he says, \"Why so serious, son?\" Comes at me with the knife... \"Why so serious?\" He sticks the blade in my mouth... \"Let's put a smile on that face!\" And... why so serious?",[11,428,429,430],{},"Joker asked : ",[18,431,432],{},"\"Why so serious?\"",[11,434,435,436,439],{},"Let me try to cook — I know quoting the Joker as life advice is concerning. But the man had a point. Not the chaos-and-murder part, obviously. The ",[18,437,438],{},"other"," part.",[304,441],{"src":442,"alt":443,"width":444},"https:\u002F\u002Fencrypted-tbn0.gstatic.com\u002Fimages?q=tbn:ANd9GcS43X3lBpvBiabxcnYnO-Izyr-1G9kYrTE6akRE8GpU-A&s=10","Joker asking why so serious",600,[11,446,447],{},"Life is serious. Objectively. Bills, deadlines, expectations, the actuarial exams I'm supposed to be studying for right now, the fact that I'm writing a blog post instead of studying for actuarial exams. Serious stuff.",[11,449,450,451,454],{},"But here's the thing I've figured out: if you take life too seriously, it'll show you ",[18,452,453],{},"exactly"," how serious it can be. It'll double down. It'll throw more at you. Suddenly you're stressed about being stressed, anxious about being anxious, and the whole thing becomes a recursive loop of misery.",[11,456,457],{},"So I stopped. I started LARPing.",[47,459,461],{"id":460},"what-larping-actually-means","What \"LARPing\" Actually Means",[11,463,464,465],{},"Not the Sword,elves, Harry Potter, Lord of the Rings  kind. ",[31,466,467],{},"Although no judgment if that's your thing.",[11,469,470,471,474],{},"LARPing — in the way I mean it — is treating life like a game. Live Action Role Play. Not in the \"nothing matters, YOLO\" nihilist way. In the ",[18,472,473],{},"structured"," way. Games have objectives, side quests, stats, buffs, debuffs, NPCs, boss fights. You play them intentionally. You don't stand in the tutorial zone crying about how hard the final boss is — you grind, you gear up, you learn the mechanics, and then you go fight.",[11,476,477,478,26],{},"The same framework applies to real life. The difference is most people don't realise they're in a game at all. They think they're in a documentary. Passive. Observing. ",[18,479,480],{},"Reacting",[11,482,483],{},"Nah. You're the playable character. You have agency. You have a build. You're allocating skill points every single day whether you realise it or not.",[47,485,487],{"id":486},"the-character-sheet","The Character Sheet",[11,489,490],{},"Let me break down my current build. This is how I actually think about it:",[11,492,493,496,497,500],{},[40,494,495],{},"Basic Armor; Dress Shoes, Tie, Fitted Shirt."," You walk into a room full of powerful people — execs, clients, whoever — and the first thing they clock isn't your CV. It's whether you look like you belong there. The shoes are +5 credibility. The tie is a social convention tax but it works. This isn't about vanity — it's about ",[18,498,499],{},"defence",". Good armor reduces incoming damage from snap judgments.",[11,502,503,506],{},[40,504,505],{},"Weapon Slot: The Things You Can Actually Do."," For me, that's Go, Python, data, automation and the fact most people hate maths. I can walk into your office, figure out what's eating your week, and automate it into oblivion. That's not just \"skills\" — that's my primary damage output. The thing I can deploy that actually changes the game state.",[11,508,509,512],{},[40,510,511],{},"Status Buffs: Conversational Skills."," Active listening. Knowing when to shut up. Asking the right question at the right time. Being genuinely curious about what someone does. These are not soft skills — they're +15 charisma buffs that stack multiplicatively with your technical stats. A technically brilliant person with zero charisma is a glass cannon. Effective. Fragile. Easy to sideline.",[11,514,515,518],{},[40,516,517],{},"Quick-Time Events: Handshakes, Elevator Pitches, The First Five Minutes."," You know that moment in a game where a prompt flashes on screen and you have 0.8 seconds to press the right button or you take damage? That's what meeting someone important feels like. The handshake — firmness, eye contact, timing. The introduction — concise, confident, memorable. Every one of these is a QTE and you either nail it or you don't.",[11,520,521,524,525,528],{},[40,522,523],{},"Debuffs: Anxiety."," Everyone has debuffs. Mine's anxiety. The voice that says \"you don't belong in this room\" while I'm literally standing in the room having been invited there. The thing about debuffs in games is you don't ",[18,526,527],{},"remove"," them — you learn to play around them. You spec into other stats to compensate. You bring consumables (coffee, deep breaths, the right playlist before a meeting). You learn the attack patterns so you know when the debuff is about to proc.",[11,530,531,534,535,538],{},[40,532,533],{},"Quest Log:"," Build tools that matter. Automate things people hate doing. Pass actuarial exams ",[31,536,537],{},"eventually",". Ship projects, not prototypes. Make enough money that I can say \"no\" to bad deals and \"yes\" to interesting ones.",[47,540,542],{"id":541},"play-the-objective","Play the Objective",[11,544,545],{},"Here's the critical distinction between LARPing and just messing around: you still play the objective.",[11,547,548],{},"I'm not saying ignore your responsibilities. I'm not saying treat deadlines like optional side quests. The game has a main storyline — your career, your growth, your relationships — and you do have to advance it.",[11,550,551,552,555],{},"But ",[18,553,554],{},"how"," do you advance it? That's where the game opens up.",[11,557,558,559,562,563,566],{},"Example: in my previous life at ",[18,560,561],{},"the company",", Brian and I automated entire workflows — data cleaning pipelines, Excel report generation, regex-based date normalisation — and finished 10-day sprints on day 1. We played the objective (deliver the work) but we did it ",[18,564,565],{},"our way"," (scripts, automation, coordinated two-device meetings where one of us was at a coffee shop and the other was screensharing). The objective was cleared. The method was unconventional. The result was us running errands during work hours instead of staring at spreadsheets.",[11,568,569,570,573],{},"That's the game. Find the optimal path to clear the objective, not the ",[18,571,572],{},"expected"," path.",[47,575,577],{"id":576},"why-im-not-a-gamer","Why \"I'm Not a Gamer\"",[11,579,580,581],{},"People hear \"treat life like a game\" and they assume I'm some hardcore gamer with RGB everything... ",[18,582,583],{},"Hmm... But I might be cracked though",[11,585,586,587,590],{},"I'm not. I don't really play video games. ",[18,588,589],{},"I'm committed to anime",". The games I do play are CODM when I'm bored.",[11,592,593,594,597,598,601],{},"The difference is this: gamers play games ",[18,595,596],{},"inside"," life. They're escaping into a designed system with clear rules, clear feedback, clear progression. LARPers bring the game ",[18,599,600],{},"out into life",". We apply game logic to the things that don't come with a tutorial.",[11,603,604,605,608],{},"A gamer gets frustrated when a real-life conversation doesn't have dialogue options. A LARPer realises it ",[18,606,607],{},"does"," — you just have to write them yourself and accept the branching narrative consequences.",[11,610,611],{},"A gamer looks for the minimap. A LARPer builds a mental one from experience and intuition.",[11,613,614,615,618,619],{},"A gamer saves before a difficult encounter. A LARPer prepares — research, practice, contingencies — because there's no reload button in real life, but there ",[18,616,617],{},"is"," preparation. ",[40,620,621],{},"But we both YOLO at times",[47,623,625],{"id":624},"the-hardest-boss-fight","The Hardest Boss Fight",[11,627,628],{},"You know what the hardest boss in this game is?",[11,630,631,632,635],{},"Your own brain. Specifically, the part of it that tells you to be ",[18,633,634],{},"serious",". To conform. To not try things that might look silly. To colour inside the lines because that's what everyone else does.",[11,637,638,639,642,643,645],{},"\"Don't treat this like a game. This is your ",[18,640,641],{},"career",". This is ",[18,644,634],{},".\"",[11,647,648],{},"That voice? That's the tutorial NPC you're supposed to stop listening to after the first level. It keeps you safe, sure. But it also keeps you in the starting zone doing fetch quests forever while other players are out there clearing dungeons.",[11,650,651,652,655],{},"The moment I started treating intimidating situations like game mechanics — the boardroom as a boss arena, networking events as social stealth missions, job interviews as dialogue trees with specific skill checks — the anxiety didn't vanish, but it became ",[18,653,654],{},"manageable",". It became a mechanic I could play around, not a wall I couldn't climb.",[47,657,659],{"id":658},"your-build-is-yours","Your Build Is Yours",[11,661,662],{},"The whole point of RPGs is that different builds work. The tank build, the DPS build, the support build, the weird hybrid build that shouldn't work but somehow does. All viable. All capable of clearing the content.",[11,664,665,666,669],{},"Your life is the same. The path everyone tells you to take — school, degree, corporate job, mortgage, retirement — that's one build. It works. It's the meta. But it's not the ",[18,667,668],{},"only"," build.",[11,671,672],{},"Some of us spec into weird skill trees. I put points into Go and automation instead of networking and corporate politics. My charisma stat is lower than it could be but my technical output is through the roof. I'm not optimised for the standard raid — I'm optimised for the fights I actually want to take.",[11,674,675,676,26],{},"And that's fine. Better than fine. It's ",[18,677,678],{},"intentional",[47,680,682],{"id":681},"lets-recap-the-game-manual","Let's Recap the Game Manual",[321,684,685,691,697,703,709,718,724],{},[232,686,687,690],{},[40,688,689],{},"You're the playable character."," Not an NPC. Not an observer. You have agency, stats, and a build. Act like it.",[232,692,693,696],{},[40,694,695],{},"Dress shoes are armour."," The right gear reduces incoming damage. Don't neglect your equipment slots.",[232,698,699,702],{},[40,700,701],{},"Handshakes are QTEs."," They happen fast, they matter, and you get better at them with practice.",[232,704,705,708],{},[40,706,707],{},"Anxiety is a debuff."," You can't remove it. You learn to play around it. Know your own attack patterns.",[232,710,711,714,715,717],{},[40,712,713],{},"Play the objective."," The game has a main quest. You have to advance it. But ",[18,716,554],{}," you advance it? That's where the fun is.",[232,719,720,723],{},[40,721,722],{},"Your build is yours."," The meta exists. It works. It's not the only build. Spec into what you're actually good at.",[232,725,726,729],{},[40,727,728],{},"There's no reload button."," But there is preparation. There is learning from failure. There is trying again tomorrow with better gear and more levels.",[366,731],{},[11,733,734],{},"I'm not a gamer. I'm a LARPer.",[11,736,737],{},"I walk into your offices, help people with their problems, automate their work, and make them happy. I shake hands with powerful people and hit the QTE prompts. I wear the armor. I manage the debuffs. I spec into the skills that matter to me.",[11,739,740],{},"And I have fun doing it. Because if you're not having fun, what's the point of playing?",[11,742,743],{},[40,744,745],{},"GG. No re.",[366,747],{},[11,749,750],{},[18,751,752,753,757,758,762],{},"This is part of the ",[264,754,756],{"href":755},"\u002Fblog\u002Fproductive-laziness","THE METHOD"," series. Part 1 covers the automation philosophy. Part 2 covers the ",[264,759,761],{"href":760},"\u002Fblog\u002Fproductive-laziness-toolkit","toolkit"," — regex pipelines, Excel injection, and the full arsenal.",{"title":383,"searchDepth":384,"depth":384,"links":764},[765,766,767,768,769,770,771],{"id":460,"depth":384,"text":461},{"id":486,"depth":384,"text":487},{"id":541,"depth":384,"text":542},{"id":576,"depth":384,"text":577},{"id":624,"depth":384,"text":625},{"id":658,"depth":384,"text":659},{"id":681,"depth":384,"text":682},"2026-07-01","I'm not a gamer. I'm a LARPer. Dress shoes are basic armor, handshakes are quick-time events, and anxiety is a debuff. Here's why Joker had a point.",{},"\u002Fblog\u002Finto-the-larp",{"title":413,"description":773},"blog\u002Finto-the-larp",[641,779],"productivity","Life is serious. But if you take it seriously, it'll show you just how serious it can be. So I LARP — treat it like a game, play the objective, and do my own thing. Dress shoes are basic armor, conversational skills are status buffs, and shaking hands with powerful people is a quick-time event.","ygecQLOgzqBeciXkV6_dzT2u8J5k_5JXlHklc5PHd_A",{"id":783,"title":784,"body":785,"date":1832,"description":1833,"extension":398,"featured":399,"meta":1834,"navigation":401,"part":384,"path":760,"seo":1835,"series":756,"stem":1836,"tags":1837,"tldr":1838,"__hash__":1839},"blog\u002Fblog\u002Fproductive-laziness-toolkit.md","The Toolkit: Regex Pipelines, Excel Injection, and the Arsenal",{"type":8,"value":786,"toc":1826},[787,795,799,805,808,817,824,831,1190,1197,1201,1204,1211,1222,1225,1275,1740,1750,1755,1759,1762,1765,1775,1784,1788,1791,1798,1801,1807,1810,1812,1817,1822],[11,788,789],{},[18,790,791,792,794],{},"This is Part 2 of the ",[264,793,756],{"href":755}," series. If you haven't read Part 1 on the automation philosophy and the two-device meeting, start there.",[47,796,798],{"id":797},"the-regex-date-cleaning-incident","The Regex Date Cleaning Incident",[11,800,801,802,26],{},"Here's a specific one. We'd get datasets — messy, real-world data — and the dates were an absolute ",[18,803,804],{},"disaster",[11,806,807],{},"I'm talking about Excel files with inconsistent formats like:",[809,810,815],"pre",{"className":811,"code":813,"language":814},[812],"language-text","2023-04-15\n15\u002F04\u002F2023\n04\u002F15\u002F2023\nApr 15 2023\n15-Apr-2023\nApril 15, 2023\n20230415\n15.04.2023\n","text",[23,816,813],{"__ignoreMap":383},[11,818,819,820,823],{},"Sometimes ",[18,821,822],{},"all in the same column",". Because of course they were.",[11,825,826,827,830],{},"We wrote a Python script with a list of compiled regex patterns, ranked by confidence. The function tried each pattern, extracted the components, and normalised everything to ISO 8601. If a pattern matched ambiguously (looking at ",[23,828,829],{},"04\u002F05\u002F2023"," — April 5th or May 4th?), it flagged the row for manual review instead of guessing.",[809,832,836],{"className":833,"code":834,"language":835,"meta":383,"style":383},"language-python shiki shiki-themes vesper","import re\nfrom datetime import datetime\n\nDATE_PATTERNS = [\n    (r'^(\\d{4})-(\\d{2})-(\\d{2})$', '%Y-%m-%d'),           # 2023-04-15\n    (r'^(\\d{2})\u002F(\\d{2})\u002F(\\d{4})$', '%d\u002F%m\u002F%Y'),            # 15\u002F04\u002F2023\n    (r'^(\\d{2})\u002F(\\d{2})\u002F(\\d{2})$', '%d\u002F%m\u002F%y'),            # 15\u002F04\u002F23\n    (r'^(\\w{3})\\s+(\\d{1,2})\\s+(\\d{4})$', '%b %d %Y'),     # Apr 15 2023\n    (r'^(\\d{1,2})-(\\w{3})-(\\d{4})$', '%d-%b-%Y'),          # 15-Apr-2023\n    (r'^(\\w{3,9})\\s+(\\d{1,2}),?\\s*(\\d{4})$', '%B %d %Y'), # April 15, 2023\n    (r'^(\\d{8})$', '%Y%m%d'),                               # 20230415\n    (r'^(\\d{2})\\.(\\d{2})\\.(\\d{4})$', '%d.%m.%Y'),          # 15.04.2023\n]\n\ndef normalize_date(value):\n    \"\"\"Try every regex pattern and return the first clean match.\"\"\"\n    for pattern, fmt in DATE_PATTERNS:\n        match = re.match(pattern, value.strip())\n        if match:\n            try:\n                return datetime.strptime(value.strip(), fmt).isoformat()\n            except ValueError:\n                continue\n    return None  # Manual review\n","python",[23,837,838,851,864,869,881,911,934,956,983,1011,1034,1057,1079,1085,1090,1102,1108,1123,1134,1143,1152,1161,1172,1178],{"__ignoreMap":383},[839,840,843,847],"span",{"class":841,"line":842},"line",1,[839,844,846],{"class":845},"sq0yK","import",[839,848,850],{"class":849},"sU-n2"," re\n",[839,852,853,856,859,861],{"class":841,"line":384},[839,854,855],{"class":845},"from",[839,857,858],{"class":849}," datetime ",[839,860,846],{"class":845},[839,862,863],{"class":849}," datetime\n",[839,865,866],{"class":841,"line":390},[839,867,868],{"emptyLinePlaceholder":401},"\n",[839,870,872,875,878],{"class":841,"line":871},4,[839,873,874],{"class":849},"DATE_PATTERNS ",[839,876,877],{"class":845},"=",[839,879,880],{"class":849}," [\n",[839,882,884,887,890,893,897,901,904,907],{"class":841,"line":883},5,[839,885,886],{"class":849},"    (",[839,888,889],{"class":845},"r'^(\\d{4})-(\\d{2})-(\\d{2})$'",[839,891,892],{"class":849},", ",[839,894,896],{"class":895},"sZOz5","'%Y-%m-",[839,898,900],{"class":899},"sNEDb","%d",[839,902,903],{"class":895},"'",[839,905,906],{"class":849},"),           ",[839,908,910],{"class":909},"ss8vJ","# 2023-04-15\n",[839,912,914,916,919,921,923,925,928,931],{"class":841,"line":913},6,[839,915,886],{"class":849},[839,917,918],{"class":845},"r'^(\\d{2})\u002F(\\d{2})\u002F(\\d{4})$'",[839,920,892],{"class":849},[839,922,903],{"class":895},[839,924,900],{"class":899},[839,926,927],{"class":895},"\u002F%m\u002F%Y'",[839,929,930],{"class":849},"),            ",[839,932,933],{"class":909},"# 15\u002F04\u002F2023\n",[839,935,937,939,942,944,946,948,951,953],{"class":841,"line":936},7,[839,938,886],{"class":849},[839,940,941],{"class":845},"r'^(\\d{2})\u002F(\\d{2})\u002F(\\d{2})$'",[839,943,892],{"class":849},[839,945,903],{"class":895},[839,947,900],{"class":899},[839,949,950],{"class":895},"\u002F%m\u002F%y'",[839,952,930],{"class":849},[839,954,955],{"class":909},"# 15\u002F04\u002F23\n",[839,957,959,961,964,966,968,971,974,977,980],{"class":841,"line":958},8,[839,960,886],{"class":849},[839,962,963],{"class":845},"r'^(\\w{3})\\s+(\\d{1,2})\\s+(\\d{4})$'",[839,965,892],{"class":849},[839,967,903],{"class":895},[839,969,970],{"class":899},"%b",[839,972,973],{"class":899}," %d",[839,975,976],{"class":895}," %Y'",[839,978,979],{"class":849},"),     ",[839,981,982],{"class":909},"# Apr 15 2023\n",[839,984,986,988,991,993,995,997,1000,1002,1005,1008],{"class":841,"line":985},9,[839,987,886],{"class":849},[839,989,990],{"class":845},"r'^(\\d{1,2})-(\\w{3})-(\\d{4})$'",[839,992,892],{"class":849},[839,994,903],{"class":895},[839,996,900],{"class":899},[839,998,999],{"class":895},"-",[839,1001,970],{"class":899},[839,1003,1004],{"class":895},"-%Y'",[839,1006,1007],{"class":849},"),          ",[839,1009,1010],{"class":909},"# 15-Apr-2023\n",[839,1012,1014,1016,1019,1021,1024,1026,1028,1031],{"class":841,"line":1013},10,[839,1015,886],{"class":849},[839,1017,1018],{"class":845},"r'^(\\w{3,9})\\s+(\\d{1,2}),?\\s*(\\d{4})$'",[839,1020,892],{"class":849},[839,1022,1023],{"class":895},"'%B ",[839,1025,900],{"class":899},[839,1027,976],{"class":895},[839,1029,1030],{"class":849},"), ",[839,1032,1033],{"class":909},"# April 15, 2023\n",[839,1035,1037,1039,1042,1044,1047,1049,1051,1054],{"class":841,"line":1036},11,[839,1038,886],{"class":849},[839,1040,1041],{"class":845},"r'^(\\d{8})$'",[839,1043,892],{"class":849},[839,1045,1046],{"class":895},"'%Y%m",[839,1048,900],{"class":899},[839,1050,903],{"class":895},[839,1052,1053],{"class":849},"),                               ",[839,1055,1056],{"class":909},"# 20230415\n",[839,1058,1060,1062,1065,1067,1069,1071,1074,1076],{"class":841,"line":1059},12,[839,1061,886],{"class":849},[839,1063,1064],{"class":845},"r'^(\\d{2})\\.(\\d{2})\\.(\\d{4})$'",[839,1066,892],{"class":849},[839,1068,903],{"class":895},[839,1070,900],{"class":899},[839,1072,1073],{"class":895},".%m.%Y'",[839,1075,1007],{"class":849},[839,1077,1078],{"class":909},"# 15.04.2023\n",[839,1080,1082],{"class":841,"line":1081},13,[839,1083,1084],{"class":849},"]\n",[839,1086,1088],{"class":841,"line":1087},14,[839,1089,868],{"emptyLinePlaceholder":401},[839,1091,1093,1096,1099],{"class":841,"line":1092},15,[839,1094,1095],{"class":845},"def",[839,1097,1098],{"class":899}," normalize_date",[839,1100,1101],{"class":849},"(value):\n",[839,1103,1105],{"class":841,"line":1104},16,[839,1106,1107],{"class":895},"    \"\"\"Try every regex pattern and return the first clean match.\"\"\"\n",[839,1109,1111,1114,1117,1120],{"class":841,"line":1110},17,[839,1112,1113],{"class":845},"    for",[839,1115,1116],{"class":849}," pattern, fmt ",[839,1118,1119],{"class":845},"in",[839,1121,1122],{"class":849}," DATE_PATTERNS:\n",[839,1124,1126,1129,1131],{"class":841,"line":1125},18,[839,1127,1128],{"class":849},"        match ",[839,1130,877],{"class":845},[839,1132,1133],{"class":849}," re.match(pattern, value.strip())\n",[839,1135,1137,1140],{"class":841,"line":1136},19,[839,1138,1139],{"class":845},"        if",[839,1141,1142],{"class":849}," match:\n",[839,1144,1146,1149],{"class":841,"line":1145},20,[839,1147,1148],{"class":845},"            try",[839,1150,1151],{"class":849},":\n",[839,1153,1155,1158],{"class":841,"line":1154},21,[839,1156,1157],{"class":845},"                return",[839,1159,1160],{"class":849}," datetime.strptime(value.strip(), fmt).isoformat()\n",[839,1162,1164,1167,1170],{"class":841,"line":1163},22,[839,1165,1166],{"class":845},"            except",[839,1168,1169],{"class":899}," ValueError",[839,1171,1151],{"class":849},[839,1173,1175],{"class":841,"line":1174},23,[839,1176,1177],{"class":845},"                continue\n",[839,1179,1181,1184,1187],{"class":841,"line":1180},24,[839,1182,1183],{"class":845},"    return",[839,1185,1186],{"class":849}," None  ",[839,1188,1189],{"class":909},"# Manual review\n",[11,1191,1192,1193,1196],{},"We had a version of this for ",[18,1194,1195],{},"every"," data cleaning task. Phone numbers, currency strings, inconsistent address formats, messy categorical variables spelled five different ways. Each one got its own script with its own set of patterns. We'd run them in a pipeline, flag the unparsable rows, and finish in minutes what used to take a full day of copy-pasting in Excel.",[47,1198,1200],{"id":1199},"the-excel-formula-injection-trick","The Excel Formula Injection Trick",[11,1202,1203],{},"This one I'm actually proud of.",[11,1205,1206,1207,1210],{},"A lot of the people we were delivering reports to? They lived in Excel. They wanted to open a file and see everything: the numbers, the graphs, the conditional formatting. They didn't want to run a script. They didn't want to connect to a database. They wanted a ",[23,1208,1209],{},".xlsx"," file they could double-click and immediately start reading graphs.",[11,1212,1213,1214,1217,1218,1221],{},"So we gave them that. But here's the thing — we cleaned the data in Python, ran the manipulation in Python, and then... we used Python to ",[40,1215,1216],{},"write Excel formulas into specific cells"," and ",[40,1219,1220],{},"inject chart definitions"," so everything auto-populated on open.",[11,1223,1224],{},"The workflow:",[321,1226,1227,1233,1239,1253,1269],{},[232,1228,1229,1232],{},[40,1230,1231],{},"Clean"," the raw data in Python (regex pipeline).",[232,1234,1235,1238],{},[40,1236,1237],{},"Transform"," it into the exact shape our \"cheat sheet\" required — specific column names in specific positions.",[232,1240,1241,1244,1245,1248,1249,1252],{},[40,1242,1243],{},"Write"," it to an Excel file using ",[23,1246,1247],{},"openpyxl",", but ",[18,1250,1251],{},"also"," inject formulas into summary cells.",[232,1254,1255,1258,1259,892,1262,892,1265,1268],{},[40,1256,1257],{},"Add"," Excel-native functions into specific cells — things like ",[23,1260,1261],{},"=SUMIFS()",[23,1263,1264],{},"=XLOOKUP()",[23,1266,1267],{},"=UNIQUE()"," — so the Excel power users could trace the numbers themselves.",[232,1270,1271,1274],{},[40,1272,1273],{},"Embed"," chart definitions with exact data ranges so the graphs auto-populated when the formulas recalculated on file open.",[809,1276,1278],{"className":833,"code":1277,"language":835,"meta":383,"style":383},"from openpyxl import Workbook\nfrom openpyxl.chart import BarChart, Reference\nfrom openpyxl.utils import get_column_letter\n\nwb = Workbook()\nws = wb.active\n\n# Write cleaned data\nfor row in cleaned_data:\n    ws.append(row)\n\n# Inject formulas in summary cells\nws['H1'] = 'Total (Formula)'\nfor i in range(2, len(cleaned_data) + 2):\n    ws[f'H{i}'] = f'=SUM(B{i}:G{i})'\n\n# Add an XLOOKUP lookup table so Excel natives can follow the logic\nws['J1'] = 'Category'\nws['K1'] = 'Lookup Value'\nfor idx, cat in enumerate(categories, start=2):\n    ws[f'J{idx}'] = cat\n    ws[f'K{idx}'] = f'=XLOOKUP(J{idx}, A:A, H:H)'\n\n# Create chart\nchart = BarChart()\nchart.title = \"Auto-Generated Report\"\ndata = Reference(ws, min_col=2, max_col=7, min_row=1, max_row=len(cleaned_data))\ncats = Reference(ws, min_col=1, min_row=2, max_row=len(cleaned_data))\nchart.add_data(data, titles_from_data=True)\nchart.set_categories(cats)\nws.add_chart(chart, \"M1\")\n\nwb.save(\"report.xlsx\")\n",[23,1279,1280,1292,1304,1316,1320,1330,1340,1344,1349,1362,1367,1371,1376,1392,1427,1477,1481,1486,1500,1514,1535,1560,1595,1599,1604,1615,1626,1667,1695,1706,1712,1724,1729],{"__ignoreMap":383},[839,1281,1282,1284,1287,1289],{"class":841,"line":842},[839,1283,855],{"class":845},[839,1285,1286],{"class":849}," openpyxl ",[839,1288,846],{"class":845},[839,1290,1291],{"class":849}," Workbook\n",[839,1293,1294,1296,1299,1301],{"class":841,"line":384},[839,1295,855],{"class":845},[839,1297,1298],{"class":849}," openpyxl.chart ",[839,1300,846],{"class":845},[839,1302,1303],{"class":849}," BarChart, Reference\n",[839,1305,1306,1308,1311,1313],{"class":841,"line":390},[839,1307,855],{"class":845},[839,1309,1310],{"class":849}," openpyxl.utils ",[839,1312,846],{"class":845},[839,1314,1315],{"class":849}," get_column_letter\n",[839,1317,1318],{"class":841,"line":871},[839,1319,868],{"emptyLinePlaceholder":401},[839,1321,1322,1325,1327],{"class":841,"line":883},[839,1323,1324],{"class":849},"wb ",[839,1326,877],{"class":845},[839,1328,1329],{"class":849}," Workbook()\n",[839,1331,1332,1335,1337],{"class":841,"line":913},[839,1333,1334],{"class":849},"ws ",[839,1336,877],{"class":845},[839,1338,1339],{"class":849}," wb.active\n",[839,1341,1342],{"class":841,"line":936},[839,1343,868],{"emptyLinePlaceholder":401},[839,1345,1346],{"class":841,"line":958},[839,1347,1348],{"class":909},"# Write cleaned data\n",[839,1350,1351,1354,1357,1359],{"class":841,"line":985},[839,1352,1353],{"class":845},"for",[839,1355,1356],{"class":849}," row ",[839,1358,1119],{"class":845},[839,1360,1361],{"class":849}," cleaned_data:\n",[839,1363,1364],{"class":841,"line":1013},[839,1365,1366],{"class":849},"    ws.append(row)\n",[839,1368,1369],{"class":841,"line":1036},[839,1370,868],{"emptyLinePlaceholder":401},[839,1372,1373],{"class":841,"line":1059},[839,1374,1375],{"class":909},"# Inject formulas in summary cells\n",[839,1377,1378,1381,1384,1387,1389],{"class":841,"line":1081},[839,1379,1380],{"class":849},"ws[",[839,1382,1383],{"class":895},"'H1'",[839,1385,1386],{"class":849},"] ",[839,1388,877],{"class":845},[839,1390,1391],{"class":895}," 'Total (Formula)'\n",[839,1393,1394,1396,1399,1401,1404,1407,1410,1412,1415,1418,1421,1424],{"class":841,"line":1087},[839,1395,1353],{"class":845},[839,1397,1398],{"class":849}," i ",[839,1400,1119],{"class":845},[839,1402,1403],{"class":899}," range",[839,1405,1406],{"class":849},"(",[839,1408,1409],{"class":899},"2",[839,1411,892],{"class":849},[839,1413,1414],{"class":899},"len",[839,1416,1417],{"class":849},"(cleaned_data) ",[839,1419,1420],{"class":845},"+",[839,1422,1423],{"class":899}," 2",[839,1425,1426],{"class":849},"):\n",[839,1428,1429,1432,1435,1438,1441,1444,1447,1449,1451,1453,1456,1459,1461,1463,1465,1468,1470,1472,1474],{"class":841,"line":1092},[839,1430,1431],{"class":849},"    ws[",[839,1433,1434],{"class":845},"f",[839,1436,1437],{"class":895},"'H",[839,1439,1440],{"class":899},"{",[839,1442,1443],{"class":849},"i",[839,1445,1446],{"class":899},"}",[839,1448,903],{"class":895},[839,1450,1386],{"class":849},[839,1452,877],{"class":845},[839,1454,1455],{"class":845}," f",[839,1457,1458],{"class":895},"'=SUM(B",[839,1460,1440],{"class":899},[839,1462,1443],{"class":849},[839,1464,1446],{"class":899},[839,1466,1467],{"class":895},":G",[839,1469,1440],{"class":899},[839,1471,1443],{"class":849},[839,1473,1446],{"class":899},[839,1475,1476],{"class":895},")'\n",[839,1478,1479],{"class":841,"line":1104},[839,1480,868],{"emptyLinePlaceholder":401},[839,1482,1483],{"class":841,"line":1110},[839,1484,1485],{"class":909},"# Add an XLOOKUP lookup table so Excel natives can follow the logic\n",[839,1487,1488,1490,1493,1495,1497],{"class":841,"line":1125},[839,1489,1380],{"class":849},[839,1491,1492],{"class":895},"'J1'",[839,1494,1386],{"class":849},[839,1496,877],{"class":845},[839,1498,1499],{"class":895}," 'Category'\n",[839,1501,1502,1504,1507,1509,1511],{"class":841,"line":1136},[839,1503,1380],{"class":849},[839,1505,1506],{"class":895},"'K1'",[839,1508,1386],{"class":849},[839,1510,877],{"class":845},[839,1512,1513],{"class":895}," 'Lookup Value'\n",[839,1515,1516,1518,1521,1523,1526,1529,1531,1533],{"class":841,"line":1145},[839,1517,1353],{"class":845},[839,1519,1520],{"class":849}," idx, cat ",[839,1522,1119],{"class":845},[839,1524,1525],{"class":899}," enumerate",[839,1527,1528],{"class":849},"(categories, start",[839,1530,877],{"class":845},[839,1532,1409],{"class":899},[839,1534,1426],{"class":849},[839,1536,1537,1539,1541,1544,1546,1549,1551,1553,1555,1557],{"class":841,"line":1154},[839,1538,1431],{"class":849},[839,1540,1434],{"class":845},[839,1542,1543],{"class":895},"'J",[839,1545,1440],{"class":899},[839,1547,1548],{"class":849},"idx",[839,1550,1446],{"class":899},[839,1552,903],{"class":895},[839,1554,1386],{"class":849},[839,1556,877],{"class":845},[839,1558,1559],{"class":849}," cat\n",[839,1561,1562,1564,1566,1569,1571,1573,1575,1577,1579,1581,1583,1586,1588,1590,1592],{"class":841,"line":1163},[839,1563,1431],{"class":849},[839,1565,1434],{"class":845},[839,1567,1568],{"class":895},"'K",[839,1570,1440],{"class":899},[839,1572,1548],{"class":849},[839,1574,1446],{"class":899},[839,1576,903],{"class":895},[839,1578,1386],{"class":849},[839,1580,877],{"class":845},[839,1582,1455],{"class":845},[839,1584,1585],{"class":895},"'=XLOOKUP(J",[839,1587,1440],{"class":899},[839,1589,1548],{"class":849},[839,1591,1446],{"class":899},[839,1593,1594],{"class":895},", A:A, H:H)'\n",[839,1596,1597],{"class":841,"line":1174},[839,1598,868],{"emptyLinePlaceholder":401},[839,1600,1601],{"class":841,"line":1180},[839,1602,1603],{"class":909},"# Create chart\n",[839,1605,1607,1610,1612],{"class":841,"line":1606},25,[839,1608,1609],{"class":849},"chart ",[839,1611,877],{"class":845},[839,1613,1614],{"class":849}," BarChart()\n",[839,1616,1618,1621,1623],{"class":841,"line":1617},26,[839,1619,1620],{"class":849},"chart.title ",[839,1622,877],{"class":845},[839,1624,1625],{"class":895}," \"Auto-Generated Report\"\n",[839,1627,1629,1632,1634,1637,1639,1641,1644,1646,1649,1652,1654,1657,1660,1662,1664],{"class":841,"line":1628},27,[839,1630,1631],{"class":849},"data ",[839,1633,877],{"class":845},[839,1635,1636],{"class":849}," Reference(ws, min_col",[839,1638,877],{"class":845},[839,1640,1409],{"class":899},[839,1642,1643],{"class":849},", max_col",[839,1645,877],{"class":845},[839,1647,1648],{"class":899},"7",[839,1650,1651],{"class":849},", min_row",[839,1653,877],{"class":845},[839,1655,1656],{"class":899},"1",[839,1658,1659],{"class":849},", max_row",[839,1661,877],{"class":845},[839,1663,1414],{"class":899},[839,1665,1666],{"class":849},"(cleaned_data))\n",[839,1668,1670,1673,1675,1677,1679,1681,1683,1685,1687,1689,1691,1693],{"class":841,"line":1669},28,[839,1671,1672],{"class":849},"cats ",[839,1674,877],{"class":845},[839,1676,1636],{"class":849},[839,1678,877],{"class":845},[839,1680,1656],{"class":899},[839,1682,1651],{"class":849},[839,1684,877],{"class":845},[839,1686,1409],{"class":899},[839,1688,1659],{"class":849},[839,1690,877],{"class":845},[839,1692,1414],{"class":899},[839,1694,1666],{"class":849},[839,1696,1698,1701,1703],{"class":841,"line":1697},29,[839,1699,1700],{"class":849},"chart.add_data(data, titles_from_data",[839,1702,877],{"class":845},[839,1704,1705],{"class":849},"True)\n",[839,1707,1709],{"class":841,"line":1708},30,[839,1710,1711],{"class":849},"chart.set_categories(cats)\n",[839,1713,1715,1718,1721],{"class":841,"line":1714},31,[839,1716,1717],{"class":849},"ws.add_chart(chart, ",[839,1719,1720],{"class":895},"\"M1\"",[839,1722,1723],{"class":849},")\n",[839,1725,1727],{"class":841,"line":1726},32,[839,1728,868],{"emptyLinePlaceholder":401},[839,1730,1732,1735,1738],{"class":841,"line":1731},33,[839,1733,1734],{"class":849},"wb.save(",[839,1736,1737],{"class":895},"\"report.xlsx\"",[839,1739,1723],{"class":849},[11,1741,1742,1743,1746,1747],{},"The Excel fans opened the file, saw the numbers, saw the graphs, saw the formulas. They nodded approvingly. They understood how the numbers connected because they could click on a cell and trace the formula. We got to automate the grunt work ",[18,1744,1745],{},"and"," make it transparent. ",[40,1748,1749],{},"Best of both worlds.",[11,1751,1752],{},[40,1753,1754],{},"We were cracked.",[47,1756,1758],{"id":1757},"the-collection","The Collection",[11,1760,1761],{},"Eventually we built up a whole arsenal. Python scripts for specific data cleaning tasks. R scripts for statistical analysis with pre-written ggplot2 templates. Excel templates with premade pivot tables. PowerShell scripts for file organisation.",[11,1763,1764],{},"Every script had a name. Every script had a README (eventually, after the third time we forgot what something did). Every script was designed to be run with one command and produce a consistent output.",[11,1766,1767,1768,1771,1772,1774],{},"We stopped writing code for specific tasks and started writing code that ",[40,1769,1770],{},"generated"," the deliverable. The difference is subtle but massive: instead of \"clean this CSV\" you write \"clean all CSVs that look like this.\" The first approach solves today's problem. The second approach solves today's ",[18,1773,1745],{}," next week's.",[11,1776,1777,1778,1783],{},"No wonder ",[264,1779,1782],{"href":1780,"rel":1781},"https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fbrian-m-mampane-29672121b",[268],"Brian"," and I keep getting asked to automate things for people.",[47,1785,1787],{"id":1786},"the-real-payoff","The Real Payoff",[11,1789,1790],{},"It's not the time. I mean, it is — saving hours or days per week adds up. But the real payoff is something else.",[11,1792,1793,1794,1797],{},"When you automate something, you ",[18,1795,1796],{},"understand"," it. You've decomposed the task into its actual components. You've figured out which parts are patterns and which parts are exceptions. You've built a model of the work in your head, and then you've encoded that model into code.",[11,1799,1800],{},"The people who say \"it's faster to just do it manually\" are missing the point. The automation isn't just about saving time on this one task. It's about building a reusable understanding. It's about learning to see the patterns. It's about being the person who looks at a messy process and thinks \"there's a better way to do this.\"",[11,1802,1803,1804,26],{},"That skill — the ability to see the pattern, abstract it, and encode it — that's what actually scales. Not the scripts themselves. ",[18,1805,1806],{},"The method",[11,1808,1809],{},"And yeah, also the time. Having time to run errands during work hours because you've already delivered is pretty great too.",[366,1811],{},[11,1813,1814],{},[40,1815,1816],{},"It's not being lazy. It's called productivity maxxing.",[11,1818,1819],{},[18,1820,1821],{},"If you've got something repetitive eating your week and you can't figure out how to automate it — hit me up. We're always free. For the right price.",[1823,1824,1825],"style",{},"html pre.shiki code .sq0yK, html code.shiki .sq0yK{--shiki-default:#A0A0A0}html pre.shiki code .sU-n2, html code.shiki .sU-n2{--shiki-default:#FFF}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .ss8vJ, html code.shiki .ss8vJ{--shiki-default:#8B8B8B94}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":383,"searchDepth":384,"depth":384,"links":1827},[1828,1829,1830,1831],{"id":797,"depth":384,"text":798},{"id":1199,"depth":384,"text":1200},{"id":1757,"depth":384,"text":1758},{"id":1786,"depth":384,"text":1787},"2026-06-19","The specific scripts behind THE METHOD — regex date cleaning, Excel formula injection, and an arsenal that finished 10-day sprints on day 1.",{},{"title":784,"description":1833},"blog\u002Fproductive-laziness-toolkit",[779,835],"Regex pipelines that normalise messy dates, Excel formula injection that makes reports auto-populate, and the complete automation arsenal. The technical playbook behind finishing 10-day sprints on day 1.","HaYrI7nvclpnjNSG6X7S56Z0DzMSjIIm45_4ZK8wkgo",{"id":1841,"title":1842,"body":1843,"date":1832,"description":1992,"extension":398,"featured":399,"meta":1993,"navigation":401,"part":842,"path":755,"seo":1994,"series":756,"stem":1995,"tags":1996,"tldr":1997,"__hash__":1998},"blog\u002Fblog\u002Fproductive-laziness.md","Automate Everything, Deliver Early, Run Errands",{"type":8,"value":1844,"toc":1987},[1845,1848,1862,1875,1879,1882,1888,1899,1902,1906,1917,1920,1926,1932,1936,1939,1946,1950,1953,1960,1963,1976,1978],[11,1846,1847],{},"We were skill maxing from the get go.",[11,1849,1850,1851,1853,1854,1857,1858,1861],{},"During my time at ",[18,1852,561],{}," — let's call it that — we worked on a lot. I mean a ",[18,1855,1856],{},"lot",". Learned Python and R. The classic 6 Udemy courses enrolled, zero completed. ",[31,1859,1860],{},"Story of my life."," We were given work: analyse this, clean that, prepare this report, analysis of surplus, analyse model variance, help rewrite this function to be faster so we can generate IFRS17-compliant reports, we need a PowerBI dashboard, graphs, etc.",[11,1863,1864,1865,1868,1869,1872,1873,26],{},"The work was intense. But here's the thing: ",[264,1866,1782],{"href":1780,"rel":1867},[268]," learned ",[18,1870,1871],{},"a method",". And together we had ",[40,1874,756],{},[47,1876,1878],{"id":1877},"what-is-the-method","What Is The Method?",[11,1880,1881],{},"Simple. Automate the hell out of anything repetitive and save yourself days — weeks — of work. That's it. That's the whole thing.",[11,1883,1884,1885],{},"They used the typical Agile framework — these folks love their sprints. We'd get a 10-day deadline, submit on day 7, knowing we were basically done on day 1. And then what? We'd go run errands, hit the town, do whatever. ",[31,1886,1887],{},"We built a reputation by delivering early, by the way. Not as slackers.",[11,1889,1890,1891,1894,1895,1898],{},"People think automation is about replacing humans. No. Automation is about ",[40,1892,1893],{},"replacing the tedium",". The part of your job that makes you ask yourself \"So I have to keep doing this? Maybe I can use AI.\" — that's what you automate. The part where you actually think, solve problems, build things — ",[18,1896,1897],{},"that's"," what you keep.",[11,1900,1901],{},"Give us something repetitive, we'll automate it.",[47,1903,1905],{"id":1904},"playing-the-game","Playing the Game",[11,1907,1908,1909,1912,1913,1916],{},"Here's the thing about automating everything: you can't let ",[18,1910,1911],{},"them"," know you're automating everything. The hands that feed you need to see progress. They need to see ",[18,1914,1915],{},"you"," working.",[11,1918,1919],{},"So we got clever about it.",[11,1921,1922,1925],{},[40,1923,1924],{},"The Partial Deliverable Trick."," We'd set up our scripts to output intermediate results at each stage. Cleaned but untransformed data? Output. Transformed but not formatted? Output. Formatted but without the charts? Output. We had a whole pipeline of artifacts, and at any point we could screenshare and show \"look at all the progress we've made.\" They saw momentum. We saw a script running in the background while we were at lunch.",[11,1927,1928,1931],{},[40,1929,1930],{},"The Two-Device Meeting."," This one was cinema.",[304,1933],{"src":1934,"alt":1935,"width":308},"https:\u002F\u002Fi.kym-cdn.com\u002Fentries\u002Ficons\u002Foriginal\u002F000\u002F047\u002F133\u002Fcover1.jpg","Absolute Cinema",[11,1937,1938],{},"One of us would join the daily standup on their phone — from the car, from a coffee shop, from wherever. The other would be on the laptop, screensharing the latest output. The phone person would ask the \"dumb\" questions that bought us time. The laptop person would present the results and look competent. Meanwhile the phone person was driving to an errand, walking around town, whatever. They saw two engaged employees. We saw a coordinated heist.",[11,1940,1941,1942,1945],{},"This wasn't being dishonest. It was being ",[18,1943,1944],{},"strategic",". The outputs were real. The work was done. We just refused to pretend that staying at our desks for 8 hours was the same as being productive.",[47,1947,1949],{"id":1948},"so-whats-your-method","So What's Your Method?",[11,1951,1952],{},"I'm not telling you to quit your job and build an automation empire. I'm asking: what are you doing right now that's repetitive? That you do weekly, daily, hourly, that a script could do instead?",[11,1954,1955,1956,1959],{},"The thing most people get wrong is they think automation requires some big up-front investment. \"I'll write the script when I have time.\" But the scripts you write don't need to be perfect. They don't need to handle every edge case. They need to handle ",[18,1957,1958],{},"your"," case. Right now. For that one report you're dreading.",[11,1961,1962],{},"Here's my rule: if you've done the same manual task three times, you've already lost the time it would have taken to automate it. The fourth time is just punishing yourself.",[11,1964,1965,1966,1968,1969,1972,1973,1975],{},"Start small. One script. One pattern. One ",[23,1967,1247],{}," workbook. One ",[23,1970,1971],{},"re.compile()",". Build from there. Before you know it, you'll have a catalog of scripts and someone will be asking ",[18,1974,1915],{}," to automate things.",[366,1977],{},[11,1979,1980,276,1983,1986],{},[40,1981,1982],{},"Next up:",[264,1984,1985],{"href":760},"Part 2 — The Toolkit: Regex Pipelines, Excel Injection, and the Arsenal"," — the specific scripts, the regex date cleaning incident, the Excel formula injection trick, and how we built a whole arsenal of automation tools.",{"title":383,"searchDepth":384,"depth":384,"links":1988},[1989,1990,1991],{"id":1877,"depth":384,"text":1878},{"id":1904,"depth":384,"text":1905},{"id":1948,"depth":384,"text":1949},"10-day deadline. Done in 1. Two-device meetings, regex pipelines, Excel injection, and the art of looking busy while your script does the work. This is THE METHOD.",{},{"title":1842,"description":1992},"blog\u002Fproductive-laziness",[779,835],"Automate everything repetitive, look busy while your script does the work, and never let them know how fast you really are. The partial deliverable trick, the two-device meeting, and the art of productive laziness.","iD7iLwC2yXvuvbUSQbTGyKqUQtHoeVZUQnEoJXI1HwE",{"id":2000,"title":2001,"body":2002,"date":2253,"description":2254,"extension":398,"featured":399,"meta":2255,"navigation":401,"part":384,"path":2256,"seo":2257,"series":2011,"stem":2258,"tags":2259,"tldr":2260,"__hash__":2261},"blog\u002Fblog\u002Fknow-your-worth-part-2.md","How to Spot Bad Deals and Break the Cycle",{"type":8,"value":2003,"toc":2247},[2004,2013,2017,2020,2026,2032,2042,2048,2054,2060,2069,2073,2080,2091,2094,2108,2111,2125,2136,2139,2143,2146,2152,2158,2164,2170,2177,2181,2184,2187,2223,2230,2233,2235],[11,2005,2006],{},[18,2007,791,2008,2012],{},[264,2009,2011],{"href":2010},"\u002Fblog\u002Fknow-your-worth","Know Your Worth"," series. If you haven't read Part 1 on the desperation spiral and contract red flags, start there.",[47,2014,2016],{"id":2015},"how-to-spot-a-bad-deal","How to Spot a Bad Deal",[11,2018,2019],{},"I've been around enough to smell these from a mile away. Here's what to look for:",[11,2021,2022,2025],{},[40,2023,2024],{},"The salary is \"competitive.\""," This means \"we don't want to tell you what it is because you'll stop talking to us.\" Real companies quote numbers. Bullshit artists use adjectives.",[11,2027,2028,2031],{},[40,2029,2030],{},"They mention \"projections\" before you're profitable."," Projected revenue in a startup that hasn't found product-market fit is a lottery ticket.",[11,2033,2034,2037,2038,2041],{},[40,2035,2036],{},"They ask about your current salary."," Standard trap. They want to anchor negotiations to your ",[18,2039,2040],{},"previous"," bad deal, not the value of the role. Answer: \"I'd prefer to discuss what the role is worth.\"",[11,2043,2044,2047],{},[40,2045,2046],{},"The job description is a wishlist."," \"Looking for a rockstar who can go from 8-5pm (obviously they'll forget to add a boundry to the workdays), manage a team, build a CRM, analyse how much they got and wont pay you, and also make coffee.\" They want one person to do the work of three. You will burn out.",[11,2049,2050,2053],{},[40,2051,2052],{},"Interview process is chaotic."," If they're disorganised in the hiring stage, imagine what the day-to-day is like.",[11,2055,2056,2059],{},[40,2057,2058],{},"They rush you."," \"We need an answer by Friday.\" High-pressure tactics are a red flag. If the role is good, it'll be good next week.",[11,2061,2062,2065,2066],{},[40,2063,2064],{},"\"We're like a family.\""," No you're not. ",[18,2067,2068],{},"My family doesn't PIP aka Performance Improvement Plans when revenue is down.",[47,2070,2072],{"id":2071},"the-fix-is-stopping-being-desperate","The Fix Is Stopping Being Desperate",[11,2074,2075,2076,2079],{},"I know. \"Just stop being desperate\" is the ",[18,2077,2078],{},"most"," useless advice when your bank account is at zero. I get it.",[11,2081,2082,2083,2086,2087,2090],{},"But there's a difference between ",[18,2084,2085],{},"feeling"," desperate and ",[18,2088,2089],{},"acting"," desperate. And the acting part is what kills you.",[11,2092,2093],{},"Here's what acting desperate looks like:",[229,2095,2096,2099,2102,2105],{},[232,2097,2098],{},"Accepting the first offer without negotiating",[232,2100,2101],{},"Not asking about benefits, growth, or expectations",[232,2103,2104],{},"Signing anything put in front of you",[232,2106,2107],{},"Ignoring every red flag because you're scared the offer will vanish",[11,2109,2110],{},"Here's what it looks like to not act desperate even when you are:",[229,2112,2113,2116,2119,2122],{},[232,2114,2115],{},"\"I need a few days to review the offer\"",[232,2117,2118],{},"\"Can we discuss the salary band for this role?\"",[232,2120,2121],{},"\"What does career progression look like after the first year?\"",[232,2123,2124],{},"Walking away when the terms don't work",[11,2126,2127,2128,2131,2132,2135],{},"The paradox is that ",[40,2129,2130],{},"not acting desperate makes you more attractive as a candidate",". Recruiters ",[18,2133,2134],{},"want"," someone who seems like they have options. It signals competence. It signals that other people value you.",[11,2137,2138],{},"When you crawl for the job — begging, pleading, accepting anything — you're telling them \"I have no other options.\" And that's exactly the candidate who gets the lowball.",[47,2140,2142],{"id":2141},"the-ripple-effect","The Ripple Effect",[11,2144,2145],{},"This is the part that keeps me up.",[11,2147,2148,2149,26],{},"Every time a graduate accepts an exploitative offer — unpaid internship, ludicrously below-market salary, \"exposure\" instead of payment — they're not just hurting themselves. They're creating a data point that says ",[18,2150,2151],{},"this is acceptable",[11,2153,2154,2155,2157],{},"The company goes to market research next quarter and says \"well, we filled the role at BWP5000 last time, so the band is BWP5-6000.\" And the next candidate, who might have commanded BWP10,000 somewhere else, now has to fight uphill against ",[18,2156,1958],{}," acceptance.",[11,2159,2160,2161,26],{},"You're not competing against other candidates. You're competing against the ",[18,2162,2163],{},"ghost of the last person who settled",[11,2165,2166,2167,26],{},"The only way to break the cycle is to stop accepting deals that shouldn't exist. This means more candidates need to say no. It means more candidates need to walk. It means more graduates need to realise that ",[18,2168,2169],{},"not every job is better than no job",[11,2171,2172,2173,2176],{},"Taking a shitty job because you're desperate does not fix your desperation. It just moves the timeline. Now you're six months in, miserable, underpaid, and ",[18,2174,2175],{},"still"," looking. But now you have less energy to interview, less confidence in your skills, and a gap on your CV that you'll have to explain.",[47,2178,2180],{"id":2179},"so-what-do-you-do","So What Do You Do?",[11,2182,2183],{},"I don't have a perfect answer. The market is genuinely rough right now. Jobs are scarce. The system is designed to exploit your uncertainty.",[11,2185,2186],{},"But here's what I know:",[321,2188,2189,2195,2201,2211,2217],{},[232,2190,2191,2194],{},[40,2192,2193],{},"Know the market rate."," Talk to people. Check levels. FYI, ask people, you claimed you've been networking ask your network. Walk in with a number.",[232,2196,2197,2200],{},[40,2198,2199],{},"Negotiate everything."," The worst they can say is no, and you're already considering the offer as-is. There's no downside.",[232,2202,2203,2206,2207,2210],{},[40,2204,2205],{},"Read the contract."," Not as a legal document. Read it as a ",[18,2208,2209],{},"vibe check",". If you see clauses that feel predatory, ask about them.",[232,2212,2213,2216],{},[40,2214,2215],{},"Talk to someone who's been in the industry."," Your parents don't know what you should make in this market. Find someone who does.",[232,2218,2219,2222],{},[40,2220,2221],{},"Say no."," It's terrifying. Do it anyway. The offer you're afraid to lose is often the offer you should have walked from.",[11,2224,2225,2226,2229],{},"And remember: every time you accept a bad deal, you're not just setting your own floor. You're setting ",[18,2227,2228],{},"everyone else's"," ceiling.",[11,2231,2232],{},"The next graduate who comes after you deserved better.",[366,2234],{},[11,2236,2237,2240,2241,276,2244],{},[40,2238,2239],{},"PS:"," If you're a graduate reading this and you've already accepted a deal you're not sure about — you're not stuck. Contracts can be renegotiated. Jobs can be quit. ",[18,2242,2243],{},"Bad decisions can be unfucked.",[40,2245,2246],{},"The only irreversible mistake is believing you have no choice.",{"title":383,"searchDepth":384,"depth":384,"links":2248},[2249,2250,2251,2252],{"id":2015,"depth":384,"text":2016},{"id":2071,"depth":384,"text":2072},{"id":2141,"depth":384,"text":2142},{"id":2179,"depth":384,"text":2180},"2026-06-18","How to spot a bad deal from a mile away, why not acting desperate makes you more attractive, and the ripple effect of every graduate who settled.",{},"\u002Fblog\u002Fknow-your-worth-part-2",{"title":2001,"description":2254},"blog\u002Fknow-your-worth-part-2",[641],"How to spot predatory offers, why saying no makes you more attractive, and the ripple effect of settling. Every bad deal you accept becomes the ceiling for the next grad.","z9WJ-m-mjLS-4IS5BxjAl3H_K6UUxkLGpmHwQL23slg",{"id":2263,"title":2264,"body":2265,"date":2253,"description":2511,"extension":398,"featured":399,"meta":2512,"navigation":401,"part":842,"path":2010,"seo":2513,"series":2011,"stem":2514,"tags":2515,"tldr":2516,"__hash__":2517},"blog\u002Fblog\u002Fknow-your-worth.md","Your Desperation Is the Reason the Next Guy Gets a Shitty Offer",{"type":8,"value":2266,"toc":2506},[2267,2270,2277,2284,2290,2294,2305,2315,2318,2321,2335,2341,2344,2348,2351,2356,2366,2380,2391,2401,2407,2411,2414,2417,2456,2467,2470,2473,2484,2490,2493,2496,2498],[11,2268,2269],{},"You know that friend of yours who graduated top of their class but is currently \"taking a break\" because the job market is cooked? Yeah. We all have one.",[11,2271,2272,2273,2276],{},"Or worse — you ",[18,2274,2275],{},"are"," that friend.",[11,2278,2279,2280,2283],{},"I've been watching this cycle play out in real time. Every graduation season, a fresh batch of high energy, optimistic broke graduates spills into the market, and a certain type of recruiter sharpens their fangs. It's not malice — it's ",[18,2281,2282],{},"opportunity",". Desperate people take bad deals. And the market knows this.",[11,2285,2286,2287],{},"Here's the thing nobody tells you: ",[40,2288,2289],{},"your desperation is the reason the next guy gets a shitty offer.",[47,2291,2293],{"id":2292},"the-desperation-spiral","The Desperation Spiral",[11,2295,2296,2297,2300,2301,2304],{},"You've sent out 200 applications. You've had 17 interviews. You've gotten 3 rejections and 14 \"we'll keep your CV on file\" (which is corporate for ",[18,2298,2299],{},"\"Hard luck chief, already forgot your name\"","). Your savings are running out. Your parents are making those ",[18,2302,2303],{},"concerned"," sounds whenever you walk past the living room.",[11,2306,2307,2308,2311,2312,26],{},"Then an offer comes. It's not great. The salary is mediocre, the benefits are thin, and the job description had so many red flags you could call it ",[40,2309,2310],{},"The Chinese Communist Party Parade",". But it's ",[18,2313,2314],{},"an offer",[11,2316,2317],{},"So you take it.",[11,2319,2320],{},"And now the recruiter has data point: \"Graduate accepted offer at #### with a 2 week notice clause. We can go lower next time.\"",[11,2322,2323,2326,2327,2330,2331,2334],{},[40,2324,2325],{},"That's the mechanism."," Every bad deal that gets accepted becomes the baseline for the next negotiation. Companies don't pull salary bands out of thin air — they pull them from what ",[18,2328,2329],{},"people actually agree to",". ",[40,2332,2333],{},"High supply of the desperate pushing prices lower and lower",". If you take lets say BWP5,000 for a job that should pay BWP10,000, you just told every recruiter in the industry that P5,000 is acceptable for that role.",[11,2336,2337,2338,2340],{},"The next grad who comes along doesn't get offered BWP10000. They get offered BWP5,400 — a 8% bump from ",[18,2339,1958],{}," desperation, not a market rate.",[11,2342,2343],{},"You didn't just screw yourself. You screwed the pipeline.",[47,2345,2347],{"id":2346},"who-decides-your-worth","Who Decides Your Worth?",[11,2349,2350],{},"There's this beautiful fiction we tell ourselves about the free market: that your salary is determined by your skill, your output, and your value. That compensation is a meritocratic reflection of how good you are at your job.",[11,2352,2353],{},[31,2354,2355],{},"Lmao.",[11,2357,2358,2359,276,2362,2365],{},"Your salary is determined by ",[31,2360,2361],{},"four",[40,2363,2364],{},"three things",":",[321,2367,2368,2371,2374],{},[232,2369,2370],{},"How badly they need someone",[232,2372,2373],{},"How badly you need the job",[232,2375,2376,2377],{},"Whether you know how to say no\n",[31,2378,2379],{},"4. How much they got in the bank lowkey",[11,2381,2382,2383,2386,2387,2390],{},"That's it. That's the whole formula. Skill matters only at the threshold — you either ",[18,2384,2385],{},"can"," do the job or you ",[18,2388,2389],{},"can't",". The minute you clear that bar, the negotiation becomes a psychological game, not a technical one.",[11,2392,2393,2396,2397,2400],{},[18,2394,2395],{},"Recruiters are not in the business of paying you what you think you're worth."," They're in the business of paying you the ",[18,2398,2399],{},"least they can get away with",". This is not a value judgment — it's their job description. The same way your job is to write code or analyse data or design systems, their job is to minimise labour cost while filling the seat.",[11,2402,2403,2404],{},"If you walk in thinking \"they'll recognise my talent and pay me fairly,\" you are the mark in a game you didn't know you were playing. ",[18,2405,2406],{},"You're the new coworker — everyone's coming at you with the wrong intentions.",[47,2408,2410],{"id":2409},"the-contract-tells-you-everything","The Contract Tells You Everything",[11,2412,2413],{},"Let's talk about the documents nobody reads.",[11,2415,2416],{},"I've seen graduate contracts that include:",[229,2418,2419,2428,2438,2447],{},[232,2420,2421,2424,2425,2427],{},[40,2422,2423],{},"\"Probation period\""," that functionally means you can be fired for any reason, but ",[18,2426,1915],{}," have to give 4 weeks notice. This is not reciprocal.",[232,2429,2430,2433,2434,2437],{},[40,2431,2432],{},"Non-compete agreements"," on a BWP5000 role. ",[40,2435,2436],{},"Laughable and almost certainly unenforceable"," in most jurisdictions for a junior employee, but it looks scary on paper.",[232,2439,2440,2443,2444],{},[40,2441,2442],{},"Performance based pay"," that lets them pay you that extra if they feel like it or to string you along just a bit longer. ",[18,2445,2446],{},"Remember young desperate graduates have the energy and love doing the most. Thats why Government Workers complain about them",[232,2448,2449,2452,2453],{},[40,2450,2451],{},"\"Anything and Everything Duties and Responsibilities\""," — the catch-all clause that lets them pile on whatever work they want outside your actual job description. This is the contract equivalent of ",[18,2454,2455],{},"\"let them eat cake.\"",[11,2457,2458,2459,2462,2463,2466],{},"Marie Antoinette never actually said ",[18,2460,2461],{},"\"Qu'ils mangent de la brioche\""," — the line was propaganda, weaponised by pamphleteers to paint her as a detached, frivolous monarch who thought the peasants could just eat luxury bread when they couldn't afford the regular stuff. Whether she said it or not, the ",[18,2464,2465],{},"structure"," of the response is what matters: a person in power, completely insulated from reality, offering a fantasy solution to a genuine problem.",[11,2468,2469],{},"That's exactly what the vague \"any other duties\" clause is. It sounds reasonable — flexibility! adaptability! team player! — but in practice it means they can shift your entire job description whenever it suits them. Hired as a data analyst? Congratulations, you're now also the IT help desk, the social media manager, the meeting note-taker, and the person who has to explain why the printer isn't working. For the same salary.",[11,2471,2472],{},"The young graduate is the starving populace. Desperate for a job, desperate to prove they're a \"team player,\" desperate enough to accept that the vague catch-all in paragraph 14 probably won't be a problem. And the company? It's Marie Antoinette — completely out of touch with what that clause actually costs the person who signs it.",[11,2474,2475,2476,2479,2480,2483],{},"All of this is theatre. The contract is a ",[18,2477,2478],{},"script",", not a binding prediction of reality. Companies put things in contracts not because they'll enforce them, but because they want you to ",[18,2481,2482],{},"believe"," they will.",[11,2485,2486,2487],{},"Here's the part that matters: ",[40,2488,2489],{},"if a clause is not legally enforceable, it doesn't matter if you signed it.",[11,2491,2492],{},"Non-competes for junior? Struck down in court daily. \"Unlimited leave\" policies that ghost your requests? Constructive dismissal waiting to happen. \"Discretionary bonuses\" that never materialise? That's just theft with extra paperwork.",[11,2494,2495],{},"Just because it's in the contract doesn't mean it's real. The law is the actual contract. Everything else is a suggestion.",[366,2497],{},[11,2499,2500,276,2502,2505],{},[40,2501,1982],{},[264,2503,2504],{"href":2256},"Part 2 — How to Spot Bad Deals and Break the Cycle"," — how to spot predatory offers, why not acting desperate makes you more attractive, and the ripple effect that keeps me up at night.",{"title":383,"searchDepth":384,"depth":384,"links":2507},[2508,2509,2510],{"id":2292,"depth":384,"text":2293},{"id":2346,"depth":384,"text":2347},{"id":2409,"depth":384,"text":2410},"Young graduates are desperate, recruiters are predatory, and the reason the next guy gets a shitty offer is because you took the first one that came along.",{},{"title":2264,"description":2511},"blog\u002Fknow-your-worth",[641],"Every bad deal you accept becomes the baseline for the next grad. How desperation drives down wages, the contract clauses that screw you, and why your salary has nothing to do with your skill.","d1_Sz8_48yVlZM_wDh1R1UoxLMTun5iVVQy36SEH51E",{"id":2519,"title":2520,"body":2521,"date":2758,"description":2759,"extension":398,"featured":399,"meta":2760,"navigation":401,"part":402,"path":2761,"seo":2762,"series":402,"stem":2763,"tags":2764,"tldr":402,"__hash__":2765},"blog\u002Fblog\u002Ftheyre-tracking-what-we-do.md","They're Tracking What We Do",{"type":8,"value":2522,"toc":2752},[2523,2538,2544,2551,2555,2558,2575,2578,2595,2601,2608,2611,2614,2631,2642,2645,2649,2658,2671,2674,2681,2684,2688,2695,2698,2730,2733,2736,2742,2744,2749],[11,2524,2525,2526,2529,2530,2533,2534,2537],{},"Ever noticed that ",[18,2527,2528],{},"Tiktok"," is tracking you? You search for something and then all of a sudden every reel on you ",[18,2531,2532],{},"FYP",", every recommendation, every ",[18,2535,2536],{},"thought"," and ad becomes too relatable or suited to you.",[11,2539,2540,2541],{},"It's not paranoia. ",[40,2542,2543],{},"They are watching.",[11,2545,2546,2547,2550],{},"But here's the thing: there's watching, and then there's ",[18,2548,2549],{},"watching",". And the difference matters more than the privacy blogs want you to think.",[47,2552,2554],{"id":2553},"what-tracking-actually-is","What \"tracking\" actually is",[11,2556,2557],{},"When I say I have analytics on this site, I don't mean I know your name, your IP, your shoe size, or that you searched Elon Musk becoming a trillionaire. I mean I know:",[229,2559,2560,2563,2566,2569,2572],{},[232,2561,2562],{},"How many people visited a page",[232,2564,2565],{},"What browser they used (roughly)",[232,2567,2568],{},"What link they clicked to get here",[232,2570,2571],{},"Whether they found what they were looking for (search queries on the blog)",[232,2573,2574],{},"The rest i will leave up to you to figure out",[11,2576,2577],{},"That's it. No cookies. No fingerprinting. No profiles.",[11,2579,2580,2587,2588,2591,2592],{},[40,2581,2582],{},[264,2583,2586],{"href":2584,"rel":2585},"https:\u002F\u002Fumami.is",[268],"Umami"," — the tool I use — doesn't even collect enough data to require a ",[18,2589,2590],{},"cookie banner"," . It's lightweight, open source, and runs on my own domain so it ",[18,2593,2594],{},"doesn't get blocked by adblockers so you cant dodge it.",[47,2596,2598],{"id":2597},"the-hypocrisy",[31,2599,2600],{},"The hypocrisy",[11,2602,2603,2604,2607],{},"Okay cool, ",[18,2605,2606],{},"the"," hypocrisy.",[11,2609,2610],{},"I care about privacy. I use Firefox. I run uBlock Origin. I've told people to stop using Google Chrome, I got a friend that just uses Brave Browser. I am, in every sense, someone who loves privacy.",[11,2612,2613],{},"And I also put tracking software on my website.",[11,2615,2616,2619,2620,2622,2623,2626,2627,2630],{},[31,2617,2618],{},"At the time I had no defence"," Here's my defence: the data isn't for ",[18,2621,1911],{},", it's for ",[18,2624,2625],{},"me",". I don't sell it, I don't share it, I don't even look at it that ",[18,2628,2629],{},"often",". But when I do, it tells me useful things:",[229,2632,2633,2636,2639],{},[232,2634,2635],{},"\"Nobody is reading this post\" → either the title doesn't grab or the content misses",[232,2637,2638],{},"\"Everyone bounces on this page\" → something is broken or confusing",[232,2640,2641],{},"\"People keep searching for X\" → X is something I should write about",[11,2643,2644],{},"Without analytics, I'm flying blind. I could write 50 posts and have no idea if any of them land. That's not humility, that's negligence. I'm basically asking for your time and then not even checking if I'm wasting it.",[47,2646,2648],{"id":2647},"the-posthog-detour","The PostHog detour",[11,2650,2651,2652,2657],{},"I started with ",[264,2653,2656],{"href":2654,"rel":2655},"https:\u002F\u002Fposthog.com",[268],"PostHog",". It's powerful — session replays, feature flags, heatmaps, etc. If I were building a SaaS product, I'd use it in a heartbeat.",[11,2659,2660,2661,2664,2665,2330,2668],{},"But this isn't a SaaS product. It's my portfolio with a blog. And having session replays of people scrolling through my about page felt... invasive. ",[18,2662,2663],{},"I"," felt like the guy she gets the ick from when he says ",[18,2666,2667],{},"\"I watch anime\"",[40,2669,2670],{},"Btw I do watch anime.",[11,2672,2673],{},"So I ripped it out.",[11,2675,2676,2677,2680],{},"I replaced it with ",[264,2678,2586],{"href":2584,"rel":2679},[268],". One script, zero cookies, a dashboard that shows me exactly what I need to know and nothing else. It runs on my own domain so adblockers can stop me. It's open source. It costs nothing.",[11,2682,2683],{},"I dropped a 22 event tracking system down to 7 events being tracked and I felt better.",[47,2685,2687],{"id":2686},"this-is-the-part-where-i-admit-im-still-tracking-you","This is the part where I admit I'm still tracking you",[11,2689,2690,2691,2694],{},"I ",[18,2692,2693],{},"am",". Just... politely. I'm the manager at Doppio Zero that secretly comes to your table and tells you he's your waiter that afternoon, not the guy without a life.",[11,2696,2697],{},"Here's what I actually track on this site right now:",[229,2699,2700,2706,2712,2718,2724],{},[232,2701,2702,2705],{},[40,2703,2704],{},"What blog posts you read"," — so I know what to write more of",[232,2707,2708,2711],{},[40,2709,2710],{},"What you search for (in the blog search)"," — so I know what's missing",[232,2713,2714,2717],{},[40,2715,2716],{},"If you reach out"," — so I know the contact form works",[232,2719,2720,2723],{},[40,2721,2722],{},"If something breaks"," — so I can fix it",[232,2725,2726,2729],{},[40,2727,2728],{},"If you click a link to my GitHub or LinkedIn"," — so I know what's driving interest",[11,2731,2732],{},"That's the list. I could track more. I don't want to.",[11,2734,2735],{},"And honestly? If this makes you uncomfortable, you're probably already running an adblocker that blocks it anyway. Problem solved.",[2737,2738,2739],"blockquote",{},[11,2740,2741],{},"Off to touch grass.",[366,2743],{},[11,2745,2746],{},[40,2747,2748],{},"PS: I'm watching you.",[11,2750,2751],{},"(Just... from across the room... Respectfully... With a nice dashboard.)",{"title":383,"searchDepth":384,"depth":384,"links":2753},[2754,2755,2756,2757],{"id":2553,"depth":384,"text":2554},{"id":2597,"depth":384,"text":2600},{"id":2647,"depth":384,"text":2648},{"id":2686,"depth":384,"text":2687},"2026-06-16","I got analytics on my website. I also hate being tracked. It is what it is.",{},"\u002Fblog\u002Ftheyre-tracking-what-we-do",{"title":2520,"description":2759},"blog\u002Ftheyre-tracking-what-we-do",[],"6deVyLlTE50GJiFbTybEWy3GTJKRX0IJWVPAPKHTm0U",{"id":2767,"title":2768,"body":2769,"date":2873,"description":2874,"extension":398,"featured":399,"meta":2875,"navigation":401,"part":384,"path":2876,"seo":2877,"series":2778,"stem":2878,"tags":2879,"tldr":2882,"__hash__":2883},"blog\u002Fblog\u002Fpi-agent-part-2.md","How to Actually Use AI Agents (Stop Treating Them Like Magic Wands)",{"type":8,"value":2770,"toc":2868},[2771,2780,2784,2787,2790,2793,2796,2803,2808,2814,2825,2832,2835,2838,2842,2849,2852,2856,2865],[11,2772,2773],{},[18,2774,791,2775,2779],{},[264,2776,2778],{"href":2777},"\u002Fblog\u002Fpi-agent","AI Agents"," series. If you haven't read Part 1 on the real cost of AI coding tools and why you're overpaying, start there.",[47,2781,2783],{"id":2782},"the-real-problem-with-how-people-use-agents","The Real Problem With How People Use Agents",[11,2785,2786],{},"Most junior developers use agents. A lot. But there's a pattern I see over and over: they treat the agent like a magic wand.",[11,2788,2789],{},"\"Build me a website.\"",[11,2791,2792],{},"\"Make me a fullstack app.\"",[11,2794,2795],{},"\"Write a complete ...\"",[11,2797,2798,2799,2802],{},"One-shot prompts, no precise context, massive scope, long context windows. The agent spits out hundreds of lines. Half of it doesn't compile. A junior dev aka ",[18,2800,2801],{},"the true vibe coder"," gets frustrated and blames the model. They try a different model. Same result. They conclude \"agents don't work.\"",[11,2804,2805],{},[40,2806,2807],{},"Bro... Are you serious?",[11,2809,2810,2811,2813],{},"The developers who get real value from agents aren't the ones asking for the moon. They're the ones who are ",[40,2812,678],{},". They say:",[229,2815,2816,2819,2822],{},[232,2817,2818],{},"\"I wrote this function that takes these parameters. Fix this section's performance.\"",[232,2820,2821],{},"\"Plug this endpoint into that service.\"",[232,2823,2824],{},"\"Refactor this component to use the new API shape.\"",[11,2826,2827,2828,2831],{},"Surgical, specific, contextual. The agent isn't being asked to build something from scratch — it's being asked to ",[18,2829,2830],{},"do a job"," within an existing codebase that it can read and understand.",[11,2833,2834],{},"The best way to use an agent is not to ask it to build you a house. It's to hand it a blueprint, point at a specific wall, and say \"move this window three feet to the left.\"",[11,2836,2837],{},"Most people aren't intentional with their agents. They don't give them enough context. They don't frame the task narrowly enough. They don't let the agent read the surrounding code before asking it to make a change. They treat it like a search engine that generates code instead of like a teammate that needs a clear brief.",[47,2839,2841],{"id":2840},"the-models-are-already-good-enough","The Models Are Already Good Enough",[11,2843,2844,2845,2848],{},"The OpenCode data proves it. DeepSeek v4 Flash — the top model by a landslide — is a fraction of the cost of the frontier alternatives. It's definitely not the most capable model on the bench. But it doesn't need to be, because the ",[18,2846,2847],{},"workflow"," around it does the heavy lifting.",[11,2850,2851],{},"The agent has access to the project. It reads the context, runs the tests, iterates on failures. The model just needs to be good enough to write code that passes those tests. And at $0.05\u002Fsession, it's good enough to do that all day.",[47,2853,2855],{"id":2854},"supervised-collaboration","Supervised Collaboration",[11,2857,2858,2859,2864],{},"The real paradigm, as ",[264,2860,2863],{"href":2861,"rel":2862},"https:\u002F\u002Fworld.hey.com\u002Fdhh\u002Fpromoting-ai-agents-3ee04945",[268],"DHH put it",", is supervised collaboration. The agent does the grunt work. You review the output, guide the direction, make the calls. This only works when the agent has real tools — terminal access, file editing, build execution. And it works best when you give it a specific job, not a vague ambition.",[11,2866,2867],{},"The model race is a distraction. The data is clear: real developers don't use the most expensive models. They use the ones that are cheap enough to run constantly and good enough to get the job done. What separates a productive agent session from a frustrating one isn't the model. It's whether you told the agent what to build, or whether you told it what problem to solve.",{"title":383,"searchDepth":384,"depth":384,"links":2869},[2870,2871,2872],{"id":2782,"depth":384,"text":2783},{"id":2840,"depth":384,"text":2841},{"id":2854,"depth":384,"text":2855},"2026-06-15","Why 'Build me a website' doesn't work, the vibe coder problem, and why supervised collaboration beats magic wands every time.",{},"\u002Fblog\u002Fpi-agent-part-2",{"title":2768,"description":2874},"blog\u002Fpi-agent-part-2",[2880,2881,779,407],"ai","developer-experience","Most people use agents wrong — vague prompts, massive scope, zero context. The developers who get value are surgical and intentional. Supervised collaboration, not magic wands.","mNazg88guyXF-DU8ycz7s14X3KstUO0vZ5LSQUz3JLk",{"id":2885,"title":2886,"body":2887,"date":2873,"description":3098,"extension":398,"featured":399,"meta":3099,"navigation":401,"part":842,"path":2777,"seo":3100,"series":2778,"stem":3101,"tags":3102,"tldr":3103,"__hash__":3104},"blog\u002Fblog\u002Fpi-agent.md","The Real Cost of AI Coding Tools (And Why You're Overpaying)",{"type":8,"value":2888,"toc":3094},[2889,2892,2901,2904,2942,2960,2963,2970,2974,2983,2990,2993,2997,3004,3007,3012,3026,3029,3034,3045,3050,3069,3072,3075,3078,3081,3084,3086],[11,2890,2891],{},"I keep seeing the same conversation online. \"Have you tried the new Opus?\" \"Gemini 3.5 is unbelievable.\" \"You need the $200\u002Fmo plan or you're leaving money on the table.\"",[11,2893,2894,2895,2900],{},"Then I look at the actual usage data from ",[264,2896,2899],{"href":2897,"rel":2898},"https:\u002F\u002Fopencode.ai\u002Fdata",[268],"OpenCode"," — a terminal-based coding agent platform — and the picture is completely different.",[11,2902,2903],{},"Market share by model author over the last eight weeks:",[321,2905,2906,2912,2918,2924,2930,2936],{},[232,2907,2908,2911],{},[40,2909,2910],{},"DeepSeek"," — 58.4%",[232,2913,2914,2917],{},[40,2915,2916],{},"Moonshot"," — 24.6%",[232,2919,2920,2923],{},[40,2921,2922],{},"Qwen"," — 5.9%",[232,2925,2926,2929],{},[40,2927,2928],{},"Zhipu"," — 5.5%",[232,2931,2932,2935],{},[40,2933,2934],{},"MiniMax"," — 3.5%",[232,2937,2938,2941],{},[40,2939,2940],{},"Xiaomi"," — 2.1%",[11,2943,2944,2945,2948,2949,2952,2953,2956,2957],{},"Every single one is Chinese. Every single one is cheap. Average session cost: ",[40,2946,2947],{},"$0.05."," Token cost per million: ",[40,2950,2951],{},"$0.14 input, $0.28 output."," Cache ratio: ",[40,2954,2955],{},"97%."," Tokens per session: ",[40,2958,2959],{},"4.8 million.",[11,2961,2962],{},"The models everyone on Twitter is hyping? Barely on the board. Real developers, doing real work, voting with their wallets.",[11,2964,2965,2966,2969],{},"This isn't a \"China vs the world\" thing. It's a ",[40,2967,2968],{},"cost-to-value"," thing. Frontier models are expensive — $2–$15 per session depending on what you're doing. But the vast majority of coding work doesn't need frontier intelligence. Writing a test, refactoring a function, debugging a type error — these are handled perfectly well by models that cost a nickel per session. The only thing holding them back was the tooling.",[47,2971,2973],{"id":2972},"the-harness-is-the-unlock","The Harness Is the Unlock",[11,2975,2976,2977,2982],{},"I've been using ",[264,2978,2981],{"href":2979,"rel":2980},"https:\u002F\u002Fpi.dev\u002F",[268],"Pi"," — a minimal terminal harness for coding agents. You bring your own models, tools, and workflows. It supports fifteen-plus providers, so you can switch between a cheap model for daily work and a frontier model for the hard stuff. The harness doesn't care.",[11,2984,2985,2986,2989],{},"People use Claude Code and OpenCode for the same reason: ",[40,2987,2988],{},"they want the agent to actually touch the code."," Not suggest it. Edit it, run it, fix it. That's the real shift — terminal-native agents that operate on your project instead of just chatting about it.",[11,2991,2992],{},"But the deep truth about why people default to the most expensive tools is simple: it's the path of least resistance. Claude Code ships as a product. Pi is a harness you configure. One is an on-ramp, the other is a workshop. Most people take the on-ramp because it's there, not because they've calculated the cost.",[47,2994,2996],{"id":2995},"the-insane-economics-of-ai-subscriptions","The Insane Economics of AI Subscriptions",[11,2998,2999,3000,3003],{},"There's a question nobody wants to ask: why are we spending hundreds or thousands of dollars a month on AI tools to write code for projects that don't even have any users ",[31,3001,3002],{},"yet","?",[11,3005,3006],{},"Let's look at the actual numbers.",[11,3008,3009],{},[40,3010,3011],{},"Subscriptions:",[229,3013,3014,3017,3020,3023],{},[232,3015,3016],{},"Claude Pro: $20\u002Fmo. Max: $100\u002Fmo.",[232,3018,3019],{},"ChatGPT Plus: $20\u002Fmo. Pro: $100\u002Fmo.",[232,3021,3022],{},"GitHub Copilot Pro: $10\u002Fmo. Pro+: $39\u002Fmo. Max: $100\u002Fmo.",[232,3024,3025],{},"Cursor Pro: $20\u002Fmo. Ultra: $60\u002Fmo.",[11,3027,3028],{},"Stack two or three of these and you're at $200–$400\u002Fmo easily. Before you've written a single line of code. For a project with five GitHub stars, zero active users, no revenue.",[11,3030,3031],{},[40,3032,3033],{},"Frontier API costs:",[229,3035,3036,3039,3042],{},[232,3037,3038],{},"GPT-5.5: $5.00 input \u002F $30.00 output per 1M tokens.",[232,3040,3041],{},"GPT-5.4: $2.50 input \u002F $15.00 output.",[232,3043,3044],{},"Claude Opus class models: $5-15 input \u002F $25-75 output per 1M tokens.",[11,3046,3047],{},[40,3048,3049],{},"And what OpenCode data shows:",[229,3051,3052,3057,3064],{},[232,3053,3054,3055],{},"Average session cost with Chinese models: ",[40,3056,2947],{},[232,3058,3059,3060,3063],{},"Token cost: ",[40,3061,3062],{},"$0.14 input \u002F $0.28 output"," per 1M tokens.",[232,3065,3066,3067],{},"Tokens per session: ",[40,3068,2959],{},[11,3070,3071],{},"You can run a hundred sessions on cheap models for the cost of a single frontier session. And the Chinese models are capturing 100% of real usage on the platform. Not because they're sentimental — because they work.",[11,3073,3074],{},"And what are you getting for that $100\u002Fmo subscription? A copy-paste workflow from a browser tab. Maybe some agentic features if you use the premium tiers. But the economics make no sense. You're burning capital on inference that could be spent on a dozen cheaper models running in a proper harness.",[11,3076,3077],{},"Even worse — the more you spend, the less you learn. I see people generating thousands of lines of code they don't understand, shipping features they couldn't explain, building systems they couldn't debug. They're paying a premium to stay incompetent. The expensive model writes the code, the developer approves it blindly, and when something breaks, they have no idea where to start looking.",[11,3079,3080],{},"You know what forces you to understand your code? Reading it. Running it. Debugging it. A $200\u002Fmo model generating code you skim and accept doesn't make you productive — it makes you a manager of an intern you can't fire who writes code you can't read.",[11,3082,3083],{},"The people doing this right aren't the ones spending the most. They're the ones who picked a cheap model, configured a harness with real tools, and treat the agent like a junior developer they actually supervise. The cost is incidental. The workflow is the point.",[366,3085],{},[11,3087,3088,276,3090,3093],{},[40,3089,1982],{},[264,3091,3092],{"href":2876},"Part 2 — How to Actually Use AI Agents (Stop Treating Them Like Magic Wands)"," — why \"Build me a website\" doesn't work, the vibe coder problem, and why supervised collaboration is the real paradigm.",{"title":383,"searchDepth":384,"depth":384,"links":3095},[3096,3097],{"id":2972,"depth":384,"text":2973},{"id":2995,"depth":384,"text":2996},"OpenCode data shows DeepSeek owns 58% of real usage at $0.05\u002Fsession. The model race is a distraction from what actually matters.",{},{"title":2886,"description":3098},"blog\u002Fpi-agent",[2880,2881,779,407],"OpenCode data shows 58% of real coding sessions run on Chinese models at $0.05\u002Fsession. The frontier model race is a distraction — workflow, tooling, and being intentional with your agent matter way more than which model you pick.","o_K81HEL5lm2N0zUA8n6i8aFeYj2Z4zC-h5DBwp9GS8",{"id":3106,"title":3107,"body":3108,"date":3559,"description":3560,"extension":398,"featured":399,"meta":3561,"navigation":401,"part":402,"path":3562,"seo":3563,"series":3564,"stem":3565,"tags":3566,"tldr":3567,"__hash__":3568},"blog\u002Fblog\u002Fv-star-v09.md","I Actually Made Money (Testing)",{"type":8,"value":3109,"toc":3550},[3110,3113,3116,3119,3121,3127,3131,3134,3137,3140,3343,3350,3353,3356,3360,3363,3369,3375,3378,3381,3385,3388,3410,3419,3433,3442,3446,3452,3455,3459,3467,3471,3474,3478,3531,3534,3542,3547],[11,3111,3112],{},"v0.7.0 introduced interfaces. PathGenerator, ContingencyCalculator — the engine got composable. That was the foundation.",[11,3114,3115],{},"v0.9.5 is where it pays off.",[11,3117,3118],{},"I added profit testing. I redesigned the server so it doesn't fall over under load. I killed the Python bridge. I froze the API. And somewhere along the way, a desktop app appeared.",[366,3120],{},[11,3122,3123,3126],{},[18,3124,3125],{},"Quick context if you're not an actuary:"," Insurance companies collect money upfront (premiums) but don't know when they'll have to pay it out (claims). Actuaries figure out if you're charging enough. They build models projecting cashflows 30, 40, 50 years into the future — mortality rates, investment returns, expenses. Historically, this is done in Excel with VBA macros. v-star is me replacing that whole mess with proper software.",[47,3128,3130],{"id":3129},"the-big-one-profit-testing","The Big One: Profit Testing",[11,3132,3133],{},"When an insurance company designs a new product, they need to know: will it make money? Not \"probably yes\" — actual numbers. How much profit each year? When do you break even? What's your return?",[11,3135,3136],{},"That's what profit testing does. Takes a policy, a set of assumptions, and spits out cashflows year by year for the entire life of the policy. The stuff people spend weeks building in Excel.",[11,3138,3139],{},"Here's what it looks like in Go:",[809,3141,3145],{"className":3142,"code":3143,"language":3144,"meta":383,"style":383},"language-go shiki shiki-themes vesper","mort, _ := mortality.LoadCSV(\"utilsoc-qx.csv\")\nassumptions := profit.Assumptions{\n    Mortality:       mort,\n    EarnedRate:      0.05,\n    DiscountRate:    0.08,\n    Expenses:        500,\n    RenewalExpense:  50,\n    CommissionRate:  0.05,\n}\npolicy := profit.Policy{\n    Age: 30, Term: 20, SumAssured: 100000, Premium: 5000,\n}\nresults := profit.Run(policy, assumptions)\nfmt.Printf(\"Profit margin: %.2f%%\\n\", results.ProfitMargin*100)\n","go",[23,3146,3147,3168,3186,3191,3202,3212,3222,3232,3241,3246,3262,3290,3294,3310],{"__ignoreMap":383},[839,3148,3149,3152,3155,3158,3161,3163,3166],{"class":841,"line":842},[839,3150,3151],{"class":849},"mort, _ ",[839,3153,3154],{"class":845},":=",[839,3156,3157],{"class":849}," mortality.",[839,3159,3160],{"class":899},"LoadCSV",[839,3162,1406],{"class":849},[839,3164,3165],{"class":895},"\"utilsoc-qx.csv\"",[839,3167,1723],{"class":849},[839,3169,3170,3173,3175,3178,3180,3183],{"class":841,"line":384},[839,3171,3172],{"class":849},"assumptions ",[839,3174,3154],{"class":845},[839,3176,3177],{"class":899}," profit",[839,3179,26],{"class":849},[839,3181,3182],{"class":899},"Assumptions",[839,3184,3185],{"class":849},"{\n",[839,3187,3188],{"class":841,"line":390},[839,3189,3190],{"class":849},"    Mortality:       mort,\n",[839,3192,3193,3196,3199],{"class":841,"line":871},[839,3194,3195],{"class":849},"    EarnedRate:      ",[839,3197,3198],{"class":899},"0.05",[839,3200,3201],{"class":849},",\n",[839,3203,3204,3207,3210],{"class":841,"line":883},[839,3205,3206],{"class":849},"    DiscountRate:    ",[839,3208,3209],{"class":899},"0.08",[839,3211,3201],{"class":849},[839,3213,3214,3217,3220],{"class":841,"line":913},[839,3215,3216],{"class":849},"    Expenses:        ",[839,3218,3219],{"class":899},"500",[839,3221,3201],{"class":849},[839,3223,3224,3227,3230],{"class":841,"line":936},[839,3225,3226],{"class":849},"    RenewalExpense:  ",[839,3228,3229],{"class":899},"50",[839,3231,3201],{"class":849},[839,3233,3234,3237,3239],{"class":841,"line":958},[839,3235,3236],{"class":849},"    CommissionRate:  ",[839,3238,3198],{"class":899},[839,3240,3201],{"class":849},[839,3242,3243],{"class":841,"line":985},[839,3244,3245],{"class":849},"}\n",[839,3247,3248,3251,3253,3255,3257,3260],{"class":841,"line":1013},[839,3249,3250],{"class":849},"policy ",[839,3252,3154],{"class":845},[839,3254,3177],{"class":899},[839,3256,26],{"class":849},[839,3258,3259],{"class":899},"Policy",[839,3261,3185],{"class":849},[839,3263,3264,3267,3270,3273,3276,3279,3282,3285,3288],{"class":841,"line":1036},[839,3265,3266],{"class":849},"    Age: ",[839,3268,3269],{"class":899},"30",[839,3271,3272],{"class":849},", Term: ",[839,3274,3275],{"class":899},"20",[839,3277,3278],{"class":849},", SumAssured: ",[839,3280,3281],{"class":899},"100000",[839,3283,3284],{"class":849},", Premium: ",[839,3286,3287],{"class":899},"5000",[839,3289,3201],{"class":849},[839,3291,3292],{"class":841,"line":1059},[839,3293,3245],{"class":849},[839,3295,3296,3299,3301,3304,3307],{"class":841,"line":1081},[839,3297,3298],{"class":849},"results ",[839,3300,3154],{"class":845},[839,3302,3303],{"class":849}," profit.",[839,3305,3306],{"class":899},"Run",[839,3308,3309],{"class":849},"(policy, assumptions)\n",[839,3311,3312,3315,3318,3320,3323,3326,3329,3332,3335,3338,3341],{"class":841,"line":1087},[839,3313,3314],{"class":849},"fmt.",[839,3316,3317],{"class":899},"Printf",[839,3319,1406],{"class":849},[839,3321,3322],{"class":895},"\"Profit margin: ",[839,3324,3325],{"class":849},"%.2f%%",[839,3327,3328],{"class":845},"\\n",[839,3330,3331],{"class":895},"\"",[839,3333,3334],{"class":849},", results.ProfitMargin",[839,3336,3337],{"class":845},"*",[839,3339,3340],{"class":899},"100",[839,3342,1723],{"class":849},[11,3344,3345,3346,3349],{},"Define your assumptions, define the policy, call ",[23,3347,3348],{},"Run()",". You get back profit signature, profit vector, PV of profits, profit margin, payback year, and IRR. Everything an actuary needs to know if a product is viable.",[11,3351,3352],{},"The math is straightforward: every year of a policy, money flows in and out. Premiums come in at the start. Expenses and commission get paid. The reserve earns interest. Death claims get paid at the end. Profit = what's left. Discount it back at the risk rate, and you know if your product makes sense.",[11,3354,3355],{},"457 lines of code. 453 lines of tests. 93.1% coverage. Because if I'm projecting money for actual insurance products, the code better be tested.",[47,3357,3359],{"id":3358},"the-irr-fix-and-why-i-almost-shipped-broken-code","The IRR Fix (And Why I Almost Shipped Broken Code)",[11,3361,3362],{},"IRR is the interest rate that makes net profit zero when discounted back. Higher IRR = better product. I found two bugs in my implementation, and fixing them taught me something.",[11,3364,3365,3368],{},[40,3366,3367],{},"Bug 1: The 200% ceiling."," The old code had a hardcoded ceiling at 200%. If your policy was very profitable, it just returned 2.0 — a sentinel, a lie. Fixed: now it dynamically searches up to 100,000,000% (effectively infinite). Because if you're making 200% IRR, you're not selling insurance, you're printing money.",[11,3370,3371,3374],{},[40,3372,3373],{},"Bug 2: Linear interpolation doesn't work."," I told an AI coding agent to implement IRR using linear interpolation — the simple method from math class. It looked at me and said \"No. Use the Illinois algorithm.\"",[11,3376,3377],{},"The Illinois algorithm (1971, University of Illinois) fixes a specific problem with interpolation: when the same endpoint keeps getting selected, its value gets halved, breaking the stall. Converges faster, more precise, still no calculus.",[11,3379,3380],{},"I almost shipped janky linear interpolation. The AI saved me from myself. Then I shipped the Illinois algorithm with the stagnation flags inverted — halving the wrong endpoint — and had to fix that too. Long-term policies failed within 100 iterations because the algorithm pulled in the wrong direction. One line fix once I tracked it down.",[47,3382,3384],{"id":3383},"the-server-grew-up","The Server Grew Up",[11,3386,3387],{},"The v0.7.0 server was functional. Under load it fell over like a spreadsheet with circular references.",[11,3389,3390,3391,892,3394,892,3397,892,3400,892,3403,892,3406,3409],{},"v0.9.5 fixes that. Six routes: ",[23,3392,3393],{},"\u002Fvalue",[23,3395,3396],{},"\u002Fsimulate",[23,3398,3399],{},"\u002Fannuity",[23,3401,3402],{},"\u002Freserve",[23,3404,3405],{},"\u002Fprofit",[23,3407,3408],{},"\u002Fhealth",". That's it. Everything else got killed or merged. Stateless — deploy it, kill it, scale it horizontally.",[11,3411,3412,3415,3416,3418],{},[40,3413,3414],{},"Concurrency limiter:"," Bounded wait queue — up to 5 seconds, then 503. Under 50 concurrent ",[23,3417,3396],{}," requests, P95 is 65ms and every single one succeeds. Before this change, 46 out of 50 got instant 503s.",[11,3420,3421,276,3424,1217,3426,3428,3429,3432],{},[40,3422,3423],{},"Result cache:",[23,3425,3399],{},[23,3427,3402],{}," responses are cached. 1000-entry FIFO. ",[23,3430,3431],{},"X-Cache: hit\u002Fmiss"," headers. P50 dropped from 2.19ms to 0.75ms.",[11,3434,3435,3438,3439,3441],{},[40,3436,3437],{},"Middleware stack:"," Composable and independently testable. 266 lines of tests. Because middleware is one of those things everyone writes and nobody tests, and that's ",[18,3440,453],{}," why it breaks in production.",[47,3443,3445],{"id":3444},"i-killed-the-python-bridge","I Killed the Python Bridge",[11,3447,3448,3449,26],{},"I was maintaining a Python package — pyproject.toml, uv.lock, .venv, CI for building wheels, demo notebooks. Over-engineered. Worse, it sent the wrong message: \"You need Python to use v-star.\" No. You need ",[18,3450,3451],{},"HTTP",[11,3453,3454],{},"Replaced with a directory of example scripts — one Python, one JavaScript, one curl. No dependencies beyond the stdlib. Same endpoints, any language. The way it should be.",[47,3456,3458],{"id":3457},"theres-a-desktop-app-now","There's a Desktop App Now",[11,3460,3461,3466],{},[264,3462,3465],{"href":3463,"rel":3464},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Fv-desktop",[268],"v-desktop"," is v-star in a window. Built with Wails (Go backend, web frontend, native window — no Electron bloat). Nine tabs, mortality table browser with built-in CSO 2017 tables, Chart.js graphs, keyboard shortcuts, dark theme. Runs on Windows, macOS, Linux. One binary. Pre-built releases for all platforms.",[47,3468,3470],{"id":3469},"api-freeze","API Freeze",[11,3472,3473],{},"I audited every single export across all 12 packages. Wrote an ADR. Eight packages locked, three already stable, one minor fix. The public API is stable. Not going to break your code with the next release.",[47,3475,3477],{"id":3476},"the-v095-stack","The v0.9.5 Stack",[229,3479,3480,3486,3495,3501,3507,3513,3519,3525],{},[232,3481,3482,3485],{},[40,3483,3484],{},"Core:"," Go 1.24+, zero dependencies, standard library only",[232,3487,3488,3491,3492,3494],{},[40,3489,3490],{},"Profit Testing:"," Go package + ",[23,3493,3405],{}," endpoint, full metrics",[232,3496,3497,3500],{},[40,3498,3499],{},"CensusSource:"," Interface with 3 implementations, 100% coverage",[232,3502,3503,3506],{},[40,3504,3505],{},"Server:"," 6 routes, stateless, middleware, cache, concurrency limiter",[232,3508,3509,3512],{},[40,3510,3511],{},"Desktop:"," v-desktop, native GUI, 9 tools, cross-platform",[232,3514,3515,3518],{},[40,3516,3517],{},"API Clients:"," Python, JavaScript, cURL. Just HTTP",[232,3520,3521,3524],{},[40,3522,3523],{},"Performance:"," VaR\u002FCTE 1.8× faster, zero allocations",[232,3526,3527,3530],{},[40,3528,3529],{},"Coverage:"," Annuities 94%, Server 82%, Profit 93.1%",[11,3532,3533],{},"v1.0.0 is close. Push coverage past 90%, maybe a Dockerfile, actual documentation that doesn't read like a stream of consciousness.",[11,3535,3536,3537,3541],{},"Repo at ",[264,3538,3540],{"href":266,"rel":3539},[268],"github.com\u002Flubasinkal\u002Fv-star",". MIT. Zero dependencies. Actually usable.",[11,3543,3544],{},[31,3545,3546],{},"Now I have some business stuff to break.",[1823,3548,3549],{},"html pre.shiki code .sU-n2, html code.shiki .sU-n2{--shiki-default:#FFF}html pre.shiki code .sq0yK, html code.shiki .sq0yK{--shiki-default:#A0A0A0}html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":383,"searchDepth":384,"depth":384,"links":3551},[3552,3553,3554,3555,3556,3557,3558],{"id":3129,"depth":384,"text":3130},{"id":3358,"depth":384,"text":3359},{"id":3383,"depth":384,"text":3384},{"id":3444,"depth":384,"text":3445},{"id":3457,"depth":384,"text":3458},{"id":3469,"depth":384,"text":3470},{"id":3476,"depth":384,"text":3477},"2026-05-29","Profit testing, an API that doesn't suck, the Python bridge got killed, and a desktop app appeared.",{},"\u002Fblog\u002Fv-star-v09",{"title":3107,"description":3560},"V-Star","blog\u002Fv-star-v09",[408,3144,407],"Profit testing with full metrics (IRR, profit margin, payback year), a server that won't fall over, and a native desktop app via Wails. The Python bridge died so HTTP could live. v1.0.0 is close.","V7PwBA6kejJkbgbbhmAeIndPH8Xnl22OWwk8z6-yjkg",{"id":3570,"title":3571,"body":3572,"date":3772,"description":3773,"extension":398,"featured":399,"meta":3774,"navigation":401,"part":402,"path":3775,"seo":3776,"series":3564,"stem":3777,"tags":3778,"tldr":3780,"__hash__":3781},"blog\u002Fblog\u002Fv-star-v07.md","Stop Hardcoding Everything",{"type":8,"value":3573,"toc":3764},[3574,3577,3584,3587,3591,3600,3614,3627,3633,3636,3640,3654,3657,3690,3693,3697,3700,3707,3711,3714,3734,3737,3741,3744,3747,3751,3754,3761],[11,3575,3576],{},"The first version of v-star was basically me seeing how much I could abuse Go's concurrency to make a spreadsheet user's life miserable. All raw speed. 1M policies, 300ms, boom. I thought I had it figured out.",[11,3578,3579,3580,3583],{},"But here's the thing: raw speed is ",[18,3581,3582],{},"easy",". Architecture is the hard part.",[11,3585,3586],{},"v0.7.0 is where I stopped treating this like a script and started treating it like a framework. The big move? Interfaces.",[47,3588,3590],{"id":3589},"the-stop-hardcoding-pivot","The \"Stop Hardcoding\" Pivot",[11,3592,3593,3594,276,3597],{},"I realised I was trapped. If I wanted a different interest rate model, I had to rip out the guts of the simulator. ",[31,3595,3596],{},"Who does that?",[18,3598,3599],{},"I did.",[11,3601,3602,3603,892,3606,3609,3610,3613],{},"So I killed the concrete dependencies. Now we have ",[23,3604,3605],{},"PathGenerator",[23,3607,3608],{},"ContingencyCalculator",", and ",[23,3611,3612],{},"MortalityTable"," as interfaces.",[11,3615,3616,3617,3620,3621,3623,3624,26],{},"Until now, it was just Geometric Brownian Motion for path generation. Cool, but GBM just drifts. Real interest rates don't do that — they mean-revert. Enter the ",[40,3618,3619],{},"Vasicek Model",". Because I'm using a ",[23,3622,3605],{}," interface, I just plugged in a Vasicek generator and suddenly the simulation actually makes sense for real-world financial engineering. It's not just a feature, it's what makes the tool ",[18,3625,3626],{},"useful",[11,3628,3629,3630,3632],{},"Same for the ",[23,3631,3608],{},". I stopped doing just present values and started doing actual actuarial science — whole life, term insurance, deferred annuities, net single premiums. If it's a life-contingent cash flow, v-star can probably calculate it without me rewriting the core engine.",[11,3634,3635],{},"One interface change unlocked all of that. That's the power of programming to interfaces instead of concrete types.",[47,3637,3639],{"id":3638},"the-unix-way","The Unix Way",[11,3641,3642,3643,3646,3647,3650,3651,26],{},"v-star was always CLI-first, but v0.7.0 makes it ",[18,3644,3645],{},"composable",". I added ",[23,3648,3649],{},"StreamCensusFromReader",". The engine doesn't care if your data is in a file, an HTTP body, or some buffer in memory. It just wants an ",[23,3652,3653],{},"io.Reader",[11,3655,3656],{},"You can literally curl a CSV and pipe it straight into the engine:",[809,3658,3662],{"className":3659,"code":3660,"language":3661,"meta":383,"style":383},"language-bash shiki shiki-themes vesper","curl -s https:\u002F\u002Fapi.provider.com\u002Fcensus | .\u002Fv-star read --stdin --benchmark\n","bash",[23,3663,3664],{"__ignoreMap":383},[839,3665,3666,3669,3672,3675,3678,3681,3684,3687],{"class":841,"line":842},[839,3667,3668],{"class":899},"curl",[839,3670,3671],{"class":895}," -s",[839,3673,3674],{"class":895}," https:\u002F\u002Fapi.provider.com\u002Fcensus",[839,3676,3677],{"class":845}," |",[839,3679,3680],{"class":899}," .\u002Fv-star",[839,3682,3683],{"class":895}," read",[839,3685,3686],{"class":895}," --stdin",[839,3688,3689],{"class":895}," --benchmark\n",[11,3691,3692],{},"And if you want the fastest possible I\u002FO, there's the mmap path. 10M rows in 0.80 seconds. Zero-copy parsing. As fast as the OS can read the disk.",[47,3694,3696],{"id":3695},"the-http-api","The HTTP API",[11,3698,3699],{},"The biggest grown-up move in this version is the server. v-star is now a REST API. You don't need Go to use it. Call it from Python, R, or even Excel via HTTP.",[11,3701,3702,3703,3706],{},"Need a Monte Carlo risk report with VaR 95% and CTE 99%? Hit the ",[23,3704,3705],{},"\u002Fmontecarlo"," endpoint. Industry-standard risk metrics delivered as JSON in milliseconds. Same engine, wrapped in a way that makes it accessible to the rest of your stack.",[47,3708,3710],{"id":3709},"the-flex-benchmarks","The Flex (Benchmarks)",[11,3712,3713],{},"Because I can't help myself:",[229,3715,3716,3722,3728],{},[232,3717,3718,3721],{},[40,3719,3720],{},"PV Calculation:"," 380 million calls per second",[232,3723,3724,3727],{},[40,3725,3726],{},"Parallel Valuation:"," 272 million policies per second",[232,3729,3730,3733],{},[40,3731,3732],{},"Monte Carlo:"," 3.7 million paths per second",[11,3735,3736],{},"Combine that speed with the new interfaces and you're not running a simulation. You're running a professional-grade actuarial lab on your laptop.",[47,3738,3740],{"id":3739},"the-boring-stuff-that-matters","The Boring Stuff That Matters",[11,3742,3743],{},"Parallel Monte Carlo — each worker gets its own RNG so they aren't fighting over a lock. More cores = faster simulation.",[11,3745,3746],{},"Version injection via ldflags — no more hardcoding the version in main.go like a caveman. It's a tiny change, but it's the \"I actually know how to build Go tools\" flex.",[47,3748,3750],{"id":3749},"whats-next","What's Next",[11,3752,3753],{},"The foundation is finally not made of cardboard. Custom mortality tables from real CSV files. Variance reduction with antithetic variates. Stepped and increasing benefits. The fun stuff.",[11,3755,3756,3757,3760],{},"Repo is at ",[264,3758,3540],{"href":266,"rel":3759},[268],". Go build it, or don't. I don't care.",[1823,3762,3763],{},"html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}html pre.shiki code .sq0yK, html code.shiki .sq0yK{--shiki-default:#A0A0A0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":383,"searchDepth":384,"depth":384,"links":3765},[3766,3767,3768,3769,3770,3771],{"id":3589,"depth":384,"text":3590},{"id":3638,"depth":384,"text":3639},{"id":3695,"depth":384,"text":3696},{"id":3709,"depth":384,"text":3710},{"id":3739,"depth":384,"text":3740},{"id":3749,"depth":384,"text":3750},"2026-05-23","Interfaces, Vasicek models, and realising concrete types are a trap.",{},"\u002Fblog\u002Fv-star-v07",{"title":3571,"description":3773},"blog\u002Fv-star-v07",[408,3144,3779,407],"simulation","Interfaces over concrete types unlocked the Vasicek model, composable CLI pipes, and a REST API — all without rewriting the engine. PV at 380M calls\u002Fsec, parallel valuation at 272M policies\u002Fsec. This is what treating actuarial software like a framework looks like.","DdDDBQJr5kfUdjmCG8m2nzcVN1hyPQ7nmchUPpOR2MI",{"id":3783,"title":3784,"body":3785,"date":3994,"description":3995,"extension":398,"featured":399,"meta":3996,"navigation":401,"part":402,"path":3997,"seo":3998,"series":3999,"stem":4000,"tags":4001,"tldr":4004,"__hash__":4005},"blog\u002Fblog\u002Fquant-markov.md","Regime Detection — The Simple Approach That Works",{"type":8,"value":3786,"toc":3984},[3787,3796,3799,3805,3808,3812,3815,3825,3834,3840,3843,3847,3850,3856,3862,3868,3874,3877,3881,3884,3887,3891,3894,3897,3900,3903,3907,3910,3921,3924,3927,3930,3934,3940,3946,3952,3958,3961,3965,3968,3971,3974,3978,3981],[11,3788,3789,3790,3795],{},"Inspired by ",[264,3791,3794],{"href":3792,"rel":3793},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=CkXljL6eI5A",[268],"Roman Paolucci",". Go watch his stuff. It's good.",[11,3797,3798],{},"Here's something that actually works. Not fancy. Not complicated. Just useful.",[11,3800,3801,3802],{},"Regime detection is figuring out what kind of market you're in before you trade it. Markets exist in different states. Some favour trend following. Some favour mean reversion. Some want you to stay away entirely. The trick is knowing which state you're in. ",[18,3803,3804],{},"That's the whole game.",[11,3806,3807],{},"Think of it like weather. You don't wear a raincoat when it's sunny. You don't trade aggressively when the market is volatile. You adjust based on conditions. That's it.",[47,3809,3811],{"id":3810},"the-three-things-that-matter","The Three Things That Matter",[11,3813,3814],{},"I learned this from losing money, not from textbooks. Three things determine the regime.",[11,3816,3817,3820,3821,3824],{},[40,3818,3819],{},"Trend strength"," — How strong is the current move? A trending market moves in one direction consistently. A ranging market goes nowhere. There's a standard indicator for this — been around for decades. It measures directional movement. Not up or down. Just ",[18,3822,3823],{},"how strong",". When trend strength is high, trends tend to continue. When it's low, they tend to reverse. That's the key insight.",[11,3826,3827,3830,3831,3833],{},[40,3828,3829],{},"Return direction"," — Where has the market been moving? Not predicting. Observing. Positive recent returns = bullish bias. Negative = bearish bias. This sounds obvious because it ",[18,3832,617],{}," obvious. Don't overcomplicate what works.",[11,3835,3836,3839],{},[40,3837,3838],{},"Relative volatility"," — Is the market more volatile than usual, or less? High volatility = stressed market = danger = smaller positions. Low volatility = calm = normal positions.",[11,3841,3842],{},"That's it. Three numbers at every bar. Classify the regime. Only take trades that match. Size based on clarity.",[47,3844,3846],{"id":3845},"why-not-machine-learning","Why Not Machine Learning?",[11,3848,3849],{},"I tried ML approaches. Hidden Markov Models. Gaussian mixtures. Neural networks. Here's what happened:",[11,3851,3852,3855],{},[40,3853,3854],{},"Inconsistent."," Same data, different results. Run it twice, get different regimes.",[11,3857,3858,3861],{},[40,3859,3860],{},"Overfitted."," Backtests looked amazing. Forward tests failed. The model memorised, didn't learn.",[11,3863,3864,3867],{},[40,3865,3866],{},"Uninterpretable."," Even when it worked, I couldn't explain why. If I can't explain it, I can't trust it.",[11,3869,3870,3873],{},[40,3871,3872],{},"Drifted."," What worked last year doesn't work this year. Constant retraining. Added complexity without added value.",[11,3875,3876],{},"The assumptions behind ML (stationarity, ergodicity, convergence) don't hold in markets. The math itself is suspect. ML is a tool, not magic. Know when to use it, and more importantly, know when not to.",[47,3878,3880],{"id":3879},"what-actually-works","What Actually Works",[11,3882,3883],{},"Simplicity. Deterministic — same inputs, same outputs every time. No random seeds, no variation. Interpretable — you can see exactly why the regime was classified that way. Testable — validate on historical data independently. Robust — doesn't drift, doesn't break, doesn't need constant retraining.",[11,3885,3886],{},"Sometimes the fancy solution isn't the better solution. Simplicity wins.",[47,3888,3890],{"id":3889},"the-markov-property-briefly","The Markov Property (Briefly)",[11,3892,3893],{},"A stochastic process is Markov if the future depends only on the present, not the past. In markets? Debatable. The assumption doesn't hold perfectly, but it approximates reasonably well on short timeframes. That's good enough for regime detection. You're not predicting the next price — you're classifying the current environment.",[11,3895,3896],{},"Stationarity is the bigger problem. A process is stationary if its statistical properties don't change over time. Financial returns aren't stationary. That's the fundamental challenge. The variance changes, the regimes change, the relationships break.",[11,3898,3899],{},"The academic approach uses hidden states with transitions governed by a Markov chain, then infers states via the Baum-Welch algorithm. The problem: convergence isn't guaranteed. Multiple local optima exist. Different initializations give different results. That's not robust.",[11,3901,3902],{},"My approach: observe the states directly. The regime is what you see, not what you infer. That's simpler. Sometimes simpler is better.",[47,3904,3906],{"id":3905},"the-test-that-matters","The Test That Matters",[11,3908,3909],{},"Not backtests. Walk-forward testing.",[229,3911,3912,3915,3918],{},[232,3913,3914],{},"Train on earlier data. Test on later data.",[232,3916,3917],{},"Roll forward. Repeat.",[232,3919,3920],{},"Compare filtered vs unfiltered performance.",[11,3922,3923],{},"That's the only test that matters. If it works forward, it works. If it doesn't, it doesn't.",[11,3925,3926],{},"My hypothesis was simple: certain regime conditions favour trend continuation. Test: run with and without the filter. Compare. The filtered version performed better. That's validation.",[11,3928,3929],{},"Backtests lie. Walk-forward tells truth.",[47,3931,3933],{"id":3932},"what-this-gives-you","What This Gives You",[11,3935,3936,3939],{},[40,3937,3938],{},"Context"," — You know what environment you're trading in. That's crucial.",[11,3941,3942,3945],{},[40,3943,3944],{},"Filtering"," — You only take trades that match conditions. That's edge preservation.",[11,3947,3948,3951],{},[40,3949,3950],{},"Risk management"," — You size based on clarity. Ambiguous regimes mean smaller trades.",[11,3953,3954,3957],{},[40,3955,3956],{},"Process"," — You're not guessing. You're responding. That's systematic.",[11,3959,3960],{},"The goal isn't to predict. It's to adapt.",[47,3962,3964],{"id":3963},"limitations-being-honest","Limitations (Being Honest)",[11,3966,3967],{},"Regime detection lags. The market enters a new regime, the indicators catch up, and by then you're one step behind. Accept it. Manage it.",[11,3969,3970],{},"The lookback periods matter. You can overfit them. Don't tune on in-sample only — walk-forward validate.",[11,3972,3973],{},"Markets can switch regimes instantly. The detection is retrospective. Nobody predicts regime changes. Nobody.",[47,3975,3977],{"id":3976},"the-takeaway","The Takeaway",[11,3979,3980],{},"Markets have regimes. Understand the state you're trading in. Simple beats complex. Deterministic beats stochastic. Interpretable beats impressive. Test everything — walk-forward validates, backtests lie. ML isn't magic, it's a tool.",[11,3982,3983],{},"The quant journey isn't about finding perfection. It's about building process, testing it, and improving it. Now go build something simple. Test it. Break it. Fix it. That's how it's done.",{"title":383,"searchDepth":384,"depth":384,"links":3985},[3986,3987,3988,3989,3990,3991,3992,3993],{"id":3810,"depth":384,"text":3811},{"id":3845,"depth":384,"text":3846},{"id":3879,"depth":384,"text":3880},{"id":3889,"depth":384,"text":3890},{"id":3905,"depth":384,"text":3906},{"id":3932,"depth":384,"text":3933},{"id":3963,"depth":384,"text":3964},{"id":3976,"depth":384,"text":3977},"2026-04-17","Why I stopped using HMMs and went back to basics. Trend strength, return direction, and relative volatility. No ML, no black boxes.",{},"\u002Fblog\u002Fquant-markov",{"title":3784,"description":3995},"Quant Journey","blog\u002Fquant-markov",[4002,3779,4003],"quant","trading","HMMs are overkill. Three simple indicators — trend strength, return direction, relative volatility — classify market regimes better than anything ML can do. Deterministic, interpretable, and walk-forward validated.","7_HghsMYXxkRI0tad7Lex7G-15K5PnNHLXNMO5oDswc",{"id":4007,"title":4008,"body":4009,"date":4197,"description":4198,"extension":398,"featured":401,"meta":4199,"navigation":401,"part":402,"path":4200,"seo":4201,"series":3999,"stem":4202,"tags":4203,"tldr":4204,"__hash__":4205},"blog\u002Fblog\u002Fquant-journey.md","Five Years of Losing Money So You Don't Have To",{"type":8,"value":4010,"toc":4189},[4011,4014,4021,4024,4028,4031,4037,4047,4053,4059,4066,4077,4081,4084,4091,4094,4098,4101,4111,4117,4123,4126,4129,4133,4136,4139,4142,4145,4148,4152,4155,4158,4161,4164,4167,4170,4174,4180,4183,4186],[11,4012,4013],{},"I've been trading since 2021. My first year of university. I had my first allowance, some saved-up pocket money, and the kind of confidence you only get when you don't know anything.",[11,4015,4016,4017,4020],{},"That was five years ago. I'm graduated now (December 2025), and I'm still at it. Still losing sometimes. Still learning. The winning was sporadic. The losing was consistent. That's the ",[18,4018,4019],{},"worst"," kind of relationship with trading — the occasional win keeps you hooked just long enough to lose more.",[11,4022,4023],{},"I wasn't a trader. I was a gambler with extra steps. And I didn't even know it.",[47,4025,4027],{"id":4026},"what-university-taught-me-that-didnt-work","What University Taught Me (That Didn't Work)",[11,4029,4030],{},"I studied actuarial science. Statistics, probability theory, stochastic processes, time series analysis, differential equations. All the fancy concepts that sound great in textbooks. And I tried to apply every single one of them to trading. Of course I did. That's who I am.",[11,4032,4033,4036],{},[40,4034,4035],{},"ARIMA models"," — Looked amazing on historical data. Completely useless live. Markets aren't Gaussian. They're not even close. The stationarity assumption is a fantasy. Markets evolve, adapt, and break your models.",[11,4038,4039,4042,4043,4046],{},[40,4040,4041],{},"GARCH for volatility"," — Captures volatility clustering, sure. But capturing volatility isn't predicting direction. I'd know the market was about to move, just not ",[18,4044,4045],{},"which way",". Useful information? Yes. Tradeable edge? No.",[11,4048,4049,4052],{},[40,4050,4051],{},"Copula theory"," — I asked my lecturer to go deeper on this. Everyone else wanted to move on, so I learned it myself. The theory is decent. The implementation is a nightmare. Correlation between assets changes in a crisis. Copulas assume a static relationship. Markets don't do static. They do chaos.",[11,4054,4055,4058],{},[40,4056,4057],{},"Jacobian multipliers and continuity conditions"," — Beautiful math. Almost entirely useless for actually making money. It's like knowing how an engine works but not knowing how to drive.",[11,4060,4061,4062,4065],{},"The brutal truth: most academic statistics doesn't apply to trading. Not because the concepts are wrong, but because the ",[18,4063,4064],{},"assumptions don't hold",". The distributions aren't normal. The relationships aren't stable. Markets aren't textbook problems.",[11,4067,4068,4069,4072,4073,4076],{},"The one thing that did translate? ",[40,4070,4071],{},"Monte Carlo simulation."," Not for predicting the future, but for understanding the range of what ",[18,4074,4075],{},"could"," happen. That's the whole point.",[47,4078,4080],{"id":4079},"my-honours-thesis","My Honours Thesis",[11,4082,4083],{},"\"Teaching Old Models New Tricks: Comparative Analysis of Classical and Machine Learning Models for FX Forward Contract Evaluation Through Future Spot Rate Prediction Across Developed and Emerging Markets.\"",[11,4085,4086,4087,4090],{},"Long title. Complex topic. One real finding: ",[40,4088,4089],{},"ML doesn't automatically beat classical models."," Classical models have economic theory behind them. ML just optimises for the training data. Sometimes classical wins. Sometimes ML wins. It's not about which is better — it's about understanding when each approach makes sense.",[11,4092,4093],{},"I learned something important from that research. ML is a tool, not a magic wand. You need domain expertise to use it properly. Otherwise you're just throwing data at an algorithm and hoping something sticks.",[47,4095,4097],{"id":4096},"ml-is-a-lie-for-most-people","ML Is A Lie (For Most People)",[11,4099,4100],{},"Let me be direct. I tried ML in trading. Hard. And the narrative around it is completely overblown.",[11,4102,4103,4106,4107,4110],{},[40,4104,4105],{},"Overfitting is the default."," Thousands of features, limited data — your model ",[18,4108,4109],{},"will"," find patterns that don't exist. Backtests look incredible. Forward tests fail. The model memorised the past instead of learning anything useful.",[11,4112,4113,4116],{},[40,4114,4115],{},"Data leakage is everywhere."," Accidentally include future information in your training set, build a model that seems to predict perfectly, deploy it and lose money. I did this multiple times. Each time I thought I'd found something. Each time I was wrong.",[11,4118,4119,4122],{},[40,4120,4121],{},"Feature engineering is guesswork."," You try hundreds of features, keep the ones that work in backtests, and call it research. That's not science. That's selection bias with extra steps.",[11,4124,4125],{},"You need deep domain expertise to do ML properly. Years of experience understanding how markets behave, what features actually capture edge, how to validate properly. If you're not that person, you're not doing ML. You're doing high-tech gambling with extra steps.",[11,4127,4128],{},"I wasn't that person. Most people who try ML in trading aren't that person either.",[47,4130,4132],{"id":4131},"why-i-stopped-discretionary-trading","Why I Stopped Discretionary Trading",[11,4134,4135],{},"Discretionary trading sounds appealing. You look at a chart, you see a pattern, you make a call. You feel smart. You feel in control.",[11,4137,4138],{},"That feeling is a trap. The market doesn't care how smart you feel.",[11,4140,4141],{},"The problem with discretionary trading is consistency. You make good decisions sometimes and bad decisions sometimes, and you can't tell which is which until the trade is closed. There's no system. No process. Just you, guessing, hoping, and fighting your own emotions.",[11,4143,4144],{},"Every discretionary trader says they can control their emotions. They can't. The moment you see red on your screen, your brain does things you didn't plan for. Exit early. Double down. Revenge trade. You think you're different. You're not.",[11,4146,4147],{},"That's why I moved to systematic trading. Not because I'm smarter. Because I know I'm not smart enough to beat the market with feelings.",[47,4149,4151],{"id":4150},"hypothesis-testing-is-the-way","Hypothesis Testing Is The Way",[11,4153,4154],{},"Now every strategy I build starts with a hypothesis. Something testable. Something with clear rules. Something that can be proven wrong. If you can't prove it wrong, it's not a hypothesis — it's a hope.",[11,4156,4157],{},"Hypothesis: Markets that are ranging will mean revert within a certain timeframe.\nTest: Build a system that trades mean reversion in ranging markets. Run it. See if it works.",[11,4159,4160],{},"Hypothesis: Trend following works in strong trends.\nTest: Build a system that trades with the trend under certain conditions. Run it. See if it makes money.",[11,4162,4163],{},"If the data says your hypothesis is wrong, you don't double down. You don't tweak parameters until it works. You accept it and move on. That's the difference between trading and gambling. Gamblers double down when they're wrong. Researchers move on.",[11,4165,4166],{},"The system I run now has regime detection, Monte Carlo simulation for stop placement, Markov chain filtering, Kelly Criterion for position sizing, and hard risk limits. 1% max per trade. 3% daily limit.",[11,4168,4169],{},"Does it work all the time? Nothing works all the time. But it loses less when it's wrong. It survives drawdowns. And it's testable — I can run the same tests again and validate the results.",[47,4171,4173],{"id":4172},"the-brutal-truth","The Brutal Truth",[11,4175,4176,4177,4179],{},"Algorithmic trading won't make me invincible. It won't make me rich ",[18,4178,3002],{},". It won't make the market stop doing what it does.",[11,4181,4182],{},"But it makes me consistent. It makes me survive longer. And most importantly, it makes me lose less when I'm wrong. That's the actuarial promise — not that you won't lose, but that you'll lose less than you would have otherwise. Over time, that's everything.",[11,4184,4185],{},"The code is private. Not because it's special, but because it's not finished. It lives on my server somewhere, running, learning, losing less every day.",[11,4187,4188],{},"I'm in it for the long haul.",{"title":383,"searchDepth":384,"depth":384,"links":4190},[4191,4192,4193,4194,4195,4196],{"id":4026,"depth":384,"text":4027},{"id":4079,"depth":384,"text":4080},{"id":4096,"depth":384,"text":4097},{"id":4131,"depth":384,"text":4132},{"id":4150,"depth":384,"text":4151},{"id":4172,"depth":384,"text":4173},"2026-04-15","From ARIMA failures to ML lies to system not strategy. Still learning, still losing sometimes, but now losing less.",{},"\u002Fblog\u002Fquant-journey",{"title":4008,"description":4198},"blog\u002Fquant-journey",[408,4002,3779,4003],"Five years of losing money taught me more than any textbook. Why ARIMA fails, why ML is a lie for most people, why discretionary trading is a trap, and how hypothesis-driven systematic trading finally stopped the bleeding.","0Shkv-TXfX10r_0WPBqucP8u57f26fpbG2z0L_zPRYU",{"id":4207,"title":4208,"body":4209,"date":4399,"description":4400,"extension":398,"featured":399,"meta":4401,"navigation":401,"part":402,"path":4402,"seo":4403,"series":3564,"stem":4404,"tags":4405,"tldr":4406,"__hash__":4407},"blog\u002Fblog\u002Fv-star-interest.md","6.8x Faster, AI That Helped, and Why VBA Needs to Die",{"type":8,"value":4210,"toc":4392},[4211,4217,4221,4231,4234,4237,4243,4249,4255,4261,4264,4268,4271,4277,4280,4291,4294,4298,4305,4308,4312,4315,4324,4327,4333,4339,4345,4351,4360,4363,4366,4370,4373,4376,4383,4386],[11,4212,4213,4214,26],{},"I spent a weekend doing something deeply specific, and I need to tell someone about it. This is about v-star — my actuarial engine in Go — and what happened in a few days of focused work. Some of it is technical. Some of it is philosophical. All of it is ",[18,4215,4216],{},"true enough",[47,4218,4220],{"id":4219},"the-performance-story-first","The Performance Story First",[11,4222,4223,4224,4227,4228],{},"I improved CSV parsing throughput by ",[40,4225,4226],{},"6.8x",". Let me say that again because I still don't fully believe it: from roughly 1.5 million rows per second to about 10.7 million. The parallel version hits 11 million. ",[18,4229,4230],{},"Don't tell anyone but I got a server and it hit 38 million.",[11,4232,4233],{},"For context: a \"row\" is one record in a CSV file. One policy. One customer. One calculation waiting to happen. When you're processing millions of policies, going from 1.5M to 10.7M rows per second changes everything.",[11,4235,4236],{},"Four optimisations got us there:",[11,4238,4239,4242],{},[40,4240,4241],{},"Streaming processor"," — Read and process one line at a time instead of loading the entire file into memory. Less RAM, faster results.",[11,4244,4245,4248],{},[40,4246,4247],{},"Pre-allocated slices"," — Count the fields first, create exactly the right-sized container. No wasted memory, no resizing overhead.",[11,4250,4251,4254],{},[40,4252,4253],{},"String interning"," — Go's unsafe package creates views into existing memory instead of copying bytes to new strings. Same data, no copy. Massive savings at millions of rows.",[11,4256,4257,4260],{},[40,4258,4259],{},"sync.Pool"," — Reuse temporary arrays instead of creating new ones for every row. Less garbage collection pressure.",[11,4262,4263],{},"I also ran benchmarks against Python with Pandas and Polars. I beat Pandas handily. Polars is a hit-or-miss — sometimes I win, sometimes I lose. It's early to say. The point isn't that Python is bad. The point is what you can achieve when you optimise carefully and leverage Go's strengths.",[47,4265,4267],{"id":4266},"ai-actually-helped","AI Actually Helped",[11,4269,4270],{},"I was skeptical about using AI for performance optimisation. Code generation, explanations, debugging — sure. But optimisation felt like it required human intuition, profiler data, and hours of staring at flame graphs.",[11,4272,4273,4274],{},"Then I fed the CSV parser code to an AI and asked it to find unnecessary memory allocations. And it found things. ",[18,4275,4276],{},"A lot of things.",[11,4278,4279],{},"It found byte-to-string conversions in tight loops creating garbage on every iteration. It noticed the sync.Pool was missing entirely. It spotted append operations without capacity pre-allocation. It pattern-matched across the codebase and connected dots I would have found eventually — but not quickly.",[11,4281,4282,4283,4286,4287,4290],{},"Is this cheating? It's called embracing the times. Using AI to find performance issues is the same as using a profiler — a tool that surfaces information you'd find eventually, but faster. The expertise is in knowing ",[18,4284,4285],{},"what"," to fix and ",[18,4288,4289],{},"why"," it matters. The AI just helped me find it.",[11,4292,4293],{},"After implementing the fixes: 6.8x faster. That's not hallucination. That's measurable, reproducible, objective improvement.",[47,4295,4297],{"id":4296},"the-other-stuff-that-happened","The Other Stuff That Happened",[11,4299,4300,4301,4304],{},"I also added four core actuarial packages in that same stretch: interest rate calculations, mortality tables, annuity valuations, and reserve calculations. The mathematical backbone of any actuarial tool. Fixed a bug in CSV parsing where px columns weren't being detected correctly — small detail, but parsing errors flow through everything downstream. Added a ",[23,4302,4303],{},"--table"," flag to the CLI. Updated the README, ROADMAP, and LICENSE for v0.2.0.",[11,4306,4307],{},"Version numbers feel ambitious for something with maybe a dozen users total. But there's something satisfying about formalising progress. It forces you to look at what you've done and call it \"a release.\"",[47,4309,4311],{"id":4310},"why-vba-needs-to-die-and-why-it-wont","Why VBA Needs to Die (And Why It Won't)",[11,4313,4314],{},"Let me talk about something that actually matters.",[11,4316,4317,4318,4320,4321,4323],{},"Visual Basic for Applications is a ",[18,4319,804],{},". It's the programming language embedded in Microsoft Excel. It's been around since 1993. Over 30 years of accumulated technical debt in every insurance company, pension fund, and actuarial firm on the planet. And most actuarial work is ",[18,4322,2175],{}," done in VBA and Excel. Not because it's good. Because it's what everyone knows.",[11,4325,4326],{},"The specific problems:",[11,4328,4329,4332],{},[40,4330,4331],{},"No type safety."," Pass a string where a number belongs and Excel will try its best. Sometimes it works. Sometimes it returns \"type mismatch\" at 2 AM before a deadline. Sometimes it silently does the wrong thing.",[11,4334,4335,4338],{},[40,4336,4337],{},"No version control."," Excel files don't diff well. VBA code stored in Excel modules is invisible to Git. You can't review changes, branch, or merge. Every \"improvement\" is a new file.",[11,4340,4341,4344],{},[40,4342,4343],{},"Single-threaded."," Want to process a million policies? Hope you like waiting.",[11,4346,4347,4350],{},[40,4348,4349],{},"Fragile."," Move a cell? Formula breaks. Rename a sheet? Formula breaks. Change a column order? Formula breaks. The dependencies are invisible and impossible to track.",[11,4352,4353,4356,4357],{},[40,4354,4355],{},"\"It works on my machine.\""," Different Excel versions, regional settings, security settings — code that runs perfectly on your machine fails on your client's. No way to test until production. ",[18,4358,4359],{},"This is the exact reason I wrote v-star in Go with no dependencies.",[11,4361,4362],{},"VBA persists because the institutional knowledge is decades deep, regulators accept it, universities still teach it, and there's no catastrophic failure to force change. When VBA breaks, it breaks quietly. Wrong numbers get caught in review. Nobody dies.",[11,4364,4365],{},"That's why I'm building v-star. Not to replace every spreadsheet — that's unrealistic. But to show that another way exists. Code that's version-controlled, typed, concurrent, and fast. It's possible. You just have to choose to do it.",[47,4367,4369],{"id":4368},"who-this-is-actually-for","Who This Is Actually For",[11,4371,4372],{},"I've been honest with myself about this. v-star probably has a tiny audience. Actuarial science is a niche field. High-performance actuarial software in Go is a niche within a niche.",[11,4374,4375],{},"Maybe it's for students learning actuarial math who want to see how formulas translate to real code. Maybe it's for actuaries tired of waiting 20 minutes for a valuation. Maybe it's just for me — a project that scratches an itch and teaches me Go deeply.",[11,4377,4378,4379,4382],{},"The goal has shifted as I've worked on it. It's no longer about replacing Excel models. It's simpler than that: ",[40,4380,4381],{},"make something that exists."," Something that's fast. Something that proves you can build actuarial software without VBA, without the traditional ecosystem. Something that demonstrates it's possible, even if nobody ever uses it.",[11,4384,4385],{},"There's a strange satisfaction in that. Building something just to prove it can be built. Learning things you'd never learn from tutorials because you're solving problems that actually matter.",[11,4387,3756,4388,4391],{},[264,4389,3540],{"href":266,"rel":4390},[268],". MIT licensed. Use it if it helps.",{"title":383,"searchDepth":384,"depth":384,"links":4393},[4394,4395,4396,4397,4398],{"id":4219,"depth":384,"text":4220},{"id":4266,"depth":384,"text":4267},{"id":4296,"depth":384,"text":4297},{"id":4310,"depth":384,"text":4311},{"id":4368,"depth":384,"text":4369},"2026-03-23","A week of performance optimisation, AI that actually earned its keep, and a rant about actuarial software in 2026.",{},"\u002Fblog\u002Fv-star-interest",{"title":4208,"description":4400},"blog\u002Fv-star-interest",[408,2880,641,3144,407],"6.8x CSV parsing speedup via streaming, string interning, and sync.Pool. AI that actually earned its keep finding memory allocations. Plus a manifesto on why VBA needs to die and actuarial software deserves better.","jYtBnUzx-D53ay927dKxiSTEoFmEO8-BKfuMppuZCnA",{"id":4409,"title":4410,"body":4411,"date":4966,"description":4967,"extension":398,"featured":399,"meta":4968,"navigation":401,"part":384,"path":4969,"seo":4970,"series":4420,"stem":4971,"tags":4972,"tldr":4974,"__hash__":4975},"blog\u002Fblog\u002Fneovim-config.md","My Neovim Config (and How It Actually Works)",{"type":8,"value":4412,"toc":4954},[4413,4422,4426,4442,4446,4457,4477,4480,4484,4490,4504,4510,4524,4547,4561,4582,4622,4632,4640,4650,4660,4690,4700,4720,4724,4731,4735,4762,4766,4773,4824,4828,4831,4835,4838,4881,4899,4903,4928,4934],[11,4414,4415],{},[18,4416,791,4417,4421],{},[264,4418,4420],{"href":4419},"\u002Fblog\u002Fneovim-motions","Neovim Guide",". If you haven't read Part 1 on why Vim motions are worth learning, start there.",[47,4423,4425],{"id":4424},"my-config-where-kickstart-ended-and-opinions-began","My Config: Where Kickstart Ended and Opinions Began",[11,4427,4428,4429,4434,4435,4438,4439],{},"My ",[264,4430,4433],{"href":4431,"rel":4432},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Fnvim",[268],"nvim config"," started from kickstart, but it's evolved into something more structured. It's modularised now — not the single-file approach anymore. The config lives in ",[23,4436,4437],{},"lua\u002Fcustom\u002F"," with separate directories for plugins, LSP, and utilities. The philosophy is still the same though: ",[40,4440,4441],{},"if I don't use it, it's gone. If I can't explain what a line does, it doesn't belong.",[99,4443,4445],{"id":4444},"the-structure","The Structure",[11,4447,4448,4449,4452,4453,4456],{},"The entry point is still one ",[23,4450,4451],{},"init.lua",", but it's lean. It loads keymaps and options immediately, defers the statusline until the first buffer opens, and lazy-loads utility modules (floating terminal, sessions, tabs) in the background. Then it bootstraps ",[23,4454,4455],{},"lazy.nvim"," and loads plugins from two directories:",[229,4458,4459,4465,4471],{},[232,4460,4461,4464],{},[23,4462,4463],{},"lua\u002Fcustom\u002Fplugins\u002F"," — one file per plugin or plugin group",[232,4466,4467,4470],{},[23,4468,4469],{},"lua\u002Fcustom\u002Flsp\u002F"," — LSP config, autocomplete, and formatting",[232,4472,4473,4476],{},[23,4474,4475],{},"lua\u002Fcustom\u002Futil\u002F"," — floating terminal, session management, tabs",[11,4478,4479],{},"Everything is lazy-loaded by default. A plugin only loads when you actually trigger it. Startup is fast because nothing is loaded until you need it.",[99,4481,4483],{"id":4482},"plugins-whats-actually-in-there","Plugins (What's Actually In There)",[11,4485,4486,4487,4489],{},"Let me walk through what I'm running and ",[18,4488,4289],{}," — because every plugin earns its spot.",[11,4491,4492,4495,4496,4499,4500,4503],{},[40,4493,4494],{},"Fzf-lua"," — The fuzzy finder. Files, grep, buffers, git commits, diagnostics, help tags — everything goes through Fzf-lua. It's fast and vim's built-in code action menus use the Fzf-lua UI instead of that ugly default popup. Keybindings are all under ",[23,4497,4498],{},"\u003Cleader>s"," for search and ",[23,4501,4502],{},"\u003Cleader>g"," for git.",[11,4505,4506,4509],{},[40,4507,4508],{},"Treesitter"," — Syntax highlighting that actually understands your code. Instead of regex (which is like reading a book one letter at a time), Treesitter parses your code into a tree structure. Highlighting, indentation, and code context all make sense now. Auto-installs parsers for whatever language you open.",[11,4511,4512,4515,4516,4519,4520,4523],{},[40,4513,4514],{},"Flash"," — Press ",[23,4517,4518],{},"s"," and type a few characters — every match on screen gets a label. Type the label, you're there. It's like easymotion but actually good. Works in normal, visual, and operator-pending modes. ",[23,4521,4522],{},"S"," does the same thing but for Treesitter nodes, so you can jump to specific code structures.",[11,4525,4526,4529,4530,892,4533,892,4536,4539,4540,1217,4543,4546],{},[40,4527,4528],{},"Blink.cmp"," — Autocomplete engine. I switched from nvim-cmp to blink because it's faster and the config is cleaner. Sources are LSP, path, snippets, and buffer. Completion menu shows the source (",[839,4531,4532],{},"lsp",[839,4534,4535],{},"snip",[839,4537,4538],{},"buf",") so you know where suggestions come from. Auto-brackets are enabled. ",[23,4541,4542],{},"Tab",[23,4544,4545],{},"Shift-Tab"," cycle through completions and jump snippets.",[11,4548,4549,4552,4553,4556,4557,4560],{},[40,4550,4551],{},"Conform"," — Formatter. Runs on save. ",[23,4554,4555],{},"stylua"," for Lua, ",[23,4558,4559],{},"biome"," for JavaScript\u002FTypeScript\u002FVue\u002FHTML\u002FCSS\u002FJSON. No config files to maintain — it just works.",[11,4562,4563,4566,4567,1217,4570,4573,4574,4577,4578,4581],{},[40,4564,4565],{},"Gitsigns"," — Git diff indicators in the gutter. But the real killer feature is inline blame — every line shows who last touched it and when. ",[23,4568,4569],{},"]c",[23,4571,4572],{},"[c"," jump between hunks. ",[23,4575,4576],{},"\u003Cleader>hs"," stages a hunk. ",[23,4579,4580],{},"\u003Cleader>hp"," previews it. Full git workflow without leaving the buffer.",[11,4583,4584,4587,4588,4591,4592,4595,4596,4599,4600,4599,4603,1030,4606,4609,4610,4613,4614,4617,4618,4621],{},[40,4585,4586],{},"Mini.nvim"," — A bundle of small, focused plugins. I use: ",[23,4589,4590],{},"mini.pairs"," (auto-close brackets), ",[23,4593,4594],{},"mini.surround"," (add\u002Fdelete\u002Freplace surrounds with ",[23,4597,4598],{},"gsa","\u002F",[23,4601,4602],{},"gsd",[23,4604,4605],{},"gsr",[23,4607,4608],{},"mini.ai"," (enhanced text objects), ",[23,4611,4612],{},"mini.comment"," (toggle comments), ",[23,4615,4616],{},"mini.indentscope"," (visual indent guides), and ",[23,4619,4620],{},"mini.notify"," (clean notification popups). One import, six plugins, zero bloat.",[11,4623,4624,4627,4628,4631],{},[40,4625,4626],{},"Which-key"," — Shows available keybindings as you press them. Uses the ",[23,4629,4630],{},"helix"," preset for a clean popup. This is how you learn your own config without memorising everything upfront.",[11,4633,4634,4515,4637,4639],{},[40,4635,4636],{},"Oil.nvim",[23,4638,999],{}," and you get a file browser that works like a buffer. Rename files by editing text. Create files by typing a new line. Delete by removing the line. It's the most intuitive file manager I've used.",[11,4641,4642,4645,4646,4649],{},[40,4643,4644],{},"Neo-tree"," — Toggle with ",[23,4647,4648],{},"\u003Cleader>e",". Opens on the right side. I use Oil more, but Neo-tree is there for when I want a visual overview of the project structure.",[11,4651,4652,4655,4656,4659],{},[40,4653,4654],{},"Render-markdown"," — When you open a ",[23,4657,4658],{},".md"," file, it renders headings, checkboxes, code blocks, and inline formatting with proper styling. Writing markdown in Neovim finally looks good.",[11,4661,4662,4665,4666,892,4669,892,4672,892,4675,4678,4679,1217,4682,4685,4686,4689],{},[40,4663,4664],{},"Todo-comments"," — Highlights ",[23,4667,4668],{},"TODO",[23,4670,4671],{},"FIXME",[23,4673,4674],{},"HACK",[23,4676,4677],{},"NOTE"," in your code. ",[23,4680,4681],{},"]t",[23,4683,4684],{},"[t"," jump between them. ",[23,4687,4688],{},"\u003Cleader>st"," searches all todos across the project via Telescope.",[11,4691,4692,4695,4696,4699],{},[40,4693,4694],{},"ts-autotag"," — Auto-closes and renames HTML\u002FJSX tags. Type ",[23,4697,4698],{},"\u003Cdiv>"," and the closing tag appears. Rename the opening tag, the closing tag updates. Small thing, big quality of life.",[11,4701,4702,4705,4706,4556,4709,4712,4713,4715,4716,4719],{},[40,4703,4704],{},"LSP (nvim-lspconfig + Mason)"," — ",[23,4707,4708],{},"lua_ls",[23,4710,4711],{},"ts_ls"," for TypeScript\u002FJavaScript (with Vue support via the Vue language server plugin), ",[23,4714,4555],{}," for formatting. Mason auto-installs what's needed. ",[23,4717,4718],{},"fidget.nvim"," shows LSP progress in the corner so you know when the server is doing something.",[99,4721,4723],{"id":4722},"hand-built-statusline-no-plugin-needed","Hand-Built Statusline (No Plugin Needed)",[11,4725,4726,4727,4730],{},"I don't use lualine or any statusline plugin. It's a hand-written Lua function in ",[23,4728,4729],{},"statusline.lua"," that renders directly into Neovim's native statusline. Shows the mode (colour-coded — violet for normal, blue for insert, cyan for visual), git branch (from gitsigns), diagnostics count, current file, LSP client name, filetype, line\u002Fcolumn, and scroll percentage. About 150 lines of Lua. No dependencies. Loads after the first buffer to save ~5-10ms on startup.",[99,4732,4734],{"id":4733},"custom-utilities","Custom Utilities",[229,4736,4737,4750,4756],{},[232,4738,4739,4705,4742,4745,4746,4749],{},[40,4740,4741],{},"Floating terminal",[23,4743,4744],{},"\u003Cleader>tt"," toggles a floating terminal at 80% of the editor size. Reuses the same buffer. Auto-closes when the shell exits. ",[23,4747,4748],{},"Esc Esc"," exits terminal mode.",[232,4751,4752,4755],{},[40,4753,4754],{},"Sessions"," — Auto-saves and restores your session so you pick up where you left off.",[232,4757,4758,4761],{},[40,4759,4760],{},"Tabs"," — Better tab\u002Fbuffer management.",[99,4763,4765],{"id":4764},"sensible-defaults-the-boring-stuff-that-matters","Sensible Defaults (The Boring Stuff That Matters)",[11,4767,4768,4769,4772],{},"A few things in ",[23,4770,4771],{},"options.lua"," that make a real difference:",[229,4774,4775,4778,4784,4787,4796,4799,4802,4812,4818],{},[232,4776,4777],{},"Relative line numbers — you know how far away things are",[232,4779,4780,4783],{},[23,4781,4782],{},"scrolloff = 10"," — keeps the cursor 10 lines from the edge so context is always visible",[232,4785,4786],{},"Transparent background — the editor background matches your terminal",[232,4788,4789,4792,4793,4795],{},[23,4790,4791],{},"cmdheight = 0"," — hides the command line until you press ",[23,4794,2365],{},", gives you an extra line of code",[232,4797,4798],{},"Cursorline only in the active window — the focused window has a highlighted line, others don't",[232,4800,4801],{},"Smooth scrolling — because why not",[232,4803,4804,4807,4808,4811],{},[23,4805,4806],{},"; as :"," — press ",[23,4809,4810],{},";"," to enter command mode. One less keypress every time.",[232,4813,4814,4817],{},[23,4815,4816],{},"Alt+j\u002Fk"," to move lines up\u002Fdown — because sometimes you just need to reorder things fast",[232,4819,4820,4823],{},[23,4821,4822],{},"Ctrl+d\u002Fu"," centres after half-page jump — no more losing your cursor",[99,4825,4827],{"id":4826},"disabled-built-in-plugins","Disabled Built-In Plugins",[11,4829,4830],{},"Neovim ships with plugins most people never use. I disable: gzip, tar, zip, netrw, tohtml, matchit, matchparen, rplugin, editorconfig, man, and spellfile. Each one is a tiny startup cost that adds up. If I need them, I can re-enable them. So far, I haven't.",[99,4832,4834],{"id":4833},"how-it-can-help-you-if-youre-new","How It Can Help You If You're New",[11,4836,4837],{},"If you're just starting to code, here's what this config gives you out of the box:",[229,4839,4840,4846,4852,4858,4864,4870,4876],{},[232,4841,4842,4845],{},[40,4843,4844],{},"Autocomplete"," that actually works without being annoying",[232,4847,4848,4851],{},[40,4849,4850],{},"Errors highlighted"," in real-time so you catch mistakes before running the code",[232,4853,4854,4857],{},[40,4855,4856],{},"Fast file navigation"," — no more scrolling through a file tree, just fuzzy search",[232,4859,4860,4863],{},[40,4861,4862],{},"Git integration"," built right into the editor — blame, stage, diff, without leaving your code",[232,4865,4866,4869],{},[40,4867,4868],{},"A statusline that tells you everything"," — mode, branch, errors, file, position",[232,4871,4872,4875],{},[40,4873,4874],{},"A floating terminal"," — run commands without leaving Neovim",[232,4877,4878,4880],{},[40,4879,4626],{}," — shows you what keybindings are available as you press keys",[11,4882,4883,4884,4886,4887,4890,4891,4894,4895,4898],{},"You don't need to understand everything on day one. Clone the config, open Neovim, start writing code. Which-key will guide you. Learn the motions as you go. ",[23,4885,1443],{}," to insert, ",[23,4888,4889],{},"Esc"," to go back to normal mode, ",[23,4892,4893],{},":w"," to save, ",[23,4896,4897],{},":q"," to quit. That's enough to get started. The rest comes naturally.",[47,4900,4902],{"id":4901},"the-how-do-i-exit-vim-section","The \"How Do I Exit Vim\" Section",[11,4904,4905,4906,4908,4909,4912,4913,4916,4917,4920,4921,4924,4925,4927],{},"Just kidding. It's ",[23,4907,4897],{},". Or ",[23,4910,4911],{},":wq"," to save and quit. Or ",[23,4914,4915],{},"ZZ"," if you're feeling fancy. Or ",[23,4918,4919],{},":qa!"," if you're having a mental breakdown. Or ",[23,4922,4923],{},"\u003Cleader>x"," in my config because I got tired of typing ",[23,4926,4911],{}," like a caveman.",[11,4929,4930,4931,26],{},"In all seriousness — the learning curve is real, but it's front-loaded. You struggle for a week, then you're productive, then you're faster than you were before, and you never look back. It's like learning to ride a bike. Except the bike is your text editor, and falling down is accidentally deleting 47 lines with ",[23,4932,4933],{},"dG",[11,4935,4936,4937,4941,4942,4947,4948,4953],{},"If you want a starting point, ",[264,4938,4940],{"href":4431,"rel":4939},[268],"my config"," is on GitHub. ",[264,4943,4946],{"href":4944,"rel":4945},"https:\u002F\u002Fgithub.com\u002Fnvim-lua\u002Fkickstart.nvim",[268],"Kickstart.nvim"," is where it all began — and ",[264,4949,4952],{"href":4950,"rel":4951},"https:\u002F\u002Fyoutu.be\u002Fm8C0Cq9Uv9o",[268],"this video"," is the best walkthrough I've found for getting started from scratch.",{"title":383,"searchDepth":384,"depth":384,"links":4955},[4956,4965],{"id":4424,"depth":384,"text":4425,"children":4957},[4958,4959,4960,4961,4962,4963,4964],{"id":4444,"depth":390,"text":4445},{"id":4482,"depth":390,"text":4483},{"id":4722,"depth":390,"text":4723},{"id":4733,"depth":390,"text":4734},{"id":4764,"depth":390,"text":4765},{"id":4826,"depth":390,"text":4827},{"id":4833,"depth":390,"text":4834},{"id":4901,"depth":384,"text":4902},"2026-03-21","A deep dive into a modular Neovim configuration, exploring the power of Vim motions, lazy-loading plugins, and building a high-efficiency development environment.",{},"\u002Fblog\u002Fneovim-config",{"title":4410,"description":4967},"blog\u002Fneovim-config",[4973,779],"editor","Inside my modular Neovim config: lazy-loading plugins, LSP setup, a hand-written statusline, floating terminal, session management, and the sensible defaults that make it all work. No bloat, no black magic.","9goe4zwZWW-DXKxeprZOSaedTgya6Hps8HOTqI-bArM",{"id":4977,"title":4978,"body":4979,"date":4966,"description":5119,"extension":398,"featured":399,"meta":5120,"navigation":401,"part":842,"path":4419,"seo":5121,"series":4420,"stem":5122,"tags":5123,"tldr":5124,"__hash__":5125},"blog\u002Fblog\u002Fneovim-motions.md","Vim Motions Actually Work (And Why You Should Try Them)",{"type":8,"value":4980,"toc":5114},[4981,4991,4995,5006,5025,5028,5032,5039,5046,5049,5053,5056,5083,5090,5093,5095,5103],[11,4982,4983,4986,4987,4990],{},[18,4984,4985],{},"I use Neovim."," There, I said it. Before you click away thinking \"oh great, another Vim user telling me my IDE is garbage\" — sit down, shut up, and hear me out. I promise I won't say ",[23,4988,4989],{},"hjkl"," more than... okay, I'll say it a few times. But there's a reason.",[47,4992,4994],{"id":4993},"why-vim-motions-are-actually-insane","Why Vim Motions Are Actually Insane",[11,4996,4997,4998,5001,5002,5005],{},"Here's the thing nobody tells you when you're starting out as a developer: you spend roughly 90% of your time ",[18,4999,5000],{},"reading"," code and ",[18,5003,5004],{},"moving around"," files, not actually typing new code. The bottleneck isn't your typing speed. It's how fast you can navigate, select, delete, rearrange, and manipulate the text that's already there.",[11,5007,5008,5009,5012,5013,5016,5017,5020,5021,5024],{},"Vim motions are like giving your keyboard a PhD in text manipulation. ",[23,5010,5011],{},"ciw"," — change inner word. ",[23,5014,5015],{},"da{"," — delete around braces. ",[23,5018,5019],{},"yap"," — yank a paragraph. You're not pressing keys one at a time anymore; you're ",[18,5022,5023],{},"composing commands",". It's like going from pointing at letters on a whiteboard to writing actual sentences.",[11,5026,5027],{},"Does it feel weird at first? Absolutely. You will open Vim, try to type, and nothing will happen. You will Google \"how to exit Vim\" at 2 AM and question every life decision that led you here. That's normal. That's the initiation ritual. After about a week, something clicks, and suddenly your mouse feels like a steering wheel made of wet spaghetti.",[47,5029,5031],{"id":5030},"but-its-not-for-everyone-and-thats-fine","But It's Not for Everyone (And That's Fine)",[11,5033,5034,5035,5038],{},"Look, I'm not going to pretend Vim motions are the answer to all your problems. If you're happy in VS Code, if your workflow is smooth, if you're shipping code and solving problems — you don't ",[18,5036,5037],{},"need"," Vim. Vim motions won't fix your broken TypeScript types. They won't debug your React re-renders. They won't make your Python script run faster.",[11,5040,5041,5042,5045],{},"But if you've ever felt like your editor is slowing you down — if you find yourself reaching for the mouse every 3 seconds, if you're tired of clicking through menus, if you want to feel like you're actually ",[18,5043,5044],{},"controlling"," your code instead of just editing it — then Vim motions might be worth a look.",[11,5047,5048],{},"The best part? You don't even need to use Neovim to try them.",[47,5050,5052],{"id":5051},"the-try-before-you-commit-starter-pack","The \"Try Before You Commit\" Starter Pack",[11,5054,5055],{},"Before going full Neovim, try Vim motions in your current editor:",[229,5057,5058,5068,5077],{},[232,5059,5060,5063,5064,5067],{},[40,5061,5062],{},"VS Code"," — Install the ",[23,5065,5066],{},"vscodevim"," extension. That's it. You get Vim keybindings inside VS Code. Keep your extensions, keep your theme, keep your comfort. Just start moving faster.",[232,5069,5070,5073,5074,5076],{},[40,5071,5072],{},"Zed"," — Has built-in Vim mode. Toggle it on, and you're good. Zed is fast as hell too, so you get Vim motions ",[18,5075,1745],{}," a modern editor.",[232,5078,5079,5082],{},[40,5080,5081],{},"JetBrains"," — The IdeaVim plugin. Same deal. Vim inside your existing IDE.",[11,5084,5085,5086,26],{},"If after a month you're thinking \"okay, this is actually kind of nice\" — then maybe it's time for Neovim. And if you want a starting point that won't make you want to throw your laptop into the sea, use ",[264,5087,5089],{"href":4944,"rel":5088},[268],"kickstart.nvim",[11,5091,5092],{},"Kickstart is a single-file Neovim config maintained by TJ DeVries (the guy who basically built half of the Neovim ecosystem). It's not a distribution — it's a starting point. One file. You read it, you understand it, you customise it. No 400 plugins you'll never configure. No black magic. Just Lua code you can actually read.",[366,5094],{},[11,5096,5097,276,5099,5102],{},[40,5098,1982],{},[264,5100,5101],{"href":4969},"Part 2 — My Neovim Config (and How It Actually Works)"," — a full walkthrough of my modular config, every plugin and why it's there, the hand-built statusline, and the sensible defaults that make the whole thing tick.",[11,5104,4936,5105,4941,5108,4947,5111,4953],{},[264,5106,4940],{"href":4431,"rel":5107},[268],[264,5109,4946],{"href":4944,"rel":5110},[268],[264,5112,4952],{"href":4950,"rel":5113},[268],{"title":383,"searchDepth":384,"depth":384,"links":5115},[5116,5117,5118],{"id":4993,"depth":384,"text":4994},{"id":5030,"depth":384,"text":5031},{"id":5051,"depth":384,"text":5052},"Why Vim motions improve your workflow, how to try them in VS Code or Zed, and a walkthrough of my minimal Neovim config.",{},{"title":4978,"description":5119},"blog\u002Fneovim-motions",[4973,779],"Vim motions are the fastest way to navigate and manipulate code. You don't need Neovim to try them — VS Code, Zed, and JetBrains all support Vim keybindings. Start here before going full terminal.","7iSL64ok3xu8u814nve3NZqcY3yCcqEp6ukU3i4DxNM",{"id":5127,"title":5128,"body":5129,"date":5260,"description":5261,"extension":398,"featured":399,"meta":5262,"navigation":401,"part":402,"path":5263,"seo":5264,"series":3564,"stem":5265,"tags":5266,"tldr":5267,"__hash__":5268},"blog\u002Fblog\u002Fv-star.md","v-star: The Actuarial Engine That Made Me Stop Using Excel",{"type":8,"value":5130,"toc":5255},[5131,5134,5137,5140,5144,5154,5160,5163,5167,5173,5179,5185,5231,5234,5238,5241,5244,5252],[11,5132,5133],{},"Most actuarial software is either an enterprise tool that costs six figures or an Excel model that breaks when you breathe on it. I've spent enough time in both to know there's a gap you could drive a truck through.",[11,5135,5136],{},"So I built v-star.",[11,5138,5139],{},"It's a high-performance actuarial engine in Go. Zero dependencies. Just the standard library. It handles concurrent financial simulations, mass policy valuations, and Monte Carlo interest rate modeling at speeds that'll make you wonder why you ever tolerated spreadsheets.",[47,5141,5143],{"id":5142},"the-name","The Name",[11,5145,5146,5147,5150,5151,5153],{},"v-star (v*) is actuarial notation. When an annuity's payments compound at rate ",[18,5148,5149],{},"j"," but are discounted at rate ",[18,5152,1443],{},", the adjusted discount factor is:",[809,5155,5158],{"className":5156,"code":5157,"language":814},[812],"v* = (1 + j) · v\n",[23,5159,5157],{"__ignoreMap":383},[11,5161,5162],{},"It's an inside joke from university — one my lecturer and coursemates will recognise. If the math is niche enough, the project name should be too.",[47,5164,5166],{"id":5165},"what-it-actually-does","What It Actually Does",[11,5168,5169,5172],{},[40,5170,5171],{},"Policy valuation"," — Stream-processes CSV files of insurance policies and calculates present values. Handles over a million records in under 300ms. It streams data instead of loading everything into RAM, so your laptop won't start sounding like a jet engine.",[11,5174,5175,5178],{},[40,5176,5177],{},"Monte Carlo simulation"," — Generates interest rate paths for stochastic modeling. 100,000 paths with 10 time steps in about 100ms. Uses geometric Brownian motion with configurable drift and volatility. You can sit there tweaking parameters and watching it churn through simulations like they're nothing.",[11,5180,5181,5184],{},[40,5182,5183],{},"CLI-first"," — No web dashboard, no config files, no GUI. Just flags and pipes:",[809,5186,5188],{"className":3659,"code":5187,"language":3661,"meta":383,"style":383},".\u002Fv-star -i 0.05 -j 0.02\n.\u002Fv-star read policies.csv --benchmark\n.\u002Fv-star montecarlo --paths=100000 --steps=10\n",[23,5189,5190,5207,5218],{"__ignoreMap":383},[839,5191,5192,5195,5198,5201,5204],{"class":841,"line":842},[839,5193,5194],{"class":899},".\u002Fv-star",[839,5196,5197],{"class":895}," -i",[839,5199,5200],{"class":899}," 0.05",[839,5202,5203],{"class":895}," -j",[839,5205,5206],{"class":899}," 0.02\n",[839,5208,5209,5211,5213,5216],{"class":841,"line":384},[839,5210,5194],{"class":899},[839,5212,3683],{"class":895},[839,5214,5215],{"class":895}," policies.csv",[839,5217,3689],{"class":895},[839,5219,5220,5222,5225,5228],{"class":841,"line":390},[839,5221,5194],{"class":899},[839,5223,5224],{"class":895}," montecarlo",[839,5226,5227],{"class":895}," --paths=100000",[839,5229,5230],{"class":895}," --steps=10\n",[11,5232,5233],{},"Everything is composable. Pipe stuff in, get stuff out. Unix philosophy applied to actuarial science.",[47,5235,5237],{"id":5236},"why-this-exists","Why This Exists",[11,5239,5240],{},"I got tired of tools that treat actuarial work like it needs to be hidden behind layers of abstraction. The math isn't that complicated. The data isn't that special. What's hard is doing it fast, at scale, and in a way you can actually audit.",[11,5242,5243],{},"v-star is the opposite of a black box. Every calculation is readable. Every assumption is visible. No hidden columns, no macros that run in the background, no \"we've always done it this way.\"",[11,5245,5246,5247,5251],{},"The code is on ",[264,5248,5250],{"href":266,"rel":5249},[268],"GitHub",". Go read it. Or don't. But if you're still running actuarial models in Excel, ask yourself why.",[1823,5253,5254],{},"html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":383,"searchDepth":384,"depth":384,"links":5256},[5257,5258,5259],{"id":5142,"depth":384,"text":5143},{"id":5165,"depth":384,"text":5166},{"id":5236,"depth":384,"text":5237},"2026-03-20","A zero-dependency Go engine for concurrent financial simulations. 1M policies in under 300ms. No spreadsheets required.",{},"\u002Fblog\u002Fv-star",{"title":5128,"description":5261},"blog\u002Fv-star",[408,3144,3779],"A zero-dependency Go engine for concurrent financial simulations. 1M policies in under 300ms, Monte Carlo at 100k paths in ~100ms, CLI-first with composable Unix-style pipes. The spreadsheet killer nobody asked for but everyone needs.","UgvDaJsEpmRXowsQBxYfsr4htNZZddKMz4fsyBitB3Q",{"id":5270,"title":5271,"body":5272,"date":5426,"description":5427,"extension":398,"featured":399,"meta":5428,"navigation":401,"part":402,"path":5429,"seo":5430,"series":5431,"stem":5432,"tags":5433,"tldr":5434,"__hash__":5435},"blog\u002Fblog\u002Fwhypython.md","Python: The Language I Can't Quit (And Why You Shouldn't Either)",{"type":8,"value":5273,"toc":5418},[5274,5277,5284,5287,5291,5294,5297,5323,5326,5330,5333,5336,5339,5343,5349,5355,5367,5373,5377,5383,5386,5389,5393,5402,5405,5409,5412,5415],[11,5275,5276],{},"I spend a lot of time talking about Go — how fast it is, how clean the concurrency model is, how you get a single binary at the end. All true. I stand by it.",[11,5278,5279,5280,5283],{},"But Python is still the language I reach for first when I need to ",[18,5281,5282],{},"think"," about a problem.",[11,5285,5286],{},"Go is what I use when I know what I'm building. Python is what I use when I'm figuring out what to build. They serve different purposes, and pretending otherwise is dishonest.",[47,5288,5290],{"id":5289},"why-python-keeps-winning","Why Python Keeps Winning",[11,5292,5293],{},"The syntax is the obvious answer. Python reads like pseudocode that actually runs. You write a loop and it makes sense. You import a library and it works (usually). The barrier between \"having an idea\" and \"seeing results\" is lower than any other language I've used.",[11,5295,5296],{},"But the real reason Python dominates — especially in actuarial and quant work — is the ecosystem. It's not even close.",[229,5298,5299,5305,5311,5317],{},[232,5300,5301,5304],{},[40,5302,5303],{},"NumPy and Pandas"," for numerical work. Handles data at scales that would make Excel cry.",[232,5306,5307,5310],{},[40,5308,5309],{},"SciPy and scikit-learn"," for statistical modeling and machine learning.",[232,5312,5313,5316],{},[40,5314,5315],{},"Matplotlib and Plotly"," for visualisation that doesn't make you want to gouge your eyes out.",[232,5318,5319,5322],{},[40,5320,5321],{},"TensorFlow and PyTorch"," when you need to go deeper.",[11,5324,5325],{},"There's no equivalent density of high-quality libraries in any other language. R comes closest, but the tooling is worse and the deployment story is a nightmare. Go has a small but high-quality ecosystem that covers most needs, but it doesn't have Pandas. And sometimes you just need Pandas.",[47,5327,5329],{"id":5328},"the-gil-is-real-but-its-not-a-dealbreaker","The GIL Is Real, But It's Not a Dealbreaker",[11,5331,5332],{},"Everyone loves to hate on Python's Global Interpreter Lock. And sure, it's a real limitation. You can't use threads for CPU-bound work because only one thread runs at a time. For parallel number crunching, you need multiprocessing, which comes with its own headaches.",[11,5334,5335],{},"But here's the thing: NumPy bypasses the GIL. It's written in C and Fortran, and it doesn't play by Python's rules. For most actuarial and data science work, you're spending your time in NumPy operations anyway, so the GIL is mostly irrelevant.",[11,5337,5338],{},"The GIL matters when you're writing custom loops over millions of items. If that's your workload, use Go. If you're doing matrix operations, statistical modeling, or data wrangling, Python is fine.",[47,5340,5342],{"id":5341},"what-i-actually-use-it-for","What I Actually Use It For",[11,5344,5345,5348],{},[40,5346,5347],{},"Exploratory data analysis"," — Jupyter notebooks are still the best environment for poking at data and figuring out what's going on. I start there, get a feel for the data, then formalise the logic into proper modules.",[11,5350,5351,5354],{},[40,5352,5353],{},"Prototyping"," — When I'm testing a new pricing model or a trading strategy, I prototype in Python first. It's faster to iterate. Once the logic is solid, I might rewrite the performance-critical parts in Go.",[11,5356,5357,5360,5361,5366],{},[40,5358,5359],{},"Automation"," — Python's standard library is incredibly rich. File management (I built ",[264,5362,5365],{"href":5363,"rel":5364},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Fgniphyl",[268],"gniphyl"," for this), data processing, API wrappers — Python handles the glue code that connects all the pieces.",[11,5368,5369,5372],{},[40,5370,5371],{},"Plotting"," — Matplotlib and Plotly produce publication-quality charts with reasonable effort. R is better for statistical plots, but Python is good enough, and I don't have to switch languages to get it.",[47,5374,5376],{"id":5375},"when-not-to-use-it","When NOT to Use It",[11,5378,5379,5380,26],{},"I've learned this the hard way. Python is not a systems language. It's not for high-frequency trading, real-time processing, or anything that needs to run reliably for months without intervention. The deployment story is fragile — virtual environments, dependency conflicts, Python version mismatches. It's manageable, but it's never ",[18,5381,5382],{},"pleasant",[11,5384,5385],{},"If you're building a production service that needs to handle load, or a simulation engine that processes millions of policies, or anything where performance and reliability are critical — reach for Go, Rust, or even Java before Python.",[11,5387,5388],{},"But for everything else? Python is fine. More than fine. It's the most productive language I know for the 80% of work that doesn't need C-level performance.",[47,5390,5392],{"id":5391},"the-tooling-keeps-getting-better","The Tooling Keeps Getting Better",[11,5394,5395,5396,5401],{},"The latest development that's worth paying attention to: ",[264,5397,5400],{"href":5398,"rel":5399},"https:\u002F\u002Fdocs.astral.sh\u002Fuv",[268],"uv",". It's a Python package manager written in Rust, by the same team that built Ruff. It's 10-100x faster than pip, handles environments and dependencies with a single tool, and generates proper lockfiles.",[11,5403,5404],{},"No more 3 AM pip resolution errors. No more \"I don't know what version of anything is installed.\" Uv fixes the worst part of the Python experience.",[47,5406,5408],{"id":5407},"the-bottom-line","The Bottom Line",[11,5410,5411],{},"Python isn't going anywhere. It dominates data science, machine learning, and actuarial modeling for good reasons. The ecosystem is unmatched, the productivity is real, and the tooling is finally catching up to the language's ambitions.",[11,5413,5414],{},"I use Go for systems. I use Python for exploration. I use Excel only when someone pays me to. That balance has served me well, and I'd recommend something similar to anyone doing quantitative work.",[11,5416,5417],{},"Learn Python first. Then learn Go for when you need to ship. And leave VBA in the grave where it belongs.",{"title":383,"searchDepth":384,"depth":384,"links":5419},[5420,5421,5422,5423,5424,5425],{"id":5289,"depth":384,"text":5290},{"id":5328,"depth":384,"text":5329},{"id":5341,"depth":384,"text":5342},{"id":5375,"depth":384,"text":5376},{"id":5391,"depth":384,"text":5392},{"id":5407,"depth":384,"text":5408},"2025-10-29","A love letter to the language that bridges actuarial theory and real software — GIL and all.",{},"\u002Fblog\u002Fwhypython",{"title":5271,"description":5427},"Actuarial Engineering","blog\u002Fwhypython",[408,835,407],"Python is still the language I reach for first when I need to think. NumPy, Pandas, prototyping, and the unmatched ecosystem. The GIL is real but mostly irrelevant. When to use Python, when to use Go, and why uv finally fixes the packaging nightmare.","JL-huBjxAjipnDXgreefA-A3TjFvLYknLPgxvY9QX8A",{"id":5437,"title":5438,"body":5439,"date":5518,"description":5519,"extension":398,"featured":399,"meta":5520,"navigation":401,"part":402,"path":5521,"seo":5522,"series":402,"stem":5523,"tags":5524,"tldr":5525,"__hash__":5526},"blog\u002Fblog\u002Factuworry.md","Actuworry: Because Spreadsheets Shouldn't Be That Hard",{"type":8,"value":5440,"toc":5513},[5441,5448,5451,5458,5462,5476,5479,5483,5486,5489,5491,5505],[11,5442,5443,5444,5447],{},"Every actuarial student hits the same wall. You learn the theory — pricing, reserving, forecasting — and then you try to actually ",[18,5445,5446],{},"build"," something with it. Excel is too fragile. The enterprise tools cost more than your tuition. R crashes when you look at it wrong.",[11,5449,5450],{},"So I built Actuworry.",[11,5452,5453,5454,5457],{},"It's not a replacement for Prophet or Axis or any of those million-dollar actuarial platforms. It's something simpler: a Go-based simulation engine that models core actuarial functions — pricing, reserving, forecasting — in a way that's transparent, extensible, and ",[18,5455,5456],{},"actually works"," without a support contract.",[47,5459,5461],{"id":5460},"what-it-does","What It Does",[229,5463,5464,5467,5470,5473],{},[232,5465,5466],{},"Basic reserving calculations that don't hide the math behind a GUI",[232,5468,5469],{},"Pricing models where you can actually see the assumptions",[232,5471,5472],{},"Stochastic simulations (still building this out)",[232,5474,5475],{},"Clean Go modules that map directly to actuarial workflows",[11,5477,5478],{},"Everything uses the Go standard library. No frameworks, no dependency hell, no \"works on my machine.\" You clone it, you build it, you run it.",[47,5480,5482],{"id":5481},"why-go","Why Go?",[11,5484,5485],{},"Because actuarial work is math, and math should be fast. Go compiles to native code. It handles concurrency without making you cry. It produces a single binary with no runtime dependencies. You can send it to a colleague and it just works. Try doing that with a Python script.",[11,5487,5488],{},"Also, I wanted to learn Go deeply. Building real financial systems beats reading tutorials every time.",[47,5490,3750],{"id":3749},[229,5492,5493,5496,5499,5502],{},[232,5494,5495],{},"Monte Carlo simulations for stochastic reserving",[232,5497,5498],{},"Better documentation and examples",[232,5500,5501],{},"Maybe a web UI with Vue for the visual learners",[232,5503,5504],{},"Benchmarks against spreadsheet workflows (spoiler: Go wins)",[11,5506,5507,5508,5512],{},"Actuworry is on ",[264,5509,5250],{"href":5510,"rel":5511},"https:\u002F\u002Fgithub.com\u002Flubasinkal\u002Factuworry",[268],". Fork it, star it, or just read the code. It's open for a reason — actuarial knowledge shouldn't be locked behind expensive licenses and opaque black boxes.",{"title":383,"searchDepth":384,"depth":384,"links":5514},[5515,5516,5517],{"id":5460,"depth":384,"text":5461},{"id":5481,"depth":384,"text":5482},{"id":3749,"depth":384,"text":3750},"2025-08-06","A Go-powered actuarial simulation tool that started as a frustration and turned into something useful.",{},"\u002Fblog\u002Factuworry",{"title":5438,"description":5519},"blog\u002Factuworry",[408,3144,3779],"A Go-powered actuarial simulation engine built out of pure frustration with Excel fragility and six-figure enterprise tools. Pricing, reserving, forecasting — zero dependencies, one binary, no support contract required.","qvEdKxZOwzTTPmmXjqyDhHuGsVCvyDmV6Hzn-bnEcj0",{"id":5528,"title":5529,"body":5530,"date":5518,"description":6137,"extension":398,"featured":399,"meta":6138,"navigation":401,"part":402,"path":375,"seo":6139,"series":5431,"stem":6140,"tags":6141,"tldr":6142,"__hash__":6143},"blog\u002Fblog\u002Fwhygolang.md","Why I Use Go (And You Probably Should Too)",{"type":8,"value":5531,"toc":6129},[5532,5535,5545,5548,5552,5555,5558,5743,5749,5753,5756,5771,5778,5781,5785,5788,5791,5794,5797,5801,5804,5854,5857,5870,5874,5877,6094,6097,6101,6104,6111,6123,6126],[11,5533,5534],{},"Every actuarial student learns the same workflow. You build your model in Excel, validate it in R, and present it in PowerPoint. If you're feeling fancy, maybe you write some Python. And you spend your entire career praying the spreadsheet doesn't corrupt itself.",[11,5536,5537,5538,5540,5541,5544],{},"I went through that pipeline. I wrote thousands of Excel formulas. I built R models that crashed with \"",[839,5539,1656],{}," NA\" and no further explanation. I debugged NumPy code in Jupyter notebooks at 3 AM. And at some point I realised: this is insane. We're doing math that ",[18,5542,5543],{},"matters"," — pricing insurance policies, calculating reserves, determining solvency — and we're doing it in tools that weren't designed for any of this.",[11,5546,5547],{},"That's when I started writing Go.",[47,5549,5551],{"id":5550},"the-speed-difference-is-embarrassing","The Speed Difference Is Embarrassing",[11,5553,5554],{},"Go compiles to native machine code. That means simulations that take minutes in Python or hours in R run in seconds. There's no interpreter overhead. No GIL bottleneck. No hidden type coercion that silently corrupts your results.",[11,5556,5557],{},"Here's a concrete example. In my reserving prototype, the Go version ran as fast as the Python equivalent using NumPy — and NumPy is already written in C. But Go used less memory, had zero dependencies, and didn't break when I looked at it wrong.",[809,5559,5561],{"className":3142,"code":5560,"language":3144,"meta":383,"style":383},"func runSimulation(policies []Policy, scenarios int) []Result {\n    results := make([]Result, len(policies))\n    var wg sync.WaitGroup\n\n    for i, policy := range policies {\n        wg.Add(1)\n        go func(p Policy, idx int) {\n            defer wg.Done()\n            results[idx] = simulatePolicy(p, scenarios)\n        }(policy, i)\n    }\n\n    wg.Wait()\n    return results\n}\n",[23,5562,5563,5591,5613,5629,5633,5647,5660,5681,5695,5708,5713,5718,5722,5732,5739],{"__ignoreMap":383},[839,5564,5565,5568,5571,5574,5576,5579,5582,5585,5588],{"class":841,"line":842},[839,5566,5567],{"class":845},"func",[839,5569,5570],{"class":899}," runSimulation",[839,5572,5573],{"class":849},"(policies []",[839,5575,3259],{"class":899},[839,5577,5578],{"class":849},", scenarios ",[839,5580,5581],{"class":845},"int",[839,5583,5584],{"class":849},") []",[839,5586,5587],{"class":899},"Result",[839,5589,5590],{"class":849}," {\n",[839,5592,5593,5596,5598,5601,5604,5606,5608,5610],{"class":841,"line":384},[839,5594,5595],{"class":849},"    results ",[839,5597,3154],{"class":845},[839,5599,5600],{"class":899}," make",[839,5602,5603],{"class":849},"([]",[839,5605,5587],{"class":899},[839,5607,892],{"class":849},[839,5609,1414],{"class":899},[839,5611,5612],{"class":849},"(policies))\n",[839,5614,5615,5618,5621,5624,5626],{"class":841,"line":390},[839,5616,5617],{"class":845},"    var",[839,5619,5620],{"class":849}," wg ",[839,5622,5623],{"class":899},"sync",[839,5625,26],{"class":849},[839,5627,5628],{"class":899},"WaitGroup\n",[839,5630,5631],{"class":841,"line":871},[839,5632,868],{"emptyLinePlaceholder":401},[839,5634,5635,5637,5640,5642,5644],{"class":841,"line":883},[839,5636,1113],{"class":845},[839,5638,5639],{"class":849}," i, policy ",[839,5641,3154],{"class":845},[839,5643,1403],{"class":845},[839,5645,5646],{"class":849}," policies {\n",[839,5648,5649,5652,5654,5656,5658],{"class":841,"line":913},[839,5650,5651],{"class":849},"        wg.",[839,5653,1257],{"class":899},[839,5655,1406],{"class":849},[839,5657,1656],{"class":899},[839,5659,1723],{"class":849},[839,5661,5662,5665,5668,5671,5673,5676,5678],{"class":841,"line":936},[839,5663,5664],{"class":845},"        go",[839,5666,5667],{"class":845}," func",[839,5669,5670],{"class":849},"(p ",[839,5672,3259],{"class":899},[839,5674,5675],{"class":849},", idx ",[839,5677,5581],{"class":845},[839,5679,5680],{"class":849},") {\n",[839,5682,5683,5686,5689,5692],{"class":841,"line":958},[839,5684,5685],{"class":845},"            defer",[839,5687,5688],{"class":849}," wg.",[839,5690,5691],{"class":899},"Done",[839,5693,5694],{"class":849},"()\n",[839,5696,5697,5700,5702,5705],{"class":841,"line":985},[839,5698,5699],{"class":849},"            results[idx] ",[839,5701,877],{"class":845},[839,5703,5704],{"class":899}," simulatePolicy",[839,5706,5707],{"class":849},"(p, scenarios)\n",[839,5709,5710],{"class":841,"line":1013},[839,5711,5712],{"class":849},"        }(policy, i)\n",[839,5714,5715],{"class":841,"line":1036},[839,5716,5717],{"class":849},"    }\n",[839,5719,5720],{"class":841,"line":1059},[839,5721,868],{"emptyLinePlaceholder":401},[839,5723,5724,5727,5730],{"class":841,"line":1081},[839,5725,5726],{"class":849},"    wg.",[839,5728,5729],{"class":899},"Wait",[839,5731,5694],{"class":849},[839,5733,5734,5736],{"class":841,"line":1087},[839,5735,1183],{"class":845},[839,5737,5738],{"class":849}," results\n",[839,5740,5741],{"class":841,"line":1092},[839,5742,3245],{"class":849},[11,5744,5745,5746,5748],{},"That's concurrent simulation. One ",[23,5747,3144],{}," keyword. No thread pools. No multiprocessing boilerplate. No tears.",[47,5750,5752],{"id":5751},"concurrency-that-doesnt-make-you-cry","Concurrency That Doesn't Make You Cry",[11,5754,5755],{},"Need to simulate 10,000 policy paths in parallel? Run stress testing across a range of assumptions? Go's goroutines and channels make this trivial. Not \"trivial if you have a CS degree.\" Trivial as in:",[809,5757,5759],{"className":3142,"code":5758,"language":3144,"meta":383,"style":383},"go calculateReserves(policyBatch)\n",[23,5760,5761],{"__ignoreMap":383},[839,5762,5763,5765,5768],{"class":841,"line":842},[839,5764,3144],{"class":845},[839,5766,5767],{"class":899}," calculateReserves",[839,5769,5770],{"class":849},"(policyBatch)\n",[11,5772,5773,5774,5777],{},"That's the whole thing. Compare that to Python's ",[23,5775,5776],{},"multiprocessing"," module, which requires you to understand processes, queues, pickling, and the various ways your code will silently fail in production.",[11,5779,5780],{},"In R you'd probably just give up and go get coffee. In Go, it's one keyword.",[47,5782,5784],{"id":5783},"one-binary-no-drama","One Binary, No Drama",[11,5786,5787],{},"Remember the last time you tried to share a Python script with a colleague?",[11,5789,5790],{},"\"Install Python 3.10. Actually 3.11. Wait, what version do you have? Okay, create a virtual environment. Now pip install -r requirements.txt. It failed? Oh, that package doesn't support your OS. Try — you know what, just use Docker.\"",[11,5792,5793],{},"Go produces a single static binary. I can compile it on my machine, send it to anyone, and it runs. No runtime. No dependencies. No \"works on my machine.\" No excuses.",[11,5795,5796],{},"For actuarial work — where you're often handing off models to people who aren't professional developers — this is a killer feature.",[47,5798,5800],{"id":5799},"type-safety-saves-sanity","Type Safety Saves Sanity",[11,5802,5803],{},"Go is statically typed. This catches bugs at compile time instead of three hours into a Monte Carlo simulation.",[809,5805,5807],{"className":3142,"code":5806,"language":3144,"meta":383,"style":383},"func calculatePremium(age string, amount float64) float64 {\n    return age * amount  \u002F\u002F Compile error: can't multiply string\n}\n",[23,5808,5809,5835,5850],{"__ignoreMap":383},[839,5810,5811,5813,5816,5819,5822,5825,5828,5831,5833],{"class":841,"line":842},[839,5812,5567],{"class":845},[839,5814,5815],{"class":899}," calculatePremium",[839,5817,5818],{"class":849},"(age ",[839,5820,5821],{"class":845},"string",[839,5823,5824],{"class":849},", amount ",[839,5826,5827],{"class":845},"float64",[839,5829,5830],{"class":849},") ",[839,5832,5827],{"class":845},[839,5834,5590],{"class":849},[839,5836,5837,5839,5842,5844,5847],{"class":841,"line":384},[839,5838,1183],{"class":845},[839,5840,5841],{"class":849}," age ",[839,5843,3337],{"class":845},[839,5845,5846],{"class":849}," amount  ",[839,5848,5849],{"class":909},"\u002F\u002F Compile error: can't multiply string\n",[839,5851,5852],{"class":841,"line":390},[839,5853,3245],{"class":849},[11,5855,5856],{},"Python lets that explode at runtime. Go tells you you're wrong before you even run it. When your simulation takes hours to complete, you'll appreciate catching mistakes early.",[11,5858,5859,5860,892,5863,892,5866,5869],{},"The tooling is also just better. ",[23,5861,5862],{},"go test",[23,5864,5865],{},"go fmt",[23,5867,5868],{},"go mod tidy"," — all built in. No installing linters, formatters, or dependency managers separately. Go handles it like a responsible adult.",[47,5871,5873],{"id":5872},"the-standard-library-is-enough","The Standard Library Is Enough",[11,5875,5876],{},"I rarely need third-party libraries in Go. The standard library covers math, file I\u002FO, HTTP servers, JSON encoding, cryptography, time handling — everything I need for building actuarial systems.",[809,5878,5880],{"className":3142,"code":5879,"language":3144,"meta":383,"style":383},"package main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"net\u002Fhttp\"\n)\n\ntype Premium struct {\n    Age    int     `json:\"age\"`\n    Amount float64 `json:\"amount\"`\n}\n\nfunc calculateHandler(w http.ResponseWriter, r *http.Request) {\n    premium := Premium{Age: 35, Amount: 100000}\n    json.NewEncoder(w).Encode(premium)\n}\n\nfunc main() {\n    http.HandleFunc(\"\u002Fcalculate\", calculateHandler)\n    http.ListenAndServe(\":8080\", nil)\n}\n",[23,5881,5882,5890,5894,5901,5912,5921,5925,5929,5942,5952,5962,5966,5970,6002,6024,6041,6045,6049,6059,6075,6090],{"__ignoreMap":383},[839,5883,5884,5887],{"class":841,"line":842},[839,5885,5886],{"class":845},"package",[839,5888,5889],{"class":899}," main\n",[839,5891,5892],{"class":841,"line":384},[839,5893,868],{"emptyLinePlaceholder":401},[839,5895,5896,5898],{"class":841,"line":390},[839,5897,846],{"class":845},[839,5899,5900],{"class":849}," (\n",[839,5902,5903,5906,5909],{"class":841,"line":871},[839,5904,5905],{"class":895},"    \"",[839,5907,5908],{"class":899},"encoding\u002Fjson",[839,5910,5911],{"class":895},"\"\n",[839,5913,5914,5916,5919],{"class":841,"line":883},[839,5915,5905],{"class":895},[839,5917,5918],{"class":899},"net\u002Fhttp",[839,5920,5911],{"class":895},[839,5922,5923],{"class":841,"line":913},[839,5924,1723],{"class":849},[839,5926,5927],{"class":841,"line":936},[839,5928,868],{"emptyLinePlaceholder":401},[839,5930,5931,5934,5937,5940],{"class":841,"line":958},[839,5932,5933],{"class":845},"type",[839,5935,5936],{"class":899}," Premium",[839,5938,5939],{"class":845}," struct",[839,5941,5590],{"class":849},[839,5943,5944,5947,5949],{"class":841,"line":985},[839,5945,5946],{"class":849},"    Age    ",[839,5948,5581],{"class":845},[839,5950,5951],{"class":895},"     `json:\"age\"`\n",[839,5953,5954,5957,5959],{"class":841,"line":1013},[839,5955,5956],{"class":849},"    Amount ",[839,5958,5827],{"class":845},[839,5960,5961],{"class":895}," `json:\"amount\"`\n",[839,5963,5964],{"class":841,"line":1036},[839,5965,3245],{"class":849},[839,5967,5968],{"class":841,"line":1059},[839,5969,868],{"emptyLinePlaceholder":401},[839,5971,5972,5974,5977,5980,5983,5985,5988,5991,5993,5995,5997,6000],{"class":841,"line":1081},[839,5973,5567],{"class":845},[839,5975,5976],{"class":899}," calculateHandler",[839,5978,5979],{"class":849},"(w ",[839,5981,5982],{"class":899},"http",[839,5984,26],{"class":849},[839,5986,5987],{"class":899},"ResponseWriter",[839,5989,5990],{"class":849},", r ",[839,5992,3337],{"class":845},[839,5994,5982],{"class":899},[839,5996,26],{"class":849},[839,5998,5999],{"class":899},"Request",[839,6001,5680],{"class":849},[839,6003,6004,6007,6009,6011,6014,6017,6020,6022],{"class":841,"line":1087},[839,6005,6006],{"class":849},"    premium ",[839,6008,3154],{"class":845},[839,6010,5936],{"class":899},[839,6012,6013],{"class":849},"{Age: ",[839,6015,6016],{"class":899},"35",[839,6018,6019],{"class":849},", Amount: ",[839,6021,3281],{"class":899},[839,6023,3245],{"class":849},[839,6025,6026,6029,6032,6035,6038],{"class":841,"line":1092},[839,6027,6028],{"class":849},"    json.",[839,6030,6031],{"class":899},"NewEncoder",[839,6033,6034],{"class":849},"(w).",[839,6036,6037],{"class":899},"Encode",[839,6039,6040],{"class":849},"(premium)\n",[839,6042,6043],{"class":841,"line":1104},[839,6044,3245],{"class":849},[839,6046,6047],{"class":841,"line":1110},[839,6048,868],{"emptyLinePlaceholder":401},[839,6050,6051,6053,6056],{"class":841,"line":1125},[839,6052,5567],{"class":845},[839,6054,6055],{"class":899}," main",[839,6057,6058],{"class":849},"() {\n",[839,6060,6061,6064,6067,6069,6072],{"class":841,"line":1136},[839,6062,6063],{"class":849},"    http.",[839,6065,6066],{"class":899},"HandleFunc",[839,6068,1406],{"class":849},[839,6070,6071],{"class":895},"\"\u002Fcalculate\"",[839,6073,6074],{"class":849},", calculateHandler)\n",[839,6076,6077,6079,6082,6084,6087],{"class":841,"line":1145},[839,6078,6063],{"class":849},[839,6080,6081],{"class":899},"ListenAndServe",[839,6083,1406],{"class":849},[839,6085,6086],{"class":895},"\":8080\"",[839,6088,6089],{"class":849},", nil)\n",[839,6091,6092],{"class":841,"line":1154},[839,6093,3245],{"class":849},[11,6095,6096],{},"That's a working web server for actuarial calculations. No Flask. No Django. No 47-line requirements.txt.",[47,6098,6100],{"id":6099},"what-this-means-for-actuarial-work","What This Means for Actuarial Work",[11,6102,6103],{},"I'm not saying drop everything and rewrite your entire toolkit in Go. Python is still better for quick prototyping and data exploration. R is still the king of statistical modeling. Excel still has its place for ad-hoc analysis and business presentations.",[11,6105,6106,6107,6110],{},"But when you need to build a ",[18,6108,6109],{},"system"," — something that processes millions of policies, runs complex simulations, and produces reliable results that non-developers can use — Go is the right tool. It's fast, safe, and boring in the best possible way. No surprises. No runtime errors at 2 AM. Just compiled code that does exactly what you told it to do.",[11,6112,6113,6114,6117,6118,6122],{},"I've been building with Go for a while now. My actuarial engine ",[264,6115,269],{"href":266,"rel":6116},[268]," processes a million policies in under 300ms. My reserving tool ",[264,6119,6121],{"href":5510,"rel":6120},[268],"Actuworry"," does everything the enterprise tools do, without the six-figure license fee.",[11,6124,6125],{},"And I never have to debug an Excel macro again. That alone was worth the switch.",[1823,6127,6128],{},"html pre.shiki code .sq0yK, html code.shiki .sq0yK{--shiki-default:#A0A0A0}html pre.shiki code .sNEDb, html code.shiki .sNEDb{--shiki-default:#FFC799}html pre.shiki code .sU-n2, html code.shiki .sU-n2{--shiki-default:#FFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .ss8vJ, html code.shiki .ss8vJ{--shiki-default:#8B8B8B94}html pre.shiki code .sZOz5, html code.shiki .sZOz5{--shiki-default:#99FFE4}",{"title":383,"searchDepth":384,"depth":384,"links":6130},[6131,6132,6133,6134,6135,6136],{"id":5550,"depth":384,"text":5551},{"id":5751,"depth":384,"text":5752},{"id":5783,"depth":384,"text":5784},{"id":5799,"depth":384,"text":5800},{"id":5872,"depth":384,"text":5873},{"id":6099,"depth":384,"text":6100},"From spreadsheet purgatory to compiled bliss — a personal take on why Go dominates for actuarial systems.",{},{"title":5529,"description":6137},"blog\u002Fwhygolang",[408,3144,3779,407],"From spreadsheet purgatory to compiled bliss — why Go dominates for actuarial systems. Concurrency with one keyword, single static binaries, type safety that catches bugs at compile time, and a standard library that's enough. For when Python's too slow and Excel's too fragile.","zs6-D1Xlex8e3gVmXQBHh-DvGG6fQOScLNDjvlAInRE",{"id":6145,"title":6146,"body":6147,"date":6286,"description":6287,"extension":398,"featured":399,"meta":6288,"navigation":401,"part":402,"path":6289,"seo":6290,"series":4420,"stem":6291,"tags":6292,"tldr":6293,"__hash__":6294},"blog\u002Fblog\u002Fneovim.md","Neovim: Just Try It Already",{"type":8,"value":6148,"toc":6280},[6149,6152,6155,6158,6162,6166,6169,6178,6181,6185,6192,6205,6208,6212,6215,6234,6237,6253,6257,6269,6274,6277],[11,6150,6151],{},"I get it. You've heard the Vim pitch a hundred times. \"It's faster.\" \"You never have to touch the mouse.\" \"Once you learn it, you can't go back.\" It sounds like a cult. I know, because I was skeptical too.",[11,6153,6154],{},"Then I tried it. For real. Not for an hour. For a week.",[11,6156,6157],{},"Here's what happened.",[47,6159,6161],{"id":6160},"the-first-day-is-hell","The First Day Is Hell",[304,6163],{"src":6164,"alt":6165,"width":308},"https:\u002F\u002Fi.kym-cdn.com\u002Fentries\u002Ficons\u002Fmobile\u002F000\u002F038\u002F208\u002FAnime_Girl_Punching_Wall_banner.jpg","Anime Girl Punching Wall banner",[6167,6168],"br",{},[11,6170,6171,6172,6177],{},"You open ",[264,6173,6176],{"href":6174,"rel":6175},"https:\u002F\u002Fneovim.io\u002F",[268],"Neovim",". You try to type. Nothing happens. You press a few keys and suddenly half your file is deleted. You Google \"how to exit vim\" at 2 AM. You question every decision that led you here.",[11,6179,6180],{},"This is normal. This is the initiation ritual. Everyone goes through it. The people who tell you they didn't are lying.",[47,6182,6184],{"id":6183},"what-nobody-tells-you","What Nobody Tells You",[11,6186,6187,6188,6191],{},"The thing I didn't understand until I actually committed: you don't learn Vim motions to type faster. You learn them to ",[18,6189,6190],{},"edit"," faster. And editing is 90% of what you do as a developer. Reading code. Moving around files. Selecting, deleting, rearranging. The typing part is almost irrelevant.",[11,6193,6194,5012,6196,5016,6198,6200,6201,6204],{},[23,6195,5011],{},[23,6197,5015],{},[23,6199,5019],{}," — yank a paragraph. These aren't key combinations. They're ",[18,6202,6203],{},"commands",". You compose them like sentences. Verb + object + modifier. It's a language for text manipulation, not a collection of shortcuts.",[11,6206,6207],{},"That's the insight that made it click for me.",[47,6209,6211],{"id":6210},"the-try-before-you-buy-path","The Try-Before-You-Buy Path",[11,6213,6214],{},"You don't need to go full Neovim to try this. I tell everyone the same thing:",[229,6216,6217,6222,6229],{},[232,6218,6219,6221],{},[40,6220,5062],{}," — Install vscodevim. Keep your extensions. Keep your theme. Just start using Vim keybindings inside an editor you already know.",[232,6223,6224,6226,6227,5076],{},[40,6225,5072],{}," — Has Vim mode built in. Toggle it on. Zed is fast enough that you get the motions ",[18,6228,1745],{},[232,6230,6231,6233],{},[40,6232,5081],{}," — IdeaVim plugin. Same deal.",[11,6235,6236],{},"Give it a month. If it clicks, great. If not, uninstall it. No harm done.",[11,6238,6239,6240,6242,6243,6246,6247,6252],{},"But if it ",[18,6241,607],{}," click, and you want to try real Neovim, start with ",[264,6244,5089],{"href":4944,"rel":6245},[268],". It's a single-file config maintained by ",[264,6248,6251],{"href":6249,"rel":6250},"https:\u002F\u002Fgithub.com\u002Ftjdevries",[268],"TJ DeVries"," — the guy who built half the Neovim plugin ecosystem. It's not a distribution. It's a starting point. One file. You can actually read it and understand what it does. Then you make it your own.",[47,6254,6256],{"id":6255},"the-setup-i-run-now","The Setup I Run Now",[11,6258,6259,6260,6262,6263,6265,6266,6268],{},"My config started from kickstart and evolved into something more structured. It's modularised — plugins in ",[23,6261,4463],{},", LSP config in ",[23,6264,4469],{},", utilities in ",[23,6267,4475],{},". Everything is lazy-loaded. Nothing loads until you actually need it.",[11,6270,6271,6272],{},"The philosophy is simple: ",[40,6273,4441],{},[11,6275,6276],{},"That's it. No 400-plugin distributions. No black magic Lua you're afraid to touch. Just a config you actually understand, running an editor that stays out of your way.",[11,6278,6279],{},"Give it a week. What's the worst that could happen?",{"title":383,"searchDepth":384,"depth":384,"links":6281},[6282,6283,6284,6285],{"id":6160,"depth":384,"text":6161},{"id":6183,"depth":384,"text":6184},{"id":6210,"depth":384,"text":6211},{"id":6255,"depth":384,"text":6256},"2025-07-10","Modal editing is not a cult. It's just faster. Here's why you should give it a week.",{},"\u002Fblog\u002Fneovim",{"title":6146,"description":6287},"blog\u002Fneovim",[4973],"Modal editing isn't a cult, it's a language for text manipulation. Give it a week — try Vim bindings in VS Code or Zed before going full Neovim. Here's what nobody tells you about the first day and why it's worth pushing through.","wSY86EyrDm4IMYL7fI_g9WGJQI0m2eyOiQwGPjMZCO0",{"id":6296,"title":6297,"body":6298,"date":6399,"description":6400,"extension":398,"featured":399,"meta":6401,"navigation":401,"part":402,"path":6402,"seo":6403,"series":402,"stem":6404,"tags":6405,"tldr":402,"__hash__":6406},"blog\u002Fblog\u002Fgetting-started-with-nuxt.md","I Started With Vue (And Then Nuxt Because It Was Easier)",{"type":8,"value":6299,"toc":6394},[6300,6318,6323,6327,6330,6333,6340,6344,6353,6371,6374,6378,6381,6384,6387],[11,6301,6302,6303,6306,6307,6312,6313,26],{},"I should probably start with the truth: ",[18,6304,6305],{},"I'm not a web developer."," I'm an actuarial science graduate who started building this site because I wanted a place to write, and someone told me ",[264,6308,6311],{"href":6309,"rel":6310},"https:\u002F\u002Fvuejs.org\u002F",[268],"Vue"," was easier than ",[264,6314,6317],{"href":6315,"rel":6316},"https:\u002F\u002Freactjs.org\u002F",[268],"React",[11,6319,6320],{},[40,6321,6322],{},"They were right.",[47,6324,6326],{"id":6325},"why-i-didnt-start-with-react","Why I Didn't Start With React",[11,6328,6329],{},"Everyone told me to learn React. It's what the industry uses, they said. Most jobs want React, they said. But every time I tried to get started, I hit a wall. JSX felt weird. The boilerplate was insane. I spent more time configuring Webpack than actually building anything.",[11,6331,6332],{},"Then I found Vue. And it just... worked. The syntax was plain HTML with some extra attributes. I could add it to a page without a build step. I understood what my code was doing without reading a 50-page guide on fiber architecture or reconciliation.",[11,6334,6335,6336,6339],{},"That's not a knock on React. It's just a fact: Vue is easier to pick up when you don't come from a web development background. And I ",[18,6337,6338],{},"don't"," come from a web development background. I come from spreadsheets and statistical models.",[47,6341,6343],{"id":6342},"nuxt-made-it-even-easier","Nuxt Made It Even Easier",[11,6345,6346,6347,6352],{},"After I got comfortable with Vue, I wanted to build an actual site. A blog. With routing, markdown content, SEO, the works. And that's where ",[264,6348,6351],{"href":6349,"rel":6350},"https:\u002F\u002Fnuxtjs.org\u002F",[268],"Nuxt"," came in.",[11,6354,6355,6358,6359,6362,6363,6366,6367,6370],{},[264,6356,6351],{"href":6349,"rel":6357},[268]," is Vue but with opinions — good opinions. File-based routing means you create a ",[23,6360,6361],{},".vue"," file and suddenly you have a page. The ",[23,6364,6365],{},"@nuxt\u002Fcontent"," module lets me write markdown files in a ",[23,6368,6369],{},"content\u002F"," directory and they automatically become blog posts. No database. No CMS. No headache.",[11,6372,6373],{},"For someone like me who just wants to write and ship, that's a killer feature.",[47,6375,6377],{"id":6376},"what-i-still-dont-know","What I Still Don't Know",[11,6379,6380],{},"I don't know how to configure Vite from scratch. I don't know what tree-shaking actually does under the hood. I don't know how to write a custom Vite plugin, and I probably never will.",[11,6382,6383],{},"But I know how to write a markdown file and have it show up on my site. I know how to add a tag filter to my blog index. I know how to deploy to Cloudflare Pages with a single command.",[11,6385,6386],{},"And honestly? That's enough for now. The rest I'll learn when I need it.",[11,6388,6389,6390,6393],{},"Nuxt didn't make me a web developer overnight. But it made me a person who can ",[18,6391,6392],{},"build a website",", which is close enough.",{"title":383,"searchDepth":384,"depth":384,"links":6395},[6396,6397,6398],{"id":6325,"depth":384,"text":6326},{"id":6342,"depth":384,"text":6343},{"id":6376,"depth":384,"text":6377},"2025-06-24","I'm not a web dev veteran. I just picked Vue, then Nuxt, and somehow it all made sense.",{},"\u002Fblog\u002Fgetting-started-with-nuxt",{"title":6297,"description":6400},"blog\u002Fgetting-started-with-nuxt",[],"qVOgtwivFq5iGJbEWF7Mud-sbmPPP2UOUnh23giNv2c",{"id":6408,"title":6409,"body":6410,"date":6432,"description":6433,"extension":398,"featured":401,"meta":6434,"navigation":401,"part":402,"path":6435,"seo":6436,"series":402,"stem":6437,"tags":6438,"tldr":402,"__hash__":6439},"blog\u002Fblog\u002Fwelcome.md","Starting Somewhere",{"type":8,"value":6411,"toc":6430},[6412,6415,6418,6424,6427],[11,6413,6414],{},"Every blog needs a first post. This is mine.",[11,6416,6417],{},"I'm not going to promise to write weekly, or monthly, or on any kind of schedule. I've seen that promise made and broken enough times to know it's a trap. I'll write when I have something to say.",[11,6419,6420,6421,6423],{},"What I ",[18,6422,4109],{}," say is this: I'm an actuarial science graduate who fell in love with programming somewhere along the way. I build things in Go, Python, and whatever else gets the job done. I trade markets (badly, sometimes). I tinker. I break stuff. I fix it. I write about the stuff that sticks.",[11,6425,6426],{},"That's it. No manifesto. No grand plan. Just a log.",[11,6428,6429],{},"Let's see where it goes.",{"title":383,"searchDepth":384,"depth":384,"links":6431},[],"2025-06-23","First post, no manifesto. Just a space to think in public.",{},"\u002Fblog\u002Fwelcome",{"title":6409,"description":6433},"blog\u002Fwelcome",[],"GhJq45LHof-Z3kaE9HhTY2lzT_q-Gv-vLVW5gWMT4uU",1784129939650]