{"v":[{"id":639,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/40#issuecomment-639","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/40","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued, so whether to make it a submodule or fold it in is moot. Forms V2 is a web-only build (docs\/FormsV2Plan.md \u00a73.2). The directory is still untracked in this working tree. Archive or delete it on purpose rather than leaving it there by accident.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":640,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/41#issuecomment-640","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/41","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued, so no Android release build will be made. Forms V2 is a web-only build (docs\/FormsV2Plan.md \u00a73.2). If a native app is ever started again, release signing has to be settled before the first store upload.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":641,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/42#issuecomment-641","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/42","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued, so there is no native device layer to harden. Forms V2 is a web-only build and online-only (docs\/FormsV2Plan.md \u00a73.2). The findings here are worth re-reading if a native app is ever started again.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":642,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/43#issuecomment-642","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/43","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued, so there will be no device release for calls or push to be part of. Email and text alerts through the `notify\/*` subscriptions are unaffected. Any app-side call\/push wiring that is still left in this repo can be deleted as dead code.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":643,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/46#issuecomment-643","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/46","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued, so none of its flavors (H, B or C) will be built. Forms V2 is a web-only build (docs\/FormsV2Plan.md \u00a73.2).","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":644,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/47#issuecomment-644","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/47","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: the `apiapp\/` Flutter app has been discontinued. Field staff start a visit from the web app (`\/visits-v2`) under Forms V2, so a native home screen is no longer needed.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":645,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/21#issuecomment-645","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/21","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: this design depends on the API HHP app, meaning the `apiapp\/` Flutter app (device binding, biometric gate and the prompt UI), and that app has been discontinued. `push` stays out of `AUTH_FACTOR_BUILT`, so the chooser never offers it. If phone approval comes back with a different app, the safety rules above (number matching, rate limit, audited denial, short expiry, the verdict coming only from the device) still apply.","assets":[],"created_at":"2026-10-01T09:15:05-05:00","updated_at":"2026-10-01T09:15:05-05:00"},{"id":646,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/4#issuecomment-646","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/4","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing as superseded by Forms V2. This issue asked for a printable view of a V1 `form_instance`. V2 instead renders each form onto its original paper from a published placement map and keeps the result in the encrypted document store (`code\/form_v2_artifact.inc.php`, docs\/FormsV2Plan.md \u00a79). No V1 visit was ever started for a real client, so a V1 print path would have nothing to print.","assets":[],"created_at":"2026-10-01T09:15:06-05:00","updated_at":"2026-10-01T09:15:06-05:00"},{"id":647,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/55#issuecomment-647","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/55","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing as superseded by Forms V2. Under V2, the form catalogue is the original paper plus a placement map (`docs\/form-placements\/`) and a prepared interview (`docs\/form-v2-interviews\/`), not hand-authored V1 definitions, and page-tagging the ISP for V1 packets no longer unblocks anything. What remains is tracked in docs\/FormsV2Plan.md (\u00a79, and the missing-originals row of its blocker table).","assets":[],"created_at":"2026-10-01T09:15:06-05:00","updated_at":"2026-10-01T09:15:06-05:00"},{"id":658,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/49#issuecomment-658","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/49","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed 2026-10-01 by retiring the Forms V1 runtime, and by making the guard fail closed.\n\n* **The hole is gone.** Every endpoint that could reach a `fk_hh_member = 0` instance has been deleted: `formSessionAcquire`, `formSync`, `formFile`, `formAmend`, `formSessionRelease`\/`Break`, `formInstanceGet`\/`Answers`\/`Void`, and the rest of the V1 fill, sync and review runtime. Visits are Forms V2 now.\n* **The rule is fixed where it lived.** `hh_member_pk_in_scope()` (`code\/functions_hhm.inc.php`) now returns **false** for a pk of 0 or less. It used to return true. Every V2 caller already refused a zero itself, so V2 behaves the same. `scripts\/test-access-hygiene.php` now asserts this so it cannot quietly flip back.\n* **When staff or HR forms arrive**, a document about somebody who is not a client needs its own scope rule, for example the attendant's once the attendant record (RolesPlan \u00a77) exists. It must not be an exemption in the client rule. CLAUDE.md says so in the Permissions section.\n\nThe V1 tables (`form_instance` and the others) are kept, with any rows in them, and nothing reads them. Dropping them is a separate decision.\n\nStatus: the change is in the working tree and **not yet committed**. It has been applied to the local dev database only. The production deploy steps are in the docs\/Changelog.md entry for 2026-10-01.","assets":[],"created_at":"2026-10-01T09:52:49-05:00","updated_at":"2026-10-01T09:52:49-05:00"},{"id":861,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/76#issuecomment-861","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/76","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nThis is a test comment.","assets":[{"id":3,"name":"photo-20261001-131430-1.jpg","size":223494,"download_count":4,"created_at":"2026-10-01T13:14:31-05:00","uuid":"fc4ae3b8-8e92-4711-beed-c73428143432","browser_download_url":"https:\/\/www-s01.apinet.com\/codeforge\/attachments\/fc4ae3b8-8e92-4711-beed-c73428143432","type":"attachment"}],"created_at":"2026-10-01T13:14:30-05:00","updated_at":"2026-10-01T13:14:31-05:00"},{"id":866,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/132#issuecomment-866","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/132","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 6c07bb9: the idle sign-out is now 15 minutes (was 3). It is one constant, `SESSION_IDLE_TIMEOUT` in `code\/functions_auth.inc.php`. The browser countdown reads its remaining time from `\/do\/sessionCheck`, so the \"Stay Signed In\" warning still appears with 1 minute remaining (after 14 minutes of inactivity). The 12-hour absolute limit is unchanged.\n\nTakes effect once deployed. Note: 3 minutes was the documented HIPAA automatic-logoff setting, so any policy document that states 3 minutes should be updated to say 15.","assets":[],"created_at":"2026-10-01T13:45:51-05:00","updated_at":"2026-10-01T13:48:15-05:00"},{"id":874,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82#issuecomment-874","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Proposal: use a local copy of the NPPES file, not the live API\n\nCMS publishes the whole NPI registry as a free download (https:\/\/download.cms.gov\/nppes\/NPI_Files.html): a **full file each month**, a **weekly update file**, and a **monthly list of deactivated NPIs**. We can load it into our own table and search that table, so a visit never calls out to anything.\n\n**Import** (`scripts\/import-npi.php`, run on a monthly timer, then weekly for updates):\n- Stream the CSV inside the zip. The full file is about 9 GB with 300+ columns, so read it row by row and keep only what we use.\n- Keep **individuals** (entity type 1) whose practice location is in **Texas**, plus **organisations** (entity type 2) with a pharmacy taxonomy (`3336*`), which covers E1 as well.\n- Columns to keep: NPI, last\/first\/middle name, credential, primary taxonomy (shown as specialty), practice address, city, ZIP, phone, fax, deactivation date.\n- Upsert by NPI. Mark deactivated NPIs inactive rather than deleting them, so an old visit still resolves to the doctor it named.\n- New table `npi_provider` via `migrations\/`. It holds only public data, so it is stored in plaintext with no PHI. Index it on (last name, first name) and ZIP.\n\n**Lookup** (`\/do\/npiSearch`, gated like the visit screens):\n- Staff type a last name and optionally a first name, and can narrow by city or ZIP. We return up to about 20 matches showing name, credential, specialty and city. Staff pick one, address and phone fill in, and staff can still edit them.\n- Searching by name alone isn't precise enough for common surnames. Narrowing by city or ZIP fixes that. Defaulting to the client's own ZIP would also work, and it is safe because the search stays on our server.\n- Store the **NPI** with the answer, not just the address text, so we can trace which record was picked.\n\n**Why a download beats the API:** nothing about the client, not even the doctor's name, leaves our server. Production needs no outbound access at visit time. Search speed doesn't depend on CMS being up. The weekly update file keeps us at most a week behind the registry.\n\nThe open question for the customer still applies: if they keep their own list of doctors, we could load it into the same table as an extra source.","assets":[],"created_at":"2026-10-01T15:22:54-05:00","updated_at":"2026-10-01T15:22:54-05:00"},{"id":875,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99#issuecomment-875","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Proposal: use the same NPI download as #82\n\nPharmacies are already in the NPPES file proposed in #82: organisations (entity type 2) with a pharmacy taxonomy (`3336*`). The import keeps them in the same `npi_provider` table, so we need no second source and no second import.\n\n**Where the answer comes from, in order:**\n1. **On file.** If the client's last visit recorded a pharmacy, start with that one already selected. VESTA as a source is still the open question for the customer.\n2. **Look it up.** Staff type part of the name. Results are limited to the **client's ZIP or city** first, because a name like \"Walgreens\" or \"CVS\" matches hundreds of stores and only the location tells them apart. Each match shows the name, street address and phone.\n3. Staff pick one. Name and phone fill in, and both can still be edited.\n\n**Details:**\n- Also import the NPPES \"other name\" file, so a store's DBA name (e.g. \"CVS Pharmacy #1234\") can be searched as well as its legal company name.\n- Show the pharmacies this agency has picked before at the top of the results. That gives the requested drop-down of known pharmacies with no separate list to maintain.\n- Store the pharmacy's **NPI** with the answer, as for the doctor.\n- The search runs against our own table, so nothing about the client leaves our server.\n\n**Still needed from the customer:** does VESTA or their own records already hold each client's pharmacy? If so, we prefill from that and use the lookup only when nothing is on file.","assets":[],"created_at":"2026-10-01T15:24:26-05:00","updated_at":"2026-10-01T15:24:26-05:00"},{"id":877,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99#issuecomment-877","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nVESTA does not record (and thus does not export) any pharmacy information. We'll need to record it in our member record (after being accepted by office review) and prefill any future forms from that.","assets":[],"created_at":"2026-10-01T15:59:22-05:00","updated_at":"2026-10-01T15:59:22-05:00"},{"id":878,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-878","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Where a shared question is worded differently on each form\n\n19 interview questions fill more than one form. **8 of them are worded differently from form to form**; these are the ones the customer needs to decide on. The other 11 (advance directives, release of records, unable-to-sign reason, rights reviewed) read the same on both consents and on Private Pay, so they need no decision.\n\n| Question on screen today | Wording on each form |\n|---|---|\n| Client's sex | Client Information Sheet: **\"Gender\"** \u00b7 Emergency\/Disaster Form, PAS Service Plan: **\"Sex\"** |\n| Emergency priority class | Client Information Sheet: **\"Classification\"** \u00b7 Emergency\/Disaster Form: **\"What category describes your special needs?\"** |\n| First emergency contact: name | Client Information Sheet: **\"First Emergency Contact \u2014 Contact Person\"** \u00b7 Emergency\/Disaster Form: **\"In case of emergency contact \u2014 Name\"** |\n| First emergency contact: relationship | Client Information Sheet: **\"First Emergency Contact \u2014 Relation\"** \u00b7 Emergency\/Disaster Form: **\"Relationship\"** |\n| First emergency contact: phone | Client Information Sheet: **\"First Emergency Contact \u2014 Phone Number\"** \u00b7 Emergency\/Disaster Form: **\"Phone Number\"** |\n| Level of supervision (RN or non-RN) | PAS Service Plan: **\"Plan of Supervision\"** \u00b7 Form 3040: **\"Level of Supervision\"** |\n| Tasks in the service plan | PAS Service Plan: **\"Service Plan Tasks\"** \u00b7 Coordination of Care: **\"Tasks provided\"** |\n| Other task in the service plan | PAS Service Plan: **\"Service Plan \u2014 Other\"** \u00b7 Coordination of Care: **\"Other (specify)\"** |\n\n### Two things make the decision harder\n\n- **Some wordings don't stand on their own.** \"Phone Number\", \"Relationship\" and \"Classification\" only make sense next to the rest of the paper form. The Emergency\/Disaster Form's wording also reads as a different question from the Client Information Sheet's. So \"one form wins\" would need a rule like \"use the Client Information Sheet's wording, which is the most complete\". Showing each form's wording could be confusing here.\n- **Wording that matches can still be a fragment.** The medical power of attorney's phone line is just **\"Phone\"** on all three forms. \"Which directives\" and \"Reviewed and acknowledged\" also only make sense next to the paragraph above them. Showing the form's wording as the label works there only if the surrounding paper text (already shown underneath) stays visible.\n\n### Source\n\nThe wordings come from the field labels in our form definitions (`docs\/forms\/*.json`), which were transcribed from the paper forms, and the list of shared questions from `FORM_V2_SHARED_ELEMENTS` in `code\/form_v2_shared.inc.php`. They should match the paper closely but may not be exact, so check them against the paper before sending this to the customer.","assets":[],"created_at":"2026-10-01T18:08:58-05:00","updated_at":"2026-10-01T18:08:58-05:00"},{"id":879,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-879","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Most of this change does not wait on the customer\n\nThe customer's decision above covers only the **8 shared questions** whose wording differs between forms. In the SOC\/AO interview, **250 questions belong to a single form**, so they can simply take that form's printed wording as their label. That work can go ahead now.\n\nThe example the customer raised is one of these. It is on the PAS Service Plan only (`form.pas-eval-service-plan.duplicate_phc`):\n\n| | Form wording | Our interview today (`docs\/form-v2-interviews\/SOC_AO.json`) |\n|---|---|---|\n| Question | \"Do these services appear to duplicate PHC services?\" | \"Do any of those services overlap with what our attendant will do?\" |\n| Follow-up | \"Comments\" | \"Explain the overlap.\" |\n\nThe follow-up box has the same problem: the form calls it \"Comments\".\n\n**Keep the explanations when the labels change:**\n- The form's own instruction (\"If yes, notify the individual's DADS caseworker before requesting prior approval\u2026\") is already shown under the question and stays there (FormsV2Plan \u00a74.1.2).\n- Our plain-English help line (\"Your judgement: would the client be getting the same help twice?\") can stay as a hint below the form's wording. That way the label matches the paper without losing the explanation.","assets":[],"created_at":"2026-10-01T18:12:22-05:00","updated_at":"2026-10-01T18:12:22-05:00"},{"id":921,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/105#issuecomment-921","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/105","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** this probably gives us the mapping.\n\n> Opposite Answers:\n> Is a change in tasks or hours, or should services end? Yes, No\n> Is the service plan enough to meet clients needs? No, Yes\n\nIn the interview that is `form.forms-3040.change_needed` \u2194 `form.forms-3040.service_plan_adequate`. These are likely Q3b and Q7. Check against the paper before building.\n\nSnag: `service_plan_adequate` has a third option, \"Does not apply\", which has no opposite. Decide whether it goes away once the answer is derived.\n\nDon't mix this up with H6 (#96), \"Does the client need the services?\" (`continues_to_need_services`). That one is a separate always-Yes default.","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":923,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103#issuecomment-923","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** the frequency is a drop-down with three values, and each task has a default.\n\n> How often: Drop Down QD - Every Day, QW - Every week, PRN - As Needed\n\n| Task | Default |\n|---|---|\n| Bathing, Dressing, Exercising, Feeding\/Eating, Grooming\/Shaving\/Oral Care, Routine Hair\/Skin Care, Toileting, Transfer, Ambulation, Cleaning, Walking, Meal Preparation, Assist w\/ Self-Admin Med | QD |\n| Laundry, Shopping | QW |\n| Escort | PRN |\n| Blank \"other\" row | drop-down (QD, QW, PRN) |\n\nToday `form.forms-3040.task_*_freq` is **free text** (\"daily, 3 times a week\"). So:\n1. Make it a choice of QD \/ QW \/ PRN, with the defaults above, and only for tasks in the client's VESTA list (S4 #89 \/ A4 #108).\n2. Add the blank \"other\" task row, with its name typed in and its frequency from the drop-down.\n3. Still to ask: with sensible defaults, do we still need to *refuse* QW on a QD task, or is the default enough?","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":926,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/106#issuecomment-926","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/106","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** the exact value to fill.\n\n> Who should the attendant report them to? \u2026 autofill it to the Agency \"Legacy Home Health Agency & main phone number\" Form 3040 Check Off 10-4\n\n* Name: the agency name.\n* Phone: the agency's **main** number. The fact we have is `agency_phone_24h`. Ask whether \"main\" is a different number, and if it is, add it as a system fact.\n* \"Check Off 10-4\" looks like the paper's item number. Record it for G2 (#78).\n\nThe question above it, \"Which health problems must the attendant report?\", is now A7 (#138).","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":929,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/109#issuecomment-929","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/109","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):**\n\n> Attendants and supervision: Attendant A-C - Pull from Service Plan?\n\nThis partly answers the question: Attendants A\u2013C on the 3040 come from the service plan (the *Attendants and their hours* step \/ `schedule.attendant_*`). It still has a question mark, so confirm it with the customer.","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":930,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/95#issuecomment-930","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/95","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** the format.\n\n> Change of Tasks, Hours, or Services Ending: Template Elements Notes and Fill in Blanks\n\nSo the answer is a structured template (elements, plus notes and fill-in blanks), not free text. We still need the template's actual elements from the customer.\n\nAlso related: the Yes\/No on this question is the opposite of \"Is the service plan enough\u2026\" (A1, #105).","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":932,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/112#issuecomment-932","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/112","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):**\n\n> Drop down with who's signing for Client\n\nThe question of who signs for the client, and on what authority (`shared.release.authority`, \"If someone is signing for the client, what gives them the right to?\"), should be a **drop-down**, not typed. Get the list from the customer (POA, MPOA, guardian, parent\u2026). The POA choice is then what opens the follow-up question this issue is about. Hiding all of it when the client signs is W1 (#111).","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":934,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/90#issuecomment-934","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/90","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):**\n\n> Other care the client receives <-> Service plan\n\nThe service plan's \"other services\" and the *Other care the client receives* step (Coordination of Care) are the same information. Filed as K2 (#140): derive the service plan line from Coordination of Care, so \"no providers entered\" answers what prints here.","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":936,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97#issuecomment-936","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** confirmed. \"Does the client's schedule vary week to week? No\", answered automatically. The schedule note next to it is H9 (#139).","assets":[],"created_at":"2026-10-02T09:06:26-05:00","updated_at":"2026-10-02T09:06:26-05:00"},{"id":938,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/102#issuecomment-938","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/102","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):** \"Task Guide - Review Needed\". Still undecided on their side.","assets":[],"created_at":"2026-10-02T09:06:27-05:00","updated_at":"2026-10-02T09:06:27-05:00"},{"id":939,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/107#issuecomment-939","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/107","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**From Frank's notes of the on-site meeting (29 Sep):**\n\n> Is this only an orientation or is no attendant assigned yet? Review needed\n\nThis is `form.forms-3040.satisfaction_na_note`. It gates every per-attendant satisfaction and task-performance question, so the decision here changes how much of the 3040 is asked at an SOC.","assets":[],"created_at":"2026-10-02T09:06:27-05:00","updated_at":"2026-10-02T09:06:27-05:00"},{"id":940,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-940","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built on dev (5 Oct 2026). The customer decision is still open\n\n**What changed on the visit screen**\n- Each question is now called what the paper form calls it (taken from the field labels in our form definitions). Our plain-English wording is kept underneath it as an \"Ask: \u2026\" line, so the supervisor still has something natural to read out.\n- **276 of the 316** SOC\/AO questions now carry the form's wording. 16 of those (Form 3040 items 4, 5, 8, 9, 10-5, 10-6, 3B) were set to the paper's exact text, because the definition had shortened it.\n- Example: the PAS Service Plan question now reads **\"Do these services appear to duplicate PHC services?\"**, with \"Ask: Do any of those services overlap with what our attendant will do?\" below it.\n\n**Still waiting on the customer: 40 questions where the forms word it differently**\n- The 8 shared questions in the table above.\n- The client's details that print on many forms. For example, the client's name is worded 14 different ways (\"Individual's Name (please print)\", \"Client Name\", \"Patient Name\"\u2026). The same goes for date of birth, Medicaid number, phone, address and physician.\n- The schedule cells (the ISP says \"Sun\", the 3040 says \"Sunday\").\n\nUntil the customer decides, these keep our wording. The \u24d8 panel on each one lists every form's own wording (\"On this form: 'Gender'\"). Once they decide, the change is a rule in one place (`form_v2_paper_wording()`), not 40 edits.\n\n**Not changed:** the wording of answer *choices* (e.g. \"In person\" \/ \"By telephone\"). Should those go back to the paper's too? That's a question for the customer as well.\n\nTechnical: FormsV2Plan \u00a74.1.3, Changelog 2026-10-05. Interview file key `say` is new and documented in `docs\/form-v2-interviews\/README.md`.","assets":[],"created_at":"2026-10-05T09:24:16-05:00","updated_at":"2026-10-05T09:24:16-05:00"},{"id":941,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/78#issuecomment-941","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/78","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built on dev (5 Oct 2026)\n\n**Only Form 3040 prints question numbers.** I checked every Start of Care blank: the Client Information Sheet, both consents, Coordination of Care, the Emergency\/Disaster plan, 2-1-1, the PAS Service Plan, the Task Guide, Things You May Not Do, the Transfer form, Private Pay and the H2076. None of the others numbers its questions, so for those forms no number is shown, and none is made up.\n\n**On the 3040** the numbers are items 1, 2A, 2B, 2C, 3A, 3B, 4\u20139 and 10-1 to 10-7. They are recorded for all 96 of its numbered fields. The visit screen shows them beside the question, e.g. **\"Form 3040, item 8\"**. The form itself calls them \"items\" (\"items 4 and\/or 5\"). A question that fills several numbered forms would show each one.\n\n**How it's stored:** a `numbering` block in `docs\/form-placements\/forms-3040.json`, carried into the form's map, so the numbers are pinned with the paper. A new revision of the form can renumber it. The Form Explorer shows the number on each box row. Still to do: the data-element list (R1).\n\nTechnical: FormsV2Plan \u00a74.1.3, Changelog 2026-10-05.","assets":[],"created_at":"2026-10-05T09:24:16-05:00","updated_at":"2026-10-05T09:24:16-05:00"},{"id":942,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-942","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Answer choices now match the form too (5 Oct 2026, dev)\n\nDecided: choices say what the form says. **\"HDM\"**, not \"Home-delivered meals\". The same goes for DME, DAHS, N\/A, RN \/ Non-RN, NON HCBS, Family Residence, and so on.\n\n- 252 reworded choices were removed from the SOC\/AO interview, so the form's wording shows through.\n- The same rule as the question labels applies: a choice uses the form's wording wherever every form that offers it agrees.\n\n**Three choice lists still keep our wording, because the forms disagree.** These join the 40 questions waiting on the customer:\n- **Emergency priority class:** the Client Information Sheet has a one-line choice (\"Class 1: Bed confined \/ services may not be postponed\"). The disaster plan has a paragraph for each class.\n- **Sex:** the ISP prints \"M\" \/ \"F\", the other forms \"Male\" \/ \"Female\".\n- **Service tasks:** the ISP prints numbered bilingual codes (\"1 - Bathing \/ Ba\u00f1ar\"), the Coordination of Care plain names (\"Bathing\").","assets":[],"created_at":"2026-10-05T09:45:05-05:00","updated_at":"2026-10-05T09:45:05-05:00"},{"id":943,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127#issuecomment-943","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built (2026-10-05), not yet committed.**\n\n`scripts\/form-v2-pipeline.php --interview=SOC_AO_V2` (or `--packet=SOC_AO`) runs the whole chain:\n\n- **Import.** The packet's form definitions, placements and signing lines go through the existing importers. The interview comes from `docs\/form-v2-interviews\/`. The import is idempotent: a file that already matches the draft or the published version writes nothing. An interview draft edited on screen is not overwritten unless `--replace` is given.\n- **Publish.** `form_v2_pipeline_run()` publishes the definitions, then the maps, then the interview (through the editor's gate), then a release (`SOC_AO-20261005`, numbered if the code is taken). All of it is one transaction, using the screens' own publishers. It commits only if nothing was refused and the #130 check passes on the release it produced.\n- **Modes.** The default is a dry run: the chain runs and is rolled back, so the output is the actual release rather than a prediction. `--import` writes drafts only. `--apply --as=EMAIL` publishes.\n- **Activation.** `--activate=9,10` activates afterwards. It is refused on production (see #128).\n- **Idempotent.** If the content is the same as the last release, no new release is made.\n\nReview page: `\/admin\/form-v2-publish` (see #130).\n\nTwo limits:\n- The placement proposer reads the *published* definition. A form that gains a new field therefore needs its definition published before its placement can be proposed. The pipeline refuses this case and names it; it does not guess.\n- Packets are not imported or published by the chain. They keep `import-form-packets.php --publish`.\n\nAlso fixed: `form-publish.php` judged a later form's map against the *old* definition when it published several forms in one run, because of a per-request cache.","assets":[],"created_at":"2026-10-05T10:06:03-05:00","updated_at":"2026-10-05T10:06:03-05:00"},{"id":946,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/130#issuecomment-946","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/130","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built (2026-10-05), not yet committed.**\n\n`form_v2_publish_check()` (pure, `code\/form_v2_review.inc.php`) is built on the Form Explorer's model, and three places read it: the Explorer's **Gaps** tab, the CLI (#127) and the new review page. A publish is refused unless all nine checks pass:\n\n1. publish rules\n2. required boxes are asked\n3. every box is filled, or blank for a written reason\n4. no orphan questions\n5. every branch reachable\n6. bindings\n7. paper coverage\n8. every shared answer has a ruling\n9. **every form prints a proof**, using the same rule a kept form uses\n\nThe Gaps tab does not render proofs, so it shows that check as \"not run\", which never counts as a pass.\n\n**Review page `\/admin\/form-v2-publish`** (menu: *Publish an Interview*) shows:\n- the verdict and the check;\n- what publishing does;\n- what changed: questions new, reworded (old and new side by side), moved, asked under another condition or removed, plus changes to the forms and placements;\n- every question in full;\n- one **Publish all of this** button (needs the Approve right).\n\nThe page is a rollback rehearsal of the real publish. The button sends the reviewed release's fingerprint, and the publish refuses if anything changed in between. It never activates.\n\nSOC_AO rehearses clean today: 316 questions, all nine checks pass.\n\nTests: `test-form-v2-review.php` (51) and `test-form-v2-pipeline-state.php` (19).","assets":[],"created_at":"2026-10-05T10:06:03-05:00","updated_at":"2026-10-05T10:06:03-05:00"},{"id":948,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127#issuecomment-948","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Update: the \"new field\" limitation is gone.**\n\nThe placement and signing-line proposers now read the definition *about to be* published: each form's newest draft wherever it differs from the published version. `--published` restores the old reading.\n\nSo a field added to a form gets its boxes in the same `--import`, and the one-button publish carries the form and its placement together. Publishing still judges each map against the definition published at that moment, which in the pipeline is the one it has just published.\n\nProved on dev:\n- I added a tick box to a draft of *Things Your Attendant May Not Do*.\n- One rehearsal published the definition (\"New field\") and the map (\"Now printed\").\n- The check refused only because the interview didn't ask the new field yet, which is correct.\n\nThe test drafts were removed afterwards. The regression is pinned in `test-form-v2-pipeline-state.php`, which now has 24 checks.","assets":[],"created_at":"2026-10-05T10:12:28-05:00","updated_at":"2026-10-05T10:12:28-05:00"},{"id":949,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/129#issuecomment-949","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/129","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Decision: remove the edit screens.** The files are the source of truth, and a correction is a file change.\n\n**Done (2026-10-05):**\n* `\/admin\/form-v2-workflows` (Visit Interviews) and `\/admin\/form-v2-maps` (Form Placement) are off the menu and unrouted (both now 404). They are kept for reference in `archive\/form-v2-editors\/`, with a README covering why and how to bring one back.\n* Their 15 endpoints moved there too: `formV2Workflow{Create,Get,List,NewDraft,Preview,Publish,SaveDraft}`, `formV2Map{Get,List,NewDraft,Preview,Publish,SaveDraft,SuggestSignings}`, and `formV2ReleasePublish` (assembling a release by hand, which the pipeline replaced).\n* **Two things on the interview screen were not editing**, so they stayed live on a new page, `\/admin\/form-v2-releases` (**Paperwork in Use**):\n  * **choosing a release for each agency** (#128). The CLI's `--activate` is refused on production, so without this page nothing could put a release in use;\n  * **retiring an interview, or bringing one back.**\n* Publish an Interview, the Explorer's Gaps tab and `form-v2-pipeline.php` now point there. The library functions are unchanged.\n\nThe route is now: files \u2192 Form Explorer (check) \u2192 Publish an Interview \u2192 Paperwork in Use.","assets":[],"created_at":"2026-10-05T10:24:08-05:00","updated_at":"2026-10-05T10:24:08-05:00"},{"id":952,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/126#issuecomment-952","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/126","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Status, 2026-10-05: mostly done by #127\/#130. Two pieces are still open.**\n\nThe **Publish an Interview** page (`\/admin\/form-v2-publish`) already shows what changed between what you are about to publish and the latest release:\n* **Questions:** new, reworded (old and new wording side by side), moved, removed, or asked under a different condition, with an \"Only what changed\" filter (`form_v2_workflow_diff()`).\n* **Forms:** for each form, changes to the definition and to where answers print (`form_v2_form_def_diff()`, `form_v2_map_diff()`).\n* **The release as a whole:** `form_v2_release_compare()` now has a screen.\n\nThat covers \"draft beside published\". It sits on the Publish page rather than the Form Explorer, which is where the review happens.\n\n**Still open:**\n1. **Compare any two releases after they are published** (for example \"what changed between the 1 Oct and 5 Oct releases\"). Today you only see a comparison at publish time. The natural place is beside each release on the new **Paperwork in Use** page (`\/admin\/form-v2-releases`, #129).\n2. **Link each change to the issue that asked for it.** Releases carry free-text notes, and nothing ties a change to an issue. The links could come from the release notes, or from the commit that changed the prepared file.\n\nSmaller point: a prefill change shows up through the question and placement diffs, but it is not called out as a prefill change of its own.","assets":[],"created_at":"2026-10-05T10:32:13-05:00","updated_at":"2026-10-05T10:32:13-05:00"},{"id":954,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/129#issuecomment-954","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/129","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Follow-up: the Form Editor is archived too.** `\/admin\/forms-edit` was still live and on the menu in production after the archive deploy. Form definitions are files in `docs\/forms\/`, so the same drift applies.\n\n* The page and its eight endpoints (`formCreate`, `formImport`, `formDef{NewDraft,SaveDraft,Publish,Archive,Get,List}`) moved, unrouted, to `archive\/form-editor\/`, with a README.\n* **Import** with `scripts\/import-forms.php`.\n* **Publish** with `scripts\/form-v2-pipeline.php` or `\/admin\/form-v2-publish` for a form in a visit type, and `scripts\/form-publish.php --forms=CODE --apply --as=EMAIL` for any other form. All three use the same publish gate the editor used.\n\nNot changed: the Visit Packets editor (`\/admin\/form-packets`). It is off the menu but still reachable by URL, and packet definitions are files in `docs\/form-packets\/`.","assets":[],"created_at":"2026-10-05T10:34:31-05:00","updated_at":"2026-10-05T10:34:31-05:00"},{"id":955,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/80#issuecomment-955","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/80","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done as part of #77\/#78 (5 Oct 2026).\n\n- **FormsV2Plan:** new \u00a74.1.3 \"The form's wording and numbers are the question's\" explicitly reverses the spoken-label design. A question's label is the paper's wording; our wording moved to `say` (\"Ask: \u2026\"). The \u00a74.1.2 rule that the form's explanatory text must be on screen still stands. \u00a74.1.2 was left as history rather than rewritten, because \u00a74.1.3 overrides it by name.\n- **`docs\/form-v2-interviews\/README.md`:** says the label is the form's own wording (leave `label` out to get it) and `say` is how to ask it aloud.\n- **Project memory** (`feedback_v2_placement_by_claude`): marked superseded 2026-10-05 with the new rule.\n\n**Still open, tracked on #77:** the customer's choice of wording for the 40 questions where the forms disagree, and the three choice lists (emergency class, sex, service tasks). Until then those keep our wording, and the \"which forms\" panel shows each form's own.","assets":[],"created_at":"2026-10-05T11:21:05-05:00","updated_at":"2026-10-05T11:21:05-05:00"},{"id":958,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/79#issuecomment-958","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/79","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Status, 5 Oct 2026.\n\n**On each question: done** (shipped with #77\/#78). The \u24d8 \"Which forms this goes on\" panel lists every form the answer prints on, that form's own wording, and a link to the blank. The label shows \"Form 3040, item 8\" where the form prints item numbers.\n\n**Visit's list of documents: done in the repo, not yet published.** Rule: a form's number is used only when the filename or the paper itself shows it, in the format **\"Form N \u2013 Name\"**:\n- Form 3040 \u2013 Attendant Orientation \/ Supervisory Visit (filename)\n- Form H2076 \u2013 Authorization to Release Medical Information (printed)\n- Form 2101 \u2013 Authorization for Community Care Services (printed on the faxed copy)\n- Form 3052 \u2013 MD Order (reformatted)\n- Form TDEM 21 \u2013 STEAR, State of Texas Emergency Assistance Registry (blank catalogue only)\n\nThe other SOC forms (consents, Client Information Sheet, Coordination of Care, ISP, etc.) print no number and keep their names. Changed in `docs\/form-packets\/*.json` (the document list), the two definitions, and `code\/form_blanks.inc.php`. `scripts\/test-form-blanks.php` now fails on a numbered title in any other format.\n\n**To apply:** `import-form-packets.php --publish --as=\u2026`, then `form-v2-pipeline.php --packet=SOC_AO --apply --as=\u2026` for the two definition titles. Close once it is visible on a visit.","assets":[],"created_at":"2026-10-05T11:21:06-05:00","updated_at":"2026-10-05T11:21:06-05:00"},{"id":960,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/79#issuecomment-960","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/79","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Published on dev and confirmed: the form numbers show in the visit's list of documents. Closing.","assets":[],"created_at":"2026-10-05T11:28:03-05:00","updated_at":"2026-10-05T11:28:03-05:00"},{"id":963,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/92#issuecomment-963","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/92","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built, not yet published (5 Oct 2026).**\n\n- Authorized weekly hours are no longer a question. `auth.weekly_hours` is in `FORM_V2_FROZEN_KEYS`, so it prints straight from the visit's copy of the VESTA authorization and no answer can change it. The data dictionary went to version 3, so every interview and release has to be republished.\n- The visit's schedule step shows **\"Authorized: 20.00 hours a week\"**, read-only, under the totals, and says *\"The schedule is X hours over\"* when the schedule adds up to more. This is a notice only. Enforcing the limit is H3.\n- The question is gone from `docs\/form-v2-interviews\/SOC_AO.json`, and the service plan step's intro says where the hours are shown now.\n- **`auth.days_per_week` stays asked.** The customer said the time frame may change, and days per week is the time frame.\n- The Form Explorer shows it as *\"Shown from the authorization, cannot be changed at the visit\"*.\n\n**To finish:** the drafts are imported on dev (`form-v2-pipeline.php --packet=SOC_AO --import --replace`) and the rehearsal passes all nine checks. Next: review and publish on `\/admin\/form-v2-publish`, then choose the release on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-05T11:39:41-05:00","updated_at":"2026-10-05T11:39:41-05:00"},{"id":964,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91#issuecomment-964","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built, not yet published (5 Oct 2026).**\n\n- Times are still entered as start and end. **Each attendant's day has to come out in quarter hours.** The server refuses a `schedule.attendant_N_{day}` cell whose total isn't a multiple of 15 minutes (`FORM_V2_SCHEDULE_STEP_MINUTES`). The refusal says what the times come to: *\"These times come to 1 hour 10 minutes. Each day's hours must come out in quarter hours (1.00, 1.25, 1.50, 1.75), so move a start or end time.\"* Odd minutes that add up are fine (8:10\u20139:25 = 1.25), and a split day is judged on its total.\n- The visit page marks an off-step day in red as it is typed, and shows every hours figure to two decimals (cells, per-attendant week, all-attendant totals).\n- A VESTA schedule day that is off the step isn't carried into the visit. The supervisor sees \"VESTA has \u2026, which could not be used\" and enters it properly.\n- **The printed forms show two decimals.** There's a new `decimals` key on `number` fields, set to 2 on all 30 hours boxes: the 3040 (schedule totals, hours per attendant, authorized hours), the service plan (schedule totals, Total Hours) and both consents (hours per week).\n\n**To finish:** same publish as #92. It needs new versions of the 3040, the service plan and both consent forms, which the pipeline rehearsal already covers.","assets":[],"created_at":"2026-10-05T11:39:42-05:00","updated_at":"2026-10-05T11:39:42-05:00"},{"id":966,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/86#issuecomment-966","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/86","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built, not yet published (5 Oct 2026).**\n\n- The service end date question is removed from `docs\/form-v2-interviews\/SOC_AO.json` and recorded in its `not_asked`, with the customer's reason.\n- Only one form prints it: the service plan's \"End Date (if applicable)\". No form requires it, so the publish check accepts it. It now prints blank, because a value that isn't asked is never projected, even though VESTA has one.\n- The Form Explorer shows it as *\"Not asked: prints blank\"*, with the reason.\n\n**To finish:** same publish as #92 \/ #91.","assets":[],"created_at":"2026-10-05T11:39:42-05:00","updated_at":"2026-10-05T11:39:42-05:00"},{"id":969,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91#issuecomment-969","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Changed:** a VESTA schedule day that doesn't come out in quarter hours is now **still filled in**. It carries the same message a typed one gets (\"These times come to 1 hour 10 minutes. Each day's hours must come out in quarter hours\u2026\"), shown under the cell and listed on the visit's check. The forms can't be printed for signing until it is fixed. A day the supervisor types off the quarter hour is still refused when saved. The rule lives in one place (`form_v2_visit_value_problem()`), and the save, the check and the page all read it.","assets":[],"created_at":"2026-10-05T11:43:02-05:00","updated_at":"2026-10-05T11:43:02-05:00"},{"id":970,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/92#issuecomment-970","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/92","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Committed in efc8b74. Closing. To reach the customer, the SOC_AO release still has to be published (`\/admin\/form-v2-publish`) and chosen for each agency (`\/admin\/form-v2-releases`).","assets":[],"created_at":"2026-10-05T11:54:03-05:00","updated_at":"2026-10-05T11:54:03-05:00"},{"id":971,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91#issuecomment-971","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/91","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Committed in efc8b74. Closing. To reach the customer, the SOC_AO release still has to be published (`\/admin\/form-v2-publish`, which includes new versions of the 3040, the service plan and both consent forms) and chosen for each agency (`\/admin\/form-v2-releases`).","assets":[],"created_at":"2026-10-05T11:54:03-05:00","updated_at":"2026-10-05T11:54:03-05:00"},{"id":972,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/86#issuecomment-972","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/86","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Committed in efc8b74. Closing. To reach the customer, the SOC_AO release still has to be published (`\/admin\/form-v2-publish`) and chosen for each agency (`\/admin\/form-v2-releases`).","assets":[],"created_at":"2026-10-05T11:54:03-05:00","updated_at":"2026-10-05T11:54:03-05:00"},{"id":977,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138#issuecomment-977","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nThe form blank includes pre-filled \"Fall or Injury\" and \"Rash or Skin Breakdown\" as those are the health problems the attendant must report. We don't change the wording on the interview but we include those two items. The form is answered yes\/no as an acknowledgement. When generating, put checks on the items on the form if they answer Yes.","assets":[],"created_at":"2026-10-05T11:58:56-05:00","updated_at":"2026-10-05T11:58:56-05:00"},{"id":978,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138#issuecomment-978","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in the files (not yet committed or published):\n\n- **Definition** (`docs\/forms\/forms-3040.json`): `ori_report_health_problems` is now a Yes\/No (`checkbox`) instead of free text. Its help text lists the two problems as the paper prints them (Fall or Injury, Rash or Skin Breakdown), so they show with the question.\n- **Interview** (`SOC_AO.json`): the wording is unchanged (\"Which health problems must the attendant report?\"). The help now says to answer Yes once the attendant has been told.\n- **Printing** (`docs\/form-placements\/forms-3040.json`): a Yes puts an X in item 10-4's box (it was not placed before) and a \u2713 beside each of the two printed problems. A No prints nothing. Checked on a proof.\n- **Pipeline fix** found along the way: the interview import was seeded from the *published* definitions but checked against the new ones, so a field whose type changed was refused on every run. The import now reads the definitions about to be published. Pinned in `test-form-v2-pipeline-state`.\n\nDefaulting it to Yes is left to #101, which is still open.\n\nTo ship: `form-v2-pipeline.php --packet=SOC_AO --import` (already rehearsed clean on dev), then review and publish on `\/admin\/form-v2-publish`.","assets":[],"created_at":"2026-10-05T12:06:29-05:00","updated_at":"2026-10-05T12:06:29-05:00"},{"id":984,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/146#issuecomment-984","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/146","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in `pages\/admin\/systems-edit.php` (not yet committed).\n\n**What was wrong:** the single \"Vesta Agency ID\" box saved a `vesta_agency_id` system setting that **nothing reads**. The pipeline maps VESTA agency \u2192 system with the constant `HH_AGENCY_SYSTEM` in `code\/functions_hhm.inc.php` (347 + 353 \u2192 11, 524 \u2192 10, 354 \u2192 9). Typing a second ID into the box would have changed nothing.\n\n**What changed:** the box is replaced by a read-only list of every VESTA agency ID that feeds the system, taken from `HH_AGENCY_SYSTEM` and named from `$SITE['agencies']`. For example, system 11 shows `347 LHHA CBR` and `353 LHHA RGV`, with the note \"Set in the data pipeline, not here.\" The save no longer writes the setting. Any old rows are left in place, unread. I checked that system 11 renders correctly on dev.\n\n**Not done:** making the mapping editable on screen. That would mean moving `HH_AGENCY_SYSTEM`, the CLI loaders' local `AGENCY_SYSTEM` copies and `get-reports.js`'s list into the database. If that's wanted, it should be its own issue.\n\nChangelog and UserChangelog updated.","assets":[],"created_at":"2026-10-05T13:27:36-05:00","updated_at":"2026-10-05T13:27:36-05:00"},{"id":985,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/146#issuecomment-985","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/146","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing. The systems editor now lists every VESTA agency ID that feeds a system, read-only, from `HH_AGENCY_SYSTEM`. Making the mapping editable on screen would be a separate issue, if it's wanted.","assets":[],"created_at":"2026-10-05T13:29:54-05:00","updated_at":"2026-10-05T13:29:54-05:00"},{"id":987,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/144#issuecomment-987","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/144","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in ac295e1 (`pages\/admin\/systems-edit.php`):\n\n* **Logo** moved into the right-hand column with the color settings.\n* **Unexplained blank fields** removed. These were eight `office_*` \/ `mailing_*` address settings with no label. The save ignored them and nothing read them; the agency's address is the Address field in the Agency section.\n* **Agency section** is now a compact two-column grid with one shared help line. System Name, Short Name and the VESTA agency IDs share a row, and each color is paired with a small preview swatch.\n* **Help text** no longer mentions form prefill.\n\nThe VESTA agency ID box was then changed to a read-only list in #146.","assets":[],"created_at":"2026-10-05T13:30:31-05:00","updated_at":"2026-10-05T13:30:31-05:00"},{"id":990,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138#issuecomment-990","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/138","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Closing: 10-4 is now a Yes\/No reminder, and a Yes ticks the box and checks both printed problems (details above). It reaches visits with the next SOC\/AO publish on `\/admin\/form-v2-publish`. Defaulting it to Yes is tracked in #101.","assets":[],"created_at":"2026-10-05T13:31:49-05:00","updated_at":"2026-10-05T13:31:49-05:00"},{"id":993,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/142#issuecomment-993","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/142","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Confirmed on production: the agency administrator is set to \"Ambrose Hernandez\". All good \u2014 closing.","assets":[],"created_at":"2026-10-05T14:08:29-05:00","updated_at":"2026-10-05T14:08:29-05:00"},{"id":995,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/87#issuecomment-995","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/87","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in the working tree (not yet committed or published). `shared.supervision_level` is now a fixed value (new derive op `fixed` in `code\/form_v2_shared.inc.php`): it's never asked and always prints **Non-RN**, which is how both the service plan's \"Plan of Supervision\" and the 3040's \"Level of Supervision\" say Supervisor. The question is gone from `docs\/form-v2-interviews\/SOC_AO.json`. Takes effect after `form-v2-pipeline.php --packet=SOC_AO --import --replace` and a publish.","assets":[],"created_at":"2026-10-05T14:17:29-05:00","updated_at":"2026-10-05T14:17:29-05:00"},{"id":996,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/96#issuecomment-996","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/96","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Decision (owner, 5 Oct 2026):** remove the question and always print Yes.\n\nDone in the working tree (not yet committed or published). New fixed value `shared.continues_to_need_services` (claims `forms-3040.continues_to_need_services`), which always prints **Yes** on 3A. The question is gone from `SOC_AO.json`. Takes effect after the SOC_AO import and publish.","assets":[],"created_at":"2026-10-05T14:17:29-05:00","updated_at":"2026-10-05T14:17:29-05:00"},{"id":997,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97#issuecomment-997","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in the working tree (not yet committed or published). New fixed value `shared.schedule_variable` (claims `forms-3040.schedule_variable`), which always marks **\"Is not variable\"**. No follow-up questions depended on it. The question is gone from `SOC_AO.json`. Takes effect after the SOC_AO import and publish.","assets":[],"created_at":"2026-10-05T14:17:29-05:00","updated_at":"2026-10-05T14:17:29-05:00"},{"id":998,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/139#issuecomment-998","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/139","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in the working tree (not yet committed or published).\n\n1. The paper reads \"ATTENDANT SCHEDULE (for use by provider agency) FWD: ____  Schedule \u2610 Is \u2610 Is not variable\". The blank is a date, which fits \"pulls from Service Plan Start Date\". The paper doesn't spell out \"FWD\".\n2. `forms-3040.schedule_fwd` is now a `date` field prefilled from `auth.service_start_date`, the same value the service plan prints as its Start Date. So it's one question, asked on the service plan step and confirmed against VESTA, and the 3040 prints it as a date (placement `format: date`). The Explorer shows its source as that question. The free-text \"Schedule note (FWD)\" question is removed from `SOC_AO.json`.\n\nTakes effect after the SOC_AO import and publish.","assets":[],"created_at":"2026-10-05T14:17:29-05:00","updated_at":"2026-10-05T14:17:29-05:00"},{"id":999,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/87#issuecomment-999","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/87","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production 5 Oct 2026. Closing.","assets":[],"created_at":"2026-10-05T14:21:35-05:00","updated_at":"2026-10-05T14:21:35-05:00"},{"id":1000,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/96#issuecomment-1000","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/96","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production 5 Oct 2026. Closing.","assets":[],"created_at":"2026-10-05T14:21:35-05:00","updated_at":"2026-10-05T14:21:35-05:00"},{"id":1001,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97#issuecomment-1001","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/97","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production 5 Oct 2026. Closing.","assets":[],"created_at":"2026-10-05T14:21:35-05:00","updated_at":"2026-10-05T14:21:35-05:00"},{"id":1002,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/139#issuecomment-1002","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/139","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production 5 Oct 2026. Closing.","assets":[],"created_at":"2026-10-05T14:21:35-05:00","updated_at":"2026-10-05T14:21:35-05:00"},{"id":1008,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/78#issuecomment-1008","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/78","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production 5 Oct 2026. The visit screen shows \"Form 3040, item N\" beside each question the 3040 numbers, and the Form Explorer shows the number on each box row. The 3040 is the only Start of Care form that prints question numbers.\n\nThe leftover \"add the numbers to the data-element list (R1)\" isn't needed. That list was a one-off snapshot from 29 Sep, and the Form Explorer replaced it; the Explorer already shows the numbers. Closing.","assets":[],"created_at":"2026-10-05T14:24:13-05:00","updated_at":"2026-10-05T14:24:13-05:00"},{"id":1012,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/89#issuecomment-1012","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/89","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Done (5 Oct 2026).** The service plan's tasks are now the tasks authorized in VESTA, and they can't be changed at the visit.\n\n**What the supervisor sees**\n- The service plan step shows \"The tasks the client is authorized for\", read-only, in place of the tick list. It says that a wrong task is fixed by asking the office to correct the authorization in VESTA, the same route as the hours (#92).\n- If VESTA lists no tasks for the client, the step says so instead of showing an empty list.\n\n**What prints**\n- The service plan, the coordination of care form and the task guide (#102) all print from that one list.\n- **\"Other tasks\"** is no longer asked. A task VESTA doesn't list isn't authorized, so the \"Other\" task lines on the service plan and the coordination of care form print blank.\n\n**Checked on dev** (production data, demo client): her 8 VESTA tasks show read-only, and the same 8 print on all three forms.\n\n**Technical:** `auth.service_tasks` joins `FORM_V2_FROZEN_KEYS` (data dictionary 3 \u2192 4). Its shared entry is now source context and also prints on the task guide. A new interview key, `shows`, lets a step display a fixed value read-only. `shared.plan.tasks_other` moves to `not_asked`.\n\n**To go live:** run `form-v2-pipeline.php --packet=SOC_AO --import --replace`, review and publish on `\/admin\/form-v2-publish`, then choose the new release on `\/admin\/form-v2-releases`. Visits already started keep their current paperwork.\n\nNot changed: the 3040's orientation task grid (A4) still asks about every task.","assets":[],"created_at":"2026-10-05T15:31:17-05:00","updated_at":"2026-10-05T15:31:17-05:00"},{"id":1015,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/102#issuecomment-1015","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/102","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Decided and done (5 Oct 2026).** This follows from #89: the client's tasks are now VESTA's authorized list. We used both options, one on the screen and one on the paper.\n\n- **On screen: only the client's tasks.** The task guide step lists just the tasks the client is authorized for, each with what the task guide says it includes, so the supervisor goes over only those. Tasks that share a guide (transfer and ambulation, exercising and walking) appear as one line.\n- **On paper: the full guide, with the client's tasks marked.** The printed task guide is one fixed page, so nothing is cut from it. Each of the client's guides gets an X beside its heading. The marks were already measured on the page.\n\n**Checked on dev** (demo client, 8 tasks): the screen shows only her 8 tasks. The printed guide marks guides 01, 02, 05\/5A, 06\/07, 10, 11, 12 and 14.\n\n**Technical:** `task-guide.task_guide_reviewed` is now a target of the frozen `auth.service_tasks` entry, mapped to the guide codes. The step's `shows` carries each task's text as `option_info`.\n\nIt goes live with the same publish as #89.","assets":[],"created_at":"2026-10-05T15:31:17-05:00","updated_at":"2026-10-05T15:31:17-05:00"},{"id":1020,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127#issuecomment-1020","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/127","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Closing \u2014 committed and verified (2026-10-05).**\n\nThe pipeline is on `main` (`ef52232`, `745cfee`, `3bbf2db`): `scripts\/form-v2-pipeline.php`, `code\/form_v2_pipeline.inc.php`, `\/admin\/form-v2-publish`, and `scripts\/test-form-v2-pipeline-state.php` (25 passed, 0 failed).\n\nChecked against the issue:\n- **import \u2192 validate \u2192 publish \u2192 release \u2192 (activate):** one command, one transaction. Activation is refused on production and is #128's.\n- **Dry run by default, `--apply` to write:** yes. The dry run is a rehearsal that gets rolled back.\n- **Idempotent:** an unchanged file writes nothing, and unchanged content makes no new release.\n- **Same checks as the screens, refusals printed:** it uses the screens' own publishers and the #130 check.\n- **Interview publish, release assembly, single command:** all built.\n\nStill outside the chain, on purpose: packets (`import-form-packets.php --publish`).","assets":[],"created_at":"2026-10-05T15:41:54-05:00","updated_at":"2026-10-05T15:41:54-05:00"},{"id":1024,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/130#issuecomment-1024","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/130","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Closing \u2014 committed and verified (2026-10-05).**\n\nOn `main` (`ef52232`, `745cfee`, `7e183f9`). `form_v2_publish_check()` in `code\/form_v2_review.inc.php` is the one implementation, and all three places call it:\n- the Explorer's Gaps tab (`backend\/formV2ExplorerGet.php`),\n- the CLI (`code\/form_v2_pipeline.inc.php`, #127),\n- the review page (`\/admin\/form-v2-publish`).\n\nSo the screen and the CLI cannot disagree.\n\nChecked against the issue:\n- **Every field filled, or blank for a recorded reason:** check 3.\n- **Every branch reachable:** check 5.\n- **No orphaned questions:** check 4.\n- **Every shared element has a ruling:** check 8. `test-form-v2-shared.php`: 202 passed, 0 failed.\n- **Every proof PDF renders:** check 9.\n- **Refuses on failure:** the publish commits only if every check passes.\n\nToday's run:\n- `test-form-v2-review.php`: 53 passed, 0 failed.\n- `test-form-v2-pipeline-state.php`: 25 passed, 0 failed.\n- `form-v2-pipeline.php --interview=SOC_AO_V2` rehearsal: all nine checks `ok`, and release `SOC_AO-20261005-5` would be published.","assets":[],"created_at":"2026-10-05T15:43:52-05:00","updated_at":"2026-10-05T15:43:52-05:00"},{"id":1045,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/108#issuecomment-1045","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/108","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Done (5 Oct 2026).** The 3040's task questions now come from the client's VESTA tasks, the same list as the service plan, the coordination of care form and the task guide (#89, #102).\n\n**What the supervisor sees**\n- The 3040's task rows are asked only for the tasks the client is authorized for in VESTA. That covers how often each task is done and, when attendants are already working, how each attendant is doing with it.\n- At an orientation, only the frequency of each authorized task is asked, as before.\n- If VESTA lists no tasks, the task grid asks nothing.\n\n**What prints**\n- The rows for the client's tasks print as answered. The other rows on the 3040 print blank.\n\n**Checked on dev** (production data, demo client): her 8 VESTA tasks give 8 frequency questions at an orientation, and 32 questions with attendants working, down from 16 and 64.\n\n**Technical:** each `form.forms-3040.task_{row}_{freq,a,b,c}` question in `SOC_AO.json` is gated on `{\"contains\": [{\"field\": \"auth.service_tasks\"}, \"<task>\"]}`, ANDed with the existing orientation gate on the A\/B\/C ratings. The 3040's 16 rows match `FORM_FACT_SERVICE_TASK_DOMAIN` one to one, which `test-form-v2-workflow` pins. No schema or dictionary change. Commit 61655ab.\n\n**To go live:** it goes out with the same publish as #89: `form-v2-pipeline.php --packet=SOC_AO --import --replace`, review and publish on `\/admin\/form-v2-publish`, then choose the new release on `\/admin\/form-v2-releases`. Visits already started keep their current paperwork.\n\nNot changed: the limit on frequency per task is T3 (#103).","assets":[],"created_at":"2026-10-05T15:55:11-05:00","updated_at":"2026-10-05T15:55:11-05:00"},{"id":1050,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/84#issuecomment-1050","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/84","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 5e24274. **Plan for emergencies** is now the step right after **Communication and emergency contacts**, before Health, home and needs. The other steps keep their order.\n\nChecked on dev: the interview reseeded cleanly, and a new Start of Care visit for the demo client shows the new order.\n\n**To go live:** `form-v2-pipeline.php --packet=SOC_AO --import --replace`, review and publish on `\/admin\/form-v2-publish`, then choose the release on `\/admin\/form-v2-releases`.\n\nNote: the customer's full section order (O2) may reorder the steps again.","assets":[],"created_at":"2026-10-05T15:57:32-05:00","updated_at":"2026-10-05T15:57:32-05:00"},{"id":1056,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/104#issuecomment-1056","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/104","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Found and fixed\n\n**Cause: a hidden layer in the blank Task Guide PDF.** Our placement map and the render were not at fault.\n\nThe blank (`docs\/form-blanks\/task-guide.pdf`) came from a 2021 Adobe Illustrator file. It had two layers, and **\"Layer 1\" was switched off by default**. That hidden layer held an older copy of the page:\n\n- the **previous Legacy logo** (\"Health\/Hospice & PHC\/CBA Agency \u2013 *A Legacy of Caring*\"), in the same corner where the agency logo now prints;\n- a **second copy of the bottom line**, \"This program is only for the Client \u2013 NOT the entire family\", slightly offset so it looked doubled (and second copies of the section headings).\n\nWhether you saw it depended on the viewer. Our proof tooling (poppler) respects \"off\", so every proof looked clean. Apple's built-in PDF viewer and some print paths ignore it and draw the layer. The PDF tool that combines our answers with the blank (qpdf) carried the layer into every review and signed copy. Forcing the layer on reproduced exactly what the customer described.\n\n**Ruled out:** the placement map (proofs and a rebuild of the real review \u2192 signed path draw one logo and one bottom line); stamping twice at signing (the demo-day signed copy drew no logo, as designed); and the in-page form viewer.\n\n## Fix\n- Rebuilt the blank with the hidden layer's content removed and the layer settings dropped. Also removed a damaged page thumbnail of the old page and Illustrator's private data. qpdf now reports no errors; the old file had two damaged streams.\n- The visible page is **pixel-identical** to the old blank, and the text is unchanged, so every measured box stands. Only the fingerprint pin in `docs\/form-placements\/task-guide.json` changed (`e125925c\u2026` \u2192 `3aa2ceb5\u2026`).\n- The map moved onto the new paper and was published; deployed.\n- No other blank has a hidden layer.\n\nVisits prepared before the new release keep their pinned (old) paper, so only new visits get the fix.\n\nThe guard that keeps this from happening again is a separate action item.","assets":[],"created_at":"2026-10-05T16:09:53-05:00","updated_at":"2026-10-05T16:09:53-05:00"},{"id":1123,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/105#issuecomment-1123","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/105","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 5e9e9f9.\n\n* Confirmed against the paper: 3B is `form.forms-3040.change_needed`, item 7 is `form.forms-3040.service_plan_adequate`.\n* Item 7 is no longer asked. It is a new shared element, `shared.service_plan_adequate`, worked out with a new derived op `map`: 3B Yes prints No on item 7, and 3B No prints Yes. If 3B is unanswered, item 7 prints blank.\n* **N\/A:** dropped, because it has no opposite. Once 3B is answered, item 7 always has an answer.\n* 3B's help now says it also answers item 7.\n* The Form Explorer shows item 7 as worked out from 3B.\n* Tests: `test-form-v2-shared` checks both directions on the real projection. It also checks that the map names every answer 3B offers.\n\n**To go live:** run `form-v2-pipeline.php --packet=SOC_AO --import --replace`, publish on `\/admin\/form-v2-publish`, then choose the release on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T09:44:01-05:00","updated_at":"2026-10-06T09:44:01-05:00"},{"id":1124,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/106#issuecomment-1124","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/106","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 5e9e9f9.\n\n* `ori_report_to_name` now fills from the agency name (`system.agency_legal_name`).\n* `ori_report_to_phone` now fills from the agency's **main** phone (`system.agency_phone`), as Frank's notes asked. It does not use the 24-hour line the consents print. No new setting was needed.\n* Both questions are removed from the SOC\/AO interview. The 3040 definition is now v6.\n* \"10-4\" was already recorded as the item number (G2).\n\n**To go live:** the same publish as #105. Set each agency's phone on Admin \u2192 Systems. If it is empty, the line prints blank.","assets":[],"created_at":"2026-10-06T09:44:01-05:00","updated_at":"2026-10-06T09:44:01-05:00"},{"id":1126,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/101#issuecomment-1126","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/101","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Built in 5e9e9f9. I'm leaving this open until Jane confirms the list below.\n\n**How it works:** there is a new interview setting, `starts_as`. When a visit is started, the question already holds that answer, and the record's own value wins if it has one. The screen says under the question: \"Starts as Yes. Change it if that is not what happened.\" That way it stays clear the supervisor is attesting to it. A pre-set answer that is changed never counts as a difference from VESTA.\n\n**Starts as Yes:** these are the things the supervisor did.\n- Disaster plan: went over the agency policies\n- Task Guide: went over the program's scope, the safety instructions, and when to call the supervisor\n- Things You May Not Do: explained that services are only for the client\n- 3040 orientation: the client's health condition; tasks, schedule and safety; reporting events to the supervisor; the complaint procedure given and explained\n- PAS\/PHC and PCS consents: went over the rights\n- PAS\/PHC consent: client told in writing and in person what the attendant can and cannot do\n- Private Pay: attendant instructions; the Obligation of Client and terms\n- Advance directives: client received the agency's information\n\n**Left blank on purpose:** these record what the client decides or says, and a default there would be the agency answering for the client.\n- Consent to services (all three consents)\n- \"Has the client reviewed and approved the service plan\" (PAS\/PHC and Private Pay)\n- 2-1-1: \"Does the client understand them?\"\n- 3040: \"Has the attendant had HIV \/ hepatitis training?\" (a fact about the attendant, not something explained at the visit)\n\n**Jane:** please confirm both lists. Moving a question between them is a one-line change.\n\n**To go live:** the same publish as #105.","assets":[],"created_at":"2026-10-06T09:44:01-05:00","updated_at":"2026-10-06T09:44:01-05:00"},{"id":1130,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/88#issuecomment-1130","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/88","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Confirmed:** the customer says this applies to every form, not only the service plan.\n\n**Done in b9d8709.** \"How often will the attendant be supervised?\" is no longer asked on any form. Each form now prints the answer itself:\n\n| Form | Prints |\n|---|---|\n| PAS service plan | \"Every __ Months \/ PRN\" box, with 12 |\n| Form 3040 | \"Q 12 month \/ PRN\" |\n| PCS consent | Other, with \"12 months \/ PRN\" written in (the paper only offers 6 months \/ Other) |\n| Private Pay agreement | Other, with \"12 months \/ PRN\" written in (the paper only offers 6 months \/ 60 days \/ Other) |\n\nHow it works: three new fixed shared values in `code\/form_v2_shared.inc.php` (`shared.supervision_frequency`, `shared.supervision_months`, `shared.supervision_frequency_other`). I removed the eight questions from `docs\/form-v2-interviews\/SOC_AO.json`. The service plan's \"Other\" frequency line is never ticked, so it is listed in `not_asked` and prints blank. Tests are in `test-form-v2-shared`.\n\n**To apply:** run `form-v2-pipeline.php --packet=SOC_AO --import --replace`. I've already done this on dev, and the rehearsal passes all nine checks. Then review and publish on `\/admin\/form-v2-publish`, and choose the release on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T09:49:54-05:00","updated_at":"2026-10-06T09:49:54-05:00"},{"id":1133,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/93#issuecomment-1133","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/93","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Decided (Charles, 6 Oct): over is a hard stop; under is allowed, with a warning and a flag on the review page.** Built.\n\n**Over the authorized hours**\n- A schedule change that takes the week over the authorized hours is **not saved**. The supervisor sees why under the day: \"The schedule comes to 22.00 hours a week, 1.00 more than the 21.00 hours authorized. Hours can be moved between days, but the week cannot go over\u2026\"\n- The check lists an over week against the schedule, so **the forms can't be generated** until it fits.\n- If VESTA's own schedule is already over, the visit still opens with it. Any change that brings the week down is accepted, so it can be fixed one day at a time.\n\n**Under the authorized hours**\n- Allowed. The running total beside \"Authorized: N hours a week\" says \"The schedule is X hours under. This is allowed, and the office will see it\u2026\"\n- On **Visit Review** the visit gets a yellow \"Schedule is under the authorized hours\" banner, and the list shows a badge saying \"Hours under authorized\".\n\nAn empty schedule (nothing entered yet) isn't flagged as under. Hours are compared in whole minutes against the pinned `auth.weekly_hours`.\n\nCode: `form_v2_schedule_week()` \/ `form_v2_schedule_total_status()` in `code\/form_v2_visit_rules.inc.php`, enforced in `form_v2_visit_save()` and `form_v2_visit_issues()`. I checked it on dev visit 19 (20 of 21 hours): +2 h was refused, +1 h (exactly 21) was saved and the flag cleared. Nothing needs publishing for this one. It's runtime code and applies on deploy.","assets":[],"created_at":"2026-10-06T10:06:53-05:00","updated_at":"2026-10-06T10:06:53-05:00"},{"id":1134,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103#issuecomment-1134","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Items 1 and 2 from the notes above are built.** Item 3 is still open.\n\n1. **FREQ is a drop-down: QD \u2014 Every day, QW \u2014 Every week, PRN \u2014 As needed.** Each task starts at its default from Frank's table: Laundry and Shopping start as QW, Escort as PRN, everything else as QD. The supervisor can change it. A row is still asked only when the task is in the client's VESTA list (#108). The paper prints the code (QD \/ QW \/ PRN) in the FREQ cell.\n2. **The blank \"other\" row.** \"Does the attendant do any other task that is not listed?\" is always asked, because it covers what VESTA doesn't list. Once a task is named, its frequency drop-down and A\/B\/C ratings appear. It prints on the paper's unlabelled row under \"Assist w. Self-Admin. Med\" (name on the line, code in FREQ, S\/U in the cells), checked on a rendered proof.\n3. **Still to ask the customer:** now that there are sensible defaults, do we still need to *refuse* QW on a daily task, or is the default enough? Nothing refuses it today.\n\nFiles: `docs\/forms\/forms-3040.json`, `docs\/form-placements\/forms-3040.json`, `docs\/form-v2-interviews\/SOC_AO.json`. A mark can now print a code of up to three capitals (it was one letter), with a fit check so a code can't overflow its cell. Imported to the dev drafts, and the publish rehearsal passes all nine checks. It goes out in the pending SOC_AO release with #88\/#101\/#105\/#106: publish on \/admin\/form-v2-publish, then choose the release on \/admin\/form-v2-releases.","assets":[],"created_at":"2026-10-06T10:06:53-05:00","updated_at":"2026-10-06T10:06:53-05:00"},{"id":1138,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103#issuecomment-1138","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/103","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Also fixed while I was in the grid:** choosing **N\/A** printed nothing on the 3040. That covered every A\/B\/C rating cell, including the new other row, plus items 4, 5, 8 and 9. The service plan's \"N\/A\" other-services box had the same problem. The maps now print \"N\/A\" in the rating cells and an X in the N\/A tick boxes, which I checked on a rendered proof. A test now fails any map whose N\/A box would print nothing. This goes out in the same pending SOC_AO release.","assets":[],"created_at":"2026-10-06T10:12:32-05:00","updated_at":"2026-10-06T10:12:32-05:00"},{"id":1139,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/113#issuecomment-1139","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/113","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 7f9771e.\n\n**Cause.** On the PCS consent, the PAS\/PHC consent and the Private Pay agreement, the \"Witness 1\" \/ \"Witness 2\" lines were defined as typed text, even though the paper says the witnesses must *sign* there. Because they weren't signatures, no signing claimed the date beside them, so the interview asked for it. The H2076 already had witness signatures and wasn't affected.\n\n**Change.**\n- `witness_1` \/ `witness_2` on those three forms are now signature fields. Their boxes print a drawn signature and are sized like the release signature (checked on proofs of all three forms).\n- Each witness has their own signing line, and the date beside it is stamped when the witness taps Sign.\n- The 12 witness name and date questions are removed from the SOC\/AO interview. The witness gives their name when adopting a signature.\n- Pipeline fix: the interview import read the *published* placements, so a signing line added in the same import was invisible and its date came back as a question. `form_v2_signing_maps()` now prefers a placement's draft during import.\n\n**Until W4 (#114) is done**, the witness lines are owed on every visit. Staff use \"Record why\" on each one when no witness is needed. #114 is where witnesses become required only for an X signature.\n\n**To apply:** the rehearsal on dev passes all nine checks. Publish on `\/admin\/form-v2-publish`, then choose the release on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T10:22:24-05:00","updated_at":"2026-10-06T10:22:24-05:00"},{"id":1141,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/114#issuecomment-1141","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/114","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in c9e7c29 (builds on #113, 7f9771e).\n\n**How it works.** Whether the client signs with an X is decided on the **signing screen**, when the client takes the pen. It is not an interview question.\n- Until the client has adopted a signature, each client line has a **Client signs with an X** button under its Sign button. It opens the client's adoption with a new **Signs with an X** box ticked. The box is also there when adopting from Sign.\n- **X:** the two witness lines become owed on every form that has them: the PAS\/PHC and PCS consents, Private Pay and the H2076. The witnesses give their name and draw a signature, and the date is stamped by their tap (#113).\n- **No X:** the witness lines say \"Only if the client signs with an X.\", are not counted toward finishing, and print blank. Staff no longer have to \"Record why\" on each one.\n- The choice belongs to that printing. If an answer changes and the forms are generated again, it is asked again.\n\n**Technical.**\n- New column `form_v2_visit_signer.signs_with_mark` (`migrations\/20261006_signer-signs-with-mark.sql`).\n- `form_v2_sign_plan()` makes a witness line apply only when the client's adoption carries the mark.\n- `form_v2_sign_adopt()` refuses the mark for anyone but the client.\n- Tests in `test-form-v2-sign-rules` and `test-form-v2-sign-state`.\n\n**Checked on dev.** SOC\/AO was published there, a visit was printed with private pay, and the client adopted an X: 28 \u2192 32 things to do, and the X buttons went away. Witness 1 adopted, signed and was stamped with the tap's time.\n\n**To apply:** `php scripts\/migrate.php --apply`, then publish #113's SOC\/AO release on `\/admin\/form-v2-publish` and choose it on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T11:10:26-05:00","updated_at":"2026-10-06T11:10:26-05:00"},{"id":1153,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82#issuecomment-1153","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built (commit 85a2957), not yet on production\n\nBuilt as proposed above, from a local copy of the national provider registry, not the live API.\n\n- **On the visit screen:** \"Who is the client's regular doctor?\" now has a **Find the doctor** button. Staff type part of the name. Results nearest the client's ZIP come first, and they can type a different ZIP or city. Each result shows name, specialty, address, phone and fax. Picking one fills the doctor's name, phone and the PAS service plan's **Physician Address**. Everything stays editable, and \"Not listed? Type it in the box above\" covers anyone missing.\n- **Who is searched:** Texas doctors (MD\/DO), nurse practitioners and physician assistants.\n- **What is kept:** the registry entry that was picked is recorded with the visit, encrypted.\n- **Tried on dev** with the latest weekly registry file. A pick fills all three boxes and saves.\n\n**To go live:** run the migration, load the registry once (`scripts\/import-npi.php --fetch --apply`, after which a weekly timer keeps it current), then republish SOC\/AO. Production needs outbound HTTPS to download.cms.gov for the import.\n\n**Still open:** if the customer keeps their own list of doctors, it can be loaded into the same table. Nothing is built for that yet.","assets":[],"created_at":"2026-10-06T11:42:10-05:00","updated_at":"2026-10-06T11:42:10-05:00"},{"id":1154,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99#issuecomment-1154","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/99","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Lookup built (commit 85a2957); \"on file\" prefill not yet\n\n- **On the visit screen:** the Emergency\/Disaster form's pharmacy question has a **Find the pharmacy** button. It searches the same local registry copy as the doctor lookup (#82), nearest the client first. A store's own name is searchable (\"CVS Pharmacy #1234\"), not only the company's legal name. Picking one fills the pharmacy name and phone.\n- **Known pharmacies:** pharmacies this agency has picked before are listed first and marked \"Used before\". With the name box empty, the list shows just those, so the drop-down of known pharmacies needs no separate list.\n- **Equipment supplier:** the DME supplier on the same form got the same lookup.\n\n**Not built:** saving the pharmacy on the client's record and prefilling future visits from it. As noted above, that should happen only after the office accepts the visit, and there is no office-acceptance step yet. In the meantime, the pick is recorded with the visit (encrypted), so it can be carried over once that step exists.","assets":[],"created_at":"2026-10-06T11:42:10-05:00","updated_at":"2026-10-06T11:42:10-05:00"},{"id":1156,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137#issuecomment-1156","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built from the provider registry (commit 85a2957)\n\nThe directory is the same local registry copy as the doctor and pharmacy lookups (#82, #99). The national registry lists agencies with their phone **and fax** numbers, and a weekly update keeps it current, so there is no list for the office to maintain.\n\n**Where the lookup appears:**\n- **Coordination of Care:** each agency under \"Other care the client receives\" has a **Find the agency** button that fills the agency's name, phone and fax:\n  - skilled nursing and home health aide: home health and home care agencies;\n  - therapy: therapy clinics and home health agencies;\n  - dialysis: dialysis centers;\n  - adult day care: adult day care centers.\n- **Transfer form:** the agency the client is transferring from and the agency called. The form has no phone or fax boxes, so only the name is filled.\n\nAgencies this agency has picked before come first, marked \"Used before\". The contact person is still typed, because the registry doesn't list people at an agency.\n\n**For the customer:**\n1. Is putting it on both forms right?\n2. If they have their own list, it can be loaded into the same table alongside the registry.\n3. \"Home health\" here also includes in-home support agencies, where most PAS providers are registered. Please confirm that's what they want.","assets":[],"created_at":"2026-10-06T11:42:10-05:00","updated_at":"2026-10-06T11:42:10-05:00"},{"id":1162,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/93#issuecomment-1162","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/93","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Deployed to production on 6 Oct. A week over the authorized hours is refused, and a week under is allowed and flagged on Visit Review. Closing.","assets":[],"created_at":"2026-10-06T13:25:35-05:00","updated_at":"2026-10-06T13:25:35-05:00"},{"id":1165,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/140#issuecomment-1165","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/140","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built (not yet published).** The client's other care is now **one question**: a single list at the start of *Other care the client receives*. It fills the Coordination of Care and both pages of the service plan (`shared.other_services` in `code\/form_v2_shared.inc.php`). The choices are None, skilled nursing, home health aide, therapy, dialysis, adult day care (DAHS), medical equipment (DME), meals (HDM), emergency response, transportation and other.\n\n**What each paper ticks.** These need the customer's eye:\n- **Coordination of Care:** SN, HHA, therapy, dialysis, adult day care (as ADC) and other. It has no rows for equipment, meals, emergency response or transportation, so those don't print there. Each row is another agency's care with one contact, and its single Other row can't hold several services. \"None\" prints nothing ticked.\n- **Service plan, page 1** (\"Other Service Individual is Receiving\"): nursing and aide both tick **Skilled Home Health Aide**. Therapy, dialysis and equipment tick **Other**, and its \"Other (specify)\" line is asked. None ticks **None**.\n- **Service plan, page 2** (\"Other Services\"): meals tick **HDM** and None ticks **N\/A**. Anything without its own box ticks **Other**, and \"Other (specify name)\" is asked.\n- The adult day care's agency name is also page 2's DAHS name, so it's asked once.\n\nAll the follow-up questions now sit under the list on one step, and the overlap question is hidden when the answer is None. This also settles S5 (#90): no other services prints **None** and **N\/A**.\n\n**Item 4, the suspected mis-binding: not a fault.** Every interview version (1\u20138), the form definition and the placement bind `other_services_receiving` to page 1's \"Other Service Individual is Receiving\". \"What change is needed, and why?\" is the 3040's `change_needed_detail`. It's the question shown when `change_needed` = yes, on the *Attendants* step.\n\n**To ship:** the drafts are imported on dev and the publish rehearsal passes all nine checks. Next: review and publish on `\/admin\/form-v2-publish`, then choose the release on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T13:44:18-05:00","updated_at":"2026-10-06T13:44:18-05:00"},{"id":1167,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/118#issuecomment-1167","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/118","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Most of this was already in place. The gaps are now closed (not yet published).**\n\nAlready built (25 Sep):\n- The agency's details are kept per agency: legal name, toll-free and 24-hour numbers, administrator.\n- Each **branch office's** address, phone, fax and vendor number are kept per branch.\n- Both are maintained by a Superuser at **Superuser Tools \u2192 Systems** (*Branch Offices*), and they fill every form from the client's branch.\n- No interview question asks for any of them.\n\nDone today:\n- **A missing detail is reported.** Nothing marks these fields required, so a missing one printed blank without a word. The visit's check now lists each one that is not on file, with the forms it would print blank on and where to enter it (`form_v2_visit_office_missing()`). Like any check item, it can be skipped.\n- **The 3040's Vendor No. now reads the branch's number**, as the service plan already did. Before, it read the agency's, which is an NPI on file. Which number belongs on which form (vendor or contract) is still #117 (F1).\n\n**Waiting on the customer:**\n1. **The branch details need entering.** Every branch row is blank on the dev database; production not checked. Until they are entered, the service plan and the Coordination of Care print no office address, phone, fax or vendor number, and the check will say so.\n2. **Contract number** prints on no form today. If #117's answer is \"contract number on some forms\", it gets a column beside the vendor number then.\n\nH4 (#94, the coordinator per branch) can live on the same Branch Offices table when it's picked up.","assets":[],"created_at":"2026-10-06T13:44:18-05:00","updated_at":"2026-10-06T13:44:27-05:00"},{"id":1172,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/150#issuecomment-1172","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/150","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in f6e44d8.\n\n**Detection** \u2014 `code\/form_pdf_layers.inc.php`: a scan over qpdf's JSON for layers that start off (`\/D \/OFF`, or `\/BaseState \/OFF`) that the page still uses: a content `\/OC \u2026 BDC` span (including one through a membership dictionary or inside a form XObject), an image\/XObject `\/OC`, or an annotation `\/OC`. A `\/Thumb` is a warning, never a refusal. A layer that is on everywhere passes (`forms-3040` and `things-you-may-not-do` carry `\/OFF []`).\n\n**Where it is caught, all before publishing:**\n- `scripts\/test-form-blanks.php` walks every catalogue blank, plus a real-file fixture and the scan's other shapes. All 15 current blanks pass.\n- `form_v2_template_register()` refuses with a message in words, also for a fingerprint registered before this rule. It fails closed if qpdf is missing. `form-overlay-propose.php` prints the reason and registers nothing.\n- `form-pdf-proof.php` refuses to write the proof that would hide it.\n- The publish proofs (`form-v2-pipeline.php`, `\/admin\/form-v2-publish`) fail the form, so the release is not published.\n\nVerified against the pre-#104 Task Guide: every gate refused it, naming \"Layer 1\" on page 1.\n\n**Cleaning** \u2014 new `scripts\/form-blank-clean-layers.py` (PyMuPDF in venv) merges each page's content streams, cuts the off spans and the thumbnail, and refuses to write unless every page renders pixel-identical. It cleaned the old Task Guide with an identical result. The recipe is in `docs\/form-placements\/README.md`, under \"Hidden layers\".\n\nNote: `things-you-may-not-do.pdf` carries a page thumbnail, which is reported as a note only. I left it alone, because cleaning it changes the fingerprint and needs a re-pin.","assets":[],"created_at":"2026-10-06T13:55:29-05:00","updated_at":"2026-10-06T13:55:29-05:00"},{"id":1179,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137#issuecomment-1179","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Correction to question 2 above: struck.** The customer asked for the registry lookups so that they *don't* keep their own list. The doctor, pharmacy and agency lookups use only the national provider registry, and no customer list will be loaded.\n\nStill open for the customer:\n1. Is putting it on both forms (Coordination of Care and Transfer) right?\n3. \"Home health\" also includes in-home support agencies, where most PAS providers are registered. Is that what they want?","assets":[],"created_at":"2026-10-06T13:58:42-05:00","updated_at":"2026-10-06T13:58:42-05:00"},{"id":1184,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/145#issuecomment-1184","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/145","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in a3133cc.\n\nThe visit screen (`\/visit-v2`) now has a **Client details** button that opens a panel from the right. It shows what the V1 tray showed: status and branch, personal details, care team, authorizations, attendant schedules, assigned tasks, diagnosis, Medicaid eligibility and recent calendar, plus where the signed forms go (#83). Report flags and agency codes are left out, as they were in V1.\n\n- Served by the new `\/do\/formV2VisitClient`, gated like the other visit endpoints. The client is looked up from the visit, never from an ID in the request, and each view is logged as a PHI read.\n- The panel reloads every time it is opened, and has a Refresh button.\n- Built in `hh_client_sheet()` in `code\/hh_client_sheet.inc.php`.\n\nChecked in the browser on dev with the demo client.","assets":[],"created_at":"2026-10-06T14:27:10-05:00","updated_at":"2026-10-06T14:27:10-05:00"},{"id":1186,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/83#issuecomment-1186","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/83","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in a3133cc.\n\nA new step in the visit, **Sending the forms**, comes just before *Check the answers*. It asks for:\n- the client's email, or \"The client has no email\";\n- optionally, a family member or representative: name, relationship, and email and\/or mobile number.\n\nA representative is refused unless the supervisor confirms that the client chose this person. That confirmation is asked again whenever the person's details change.\n\n**Stored on the client record**, as asked: `hh_member.forms_contact_enc`, one encrypted package, plus who saved it and when (`migrations\/20261006-03_member-forms-contact.sql`). The next visit starts from what is on file, and it also shows in the Client details panel (#145).\n\n**Not an interview question, on purpose.** No form prints it, and the interview only asks what the forms carry. A V2 visit answer also never writes back to the client record. So this is an explicit save of the client's details through `\/do\/formV2VisitContact`. The save is logged without the values.\n\nRules are in `code\/hh_member_contact_rules.inc.php` and tested standalone by `scripts\/test-member-contact-rules.php`.\n\n**Still to do:** sending the forms by email or text is not built yet.\n\n**To apply on production:** `php scripts\/migrate.php --apply`","assets":[],"created_at":"2026-10-06T14:27:10-05:00","updated_at":"2026-10-06T14:27:10-05:00"},{"id":1190,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111#issuecomment-1190","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Built in ce30deb. On dev, not published yet.\n\n**What the forms actually have.** No signature line on the SOC\/AO forms is only for a representative: every client line reads \"Client \/ Authorized Agent\". The \"signing for the client\" content is two typed lines on both consent forms and the Private Pay agreement: the signer's authority (`shared.release.authority`), and why the client cannot sign (`shared.consent.unable_to_sign_reason`).\n\n**What changed**\n- The interview asks **\"Is the client signing the forms themselves?\"** (`visit.client_signs`). It starts at Yes, and the supervisor can change it.\n- The authority and the reason are asked only when the answer is **No**. On Yes they are hidden, and a hidden answer is never printed, so those lines stay blank.\n- Signing must agree with the answer. On Yes, adopting a signature as \"someone signing for the client\" is refused. On No, adopting as the client is refused. The adoption dialog offers only the matching choice. Visits prepared before this release are unaffected.\n- Why this is in the interview rather than at signing, as the X was in #114: these lines are printed before anyone signs.\n- Fixed along the way: loading the interview file silently dropped any question that no form prints. It now keeps the ones the file asks for.\n\n**Checked:** the related tests pass, including new ones for the signing rule and the question loading. The SOC\/AO publish rehearsal shows one new question and two now conditional, and passes all nine checks. Not tested yet: clicking through it in a browser, and refusal of a contradicting signature at the database level. Only the underlying rule has a test.\n\n**To make it live:** review and publish on `\/admin\/form-v2-publish?interview=SOC_AO_V2`, then choose the release on `\/admin\/form-v2-releases`. This publish also carries the #140 and #118 interview changes.\n\n**Open point:** the question starts at Yes, which treats \"who is signing\" as something the supervisor observes rather than a decision the client makes. A wrong Yes is caught at signing. If it should start blank instead, that is a one-line change.","assets":[],"created_at":"2026-10-06T14:41:03-05:00","updated_at":"2026-10-06T14:41:03-05:00"},{"id":1194,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111#issuecomment-1194","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Open point settled (owner, 6 Oct 2026).** \"Is the client signing the forms themselves?\" starts at **Yes**. **No** means someone else signs for the client. There is no third answer: a client who refuses to sign has no interview, so refusal is never a route through it.\n\nThe build already works this way, so no code changed. The ruling is recorded in `docs\/FormsV2Plan.md`, the interviews README and the Changelog (96356f4).\n\nWhat is left is to publish the release on `\/admin\/form-v2-publish?interview=SOC_AO_V2` and choose it for each agency on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-06T14:43:16-05:00","updated_at":"2026-10-06T14:43:16-05:00"},{"id":1197,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111#issuecomment-1197","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/111","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Live: deployed, and the SOC\/AO release is published and chosen for each agency. A Start of Care visit prepared from now on asks \"Is the client signing the forms themselves?\" (starts at Yes). Visits prepared earlier keep their old interview. Closing.","assets":[],"created_at":"2026-10-06T14:45:48-05:00","updated_at":"2026-10-06T14:45:48-05:00"},{"id":1199,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/143#issuecomment-1199","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/143","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Audit of PHI read logging across Forms V2 (2026-10-06)\n\nChecked every `backend\/formV2Visit*.php` endpoint and the three V2 pages for whether a response that carries client data records `audit_phi_view()` (or an equivalent).\n\n### Already in place\n| Path | Records |\n|---|---|\n| `\/visits-v2` (preset client, open-visit list) | `phi.member.view` |\n| `\/visits-v2-review` (one visit, list) | `phi.member.view` |\n| `formV2VisitOpen` (the full visit payload: every answer) | `phi.member.view` |\n| `formV2VisitClient`, `formV2VisitContact` (read), `formV2VisitMemberSearch` | `phi.member.view` |\n| `formV2VisitSignState` | `phi.member.view` |\n| `formV2VisitDocument` (PDF open\/download) | `phi.document.open` inside `document_version_stream()`, so it can't be skipped |\n| Prepare, Print (artifact create), Abandon, sign adopt\/tap\/withdraw\/finish | `data.form_v2.*` write events carrying `fk_hh_member` |\n| `formV2VisitBlank`, `formV2TemplatePage` | none needed: a blank form carries no client |\n| `\/visit-v2` page | renders no client data server-side; it loads everything through `formV2VisitOpen` |\n\n### Gaps\n1. **`formV2VisitSave` on a conflict**: returns `current`, the decrypted stored values of the answers someone else changed (`form_v2_visit.inc.php:703-707`). This is a PHI read with no view event. It's a rare path, but it's a real one.\n2. **`formV2VisitSign`, `formV2VisitSignAdopt`, `formV2VisitSignFinish`**: each returns the full `form_v2_sign_view()`: the client's name, every signer's and representative's name, and decrypted refusal reasons. `SignState` logs the identical payload as a PHI view; these three don't. A successful write leaves a `data.form_v2.sign.*` event with `fk_hh_member`, but two paths leave nothing at all:\n   - a **replayed** request (idempotent retry), which returns the view without writing;\n   - a **failed** `SignFinish`, which still returns the view (`formV2VisitSignFinish.php`: the view is built before the `ok` check).\n\n### Judgment calls (low; recommend logging anyway for consistency)\n3. **`formV2VisitCheck`**: returns issues and progress. The messages are mostly question labels, but the schedule-step messages echo the client's hours.\n4. **`formV2VisitPlan`**: decrypts the client's facts (`form_v2_visit_plan()` documents `facts` as PHI) and returns which forms that client needs, which is derived from their program, language and so on. It takes a `pk_hh_member` straight from the request (scope-checked).\n5. **`formV2VisitLookup`**: when no `near` is sent, returns the client's ZIP from the visit as `near`.\n\n### Proposed fix\nAdd `audit_phi_view('<endpoint>', ['pk_hh_member' => \u2026, 'fk_system' => \u2026, 'pk_form_v2_visit' => \u2026])` to items 1\u20135, placed only on the branches that return the data, matching `SignState`\/`Open`. A guard test in the style of `test-backend-auth-guards.php` (every `formV2Visit*` endpoint either calls `audit_phi_view` or is listed as carrying no PHI, with the reason) would stop a new endpoint from shipping without one.","assets":[],"created_at":"2026-10-06T14:51:15-05:00","updated_at":"2026-10-06T14:51:15-05:00"},{"id":1200,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/143#issuecomment-1200","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/143","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 61d7518 (committed, not yet pushed).\n\nAll five items from the audit above now record `phi.member.view`, each only on the branch that returns the data:\n- `formV2VisitSave`: on a conflict that returns `current`, with the number of answers returned.\n- `formV2VisitSign`, `SignAdopt`, `SignFinish`: whenever the signing view goes back, including replayed requests and a failed finish.\n- `formV2VisitCheck`, `formV2VisitPlan`: on success.\n- `formV2VisitLookup`: only when `near` is the client's ZIP, not a place the supervisor typed.\n\n**Guard:** `scripts\/test-form-v2-phi-audit.php`. Every `backend\/formV2Visit*.php` must call `audit_phi_view()` or be listed with the reason it carries no PHI. Listed today: Abandon, Prepare, Print, Blank, and Document, which is logged by `document_version_stream()`. Any endpoint that calls a PHI source (`form_v2_sign_view`, `form_v2_visit_payload`, `form_v2_visit_values`, `form_v2_visit_check`, `form_v2_visit_plan`, `db_decrypt`, \u2026) must log, whether or not it is listed. Run against the pre-fix code, it fails on all seven files; it passes now. `test-backend-auth-guards.php` still passes.\n\nThe test proves a call is present, not that it sits on the right branch; that part is still a review question.\n\nLogged in `docs\/Changelog.md`. No user-facing change, so `docs\/UserChangelog.md` doesn't need an entry.","assets":[],"created_at":"2026-10-06T14:55:22-05:00","updated_at":"2026-10-06T14:55:22-05:00"},{"id":1204,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/149#issuecomment-1204","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/149","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in a951d22.\n\n- **Import:** `form-v2-pipeline.php` runs `import-form-packets.php --only={PACKET}` before anything else, because the packet decides which forms are imported. The importer is now idempotent. A file that matches the draft, or the published version when there is no draft, writes nothing, and a dry run reports \"unchanged\" or \"would update\". The run and the interview import read the packet **about to be** published (`form_packet_pending()`, `form_v2_packet_form_codes(\u2026, $pending)`). A form the file adds is therefore imported, placed and pinned in the same run. A dry run also lists forms named only in the file.\n- **Publish:** the order is forms \u2192 **packet** \u2192 maps \u2192 interview \u2192 release, all in one transaction. The packet goes through `form_packet_validate_definition()` with `form_packet_validation_ctx($db, $pk, true)`, so the install-wide item-key rule is still checked across every other published packet. It is written by `form_packet_publish_draft()`. A refusal stops the release, and nothing is kept.\n- **Check:** new pure `form_packet_def_diff()` covers forms added or removed, lines added, removed or renamed, required pages, conditions, choices and order. It feeds the packet step's changes and a new `packet_diff`, measured against the last release's pin. `\/admin\/form-v2-publish` shows it under \"What changed \u2192 The visit type\".\n- **Release:** the packet pin is part of the manifest, so a packet change alone makes a new release and an unchanged packet does not.\n\nTests: `test-form-packet` (diff) and `test-form-v2-pipeline-state`. The state test covers three cases. A draft identical to the published packet is not a change. A changed draft publishes in order into a new release, and the rehearsal keeps nothing. A draft the gate refuses stops the release. All form V2 tests pass. No schema change.","assets":[],"created_at":"2026-10-06T15:07:39-05:00","updated_at":"2026-10-06T15:07:39-05:00"},{"id":1278,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82#issuecomment-1278","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/82","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Live on production. Closing.** \"Find the doctor\" fills the doctor's name, phone and the service plan's Physician Address from the local copy of the national provider registry. The open question is settled: the customer asked for this lookup so they would not have to keep their own list of doctors, so no customer list will be loaded (same ruling as #137).","assets":[],"created_at":"2026-10-07T10:35:44-05:00","updated_at":"2026-10-07T10:35:44-05:00"},{"id":1281,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-1281","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nStandardize on \"Client Name\". When there are shared questions or answers, show the fuller version if it's one of the options. Male instead of M. Service tasks show \"1 - Bathing \/ Ba\u00f1ar\". Emergency priority class shows the paragraph.","assets":[],"created_at":"2026-10-07T10:49:46-05:00","updated_at":"2026-10-07T10:49:46-05:00"},{"id":1287,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/155#issuecomment-1287","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/155","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nUse the term \"Shared Answers\" instead of pooled in our documentation, form explorer, etc.","assets":[],"created_at":"2026-10-07T11:21:38-05:00","updated_at":"2026-10-07T11:21:38-05:00"},{"id":1288,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/153#issuecomment-1288","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/153","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nUse \"Shared Answers\" instead of \"pooled\"","assets":[],"created_at":"2026-10-07T11:22:34-05:00","updated_at":"2026-10-07T11:22:34-05:00"},{"id":1294,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/152#issuecomment-1294","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/152","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 3635af5. The list of steps beside a visit now names, under each step, the forms that step's questions print on (only the questions this visit actually shows). Titles that start \"Form \u2026\" are shortened to the number (\"Form 3040\", \"Form H2076\"); hovering shows the full names.\n\nOne open point: a step like \"Confirm the client\" reaches 10 forms, so its list is long. Shorter names for every form would need a short title added to each form definition. Say if you want that.","assets":[],"created_at":"2026-10-07T11:48:12-05:00","updated_at":"2026-10-07T11:48:12-05:00"},{"id":1295,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/153#issuecomment-1295","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/153","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 3635af5. In the Form Explorer:\n\n- A question with a provider-directory lookup carries a \"Finds the doctor\" (pharmacy, agency\u2026) badge; every answer a pick fills carries \"From the lookup\".\n- Shared Answers carry a \"Shared Answer\" badge, on questions, on every box under \"Forms and paper\", and on the Shared Answers tab.\n- Opening a question spells out the relationship with links both ways (e.g. the doctor's pick fills the phone number into question 20; a Shared Answer links to its row on the Shared Answers tab).\n\nSOC\/AO currently has 10 lookups filling 14 answers, and 22 Shared Answer questions.","assets":[],"created_at":"2026-10-07T11:48:12-05:00","updated_at":"2026-10-07T11:48:12-05:00"},{"id":1296,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/154#issuecomment-1296","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/154","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 3635af5. The Form Explorer has a \"Show only\" list at the top (the coloured counts are also clickable) that narrows every tab to one kind of answer: asked, prefilled, agency \/ branch \/ fixed, worked out, signing, nothing fills it, plus Shared Answers and provider lookups.\n\nSince agency \/ branch \/ fixed values are never asked, they have no questions; \"Forms and paper\" now has an **All forms** entry that lists every matching box across all forms in one table (36 for agency \/ branch \/ fixed on SOC\/AO). On the paper view, boxes outside the filter are faded. The choice is kept in the address (`?show=agency`, `?show=prefill`), so a filtered view can be linked.","assets":[],"created_at":"2026-10-07T11:48:13-05:00","updated_at":"2026-10-07T11:48:13-05:00"},{"id":1297,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/155#issuecomment-1297","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/155","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Done in 3635af5. \"Pool\" \/ \"pooled\" is replaced by **Shared Answers** in the Form Explorer (tab, badges, wording), the packet inventory (\"Shared Answer\"), the code comments and the docs (FormsV2Plan \u00a74.1.1 and the project notes now state the term).\n\nLeft as they are on purpose: names from the retired version 1 forms (the `pool` key in form definitions, the old pool tables and `form_pool_*` functions), so V1's mechanism isn't confused with V2's Shared Answers.","assets":[],"created_at":"2026-10-07T11:48:13-05:00","updated_at":"2026-10-07T11:48:13-05:00"},{"id":1340,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/164#issuecomment-1340","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/164","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Decided (Charles, 7 Oct 2026):**\n\n- **Translated paper gets its own map.** A Vietnamese 3040 PDF gets its own placement map onto the same 3040 fields. The answers stay the same and only the paper differs.\n- **Labels follow the paper's wording, per language** (#77). A bilingual line such as \"Bathing \/ Ba\u00f1ar\" becomes English \"Bathing\" plus Spanish \"Ba\u00f1ar\". The check that the paper's text is reachable on screen must cover every language the paper prints.\n- **The client's language turns the second language on automatically.** When the client's language (#158) has a translation, the visit opens with that language shown beside English. Staff can still switch it off, or switch on another language that has a translation.\n\nStill open: whether the translations live inside the definition or in a separate file beside it, and how translations are versioned and published.","assets":[],"created_at":"2026-10-07T13:49:42-05:00","updated_at":"2026-10-07T13:49:42-05:00"},{"id":1345,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/156#issuecomment-1345","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/156","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Where the code stands today\n\nPer visit type, part of this is already handled and part is not.\n\n| Varies by visit type | Today |\n|---|---|\n| **Which forms** | \u2705 The packet (`docs\/form-packets\/*.json`) decides, including `when` choices (PAS\/PHC vs PCS consent on the program). |\n| **Which questions, order, wording, conditions** | \u2705 The structure allows it: there is one interview file per packet (`docs\/form-v2-interviews\/{PACKET}.json`). But **only `SOC_AO.json` exists**. The other 14 packets have no interview yet. |\n| **Answer choices** | \u26a0\ufe0f Only partly. Choices come from the form definition, which is global. An interview can relabel a choice and add `option_info`, but it can't offer a **subset** of the choices. |\n| **Fixed answers (never asked)** | \u274c Not per visit type. A `derived` + `fixed` element in `code\/form_v2_shared.inc.php` is **install-wide**. Examples are supervision level = Non-RN and frequency = Q12\/PRN. Nothing lets a fixed value depend on the visit type. |\n| **Fixed answers that are *shown*** | \u274c A `fixed` element is never shown. A step's `shows` can only list values pinned from the record (`auth.*` etc.), not derived ones. |\n\n### Concrete cases already in the files\n\n- **Client Information Sheet `visit_type`** (Start of Care \/ Annual Reassessment) is currently *asked* in SOC_AO, with help text \"almost always Start of Care\". It should be fixed by the visit type: SOC_AO \u2192 `soc`, AR \u2192 `ar`.\n- **3040 `attendant_{a,b,c}_visit_type`** (AO \/ SV \/ SA) is asked once per attendant. It is probably fixed by the visit type (SOC_AO \u2192 AO, SV \u2192 SV\u2026). There may be an exception when one attendant is new and another is being supervised.\n- **3040 `visit_setting`** has help text \"Tick it at a first visit\", so it is also visit-type dependent.\n\n## Ideas\n\n1. **Make the visit type a fact.** Add a pinned `visit.packet_code` (or `visit.type`) to `form_packet_facts()` \/ the visit's pins. Then every existing mechanism can key off it:\n   - conditions: `{\"==\": [{\"field\": \"visit.type\"}, \"AR\"]}`\n   - `shows` can show it (\"This is an Annual Reassessment\")\n   - a derive can map it, e.g. `derive: {op: \"map\", from: \"visit.type\", values: {SOC_AO: \"soc\", AR: \"ar\"}}`\n\n   This reuses the existing `map` op (#105) instead of inventing a new concept.\n2. **Fixed answers per visit type: choose the file that declares them.**\n   - **(a)** Use a `map`-from-visit-type derive in `form_v2_shared.inc.php` (the idea above). Everything stays in one place, but that file starts knowing about packets.\n   - **(b)** Add a `fixed` block to each interview file (`\"fixed\": {\"form.client-information-sheet.visit_type\": \"soc\"}`), with an optional `\"show\": true` per entry for answers the supervisor should see. This keeps \"what this visit type does\" in one file per visit type, as interviews are now.\n\n   I lean towards **(b)** for anything that varies per visit type. I'd keep `form_v2_shared` `fixed` for things that never vary at this customer. The publish check should refuse a key that is both fixed in the interview and asked in it.\n3. **Shown vs hidden fixed answers:** extend `shows` to accept a fixed\/derived element (read-only, in words, as pinned values are shown now). Anything not listed there stays silent and only prints.\n4. **Choice subsets:** allow an interview question to carry `\"only\": [\"ao\", \"sv\"]`. The rule would be that it may narrow a form's choices but never add one, the same principle as \"`required` can only be made stricter\". If only one choice is left, it becomes a fixed answer.\n5. **Write the next interview to test the design.** AR is the obvious one, since it shares most forms with SOC_AO. Writing `AR.json` will show exactly which SOC_AO questions turn into fixed or shown answers, and which choices narrow.\n\n## Questions\n\n1. **Combined visits.** V2 runs **one packet per visit** today, even though `form_packet_merge()` can union several. Will staff ever do two visit types at once (e.g. AR + SV, or SOC + a transfer)? If so, two visit types may fix the same box differently (CIS `visit_type` = soc vs ar). We'd need a precedence rule, or the publish check refuses the combination. Interviews would also need merging, which nothing does yet.\n2. **Which fixed answers should the supervisor see?** Is the rule \"show anything the client is told or signs\" and hide administrative boxes? Or does the customer want to list them one by one?\n3. **3040 attendant visit type:** is it always the same for every attendant on a visit, or can one attendant be AO while another is SV on the same visit?\n4. **Is there a list from the customer** of per-visit-type differences, or do we derive it from the packets and the paper? The `Compliance RVSD. 5.13.2025` checklist names the forms but not the answers.\n5. Should a supervisor ever be able to **override** a fixed answer (e.g. a visit started as the wrong type)? Or is the answer always \"start the right visit type\"? I'd suggest the latter, so the paper can't disagree with the visit record.","assets":[],"created_at":"2026-10-07T13:54:38-05:00","updated_at":"2026-10-07T13:54:38-05:00"},{"id":1347,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-1347","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Wording decision applied (7 Oct 2026, dev)\n\nThe rule is now the customer's: **where the forms word a question or a choice differently, the screen shows the fullest wording.** \"Fullest\" means the longest. If two are the same length, the first form's wins. This covers all 40 questions and the 3 choice lists that were waiting. Our plain question stays underneath as \"Ask: \u2026\".\n\n**What the questions are now called**\n\n| Question | Label now |\n|---|---|\n| Client's name | **Client Name** (the customer's standard. The longest wording was a full sentence.) |\n| Date of birth | Date of Birth |\n| Medicaid number | Client \/ Patient's Number |\n| Phone | Telephone No. |\n| Address | Individual's Address |\n| Physician \/ phone | Name of Physician Individual Sees Regularly \u00b7 Physician Telephone No. |\n| Sex | Gender |\n| Emergency priority class | What category describes your special needs? |\n| First emergency contact | First Emergency Contact \u2014 Contact Person \/ Relation \/ Phone Number |\n| Service start date | Start Date |\n| Attendant names | Attendant A \u2014 Name (B, C) |\n| Schedule cells | Attendant A \u2014 Monday \u2026 (the full day name) |\n| Other services | Other Service Individual is Receiving |\n| Adult day care name | Adult Day Care \u2014 Agency Providing Care |\n| Level of supervision | Level of Supervision |\n\n**Choices**\n- Sex: **Male \/ Female**.\n- Emergency priority class: **the disaster plan's paragraph** for each class.\n- Service tasks: **\"1 - Bathing \/ Ba\u00f1ar\"**, on the question and on the read-only task lists. This one is set by name to the PAS Service Plan's wording. The Task Guide's headings are sometimes longer (for example \"06\/07 \u2014 Toileting (does not include transfer and ambulation)\"), so the longest-wording rule alone would have mixed the two.\n- Other services: None, Skilled Nursing Services, Home Health Aide, Day Activity and Health Services (DAHS), Meals, \u2026 One fix was needed here. The PAS Service Plan has a single \"Skilled Home Health Aide\" box that covers both skilled nursing and an aide, and an \"Other\" box that covers six more. A box like that no longer counts as the wording for any one choice. Without this, skilled nursing would have been labelled \"Skilled Home Health Aide\".\n\n**Two results worth a second look.** Both come from the rule, not from a choice we made:\n- **Medicaid number \u2192 \"Client \/ Patient's Number\"**. \"Medicaid #\" is on 3 forms and is shorter. If the customer wants \"Client Name\"-style consistency, this could be standardised the same way, for example to \"Medicaid #\".\n- **Sex \u2192 \"Gender\"**. Only the Client Information Sheet says \"Gender\"; the other two forms say \"Sex\". It wins only because it is longer.\n\nEither one would be a single entry in `FORM_V2_STANDARD_WORDING`.\n\nPublished on dev as release **SOC_AO-20261007** (interview v8). It is not chosen for any agency yet. Technical: `form_v2_paper_wording()` \/ `form_v2_paper_choices()`, FormsV2Plan \u00a74.1.3, Changelog 2026-10-07.","assets":[],"created_at":"2026-10-07T13:57:55-05:00","updated_at":"2026-10-07T13:57:55-05:00"},{"id":1459,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165#issuecomment-1459","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165","user":{"id":3,"login":"connect-bot","login_name":"","source_id":0,"full_name":"","email":"connect-bot@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/68a2723f7ad70dee2e1415be7488d84b","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/connect-bot","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2026-10-01T08:49:35-05:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"connect-bot"},"original_author":"","original_author_id":0,"body":"**Charles W.** _(via Connect)_\n<!-- connect:user=1 -->\n\nThere may be a modal pop-up if a change is made just to confirm.","assets":[],"created_at":"2026-10-07T16:23:23-05:00","updated_at":"2026-10-07T16:23:23-05:00"},{"id":1482,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165#issuecomment-1482","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Where this stands today\n\nMost of the safety rule is already in place on the server. It runs in the same transaction as the save:\n\n* **A changed answer withdraws the generated forms and every signature on them.** In `form_v2_visit_save()` (`code\/form_v2_visit.inc.php`), any accepted change calls `form_v2_visit_supersede_candidates()`. The candidate becomes `superseded`, and every tap on it stops counting. The taps are kept as the record. An unchanged answer is not counted as a change, so re-saving the same value withdraws nothing.\n* **Nothing can be signed against old copies.** Every sign, adopt and finish request checks `form_v2_candidate_is_current()` under the visit lock, and a superseded printing is refused. This is pinned in `scripts\/test-form-v2-sign-state.php` (\"a change withdraws\").\n* **The forms are always generated again from the current answers.** Signing stamps only the kept review copy, which is proved against its hash. There is no way to sign a copy that does not match the answers.\n* **A signed visit takes no more answers** (`closed` \/ `signed` refusal).\n\nWhat is missing is all on the person's side:\n\n1. **No warning before the change.** The page says *afterwards*: \"An answer changed after the forms were generated, so those copies, and the signatures on them, were withdrawn.\" By then the signatures are already gone.\n2. **Later sections are not flagged** when the change reveals new questions in them. That is #166, and I'd build it there (see that issue) rather than twice.\n3. **A signed visit cannot be corrected at all.** That needs amendments (FormsV2Plan \u00a78.5), which are not built.\n\n## Proposed plan\n\n**A. Lock the answers once forms are generated, and confirm before unlocking** (the modal from the comment above).\nWhile a current printing exists, the questions show read-only, with a **Change an answer** button. That button opens a modal:\n\n> The forms have been generated *(and N lines have been signed)*. Changing an answer withdraws these copies and their signatures. You will generate the forms again, and everyone signs again.\n> **[Change answers]  [Keep the forms]**\n\nI'd use a lock rather than catching the first keystroke. The answers autosave as they are typed, so a modal fired by an `input` event would interrupt typing and is easy to dismiss by accident. Unlocking withdraws nothing by itself. Only the first actual saved change does, so a person who unlocks and then changes nothing keeps the forms.\n\n**B. Enforce the confirmation on the server, not only in the page.**\nWhile a current candidate exists, a save must carry `withdraw_candidate = <pk>` naming it. Otherwise it is refused with code `printed` (and the page shows the modal). This stops a stale tab, a retried request or a future client from silently wiping 36 signatures. The lease already allows only one writing window, so this is a second guard, not the only one.\n\n**C. After the change, say what to do next.** Keep the existing withdrawal notice. Add, once #166 exists, \"New questions appeared in: *Emergencies*, *Coordination of Care*\" with links. The Generate button stays available, but see question 3.\n\n**D. Tests:** a save without the acknowledgement is refused while a candidate is current and accepted when there is none. A save with an acknowledgement naming an old candidate is refused as stale.\n\n## Questions\n\n1. **Does any change withdraw everything?** Today it does: one typo in a notes field means a new printing and every line signed again. The plan (\u00a76) chose that for the first release. The alternative is to keep the taps on forms whose generated copy is **byte-identical** to the one that was signed, so only the forms the change actually reached need signing again. That is safe because a tap is bound to the copy's hash, but the candidate digest currently binds the whole set, so it is a real change. **My recommendation: keep \"everything again\" for the pilot, and revisit if staff complain.**\n2. **Is correcting a *signed* visit in scope here, or a separate issue?** That is amendments (\u00a78.5): a new revision, the old signed copies kept, new copies signed. I'd make it its own issue, after submission.\n3. **Should Generate forms be blocked while a section has new questions nobody has opened** (#166)? Or should it warn and allow? I lean towards **block**, because the answers are printed exactly as they stand.\n\n","assets":[],"created_at":"2026-10-07T23:17:03-05:00","updated_at":"2026-10-07T23:17:03-05:00"},{"id":1484,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/166#issuecomment-1484","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/166","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Where this stands today\n\nThe visit sidebar (`renderSteps()` in `pages\/visit-v2.php`) shows each section with an **answered \/ shown** count. Nothing records which sections the filler has actually opened. The visit stores only `current_step`. So when an answer reveals new questions in a later section, the only sign is a count changing from 4\/4 to 4\/6, which is easy to miss. The Check step lists missing *required* answers, but an optional question that newly appears is never pointed out at all.\n\n## Proposed plan\n\n**Track what has been shown, per question, on the server.** Do not track it per section, and do not track it in the browser.\n* Record a question as **seen** when its section is open and the question is visible at that moment. The page already saves on every step change (`pendingStep`), so the save carries the visible question keys of the section being left and the server records them. Question keys are not PHI. I'd use a small table, `form_v2_visit_seen (fk_form_v2_visit, question_key, first_seen_at, seen_by)`, through a migration. It lives on the server so a visit resumed on another iPad, or after a reload, keeps it.\n* **Waiting** = visible now, not yet seen, and not answered. The server computes it beside `form_v2_visit_progress()` (pure, in `form_v2_visit_rules.inc.php`) and returns it per section with the progress.\n* **The re-flag from the description comes for free.** A question revealed by a later answer was never seen, so its section lights up. If the answer is changed back and the question hides again, the flag goes away. If it was seen once, hidden, then shown again, it stays seen. See question 2.\n\n**On screen:**\n* The sidebar shows a dot and \"*N new*\" on a section with waiting questions, beside the existing count. Inside the section, the waiting questions carry a small **New** label until the section is left.\n* After a save that reveals questions in *another* section, show a short notice: \"This answer added questions in *Emergencies*\", with a link.\n* The Check step lists \"Sections with questions you haven't seen\" above the issues, each with a link.\n\n**Tests:** pure rule cases (seen\/visible\/answered combinations; hidden then revealed), plus a state test showing the seen set survives a reload and that a step change records only that section's visible questions.\n\n## Questions\n\n1. **Should a question that is already filled in from VESTA count as waiting if nobody has opened its section?** Its value came from the record, not from the client. My recommendation: **yes**. \"Seen\" means the filler looked at it, and a prefilled authorization answer is exactly the kind that may be out of date. The flag would then mean \"not looked at\", not \"not answered\". The catch: on a fresh visit every section starts flagged, which is arguably right.\n2. **Seen once, hidden, then revealed again with its old answer:** flag it again? The plan (\u00a76) says a reactivated branch \"requires review before those saved answers become current again\". That suggests **yes**, so I'd clear \"seen\" when a question hides. Please confirm.\n3. **Does a waiting question block Generate forms?** I'd **block**, with the list of sections as the reason. Otherwise forms can be printed with a question nobody asked. The alternative is a warning the filler can override. This is the same decision as question 3 on #165.\n\n#165 depends on this for its \"revisit these sections\" half, so I'd build this one first.\n\n","assets":[],"created_at":"2026-10-07T23:17:03-05:00","updated_at":"2026-10-07T23:17:03-05:00"},{"id":1506,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/151#issuecomment-1506","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/151","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Answered: yes, VESTA shows more than one time span per day for an attendant, and we already handle it** (Changelog, 2026-08-17 \"the `schedule` prefill namespace\").\n\n* In VESTA, a split shift is several rows for the same attendant and day. `form_prefill_day_spans()` (`code\/form_prefill.inc.php`) puts them into one day cell, in time order.\n* It drops a shift that repeats across the client's authorizations. Where two spans conflict (for example 06:00\u201311:00 on one authorization and 06:00\u201312:00 on another), it keeps the longer one.\n* When this was built it was measured against the live data: 31,364 day cells, every one readable. **1,608 held more than one span, up to 5** (one real client's Attendant B has five on a Tuesday). 149 conflicted, all of the kind just described. So there is no cap at 4.\n* Forms V2 uses the same code. The visit's facts come from `form_packet_facts()`, which reads `form_prefill_sources()`, which builds the schedule from these spans. Each `schedule.attendant_N_<day>` answer is a list of spans. The visit page shows and totals every span, and the quarter-hour rule (#91) and the weekly-hours limit (#93) count each one.\n* Tests: `scripts\/test-form-prefill.php` (61 passing) covers a split shift landing in one cell, duplicates removed, conflicts, several spans typed into one cell separated by commas, and unreadable entries dropped.\n\nClosing. If you want the figures re-measured against today's data (the counts above are from August), reopen it.","assets":[],"created_at":"2026-10-07T23:22:45-05:00","updated_at":"2026-10-07T23:22:45-05:00"},{"id":1510,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/167#issuecomment-1510","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/167","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Thinking it through: emergency priority list by area\n\n**Short version.** We can ship a useful first version **without a map**, using VESTA fields we already load and the county we already hold. After that comes geocoding with the local Nominatim, plus a map you can select from by county, polygon or radius. Last, the answers collected on the V2 Emergency\/Disaster form take over from VESTA's coarser fields. The priority should be **tiers with visible reasons, not a single score**, and the rules belong to the customer.\n\n---\n\n### 1. What VESTA already gives us\n\n\"Bed-ridden\" is not a VESTA field. Several fields come close, and all of them already land in `hh_member_record.detail_enc` on the report path (Members export \/ Master List):\n\n| Field | What it tells us | Notes |\n|---|---|---|\n| `cliclass` | HHSC client classification (Class I = priority) | The closest thing VESTA has to an emergency tier. Our glossary text for Class III\/IV (`code\/glossary.inc.php`) is a guess and should be checked with the customer before anything ranks on it. |\n| `priority` (export) \/ `Priority` bool (API roster) | Priority-client flag | The API roster returns `Priority`, but it isn't in `VESTA_MEMBER_CONTRACT`. Adding it as a tolerated key costs one line. |\n| ADL flags + `freq_*`: `transfer`, `ambulation`, `walking`, `toileting`, `feeding_eating` | Mobility proxy: transfer + ambulation + toileting all assigned is roughly \"cannot leave without hands-on help\" | These are *tasks on the plan*, not a mobility assessment. Label them \"needs help with transfer \/ walking\", never \"bed-bound\". |\n| `dnr` | DNR on file | Responders need to know it. It is not a priority input. |\n| `able_to_respond` | Language, or \"Unable to Respond\" | \"Unable to respond\" should raise priority: the client can't answer a welfare call. |\n| `county` (Master List; MemberPlus for API) | Area selection **without geocoding** | Already a plaintext column on `hh_member_record`. |\n| `diagnosis` (Master List, free text) | Words like oxygen, dialysis, ventilator, insulin | Free text. Use it only as a \"check this\" hint, never as a rank input. |\n| `hh_auth` weekly hours \/ program | Heavier care plans | Secondary signal. |\n| `hh_calendar` \/ `sp-schedule` | Who has a visit in the next 24\u201372 h, and which attendant | Covers \"whose care is interrupted\" and \"which attendants to call\". |\n| Emergency contact, phone, address | The call list itself | Already stored (encrypted). |\n\n\u26a0\ufe0f **API cutover gap.** The live API roster carries **no** `cliclass`, `DNR`, ADL pairs, `ABLE_TO_RESPOND` or `County` (VestaAPIPlan \u00a73.1). The Master List enrichment brings back the ADL flags and county. `cliclass` and `DNR` only come back through **MemberPlus**: one call per member, throttling not yet known (Q7\/Q8). If this feature leans on `cliclass`, an agency moving to the API loses it. We have to decide this **before cutover**, not after.\n\n**First step: measure.** How many open clients per agency actually have `cliclass`, `priority`, the ADL flags and `county` filled in? I tried to count this from the local DB, but that means decrypting `detail_enc`, and I didn't do it without your OK. The count should be a read-only script that prints aggregate numbers only, run where we're allowed to read PHI.\n\n### 2. What the forms add (V2), and what they could add\n\nThe **Emergency \/ Disaster Preparedness Plan** (`emergency-disaster-form`) is the actual triage instrument. It already asks:\n\n- `disaster_class` I\u2013IV: the state's emergency scale. Class I is \"unable to withstand any interruption in power supply\u2026\". The answer is shared with the CIS `classification` as `shared.client.emergency_class`, and the CIS version adds \"Bed confined \/ Ambulatory\".\n- `special_needs` (yes\/no), `has_transportation` + `transportation_by_whom`, `evacuating_with_count`, `has_pet`.\n- `registered_211_stear` \/ `stear_status`.\n- DME + DME phone, physician, pharmacy, emergency contact.\n\n**Possible additions.** These would be interview questions, not new paper fields; they need a ruling under \"one meaning is one question\":\n\n- Power-dependent equipment: oxygen concentrator, ventilator, powered wheelchair or lift, hospital bed, home dialysis.\n- Medication that needs refrigeration (insulin).\n- Dialysis center and schedule (an outage interrupts *their* care, not just ours).\n- Mobility, stated directly: bed-bound \/ wheelchair \/ walker \/ independent.\n- Lives alone (nothing in VESTA says this; `evacuating_with_count` comes close).\n- Hearing, vision or cognitive impairment that affects receiving a warning.\n\n**Freshness matters.** A form answer is only as current as the last signed visit, which can be up to a year old. Every value on the emergency list should show \"as of {date}, from {VESTA | visit}\".\n\n### 3. The priority rule\n\n- **Tiers, each with reasons shown as chips:** \"Class I (visit 3\/2026)\", \"Oxygen\", \"No transportation\", \"Unable to respond\", \"Visit due tomorrow\". Staff need to *see why* someone is near the top. An opaque score invites distrust and quiet overrides.\n- **Order of precedence:** form `disaster_class` from a signed visit within 12 months \u2192 VESTA `cliclass` \/ `priority` \u2192 ADL proxy. Then modifiers inside a tier: power-dependent, no transportation, unable to respond, lives alone, care scheduled during the event window.\n- **Code layout:** a pure rule file (`code\/hh_emergency_rules.inc.php`) with a standalone test, plus a block per rule in a short customer-facing sheet, the same way FlagRules.md works. The customer writes the rule; we transcribe it.\n- **Per-event overrides:** a coordinator can pin a client up or down for one event without changing the rule.\n\n### 4. Geocoding (shared with RoutePlan.md, which is still plan-only)\n\nRoutePlan \u00a75\u2013\u00a77 already designs this, and the emergency list should be its first consumer:\n\n- `hh_geocode` exists and **nothing writes it**. Alter it **before** it holds anything: `lat_enc`\/`lng_enc` (a home coordinate is an address, so it's PHI), `geohash5` as a prefilter index (not de-identification), `precision` (rooftop \/ interpolated \/ centroid \/ none), `address_hash` so we only re-geocode when the address changes, and `source`.\n- `scripts\/geocode-members.php` against the local Nominatim (`NOM_HOST`\/`NOM_PORT`). Run it after `vesta-api-sync.php` and after the report-path load.\n- **Expect a long tail in the Valley.** Rural routes, colonias, PO boxes and \"FM 1234 & \u2026\" addresses will fall back to a ZIP or city centroid. We need:\n  - a coverage report per agency and branch (rooftop \/ centroid \/ failed), shown with the list so \"47 clients in this county could not be placed\" is visible;\n  - **manual pin correction**: staff drag the pin, it saves as `source = 'manual'`, and it stays until the address changes;\n  - centroid-only clients **included in county selection** (by VESTA county) but marked as approximate in radius and polygon selections.\n- **Tiles:** `MAP_TILE_URL` falls back to `tile.openstreetmap.org` when unset. For this page the fallback should fail closed (no map rather than a public tile server), so a misconfigured box never shows public servers which areas staff are looking at during an event. The reassessment planner's map is still switched off (\"tile server still building\"). Finishing that build is a prerequisite here, same as for routing.\n\n### 5. Choosing the area\n\n| Mode | How |\n|---|---|\n| **County** | Works on day one from the VESTA `county` text, no coordinates needed. Once geocoded, a point-in-polygon county check that **disagrees** with VESTA's county is worth flagging as a data-quality item. |\n| **Radius** | Click a point or search an address (local Nominatim), set the miles. Haversine on decrypted coordinates, prefiltered by geohash cells. |\n| **Polygon** | Draw on the map (Leaflet + leaflet-geoman or Leaflet.draw, loaded from an allowed CDN at an exact version, or vendored like PDF.js). Point-in-polygon in pure PHP, tested. |\n| **ZIP \/ branch \/ HHSC region** | Cheap filters. `DADS_Indv_Region` is on the API roster too. |\n| **Imported shape** (later) | GeoJSON\/KML upload from county emergency management, or **NWS warning polygons** from `api.weather.gov` (a fetch of public alerts that sends nothing of ours). For example: \"everyone inside tonight's flood warning, Class I first\". |\n\nBoundaries: Census TIGER\/Line counties and ZCTAs (public domain), loaded once into a `geo_area` table. They aren't PHI.\n\n**Scale.** Coordinates are encrypted, so MariaDB spatial indexes can't help. That's fine: one agency's open clients number in the thousands, sodium decryption at that volume takes milliseconds, and a geohash\/bounding-box prefilter cuts it down further. Don't trade encryption for `ST_Contains`.\n\n### 6. The output\n\n- **Ranked list + map** colored by tier, filtered by the selected area and scoped by `hh_member_scope()` (administrators see everyone, field supervisors their caseload).\n- **Call sheet export (PDF\/CSV):** name, phone, emergency contact, address, tier + reasons, DME, assigned attendant + phone, next scheduled visit. **Print before landfall**, because power and internet may be gone when it's needed. Every export goes through the PHI audit.\n- **Event tracking (phase 3):** `emergency_event` (name, area shape, opened\/closed) + `emergency_event_client` (status: not reached \/ safe \/ needs help \/ evacuated \/ hospitalized, who called and when, note). Turns the list into a welfare-check board several staff can work at once. It also produces the after-action record surveyors ask for.\n- **Attendants are affected too.** `hh_attendant` has addresses. \"Which attendants live inside the shape\" tells the coordinator whose shifts will drop.\n- **Permission:** our own right, e.g. `client.emergency\/View` (+ `\/Manage` for events), classified in `code\/perms.inc.php` and seeded only when the screen checks it. Staff-only by name.\n\n### 7. Suggested order\n\n1. **Phase 0, decide and measure:** fill rates of `cliclass` \/ `priority` \/ ADL \/ `county` per agency; customer confirms the tier rule and what Class I\u2013IV mean in their practice; decide `cliclass`\/`DNR` for API agencies (MemberPlus vs keeping the Members export); add `Priority` to the API roster mapping.\n2. **Phase 1, no map:** priority list filtered by county \/ branch \/ ZIP, with reasons and an \"as of\" source, plus the printable call sheet. Delivers most of the value and needs no new infrastructure.\n3. **Phase 2, geo:** finish the tile server; alter `hh_geocode` + `geocode-members.php` + coverage report + manual pin; map with radius and polygon selection; TIGER counties.\n4. **Phase 3, forms + events:** form `disaster_class` and the special-needs answers override VESTA in the rule; the extra interview questions above (pending a decision); event tracking board; attendants-in-area.\n5. **Phase 4:** NWS alert polygons; STEAR registration reconciliation (\"Class I and not registered with STEAR\" is an action item before hurricane season).\n\n### Questions for the customer\n\n1. When an emergency happens today, what do they do? Is there a spreadsheet or a list we should match?\n2. Does `cliclass` in VESTA mean what we think it means, and is it kept current?\n3. Do they want the extra special-needs questions (power-dependent equipment, lives alone, \u2026) added to the visit, even though the paper form doesn't print them?\n4. Who works the list during an event: office staff only, or field supervisors for their own caseloads?\n5. Should attendants be on the same map (whose shifts will drop)?","assets":[],"created_at":"2026-10-07T23:32:28-05:00","updated_at":"2026-10-07T23:32:28-05:00"},{"id":1511,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/160#issuecomment-1511","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/160","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Status: built and checked on dev. Not committed or deployed yet.**\n\nAll three changes are on the visit screen's \"Attendants and their hours\" step (`pages\/visit-v2.php`). Only how the step is drawn changed: the questions, what is saved and what prints are the same.\n\n1. **The hours table now comes right after Attendant A's phone number.** That is the table with each day's hours, the week's total and the authorized hours (with the over\/under note).\n2. **Each attendant's days and hours are behind a \"Show days and hours\" button.** The attendant's heading still shows their name and weekly total. The days open by themselves if one of them has a problem (hours that don't come out in quarter hours, or a save that was refused), or when the check sends someone to one of those days. That way nothing is hidden behind a closed button.\n3. **With one attendant, no boxes for a second or third attendant's days are shown.** An attendant with no name and no hours gets no days box and no row in the hours table.\n\nChecked on a dev visit with one attendant: the table sits right after A's phone, A's days start closed, B and C have no days boxes, and the button opens and closes. Not yet tested: typing a name for a second attendant to make their week appear.\n\n**Questions for Jane**\n\n* **Should \"Who is Attendant B?\" still show when there is only one attendant?** Right now B's name and phone questions still show, because that is how a second attendant gets added: type a name and their days appear. The other way is to hide B's questions as well and show an **\"Add another attendant\"** button instead. That is tidier, but it is one more click when there really is a second attendant.\n* **With one attendant, the hours table has two identical rows** (\"Attendant A\" and \"All attendants\"). Should the \"All attendants\" row be left out when there is only one attendant?\n* **Should the days start open when the hours don't match the authorization** (the \"schedule is X hours under\" note)? Today they open by themselves only for a day that has a problem, not for an under-scheduled week.","assets":[],"created_at":"2026-10-07T23:39:28-05:00","updated_at":"2026-10-07T23:39:28-05:00"},{"id":1516,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137#issuecomment-1516","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/137","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Confirmed by the customer (8 Oct 2026):**\n1. Yes, the agency lookup belongs on both the Coordination of Care and the Transfer form.\n2. Yes, \"home health\" should include in-home support agencies.\n\nNothing to change. The SOC\/AO release `SOC_AO-20261007` already carries both lookups, and its completeness check passes. Closing.","assets":[],"created_at":"2026-10-08T09:29:24-05:00","updated_at":"2026-10-08T09:29:24-05:00"},{"id":1519,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/166#issuecomment-1519","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/166","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built (fcc5d40)\n\n**Decisions on the three questions**\n1. **A question filled in from VESTA does not count as waiting** unless it was seen and later hidden. Waiting = shown, never seen, and unanswered.\n2. **Seen, hidden, then shown again: yes, it waits again**, even with its old answer still in it (FormsV2Plan \u00a76 reactivation review).\n3. **Waiting questions block Generate forms.** The refusal names the sections. A Superuser's \"skip the check\" also skips this. This also answers #165 question 3.\n\n**How it works**\n* New table `form_v2_visit_seen` (`migrations\/20261008_visit-seen-questions.sql`). It holds question keys and dates only, no PHI. A question is **seen** once it was on screen in a section the filler then left. The page sends `left` (the sections it left) with its save, and that includes going to Check. The server marks what those sections showed *before* the save's own changes. So a question the save reveals in the section being left is still flagged. A seen question that a change hides is cleared, in the same transaction.\n* The rules are pure functions in `form_v2_visit_rules.inc.php`: `form_v2_visit_is_waiting()`, `form_v2_visit_waiting()`, `form_v2_visit_seen_changes()` and `form_v2_visit_unseen_steps()`. `form_v2_visit_progress()` now returns `waiting` per section and overall.\n* The check returns `unseen`. `form_v2_candidate_prepare()` refuses with the new code `unseen`, after `issues`.\n\n**On screen (`\/visit-v2`)**\n* The sidebar shows a dot and \"N new\" under the section count.\n* Waiting questions carry a **New** label until the section is left.\n* When an answer brings up questions in another section, a notice says \"This answer added questions in *Transfer and choice*\", with a link.\n* The Check step lists \"Sections with questions you haven't seen\" above the issues, each with a link.\n\n**Tests:** new cases in `test-form-v2-visit-rules` (233 pass), `test-form-v2-visit-state` (146 pass) and `test-form-v2-candidate-state` (64 pass). One existing case in the candidate test was adjusted: its visit had an optional question nobody had opened, so it now hit the new refusal before the \"no blank form\" one it was testing. Also checked in the browser on dev visit 75.\n\n**To deploy:** `php scripts\/migrate.php --apply`.","assets":[],"created_at":"2026-10-08T10:13:36-05:00","updated_at":"2026-10-08T10:13:36-05:00"},{"id":1521,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165#issuecomment-1521","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Update: #166 is built (fcc5d40)\n\nThat covers the \"revisit these sections\" half of this issue:\n* When a changed answer brings up questions in a later section, that section shows a dot and \"N new\" in the sidebar. A notice also names the section, with a link.\n* A question that was looked at, hidden by a change, then shown again counts as new again, even with its old answer still in it.\n* **Question 3 is settled: block.** Generate forms is refused (code `unseen`) while any section has questions nobody has looked at. The Check step lists those sections.\n\n**Still open here:** plan items A\u2013C (lock the answers once forms are generated, with the confirm modal, and enforce it on the server with `withdraw_candidate`), and questions 1 and 2 (whether every change withdraws all signatures, and amendments to a signed visit). C's \"New questions appeared in\u2026\" wording can now use the notice #166 added.","assets":[],"created_at":"2026-10-08T10:13:36-05:00","updated_at":"2026-10-08T10:13:36-05:00"},{"id":1523,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165#issuecomment-1523","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/165","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Built (cce1ffb)\n\n**Question 1 decided: only the forms a change reaches are generated and signed again.** Signatures on forms the change doesn't touch stay good.\n\n**How a correction works**\n* **The answers lock once forms exist.** After the forms are generated, the questions are read-only behind **Change an answer**. That opens a confirmation: \"Changing an answer withdraws the forms it changes, and any signatures on them\u2026 Forms the change does not reach keep their signatures.\"\n* **There is a way back from signing.** The signing screen has \"An answer is wrong? Change it\", for when the client or employee finds an error and won't sign.\n* **The server enforces the confirmation.** While a printing is current, `form_v2_visit_save()` refuses a changed answer with code `printed`, unless the save carries `withdraw_candidate` naming that printing. Moving between sections needs nothing. A stale tab or a retried request gets the same confirmation instead of silently withdrawing signatures.\n* **Generating again keeps unchanged forms.** Each form now carries a fingerprint, `form_v2_candidate_form_digest()`: its pinned version and blank form, plus its own answers. On the next generate, a form whose fingerprint matches the last printing (`form_v2_candidate_carry()`) is not printed again. Its review copy is reused, and its signed and \"reviewed\" taps are copied onto the new printing. If every form matches, the new printing is ready at once. The forms list marks kept forms \"unchanged, signatures kept\".\n* **Adopted signatures carry too** (`form_v2_sign_carry_signers()`), so nobody draws theirs again. The exceptions:\n  * a signer the correction renamed, such as an attendant whose name changed;\n  * a client whose adoption now contradicts how the interview says the client signs.\n* **A decline does not carry.** A refusal is what the correction answers, so the line is asked again.\n* **The audit trail is kept.** Every copied tap and adoption has `fk_carried_from` pointing at the original row (`migrations\/20261008-02_carried-signatures.sql`), with the original time and the hash and digest it cited. A copy of a copy still points at the original.\n\n**Tests:** new cases in `test-form-v2-visit-rules`, `test-form-v2-sign-rules`, `test-form-v2-sign-state` (98 pass) and `test-form-v2-candidate-state`. The sign-state test covers: a signed form kept while the changed one is signed again, the decline not carried, nobody adopting again, and a refused change followed by a confirmed one. The lock and the confirmation were also checked in the browser. Keeping a signed form could not be checked in the browser locally, because the dev document store isn't configured.\n\n**To deploy:** `php scripts\/migrate.php --apply`.\n\n**Question 2 (correcting a visit that is already *signed*) is #119.** That is the second stage of correcting a mistake: the office finds it while reviewing the forms after the visit. This issue covered the first stage, the supervisor on site while signatures are being collected.","assets":[],"created_at":"2026-10-08T10:52:43-05:00","updated_at":"2026-10-08T11:00:48-05:00"},{"id":1525,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/119#issuecomment-1525","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/119","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## Scope: the office's correction, after the visit\n\nCorrecting a mistake on a form happens at two stages:\n\n1. **On site, while signatures are being collected.** The supervisor or the client spots a mistake before the visit is signed off. **Built in #165 (cce1ffb).**\n   * Once forms are generated, the answers lock behind **Change an answer**, with a confirmation. The signing screen also has \"An answer is wrong? Change it\".\n   * The fix withdraws only the forms it changes. Those are generated and signed again.\n   * Forms the fix doesn't touch keep their signatures, and nobody draws their signature again.\n2. **In the office, after the visit.** The visit is signed and filed, and office staff reviewing the forms find a mistake. **This issue.**\n\nSo the \"Before signing\" line under *Where we are* is now: any answer can be changed, and only the forms the change reaches are generated and signed again. \"After signing\" is unchanged: the signed visit takes no answers, and there is no correction path yet.\n\n**Pieces from #165 that this design can reuse:**\n* **A per-form fingerprint** (`form_v2_candidate_form_digest()`). It tells exactly which forms a correction changes. So a correction can be per form: only those forms need a new revision and new signatures, and the rest of the signed visit stands.\n* **Signature copies that point at the original** (`fk_carried_from`). Unchanged forms in a corrected revision can show their original signatures, and the record still says when and on what each was signed.\n* **The `Amend` right in the permission list** (`PERM_PROVISIONAL_NAMES`). It is still the natural gate for who may correct.\n\nThe open decisions listed above are still open: who may correct, whether the client must sign again, how the original and the correction are shown, and whether a correction is per form or reopens the visit. The office review screen (`\/visits-v2-review`) is read-only today and only shows visits still in progress, so this will also decide where a signed visit is reviewed.","assets":[],"created_at":"2026-10-08T11:00:48-05:00","updated_at":"2026-10-08T11:00:48-05:00"},{"id":1532,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/161#issuecomment-1532","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/161","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Status: built and checked on dev. Not committed or deployed yet.**\n\n**What it does**\n\n* The visit compares how many days of the week the schedule works (all attendants together) with the schedule in VESTA when the visit started.\n* If a day is added or dropped, a red note under the authorized hours says: *\"The schedule has hours on 4 days a week, and the schedule in VESTA has 5 days. Hours can move from one day to another, but the number of days can't change. Only a new authorization can change it.\"*\n* The same sentence is listed on **Check the answers**, under the weekly schedule, and the forms can't be generated until the number of days matches. It sits with the \"over the authorized hours\" problem as one entry for the week.\n* If VESTA has no schedule for the client, there is nothing to compare, so the rule doesn't apply.\n\n**One design call: it is a hard stop at the check, not a refusal at save.** The schedule saves one day at a time. Moving Friday's hours to Saturday therefore passes through a week with one day more (or one fewer). If the save refused that, a day could never be moved. So the note shows while the hours are being moved and goes away once the count is back.\n\n**Built as \"per week\"**, the same period as the hours. **Jane:** please confirm that is what \"for a given period\" means.\n\n**Where:** `form_v2_schedule_record_days()`, `form_v2_schedule_days_status()`, `form_v2_schedule_week()` now returns `days`, and `form_v2_visit_issues()` takes `record_days` (`code\/form_v2_visit_rules.inc.php`). The payload's `record_days` (`code\/form_v2_visit.inc.php`) and the note in `pages\/visit-v2.php`. Tests in `scripts\/test-form-v2-visit-rules.php` (251 pass).\n\n**Checked on dev** (visit 75: VESTA schedule Mon\u2013Fri): clearing Friday on screen shows the note in red, and nothing was saved.","assets":[],"created_at":"2026-10-08T11:15:32-05:00","updated_at":"2026-10-08T11:15:32-05:00"},{"id":1533,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/125#issuecomment-1533","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/125","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Status: built and checked on dev. Not committed or deployed yet.**\n\nStaff can now find a form's questions themselves, without the Form Explorer, in two places:\n\n**1. On the visit (`\/visit-v2`).** Above the step list there is **\"Show the questions for [every form \u25be]\"**. Choosing e.g. *Form 3040* does three things:\n* the step list keeps only the sections with 3040 questions, and Back \/ Next go through only those;\n* inside a section, only the questions that go on the 3040 are shown;\n* a blue bar says *\"Showing only the questions for Form 3040 \u2026 Show every question\"*.\n\n**Interaction with \"new\" questions (#166):** a section opened while a form is chosen **does not count as looked at**, because the rest of its questions were hidden. Marking them seen would claim somebody looked at questions nobody saw. The bar says so. \"Go to the question\" on the check clears the choice if it would hide that question. The week's hour totals still count every day, even when some days are hidden.\n\n**2. On Visit Review (`\/visits-v2-review?v=N`).** The same choice sits above **Every answer**. It hides the rows and sections that don't go on that form, opens the rest, and says how many questions go on it (63 of 134 for the 3040 on the dev demo visit).\n\nBoth use the same \"which forms does this answer go on\" list the info button on each question already shows (`form_v2_visit_question_forms()`), so the two pages can't disagree with each other or with the question.\n\n**Not listed:** boxes worked out from other answers, and boxes filled straight from the record, are not questions, so they don't appear. Visit Review's note says so.\n\nFiles: `pages\/visit-v2.php`, `pages\/visits-v2-review.php`, `code\/form_v2_visit.inc.php` (`form_v2_visit_review()` now returns each row's `forms` and the visit's `forms`).","assets":[],"created_at":"2026-10-08T11:15:32-05:00","updated_at":"2026-10-08T11:15:32-05:00"},{"id":1535,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/126#issuecomment-1535","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/126","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Status: both open pieces built and checked on dev. Not committed or deployed yet.**\n\n**1. Compare any two releases after they are published.** On **Paperwork in Use** (`\/admin\/form-v2-releases`):\n* each release has a **What changed** button, which compares it with the release before it;\n* **Compare two releases** (below the list) compares any two.\n\nThe comparison gives the same answers the Publish page gives before a publish: questions new \/ changed (old and new wording) \/ moved \/ removed, step changes, the visit type, each form's definition and placement, and the version changes. New `form_v2_release_diff()` (`code\/form_v2_pipeline.inc.php`) reuses the publish page's own diff functions. It is served read-only by the new `\/do\/formV2ReleaseCompare`, with the same rights as the rest of the page.\n\n**2. Linking changes to Action Items: done through the release notes.** A release's notes are free text, so the link comes from there and doesn't need a second place to record it:\n* `form_v2_release_action_items()` reads \"#160\" or \"Action Item 160\" from the notes;\n* each release in the list shows \"Action Items: #\u2026\";\n* a comparison lists the Action Items of every release in between;\n* the notes box on the Publish page, and `--notes` on `form-v2-pipeline.php`, now ask for them (\"e.g. #160 #161\").\n\nThis is per release, not per question. Tying each individual question change to an issue would need the commit that changed the prepared file, and production has no git history to read. None of the existing dev releases name an issue in their notes, so the list starts filling from the next publish.\n\n**The smaller point is fixed too.** A prefill change is now said in words: *\"\u201cVendor No.\u201d is now filled in from the branch office (was the agency).\"* Comparing SOC_AO-20261006 with SOC_AO-20261007 showed why it mattered. The 3040 went from version 6 to 7, and that prefill source was its only change. Before, the comparison gave no sentence explaining it.\n\nTests in `scripts\/test-form-v2-review.php` (60 pass).","assets":[],"created_at":"2026-10-08T11:15:32-05:00","updated_at":"2026-10-08T11:15:32-05:00"},{"id":1538,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/115#issuecomment-1538","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/115","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Looked at the paper. Which box is meant isn't clear, so this needs Jane.**\n\nThe only matching line is *\"I \u2610 am \u2610 am not providing a copy for my record\"*, in the advance-directive paragraph. It appears on the PAS\/PHC consent, the PCS consent and the Private Pay Agreement, and on all three **nothing below it is a tick box**. Next comes the *Medical Power of Attorney's Name \/ Phone* line, then the emergency-preparedness paragraph, which has no box. The H2076 medical release has no \"providing a record\" wording at all.\n\n**Jane:** which form, and which box should tick itself when \"am not providing a copy\" is ticked? A photo or a page number is enough. Once we know, it is a small change (a worked-out answer, no longer asked).","assets":[],"created_at":"2026-10-08T11:15:32-05:00","updated_at":"2026-10-08T11:15:32-05:00"},{"id":1542,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/110#issuecomment-1542","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/110","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Answered: Charles has told Jane where 3040 Q8 (\"Is Attendant A\/B\/C competent\u2026\") is captured. Closing.","assets":[],"created_at":"2026-10-08T11:33:06-05:00","updated_at":"2026-10-08T11:33:06-05:00"},{"id":1677,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/170#issuecomment-1677","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/170","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in `fdd3083` (committed, not yet pushed or deployed).\n\n## What changed\n- [x] **`editableSave` no longer returns ciphertext.** It answers with the value that was typed and keeps the encrypted copy only for the database write. This also fixes the settings page holding ciphertext as the field's value after an edit.\n- [x] **The htoken has its own domain.** New pure `code\/htoken.inc.php` defines the format `h1.<base64url payload>.<base64url MAC>`:\n  - The MAC is `sodium_crypto_auth` under a subkey derived from `DB_KEY_ENC` for this purpose alone (`sodium_crypto_kdf`, context `htoken__`).\n  - It covers the version prefix and the payload, and the payload must carry `\"t\":\"htoken\"`.\n  - `htoken_verify()` checks the MAC before it decodes anything.\n  - Nothing `db_encrypt()` produces has this shape, and nothing else holds the subkey, so no leak of PHI ciphertext can mint one.\n  - `set_user_cookie()` and `mobileSessionStart` both mint through `htoken_for()`.\n- [x] **The resume refuses a disabled account** (`is_enabled !== 1`). It also regenerates the session id and voids any pending sign-in.\n- [x] **Passkey sign-in refuses a disabled account.** Found while doing this: `accountPasskeyAuth` never checked `is_enabled`.\n- [x] **Other places exposing `db_encrypt()` output:** I swept `backend\/`, `pages\/` and `code\/` and found none that return it. A new assertion fails if anything decrypts a cookie or request value with `db_decrypt()` \/ `decrypt_package()`.\n- [x] **Invalidating old htokens.** Every htoken issued before this deploy is refused, because it is not in the new format, so `DB_KEY_ENC` does not need rotating. Anyone with no live server session signs in once more after the deploy.\n- [ ] **Binding the htoken to a revocable server-side row** is left to #72.\n- [ ] **Still to do on prod:** check `audit_auth('session.htoken_resumed')` for resumes that have no earlier `login.success` from the same user.\n\n## Tests\n- New `scripts\/test-htoken.php` (26 pass). It is standalone and includes the exploit's exact payload, `db_encrypt`'d under the master key, which is refused.\n- `scripts\/test-login-hardening.php` gains a #170 section that asserts the call sites.\n- Live against the local server:\n  - The editableSave oracle now returns plaintext.\n  - A legacy-format cookie for user 1 is sent to `\/login`, and so is a tampered one.\n  - A genuinely signed cookie resumes.","assets":[],"created_at":"2026-10-09T08:43:35-05:00","updated_at":"2026-10-09T08:43:35-05:00"},{"id":1679,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/171#issuecomment-1679","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/171","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in `fdd3083` (committed, not yet pushed or deployed).\n\n## What changed\n- [x] **The sign-in in progress is `$_SESSION['pending_user']`** (a pk). `accountLogin` no longer assigns `$_SESSION['user']` anywhere. Step 2 and `accountAuth` read the pending pk, and both refuse when the session is already `authed`. `accountAuth` promotes it to `$_SESSION['user']` only on a verified code, then unsets it. `accountPasskeyAuth` (where the credential decides who it is) and `dev-login` unset it too.\n- [x] **Step 1 ends a live session first.** If `authed` is set, step 1 calls `end_session('relogin')` (revoked row, cleared session, expired htoken) and then gives the session a new id. I chose this over a refusal because \"sign in as somebody else\" on a shared machine is legitimate. It must just mean signing the first person out. The session cookie is SameSite=Lax, so a cross-site POST cannot force it.\n- [x] **Disabled accounts:** step 2 and `accountAuth` both refuse an account disabled since the lookup.\n- [x] **`session_db_check()` compares the `sessions` row's `fk_user` with `$_SESSION['user']['pk_user']`.** On a mismatch it fails closed and audits `session.check_user_mismatch`.\n- [x] **`scripts\/test-login-hardening.php` has a #171 section.** It asserts:\n  - no `$_SESSION['user'] =` in `accountLogin`\n  - the pending stash, and that it is read by step 2 and `accountAuth`\n  - `end_session` before the lookup\n  - the pending pk is spent on success\n  - the `is_enabled` checks\n  - the `fk_user` comparison\n\n## Live check (local server)\n1. Signed in as user 2 (Field Supervisor), I POSTed step 1 with user 1's address.\n2. The next requests to `\/11\/admin\/roles` and `\/11\/` were redirected to `\/login`, so the session ended instead of becoming user 1.\n3. `accountAuth` then refused with \"choose how you'd like to sign in\".\n\nA normal sign-in (lookup \u2192 choose \u2192 code) still works end to end.","assets":[],"created_at":"2026-10-09T08:43:35-05:00","updated_at":"2026-10-09T08:43:35-05:00"},{"id":1691,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/180#issuecomment-1691","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/180","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 2e8c0b9. `generate_auth_code()` is deleted (no callers). `scripts\/test-login-hardening.php` now refuses `rand()` \/ `mt_rand()` \/ `lcg_value()` in `code\/functions_auth.inc.php` and `backend\/account*.php`, and the comment in `accountLogin.php` is updated.","assets":[],"created_at":"2026-10-09T08:52:01-05:00","updated_at":"2026-10-09T08:52:01-05:00"},{"id":1692,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/181#issuecomment-1692","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/181","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 2e8c0b9.\n\n- One normalisation each: pure `auth_email_key()` \/ `auth_cell_key()` (`code\/auth_factors.inc.php`), hashed by `auth_email_hash()` \/ `auth_cell_hash()` (`code\/functions_auth.inc.php`). Used by `accountLogin`, `accountAuth` (lockout key), `accountPasskeyAuth`, `pages\/users-edit.php`, `editableSave` and `migrate-hashes.php`.\n- `editableSave` writes `cell_phone_hash` for `cell_phone_enc`, stores the email lowercased, and its duplicate check uses the same hash.\n- Also fixed: the field's type came from the request, so `type=text` skipped the email validation and the duplicate check. It is now pinned per field (`EDITABLE_SELF_TYPES`).\n- Asserted in `scripts\/test-login-hardening.php`. Checked locally end to end: a number saved through `\/do\/editableSave` is found by sign-in step 1.\n\n**Deploy step:** `php scripts\/repair-user-login-hashes.php --apply` on production (dry run without `--apply`). It recomputes only `email_hash` \/ `cell_phone_hash` and lowercases `email_enc`, unlike `migrate-hashes.php`.","assets":[],"created_at":"2026-10-09T08:52:01-05:00","updated_at":"2026-10-09T08:52:01-05:00"},{"id":1701,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/172#issuecomment-1701","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/172","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 8f72261.\n\n- [x] `hhMemberDetail` now loads the member with `AND fk_system = $SITE['pk_system']`. A member of another agency returns \"Member not found\", and the attempt is audited as `security.authz.denied` with `perm = hh_member.agency`. I removed the visit list's own agency check because it no longer does anything.\n- [x] One scope rule: the existing `hh_member_in_scope()` check is kept on the agency-filtered row. That is the same thing `hh_member_pk_in_scope()` does.\n- [x] Sweep: I checked every `backend\/` and `pages\/` load of a member, record, flag, visit or fax by pk. All of them filter on `fk_system` or go through `hh_member_pk_in_scope()` \/ `form_v2_visit_for_reader()`; this endpoint was the only gap. `hhFlagResolve`'s member lookup now names the agency as well. It was already safe, because the flag is loaded by agency. `scripts\/test-backend-auth-guards.php` now fails on any `backend\/` load of `hh_member` by pk without `fk_system`. I verified it fails on the old code.\n- [x] Audit search: no cross-agency reads in the dev logs (396 member views). **Production still needs this run there:**\n  ```\n  cat logs\/audit\/access.*.jsonl | jq -c 'select(.subject.table==\"hh_member\" and .system.pk_system != .subject.fk_system) | {ts, user: .actor.pk_user, uri: .request.uri}'\n  ```\n\nVerified on dev: `\/9\/do\/hhMemberDetail` with an agency-10 pk is refused, and the same pk through `\/10\/` works.","assets":[],"created_at":"2026-10-09T09:10:18-05:00","updated_at":"2026-10-09T09:10:18-05:00"},{"id":1703,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/173#issuecomment-1703","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/173","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 83b6df9.\n\n- [x] **is_enabled** \u2014 already shipped with #170 (passkey sign-in and the htoken resume both refuse a disabled account).\n- [x] **User verification** \u2014 required in `getGetArgs()` and `processGet()`; `processCreate()` now requires it too, matching the `'required'` the create args already asked for.\n- [x] **Sign counter** \u2014 the stored `sign_count` is passed as the previous counter. It had never actually been recorded: the code read `$assertionData['counter']`, but `processGet()` returns `true`, so every row holds 0. The new value now comes from `getSignatureCounter()`. Authenticators that keep no counter (0 against 0) still sign in.\n- [x] **unserialize** \u2014 `allowed_classes => ['stdClass']` (every stored blob is `stdClass`); an unreadable blob is refused and audited.\n\nCounter, verification and other assertion failures are audited as `login.failure` with a reason. `scripts\/test-login-hardening.php` asserts the call sites and runs real P-256 assertions through the library (presence-only tap refused; stalled, backwards or dropped-to-0 counter refused; 0-against-0 accepted).\n\nUser-visible: a security key that only needs a tap is no longer enough on its own \u2014 noted in UserChangelog.","assets":[],"created_at":"2026-10-09T09:16:50-05:00","updated_at":"2026-10-09T09:16:50-05:00"},{"id":1713,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/174#issuecomment-1713","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/174","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 1b30090.\n\n- [x] **Every write branch requires POST.** In `pages\/admin\/roles.php`, save, create, activate, deactivate, assign and unassign are conditioned on `$is_post`. A GET there still selects what to display (`?pk_role=`, `?action=new`) and can no longer write. `pages\/users-edit.php` and `pages\/admin\/systems-edit.php` save only on POST. `backend\/faxReview.php` refuses a GET before it reads the action.\n- [x] **Write parameters come from `$_POST`.** None of the four handlers reads `$_REQUEST` any more.\n- [x] **New test.** `scripts\/test-write-methods.php` scans `backend\/` and `pages\/` for writes: DB write primitives, plus calls to helpers whose names say they mutate. Any handler that matches must check `REQUEST_METHOD` (or use `form_v2_visit_request()`) or be exempted with a reason. Two are exempt: `pages\/logout.php`, and the reassessment planner's zip-boundary cache. An exemption that no longer matches the scan fails the test.\n\nThe scan also found other handlers that wrote on a GET, and they now require POST too:\n- `editableSave` (now `$_POST` throughout)\n- `formPacketCreate`, `formPacketDefArchive`, `formPacketDefNewDraft`, `formPacketDefSaveDraft`\n- the three sign-in steps. A GET to `accountLogin` used to end a live session and send a code.\n\nEvery caller already used POST.\n\nChecked on dev while signed in as an administrator:\n- `GET \/9\/admin\/roles?action=assign&pk_role=1&pk_user=2` and the users-edit and systems-edit equivalents now render the page (200) and write nothing.\n- `GET \/do\/faxReview?action=accept\u2026` and `GET \/do\/editableSave?\u2026` return \"POST required.\"\n- A POST to roles still reaches its write branch.\n\n#29 (CSRF tokens) is still open. With writes now POST only, the `SameSite=Lax` cookie is a real stopgap until #29 lands.","assets":[],"created_at":"2026-10-09T13:15:29-05:00","updated_at":"2026-10-09T13:15:29-05:00"},{"id":1715,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/175#issuecomment-1715","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/175","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"Fixed in 1b30090.\n\n- [x] **One validator.** New pure `code\/local_redirect.inc.php`, with `local_path_is_safe()` and `local_redirect($path, $fallback)`. A target must start with a single `\/` and must not contain `\/\/`, a backslash or control characters. Any scheme is refused. `notif_link_is_safe()` and `dev_login_target()` now call it, so there is one definition. All four call sites use it: `pages\/login.php`, `backend\/accountAuth.php`, `backend\/accountPasskeyAuth.php` and `pages\/chooser.php`.\n- [x] **Fallback.** A target that fails validation goes to `$SITE['base']`. In `\/chooser` it goes to the chosen agency's home.\n- [x] **`\/chooser` checks the agency.** `system` must be in `$_SESSION['user']['systems']`, or the chooser page is shown. The `r` it carries forward is now URL-encoded.\n\n`test-login-hardening` covers this:\n- these are refused: `\/\/evil.example\/`, `\/\/\/evil.example`, `\/\\evil.example`, `x\/evil.example`, `https:\/\/\u2026`, `javascript:\u2026`, tab or newline smuggling, CR-LF, empty and non-string values\n- local paths are kept\n- each of the four files sends `r` through `local_redirect()` and never builds the target with `substr($_REQUEST['r'], \u2026)`\n- the chooser checks the agency membership before it redirects\n\nChecked on dev while signed in:\n- `\/login?r=\/\/evil.example\/` \u2192 `\/`\n- `\/login?r=\/\\evil.example` \u2192 `\/`\n- `\/chooser?system=9&r=x\/evil.example` \u2192 `\/9\/`\n- `\/login?r=\/9\/members` \u2192 `\/9\/members`\n- `\/chooser?system=12345` shows the chooser","assets":[],"created_at":"2026-10-09T13:15:29-05:00","updated_at":"2026-10-09T13:15:29-05:00"},{"id":1725,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/118#issuecomment-1725","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/118","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Update (9 Oct 2026): the customer has entered all the branch office addresses in production.** That clears item 1 under *Waiting on the customer* above. The Branch Offices table now uses separate boxes for street, city, state and ZIP (b31d931, c82f0b4).\n\nStill open:\n- **Which number prints where (vendor or contract):** tracked in #117 (F1).\n- **Publishing:** the SOC\/AO release with the office-details check and the 3040 Vendor No. change still has to be published on `\/admin\/form-v2-publish` and chosen on `\/admin\/form-v2-releases`.","assets":[],"created_at":"2026-10-09T15:02:39-05:00","updated_at":"2026-10-09T15:02:39-05:00"},{"id":1730,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/169#issuecomment-1730","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/169","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"**Built and deployed to production (8\u20139 Oct 2026).**\n\n- The section buttons on a visit no longer print the forms each section fills in. There were too many to fit.\n- Each section now has the same circled **i** that a question has. Tapping it opens a list of the full names of the forms that section's questions go on. Tapping it again closes the list. Tapping the section itself still opens the section.\n- The **i** stays in one place at the far right of each section. The \"N new\" marker sits to its left.\n- The \"answered \/ shown\" counts were removed from the section list and from *Check the answers*.\n\nCommits: 031f62c (the info button and list), 7f33b78 (layout). This replaces the line of form numbers added in #152. The page was the only thing changed; there was no schema change.","assets":[],"created_at":"2026-10-09T15:42:09-05:00","updated_at":"2026-10-09T15:42:09-05:00"},{"id":1763,"html_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77#issuecomment-1763","pull_request_url":"","issue_url":"https:\/\/www-s01.apinet.com\/codeforge\/apiintl\/hhmanager\/issues\/77","user":{"id":1,"login":"charles","login_name":"","source_id":0,"full_name":"Charles Willgren","email":"charles@noreply.www-s01.apinet.com","avatar_url":"https:\/\/www-s01.apinet.com\/codeforge\/avatars\/d3c894f7297c5a18eb3f6ac837c335b11a00aa9cc0961323771632b1e2d61bb2","html_url":"https:\/\/www-s01.apinet.com\/codeforge\/charles","language":"","is_admin":false,"last_login":"0001-01-01T00:00:00Z","created":"2025-12-08T10:03:41-06:00","restricted":false,"active":false,"prohibit_login":false,"location":"","pronouns":"","website":"","description":"","visibility":"public","followers_count":0,"following_count":0,"starred_repos_count":0,"username":"charles"},"original_author":"","original_author_id":0,"body":"## The last two labels are decided (13 Oct 2026, dev)\n\nThe customer's answer to the two results the rule produced:\n\n| Question | Label now |\n|---|---|\n| Medicaid number | **Medicaid #** (was \"Client \/ Patient's Number\"). Set as a standard, like \"Client Name\". \"Medicaid #\" is printed on the Client Information Sheet, the PCS consent, Things You May Not Do and the Task Guide. |\n| Sex | **Gender** (no change; the rule already gave this) |\n\nPublished on dev as release **SOC_AO-20261013** (interview v9). No agency uses it yet. The same release carries the #118 office-address change on the Coordination of Care and the PAS Service Plan.\n\n**No customer decisions remain on this issue.** It stays open until the release reaches production. Three of our own points are still to settle first:\n- **The \"Ask: \u2026\" line** has been hidden since 8 Oct. Decide whether it stays hidden, then make FormsV2Plan \u00a74.1.3 say so.\n- **Fragment labels.** Some labels that every form agrees on only make sense next to the paper, e.g. the medical power of attorney's \"Phone\", \"Which directives\" and \"Reviewed and acknowledged\". The paper's text is still shown under them. With \"Ask:\" hidden, though, nothing else explains them.\n- **Labels the rule picked that nobody has checked.** Phone \u2192 \"Telephone No.\", Address \u2192 \"Individual's Address\", Emergency priority class \u2192 \"What category describes your special needs?\", Physician \u2192 \"Name of Physician Individual Sees Regularly\". Any of these can be standardised the same way.\n\nTechnical: `FORM_V2_STANDARD_WORDING` in `code\/form_v2_project.inc.php` (`hh_member.medicaid_id`).","assets":[],"created_at":"2026-10-13T11:15:01-05:00","updated_at":"2026-10-13T11:15:01-05:00"}]}