{"slug": "i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering", "title": "I built a handover board for the friend who was doing all the remembering", "summary": "A developer built Handover, a deterministic care-coordination board that tracks medication doses, ownership, and shift handovers for family caregivers, with no model in the scoring loop. The app normalizes drug names via NIH RxNorm and pulls label sections from openFDA, flagging duplicate therapy such as Metformin and Glucophage as the same ingredient, and returns nothing rather than inventing data when both public APIs fail.", "body_md": "**This is a DEV Hacktoberfest Weekend Challenge submission: *Build for a Friend*.**\n\n**[Live app](https://handover-olive.vercel.app)** · **[GitHub](https://github.com/aniruddhaadak80/handover)** · [Agent tools](https://handover-olive.vercel.app/mcp.json)\n\nWhen my friend came home from hospital, the hard part was never the medication.\n\nIt was that four people were helping, each of them believed they knew the\n\nschedule, and nobody could prove what had already been given. She was doing all\n\nof it from memory, at 3am, with a phone full of half-finished messages.\n\nSo I built her one board instead of a family group chat.\n\nYou open a board for one person. You log doses, blood pressures, the note the\n\nphysio left, the blood test someone needs to book. Each entry carries **who owns it**, so \"somebody should do this\" has nowhere to hide.\n\nThen, before the care changes hands, you drag a **handover moment** along a\n\nshift rail. Every entry physically moves from the outgoing person to the next\n\none, and a deterministic engine re-runs for that exact instant. It tells you,\n\nin itemized form, what the next person is about to get wrong.\n\nThen you seal it. And you download a brief they can act on alone.\n\n**Two entries, one ingredient.** The worked example I ship has \"Metformin 500\n\nmg\" at breakfast and \"Glucophage 500 mg\" in the evening. Glucophage *is*\n\nmetformin. A brand name and a generic name for the same drug, on the same board,\n\nat two different times. That is the single most common medication record-keeping\n\nerror there is, and it is trivially invisible to a human reading a list.\n\n``` php\ngraph LR\n  A[\"Metformin 500 mg<br/>08:00\"] --> C[\"ingredient token<br/>metformin\"]\n  B[\"Glucophage 500 mg<br/>20:00\"] --> C\n  C --> D[\"duplicate therapy<br/>-35\"]\n  classDef data fill:#22d3ee,color:#08303a,stroke:#22d3ee\n  classDef engine fill:#a78bfa,color:#20143a,stroke:#a78bfa\n  class A,B,C data\n  class D engine\n```\n\n**A food-timing collision.** Levothyroxine needs an empty stomach. Metformin\n\nneeds food. Scheduled 30 minutes apart. Nobody catches that at 7am.\n\nThis is the part the challenge actually asks about, so I want to be precise\n\nrather than clever.\n\n**There is no model in the scoring loop, and that is a feature.** The readiness\n\nscore is a weighted mean of six factors, each penalty traceable to a specific\n\nrow on the board or a specific section of a public label. The engine takes the\n\nreference moment as a parameter, never calls the clock, and sorts its inputs,\n\nso the same board at the same moment produces byte-identical output. 48 unit\n\ntests pin that, including a byte-equality test across shuffled input.\n\nFor a family making a decision at 3am, \"the model said so\" is worth nothing. A\n\nnumber you can read back to a row is worth something. That is a place where\n\nopen, inspectable code beat a closed service for me.\n\n**The discharge-note parser is deterministic too.** Paste a discharge summary\n\nand you get reviewable draft rows with a confidence and the source line quoted\n\nback. It is regular expressions and a small grammar, and that is exactly\n\nenough. A draft regimen that a stranger cannot reproduce is a draft nobody\n\nshould trust with a parent's medication.\n\n**Live data comes from two keyless public APIs.** NIH RxNorm resolves the\n\nconcept id. openFDA supplies the label sections. Both are free, public,\n\nunauthenticated infrastructure.\n\n``` php\ngraph TB\n  A[\"Metformin 500 mg\"] --> B[\"Ingredient normalizer\"]\n  B --> C[\"RxNorm<br/>concept id\"]\n  B --> D[\"openFDA<br/>label sections\"]\n  C --> E[\"Normalized record<br/>+ fetch time + source id\"]\n  D --> E\n  E --> F[\"Engine cites<br/>the source\"]\n  classDef live fill:#22d3ee,color:#08303a,stroke:#22d3ee\n  classDef ext fill:#fbbf24,color:#3a2a00,stroke:#fbbf24\n  classDef engine fill:#a78bfa,color:#20143a,stroke:#a78bfa\n  class C,D,E live\n  class A,F engine\n  class B ext\n```\n\nAnd the failure mode is the part I care about most: **when both sources fail, nothing is invented.** The response says \n\n`fallback`, no label text is\nEvery create, update, decision and sealed handover appends one row:\n\n```\nseal_n = SHA-384( UTF-8(prevSeal) || canonicalJson(event_n) )\n```\n\nCanonical JSON sorts keys recursively and preserves array order. Deletes leave a\n\ntombstone so the chain still replays after a board is soft-deleted. Replay\n\nreports the first broken sequence number, and the suite asserts pinned\n\nknown-answer vectors so a refactor cannot quietly rewrite what already happened.\n\nThis started as me not trusting my own memory about a dose. It turned into the\n\nthing I am proudest of in the project.\n\nNine MCP tools over JSON-RPC 2.0, scoped to the calling session, with\n\nidempotent mutations so a retry cannot double-log a dose.\n\n```\n{\n  \"mcpServers\": {\n    \"handover\": { \"type\": \"http\", \"url\": \"https://handover-olive.vercel.app/api/mcp\" }\n  }\n}\n```\n\nThe mutating tools go through the same service layer as the browser buttons, so\n\nan agent cannot do anything the UI could not.\n\nThis is health-adjacent data about a specific person, and I did not want it\n\nsitting in a database I could casually read. So ownership is an anonymous\n\n128-bit session in an HTTP-only cookie, minted in Edge middleware before any\n\nhandler runs, and every query is scoped by it. No accounts, no email, no\n\nprofile, nothing to phish.\n\nThe tradeoff is real and I put it in the README: clearing your cookie loses\n\naccess to the board. I would rather ship that tradeoff honestly than pretend a\n\nsign-up flow makes the privacy better. It is first on the roadmap to fix, with\n\nhousehold roles.\n\nOne environment variable. `DATABASE_URL`.\n\n```\ngit clone https://github.com/aniruddhaadak80/handover\ncd handover\nnpm install\nnpm run dev\n```\n\nEmbedded Postgres locally, Neon in production, no keys, no model, no account.\n\n| Decision | Why the open path won | \n|---|---|\n| Deterministic engine instead of an LLM | A score you can read back to a row is auditable; a score a model produced is not. | \n| Public regulatory APIs instead of a scraped mirror | No key, no rate-limit fight, no data staleness I have to maintain. | \n| Postgres and an explicit schema instead of a document store | I needed a unique constraint on `(board_id, seq)` to make the chain tamper-evident. That needed a real relational constraint. | \n| SHA-384 in Node instead of a hosted signing service | The chain has to be verifiable by anyone who clones the repo, with no account and no bill. | \n\n`npm run typecheck`, `npm run lint`, 61 unit and integration tests, the\n\nproduction build, a Playwright journey on desktop and mobile with zero console\n\nerrors, and a checked-in `scripts/verify-live.mjs` that proves the whole loop\n\nagainst a live deployment: **60 of 60 checks passed against production**, from\n\nboard creation through an MCP mutation to seal replay and confirmed deletion.\n\nIf you are the person in this story, the point was never the software. It was\n\nbeing able to go to sleep.\n\n**Repo:** [https://github.com/aniruddhaadak80/handover](https://github.com/aniruddhaadak80/handover)\n\n**Live:** [https://handover-olive.vercel.app](https://handover-olive.vercel.app)\n\n`#devchallenge` `#weekendchallenge` `#hf26challenge`", "url": "https://wpnews.pro/news/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering", "canonical_source": "https://dev.to/aniruddhaadak/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering-2ii8", "published_at": "2026-10-03 02:05:00+00:00", "updated_at": "2026-10-03 02:07:50.614578+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-agents"], "entities": ["Handover", "NIH RxNorm", "openFDA", "Metformin", "Glucophage", "Levothyroxine", "GitHub", "Vercel"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering", "markdown": "https://wpnews.pro/news/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering.md", "text": "https://wpnews.pro/news/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering.txt", "jsonld": "https://wpnews.pro/news/i-built-a-handover-board-for-the-friend-who-was-doing-all-the-remembering.jsonld"}}