Mosquito check: admin-editable send message, off-property save fix, closer geocode, solution copy updates #211

Merged
eliribble merged 17 commits from mosquito-check-updates into main 2026-09-05 00:27:50 +00:00

Mosquito check: admin-editable send message, off-property save fix, closer geocode, solution copy updates

5 commits from the light-rat branch, split out for review. Rebased onto current main (fe6c6bd0). Builds on the short-link/campaign work already on main or landing in the short-links-click-tracking PR — none of these commits depend on that PR's code, but the send-message flow delivers the short link minted by CommunicationMosquitoCheckCreate.

1. Admin-editable message for Mosquito Check sends

Previously the SMS/email sent to a resident when staff send a Mosquito Check used one hard-coded bilingual line. Now the send accepts an optional custom message.

  • POST .../communication/{id}/mosquito-check request gains an optional JSON field: message. Empty message keeps today's default copy.
  • The literal placeholder {link} is substituted with the short link at send time. A custom message with no placeholder gets the link appended on its own line.
  • SMS path: composed message sent via text.Respond.
  • Email path: empty message renders the canned bilingual templates (virtual-inspection-request.html/.txt unchanged for that case); a custom message becomes the email body with the link button preserved (SendVirtualInspectionRequest now takes a message argument).
  • platform/virtual_inspection.go: shared defaultMosquitoCheckMessage constant + mosquitoCheckMessage(message, link) composer. The frontend modal prefills this exact string, so the two must stay in sync (comment marks the coupling).
  • ts/components/MosquitoCheckSendModal.vue (new): send modal with editable message textarea; ts/view/Communication.vue wires it in.

2. Fix off-property save missing unique constraint

Migration 00363. OffPropertyUpsert relies on ON CONFLICT (virtual_inspection_id, category), but migration 00321 omitted the unique constraint, so every off-property save failed with SQLSTATE 42P10. Adds the unique constraint; one block per category per inspection is the domain rule.

3. Reverse-closest geocode ignores far-away admin features

platform/geocode/geocode.go: ReverseGeocodeClosest previously considered administrative polygon layers (county, region, country, empire, continent, ocean, ...) whose stored point is a representative centroid that can be hundreds of km from the query point — a "preferred" admin layer could win over a genuinely nearby address/street result or over nothing. Now:

  • Administrative polygon layers are excluded from the preference order entirely.
  • A 10 km max-useful-distance guard applies to every candidate layer.
  • Null-safe distance logging.

4. Tarp and yard drain solution copy

db/migrations/00321_virtual_inspection.sql: updates the EN/ES solution copy for the Tarp or plastic cover and Yard drain habitat types (e.g. yard drain now suggests fitting screen mesh over the pipe opening and notes residents may request mesh from the district). doc/mosquito-check-solutions.md updated to match.

Caveat: 00321 already exists on main; if it has already run in an environment, the edited INSERT rows will not re-apply there — only fresh databases see the new copy. Confirm this is acceptable before merge, or move the copy change into a new migration.

Files

11 changed: 2 migrations (00321 modified, 00359 added), platform/geocode/geocode.go, platform/virtual_inspection.go, resource/communication.go, platform/email/virtual_inspection_request.go + 2 email templates, MosquitoCheckSendModal.vue (new), Communication.vue, doc/mosquito-check-solutions.md.

Verification

  • gofmt clean; go build -tags tiff OK
  • go test ./api -run TestMosquitoCheckRoutesRegister passes
  • pnpm run build-sync and build-rmo both pass

Update (2026-09-04)

Rebased onto current main (493c13f8). Migration renumbered again: 00359 → 00363 (main consumed 00358-00361 today). Verified: gofmt, go build -tags tiff, TestMosquitoCheckRoutesRegister, build-sync, build-rmo all pass on the rebuilt branch.

