[{"data":1,"prerenderedAt":1771},["ShallowReactive",2],{"all-posts":3},[4,350,1421,1634],{"id":5,"title":6,"body":7,"date":336,"description":337,"extension":338,"image":339,"meta":340,"navigation":341,"path":342,"seo":343,"stem":344,"tags":345,"__hash__":349},"blog\u002Fblog\u002Fhoney-i-shrunk-the-cloud-bill.md","Honey, I Shrunk the Cloud Bill — A Production E-Commerce Store for $0\u002FYear",{"type":8,"value":9,"toc":329},"minimark",[10,15,22,32,35,38,41,47,50,53,56,58,64,67,80,83,89,92,103,110,113,115,119,126,128,132,310,313,315,318,326],[11,12,14],"h2",{"id":13},"બચત-એ-પહેલી-મૂડી-છે","\"બચત, એ પહેલી મૂડી છે.\"",[16,17,18],"p",{},[19,20,21],"em",{},"Basically: Saving is the first investment",[23,24,25],"blockquote",{},[16,26,27,31],{},[28,29,30],"strong",{},"TL;DR"," — As a Gujarati. Being budget-smart isn't a skill I learned — it's factory settings. This is the story of how that one personality trait, a brother who questions every invoice, and a wife with a flour shop finally pushed me to build what I'd been dreaming about for years.",[33,34],"hr",{},[16,36,37],{},"Story starts long years ago, right after I passed out from university and stumbled into web development.",[16,39,40],{},"I was the first engineer in the family. Which sounds impressive until you realise it mostly meant I became the free IT support for everyone and the person expected to have a \"tech solution\" for everything — including problems that didn't exist yet.",[16,42,43,44],{},"My elder brother runs a lighting business in India. Sharp businessman, brilliant at marketing, and absolutely ruthless when it comes to spending money. Every invoice gets questioned. Every tool gets interrogated. His favourite phrase when I'd suggest something was — ",[19,45,46],{},"\"but do we actually need to pay for this?\"",[16,48,49],{},"Honestly? Annoying at the time. But also, he had a point.",[16,51,52],{},"That constant pressure turned into a bit of an obsession for me — how cheap can we actually make this? Not cut corners cheap, but genuinely efficient cheap. Especially for small businesses where every rupee matters and nobody has a cloud budget.",[16,54,55],{},"That's how I fell into the world of static sites, GitHub Pages, serverless functions, and anything that kept the monthly bill as close to zero as possible.",[33,57],{},[16,59,60,61],{},"Fast forward a few years. My brother's lighting business always needed an online catalogue. My wife recently started her own small shop. And somewhere in the back of my head, an idea had been sitting quietly for years — ",[19,62,63],{},"why is e-commerce so expensive?",[16,65,66],{},"Think about it. A small shop doesn't need a full server running 24\u002F7. What you actually need is:",[68,69,70,74,77],"ul",{},[71,72,73],"li",{},"A way to show products dynamically",[71,75,76],{},"A way to take orders",[71,78,79],{},"A way to know when someone ordered",[16,81,82],{},"Everything else is just a static page. So why are we paying like it isn't?",[16,84,85,86],{},"That's when I remembered the Google Sheets gviz API — it lets you read a spreadsheet as JSON, no backend, no authentication, just a URL. Products live in a Sheet, updated in real time, no deployment needed. My brother can update his catalogue from his phone at 2am if he wants. ",[28,87,88],{},"Bingo.",[16,90,91],{},"For orders — a tiny Cloudflare Worker. Receives the order, saves it to a free SQLite database, fires a Telegram message straight to my phone. The whole backend fits in a single file. Back in the day when I first had this idea, I used to joke that I'd build this someday. The joke lasted about ten years.",[16,93,94,95,102],{},"This year I finally built it — properly — for ",[96,97,101],"a",{"href":98,"rel":99},"https:\u002F\u002Fshop.gunjanpatel.info",[100],"nofollow","my wife's flour shop",". Dark mode, variant pricing, geo-restriction, bot protection, automatic deployments. The works.",[16,104,105,106,109],{},"Total monthly cost: ",[28,107,108],{},"$0",".",[16,111,112],{},"The only thing you'll ever pay for is a domain name — about $10-15 a year. My brother still questioned it.",[33,114],{},[11,116,118],{"id":117},"️-architecture","🏗️ Architecture",[16,120,121],{},[122,123],"img",{"alt":124,"src":125},"low-budget-shopping-cart-architecture.png","\u002Fimg\u002Fblog\u002Flow-budget-shopping-cart-architecture.png",[33,127],{},[11,129,131],{"id":130},"cost-breakdown","💰 Cost Breakdown",[133,134,135,154],"table",{},[136,137,138],"thead",{},[139,140,141,145,148,151],"tr",{},[142,143,144],"th",{},"Service",[142,146,147],{},"What it does",[142,149,150],{},"Free tier",[142,152,153],{},"Paid starts at",[155,156,157,174,190,204,220,236,250,264,279,293],"tbody",{},[139,158,159,165,168,171],{},[160,161,162],"td",{},[28,163,164],{},"GitHub Pages",[160,166,167],{},"Static hosting + CDN",[160,169,170],{},"Unlimited",[160,172,173],{},"Never needed",[139,175,176,181,184,187],{},[160,177,178],{},[28,179,180],{},"GitHub Actions",[160,182,183],{},"CI\u002FCD auto-deploy",[160,185,186],{},"2,000 min\u002Fmonth",[160,188,189],{},"$4\u002Fmonth",[139,191,192,197,200,202],{},[160,193,194],{},[28,195,196],{},"Cloudflare CDN",[160,198,199],{},"Cache, DDoS, SSL",[160,201,170],{},[160,203,173],{},[139,205,206,211,214,217],{},[160,207,208],{},[28,209,210],{},"Cloudflare Workers",[160,212,213],{},"Serverless order API",[160,215,216],{},"100,000 req\u002Fday",[160,218,219],{},"$5\u002Fmonth",[139,221,222,227,230,233],{},[160,223,224],{},[28,225,226],{},"Cloudflare D1",[160,228,229],{},"SQLite order database",[160,231,232],{},"5GB, 25M reads\u002Fday",[160,234,235],{},"$0.001\u002FGB",[139,237,238,243,246,248],{},[160,239,240],{},[28,241,242],{},"Cloudflare Turnstile",[160,244,245],{},"Bot Protection",[160,247,170],{},[160,249,173],{},[139,251,252,257,260,262],{},[160,253,254],{},[28,255,256],{},"Google Sheets",[160,258,259],{},"Product catalogue CMS",[160,261,170],{},[160,263,173],{},[139,265,266,271,274,277],{},[160,267,268],{},[28,269,270],{},"ip-api.com",[160,272,273],{},"Geo restriction",[160,275,276],{},"45 req\u002Fmin",[160,278,173],{},[139,280,281,286,289,291],{},[160,282,283],{},[28,284,285],{},"Telegram Bot",[160,287,288],{},"Order notifications",[160,290,170],{},[160,292,173],{},[139,294,295,300,302,307],{},[160,296,297],{},[28,298,299],{},"Total",[160,301],{},[160,303,304],{},[28,305,306],{},"$0\u002Fmonth",[160,308,309],{},"—",[16,311,312],{},"For a small business doing a few hundred orders a month, you'll never hit a paid tier. And if you do — congratulations, you can probably afford the $5.",[33,314],{},[16,316,317],{},"If this architecture sounds interesting and you want to dig into the actual setup, code, and how it all fits together — it's all on GitHub:",[16,319,320,321],{},"👉 ",[96,322,325],{"href":323,"rel":324},"https:\u002F\u002Fgithub.com\u002Fgunjanpatel\u002Fpatel-flours",[100],"github.com\u002Fgunjanpatel\u002Fpatel-flours",[16,327,328],{},"If you build something with this for your own brother, wife, or that one family member who always questions the invoice — I'd love to hear about it. 😄",{"title":330,"searchDepth":331,"depth":331,"links":332},"",2,[333,334,335],{"id":13,"depth":331,"text":14},{"id":117,"depth":331,"text":118},{"id":130,"depth":331,"text":131},"2026-03-14","As a Gujarati, being budget-smart is factory settings. This is the story of how that personality trait, a brother who questions every invoice, and a wife with a flour shop pushed me to build a production e-commerce store for the cost of a domain name.","md","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1665888111461-6c98379622ab?q=80&auto=format&fit=crop&w=1600",{},true,"\u002Fblog\u002Fhoney-i-shrunk-the-cloud-bill",{"title":6,"description":337},"blog\u002Fhoney-i-shrunk-the-cloud-bill",[346,347,348],"e-commerce","cost-optimisation","small-business","VcSECvpST8SKeIOm6QB80H8bMWOyyt-ZsGEL7azur2w",{"id":351,"title":352,"body":353,"date":1408,"description":1409,"extension":338,"image":1410,"meta":1411,"navigation":341,"path":1412,"seo":1413,"stem":1414,"tags":1415,"__hash__":1420},"blog\u002Fblog\u002Faccidentally-built-modern-data-platform.md","I Accidentally Built a Modern Data Platform Years Before It Became a Trend",{"type":8,"value":354,"toc":1387},[355,360,365,408,410,414,417,420,422,426,429,440,443,460,463,480,483,486,491,494,496,500,503,506,509,558,567,569,573,577,580,583,588,591,595,619,622,626,629,640,643,647,650,656,659,670,673,677,680,694,697,699,703,706,1212,1215,1218,1221,1223,1227,1230,1233,1236,1240,1244,1251,1265,1268,1270,1274,1277,1282,1285,1288,1291,1296,1299,1301,1305,1338,1340,1344,1347,1367,1370,1372,1375,1383],[356,357,359],"h3",{"id":358},"કેડ-માં-છોકરું-ને-ગામ-માં-ઢંઢેરો","\"કેડ માં છોકરું, ને ગામ માં ઢંઢેરો\"",[16,361,362],{},[19,363,364],{},"Basically: built something quietly, realised later it was a big deal.",[23,366,367,391],{},[16,368,369,371,372,375,376,379,380,375,383,386,387,390],{},[28,370,30],{}," — Years ago, I built a simple pricing system: raw tables in, a stored procedure in the middle, and one final \"correct price\" table out. At the time, I didn't know anything about terms like ",[28,373,374],{},"data products",", ",[28,377,378],{},"Foundational Data Products (FDPs)",", a ",[28,381,382],{},"Derived Data Product (DDP)",[28,384,385],{},"data lineage",", or ",[28,388,389],{},"data contracts",". I was just trying to make the website show the right price.",[16,392,393,394,397,398,375,401,404,405,109],{},"Much later, when I started learning ",[28,395,396],{},"modern data engineering",", I realised I had already built many of those concepts without knowing their names. This post is me ",[28,399,400],{},"looking back",[28,402,403],{},"connecting the dots",", and understanding those ideas through something I actually built in ",[28,406,407],{},"real life",[33,409],{},[11,411,413],{"id":412},"why-im-writing-this","Why I'm Writing This",[16,415,416],{},"I'm writing this mainly for myself.\nI've been trying to understand modern data engineering concepts, and sometimes the wording makes everything sound more complex than it is. When I step back and compare the concepts to my real experience, I realise I actually lived through many of them already — I just didn't have the terminology.",[16,418,419],{},"This blog helps me learn by linking theory to something real I built. If someone else learns from it too, great.",[33,421],{},[11,423,425],{"id":424},"the-system-i-built-before-i-had-the-words","The System I Built (Before I Had the Words)",[16,427,428],{},"Years ago, I built a pricing system for an e‑commerce site. The business side (marketing\u002Fadmin) would enter:",[68,430,431,434,437],{},[71,432,433],{},"Product‑level prices",[71,435,436],{},"Variant‑level prices",[71,438,439],{},"Start and end dates",[16,441,442],{},"I stored that data in two simple raw tables. Then I wrote a stored procedure that:",[68,444,445,448,451,454,457],{},[71,446,447],{},"cleaned and normalised the input,",[71,449,450],{},"applied pricing rules,",[71,452,453],{},"merged everything into one consistent truth,",[71,455,456],{},"fixed date issues,",[71,458,459],{},"and finally wrote out one table that the frontend could trust.",[16,461,462],{},"The rules were practical and simple:",[68,464,465,468,471,474,477],{},[71,466,467],{},"If a variant had its own price, use it.",[71,469,470],{},"Otherwise, use the product price.",[71,472,473],{},"Respect time ranges.",[71,475,476],{},"Don't leave gaps or overlaps.",[71,478,479],{},"Don't produce duplicates.",[16,481,482],{},"I didn't think about \"architecture.\" I wasn't trying to implement patterns. I just wanted a clean and reliable answer for the frontend.",[16,484,485],{},"Back then, I called it:",[23,487,488],{},[16,489,490],{},"\"the final price table.\"",[16,492,493],{},"Nothing more.",[33,495],{},[11,497,499],{"id":498},"the-same-system-with-modern-labels","The Same System, With Modern Labels",[16,501,502],{},"Later, when I started exploring modern data engineering — especially the vocabulary — things clicked.",[16,504,505],{},"My old setup actually lined up with many concepts people talk about today.",[16,507,508],{},"Here's the mapping using generic table names:",[68,510,511,524,537,546,552],{},[71,512,513,516,517,375,521],{},[28,514,515],{},"Raw inputs:"," ",[518,519,520],"code",{},"price_raw_product",[518,522,523],{},"price_raw_variant",[71,525,526,529,530,375,533,536],{},[28,527,528],{},"Foundational Data Products (FDPs):"," cleaned versions of the raw inputs (",[518,531,532],{},"price_fdp_product",[518,534,535],{},"price_fdp_variant",")",[71,538,539,542,543,536],{},[28,540,541],{},"Derived Data Product (DDP):"," the final truth table (",[518,544,545],{},"price_ddp_effective",[71,547,548,551],{},[28,549,550],{},"Orchestration:"," my stored procedure",[71,553,554,557],{},[28,555,556],{},"Consumers:"," frontend, checkout, reports, etc.",[559,560,565],"pre",{"className":561,"code":563,"language":564},[562],"language-text","Admin \u002F Marketing (price inputs)\n           |\n           v\nRaw Input Tables\n┌───────────────────────┐\n│ price_raw_product     │\n│ price_raw_variant     │\n└──────────┬────────────┘\n           v\nFoundational Data Products (FDP)\n┌───────────────────────────────┐\n│ price_fdp_product             │\n│ price_fdp_variant             │\n└──────────┬────────────────────┘\n           v\nOrchestration (Stored Procedure)\n┌───────────────────────────────┐\n│ joins, precedence, date logic │\n└──────────┬────────────────────┘\n           v\nDerived Data Product (DDP)\n┌───────────────────────────────┐\n│ price_ddp_effective           │\n└──────────┬────────────────────┘\n           v\nFrontend \u002F APIs \u002F Checkout\n","text",[518,566,563],{"__ignoreMap":330},[33,568],{},[11,570,572],{"id":571},"mapping-my-experience-to-data-engineering-concepts","Mapping My Experience to Data Engineering Concepts",[356,574,576],{"id":575},"_1-data-product","1) Data Product",[16,578,579],{},"A data product is basically a dataset with a clear purpose, rules, and an owner.",[16,581,582],{},"That final table I produced fulfilled exactly that purpose:",[23,584,585],{},[16,586,587],{},"\"Give me the correct price for any product or variant, at any time.\"",[16,589,590],{},"It had a consistent schema, the whole system depended on it, and I was the owner. So yes — it was a data product even if I never used the term.",[356,592,594],{"id":593},"_2-foundational-vs-derived-data-products","2) Foundational vs. Derived Data Products",[68,596,597,609],{},[71,598,599,600,375,602,604,605,608],{},"My cleaned tables (",[518,601,532],{},[518,603,535],{},") were ",[28,606,607],{},"FDPs"," — stable inputs.",[71,610,611,612,614,615,618],{},"My final table (",[518,613,545],{},") was the ",[28,616,617],{},"DDP"," — the business‑logic‑applied truth.",[16,620,621],{},"I didn't design it like that intentionally. But the structure naturally appeared because it was practical.",[356,623,625],{"id":624},"_3-orchestration","3) Orchestration",[16,627,628],{},"Today people use workflow tools, but my orchestration was simply:",[68,630,631,634,637],{},[71,632,633],{},"A stored procedure,",[71,635,636],{},"Triggered by a cron job,",[71,638,639],{},"Running steps in a controlled order.",[16,641,642],{},"It cleaned, merged, validated, and published the final result. Simple. And dependable.",[356,644,646],{"id":645},"_4-data-lineage","4) Data Lineage",[16,648,649],{},"I didn't draw lineage diagrams back then, but the flow was clear.",[559,651,654],{"className":652,"code":653,"language":564},[562],"price_raw_* → price_fdp_* → price_ddp_effective → frontend\n",[518,655,653],{"__ignoreMap":330},[16,657,658],{},"When something was wrong, I always knew where to look:",[68,660,661,664,667],{},[71,662,663],{},"If it was a business logic issue → the DDP.",[71,665,666],{},"If it was messy input → the raw tables.",[71,668,669],{},"If it was formatting → the FDP layer.",[16,671,672],{},"That's lineage in practice.",[356,674,676],{"id":675},"_5-data-contract","5) Data Contract",[16,678,679],{},"No one told me to write a \"data contract.\" But I still had an internal understanding:",[68,681,682,685,688,691],{},[71,683,684],{},"the columns needed to exist,",[71,686,687],{},"dates had to be valid,",[71,689,690],{},"variant overrides product,",[71,692,693],{},"the table needed to refresh with cron.",[16,695,696],{},"It wasn't formal, but the \"contract\" existed through consistent behaviour.",[33,698],{},[11,700,702],{"id":701},"the-core-business-logic","The Core Business Logic",[16,704,705],{},"Here's the simplified idea of what the stored procedure did, without the exact code.",[559,707,711],{"className":708,"code":709,"language":710,"meta":330,"style":330},"language-sql shiki shiki-themes github-light github-dark","-- Normalize raw inputs into FDPs\nCREATE TABLE price_fdp_product AS\nSELECT product_id,\n       price,\n       GREATEST(start_date, DATE '2000-01-01') AS start_date,\n       COALESCE(end_date, DATE '2999-12-31')   AS end_date\nFROM price_raw_product\nWHERE price IS NOT NULL;\n\nCREATE TABLE price_fdp_variant AS\nSELECT product_id, variant_id,\n       price,\n       GREATEST(start_date, DATE '2000-01-01') AS start_date,\n       COALESCE(end_date, DATE '2999-12-31')   AS end_date\nFROM price_raw_variant\nWHERE price IS NOT NULL;\n\n-- Materialize the DDP: variant price overrides product price\nCREATE TABLE price_ddp_effective AS\nWITH unioned AS (\n  SELECT product_id, variant_id, price, start_date, end_date, 2 AS precedence\n  FROM price_fdp_variant\n  UNION ALL\n  SELECT product_id, NULL AS variant_id, price, start_date, end_date, 1 AS precedence\n  FROM price_fdp_product\n),\nresolved AS (\n  SELECT *\n  FROM unioned\n  QUALIFY ROW_NUMBER() OVER (\n    PARTITION BY product_id, COALESCE(variant_id, -1), start_date, end_date\n    ORDER BY precedence DESC\n  ) = 1\n)\nSELECT product_id, variant_id,\n       price AS effective_price,\n       start_date, end_date,\n       CASE WHEN variant_id IS NOT NULL THEN 'variant' ELSE 'product' END AS price_source\nFROM resolved;\n","sql",[518,712,713,722,738,748,754,788,810,819,834,840,852,860,865,888,905,913,924,929,935,947,961,984,993,999,1026,1034,1040,1050,1058,1066,1083,1113,1125,1137,1143,1150,1161,1170,1204],{"__ignoreMap":330},[714,715,718],"span",{"class":716,"line":717},"line",1,[714,719,721],{"class":720},"sJ8bj","-- Normalize raw inputs into FDPs\n",[714,723,724,728,731,735],{"class":716,"line":331},[714,725,727],{"class":726},"szBVR","CREATE",[714,729,730],{"class":726}," TABLE",[714,732,734],{"class":733},"sScJk"," price_fdp_product",[714,736,737],{"class":726}," AS\n",[714,739,741,744],{"class":716,"line":740},3,[714,742,743],{"class":726},"SELECT",[714,745,747],{"class":746},"sVt8B"," product_id,\n",[714,749,751],{"class":716,"line":750},4,[714,752,753],{"class":746},"       price,\n",[714,755,757,761,764,767,769,772,776,779,782,785],{"class":716,"line":756},5,[714,758,760],{"class":759},"sj4cs","       GREATEST",[714,762,763],{"class":746},"(",[714,765,766],{"class":726},"start_date",[714,768,375],{"class":746},[714,770,771],{"class":726},"DATE",[714,773,775],{"class":774},"sZZnC"," '2000-01-01'",[714,777,778],{"class":746},") ",[714,780,781],{"class":726},"AS",[714,783,784],{"class":726}," start_date",[714,786,787],{"class":746},",\n",[714,789,791,794,797,799,802,805,807],{"class":716,"line":790},6,[714,792,793],{"class":759},"       COALESCE",[714,795,796],{"class":746},"(end_date, ",[714,798,771],{"class":726},[714,800,801],{"class":774}," '2999-12-31'",[714,803,804],{"class":746},")   ",[714,806,781],{"class":726},[714,808,809],{"class":746}," end_date\n",[714,811,813,816],{"class":716,"line":812},7,[714,814,815],{"class":726},"FROM",[714,817,818],{"class":746}," price_raw_product\n",[714,820,822,825,828,831],{"class":716,"line":821},8,[714,823,824],{"class":726},"WHERE",[714,826,827],{"class":746}," price ",[714,829,830],{"class":726},"IS NOT NULL",[714,832,833],{"class":746},";\n",[714,835,837],{"class":716,"line":836},9,[714,838,839],{"emptyLinePlaceholder":341},"\n",[714,841,843,845,847,850],{"class":716,"line":842},10,[714,844,727],{"class":726},[714,846,730],{"class":726},[714,848,849],{"class":733}," price_fdp_variant",[714,851,737],{"class":726},[714,853,855,857],{"class":716,"line":854},11,[714,856,743],{"class":726},[714,858,859],{"class":746}," product_id, variant_id,\n",[714,861,863],{"class":716,"line":862},12,[714,864,753],{"class":746},[714,866,868,870,872,874,876,878,880,882,884,886],{"class":716,"line":867},13,[714,869,760],{"class":759},[714,871,763],{"class":746},[714,873,766],{"class":726},[714,875,375],{"class":746},[714,877,771],{"class":726},[714,879,775],{"class":774},[714,881,778],{"class":746},[714,883,781],{"class":726},[714,885,784],{"class":726},[714,887,787],{"class":746},[714,889,891,893,895,897,899,901,903],{"class":716,"line":890},14,[714,892,793],{"class":759},[714,894,796],{"class":746},[714,896,771],{"class":726},[714,898,801],{"class":774},[714,900,804],{"class":746},[714,902,781],{"class":726},[714,904,809],{"class":746},[714,906,908,910],{"class":716,"line":907},15,[714,909,815],{"class":726},[714,911,912],{"class":746}," price_raw_variant\n",[714,914,916,918,920,922],{"class":716,"line":915},16,[714,917,824],{"class":726},[714,919,827],{"class":746},[714,921,830],{"class":726},[714,923,833],{"class":746},[714,925,927],{"class":716,"line":926},17,[714,928,839],{"emptyLinePlaceholder":341},[714,930,932],{"class":716,"line":931},18,[714,933,934],{"class":720},"-- Materialize the DDP: variant price overrides product price\n",[714,936,938,940,942,945],{"class":716,"line":937},19,[714,939,727],{"class":726},[714,941,730],{"class":726},[714,943,944],{"class":733}," price_ddp_effective",[714,946,737],{"class":726},[714,948,950,953,956,958],{"class":716,"line":949},20,[714,951,952],{"class":726},"WITH",[714,954,955],{"class":746}," unioned ",[714,957,781],{"class":726},[714,959,960],{"class":746}," (\n",[714,962,964,967,970,972,975,978,981],{"class":716,"line":963},21,[714,965,966],{"class":726},"  SELECT",[714,968,969],{"class":746}," product_id, variant_id, price, ",[714,971,766],{"class":726},[714,973,974],{"class":746},", end_date, ",[714,976,977],{"class":759},"2",[714,979,980],{"class":726}," AS",[714,982,983],{"class":746}," precedence\n",[714,985,987,990],{"class":716,"line":986},22,[714,988,989],{"class":726},"  FROM",[714,991,992],{"class":746}," price_fdp_variant\n",[714,994,996],{"class":716,"line":995},23,[714,997,998],{"class":726},"  UNION ALL\n",[714,1000,1002,1004,1007,1010,1012,1015,1017,1019,1022,1024],{"class":716,"line":1001},24,[714,1003,966],{"class":726},[714,1005,1006],{"class":746}," product_id, ",[714,1008,1009],{"class":726},"NULL",[714,1011,980],{"class":726},[714,1013,1014],{"class":746}," variant_id, price, ",[714,1016,766],{"class":726},[714,1018,974],{"class":746},[714,1020,1021],{"class":759},"1",[714,1023,980],{"class":726},[714,1025,983],{"class":746},[714,1027,1029,1031],{"class":716,"line":1028},25,[714,1030,989],{"class":726},[714,1032,1033],{"class":746}," price_fdp_product\n",[714,1035,1037],{"class":716,"line":1036},26,[714,1038,1039],{"class":746},"),\n",[714,1041,1043,1046,1048],{"class":716,"line":1042},27,[714,1044,1045],{"class":746},"resolved ",[714,1047,781],{"class":726},[714,1049,960],{"class":746},[714,1051,1053,1055],{"class":716,"line":1052},28,[714,1054,966],{"class":726},[714,1056,1057],{"class":726}," *\n",[714,1059,1061,1063],{"class":716,"line":1060},29,[714,1062,989],{"class":726},[714,1064,1065],{"class":746}," unioned\n",[714,1067,1069,1072,1075,1078,1081],{"class":716,"line":1068},30,[714,1070,1071],{"class":746},"  QUALIFY ",[714,1073,1074],{"class":759},"ROW_NUMBER",[714,1076,1077],{"class":746},"() ",[714,1079,1080],{"class":726},"OVER",[714,1082,960],{"class":746},[714,1084,1086,1089,1092,1094,1097,1100,1103,1105,1108,1110],{"class":716,"line":1085},31,[714,1087,1088],{"class":726},"    PARTITION",[714,1090,1091],{"class":726}," BY",[714,1093,1006],{"class":746},[714,1095,1096],{"class":759},"COALESCE",[714,1098,1099],{"class":746},"(variant_id, ",[714,1101,1102],{"class":726},"-",[714,1104,1021],{"class":759},[714,1106,1107],{"class":746},"), ",[714,1109,766],{"class":726},[714,1111,1112],{"class":746},", end_date\n",[714,1114,1116,1119,1122],{"class":716,"line":1115},32,[714,1117,1118],{"class":726},"    ORDER BY",[714,1120,1121],{"class":746}," precedence ",[714,1123,1124],{"class":726},"DESC\n",[714,1126,1128,1131,1134],{"class":716,"line":1127},33,[714,1129,1130],{"class":746},"  ) ",[714,1132,1133],{"class":726},"=",[714,1135,1136],{"class":759}," 1\n",[714,1138,1140],{"class":716,"line":1139},34,[714,1141,1142],{"class":746},")\n",[714,1144,1146,1148],{"class":716,"line":1145},35,[714,1147,743],{"class":726},[714,1149,859],{"class":746},[714,1151,1153,1156,1158],{"class":716,"line":1152},36,[714,1154,1155],{"class":746},"       price ",[714,1157,781],{"class":726},[714,1159,1160],{"class":746}," effective_price,\n",[714,1162,1164,1167],{"class":716,"line":1163},37,[714,1165,1166],{"class":726},"       start_date",[714,1168,1169],{"class":746},", end_date,\n",[714,1171,1173,1176,1179,1182,1184,1187,1190,1193,1196,1199,1201],{"class":716,"line":1172},38,[714,1174,1175],{"class":726},"       CASE",[714,1177,1178],{"class":726}," WHEN",[714,1180,1181],{"class":746}," variant_id ",[714,1183,830],{"class":726},[714,1185,1186],{"class":726}," THEN",[714,1188,1189],{"class":774}," 'variant'",[714,1191,1192],{"class":726}," ELSE",[714,1194,1195],{"class":774}," 'product'",[714,1197,1198],{"class":726}," END",[714,1200,980],{"class":726},[714,1202,1203],{"class":746}," price_source\n",[714,1205,1207,1209],{"class":716,"line":1206},39,[714,1208,815],{"class":726},[714,1210,1211],{"class":746}," resolved;\n",[16,1213,1214],{},"Not fancy.",[16,1216,1217],{},"Just practical.",[16,1219,1220],{},"In reality, I also handled edge cases (date overlaps, gaps, rounding rules, and invalid inputs).",[33,1222],{},[11,1224,1226],{"id":1225},"scheduling-freshness","Scheduling & Freshness",[16,1228,1229],{},"The entire pipeline refreshed through a cron job triggered via command line function and some database triggers. This could have been improved but when simple things solves the purpose, no need make it complicated with modern flows.",[16,1231,1232],{},"No special tools.",[16,1234,1235],{},"No workflow orchestrators.",[11,1237,1239],{"id":1238},"and-honestly-it-worked-extremely-well","And honestly, it worked extremely well.",[11,1241,1243],{"id":1242},"why-this-whole-thing-worked","Why This Whole Thing Worked",[16,1245,1246,1247,1250],{},"Looking back, the success wasn't about technology. It was about ",[28,1248,1249],{},"clarity",":",[68,1252,1253,1256,1259,1262],{},[71,1254,1255],{},"Inputs were clean.",[71,1257,1258],{},"Logic lived in one place.",[71,1260,1261],{},"Output was predictable.",[71,1263,1264],{},"Everyone trusted the result.",[16,1266,1267],{},"That trust mattered more than the fancy names.",[33,1269],{},[11,1271,1273],{"id":1272},"how-i-would-describe-it-today","How I Would Describe It Today",[16,1275,1276],{},"If I had to describe the same system in “modern terms,” I’d say:",[23,1278,1279],{},[16,1280,1281],{},"“This is a pricing data product built from foundational inputs, transformed through an orchestration step into a reliable derived output used across the platform.”",[11,1283,1284],{"id":330},"🤯",[16,1286,1287],{},"But honestly?",[16,1289,1290],{},"The simpler explanation still wins:",[23,1292,1293],{},[16,1294,1295],{},"“It’s one table that always had the right price.”",[16,1297,1298],{},"Both are true.\nJust different levels of vocabulary.",[33,1300],{},[11,1302,1304],{"id":1303},"what-i-learned-from-this","What I Learned From This",[1306,1307,1308,1313,1318,1323,1328,1333],"ol",{},[71,1309,1310],{},[28,1311,1312],{},"You can build something modern without knowing the terminology.",[71,1314,1315],{},[28,1316,1317],{},"Words can make things sound complex — the concepts are often simple.",[71,1319,1320],{},[28,1321,1322],{},"If systems are predictable and trusted, you're doing it right.",[71,1324,1325],{},[28,1326,1327],{},"Data engineering becomes clearer when tied to real experiences.",[71,1329,1330],{},[28,1331,1332],{},"Practical work teaches faster than reading definitions.",[71,1334,1335],{},[28,1336,1337],{},"Sometimes you understand what you built only years later.",[33,1339],{},[11,1341,1343],{"id":1342},"if-youre-building-something-similar","If You’re Building Something Similar",[16,1345,1346],{},"My advice is simple:",[68,1348,1349,1352,1355,1358,1361,1364],{},[71,1350,1351],{},"Clean your inputs.",[71,1353,1354],{},"Name your outputs clearly.",[71,1356,1357],{},"Keep the logic centralised.",[71,1359,1360],{},"Don’t overcomplicate orchestration.",[71,1362,1363],{},"Make your data trustworthy.",[71,1365,1366],{},"Let the fancy terms come after the architecture makes sense.",[16,1368,1369],{},"Understanding follows experience — not the other way around.",[33,1371],{},[16,1373,1374],{},"Thanks for reading. Writing this helped me understand these concepts in my own language.",[23,1376,1377],{},[16,1378,1379,1382],{},[19,1380,1381],{},"P.S. The idea, the story, and the experience are 100% mine — the only thing AI helped with was turning my brain dump into readable English. So if anything sounds unusually polished, blame the robot, not me."," 😄",[1384,1385,1386],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":330,"searchDepth":331,"depth":331,"links":1388},[1389,1390,1391,1392,1393,1400,1401,1402,1403,1404,1405,1406,1407],{"id":358,"depth":740,"text":359},{"id":412,"depth":331,"text":413},{"id":424,"depth":331,"text":425},{"id":498,"depth":331,"text":499},{"id":571,"depth":331,"text":572,"children":1394},[1395,1396,1397,1398,1399],{"id":575,"depth":740,"text":576},{"id":593,"depth":740,"text":594},{"id":624,"depth":740,"text":625},{"id":645,"depth":740,"text":646},{"id":675,"depth":740,"text":676},{"id":701,"depth":331,"text":702},{"id":1225,"depth":331,"text":1226},{"id":1238,"depth":331,"text":1239},{"id":1242,"depth":331,"text":1243},{"id":1272,"depth":331,"text":1273},{"id":330,"depth":331,"text":1284},{"id":1303,"depth":331,"text":1304},{"id":1342,"depth":331,"text":1343},"2026-02-24","Years ago, I built a simple pricing system: raw tables in, a stored procedure in the middle, one final table out. Much later I realised I had already built data products, FDPs, DDPs, lineage, and contracts — without knowing the names.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1558494949-ef010cbdcc31?auto=format&fit=crop&w=1600&q=80",{},"\u002Fblog\u002Faccidentally-built-modern-data-platform",{"title":352,"description":1409},"blog\u002Faccidentally-built-modern-data-platform",[1416,1417,1418,1419],"Data Engineering","Data Products","Data Architecture","ETL","MomWGg5Nbz0WKnULPiOKMqSfQ3R1L0epVDOjqWp0E_o",{"id":1422,"title":1423,"body":1424,"date":1624,"description":1625,"extension":338,"image":1626,"meta":1627,"navigation":341,"path":1628,"seo":1629,"stem":1630,"tags":1631,"__hash__":1633},"blog\u002Fblog\u002Faustria.md","Austria and Switzerland Tourist Places",{"type":8,"value":1425,"toc":1613},[1426,1429,1433,1436,1466,1469,1513,1517,1534,1538,1542,1586,1590],[16,1427,1428],{},"Travel teaches you how to be present. Europe's alpine corridor — Austria and Switzerland — is particularly good at forcing that lesson on you.",[11,1430,1432],{"id":1431},"austria","Austria",[356,1434,1021],{"id":1435},"_1",[68,1437,1438,1445,1452,1459],{},[71,1439,1440],{},[96,1441,1444],{"href":1442,"rel":1443},"https:\u002F\u002Fmaps.app.goo.gl\u002FLzTD6JKoNy9oykR58?g_st=ipc",[100],"Panoramic Viewpoint - Hallstatt",[71,1446,1447],{},[96,1448,1451],{"href":1449,"rel":1450},"https:\u002F\u002Fmaps.app.goo.gl\u002FpwgAF1Ss8BbTiuG77?g_st=ipc",[100],"Hallstatt Skywalk - Hallstättersee",[71,1453,1454],{},[96,1455,1458],{"href":1456,"rel":1457},"https:\u002F\u002Fmaps.app.goo.gl\u002Faopcbfkfs38tD5YR6?g_st=ipc",[100],"Salzwelten Hallstatt & Shop",[71,1460,1461],{},[96,1462,1465],{"href":1463,"rel":1464},"https:\u002F\u002Fwww.salzwelten.at\u002Fde\u002Fhallstatt\u002Fpreise-oeffnungszeiten",[100],"Ticket Price: 180 DKK",[356,1467,977],{"id":1468},"_2",[68,1470,1471,1478,1485,1492,1499,1506],{},[71,1472,1473],{},[96,1474,1477],{"href":1475,"rel":1476},"https:\u002F\u002Fmaps.app.goo.gl\u002FWHhRQDyhowWkMLnr5?g_st=ipc",[100],"Salzbug",[71,1479,1480],{},[96,1481,1484],{"href":1482,"rel":1483},"https:\u002F\u002Fmaps.app.goo.gl\u002FingeDct29C65CzhdA?g_st=ipc",[100],"Lammerklamm",[71,1486,1487],{},[96,1488,1491],{"href":1489,"rel":1490},"https:\u002F\u002Fmaps.app.goo.gl\u002F25xEYRugPAYMpNsY9?g_st=ipc",[100],"Gollinger waterfall",[71,1493,1494],{},[96,1495,1498],{"href":1496,"rel":1497},"https:\u002F\u002Fmaps.app.goo.gl\u002FwD2VMycfBwtxbotR6?g_st=ipc",[100],"Werfen Ice cave: Eisriesenwelt",[71,1500,1501],{},[96,1502,1505],{"href":1503,"rel":1504},"https:\u002F\u002Fmaps.app.goo.gl\u002Fon7oimDd5YWEXnh1A?g_st=ipc",[100],"Liechtensteinklamm",[71,1507,1508],{},[96,1509,1512],{"href":1510,"rel":1511},"https:\u002F\u002Fmaps.app.goo.gl\u002F9DQFdzezSVLUyY2w8?g_st=ipc",[100],"Sigmund Thun Klamm Wasserfall",[356,1514,1516],{"id":1515},"_3","3",[68,1518,1519,1526],{},[71,1520,1521],{},[96,1522,1525],{"href":1523,"rel":1524},"https:\u002F\u002Fmaps.app.goo.gl\u002FWC1UpGFbpgsYhhMj8",[100],"Swarovski Kristallwelten",[71,1527,1528,1529],{},"Innsbruck - ",[96,1530,1533],{"href":1531,"rel":1532},"https:\u002F\u002Fmaps.app.goo.gl\u002FVsbajUEu6BrXNjM66",[100],"Golden Roof - Goldenes Dachl",[11,1535,1537],{"id":1536},"switzerland","Switzerland",[356,1539,1541],{"id":1540},"_4","4",[68,1543,1544,1551,1558,1565,1572,1579],{},[71,1545,1546],{},[96,1547,1550],{"href":1548,"rel":1549},"https:\u002F\u002Fmaps.app.goo.gl\u002FVXXaZobBYBbCBAe38",[100],"Interlaken Ost",[71,1552,1553],{},[96,1554,1557],{"href":1555,"rel":1556},"https:\u002F\u002Fmaps.app.goo.gl\u002FHVokyvFFn9gX5J5M9",[100],"Yash Chopra Statue",[71,1559,1560],{},[96,1561,1564],{"href":1562,"rel":1563},"https:\u002F\u002Fwww.swissactivities.com\u002Fen-ch\u002Frail-pass\u002Fjungfrau-travel-pass-for-3-to-8-days\u002F",[100],"Jungfrau Travel Pass Summer - 3 to 8 Days",[71,1566,1567],{},[96,1568,1571],{"href":1569,"rel":1570},"https:\u002F\u002Fmaps.app.goo.gl\u002Fdzbx9oo4qfYyvRtY6",[100],"Grindelwald, Terminal",[71,1573,1574],{},[96,1575,1578],{"href":1576,"rel":1577},"https:\u002F\u002Fmaps.app.goo.gl\u002FEvwmgH685JLTQmrE8",[100],"Top of Europe - Jungfraujoch",[71,1580,1581],{},[96,1582,1585],{"href":1583,"rel":1584},"https:\u002F\u002Fmaps.app.goo.gl\u002FomSAPs48Rk2rbrPi7",[100],"Grindelwald-First - Top of Adventure",[356,1587,1589],{"id":1588},"_5","5",[68,1591,1592,1599,1606],{},[71,1593,1594],{},[96,1595,1598],{"href":1596,"rel":1597},"https:\u002F\u002Fmaps.app.goo.gl\u002FMxB1j8gKYWfdiAXf7",[100],"Saanen",[71,1600,1601],{},[96,1602,1605],{"href":1603,"rel":1604},"https:\u002F\u002Fmaps.app.goo.gl\u002Fi2iBnGy9wHz6CVai6",[100],"DDLJ Bridge",[71,1607,1608],{},[96,1609,1612],{"href":1610,"rel":1611},"https:\u002F\u002Fmaps.app.goo.gl\u002FYF6SwSnwGRtMZJxL8",[100],"Gstaad",{"title":330,"searchDepth":331,"depth":331,"links":1614},[1615,1620],{"id":1431,"depth":331,"text":1432,"children":1616},[1617,1618,1619],{"id":1435,"depth":740,"text":1021},{"id":1468,"depth":740,"text":977},{"id":1515,"depth":740,"text":1516},{"id":1536,"depth":331,"text":1537,"children":1621},[1622,1623],{"id":1540,"depth":740,"text":1541},{"id":1588,"depth":740,"text":1589},"2025-04-02","Notes and highlights from travels through Austria and Switzerland — architecture, alpine landscapes, and the art of slowing down.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1597086831879-756db15e81d3?auto=format&fit=crop&w=600&q=80",{},"\u002Fblog\u002Faustria",{"title":1423,"description":1625},"blog\u002Faustria",[1632],"Travel","FAsfVJjA48kxrW3s1saRrrtyR-yAqq777WSP-alYdwA",{"id":1635,"title":1636,"body":1637,"date":1761,"description":1762,"extension":338,"image":1763,"meta":1764,"navigation":341,"path":1765,"seo":1766,"stem":1767,"tags":1768,"__hash__":1770},"blog\u002Fblog\u002Frevert-full-commit.md","Revert the full commit",{"type":8,"value":1638,"toc":1756},[1639,1642,1660,1664,1668,1677,1695,1710,1743,1745,1753],[16,1640,1641],{},"Sometimes you may want to undo a whole commit with all changes. Instead of going through all the changes manually, you can simply tell git to revert a commit, which does not even have to be the last one. Reverting a commit means to create a new commit that undoes all changes that were made in the bad commit. Just like above, the bad commit remains there, but it no longer affects the the current master and any future commits on top of it.",[559,1643,1647],{"className":1644,"code":1645,"language":1646,"meta":330,"style":330},"language-shell shiki shiki-themes github-light github-dark","git revert {commit_id}\n","shell",[518,1648,1649],{"__ignoreMap":330},[714,1650,1651,1654,1657],{"class":716,"line":717},[714,1652,1653],{"class":733},"git",[714,1655,1656],{"class":774}," revert",[714,1658,1659],{"class":774}," {commit_id}\n",[11,1661,1663],{"id":1662},"about-history-rewriting","About History Rewriting",[356,1665,1667],{"id":1666},"delete-the-last-commit","Delete the last commit",[16,1669,1670,1671,1674,1675,1250],{},"Deleting the last commit is the easiest case.\nLet's say we have a remote origin with branch master that currently points to commit ",[19,1672,1673],{},"dd61ab32",".\nWe want to remove the top commit. Translated to git terminology, we want to force the master branch of the origin remote repository to the parent of ",[19,1676,1673],{},[559,1678,1680],{"className":1644,"code":1679,"language":1646,"meta":330,"style":330},"git push origin +dd61ab32^:master\n",[518,1681,1682],{"__ignoreMap":330},[714,1683,1684,1686,1689,1692],{"class":716,"line":717},[714,1685,1653],{"class":733},[714,1687,1688],{"class":774}," push",[714,1690,1691],{"class":774}," origin",[714,1693,1694],{"class":774}," +dd61ab32^:master\n",[16,1696,1697,1698,1701,1702,1705,1706,1709],{},"Where git interprets ",[518,1699,1700],{},"x^"," as the parent of ",[518,1703,1704],{},"x"," and ",[518,1707,1708],{},"+"," as a forced non-fastforward push.\nIf you have the master branch checked out locally, you can also do it in two simpler steps: First reset the branch to the parent of the current commit, then force-push it to the remote.",[559,1711,1713],{"className":1644,"code":1712,"language":1646,"meta":330,"style":330},"git reset HEAD^ --hard\n\ngit push origin -f\n",[518,1714,1715,1728,1732],{"__ignoreMap":330},[714,1716,1717,1719,1722,1725],{"class":716,"line":717},[714,1718,1653],{"class":733},[714,1720,1721],{"class":774}," reset",[714,1723,1724],{"class":774}," HEAD^",[714,1726,1727],{"class":759}," --hard\n",[714,1729,1730],{"class":716,"line":331},[714,1731,839],{"emptyLinePlaceholder":341},[714,1733,1734,1736,1738,1740],{"class":716,"line":740},[714,1735,1653],{"class":733},[714,1737,1688],{"class":774},[714,1739,1691],{"class":774},[714,1741,1742],{"class":759}," -f\n",[33,1744],{},[16,1746,1747,1748,1752],{},"This document is inspired by ",[96,1749,1750],{"href":1750,"rel":1751},"http:\u002F\u002Fchristoph.ruegg.name\u002Fblog\u002Fgit-howto-revert-a-commit-already-pushed-to-a-remote-reposit.html",[100]," - Thank you.",[1384,1754,1755],{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":330,"searchDepth":331,"depth":331,"links":1757},[1758],{"id":1662,"depth":331,"text":1663,"children":1759},[1760],{"id":1666,"depth":740,"text":1667},"2023-04-02","Sometimes you may want to undo a whole commit with all changes. Git revert creates a new commit that undoes the bad one — clean, safe, and collaborative.","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1647166545674-ce28ce93bdca?auto=format&fit=crop&w=600&q=80",{},"\u002Fblog\u002Frevert-full-commit",{"title":1636,"description":1762},"blog\u002Frevert-full-commit",[1769],"Git","W6tN31GJqSA-z0ZYyHkDOEuKiR5SYUj0UZBGqCTL0Xg",1774962206219]