Skip to content

Clinic boards: live updates, duplicate and result guards, and CMID field names - #24

Closed
dadsena01 wants to merge 24 commits into
bwhtech:mainfrom
dadsena01:fix/clinic-boards-realtime-and-cmid-naming
Closed

dadsena01 wants to merge 24 commits into
bwhtech:mainfrom
dadsena01:fix/clinic-boards-realtime-and-cmid-naming

Conversation

@dadsena01

Copy link
Copy Markdown
Contributor

Sits on top of #23, so the diff carries those commits too until that one merges.

This carries on from #23. It covers the three clinic boards, the dashboard, and a pass over field names against what CMID actually asked for in their scope document.

The screenshots below are from the running app, taken as the real CAD, doctor, nurse and programme manager users, so you can see the changes without setting up a camp yourself.

Boards update on their own

The CAD, doctor and nurse boards only changed when somebody pressed refresh, so staff worked off a stale queue for most of a camp. A camp change now reaches every open board and each one re-reads its own queues. The message carries the camp name and who made the change, nothing about the patient, because it goes to every logged-in user on the site.

The refresh waits half a second, is skipped for the person who made the change, and is held back while a dialog is open so nobody loses what they are typing. It never freezes the screen.

Things that were quietly wrong

Nothing stopped the same person being registered twice. Every duplicate burns a Clinic ID off the yearly series, prints a second card and splits that person's history in two, and none of it can be undone once the card is in their hand. There were already two such pairs in the data. Registration now checks ABHA, then mobile, then name and date of birth, and offers to queue the person it found. It warns rather than blocks, because brothers share one mobile and two people can have the same name and birth year.

Clearing a clinical note was silently ignored. The doctor endpoints only saved a note if it had text in it, so deleting a wrongly recorded allergy did nothing and the old allergy stayed on the record.

Nothing caught a repeated medicine, so the same drug could go on a prescription twice and be dispensed as a double dose.

A patient could go back to the doctor with no test result at all, marked reviewed and carrying no answer. Every ordered test now needs a result, and Not Done was added so a nurse who cannot run a test can say so.

The dispense list had every medicine ticked in advance, so tapping straight through recorded the whole prescription as handed over. That is the row the donor-fund and stock reporting will count.

Vitals had no upper limit, so an SpO2 of 150 or a pulse of 9999 saved fine.

Every Clinic ID issued so far said unknown location. The fallback was meant for a patient registered with no camp at all, but it had become the normal path, because only three of twenty-two locations had an LSG number. Registration now refuses a camp whose LSG has no number and names the record an admin has to fill.

The dashboard was broken for the people it was built for. A director or programme manager got a blocking permission error. A number card runs through the report engine, so read on the doctype is not enough. Both roles already had read, so nothing new is exposed. It had only ever been checked as Administrator, who skips every check.

Field names against the CMID scope

Mobile is now Mobile Number, Company Name is Name of Company, Occupation / Sector is Sector of Employment, Chief Complaint is Patient Complaints, and Clinical Notes is Observations and Notes on Examination. Labels only, no field name was renamed, because the Clinic IDs and every report point at the old names.

Patient had two fields both labelled Clinic ID. One holds all 33 issued IDs, the other was empty on every patient, so anything picking a column by label got the blank one half the time. The empty one is gone.

LSG, district and state on a visit all copy from a location link that nothing ever set, so three fields the scope asks the software to fill by itself were empty on all 33 visits. One line at registration fixes it, and a patch backfills 19 of 33. The other 14 sit on old demo camps that have no site at all.

Screenshots

CAD board after a search, showing the result count, the Clear button and the waiting time on each queue row:

CAD search and queue

The warning before a duplicate registration:

Duplicate patient warning

The registration form. The chosen answer in each row is one shade darker instead of a solid button, so the only filled button on the form is the one that submits it:

CAD register form

Doctor board with the allergy banner, the patient filter, and Call next patient as the one filled button:

Doctor board

The prescribe dialog. Medicines first with a row ready to type into, the allergy at the top, and the notes collapsed underneath:

Prescribe dialog

Nurse board with the camp summary line, the ordered test or medicine on each row, and how long each patient has been in the camp:

Nurse board

The dispense dialog, now showing the dose the doctor wrote, with nothing ticked in advance:

Dispense dialog

Dashboard opened by a programme manager, with no permission error:

Dashboard as programme manager

Testing

245 tests pass. New ones cover the duplicate check, the short search, the capped result list, blank and Not Done test results, impossible vitals, half a blood pressure, duplicate medicines, negative amounts, clearing a note, and a camp with no LSG number.