Mosquito check: admin-editable send message, off-property save fix, closer geocode, solution copy updates 5 commits from the light-rat branch, split out for review. Rebased onto current main (fe6c6bd0). Builds on the short-link/campaign work already on main or landing in the short-links-click-tracking PR — none of these commits depend on that PR's code, but the send-message flow delivers the short link minted by CommunicationMosquitoCheckCreate. ## 1. Admin-editable message for Mosquito Check sends Previously the SMS/email sent to a resident when staff send a Mosquito Check used one hard-coded bilingual line. Now the send accepts an optional custom message. - POST .../communication/{id}/mosquito-check request gains an optional JSON field: message. Empty message keeps today's default copy. - The literal placeholder {link} is substituted with the short link at send time. A custom message with no placeholder gets the link appended on its own line. - SMS path: composed message sent via text.Respond. - Email path: empty message renders the canned bilingual templates (virtual-inspection-request.html/.txt unchanged for that case); a custom message becomes the email body with the link button preserved (SendVirtualInspectionRequest now takes a message argument). - platform/virtual_inspection.go: shared defaultMosquitoCheckMessage constant + mosquitoCheckMessage(message, link) composer. The frontend modal prefills this exact string, so the two must stay in sync (comment marks the coupling). - ts/components/MosquitoCheckSendModal.vue (new): send modal with editable message textarea; ts/view/Communication.vue wires it in. ## 2. Fix off-property save missing unique constraint Migration 00363. OffPropertyUpsert relies on ON CONFLICT (virtual_inspection_id, category), but migration 00321 omitted the unique constraint, so every off-property save failed with SQLSTATE 42P10. Adds the unique constraint; one block per category per inspection is the domain rule. ## 3. Reverse-closest geocode ignores far-away admin features platform/geocode/geocode.go: ReverseGeocodeClosest previously considered administrative polygon layers (county, region, country, empire, continent, ocean, ...) whose stored point is a representative centroid that can be hundreds of km from the query point — a "preferred" admin layer could win over a genuinely nearby address/street result or over nothing. Now: - Administrative polygon layers are excluded from the preference order entirely. - A 10 km max-useful-distance guard applies to every candidate layer. - Null-safe distance logging. ## 4. Tarp and yard drain solution copy db/migrations/00321_virtual_inspection.sql: updates the EN/ES solution copy for the Tarp or plastic cover and Yard drain habitat types (e.g. yard drain now suggests fitting screen mesh over the pipe opening and notes residents may request mesh from the district). doc/mosquito-check-solutions.md updated to match. Caveat: 00321 already exists on main; if it has already run in an environment, the edited INSERT rows will not re-apply there — only fresh databases see the new copy. Confirm this is acceptable before merge, or move the copy change into a new migration. ## Files 11 changed: 2 migrations (00321 modified, 00359 added), platform/geocode/geocode.go, platform/virtual_inspection.go, resource/communication.go, platform/email/virtual_inspection_request.go + 2 email templates, MosquitoCheckSendModal.vue (new), Communication.vue, doc/mosquito-check-solutions.md. ## Verification - gofmt clean; go build -tags tiff OK - go test ./api -run TestMosquitoCheckRoutesRegister passes - pnpm run build-sync and build-rmo both pass ## Update (2026-09-04) Rebased onto current main (493c13f8). Migration renumbered again: 00359 → 00363 (main consumed 00358-00361 today). Verified: gofmt, go build -tags tiff, TestMosquitoCheckRoutesRegister, build-sync, build-rmo all pass on the rebuilt branch.
benjaminsperry force-pushed mosquito-check-updates from 4102a26c08
All checks were successful
/ golint (push) Successful in 2m6s
/ pnpm-build (push) Successful in 1m36s
/ gotest (push) Successful in 1m47s
to 740e61d103
All checks were successful
/ golint (push) Successful in 2m36s
/ pnpm-build (push) Successful in 1m37s
/ gotest (push) Successful in 1m48s
/ pnpm-test (push) Successful in 10s
2026-09-04 16:40:33 +00:00
Compare
eliribble requested changes 2026-09-04 21:00:08 +00:00
Dismissed
@ -105,3 +105,2 @@
'Una lona sobre leña, equipo o un vehículo donde el agua se acumula en los pliegues.',
'Pull tarps tight so water can''t pool.<br>Poke small drainage holes at the low points.<br>Store items under a solid roof when possible.',
'Estire las lonas para que el agua no se acumule.<br>Haga pequeños agujeros de drenaje en los puntos bajos.<br>Guarde los objetos bajo un techo sólido cuando sea posible.',
'Pull tarps tight so they have no dips or hollows where water can pool.<br>If water does collect, empty it out.<br>Check that sprinklers are not refilling the tarp.',
Owner

This likely won't work the way you're hoping. We've already run this migration in every environment and changing it now won't do anything to the displayed text. You'll need to either create a new migration to change this content in the database or port the content out of the database (which is what I'd recommend).

