DEV Community

Avery Lin
Avery Lin

Posted on

Pin the Meter Before the Chat

The kitchen kettle clicked off just after eleven. A laptop sat beside a cooling mug of tea. A weekend product still lacked a Pro switch.

The editor held a fresh webhook from a coding model. Stripe test keys waited in a second browser tab. One real charge could land by tomorrow morning.

The founder needed the meter to stay dull. Indie shipping loves speed and hates surprise invoices. A chat window will invent plan names overnight.

It will also invent grace days without shame. Those inventions do not belong in production math.

The useful split is blunt and physical. The model may draft screens and stubborn copy. Every price rule belongs in one small file.

That file should run on a cheap box tonight. This article treats the split as a kitchen workflow. It is a proposal with runnable fixtures only.

The meter file is a boring Python module. It knows plans, cents, and trial days. It does not know HTTP or Stripe objects.

It also refuses to read poetry from prompts. Frozen dataclasses keep accidental writes from sticking. The quote function is the only public door.

# meter.py
from dataclasses import dataclass


@dataclass(frozen=True)
class Plan:
    name: str
    cents: int
    trial_days: int
    seats: int


PLANS = {
    "free": Plan("free", 0, 0, 1),
    "pro": Plan("pro", 1200, 7, 3),
}


def quote(plan_name: str, extra_seats: int) -> dict:
    if plan_name not in PLANS:
        raise KeyError("unknown plan")
    plan = PLANS[plan_name]
    if extra_seats < 0:
        raise ValueError("seats cannot go negative")
    seat_cents = 400 * extra_seats
    total = plan.cents + seat_cents
    return {
        "plan": plan.name,
        "cents": total,
        "trial_days": plan.trial_days,
        "seats": plan.seats + extra_seats,
    }
Enter fullscreen mode Exit fullscreen mode

A golden fixture file sits beside that module. The fixture numbers stay ugly on purpose tonight. Pretty round prices often hide off-by-one seats.

# test_meter.py
import unittest
from meter import quote


class MeterFixtures(unittest.TestCase):
    def test_free_stays_zero(self):
        row = quote("free", 0)
        self.assertEqual(row["cents"], 0)
        self.assertEqual(row["seats"], 1)

    def test_pro_base(self):
        row = quote("pro", 0)
        self.assertEqual(row["cents"], 1200)
        self.assertEqual(row["trial_days"], 7)

    def test_two_extra_seats(self):
        row = quote("pro", 2)
        self.assertEqual(row["cents"], 2000)
        self.assertEqual(row["seats"], 5)

    def test_unknown_plan_blows_up(self):
        with self.assertRaises(KeyError):
            quote("enterprise", 0)

    def test_negative_seats_blows_up(self):
        with self.assertRaises(ValueError):
            quote("pro", -1)


if __name__ == "__main__":
    unittest.main()
Enter fullscreen mode Exit fullscreen mode

The founder runs it before the model gets more files. The command is small enough to memorize.

python3 -m unittest test_meter.py -v
Enter fullscreen mode Exit fullscreen mode

Green tests do not mean the webhook is honest. They mean the price door still slams. That slam is the whole point tonight.

The model may still write a handler nearby. That handler must call quote and nothing else. A one-line grep keeps the contract visible.

git grep -n "quote(" -- "*.py"
Enter fullscreen mode Exit fullscreen mode

The founder should see meter.py and the handler. Extra hits mean the chat leaked a second price path. That extra path should vanish before coffee cools.

Laptop lids kill local coding sessions without warning. Indie nights end with sleep, not with daemons. The meter then copies to a remote box that stays awake.

The box can be any free server already on hand. Disclosure: This article was prepared as part of MonkeyCode's product outreach. Free model access can draft screens while a free server keeps the meter awake.

The workflow below uses rsync and a login shell. The host alias must become a real hostname. Nothing in the script assumes a paid machine.

# freeze_meter.sh
set -euo pipefail
HOST="${REMOTE_HOST:?set REMOTE_HOST}"
ROOT="${REMOTE_ROOT:?set REMOTE_ROOT}"

rsync -az --delete \
  meter.py test_meter.py \
  "${HOST}:${ROOT}/meter/"

ssh "$HOST" "cd ${ROOT}/meter && python3 -m unittest test_meter.py -v"
Enter fullscreen mode Exit fullscreen mode
chmod +x freeze_meter.sh
REMOTE_HOST=freebox REMOTE_ROOT=/home/indie/app ./freeze_meter.sh
Enter fullscreen mode Exit fullscreen mode

The remote run is the hallway check. If it fails, the Pro switch stays dark. The founder can sleep without a ghost invoice.

A second command pins the file against drift. The founder hashes the meter before chat resumes. The hash belongs beside the golden fixtures.

sha256sum meter.py > meter.py.sha256
ssh "$REMOTE_HOST" "cd $REMOTE_ROOT/meter && sha256sum -c -" < meter.py.sha256
Enter fullscreen mode Exit fullscreen mode

A mismatch means someone edited cents in prose. The file should restore, then remote tests rerun. The founder should not negotiate with the diff tonight.

Webhook code can stay messy for one weekend. The handler should treat quote as a cash drawer. Here is a labeled sketch, not live Stripe code.

# handler_sketch.py — proposal only, not a Stripe integration
from meter import quote


def build_checkout_intent(plan_name: str, extra_seats: int) -> dict:
    snap = quote(plan_name, extra_seats)
    return {
        "amount": snap["cents"],
        "currency": "usd",
        "metadata": {
            "plan": snap["plan"],
            "seats": str(snap["seats"]),
            "trial_days": str(snap["trial_days"]),
        },
    }
Enter fullscreen mode Exit fullscreen mode

Notice the sketch never multiplies prices by itself. Seat math already happened inside meter.py tonight. The chat can change button labels without touching cents.

A tiny decision table lives in comments for humans. Free plus zero seats yields zero cents. Pro plus two seats yields two thousand cents.

Unknown plan names raise KeyError on purpose. That table is the product for an indie launch. Features may wobble while the quote cannot.

Sunday afternoon often brings a new plan name in chat. The model offers Enterprise at a friendly discount. The fixture file should fail until the founder types cents.

That failure is the product sleeping on the porch. A dark Pro switch is cheaper than a wrong charge. The kettle can wait for a second green run.

Limits walk in after the first green run. This meter has no tax, no FX, no proration. It has no refunds and no usage buckets.

It will not satisfy a marketplace with many vendors. It will not satisfy a payroll product either. Finance software needs ledgers, not a frozen dataclass.

A free remote box is not a contract. It can vanish, throttle, or reboot at lunch. A local copy of meter.py.sha256 should stay nearby.

The approach also fails when several people edit prices. A solo founder can guard one file by habit. A team needs review rules this article skips.

This workflow should not touch health billing claims. Card movement beyond a quote stays out of scope. Test keys are not a license to be careless.

The labeled sketch never charges a live card. Time-sensitive model charts are omitted on purpose. Those published numbers rot within a single week.

The fixture file does not rot if plans stay frozen. The cultural trap this week is familiar on developer forums. Coding models make it easy to mimic shipping.

A passing chat demo is not an entitlement system. Pinning the meter is a small act of engineering. It is not a platform in any sense.

It is a porch light for a one-person shop. When the Sunday demo ends, freeze_meter.sh runs once more. Then the laptop can close without leftover guilt.

The cents either held or they did not. The limits stay, and the meter still ships first.

Top comments (0)