Weekend Build Log: Freeze Four JSON Fields First A developer outlined a weekend build plan that freezes a four-field JSON contract for receipt parsing before any model runs, shipping a contract file, a single fixture, and a short validator script. The approach deliberately avoids dashboards, OAuth, agent loops, and streaming tokens, arguing that locking the output schema first prevents scope creep and churn. The writeup frames the frozen contract as the product for the weekend, with everything else deferred to a reject file. You open the laptop at 9:14 on Saturday. A messy folder of receipt photos sits nearby. You want one command that prints four JSON fields. The chat pane already wants a dashboard. It also wants OAuth, retries, and a queue. That is how quiet weekends disappear. This walkthrough stays small on purpose. You freeze a JSON contract before any model runs. Then you ship one command and one validator. Treat the steps below as a proposed weekend plan. Nothing here is a production benchmark. Your git history is the only scoreboard. You have maybe six focused hours. You do not have a product team. Scope is the only lever you still control. A coding helper fills empty hours with new surfaces. Extra surfaces demand extra tests. Extra tests steal the Sunday demo. So you lock the output shape first. The schema is the product this weekend. Everything else waits in a reject file. Recent AI coding talk mixes speed with engineering. Speed without a freeze is just churn. A frozen contract is the missing gate. You will ship three files and one command. That is the whole demo surface. You will not ship a UI. You will not ship an agent loop. You will not stream tokens into business logic. Create contract.json before you open any helper. Keep four fields only. Name them like an API, not like chat. { "version": "1", "fields": { "merchant": {"type": "string", "max": 80}, "total cents": {"type": "integer", "min": 0}, "currency": {"type": "string", "enum": "USD", "EUR", "CNY" }, "purchased on": {"type": "string", "pattern": "^ 0-9 {4}- 0-9 {2}- 0-9 {2}$"} } } Print that file. Read each field name out loud. If a field feels fuzzy, delete it now. Do not add tax , tips , or line items today. Those fields invite parsers you will not finish. Fuzzy money fields also hide rounding bugs. Commit the contract before any parser exists. The commit message can be one line. freeze: four-field receipt contract v1 is enough. Create fixtures/receipt 001.json next. This is the only legal demo input. A second fixture is a later product. { "raw text": "ORBIT CAFE\n2026-09-14\nLatte 4.50\nTotal $4.50", "expected": { "merchant": "ORBIT CAFE", "total cents": 450, "currency": "USD", "purchased on": "2026-09-14" } } One fixture proves the contract path. It does not prove OCR. It does not prove every cafe in town. If the helper asks for a corpus, refuse. You are proving a shape, not a dataset. Extra photos belong in the reject file. Redact any real merchant names before you paste. Fake the cafe. Fake the total. Keep private receipts off the prompt. Create validate.py and keep it short. No framework. No JSON Schema plugin this weekend. python /usr/bin/env python3 import json import sys from pathlib import Path CONTRACT = json.loads Path "contract.json" .read text FIELDS = CONTRACT "fields" def fail msg: str - None: print f"CONTRACT FAIL: {msg}", file=sys.stderr raise SystemExit 1 def check obj: dict - None: if set obj = set FIELDS : fail f"keys {sorted obj } = {sorted FIELDS }" merchant = obj "merchant" if not isinstance merchant, str or not merchant or len merchant 80: fail "merchant" total = obj "total cents" if not isinstance total, int or isinstance total, bool or total < 0: fail "total cents" if obj "currency" not in FIELDS "currency" "enum" : fail "currency" date = obj "purchased on" if not isinstance date, str or len date = 10: fail "purchased on" if date 4 = "-" or date 7 = "-": fail "purchased on" y, m, d = date.split "-" if not y.isdigit and m.isdigit and d.isdigit : fail "purchased on" def main - None: payload = json.loads sys.stdin.read check payload print "CONTRACT OK" if name == " main ": main Run it against the fixture expected block first. You want a green path now. The model has not written anything yet. python python3 -c 'import json,pathlib; p=json.loads pathlib.Path "fixtures/receipt 001.json" .read text ; print json.dumps p "expected" ' | python3 validate.py You should see CONTRACT OK on stdout. If you do not, stop immediately. Fix the contract by hand, not the helper. Add a second command that must fail. Feed it a renamed key on purpose. The gate is real only when it rejects drift. echo '{"merchant":"X","amount":1,"currency":"USD","purchased on":"2026-09-14"}' | python3 validate.py; echo exit:$? That run should print CONTRACT FAIL and a non-zero status. Save both commands in README.md . Those two lines are the demo script. Create parse.py after the validator is green. Hard-code the fixture path. Do not walk a photo directory. python /usr/bin/env python3 import json import re from pathlib import Path fx = json.loads Path "fixtures/receipt 001.json" .read text text = fx "raw text" merchant = text.splitlines 0 .strip date = re.search r" \d{4}-\d{2}-\d{2} ", text .group 1 cents = int round float re.search r"Total \$ 0-9. + ", text .group 1 100 out = { "merchant": merchant, "total cents": cents, "currency": "USD", "purchased on": date, } print json.dumps out This stub is allowed to look ugly. Ugly and finished beats elegant and open. You can rewrite it next month. Pipe the stub into the validator. python3 parse.py | python3 validate.py Green output is the weekend demo. Record that pipe in README.md . Do not open a browser to prove it. If the regex fails, edit parse.py yourself. Do not start a second helper session yet. Hands-on edits keep the schema honest. Create REJECT.md beside the contract. Write refusals as concrete items. Vague principles will not stop a chatty model. Reject this weekend - web dashboard - auth and sessions - PDF OCR pipeline - second fixture - retry loop around the model - streaming UI - database - extra money fields Read that list before every helper prompt. If the reply adds a rejected surface, discard the reply. Do not negotiate with the extra files. The reject file is a build tool. It is not a confession. You are buying a Sunday demo with Saturday restraint. If you use a coding helper, paste contract.json first. Paste REJECT.md second. Ask only for a four-field parse.py . Do not ask it to make this production ready. That phrase deletes weekends. Ask for stdout JSON that matches the contract. After the first patch, stop the helper. Run the validator again. If it fails, you edit the stub by hand. This is a gate, not a leaderboard. It does not measure model quality. It measures whether your weekend still has a shape. Use this table when the helper gets generous. Print it. Keep it above the keyboard. | Incoming request | Saturday action | |---|---| | Add a fifth JSON field | Refuse; append it to REJECT.md | | Rename total cents | Refuse; keep the frozen key | | Stand up a React app | Refuse | | Add a second fixture | Refuse until the pipe stays green | | Validator false positive | Fix validate.py by hand | | Parser misses the date | Edit parse.py ; no new loop | | "Make it robust" | Refuse | | Public URL with open prompt | Refuse | The table is the original artifact. Code can drift. The table states the policy in one glance. You may want a model for the first parse.py draft. You may later want a throwaway host for one JSON endpoint. Both come after the contract exists. Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode provides free model access and a free server option you can use for that bounded weekend pass. Keep contract.json in your repo either way. The helper does not own the shape. A later endpoint should accept one JSON body. It should return four fields or an error. Reuse validate.py as the server gate if you host anything. Do not point a public URL at an unbounded prompt. A free server is not a license for a dashboard. The reject file still wins. The parser will choke on missing dollar signs. That is acceptable this weekend. Document the miss in REJECT.md and move on. The helper will rename total cents to amount . The validator must reject that rename. Do not stretch the contract to match the model. Currency enums will feel rude. Keep them rude. Open string fields become soup by Sunday night. Dates without hyphens will fail the gate. Good. Your demo is a contract, not a guesser. Guessers do not survive the next fixture. Boolean true must not pass as total cents . Python treats bool as int without the extra check. That is why the validator excludes bool explicitly. This approach ignores real OCR. It ignores multi-line merchant names. It ignores tax-inclusive totals and tips. It also ignores load, auth, and retention. Those are product problems. They are not Saturday problems. The validator is not a schema platform. It is a weekend gate. Replace it later if the contract survives a month. Free model output can drift across sessions. You do not pin a vendor by hoping. You pin a file in git and a failing command. A free server is not a production posture. Treat it as a demo host only. Put secrets nowhere near that host. This article does not name models, quotas, or hardware. Those claims go stale fast. Your contract file does not. Do not use this if Monday needs every receipt parsed. You need a real pipeline then. A stub will lie to finance. Do not use this if compliance needs an audit trail. A weekend script is not that trail. You will fake safety and regret it. Do not use this if your team already owns a registry. Follow that registry instead. Do not fork a toy schema beside it. Do not paste private receipts into any helper. Redact the fixture. Fake the cafe. Keep personal amounts local. Skip this if you cannot freeze keys. If every chat can rename fields, you do not have a product. You have a conversation. Your done state is one pipe that prints CONTRACT OK . That is enough for Sunday. Anything past that is next weekend. If the helper still wants a dashboard, reread REJECT.md . Then close the chat. Ship the command and walk away.