This likely won't work the way you're hoping. We've already run this migration in every environment and changing it now won't do anything to the displayed text. You'll need to either create a new migration to change this content in the database or port the content out of the database (which is what I'd recommend).
@ -862,0 +864,4 @@
// the literal {link} placeholder is substituted with the short link at send
// time. The frontend send modal prefills this same string, so it must match
// exactly.
const defaultMosquitoCheckMessage = "Delta MVCD Mosquito Check — complete your guided yard check: {link}"
Owner

Definitely should not have 'Delta MVCD', a specific customer, in the code base as a default. Feel free to inject the current active customer, similar to how the link is injected, but not this.

Definitely should *not* have 'Delta MVCD', a specific customer, in the code base as a default. Feel free to inject the current active customer, similar to how the link is injected, but not this.
@ -0,0 +82,4 @@
// (platform/virtual_inspection.go CommunicationMosquitoCheckCreate). The
// literal {link} placeholder is substituted with the short link at send time.
const DEFAULT_MESSAGE =
"Delta MVCD Mosquito Check — complete your guided yard check: {link}";
Owner

Same as above, should not have a checked-in customer specific message in code.

Same as above, should not have a checked-in customer specific message in code.
benjaminsperry force-pushed mosquito-check-updates from 740e61d103
All checks were successful
/ golint (push) Successful in 2m36s
/ pnpm-build (push) Successful in 1m37s
/ gotest (push) Successful in 1m48s
/ pnpm-test (push) Successful in 10s
to 142aa1e912
All checks were successful
/ golint (push) Successful in 2m21s
/ pnpm-build (push) Successful in 1m38s
/ gotest (push) Successful in 1m50s
/ pnpm-test (push) Successful in 10s
2026-09-05 00:03:08 +00:00
Compare
Author
Owner

