Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

About:Pharmacopedia.ext

From Pharmacopedia
Revision as of 02:30, 16 May 2026 by Maintenance script (talk | contribs) (Terminology sweep (site-wide): drug/medication → medicine)

Template:TOC right

Pharmacopedia extension — specification

Version: 0.7.10 · Requires: MediaWiki ≥ 1.45.0 Author: MDElliottMD · License: GPL-2.0-or-later Source: /var/www/mediawiki/extensions/Pharmacopedia/

The Pharmacopedia extension turns a MediaWiki install into a structured, community-edited medicine reference. It adds parser tags, special pages, API modules, and a database schema that together support:

  • Structured medicine pages via the {{MedTemplate}} template
  • Per-user rating, voting, and reporting on effects, indications, titration strategies, anecdotes, and interactions
  • Two-perspective data capture (personal vs. provider) wherever clinically meaningful
  • Curated medicine-class categories used as interaction endpoints
  • A verified-provider role with document-based verification

High-level architecture

  • Backend (PHP): includes/ — one class per parser tag, store, special page, or API module. Auto-loaded under MediaWiki\Extension\Pharmacopedia\.
  • Frontend (JS): resources/ext.pharmacopedia.js — single IIFE binding click handlers, modals, and inline AJAX submits.
  • Styles (CSS): resources/ext.pharmacopedia.css — shared row layout, per-tag chrome, dark-theme-friendly colors.
  • Schema: sql/ — ten core tables plus four migration patches. Picked up via the LoadExtensionSchemaUpdates hook.

Parser tags

All eight tags are registered via Hooks::onParserFirstCallInit:

Tag Purpose Class
<vote> Generic up/down binary vote on an arbitrary slug VoteTag
<effect> Therapeutic or adverse effect, dual patient/provider perspectives EffectTag
<discuss> Threaded comment widget CommentTag
<effectsummary> Roll-up aggregate header EffectSummaryTag
<titration> Titration strategy card with up/down vote TitrationTag
<anecdote> Personal or provider story with up/down vote AnecdoteTag
<indication> Condition the medicine is used for, 0–5 likert rating IndicationTag
<pharmaInteractions/> Self-closing; renders the Interactions section for the current page InteractionTag

All tags except <pharmaInteractions/> take a slug argument and (where relevant) a title, label, author, ref, or perspective.

Tag wikitext examples

<indication slug="ssri-depression" title="Major depressive disorder"
  author="MDElliottMD">Use cautiously in adolescents.</indication>

<effect slug="nausea" label="Nausea"/>

<effect ref="hyperkalemia"/>   <!-- ref to global effect library -->

<titration slug="slow-start-elderly" title="Slow start (elderly)"
  author="MDElliottMD">Begin at 10 mg q AM; titrate by 10 mg every 14 days.</titration>

<anecdote slug="qi8sg2" perspective="provider"
  author="MDElliottMD">One patient developed serotonin syndrome at week 3...</anecdote>

<pharmaInteractions/>

The unified compact row layout

Indication, Effect, Interaction, Titration, and Anecdote all render through a shared row pattern:

<div class="pcp-row pcp-row-{type} pcp-{type}" ...data-*>
  <div class="pcp-row-head">
    <span class="pcp-row-title">...</span>
    <span class="pcp-row-aggs">...</span>
    <span class="pcp-row-actions">
      <button class="pcp-row-action pcp-row-action-toggle" data-target="rate">Rate</button>
      [<button data-target="notes">Notes (N)</button>]    # only for Interaction
      [× delete button]                                   # only for sysop/admin
    </span>
  </div>
  [<div class="pcp-row-panel pcp-row-rate-panel"   hidden>...</div>]
  [<div class="pcp-row-panel pcp-row-notes-panel"  hidden>...</div>]
  [<div class="pcp-row-body">...wikitext body, always visible if present...</div>]
</div>
  • Rate / Notes panels are hidden by default; the shared .pcp-row-action-toggle JS handler reveals them inline on click.
  • Bodies (page-specific descriptions for Indication / Effect / Titration / Anecdote) are always visible.
  • The × admin delete button uses the established .pcp-del-btn pattern (or .pcp-ix-del-row for interactions).
  • Each row sits in its own block-formatting context (display: flow-root) so its border-box respects floated infoboxes.

