Medication Tracker

The meds app

Tier 1, Lua. A personal medication tracker for families with chronic conditions. The architecture page describes how its parts fit together.

Schema

Table Grain
profile One row per node, at most one row ever. Holds reminder counts and early refill defaults. No row means every default.
plan One row per payer, with optional percent and frame overrides
person One row per household member
medication One row per product in the catalog
medication_alias One row per medication per other name
pharmacy One row per pharmacy
prescriber One row per prescriber
person_medication One row per medication a person tracks, under the name the person prefers
person_medication_product One row per tracked medication per catalog product; at least one each
fill One row per fill of one tracked medication, with the product dispensed when known
prior_authorization One row per approval window for one tracked medication

The views start with v_. v_active_medication is the readable one: every tracked medication in use with its refill status, refill eligibility, physical supply exhaustion, payer and early allowance. v_tracked_product lists the products of each tracked medication, and v_entry_mark their specialty and controlled marks as 1 or 0. The data model page is the reference for every table and view, and must change in the same commit as schema.sql.

Layout

Path Holds
app.lua The entry point. It loads the route modules, in the order paths are tried.
lib/routes/ One module for each part of the app. Loading a module registers its routes.
lib/text.lua, validate.lua, choices.lua, medication_name.lua, page.lua, clock.lua, form_icon.lua, when_icon.lua, periods.lua, spending_chart.lua Pure Lua with no framework calls, so plain Lua 5.4 can test them
lib/match.lua Pure Lua: how well a typed name matches a name of a medication
lib/medication_search.lua The search that every screen uses to find a medication
lib/written_name.lua Pure Lua: takes apart a name as a portal wrote it, and compares it with a name and a strength
lib/medication_pick.lua Reads the medication box of a form: a typed name, a choice, a carried id, a checked result, or a new medication
lib/product_pick.lua The products of a tracked medication while its form is open: carried in hidden fields, searched for, added or removed per round trip
lib/catalog_entry.lua The checks of a catalog entry, shared by the catalog form and the medication box
lib/reference_words.lua Pure Lua: turns the route and the dose form of a drug reference into words of the catalog
lib/authorization_words.lua Pure Lua: the levels of a prior authorization that needs attention, and their words
static/meds.css, static/*.js The one stylesheet and the five scripts. [ui] in app.toml names them all, and the frame loads them once, deferred, in the head of every page; no view carries a <script> or a stylesheet link.
static/forms.js Shows the fields of a new record when – Add new – is chosen. Every form works without it.
static/filter.js Narrows the Medications page as a person types. The server filters the same way on submit.
static/person_tab.js Remembers the person tab chosen last, by id, in local storage, and opens it by following the tab’s own link. Issue 10 tracks replacing it with Privatium person profiles.
static/drug_references.js Asks RxTerms, then the openFDA NDC Directory, then RxNorm about a name, in the browser; the search script uses it, so it is listed first
static/product_search.js The product search of every form that needs a product: asks /medications/search for JSON, pages the results, falls back to the drug references, and suggests similar products while a new medication is typed
lib/quick_add.lua A person, a pharmacy, a prescriber or a plan that a form adds by name beside its own record
lib/suggestions.lua The values in use that text boxes offer while a person types
lib/merge.lua The plan and the batch of a merge
lib/entries.lua Reads the tracked medications with their products, refill dates, words, groups and search text
lib/refill.lua Pure Lua: the group and the words of a refill status, and the status a new fill leads to
lib/fills.lua Checks a fill and writes it, with the tracked medication that goes with it
lib/portal_reader.lua Pure Lua: works out which page a pasted text came from and hands it to that page’s reader
lib/portals/ Pure Lua: one reader for each portal page the app reads, and common.lua with the dates, amounts, phone numbers and tab-separated rows they share. A new page gets its own module and a place in the order in portal_reader.lua.
lib/authorization_watch.lua Finds the prior authorizations that end soon or have ended
lib/people_filter.lua The person filter that list pages share
lib/store.lua The one place that writes and removes records
views/ One template for each page. A name that starts with _ is a partial.

Routes

Route Module Handler
GET /refills home The Refills page, or a welcome while the household has no people
GET / medications The Medications page, which is the home page, or a welcome while the household has no people
GET /setup home Links to the parts of Setup
GET, POST /setup/reminders home Reminder and early refill settings
GET, POST /setup/plans/new, /:id/edit, /:id/remove; GET /setup/plans plans Payers and their rules, with removal refused while referenced
GET /setup/people people The family
GET, POST /setup/people/new, /:id/edit, /:id/remove people Add, change, remove
GET /setup/pharmacies, GET /setup/prescribers contacts The Pharmacies page and the Prescribers page
GET, POST /setup/pharmacies/new, /:id/edit, /:id/remove contacts Add, change, remove
GET, POST /setup/prescribers/new, /:id/edit, /:id/remove contacts Add, change, remove
GET /setup/catalog catalog The catalog, narrowed by ?q=
GET /setup/catalog/:id catalog One medication with its other names
GET, POST /setup/catalog/new, /:id/edit, /:id/remove catalog Add, change, remove
GET /medications, /medications/:id medications The lists, narrowed by ?q=, and the page of one tracked medication
GET, POST /medications/new, /:id/edit, /:id/remove, POST /:id/status medications Add, change, remove, change the status (also the Restart button)
GET, POST /medications/:id/products/:link_id/remove medications Remove a product from a tracked medication
GET /people/:id/medication-list medications The list made for paper
GET, POST /fills/paste, /fills/paste/read, /fills/paste/add paste Pasted fills: paste, review, add
GET /fills fills Fill history, with search, filters and the total paid
GET /fills/reports reports Reports tab: paid by year as a chart and a table, under the same filters
GET /fills/reports/print reports The printable spending report under the same filters
GET, POST /fills/new, /:id/edit, /:id/remove fills Record, change, remove
GET /authorizations, GET, POST /authorizations/new, /:id/edit, /:id/remove authorizations Prior authorizations
POST /setup/catalog/:id/names, GET, POST /:id/names/:name_id/remove catalog Other names
GET /setup/catalog/:id/merge, GET, POST /:id/merge/:target_id catalog Merge two entries

Conventions to preserve

Extending it

Adding a field means one column in schema.sql, one input in the form, and one key in every pv.append call that writes the table. A dose form that the starter list lacks gets its icon in lib/form_icon.lua. The schema change rematerializes from the logs; existing events lack the key and the column is NULL for them. Adding a table means a CREATE TABLE with a grain comment and a section in the data model page.

Run the checks in the test how-to before finishing: privatium lint apps/meds, lua5.4 tests/lua/run.lua, python3 tests/test_supply.py, python3 tests/test_catalog.py and tests/smoke.sh. A new check in lib/validate.lua gets unit tests, and a new screen gets smoke tests, with at least one request that must be refused.