About:Pharmacopedia.ext: Difference between revisions
From Pharmacopedia
More actions
| [checked revision] | [checked revision] |
MDElliottMD (talk | contribs) Indication terminology cleanup: drop stale 'indicationslookup' API reference (the legacy IndicationsLookupApi was deleted in Phase 5b; the picker now uses pharmacopediaproblemsearch which is already documented in the row above). No other user-facing 'indication' strings remain on the wiki outside the legacy {{MedTemplate}} parameter (intentional) and 'contraindication' (separate term). |
MDElliottMD (talk | contribs) Em-dash sweep: replace em-dash with comma per project rule; PendellsCorner verbatim quotes preserved. |
||
| Line 130: | Line 130: | ||
Choice / multi vote elements expose per-option tallies via <code>tallyChoices()</code> on demand. Per-option bars render inline in the picker. The <code>results</code> attribute gates tally visibility: | Choice / multi vote elements expose per-option tallies via <code>tallyChoices()</code> on demand. Per-option bars render inline in the picker. The <code>results</code> attribute gates tally visibility: | ||
* <code>live</code> (default) | * <code>live</code> (default), tally always visible | ||
* <code>after-vote</code> | * <code>after-vote</code>, tally hidden until viewer has voted | ||
* <code>hidden</code> | * <code>hidden</code>, tally never shown (only options + "thanks" on submit) | ||
Server-side options-hash (<code>ve_options_h</code>) detects post-vote option-list edits; the API rejects new votes whose submitted hash doesn't match the live one. Voter identities are stored as HMAC-SHA256 (<code>v_voter_hash</code>) so admins reading the DB cannot map votes to user accounts without the HMAC secret. | Server-side options-hash (<code>ve_options_h</code>) detects post-vote option-list edits; the API rejects new votes whose submitted hash doesn't match the live one. Voter identities are stored as HMAC-SHA256 (<code>v_voter_hash</code>) so admins reading the DB cannot map votes to user accounts without the HMAC secret. | ||
| Line 284: | Line 284: | ||
* '''Date''' (point or range): ISO, MM/DD/YYYY, "Month D YYYY", "Month YYYY", "Season YYYY", "early/mid/late YYYY", bare year, decades ("2010s"); date RANGES via "X to Y", "X till Y", "from X to Y", "X - Y"; relative-to-now ("yesterday", "last week/month/year", "N months ago", "a few weeks ago"); holidays (christmas, halloween, new year's, valentine's, july 4th, thanksgiving with computed 4th-Thursday-of-Nov, MLK / Memorial / Labor day); age-relative ("7y8mo", "51.2yo", "at age 14", "ages 2-10"); life stages ("in childhood", "as a teen", "as a freshman", "junior year"); Nth birthday ("my 30th birthday") | * '''Date''' (point or range): ISO, MM/DD/YYYY, "Month D YYYY", "Month YYYY", "Season YYYY", "early/mid/late YYYY", bare year, decades ("2010s"); date RANGES via "X to Y", "X till Y", "from X to Y", "X - Y"; relative-to-now ("yesterday", "last week/month/year", "N months ago", "a few weeks ago"); holidays (christmas, halloween, new year's, valentine's, july 4th, thanksgiving with computed 4th-Thursday-of-Nov, MLK / Memorial / Labor day); age-relative ("7y8mo", "51.2yo", "at age 14", "ages 2-10"); life stages ("in childhood", "as a teen", "as a freshman", "junior year"); Nth birthday ("my 30th birthday") | ||
* '''Polarity''' (negation detection): "not", "didn't", "did not experience", "never", "without", "denied" → polarity=0 (negative); else 1 (positive) | * '''Polarity''' (negation detection): "not", "didn't", "did not experience", "never", "without", "denied" → polarity=0 (negative); else 1 (positive) | ||
* '''Leading verbs stripped''': "I was diagnosed with", "I took", "started taking", "tried", "was on", "experienced", "felt" | * '''Leading verbs stripped''': "I was diagnosed with", "I took", "started taking", "tried", "was on", "experienced", "felt", so the subject is the noun, not the verb | ||
* '''Adverbs stripped''': "briefly", "occasionally", "frequently", "sometimes", "always" | * '''Adverbs stripped''': "briefly", "occasionally", "frequently", "sometimes", "always", so the subject is the noun, not the modifier | ||
* '''Role splitting''': "from / caused by / due to / while on" → role='cause'; "with / during / while" → role='context' | * '''Role splitting''': "from / caused by / due to / while on" → role='cause'; "with / during / while" → role='context' | ||
* '''Ref resolution''', in priority order: | * '''Ref resolution''', in priority order: | ||
| Line 307: | Line 307: | ||
* Type selector: mood / psychotic / anxiety / panic / trauma response / dissociative / substance use / eating / sleep disturbance / pain flare / migraine / medication adjustment / hospitalization / creative surge / spiritual / transcendent / relationship crisis / grief / somatic / other | * Type selector: mood / psychotic / anxiety / panic / trauma response / dissociative / substance use / eating / sleep disturbance / pain flare / migraine / medication adjustment / hospitalization / creative surge / spiritual / transcendent / relationship crisis / grief / somatic / other | ||
* Subtype (text + datalist) | * Subtype (text + datalist), for mood: depressive / manic / hypomanic / mixed / dysphoric / euthymic | ||
* Severity slider 0-100 (per precision doctrine) | * Severity slider 0-100 (per precision doctrine) | ||
* Date range via PCPDatePicker locked to range mode | * Date range via PCPDatePicker locked to range mode | ||
| Line 330: | Line 330: | ||
Each event type has its own edit route under <code>Special:MyLifeStory</code>: | Each event type has its own edit route under <code>Special:MyLifeStory</code>: | ||
* <code>Special:MyLifeStory/edit-observation/<id></code> | * <code>Special:MyLifeStory/edit-observation/<id></code>, re-parses raw text on save; supports polarity override + date override | ||
* <code>Special:MyLifeStory/edit-episode/<id></code> | * <code>Special:MyLifeStory/edit-episode/<id></code>, full episode form | ||
* <code>Special:MyLifeStory?edit_event=<id></code> | * <code>Special:MyLifeStory?edit_event=<id></code>, existing event / image / keyframe form | ||
All three forms have side-by-side "Delete X" + "Duplicate X" buttons. Duplicate copies fields + refs + keyframe traits (NOT images) and redirects to the new row's edit form. | All three forms have side-by-side "Delete X" + "Duplicate X" buttons. Duplicate copies fields + refs + keyframe traits (NOT images) and redirects to the new row's edit form. | ||
| Line 346: | Line 346: | ||
=== Rule types (vr_rule_type) === | === Rule types (vr_rule_type) === | ||
* '''<code>private</code>''' | * '''<code>private</code>''', explicit deny (only owner) | ||
* '''<code>public</code>''' | * '''<code>public</code>''', explicit allow (anyone) | ||
* '''<code>users</code>''' | * '''<code>users</code>''', payload <code>{user_ids: [...]}</code>; allow if viewer in list | ||
* '''<code>cohort</code>''' | * '''<code>cohort</code>''', payload <code>{cohort_id: N}</code>; allow if viewer in <code>pcp_cohort_members</code> for that cohort | ||
* '''<code>link_token</code>''' | * '''<code>link_token</code>''', payload <code>{token: '...', uses_remaining: null|N}</code>; allow if URL <code>?pcpshare=TOKEN</code> matches | ||
* '''<code>reciprocal</code>''' | * '''<code>reciprocal</code>''', allow if viewer has a matching <code>users</code> rule sharing the same shape back to owner | ||
Rules scope at three levels: <code>(profile, namespace, key)</code>, <code>(profile, namespace, NULL)</code>, or <code>(profile, '*', NULL)</code>. Most-specific-first matching. Rules can have <code>vr_expires</code> (time-bounded) and <code>vr_revoked</code> (preserves audit trail). | Rules scope at three levels: <code>(profile, namespace, key)</code>, <code>(profile, namespace, NULL)</code>, or <code>(profile, '*', NULL)</code>. Most-specific-first matching. Rules can have <code>vr_expires</code> (time-bounded) and <code>vr_revoked</code> (preserves audit trail). | ||
| Line 384: | Line 384: | ||
API route: same <code>pharmacopediavote</code> endpoint; presence of <code>choices</code> or <code>options_h</code> params routes to <code>castChoice()</code>. Response includes <code>tally</code> + <code>user_choices</code> (null for binary). Tally hidden per <code>results</code> policy. | API route: same <code>pharmacopediavote</code> endpoint; presence of <code>choices</code> or <code>options_h</code> params routes to <code>castChoice()</code>. Response includes <code>tally</code> + <code>user_choices</code> (null for binary). Tally hidden per <code>results</code> policy. | ||
Drift behavior: if the page editor changes the options list after votes exist, <code>ve_options_h</code> updates and new votes' <code>v_options_h</code> reflects the new value. Existing votes stay but their hash no longer matches (marked stale). New votes whose submitted hash mismatches the live one are rejected | Drift behavior: if the page editor changes the options list after votes exist, <code>ve_options_h</code> updates and new votes' <code>v_options_h</code> reflects the new value. Existing votes stay but their hash no longer matches (marked stale). New votes whose submitted hash mismatches the live one are rejected, protects against browser cache races. Tallies still aggregate by raw index, so RENAMING an option in place silently turns old votes into new-label votes; reordering is the dangerous case. Appending new options is safe. | ||
== Research ID == | == Research ID == | ||
| Line 399: | Line 399: | ||
Wired into: | Wired into: | ||
* <code>LifeStoryStore::addImage()</code> | * <code>LifeStoryStore::addImage()</code>, life events / episodes / observations | ||
* <code>LiteratureStore::storeUploadedPdf()</code> | * <code>LiteratureStore::storeUploadedPdf()</code>, literature PDFs | ||
* <code>ProviderAppStore::saveUploadedFile()</code> | * <code>ProviderAppStore::saveUploadedFile()</code>, provider verification documents | ||
<code>AttachmentScanner</code> (used by feature-request attachments) is left alone | <code>AttachmentScanner</code> (used by feature-request attachments) is left alone, its status-return model is intentional for the queued-moderation flow there. <code>AntivirusHelper</code> (the old silent-no-op variant) was deleted; all callers consolidated onto <code>VirusScanner</code>. | ||
== Autosave infrastructure == | == Autosave infrastructure == | ||
| Line 542: | Line 542: | ||
* Efficacy (0–100 slider) | * Efficacy (0–100 slider) | ||
* Side-effect burden (0–100 slider) | * Side-effect burden (0–100 slider) | ||
* Stop reasons (personal + stopped only): JSON multi-select with optional severity slider per reason | * Stop reasons (personal + stopped only): JSON multi-select with optional severity slider per reason, codes: side_effects / ineffective / cost / no_longer_needed / clinician_advised / other | ||
* Free-text anecdote | * Free-text anecdote | ||
* Problems addressed (multi-pick with per-problem efficacy) | * Problems addressed (multi-pick with per-problem efficacy) | ||
| Line 599: | Line 599: | ||
== Notable lessons learned == | == Notable lessons learned == | ||
* '''Synthetic Event needs bubbles:true to trigger delegated listeners.''' <code>new Event('input')</code> defaults to <code>bubbles:false</code>, so listeners on parent wrappers never see programmatic dispatches. Native input/change events bubble by default; only JS-fired ones don't. Pass <code>{ bubbles: true }</code> explicitly. Bit DatePicker calendar-cell clicks 2026-05-18 | * '''Synthetic Event needs bubbles:true to trigger delegated listeners.''' <code>new Event('input')</code> defaults to <code>bubbles:false</code>, so listeners on parent wrappers never see programmatic dispatches. Native input/change events bubble by default; only JS-fired ones don't. Pass <code>{ bubbles: true }</code> explicitly. Bit DatePicker calendar-cell clicks 2026-05-18, typing in the text field autosaved fine, but picking a date from the calendar didn't, because blocksave's wrapper listener never received the event. | ||
* '''MediaWiki form-field names collide with reserved URL params.''' A form <code><input name="title"></code> or <code>name="action"></code> with user-controlled value silently HIJACKS MW's dispatch when POSTed (body param overrides URL param). Symptom: form submits but lands on a wiki article named whatever the user typed, with URL bar still showing the special page. Bit the episode form 2026-05-18 (user typed 'Fake one' as title → got a 404 'create article: Fake one' page). Fix: prefix ALL custom form inputs with <code>pcp_</code> (both the input <code>name="..."</code> AND the matching <code>$request->getVal('...', ...)</code> read). Self-referential hidden inputs (<code>name="title" value="<getPageTitle()>"</code>) are safe. | * '''MediaWiki form-field names collide with reserved URL params.''' A form <code><input name="title"></code> or <code>name="action"></code> with user-controlled value silently HIJACKS MW's dispatch when POSTed (body param overrides URL param). Symptom: form submits but lands on a wiki article named whatever the user typed, with URL bar still showing the special page. Bit the episode form 2026-05-18 (user typed 'Fake one' as title → got a 404 'create article: Fake one' page). Fix: prefix ALL custom form inputs with <code>pcp_</code> (both the input <code>name="..."</code> AND the matching <code>$request->getVal('...', ...)</code> read). Self-referential hidden inputs (<code>name="title" value="<getPageTitle()>"</code>) are safe. | ||