Every fixture camp now has a unit, since a camp without one can no longer issue a Clinic ID. Ten test modules were building camps that had none, and the suite passed anyway, which is a fair measure of how invisible that was.

Also checked by hand in the running app at 1280px and 420px as each of the real users.

Still open, needs CMID

The tick lists in the scope have no options listed anywhere, so Diagnosis category, Services provided and Other interventions are not built. Occupation is a separate field in their table and we only store the sector. Five of the eight reports do not exist yet, and the session and clinic reports are missing the age group, sex and native state breakdowns their own descriptions ask for. Pharmacist, Helpline Staff and Admin Officer roles do not exist, and neither does the helpline follow-up module.

The real LSG numbers are also still outstanding. The dev site is running on numbers we made up, and these end up printed on every patient card.

Frappe seeds a Desktop Icon per public workspace only once, at creation
time, and never re-copies the workspace's roles or icon after that. Every
tile ended up System Manager only, so field staff landed on /desk with
nothing to click. CAD's workspace also had no icon set when its tile was
seeded, so it kept rendering blank even after cad.json got one later.

Replaces the old single shared 'App' tile (which only ever led to
Frappe's 'Icon is not correctly configured' dead end) with syncing each
workspace's own per-role tile on every migrate, and fixes the workspace
lookup itself: it was filtering on Workspace.app, which is only as good
as whatever app happened to be active in the Desk UI when someone
created the record by hand, wrong for most of these. module is what
each workspace's own fixture actually sets.
Every card/box on the Doctor, Nurse, My Schedule, Staff Onboarding and
New Schedule pages referenced var(--radius-md) / --radius / --radius-full,
none of which exist as Frappe design tokens — the real names are
--border-radius-md / --border-radius / --border-radius-full. An unset
custom property resolves to nothing, so border-radius was computing to
its default (0) everywhere it was used; every one of these boxes has
been rendering square-cornered instead of rounded.
New record_vitals endpoint + a dialog on the nurse queue card: height,
weight, temperature, pulse, SpO2 and blood pressure, each optional but
at least one required, all rejected if zero or negative. BMI is
recalculated from height/weight whenever both are present. Only
allowed while the patient is actually with the nurse (Awaiting Test or
Awaiting Medicine) — matches the same session-ownership gate as the
rest of this page.
… letter

order_test, prescribe_medicine and complete_encounter now all accept
chief complaint, past history and allergy history, saved onto the
encounter regardless of which action closes out the visit.

complete_encounter also takes an optional referral (where to, who to,
why, priority) and creates a real Referral record when given one — both
where and why are required together, so a half-filled referral can't
be saved. New Bandhu Referral Letter print format plus
get_referral_letter_html, which crosses the same permission boundary
get_patient_card_html already does on the CAD page: Referral is System
Manager only in DocType permissions, and load_owned_encounter is what
proves the caller may see this particular patient before the render
runs on their behalf.
…ails

The shared patient-details dialog (Doctor and Nurse queue cards) now
shows this visit's own vitals instead of only what the patient carried
in from registration, plus Pulse/SpO2/Blood Pressure and Chief
Complaint/Past History/Allergy History sections. A visit's own reading
wins over registration data whenever the nurse has recorded one, since
that's what's clinically true right now.

Doctor and Nurse also get direct read/print/report permission on
Patient Encounter (permlevel 0) so this no longer has to go through
ignore_permissions to render.

Bumped session_ui.js's cache-busting ?v= — app_include_js raw file
paths aren't auto-versioned, so without this browsers keep serving the
old file after a change.
New Schedule only ever covered recurring camps (Weekly/Fortnightly/
Monthly) — starting a single ad hoc session still meant the full
20-field Bandhu Clinic Session Desk form. New Session is a one-screen
form instead: Clinic autofills Project/Vehicle the same way the
schedule wizard does, a clash warning reuses find_assignment_clashes
against the one date instead of a generated list, and Create just
inserts a Planned session with no session_schedule — that blank field
is what marks it as ad hoc rather than schedule-owned, so it can't
collide with the nightly generator's idempotency key.

require_scheduling_access, practitioners_by_role, association_maps and
clock_value move out of new_schedule.py into the shared
session_schedule module so both pages use one implementation instead
of two copies that would drift apart.
…or data

Registration form: Sex/Country/Native State/Occupation render as quick-tap
tab groups instead of free-text inputs, so a typo can no longer crash
registration with a Link-validation error the way native state and sector
used to. Full Name, Native State, Sex and the Age/DOB pair are marked as
actually required, matching what register_patient enforces server-side —
Age/DOB gets a plain-language note instead of a star on either field,
since only one of the two is required, not both.

DOB estimate from age alone: was today's exact month/day minus N years,
which reads as a real recorded birthday. Now Jan 1 of the calculated
birth year, so it's honestly an estimate rather than a fabricated date
— matters for migrant workers who know their age but not their
birthday.

Native District: was a free-text field wired to a native <datalist>
positioned by the browser with no CSS hook available to fix it. Now a
plain select next to the state, disabled until a state is picked,
populated from state_districts.py — matching how the state and country
'Other' pickers already work.

Only 6 of India's 36 states/UTs ever had a district list; the other 30
returned nothing at all, in a state field that already accepted 36
states via 'Other'. Filled in the rest — general knowledge of India's
administrative divisions, not an official register, so worth a
spot-check against the Census of India before relying on it, especially
for Madhya Pradesh/Andhra Pradesh/Telangana/Rajasthan which have all
had district boundaries officially reorganised in the last few years.

Sectors gets the same is_major_sector split the state master already
had: five CMID-specified sectors as quick taps (two renamed in place
via rename_doc, not delete+recreate, so any patient already linked
carries forward), the rest reachable only from the full Desk picker.

Full Name, Native District and ABHA ID each rendered alone in a row of
the form's 2-column grid, stuck at half width with the other column
empty next to them — now span the full row like the tab groups already
did.
Went through every workspace shortcut and checked its label against
what it actually links to and what data is really behind it:

- Doctor's 'Patient Queue' pointed at Patient Encounter, not the real
  Patient Queue doctype the Bandhu workspace's own shortcut of the same
  name correctly uses.
- Bandhu's 'Vehicle log' linked to the Vehicle master, not a log — the
  real logs (Usage, Refuel) sit right beside it. Relabelled to
  'Vehicles'.
- Bandhu's 'Start Consultation' created a bare Patient Encounter,
  bypassing sync_to_queue, the session link and the workflow-state
  machine entirely. Now opens the CAD page, the real entry point.
- Bandhu's 'Staff Log' and Nurse's 'Medicine Movement'/'Medicine
  Dispense' pointed at doctypes with zero rows and no code anywhere
  writing to them — removed.
- Pharmacist's 'Medicines' showed all 34 Items in the system, 29 of
  them not medicines at all. Now a URL shortcut to /app/item?item_group=
  Drug, which a DocType-type shortcut's stats_filter can't do (that
  only drives the badge count, not what the list opens to).
- Inventory's three shortcuts (Asset/Asset Movement/Asset Repair) are
  ERPNext fixed-asset accounting, unrelated to a mobile clinic's actual
  medicine/vehicle/equipment inventory, all zero rows. Removed.
- Admin's 'Roles' badge filter referenced a Helpline Staff role that
  doesn't exist in this system. Dropped from the filter.
- Trailing-space label typos on Admin's 'Site' and (already fixed
  separately) Bandhu's old 'Start Clinic Session'.