Voting / rating semantics

Element Scale Perspectives Storage
Vote tag +1 / −1 binary single pcp_votes
Titration +1 / −1 binary single pcp_votes
Anecdote +1 / −1 binary single (perspective is a metadata label, not a separate aggregate) pcp_votes
Indication 0–5 likert + "don't know" single pcp_likert_reports
Effect (patient) experienced ∈ {yes, no, unsure} + valence −3..+3 patient pcp_effect_reports (perspective=1)
Effect (provider) frequency ∈ {0, 5, 20, 33, 50, 66, 80, 95, −1 don't know} + valence −3..+3 provider pcp_effect_reports (perspective=2)
Interaction experience 1–5 + outcome −3..+3 + optional free-text note user + provider, separate aggregates pcp_interaction_reports

Server-side aggregates: n, mean of the rating field, and (where applicable) severe = (vmean ≤ −2.5).

Aggregates are recomputed and returned by every report-submit API call so the row re-renders in place without a page reload.

Effect bucketing

When a wiki <ul> contains only <effect> cards, JavaScript groups them into buckets by the provider frequency mean (data-fmean):

Bucket fmean band Default state
Common > 20 expanded, always visible
Uncommon > 5 and ≤ 20 collapsed
Rare ≤ 5, provider vmean > −2.5 collapsed
Rare but Severe ≤ 5 and vmean ≤ −2.5 expanded by default, red highlight
Not yet rated no provider data (n=0) collapsed, only renders if non-empty

The vmean ≤ −2.5 threshold is also the trip-wire for the "severe" red treatment on interaction rows.

Interactions feature

The Interactions section is rendered by placing <pharmaInteractions/> anywhere in the wikitext of a medicine article (NS_MAIN) or a Category page (NS_CATEGORY).

Entity model

An interaction is an undirected edge between two endpoints. Each endpoint has a type (medicine or category) and a slug (DB-key form of the page title).

Pairs are stored in canonical order: smaller (type, slug) tuple on the left. This collapses A↔B and B↔A into a single row.

Tables

pcp_interactions: one row per interaction edge.

 pi_id              auto-increment
 pi_element_id      FK -> pcp_votable_elements (reuse votes/comments infra)
 pi_left_type       'medicine' | 'category'
 pi_left_slug       VARBINARY(255)
 pi_right_type      'medicine' | 'category'
 pi_right_slug      VARBINARY(255)
 pi_created_user_id INT
 pi_created         BINARY(14)
 UNIQUE (pi_left_type, pi_left_slug, pi_right_type, pi_right_slug)

pcp_interaction_reports: one row per (interaction, user, perspective).

 pir_element_id   FK -> pcp_votable_elements
 pir_user_id      INT
 pir_perspective  1 = user, 2 = provider
 pir_experience   TINYINT (1..5, nullable)
 pir_valence      TINYINT (-3..+3, nullable)
 pir_note         MEDIUMBLOB (nullable)
 pir_created      BINARY(14)
 pir_updated      BINARY(14)
 UNIQUE (pir_element_id, pir_user_id, pir_perspective)

Rendering rules

  • On a medicine page M, list:
    • Direct edges: rows where M is one side.
    • Transitive edges: rows where one side is a category C that M is itself a member of (via MW's categorylinks).
  • Direct wins: if the same counterparty is reachable both directly and transitively, drop the transitive duplicate.
  • On a Category page, list direct edges only (no transitive walk).
  • Sort: pooled valence_mean ascending (most negative on top). Nulls sink. Tiebreakers: n desc, then alphabetic.
  • Severe (any of pooled / user / provider vmean ≤ −2.5): red 4 px left border + red-tinted background + "severe" pill + counterparty title in red.

Add-interaction modal

Triggered by the + Add interaction button at the bottom of the section. Two-stage UX:

  1. Search input → results split into Medicines (teal chip) and Categories (amber chip).
  2. Click Use → confirm via Add interaction button → POST to pharmacopediainteractionadd.

Categories appear in the modal only if tagged with the marker category (default Category:MedCategory, configurable via $wgPharmacopediaInteractionCategoryMarker). The transitive walk during rendering, however, uses all of a page's categories regardless of marker — historic data isn't hidden by changing the marker policy.

Delete

Two a