Deadweight: price your generosity before you ship it
Built for the DEV Weekend Challenge: Generosity Edition. Live: https://deadweight-jet.vercel.app Repo: https://github.com/AnubhavDash/DeadWeight The coat Someone in Rasuwa needs a coat. You have a coat. The arithmetic looks finished. It isn't. Between your hands and theirs sit an air waybill, a customs broker, a sorting line paid by the hour, a warehouse in a country whose warehouses are already full, and - often enough - an incinerator. Every one of those steps has a published rate. None of them appear on the box. This has a name in the humanitarian sector. They call the arrival of unrequested goods the second disaster, and they have been asking people to stop for forty years. The Logistics Cluster writes guidance about it. OCHA writes guidance about it. The IFRC's disaster-law reviews keep finding the same story: after Cyclone Pam, unsolicited donations sat in Vanuatu for twelve months. Airlink has put a number on the share of donated goods that is simply inappropriate for the response receiving it. And still the boxes come, because the impulse behind them is correct. Someone is cold and you have a coat. There is nothing wrong with that instinct. What's wrong is that nobody ever shows you the invoice. So I built the invoice. What it is Deadweight is a ledger for the gap between giving something and it arriving. You build a consignment on a manifest - winter jackets out of a wardrobe, bottled water by the litre, used shoes, soft toys, the medicine cabinet. You pick a route: air (days, and the only way into the cut-off districts), road over the Birgunj-Raxaul crossing (weeks), or sea and road (months, because Nepal is landlocked and the ocean stops at Kolkata). Then it prices the whole journey in USD, line by line, and every line opens: DECLARED VALUE $2,800.00 3 line items unusable used clothing -$1,615.00 15% of this class arrives usable SOURCE [ASSUMPTION] not needed or not appropriate -$540.00 40% of this class arrives usable SOURCE [ASSUMPTION] air freight DELβKTM -$84.00 56.0 kg Γ $1.50/kg SOURCE sorting labour -$0.31 0.4 h Γ $0.70/h local wage SOURCE [ASSUMPTION] disposal -$4.14 43.1 kg unusable Γ $0.096/kg SOURCE [ASSUMPTION] NET VALUE DELIVERED $556.55 BURDENS - 20% of declared value delivered Every row shows the rate, the range it came from, the publisher, the date, and the multiplication. The verdict is LANDS , BURDENS , or BECOMES ASH . Two things about that table matter more than the verdict. It reads every source at its kindest end by default. Costs low, usefulness high, storage at zero. A donor who doesn't like the answer can't accuse the table of stacking the deck, because it already stacked it in their favour. (You can flip it to midpoint, or to harshest, and watch $556.55 delivered become $339.73 lost.) It says out loud which of its own cells are assumptions. Four of the fourteen rate cells are reasoned from guidance rather than quoted from a source, and each one is tagged ASSUMPTION everywhere it appears, with its range, publisher, date and low confidence. A project that prices other people's generosity does not get to hide its own soft spots. The half that makes it not a lecture A tool that only tells you your coat was a mistake is a tool nobody uses twice. The question the ledger provokes is then what should I send? - and if the app can't answer that, the whole thing is a sermon. So it answers it by computation. It takes your own declared total, spends it on something the flash appeal actually named, and runs that counterfactual manifest through the same engine, at the same freight mode, at the same reading of the sources: 1,866 Γ Water purification tablets (strip of 10) - LANDS $2,799.00 declared Β· $2,630.87 delivered Β· 94% of it +$2,074.32 more reaches the response than the manifest above, for the money already being spent. No new conversion table is introduced. Nothing is quoted that wasn't already quoted. The comparison is the engine disagreeing with itself about two ways to spend one sum of money, which is a claim you can check line by line on both sides. And when nothing on the appeal's list beats what you've already put on the manifest, the panel says so and offers nothing. It never manufactures an improvement to have something to say. The rule the codebase exists to enforce Here's the part I actually care about as an engineer. One deterministic function owns every number, and no model is ever allowed to produce one. price() is pure. Same manifest, same mode, same reading, same answer, in integer US cents - the money type is branded (type Cents = number & { β¦ } ) so that writing a - b on two amounts fails to compile and you have to go through addCents(a, negate(b)) on purpose. It is the only thing in the repository that does arithmetic on money. The browser calls it. The route handlers call it. The on-chain program re-derives its verdict from the same rule. The language model's job is narration, and the guarantee that it stays narration is structural rather than hopeful. The prompt sent to Gemini contains no digits at all. Not one. The consignment is described to it in words - the route, the shape of the outcome, whether anything on it was prohibited - and every figure is handed over as a vocabulary of fifteen placeholder tokens instead of a value: {{declared}} {{net}} {{owed}} {{efficiency}} {{weight}} {{unusable}} {{cash}} {{shares}} {{verdict}} {{route}} {{reading}} {{items}} {{prohibited}} {{appeal}} {{people}} The model may state a figure only by placing one of those, written exactly as given. It never learns what any of them are worth. Substitution happens afterwards, in one function, from the engine's own output. Then normalize() reads the draft back and throws the entire thing away if: - a digit appears anywhere outside a placeholder - digits - a quantity is stated in words: "twenty per cent", "a third", "half" - words - it used a token that doesn't exist, or one that doesn't apply to this particular consignment - token - it came back empty, or over length - the model's own safety filter blocked the brief No repair step, no partial acceptance. A repair step is a negotiation, and the point is that this isn't negotiable. And a refused draft isn't a broken feature. The deterministic letter is written in the same fifteen tokens and finished by the same substitution function, so when the model is unavailable, or slow, or numeric, what you read is the engine's own prose and the byline changes from a model id to engine . The feature degrades to the truth instead of to an error toast. Google AI - the letter from the warehouse The narration isn't a summary bolted on top. It's a letter, written in the voice of the person who unpacked your consignment, writing back to tell you what happened to it. That's a thing a language model is genuinely good at and an engine is not: tone, restraint, the decision not to gloat. The route handler walks a chain - gemini-3.8-flash , then gemini-3.7-flash , then gemini-3.5-flash , overridable with GEMINI_MODELS so a retired model id is a config change rather than a deploy. Sixteen seconds per model, and it moves down the chain on a timeout, an error, a blocked brief, or a draft that failed the clamp. A model that keeps inventing numbers simply loses its turn. That chain isn't defensive theatre - it earned its keep the first time I pointed a real key at it. gemini-2.5-flash sat at the tail until it started answering 404, "no longer available to new users" while still being listed by GET /v1beta/models : being listed is not being callable. And gemini-3.8-flash , the newest and the one at the head, answers 503, "experiencing high demand" on most attempts right now. So on a live run the letter you read is written by the middle of the chain, the log records both misses by name, and the reader is never shown a spinner that ends in nothing. Every figure in the model's letter came back byte-identical to the engine's - same dollars, same percentage, same kilograms - because the model never had any of them to get wrong. The prohibited-items paragraph gets handled separately, because it isn't an arithmetic problem. Donated infant formula and part-used medicine out of a home cabinet can't be accepted at all - not sorted, not stored, not passed to anybody. The letter says so in the words the agencies use, and the ledger doesn't hand them a gentler verdict for being well meant. ElevenLabs - and not becoming somebody's free TTS proxy The letter is meant to be heard. Read aloud, "$556.55 of $2,800.00 arrived" lands somewhere a table doesn't. eleven_multilingual_v2 , chosen deliberately over the faster models because the docs are clear that the low-latency ones are less reliable at normalising written amounts, and every sentence in this letter has an amount in it. If ELEVENLABS_VOICE_ID isn't set the handler asks the account for its first voice and uses that, re-checked per request, because a voice can disappear from an account between two calls. The voice model is handed finished prose. It is given no figures to reason about and no arithmetic to do. Nothing numeric can reach your ears that price() didn't compute. There's an obvious hole in shipping a public text-to-speech endpoint, so it's closed: /api/letter returns an HMAC-SHA256 seal alongside the letter, and /api/narrate will only speak text whose seal verifies (timingSafeEqual , not === ). Send it your own paragraph and it quietly reads this build's letter instead - dropped in silence rather than refused, because a seal issued by another process isn't the caller's fault. Set LETTER_SECRET and the seal survives a redeploy; leave it unset and each process mints a random 32 bytes at boot. src/lib/seal.ts is honest in its own comments about what that does and doesn't guarantee: it's a brake on casual abuse, not an authentication system. No key? The browser's own speechSynthesis reads it. The button never lies about whether there's a voice behind it. Solana - a notary, and the honest reason for it I'll say the unfashi
Comments
No comments yet. Start the discussion.