All three review comments addressed. Branch also rebased onto current main, and the off-property migration was renumbered 00363 → 00365 (main now owns 00363_short_link / 00364_job_history — a duplicate 00363 would have broken the migration sequence at startup).

  • Habitat solution copy (#2157): wording is out of the DB entirely — ships in code (ts/rmo/content/mosquito/habitatCopy.ts, zero-import, shared by the resident walk and the sync staff card); the API no longer serves habitat text (0bb05b8f); migration 00366 drops the six text columns (1c73b0aa); the 00321 wording edit is reverted.
  • Customer in code (#2158/#2159): no customer literals remain on the send surface. Empty-message default is composed at send time from the active org name (7482f3b0); email subject and footer are org-driven with a dynamic year; the modal opens blank with a "leave blank for the district's default message" hint (b18bff9c), so the send copy has one server-side source.
  • Audit findings folded in: the HTML and TXT email templates now branch identically for custom vs canned copy (a custom message previously left the canned Spanish section rendering in HTML while TXT dropped it); the resident state carries org_name/phone_office; the wizard's remaining district/phone references are tokenized ({district_name}/{district_phone}) and substituted at render from org state (ad5a10a2, 886f8c8e, 842294ce). doc/mosquito-check-solutions.md stays canonical and notes the token behavior.

Verified on the final head: go build -tags tiff, pnpm build-rmo, pnpm build-sync all pass; pre-commit hooks clean per commit.

Two notes: the invalid/expired-link screen has no org context (no inspection exists), so it renders a neutral "contact your mosquito control district" line without a phone — happy to wire the district phone in another way if that screen needs it. And snail-mailer templates remain per-district authored content (out of scope for org-parameterization by design).

All three review comments addressed. Branch also rebased onto current main, and the off-property migration was renumbered 00363 → 00365 (main now owns 00363_short_link / 00364_job_history — a duplicate 00363 would have broken the migration sequence at startup). - **Habitat solution copy (#2157):** wording is out of the DB entirely — ships in code (`ts/rmo/content/mosquito/habitatCopy.ts`, zero-import, shared by the resident walk and the sync staff card); the API no longer serves habitat text (0bb05b8f); migration 00366 drops the six text columns (1c73b0aa); the 00321 wording edit is reverted. - **Customer in code (#2158/#2159):** no customer literals remain on the send surface. Empty-message default is composed at send time from the active org name (7482f3b0); email subject and footer are org-driven with a dynamic year; the modal opens blank with a "leave blank for the district's default message" hint (b18bff9c), so the send copy has one server-side source. - **Audit findings folded in:** the HTML and TXT email templates now branch identically for custom vs canned copy (a custom message previously left the canned Spanish section rendering in HTML while TXT dropped it); the resident state carries `org_name`/`phone_office`; the wizard's remaining district/phone references are tokenized (`{district_name}`/`{district_phone}`) and substituted at render from org state (ad5a10a2, 886f8c8e, 842294ce). `doc/mosquito-check-solutions.md` stays canonical and notes the token behavior. Verified on the final head: `go build -tags tiff`, `pnpm build-rmo`, `pnpm build-sync` all pass; pre-commit hooks clean per commit. Two notes: the invalid/expired-link screen has no org context (no inspection exists), so it renders a neutral "contact your mosquito control district" line without a phone — happy to wire the district phone in another way if that screen needs it. And snail-mailer templates remain per-district authored content (out of scope for org-parameterization by design).
Author
Owner

Reply to your review comment on db/migrations/00321_virtual_inspection.sql:

Fixed: habitat wording no longer lives in the database at all, so there is no seeded copy to go stale.

  • Labels/descriptions/solutions now ship in code: ts/rmo/content/mosquito/habitatCopy.ts, a zero-import module shared by the resident walk and the sync staff card (158b927a).
  • The API stopped serving habitat text from habitat_type (0bb05b8f); the sync staff card resolves labels from the same dictionary by key.
  • New migration 00366 drops the six text columns (goose Up/Down), regenerated with jet (1c73b0aa).
  • The wording edit to 00321 from the original diff is reverted, so no already-run migration changes.
  • doc/mosquito-check-solutions.md remains the canonical human copy reference.

Later commits tokenize the district references those rows carried ({district_name}/{district_phone}) — see the summary comment.

Reply to your review comment on `db/migrations/00321_virtual_inspection.sql`: Fixed: habitat wording no longer lives in the database at all, so there is no seeded copy to go stale. - Labels/descriptions/solutions now ship in code: `ts/rmo/content/mosquito/habitatCopy.ts`, a zero-import module shared by the resident walk and the sync staff card (158b927a). - The API stopped serving habitat text from `habitat_type` (0bb05b8f); the sync staff card resolves labels from the same dictionary by key. - New migration 00366 drops the six text columns (goose Up/Down), regenerated with jet (1c73b0aa). - The wording edit to 00321 from the original diff is reverted, so no already-run migration changes. - `doc/mosquito-check-solutions.md` remains the canonical human copy reference. Later commits tokenize the district references those rows carried (`{district_name}`/`{district_phone}`) — see the summary comment.
Author
Owner

Reply to your review comment on platform/virtual_inspection.go (default send message):

Fixed: the hard-coded default is gone. With an empty message the send now composes <OrgName> Mosquito Check — complete your guided yard check: <link> at send time from user.Organization.Name() (7482f3b0), i.e. the active customer is injected exactly like the link. The email subject (Your Mosquito Check — <OrgName>) and template footer (dynamic year + org name) are org-driven too, and the doc comments now describe the two channel defaults accurately (SMS one-liner vs canned bilingual email). No customer literal remains in platform/virtual_inspection.go.

Reply to your review comment on `platform/virtual_inspection.go` (default send message): Fixed: the hard-coded default is gone. With an empty message the send now composes `<OrgName> Mosquito Check — complete your guided yard check: <link>` at send time from `user.Organization.Name()` (7482f3b0), i.e. the active customer is injected exactly like the link. The email subject (`Your Mosquito Check — <OrgName>`) and template footer (dynamic year + org name) are org-driven too, and the doc comments now describe the two channel defaults accurately (SMS one-liner vs canned bilingual email). No customer literal remains in `platform/virtual_inspection.go`.
Author
Owner

Reply to your review comment on ts/components/MosquitoCheckSendModal.vue:

Fixed in b18bff9c: the modal no longer carries a DEFAULT_MESSAGE literal (or the "must match exactly" comment). It opens blank with helper text — {link} is replaced automatically; leave blank to send the district's default message — and the server owns the default composition, so the send copy has a single source. Staff can still type a custom message per resident.

Reply to your review comment on `ts/components/MosquitoCheckSendModal.vue`: Fixed in b18bff9c: the modal no longer carries a `DEFAULT_MESSAGE` literal (or the "must match exactly" comment). It opens blank with helper text — `{link}` is replaced automatically; leave blank to send the district's default message — and the server owns the default composition, so the send copy has a single source. Staff can still type a custom message per resident.
eliribble approved these changes 2026-09-05 00:27:45 +00:00
eliribble deleted branch mosquito-check-updates 2026-09-05 00:27:50 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Gleipnir/nidus-sync!211
No description provided.