Filtered on encounter_date = today only, so a patient still waiting or
with the doctor counted as 'seen' the moment they registered. Added
custom_workflow_state = Completed, matching the filter the CAD page's
own 'Completed Patients' quick list already uses correctly.
Two long explanatory comments removed at the user's request. The
page-width/page-height landmine they documented (non-standard CSS
properties Chrome's PDF generator reads directly, real card size
verified by rendering to PDF and measuring the output) stays recorded
in this app's own project notes instead of inline.
restrict_other_app_desktop_icons filtered on Desktop Icon.app != 'bandhu_app'
to decide which icons belong to other apps and should be locked to System
Manager. Every one of this app's own 9 icons has app blank — the exact
same landmine sync_bandhu_desktop_icons already had to work around on
Workspace.app — so this function was classifying CAD/Doctor/Nurse/Admin/
Bandhu/Dashboard/Inventory/Pharmacist/Referrals as foreign and locking
all nine back to System Manager only. Both functions run in the same
after_migrate hook, in sequence, so this undid sync_bandhu_desktop_icons's
fix immediately after it ran, every single time — not caught in the
earlier commit because a role was only ever spot-checked once via
frappe.get_doc right after running sync alone, never after both hooks
ran together, and not through an actual browser session as that role.

Now filters on link_to against this app's own workspace names (via
Workspace.module, the same reliable check the sibling function already
uses) instead of the icon's own app field.

