{"slug": "weekend-build-log-freeze-four-json-fields-first", "title": "Weekend Build Log: Freeze Four JSON Fields First", "summary": "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.", "body_md": "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.\n\nThe chat pane already wants a dashboard. It also wants OAuth, retries, and a queue. That is how quiet weekends disappear.\n\nThis walkthrough stays small on purpose. You freeze a JSON contract before any model runs. Then you ship one command and one validator.\n\nTreat the steps below as a proposed weekend plan. Nothing here is a production benchmark. Your git history is the only scoreboard.\n\nYou have maybe six focused hours. You do not have a product team. Scope is the only lever you still control.\n\nA coding helper fills empty hours with new surfaces. Extra surfaces demand extra tests. Extra tests steal the Sunday demo.\n\nSo you lock the output shape first. The schema is the product this weekend. Everything else waits in a reject file.\n\nRecent AI coding talk mixes speed with engineering. Speed without a freeze is just churn. A frozen contract is the missing gate.\n\nYou will ship three files and one command. That is the whole demo surface.\n\nYou will not ship a UI. You will not ship an agent loop. You will not stream tokens into business logic.\n\nCreate `contract.json` before you open any helper. Keep four fields only. Name them like an API, not like chat.\n\n```\n{\n  \"version\": \"1\",\n  \"fields\": {\n    \"merchant\": {\"type\": \"string\", \"max\": 80},\n    \"total_cents\": {\"type\": \"integer\", \"min\": 0},\n    \"currency\": {\"type\": \"string\", \"enum\": [\"USD\", \"EUR\", \"CNY\"]},\n    \"purchased_on\": {\"type\": \"string\", \"pattern\": \"^[0-9]{4}-[0-9]{2}-[0-9]{2}$\"}\n  }\n}\n```\n\nPrint that file. Read each field name out loud. If a field feels fuzzy, delete it now.\n\nDo not add `tax`, `tips`, or `line_items` today. Those fields invite parsers you will not finish. Fuzzy money fields also hide rounding bugs.\n\nCommit the contract before any parser exists. The commit message can be one line. `freeze: four-field receipt contract v1` is enough.\n\nCreate `fixtures/receipt_001.json` next. This is the only legal demo input. A second fixture is a later product.\n\n```\n{\n  \"raw_text\": \"ORBIT CAFE\\n2026-09-14\\nLatte 4.50\\nTotal $4.50\",\n  \"expected\": {\n    \"merchant\": \"ORBIT CAFE\",\n    \"total_cents\": 450,\n    \"currency\": \"USD\",\n    \"purchased_on\": \"2026-09-14\"\n  }\n}\n```\n\nOne fixture proves the contract path. It does not prove OCR. It does not prove every cafe in town.\n\nIf the helper asks for a corpus, refuse. You are proving a shape, not a dataset. Extra photos belong in the reject file.\n\nRedact any real merchant names before you paste. Fake the cafe. Fake the total. Keep private receipts off the prompt.\n\nCreate `validate.py` and keep it short. No framework. No JSON Schema plugin this weekend.\n\n``` python\n#!/usr/bin/env python3\nimport json\nimport sys\nfrom pathlib import Path\n\nCONTRACT = json.loads(Path(\"contract.json\").read_text())\nFIELDS = CONTRACT[\"fields\"]\n\ndef fail(msg: str) -> None:\n    print(f\"CONTRACT FAIL: {msg}\", file=sys.stderr)\n    raise SystemExit(1)\n\ndef check(obj: dict) -> None:\n    if set(obj) != set(FIELDS):\n        fail(f\"keys {sorted(obj)} != {sorted(FIELDS)}\")\n    merchant = obj[\"merchant\"]\n    if not isinstance(merchant, str) or not merchant or len(merchant) > 80:\n        fail(\"merchant\")\n    total = obj[\"total_cents\"]\n    if not isinstance(total, int) or isinstance(total, bool) or total < 0:\n        fail(\"total_cents\")\n    if obj[\"currency\"] not in FIELDS[\"currency\"][\"enum\"]:\n        fail(\"currency\")\n    date = obj[\"purchased_on\"]\n    if not isinstance(date, str) or len(date) != 10:\n        fail(\"purchased_on\")\n    if date[4] != \"-\" or date[7] != \"-\":\n        fail(\"purchased_on\")\n    y, m, d = date.split(\"-\")\n    if not (y.isdigit() and m.isdigit() and d.isdigit()):\n        fail(\"purchased_on\")\n\ndef main() -> None:\n    payload = json.loads(sys.stdin.read())\n    check(payload)\n    print(\"CONTRACT OK\")\n\nif __name__ == \"__main__\":\n    main()\n```\n\nRun it against the fixture expected block first. You want a green path now. The model has not written anything yet.\n\n``` python\npython3 -c 'import json,pathlib; p=json.loads(pathlib.Path(\"fixtures/receipt_001.json\").read_text()); print(json.dumps(p[\"expected\"]))' | python3 validate.py\n```\n\nYou should see `CONTRACT OK` on stdout. If you do not, stop immediately. Fix the contract by hand, not the helper.\n\nAdd a second command that must fail. Feed it a renamed key on purpose. The gate is real only when it rejects drift.\n\n```\necho '{\"merchant\":\"X\",\"amount\":1,\"currency\":\"USD\",\"purchased_on\":\"2026-09-14\"}' | python3 validate.py; echo exit:$?\n```\n\nThat run should print `CONTRACT FAIL` and a non-zero status. Save both commands in `README.md`. Those two lines are the demo script.\n\nCreate `parse.py` after the validator is green. Hard-code the fixture path. Do not walk a photo directory.\n\n``` python\n#!/usr/bin/env python3\nimport json\nimport re\nfrom pathlib import Path\n\nfx = json.loads(Path(\"fixtures/receipt_001.json\").read_text())\ntext = fx[\"raw_text\"]\nmerchant = text.splitlines()[0].strip()\ndate = re.search(r\"(\\d{4}-\\d{2}-\\d{2})\", text).group(1)\ncents = int(round(float(re.search(r\"Total \\$([0-9.]+)\", text).group(1)) * 100))\nout = {\n    \"merchant\": merchant,\n    \"total_cents\": cents,\n    \"currency\": \"USD\",\n    \"purchased_on\": date,\n}\nprint(json.dumps(out))\n```\n\nThis stub is allowed to look ugly. Ugly and finished beats elegant and open. You can rewrite it next month.\n\nPipe the stub into the validator.\n\n```\npython3 parse.py | python3 validate.py\n```\n\nGreen output is the weekend demo. Record that pipe in `README.md`. Do not open a browser to prove it.\n\nIf the regex fails, edit `parse.py` yourself. Do not start a second helper session yet. Hands-on edits keep the schema honest.\n\nCreate `REJECT.md` beside the contract. Write refusals as concrete items. Vague principles will not stop a chatty model.\n\n```\n# Reject this weekend\n- web dashboard\n- auth and sessions\n- PDF OCR pipeline\n- second fixture\n- retry loop around the model\n- streaming UI\n- database\n- extra money fields\n```\n\nRead that list before every helper prompt. If the reply adds a rejected surface, discard the reply. Do not negotiate with the extra files.\n\nThe reject file is a build tool. It is not a confession. You are buying a Sunday demo with Saturday restraint.\n\nIf you use a coding helper, paste `contract.json` first. Paste `REJECT.md` second. Ask only for a four-field `parse.py`.\n\nDo not ask it to make this production ready. That phrase deletes weekends. Ask for stdout JSON that matches the contract.\n\nAfter the first patch, stop the helper. Run the validator again. If it fails, you edit the stub by hand.\n\nThis is a gate, not a leaderboard. It does not measure model quality. It measures whether your weekend still has a shape.\n\nUse this table when the helper gets generous. Print it. Keep it above the keyboard.\n\n| Incoming request | Saturday action | \n|---|---|\n| Add a fifth JSON field | Refuse; append it to `REJECT.md` | \n| Rename `total_cents` | Refuse; keep the frozen key | \n| Stand up a React app | Refuse | \n| Add a second fixture | Refuse until the pipe stays green | \n| Validator false positive | Fix `validate.py` by hand | \n| Parser misses the date | Edit `parse.py` ; no new loop | \n| \"Make it robust\" | Refuse | \n| Public URL with open prompt | Refuse | \n\nThe table is the original artifact. Code can drift. The table states the policy in one glance.\n\nYou 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.\n\nDisclosure: 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.\n\nA 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.\n\nDo not point a public URL at an unbounded prompt. A free server is not a license for a dashboard. The reject file still wins.\n\nThe parser will choke on missing dollar signs. That is acceptable this weekend. Document the miss in `REJECT.md` and move on.\n\nThe helper will rename `total_cents` to `amount`. The validator must reject that rename. Do not stretch the contract to match the model.\n\nCurrency enums will feel rude. Keep them rude. Open string fields become soup by Sunday night.\n\nDates without hyphens will fail the gate. Good. Your demo is a contract, not a guesser. Guessers do not survive the next fixture.\n\nBoolean `true` must not pass as `total_cents`. Python treats `bool` as `int` without the extra check. That is why the validator excludes `bool` explicitly.\n\nThis approach ignores real OCR. It ignores multi-line merchant names. It ignores tax-inclusive totals and tips.\n\nIt also ignores load, auth, and retention. Those are product problems. They are not Saturday problems.\n\nThe validator is not a schema platform. It is a weekend gate. Replace it later if the contract survives a month.\n\nFree model output can drift across sessions. You do not pin a vendor by hoping. You pin a file in git and a failing command.\n\nA free server is not a production posture. Treat it as a demo host only. Put secrets nowhere near that host.\n\nThis article does not name models, quotas, or hardware. Those claims go stale fast. Your contract file does not.\n\nDo not use this if Monday needs every receipt parsed. You need a real pipeline then. A stub will lie to finance.\n\nDo not use this if compliance needs an audit trail. A weekend script is not that trail. You will fake safety and regret it.\n\nDo not use this if your team already owns a registry. Follow that registry instead. Do not fork a toy schema beside it.\n\nDo not paste private receipts into any helper. Redact the fixture. Fake the cafe. Keep personal amounts local.\n\nSkip this if you cannot freeze keys. If every chat can rename fields, you do not have a product. You have a conversation.\n\nYour done state is one pipe that prints `CONTRACT OK`. That is enough for Sunday. Anything past that is next weekend.\n\nIf the helper still wants a dashboard, reread `REJECT.md`. Then close the chat. Ship the command and walk away.", "url": "https://wpnews.pro/news/weekend-build-log-freeze-four-json-fields-first", "canonical_source": "https://dev.to/hackgo_6978/weekend-build-log-freeze-four-json-fields-first-56mo", "published_at": "2026-09-16 03:35:31+00:00", "updated_at": "2026-09-16 03:37:05.509795+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": [], "alternates": {"html": "https://wpnews.pro/news/weekend-build-log-freeze-four-json-fields-first", "markdown": "https://wpnews.pro/news/weekend-build-log-freeze-four-json-fields-first.md", "text": "https://wpnews.pro/news/weekend-build-log-freeze-four-json-fields-first.txt", "jsonld": "https://wpnews.pro/news/weekend-build-log-freeze-four-json-fields-first.jsonld"}}