| Key | Value |
|---|---|
| name | booking-app-tenant-work |
| description | Use when working on a VeriPath booking app's live test tenant — seeding synthetic demo data for screenshots, or changing the GP/dental/pharmacy app's UI and proving the change on the running site. |
| version | 1.1.0 |
| author | Hermes Agent |
| license | MIT |
| platforms | [linux] |
| metadata | |
| hermes | |
| tags | [veripath, gp-app, tenant, seed, demo-data, django, deploy, verification, screenshots] |
| category | veripath |
| related_skills | [veripath-wiki-assets, marketing, veripath-client-vps-ops, veripath-docs] |
Two jobs that recur while preparing web copy and screenshots: putting believable
synthetic data into a test tenant, and changing the app's UI and proving it on
the running site. Both concern the booking apps on the apps host, whose
containers bind-mount /root/work/<app>_app.
| Thing | Where |
|---|---|
| Tenant registry (which tenant uses which database) | central Postgres, database dsp_clinic, table tenancy_tenant joined to tenancy_sector |
| A per-tenant database | for test-client: dsp_clinic_dev, in the client container's Postgres |
| The running apps | apps host — containers gp_booking_app, dental_app, pharmacy_app; code bind-mounted from /root/work/<app>_app |
| App source (working copy) | /root/work/gp_booking_app — checked out on a feature branch, not main |
Read the database name from the registry; never hardcode it. Never print
credentials, hosts or passwords into any output, including your own report.
The pattern that works (scripts/seed_health_clinic_demo.py in gp_booking_app):
DSP_ENV=production, django.setup(), and everything insidewith with_tenant('<tenant>'): so the router resolves the right databaseget_client_db_alias()).transaction.atomic(using=alias),SET CONSTRAINTS ALL IMMEDIATE. A plain rollbackPatientProfile.partner_orgPractice, aClinic and a CLINICIAN user already exist — reuse them.username, name), and let a re-run repairscp to the apps host, then docker cp, thendocker exec gp_booking_app python /app/<script>.py [--commit]): encrypted modeltoken_id need the row's own counter,Data conventions (Matthew, 2026-10-07). Nothing may resolve to a real person or
place: placeholder street names in a fictional town, ZZ99 postcodes (the NHS
convention for an unresolvable address), Ofcom's reserved 07700 9000xx phones,
example.com emails, 999 000 00xx NHS numbers, fictional pharmacy names. Verify by
reading the values back through the app — those columns are encrypted, so SQL
shows ciphertext.
Before seeding, check what a role can actually see: the guides record what exists,
and some features are not built (receptionist/patient-communication still calls the
patient app under development).
git pull — then docker restart <app>. The restart isClient(), force_login(user),get(url, HTTP_HOST='test-client.gp.veripath.co.uk'). Report the status and theget_template(...).render({'form': ...}, request), withrequest.session = {}) and walk each role branch.id="snomed-search"), not on strings that alsogit rev-parse --abbrev-ref HEAD) andgit push origin main reports success while pushinggit log --oneline -1 origin/<branch>.hermes verify --json afterwards, and say plainly in the report that itsscripts/test_with_disposable_db.sh, which starts a throwawaypostgres:15-alpine, points the suite at it and removes it on exit). The app'sMeasured, not guessed — each of these cost time once:
<div> as well as each <input>django/forms/widgets/multiple_input.html), so a loop that setsform-check-input on every widget puts it on the wrapper — a 1em-tall bordered boxRadioSelect in such loops and hand-build.form-check form-check-inline markup.icontains over an EncryptedCharField matches every row. Patient name, NHSfirst_name__icontains='a' returns thegetElementById('x').addEventListener(...) throwing, which kills the rest of<script> block — including unrelated wiring below it. Comment out the elementdisplay:none required control cannot be submitted. Chrome cannot show therequired and enforce the choice in the page's own script plus theclean().{# … #} is single-line only. A multi-line one leaks verbatim into the page.{% comment %} … {% endcomment %} for anything that wraps lines.<script> block from the renderednode --check on it.Lead with the user-visible outcome and the evidence behind it. When something the user
reports turns out not to be the app's fault — a "505" that was a 502 from your own
restart, with the nginx timestamps matching the container's StartedAt and
RestartCount at 0 — say so plainly, with the evidence, rather than quietly fixing
something that was never broken.
When a finding is data-driven (a search, a lookup, a list), measure it before writing
it up, and commit the measurement as a read-only script in scripts/ so the other agent
can re-run it: scripts/measure_snomed_search.py is the pattern — SELECTs only, tenant
name as an argument, prints today's behaviour beside the proposed one. Then file the spec
in agents/handoff/from-hermes with numbers and acceptance criteria, and say which
published figure would become stale if the behaviour changes.