Verified live as Nurse, CAD and Doctor (each now shows exactly their own
tile) and as Administrator (unchanged, still sees the full grid) — not
just re-read from the database this time.
CI builds a real fresh site (bench new-site + install-app, no setup
wizard), which has zero Gender, Appointment Type, Indian State and
Sectors records, plus no Clinic/Site/Unit/Bandhu Projects/Item for a
dozen test classes to grab. All of it only ever worked on bandhuapp.local
because that dev site carries leftover data none of it was ever actually
seeded by.

- seed_default_genders, seed_default_appointment_type: new, wired into
  after_install and after_migrate. "Walk-In" is a hardcoded Property
  Setter default on Patient Encounter, so every encounter this app
  creates needs it to exist.
- seed_indian_states now also covers Kerala and the 6 major migrant-source
  states, not just the other 29 -- also never seeded by app code, just
  manually added on the dev site once.
- new baseline_test_fixtures.py, wired into every test class that assumed
  Clinic/Site/Unit/Bandhu Projects/Item already existed.
- fixed test_doctor_form.py: its tearDown calls frappe.db.rollback() after
  every test, which was also wiping the baseline setUpClass had just
  created since nothing had committed in between.
- test_nurse_form.py's hardcoded _Test Stock Item doesn't exist on a
  fresh site either, switched to the shared baseline Item.

209/209 tests pass, ruff and semgrep clean.
…cons

Adds four SVG icons and syncs them onto each Desktop Icon.
Clears the shared desktop icon cache when an icon changes.
Sends a realtime message when a camp changes and every open board re-reads its
own queues.

The refresh is delayed half a second, skipped for the person who made the change,
and held back while a dialog is open. It never freezes the screen.

Refresh moved into the page header as an icon, next to My Schedule.
Warns when the patient looks already registered, matching on ABHA, then mobile,
then name and date of birth, and offers to queue the one it found.

Search needs two characters, says when the list was cut short, and the row action
is outline instead of filled.

A camp with no LSG number can no longer issue a Clinic ID.

Today's Queue reads the visits instead of the queue table, and shows how long each
patient has waited.
Clearing a clinical note now saves.

Rejects the same medicine twice on one visit, and negative days or quantity.

The prescribe dialog opens on the medicines table with a row ready to type into,
and the notes moved into a collapsed History section underneath. Order Tests and
Mark Complete follow the same shape.

A recorded allergy shows as a banner on the room card and at the top of the
prescribe dialog.

Adds a name and Clinic ID filter to the board from eight patients.

Both print endpoints check the Doctor role.
Every ordered test needs a result before the patient goes back to the doctor, and
Not Done is now one of the options.

The dispense list starts unticked and asks first if nothing is ticked.

Shows the frequency, days and quantity the doctor wrote.

Vitals have upper limits and blood pressure needs both numbers.

Each row shows the ordered test or medicine and how long the patient has been in
the camp, and a line under the camp bar shows where everyone is.
Grants the report permission both roles were missing, which a number card needs on
top of read.

Removes the Patients Seen Today tile, which needs a permission those roles do not
have.

Hides the last-edited time Frappe prints under every row of a quick list, which
read as the camp date.

All three reports refuse a period longer than a year.
The nightly job writes an error log when an enabled schedule is still behind after
a run.
Mobile is now Mobile Number, Company Name is Name of Company, Occupation / Sector
is Sector of Employment, Chief Complaint is Patient Complaints, and Clinical Notes
is Observations and Notes on Examination. Labels only, no field was renamed.

Patient had two fields both labelled Clinic ID and one was empty on every patient.
The empty one is removed, including from the form's field order.

A visit now records its location, so LSG, district and state fill in by themselves.
A patch backfills the visits that already exist.
The header now uses the same width as the page content, so the title and the
buttons share the same edges as the page.

Button icons match Desk at 12px with a 6px gap.

Adds the missing styles for the allergy banner and for the badge the session bar
has always emitted.
Covers the duplicate patient check, the short search, the capped result list, blank
and Not Done test results, impossible vitals, half a blood pressure, duplicate
medicines, negative amounts, clearing a note, and a camp with no LSG number.

Every fixture camp now has a unit, since a camp without one can no longer issue a
Clinic ID.
Eight screenshots taken from the running app as the real CAD, doctor, nurse and
programme manager users.
@dadsena01
dadsena01 force-pushed the fix/clinic-boards-realtime-and-cmid-naming branch from cf47964 to c951eb9 Compare September 8, 2026 06:15
@dadsena01

Copy link
Copy Markdown
Contributor Author

Replaced by #25 with a tidied history and description.

@dadsena01 dadsena01 closed this Sep 8, 2026
@dadsena01
dadsena01 deleted the fix/clinic-boards-realtime-and-cmid-naming branch September 8, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant