MediaWiki:Gadget-DiagnosticTreeScoring.js: Difference between revisions

From Painwiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 15: Line 15:


   /* Interface HTML template — injected into the host element on boot */
   /* Interface HTML template — injected into the host element on boot */
   var INTERFACE_HTML = "<div class=\"proto-shell\">\n\n<!-- MASTHEAD -->\n<header class=\"proto-masthead\">\n  <div class=\"proto-logo\">Pain<span>Wiki</span></div>\n  <div class=\"proto-right\">\n    <span class=\"proto-region\">Diagnostic Algorithm &middot; Upper Thoracic Back Pain</span>\n    <button class=\"learning-toggle\" id=\"learning-toggle-btn\">Learning: ON</button>\n  </div>\n</header>\n\n<!-- RED FLAGS -->\n<div class=\"rf-outer\">\n  <div class=\"rf-panel-wrap\" id=\"rf-emergency-panel\">\n    <div class=\"rf-col-header\">&#9888; Emergency &mdash; stop and act if any are present</div>\n    <div class=\"rf-items\" id=\"rf-emergency-items\"></div>\n  </div>\n  <div class=\"rf-panel-wrap urgent\" id=\"rf-urgent-panel\">\n    <div class=\"rf-col-header\">&#9888; Urgent &mdash; refer before myofascial assessment</div>\n    <div class=\"rf-items\" id=\"rf-urgent-items\"></div>\n    <div class=\"rf-affirm\">\n      <div class=\"rf-affirm-note\">All emergency and urgent flags screened and negative &mdash; or appropriate action taken. Safe to proceed.</div>\n      <button class=\"btn-affirm\" id=\"btn-affirm\">Proceed to clinical interview &rarr;</button>\n      <div class=\"affirmed-badge\" id=\"affirmed-badge\">Red flags cleared</div>\n    </div>\n  </div>\n</div>\n\n<!-- MAIN GRID -->\n<div id=\"main-grid\">\n  <div class=\"col-left\">\n    <div id=\"question-area\"></div>\n    <div class=\"dt-trail-panel\" id=\"trail-panel\">\n      <div class=\"dt-trail-head\">\n        <span class=\"dt-trail-title\">Clinical Interview Record</span>\n        <span class=\"dt-trail-meta\" id=\"trail-counter\"></span>\n      </div>\n      <div id=\"trail-items\"></div>\n    </div>\n  </div>\n  <div class=\"col-right\">\n    <div class=\"dt-running-panel\">\n      <div class=\"dt-running-head\">\n        <span class=\"dt-running-title\">Running Differential</span>\n        <span class=\"dt-running-meta\" id=\"q-counter\">Prior weights</span>\n      </div>\n      <div id=\"diff-rows\"></div>\n      <div class=\"dt-diff-caption\">Diagnostic weights are relative, not absolute probabilities. Initial values reflect muscle prevalence and clinical feature patterns from Travell &amp; Simons. Weights update as cases are confirmed.<br>Powered by LDA with Dirichlet-Multinomial updating.</div>\n    </div>\n    <div class=\"dt-treat-panel\" id=\"treat-panel\">\n      <div class=\"dt-treat-head\"><span class=\"dt-treat-title\">Suggested Treatment Order</span></div>\n      <div id=\"treat-items\"></div>\n      <div class=\"dt-treat-caption\">Sequence derived from Travell &amp; Simons satellite TrP and functional unit relationships, ordered by diagnostic weight.</div>\n    </div>\n    <div class=\"learn-panel\" id=\"learn-panel\">\n      <div class=\"learn-head\">\n        <span class=\"learn-title\">Confirmed Cases &mdash; This Device</span>\n        <span class=\"learn-meta\" id=\"learn-total\"></span>\n      </div>\n      <div class=\"learn-intro\"><strong>Multiple muscles can be confirmed per case.</strong> Confirm every contributing muscle &mdash; each confirmation independently updates the model.</div>\n      <div id=\"learn-rows\"></div>\n      <div class=\"learn-caption\">Confirmed diagnoses update prior weights via Dirichlet-Multinomial conjugate updating. Powered by LDA.</div>\n    </div>\n  </div>\n\n  <!-- BROAD DIFFERENTIAL \u2014 gadget style, spans full width -->\n  <div style=\"grid-column:1/-1\">\n    <div class=\"dt-broad-panel\" id=\"broad-panel\">\n      <div class=\"dt-broad-header\">\n        <div class=\"dt-broad-header-top\">\n          <div class=\"dt-broad-title\">&#9632; Broad Differential Diagnosis</div>\n          <button class=\"dt-broad-toggle\" id=\"broad-toggle-btn\">Hide</button>\n        </div>\n        <div class=\"dt-epigraph\">\n          &#8220;If he does not expect the unexpected, he will not discover it &mdash;\n          for it is difficult to discover and intractable.&#8221;\n          <cite>&mdash; Heraclitus, Fr. 18</cite>\n        </div>\n      </div>\n      <div class=\"dt-broad-grid\" id=\"broad-grid\"></div>\n    </div>\n  </div>\n</div><!-- /main-grid -->\n\n<div class=\"proto-foot\">\n  <div class=\"proto-disclaimer\"><strong>Not validated for clinical use.</strong> Research prototype for educational purposes. LR values derived from Travell &amp; Simons clinical descriptions, not formal epidemiological studies.</div>\n  <button class=\"btn-reset\" id=\"reset-btn\">&#8635; Start over</button>\n</div>\n</div><!-- /proto-shell -->\n\n<script>\nconst SCHEMA={\"model\": \"bayesian_lr_scoring\", \"version\": \"1.0\", \"description\": \"Probabilistic scoring tree for upper thoracic back pain using Bayesian likelihood ratio updating with Thurstonian pairwise tiebreakers\", \"thresholds\": {\"early_exit_posterior\": 0.55, \"early_exit_gap\": 0.18, \"pairwise_trigger\": 0.22}, \"muscles\": {\"trapezius_trp2\": {\"label\": \"Upper Trapezius\", \"prior\": 0.18, \"page\": \"Muscle:Trapezius\", \"key_trp_note\": \"Lower Trapezius TrP3 is the key TrP that most commonly induces upper trapezius satellites. Treat lower trapezius first when upper trapezius TrPs recur or respond poorly. The lower trapezius may itself be a satellite of key TrPs in the latissimus dorsi.\", \"subtitle\": \"Upper fibres \\u2014 posterolateral neck, behind ear, temple\"}, \"trapezius_trp3\": {\"label\": \"Lower Trapezius\", \"prior\": 0.13, \"page\": \"Muscle:Trapezius\", \"subtitle\": \"Lower fibres \\u2014 cervical, mastoid, suprascapular (\\\"the joker\\\")\"}, \"levator\": {\"label\": \"Levator Scapulae\", \"prior\": 0.16, \"page\": \"Muscle:Levator_Scapulae\", \"key_trp_note\": \"Check Lower Trapezius TrP3 first \\u2014 it is often the key TrP driving levator scapulae and upper trapezius satellites. If levator TrPs respond poorly to treatment, lower trapezius TrP3 has not been addressed. Upper trapezius TrP1/2 also drives levator as a satellite.\", \"subtitle\": \"Neck angle stiffness, cannot look over sore shoulder\"}, \"rhomboids\": {\"label\": \"Rhomboids\", \"prior\": 0.1, \"page\": \"Muscle:Rhomboids\", \"subtitle\": \"Medial scapular border burning, present at rest\"}, \"multifidi\": {\"label\": \"Multifidi\", \"prior\": 0.09, \"page\": \"Muscle:Multifidi\", \"subtitle\": \"Deep paraspinal ache immediately beside the spine\"}, \"scaleni\": {\"label\": \"Scaleni\", \"prior\": 0.11, \"page\": \"Muscle:Scalene\", \"key_trp_note\": \"Scaleni drive satellite TrPs in upper trapezius, triceps long head, pectoralis major and minor, and serratus posterior superior. Treat scaleni before their satellites. SCM should be treated concurrently.\", \"subtitle\": \"Upper back + arm/hand symptoms, nocturnal arm aching\"}, \"supraspinatus\": {\"label\": \"Supraspinatus\", \"prior\": 0.1, \"page\": \"Muscle:Supraspinatus\", \"key_trp_note\": \"Infraspinatus TrPs are often the key TrP source driving supraspinatus satellites. If supraspinatus TrPs recur, examine infraspinatus.\", \"subtitle\": \"Mid-deltoid ache, painful arc on shoulder abduction\"}, \"splenius\": {\"label\": \"Splenius Cervicis\", \"prior\": 0.07, \"page\": \"Muscle:Splenius_Cervicis\", \"subtitle\": \"Top of shoulder, rotation restricted away from pain\"}, \"triceps_trp1\": {\"label\": \"Triceps Brachii (Long Head)\", \"prior\": 0.04, \"page\": \"Muscle:Triceps_Brachii\", \"subtitle\": \"Posterior arm to scapula, skips the elbow\"}, \"biceps\": {\"label\": \"Biceps Brachii\", \"prior\": 0.02, \"page\": \"Muscle:Biceps_Brachii\", \"subtitle\": \"Anterior shoulder primary, suprascapular spillover\"}}, \"questions\": [{\"id\": \"q_neck_rotation\", \"text\": \"Is cervical rotation restricted or painful in any direction?\", \"sublabel\": \"Can the patient NOT turn their head to look fully over one or both shoulders?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 neck rotation is restricted or painful\", \"lr\": {\"levator\": 4.5, \"splenius\": 4.0, \"trapezius_trp2\": 1.4, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.6, \"scaleni\": 1.5, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 full pain-free cervical rotation in both directions\", \"lr\": {\"levator\": 0.15, \"splenius\": 0.18, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.4, \"multifidi\": 1.3, \"scaleni\": 0.8, \"supraspinatus\": 1.2, \"triceps_trp1\": 1.3, \"biceps\": 1.4}}]}, {\"id\": \"q_rotation_direction\", \"text\": \"In which direction is cervical rotation most restricted or painful?\", \"sublabel\": \"Turn the head toward the painful side, then away \\u2014 which direction is more limited?\", \"type\": \"choice\", \"condition\": {\"q_neck_rotation\": \"yes\"}, \"answers\": [{\"id\": \"toward\", \"label\": \"Cannot fully turn TOWARD the painful side\", \"sublabel\": \"Cannot look over the sore shoulder\", \"lr\": {\"levator\": 6.0, \"splenius\": 0.15, \"trapezius_trp2\": 1.2, \"scaleni\": 1.3, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"triceps_trp1\": 0.7, \"biceps\": 0.6}}, {\"id\": \"away\", \"label\": \"Cannot fully turn AWAY from the painful side\", \"sublabel\": \"Cannot look over the opposite shoulder; OR pain at top of shoulder\", \"lr\": {\"splenius\": 6.0, \"levator\": 0.15, \"trapezius_trp2\": 1.0, \"scaleni\": 1.2, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.6, \"biceps\": 0.5}}, {\"id\": \"both\", \"label\": \"Restricted in both directions\", \"lr\": {\"levator\": 2.0, \"splenius\": 2.0, \"scaleni\": 2.5, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.8, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}]}, {\"id\": \"q_pain_location\", \"text\": \"Where is the upper thoracic back pain most intense?\", \"sublabel\": \"Choose the location that best describes the dominant pain zone\", \"type\": \"choice\", \"answers\": [{\"id\": \"medial_border\", \"label\": \"Along the medial border of the scapula \\u2014 inner edge of the shoulder blade\", \"sublabel\": \"A burning sensation specifically along the edge of the scapula\", \"lr\": {\"rhomboids\": 6.0, \"trapezius_trp3\": 1.5, \"scaleni\": 2.0, \"levator\": 1.0, \"trapezius_trp2\": 0.6, \"multifidi\": 0.3, \"supraspinatus\": 0.5, \"splenius\": 0.4, \"triceps_trp1\": 0.5, \"biceps\": 0.3}}, {\"id\": \"interscapular\", \"label\": \"Between the shoulder blades \\u2014 diffuse interscapular or upper thoracic\", \"sublabel\": \"Aching or burning between or below the scapulae\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 3.0, \"rhomboids\": 1.5, \"scaleni\": 2.0, \"levator\": 0.8, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"splenius\": 0.6, \"triceps_trp1\": 1.5, \"biceps\": 1.5}}, {\"id\": \"paraspinal\", \"label\": \"Deep unilateral paraspinal ache \\u2014 beside the spine only\", \"sublabel\": \"Strictly beside the spine, not reaching the scapula\", \"lr\": {\"multifidi\": 5.0, \"trapezius_trp3\": 2.0, \"rhomboids\": 0.4, \"trapezius_trp2\": 0.5, \"levator\": 0.6, \"scaleni\": 0.5, \"supraspinatus\": 0.3, \"splenius\": 0.7, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder or neck angle \\u2014 not the scapular blade\", \"sublabel\": \"Pain at the peak of the shoulder slope or in the neck-shoulder angle\", \"lr\": {\"levator\": 3.5, \"splenius\": 4.0, \"trapezius_trp2\": 2.5, \"scaleni\": 1.5, \"trapezius_trp3\": 0.8, \"rhomboids\": 0.4, \"multifidi\": 0.3, \"supraspinatus\": 0.6, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"posterior_arm\", \"label\": \"Posterior arm or posterior shoulder \\u2014 back of the arm is the dominant pain\", \"sublabel\": \"Upper back aching is secondary to a posterior arm or shoulder complaint\", \"lr\": {\"triceps_trp1\": 7.0, \"biceps\": 0.3, \"supraspinatus\": 0.8, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"multifidi\": 0.2, \"scaleni\": 0.5, \"levator\": 0.4, \"splenius\": 0.3}}]}, {\"id\": \"q_arm_symptoms\", \"text\": \"Are there arm or hand symptoms alongside the upper back pain?\", \"sublabel\": \"Arm numbness, hand swelling, nocturnal arm aching, or arm heaviness\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 arm, forearm, or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"triceps_trp1\": 2.0, \"biceps\": 1.5, \"supraspinatus\": 1.3, \"levator\": 0.7, \"splenius\": 0.7, \"trapezius_trp2\": 0.6, \"trapezius_trp3\": 0.5, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain confined to the upper back and/or neck\", \"lr\": {\"scaleni\": 0.25, \"triceps_trp1\": 0.7, \"biceps\": 0.6, \"supraspinatus\": 0.9, \"levator\": 1.2, \"splenius\": 1.2, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.3, \"rhomboids\": 1.4, \"multifidi\": 1.4}}]}, {\"id\": \"q_nocturnal_arm\", \"text\": \"Is the patient woken at night by arm aching or numbness?\", \"sublabel\": \"Must hang the arm over the side of the bed or shake it for relief\", \"type\": \"binary\", \"condition\": {\"q_arm_symptoms\": \"yes\"}, \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 woken at night; arm hanging over bed side gives relief\", \"lr\": {\"scaleni\": 8.0, \"triceps_trp1\": 0.8, \"biceps\": 0.7, \"supraspinatus\": 0.6, \"levator\": 0.5, \"splenius\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 arm symptoms are daytime or positional only\", \"lr\": {\"scaleni\": 0.5, \"triceps_trp1\": 1.2, \"biceps\": 1.3, \"supraspinatus\": 1.2, \"levator\": 1.0, \"splenius\": 1.0, \"trapezius_trp2\": 1.0, \"trapezius_trp3\": 1.0, \"rhomboids\": 1.0, \"multifidi\": 1.0}}]}, {\"id\": \"q_postural\", \"text\": \"Is the pain clearly worse during or after sustained sitting, desk work, or forward head posture?\", \"sublabel\": \"Pain builds during a sitting work session; relieved by moving or lying down\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 clearly postural; builds with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp2\": 4.0, \"trapezius_trp3\": 3.5, \"levator\": 2.5, \"scaleni\": 2.0, \"splenius\": 2.0, \"multifidi\": 2.0, \"rhomboids\": 1.3, \"supraspinatus\": 1.0, \"triceps_trp1\": 1.5, \"biceps\": 1.0}}, {\"id\": \"no\", \"label\": \"No \\u2014 not clearly worse with posture or sitting\", \"lr\": {\"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.4, \"levator\": 0.6, \"scaleni\": 0.7, \"splenius\": 0.6, \"multifidi\": 0.6, \"rhomboids\": 1.5, \"supraspinatus\": 1.2, \"triceps_trp1\": 0.9, \"biceps\": 1.0}}]}, {\"id\": \"q_rest_pain\", \"text\": \"Is the pain present predominantly AT REST \\u2014 a burning or gnawing quality along the inner edge of the shoulder blade \\u2014 not clearly worse with movement?\", \"sublabel\": \"Burning along the medial scapular border that is present regardless of posture or arm movement\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 burning along the scapular border, present at rest\", \"lr\": {\"rhomboids\": 7.0, \"trapezius_trp3\": 1.3, \"scaleni\": 1.2, \"trapezius_trp2\": 0.5, \"levator\": 0.4, \"multifidi\": 0.4, \"supraspinatus\": 0.4, \"splenius\": 0.4, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain varies with posture, activity, or movement\", \"lr\": {\"rhomboids\": 0.3, \"trapezius_trp3\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.2, \"levator\": 1.2, \"multifidi\": 1.1, \"supraspinatus\": 1.2, \"splenius\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}, {\"id\": \"q_painful_arc\", \"text\": \"Is there a painful arc during shoulder abduction \\u2014 pain appearing between about 60\\u00b0 and 120\\u00b0 of raising the arm, then diminishing above that?\", \"sublabel\": \"Ask the patient to raise the arm sideways \\u2014 does pain appear and then disappear as the arm reaches overhead?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 painful arc present between 60\\u00b0 and 120\\u00b0 of abduction\", \"lr\": {\"supraspinatus\": 8.0, \"biceps\": 1.5, \"scaleni\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"levator\": 0.6, \"splenius\": 0.5, \"multifidi\": 0.3, \"triceps_trp1\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 shoulder abduction is pain-free or uniformly painful throughout\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 0.9, \"scaleni\": 1.1, \"trapezius_trp2\": 1.2, \"trapezius_trp3\": 1.2, \"rhomboids\": 1.3, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.3, \"triceps_trp1\": 1.2}}]}, {\"id\": \"q_elbow_skip\", \"text\": \"Is there posterior arm pain that SKIPS the elbow \\u2014 pain above and/or below the elbow but NOT at the elbow itself?\", \"sublabel\": \"The back of the upper arm and/or dorsal forearm ache, but the elbow region is not painful\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 posterior arm pain skipping the elbow\", \"lr\": {\"triceps_trp1\": 9.0, \"biceps\": 0.3, \"supraspinatus\": 0.5, \"scaleni\": 0.6, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.3, \"multifidi\": 0.2}}, {\"id\": \"no\", \"label\": \"No \\u2014 no posterior arm pain, or elbow IS involved\", \"lr\": {\"triceps_trp1\": 0.15, \"biceps\": 1.1, \"supraspinatus\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_anterior_shoulder\", \"text\": \"Is the anterior shoulder the PRIMARY pain location \\u2014 and can the patient lie comfortably on the affected side?\", \"sublabel\": \"Anterior shoulder aching (front of shoulder) is the main complaint; lying on that side does not worsen pain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 anterior shoulder primary; can lie on the affected side\", \"lr\": {\"biceps\": 7.0, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.2, \"scaleni\": 0.7, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 anterior shoulder is not the primary zone, or cannot lie on affected side\", \"lr\": {\"biceps\": 0.2, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.2, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_visual_blur\", \"text\": \"Does the patient report any blurring of vision on the same side as the pain?\", \"sublabel\": \"Ipsilateral visual blurring \\u2014 not bilateral, not related to eye strain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 ipsilateral visual blurring reported\", \"lr\": {\"splenius\": 8.0, \"levator\": 0.9, \"trapezius_trp2\": 0.7, \"trapezius_trp3\": 0.6, \"rhomboids\": 0.5, \"multifidi\": 0.5, \"scaleni\": 0.6, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 no visual symptoms\", \"lr\": {\"splenius\": 0.6, \"levator\": 1.1, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.1, \"multifidi\": 1.1, \"scaleni\": 1.1, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}], \"pairwise\": [{\"id\": \"pw_levator_splenius\", \"pair\": [\"levator\", \"splenius\"], \"text\": \"Tiebreaker \\u2014 Levator Scapulae vs Splenius Cervicis\", \"question\": \"Where is the pain centred \\u2014 at the angle of the neck and upper scapular corner, OR at the TOP of the shoulder slope?\", \"answers\": [{\"id\": \"neck_angle\", \"label\": \"Neck angle and upper scapular corner\", \"lr\": {\"levator\": 4.0, \"splenius\": 0.25}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder slope\", \"lr\": {\"levator\": 0.25, \"splenius\": 4.0}}]}, {\"id\": \"pw_trap2_trap3\", \"pair\": [\"trapezius_trp2\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP2 vs TrP3\", \"question\": \"Is the burning between the shoulder blades closer to the ACROMION and mid-scapular region, or closer to the THORACIC SPINE?\", \"answers\": [{\"id\": \"lateral\", \"label\": \"More lateral \\u2014 toward the acromion or mid-scapular area\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 0.3}}, {\"id\": \"medial\", \"label\": \"More medial \\u2014 close to the thoracic vertebrae\", \"lr\": {\"trapezius_trp2\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_trap_rhomboid\", \"pair\": [\"trapezius_trp3\", \"rhomboids\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP3 vs Rhomboids\", \"question\": \"Is the pain clearly worse with sustained sitting or posture, OR present equally at rest regardless of posture?\", \"answers\": [{\"id\": \"postural\", \"label\": \"Clearly worse with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp3\": 3.0, \"rhomboids\": 0.3}}, {\"id\": \"rest\", \"label\": \"Present at rest \\u2014 not clearly postural\", \"lr\": {\"trapezius_trp3\": 0.3, \"rhomboids\": 3.0}}]}, {\"id\": \"pw_scaleni_levator\", \"pair\": [\"scaleni\", \"levator\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Levator Scapulae\", \"question\": \"Are there arm or hand symptoms (numbness, swelling, nocturnal aching) alongside the upper back pain?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 4.0, \"levator\": 0.3}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.3, \"levator\": 2.5}}]}, {\"id\": \"pw_supraspinatus_biceps\", \"pair\": [\"supraspinatus\", \"biceps\"], \"text\": \"Tiebreaker \\u2014 Supraspinatus vs Biceps Brachii\", \"question\": \"Is the arm pain primarily at the LATERAL upper arm (mid-deltoid), or at the ANTERIOR shoulder (front)?\", \"answers\": [{\"id\": \"lateral_arm\", \"label\": \"Lateral upper arm \\u2014 mid-deltoid region\", \"lr\": {\"supraspinatus\": 4.0, \"biceps\": 0.2}}, {\"id\": \"anterior\", \"label\": \"Anterior shoulder \\u2014 front of the shoulder\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 4.0}}]}, {\"id\": \"pw_multifidi_trap3\", \"pair\": [\"multifidi\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Multifidi vs Trapezius TrP3\", \"question\": \"Is the paraspinal pain DEEP and immediately adjacent to the spinous processes (within 2 cm of midline), or more SUPERFICIAL and extending laterally toward the scapula?\", \"answers\": [{\"id\": \"deep_midline\", \"label\": \"Deep \\u2014 immediately beside the spinous processes, within 2 cm of midline\", \"lr\": {\"multifidi\": 4.0, \"trapezius_trp3\": 0.3}}, {\"id\": \"superficial_lateral\", \"label\": \"Superficial \\u2014 extends laterally toward the scapular border\", \"lr\": {\"multifidi\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_scaleni_trap2\", \"pair\": [\"scaleni\", \"trapezius_trp2\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Trapezius TrP2\", \"question\": \"Are there any arm or hand symptoms \\u2014 numbness, tingling, hand swelling, or nocturnal arm aching?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"trapezius_trp2\": 0.2}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.25, \"trapezius_trp2\": 3.5}}]}], \"treatment_dag\": {\"edges\": [{\"from\": \"scaleni\", \"to\": \"trapezius_trp2\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for upper trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"trapezius_trp3\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for mid-trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"triceps_trp1\", \"type\": \"key_satellite\", \"label\": \"Scaleni drive long head triceps satellite TrPs (T&S Table 3.3)\"}, {\"from\": \"trapezius_trp2\", \"to\": \"levator\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius TrP2 drives levator scapulae satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"splenius\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives splenius cervicis satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"rhomboids\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives rhomboid minor satellite TrPs\"}, {\"from\": \"levator\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Levator scapulae and upper trapezius share functional unit \\u2014 treat concurrently\"}, {\"from\": \"trapezius_trp2\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Upper and mid trapezius share functional unit \\u2014 treat in same session\"}, {\"from\": \"trapezius_trp3\", \"to\": \"rhomboids\", \"type\": \"functional_unit\", \"label\": \"Mid-trapezius and rhomboids share scapular retraction function\"}, {\"from\": \"supraspinatus\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Supraspinatus and upper trapezius share rotator cuff/shoulder girdle function\"}, {\"from\": \"multifidi\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Multifidi and mid-trapezius share thoracic stabilisation function\"}, {\"from\": \"scaleni\", \"to\": \"levator\", \"type\": \"secondary_load\", \"label\": \"Levator scapulae overloaded secondarily when scaleni are in protective splinting\"}, {\"from\": \"levator\", \"to\": \"rhomboids\", \"type\": \"secondary_load\", \"label\": \"Rhomboids overloaded secondarily with chronic levator dysfunction\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"secondary_load\", \"label\": \"Biceps develops secondary TrPs as antagonist to triceps \\u2014 treat after triceps resolves\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"antagonist_risk\", \"label\": \"Triceps/biceps antagonist pair \\u2014 treat in alternating partial cycles, not sequentially to completion\"}, {\"from\": \"supraspinatus\", \"to\": \"scaleni\", \"type\": \"antagonist_risk\", \"label\": \"Releasing supraspinatus may reactively cramp scaleni \\u2014 treat alternately\"}], \"edge_type_labels\": {\"key_satellite\": \"Treat first \\u2014 key TrP driving satellites\", \"functional_unit\": \"Treat concurrently in same session\", \"secondary_load\": \"Treat after primary resolves\", \"antagonist_risk\": \"\\u26a0 Treat in alternating cycles \\u2014 reactive cramp risk\"}}};\nconst T=SCHEMA.thresholds,MUSCLE_IDS=Object.keys(SCHEMA.muscles),DAG=SCHEMA.treatment_dag;\nconst LS_KEY='painwiki_upper_thoracic_counts_v1';\nconst RF_EMERGENCY=[{\"id\": \"rf-e1\", \"label\": \"Aortic dissection\", \"question\": \"Sudden tearing or ripping interscapular pain; hypertension or Marfan features; pulse or BP difference between arms?\"}, {\"id\": \"rf-e2\", \"label\": \"Pulmonary embolism\", \"question\": \"Sudden-onset pleuritic chest or back pain (sharp, worse on inhalation); unexplained breathlessness, tachycardia, or hypoxia; recent immobility, surgery, or long-haul travel?\"}, {\"id\": \"rf-e3\", \"label\": \"Spinal cord compression / myelopathy\", \"question\": \"Bilateral arm or leg weakness, gait disturbance, loss of hand dexterity, or bowel/bladder dysfunction alongside neck or upper back pain?\"}, {\"id\": \"rf-e4\", \"label\": \"Meningism\", \"question\": \"Neck and upper back pain with fever, photophobia, or cerebellar signs (ataxia, dysarthria, nystagmus)?\"}];\nconst RF_URGENT=[{\"id\": \"rf-u1\", \"label\": \"Vertebral fracture\", \"question\": \"History of significant trauma, or patient is osteoporotic (post-menopausal, long-term corticosteroids, age > 70) with sudden-onset upper thoracic pain?\"}, {\"id\": \"rf-u2\", \"label\": \"Serious spinal pathology (tumour / infection)\", \"question\": \"Constant, progressive upper thoracic pain unrelated to posture or movement, worse at night lying down? Unexplained weight loss, fever, or history of cancer?\"}, {\"id\": \"rf-u3\", \"label\": \"Cervical instability\", \"question\": \"History of head or neck trauma combined with bilateral upper limb symptoms, gait disturbance, or upper cervical pain?\"}, {\"id\": \"rf-u4\", \"label\": \"Cardiac angina (exertional component)\", \"question\": \"Upper thoracic or interscapular pain with a clear exertional component, relieved by rest or GTN? Left-sided with arm radiation?\"}, {\"id\": \"rf-u5\", \"label\": \"Inflammatory arthropathy\", \"question\": \"Bilateral posterior neck or thoracic stiffness WORSE in the morning and improving with movement, with peripheral joint swelling or systemic symptoms?\"}];\nconst BROAD_DIFF=[{\"condition\": \"Cervical disc herniation (C5\\u2013C7)\", \"confidence\": \"uncommon\", \"mimics\": \"Upper thoracic and interscapular referred pain via dorsal rami; arm symptoms overlapping with scaleni and trapezius TrP patterns\", \"distinguishing_feature\": \"Dermatomal arm pain; reflex change (biceps C5\\u2013C6, triceps C7); true myotomal weakness; Spurling's test positive. TrP pain does not produce reflex changes or dermatomal sensory deficit. TrPs commonly develop secondarily to radiculopathy.\", \"action\": \"MRI cervical spine if neurological signs present. Treat TrPs concurrently \\u2014 they frequently coexist with disc pathology and may be the dominant pain source.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 neurological\", \"confidence\": \"rare\", \"mimics\": \"Arm and hand symptoms with upper thoracic pain; ulnar symptoms overlap with scaleni and pectoralis minor patterns\", \"distinguishing_feature\": \"Roos test (EAST test) positive at 3 minutes; nerve conduction studies confirm. Scaleni TrPs frequently coexist with and drive TOS symptoms \\u2014 TrP inactivation often resolves or substantially reduces TOS.\", \"action\": \"Inactivate scaleni TrPs first before TOS workup. Refer for nerve conduction studies if symptoms persist after TrP treatment.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 vascular\", \"confidence\": \"rare\", \"mimics\": \"Anterior shoulder and arm pain with upper thoracic aching; overlaps with subclavius and scaleni TrP patterns\", \"distinguishing_feature\": \"Radial pulse reduction or loss with arm abduction; hand oedema and finger stiffness. Wright manoeuvre positive. Scaleni and subclavius TrPs contribute to vascular compression via taut band tension.\", \"action\": \"Check radial pulse in standard and abducted positions. Scaleni and subclavius TrP inactivation is first-line. Refer for vascular assessment if pulse loss persists after TrP treatment.\"}, {\"condition\": \"Rotator cuff tendinopathy / subacromial impingement\", \"confidence\": \"uncommon\", \"mimics\": \"Painful arc on shoulder abduction and lateral arm aching overlapping with supraspinatus TrP referral to the upper thoracic region\", \"distinguishing_feature\": \"Tenderness at greater tuberosity insertion; positive Neer's or Hawkins-Kennedy sign; imaging confirms tendon changes. TrP taut bands impose sustained enthesopathic tension \\u2014 coexistence is common and causally related.\", \"action\": \"Treat supraspinatus TrPs first and reassess tendon findings after inactivation. Both conditions require treatment when identified together.\"}, {\"condition\": \"Thoracic zygapophyseal (facet) joint pain\", \"confidence\": \"uncommon\", \"mimics\": \"Deep upper thoracic paraspinal pain in the same zone as multifidi TrP referral\", \"distinguishing_feature\": \"Pain reproduced by passive PA intersegmental pressures over the facet joints; hard end-feel on accessory movement testing. Multifidi TrPs and facet dysfunction coexist at the same segment and perpetuate each other \\u2014 soft end-feel suggests TrP predominance.\", \"action\": \"Segmental accessory movement testing. Treat multifidi TrPs first \\u2014 articular dysfunction often resolves with TrP inactivation. Mobilise the segment if hard end-feel persists.\"}, {\"condition\": \"Herpes zoster \\u2014 pre-eruptive and post-herpetic\", \"confidence\": \"uncommon\", \"mimics\": \"Unilateral burning upper thoracic pain before the rash appears \\u2014 indistinguishable from rhomboid, multifidi, or intercostal TrP patterns at onset\", \"distinguishing_feature\": \"Dermatomal distribution; allodynia (light touch painful in a band); vesicles appear 1\\u20134 days after pain onset. Age > 50 or immunocompromised increases suspicion. Post-herpetic: TrP pain coexists with neurogenic shooting pain.\", \"action\": \"Examine the skin carefully at every visit for unilateral burning thoracic pain. If vesicles present, refer urgently \\u2014 antiviral window is 72 hours from rash onset.\"}, {\"condition\": \"First rib dysfunction\", \"confidence\": \"rare\", \"mimics\": \"Upper thoracic and arm symptoms closely associated with scaleni TrP presentation \\u2014 scaleni attach to the first rib and their taut bands directly elevate it\", \"distinguishing_feature\": \"First rib elevated and tender on posterior superior palpation; restricted first rib caudal glide on accessory movement testing. Scaleni TrPs are almost universally present concurrently.\", \"action\": \"First rib mobilisation in conjunction with scaleni TrP inactivation \\u2014 the two conditions are mechanically linked and must be treated together.\"}, {\"condition\": \"Winged scapula / serratus anterior weakness\", \"confidence\": \"rare\", \"mimics\": \"Medial scapular border pain and upper thoracic aching overlapping with rhomboid TrP presentation\", \"distinguishing_feature\": \"Scapular winging visible on push-up against wall or forward arm elevation; serratus anterior weakness on manual muscle testing. Rhomboid TrPs develop secondarily to serratus anterior inhibition \\u2014 the rhomboids are overloaded trying to retract a scapula that serratus cannot stabilise.\", \"action\": \"Serratus anterior rehabilitation is the primary treatment. Treat rhomboid TrPs concurrently but address serratus anterior as the underlying driver \\u2014 rhomboid TrPs will recur without it.\"}, {\"condition\": \"Glenohumeral osteoarthritis\", \"confidence\": \"uncommon\", \"mimics\": \"Diffuse shoulder girdle and upper thoracic pain, particularly in older patients with supraspinatus and biceps TrP patterns\", \"distinguishing_feature\": \"Capsular pattern restriction (external rotation > abduction > internal rotation); crepitus on movement; radiological changes. Arthritis does not produce spot-tender taut bands. TrPs coexist and are independently treatable.\", \"action\": \"Shoulder radiograph. Inactivate TrPs concurrently with joint management \\u2014 TrP treatment can substantially reduce pain even in the presence of established arthritis.\"}, {\"condition\": \"Fibromyalgia\", \"confidence\": \"uncommon\", \"mimics\": \"Widespread upper back and shoulder girdle tenderness overlapping with all muscles in this algorithm simultaneously\", \"distinguishing_feature\": \"Widespread pain \\u2265 3 months across multiple body regions; diffuse tenderness without specific TrP referral patterns; fatigue and non-restorative sleep. TrPs produce specific referred pain patterns and are a treatable component of fibromyalgia.\", \"action\": \"Systematic TrP examination alongside fibromyalgia management. Treating active TrPs reduces the overall pain burden independently \\u2014 do not withhold TrP treatment because fibromyalgia is also present.\"}, {\"condition\": \"Residual pain after spinal manipulation or injection\", \"confidence\": \"atypical\", \"mimics\": \"Persistent upper thoracic pain after a spinal procedure \\u2014 may be re-attributed to the joint when myofascial TrPs are the active pain source\", \"distinguishing_feature\": \"After a spinal intervention, pain persisting at a lower level or shifting in character indicates TrPs masked by dominant segmental pain are now the primary source. The TrPs were pre-existing \\u2014 not created by the procedure.\", \"action\": \"Re-examine muscles systematically after any spinal intervention. Most commonly harbouring residual TrPs: trapezius (upper and mid), levator scapulae, rhomboids, multifidi.\"}];\n\n// \u2500\u2500 RED FLAGS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildRedFlags(){\n  function makeFlags(arr,id,isUrgent){\n    document.getElementById(id).innerHTML=arr.map(rf=>\n      '<div class=\"rf-item\">'+\n      '<label class=\"rf-check-wrap\" for=\"'+rf.id+'\">'+\n      '<input type=\"checkbox\" class=\"rf-cb\" id=\"'+rf.id+'\">'+\n      '</label>'+\n      '<div class=\"rf-body\">'+\n      '<div class=\"rf-label\">'+rf.label+'</div>'+\n      '<div class=\"rf-question\">'+rf.question+'</div>'+\n      '</div></div>'\n    ).join('');\n  }\n  makeFlags(RF_EMERGENCY,'rf-emergency-items',false);\n  makeFlags(RF_URGENT,'rf-urgent-items',true);\n  document.getElementById('btn-affirm').addEventListener('click',()=>{\n    document.getElementById('btn-affirm').style.display='none';\n    document.getElementById('affirmed-badge').classList.add('show');\n    const mg=document.getElementById('main-grid');\n    mg.classList.add('visible');\n    posteriors=getUpdatedPriors();advance();renderLearnPanel();\n    setTimeout(()=>mg.scrollIntoView({behavior:'smooth',block:'start'}),80);\n  });\n}\n\n// \u2500\u2500 BROAD DIFFERENTIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildBroadDiff(){\n  document.getElementById('broad-grid').innerHTML=BROAD_DIFF.map(d=>\n    '<div class=\"dt-diff-item\">'+\n    '<div class=\"dt-diff-confidence '+d.confidence+'\">'+d.confidence+'</div>'+\n    '<div class=\"dt-diff-name\">'+d.condition+'</div>'+\n    '<div class=\"dt-diff-mimics\">'+d.mimics+'</div>'+\n    '<div class=\"dt-diff-detail\">'+\n    '<div class=\"dt-diff-distinguisher\"><strong>Distinguishing features</strong>'+d.distinguishing_feature+'</div>'+\n    '<div class=\"dt-diff-action\"><strong>Action</strong>'+d.action+'</div>'+\n    '</div></div>'\n  ).join('');\n  // click to expand\n  document.getElementById('broad-grid').addEventListener('click',e=>{\n    const item=e.target.closest('.dt-diff-item');\n    if(item) item.classList.toggle('open');\n  });\n  // toggle visibility\n  document.getElementById('broad-toggle-btn').addEventListener('click',()=>{\n    const grid=document.getElementById('broad-grid');\n    const hidden=grid.classList.toggle('hidden');\n    document.getElementById('broad-toggle-btn').textContent=hidden?'Show':'Hide';\n  });\n}\n\n// \u2500\u2500 LEARNING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadCounts(){try{const r=localStorage.getItem(LS_KEY);if(r)return JSON.parse(r);}catch(e){}const c={};MUSCLE_IDS.forEach(m=>c[m]=0);return c;}\nfunction saveCounts(c){try{localStorage.setItem(LS_KEY,JSON.stringify(c));}catch(e){}}\nfunction incrementCount(mid){const c=loadCounts();c[mid]=(c[mid]||0)+1;saveCounts(c);return c;}\nfunction getUpdatedPriors(){const counts=loadCounts(),p={};MUSCLE_IDS.forEach(m=>p[m]=SCHEMA.muscles[m].prior+(counts[m]||0)*0.02);return normalise(p);}\n\n// \u2500\u2500 STATE & MATH \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet answers={},posteriors={},trail=[],pairwiseDone=new Set(),queueIdx=0,earlyDone=false;\nfunction normalise(p){let t=0;MUSCLE_IDS.forEach(m=>t+=p[m]);const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]/t);return o;}\nfunction applyLR(p,lr){const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]*(lr[m]||1.0));return normalise(o);}\nfunction ranked(){return MUSCLE_IDS.map(m=>({id:m,p:posteriors[m],label:SCHEMA.muscles[m].label,subtitle:SCHEMA.muscles[m].subtitle||'',page:SCHEMA.muscles[m].page,note:SCHEMA.muscles[m].key_trp_note||null})).sort((a,b)=>b.p-a.p);}\nfunction shouldEarlyExit(r){return r[0].p>=T.early_exit_posterior&&(r[0].p-r[1].p)>=T.early_exit_gap;}\nfunction getPairwise(r){if((r[0].p-r[1].p)>=T.pairwise_trigger)return null;const s=new Set([r[0].id,r[1].id]);for(const pw of SCHEMA.pairwise)if(!pairwiseDone.has(pw.id)&&pw.pair.every(x=>s.has(x)))return pw;return null;}\nfunction condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}\nfunction nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}\nfunction buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL['key_satellite'],note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}\n\n// \u2500\u2500 RENDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction renderDiff(){const r=ranked();document.getElementById('diff-rows').innerHTML=r.map((m,i)=>{const rank=i+1,w=(m.p*100).toFixed(1),bw=Math.max(0.5,m.p*100).toFixed(1),dim=rank>6?' dimmed':'',rc=rank<=3?' r'+rank:'';return'<div class=\"dt-diff-bar-row'+rc+dim+'\"><div class=\"dt-diff-bar-rank\">'+rank+'</div><div class=\"dt-diff-bar-name\">'+m.label+'</div><div class=\"dt-diff-bar-track\"><div class=\"dt-diff-bar-fill\" style=\"width:'+bw+'%\"></div></div><div class=\"dt-diff-bar-weight\">'+w+'</div></div>';}).join('');}\nfunction renderTrail(){const el=document.getElementById('trail-panel');if(!trail.length){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('trail-counter').textContent=trail.length+' feature'+(trail.length>1?'s':'')+' recorded';document.getElementById('trail-items').innerHTML=trail.map(t=>'<div class=\"dt-trail-item\"><div class=\"dt-trail-q\">'+t.q+'</div><div class=\"dt-trail-a\">&#8627; '+t.a+'</div></div>').join('');}\nfunction renderCounter(){const tot=SCHEMA.questions.length,ans=Object.keys(answers).length;document.getElementById('q-counter').textContent=ans===0?'Prior weights':ans+'\\u202f/\\u202f'+tot+' features';}\nfunction renderLearnPanel(){const counts=loadCounts(),total=Object.values(counts).reduce((a,b)=>a+b,0);const el=document.getElementById('learn-panel');if(!total){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('learn-total').textContent=total+' confirmation'+(total>1?'s':'');document.getElementById('learn-rows').innerHTML=MUSCLE_IDS.filter(m=>counts[m]>0).sort((a,b)=>counts[b]-counts[a]).map(m=>'<div class=\"learn-row\"><div class=\"learn-muscle\">'+SCHEMA.muscles[m].label+'</div><div class=\"learn-count\">'+counts[m]+'&times;</div></div>').join('');}\nfunction renderQuestion(q,isPW){const qText=q.text||q.question;const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';const sub=q.sublabel?'<div class=\"dt-rationale\">'+q.sublabel+'</div>':'';const btns='<div class=\"dt-answers\"><button class=\"dt-answer dt-answer-yes\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"yes\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('Yes')+'\">Yes</button><button class=\"dt-answer dt-answer-no\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"no\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('No')+'\">No</button></div>';document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+btns+'</div>';}\n\n// Fix: answer buttons need correct aid from schema\nfunction renderQuestionFull(q,isPW){\n  const qText=q.text||q.question;\n  const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';\n  const sub=q.sublabel?'<div class=\"dt-rationale\" style=\"font-size:.78rem;color:#57534e;font-style:italic;margin-bottom:.55rem;line-height:1.5\">'+q.sublabel+'</div>':'';\n  const btns=q.answers.map(a=>{\n    const asub=a.sublabel?'<div style=\"font-size:.72rem;color:#57534e;font-weight:400;margin-top:2px\">'+a.sublabel+'</div>':'';\n    const cls=q.answers.length===2\n      ? (q.answers.indexOf(a)===0?'dt-answer dt-answer-yes':'dt-answer dt-answer-no')\n      : 'dt-answer dt-answer-yes';\n    return'<button class=\"'+cls+'\" style=\"'+(q.answers.length>2?'flex:none;flex-direction:column;align-items:flex-start;':'')+'\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"'+a.id+'\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent(a.label)+'\">'+a.label+asub+'</button>';\n  }).join('');\n  const ansWrap=q.answers.length>2\n    ?'<div style=\"display:flex;flex-direction:column;gap:.5rem;margin-top:1rem\">'+btns+'</div>'\n    :'<div class=\"dt-answers\">'+btns+'</div>';\n  document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+ansWrap+'</div>';\n}\n\nfunction renderEarlyExit(r){earlyDone=true;const top=r[0];document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-early-badge\">Confident result available</div><div class=\"dt-early-muscle\">'+top.label+'</div><div class=\"dt-early-sub\">'+top.subtitle+'<br>Relative weight '+(top.p*100).toFixed(0)+' &mdash; accept to view result, or continue refining.</div><div class=\"dt-early-btns\"><button class=\"dt-btn-primary\" id=\"btn-accept\">Accept result</button><button class=\"dt-btn-secondary\" id=\"btn-continue\">Continue refining</button></div></div>';document.getElementById('btn-accept').addEventListener('click',showResult);document.getElementById('btn-continue').addEventListener('click',()=>{earlyDone=true;advance();});}\n\nfunction buildAddConfirm(r){const rem=r.slice(3);if(!rem.length)return'';const opts=rem.map(m=>'<option value=\"'+m.id+'\">'+m.label+' (rank '+(r.indexOf(m)+1)+', weight '+(m.p*100).toFixed(1)+')</option>').join('');return'<div class=\"dt-add-confirm\"><div class=\"dt-add-confirm-label\">Also confirm a muscle not in the top three</div><div class=\"dt-add-confirm-row\"><select class=\"dt-add-select\" id=\"add-select\"><option value=\"\">\u2014 select muscle \u2014</option>'+opts+'</select><button class=\"dt-btn-add-confirm\" id=\"btn-add-confirm-ok\">Confirm</button></div><div class=\"dt-add-confirm-done\" id=\"add-confirm-done\">&#10003; Confirmed and model updated</div></div>';}\n\nfunction showResult(){\n  const r=ranked();\n  const ranks=['Most likely','2nd','3rd'],rk=['rk1','rk2','rk3'],card_cls=['dt-card-result','dt-card','dt-card'];\n  const cards=r.slice(0,3).map((m,i)=>{\n    const w=(m.p*100).toFixed(1);\n    const note=m.note?'<div class=\"dt-key-note\"><div class=\"dt-key-note-label\">Key TrP relationship</div>'+m.note+'</div>':'';\n    return'<div class=\"dt-card '+card_cls[i]+'\" style=\"margin-bottom:10px\">'+\n      '<div class=\"dt-result-rank '+rk[i]+'\">'+ranks[i]+'</div>'+\n      '<div class=\"dt-result-name\">'+m.label+'</div>'+\n      '<div class=\"dt-result-sub\">'+m.subtitle+'</div>'+\n      '<div class=\"dt-result-weight\">Relative weight: '+w+'</div>'+\n      '<div class=\"dt-result-actions\">'+\n      '<a class=\"dt-wiki-link\" href=\"https://painwiki.com/wiki/index.php?title='+m.page+'\" target=\"_blank\" rel=\"noopener\">'+m.label+' on PainWiki</a>'+\n      '<button class=\"dt-confirm-btn\" data-muscle=\"'+m.id+'\" data-label=\"'+encodeURIComponent(m.label)+'\">Confirm this muscle</button>'+\n      '</div>'+note+'</div>';\n  }).join('');\n  const addSec=buildAddConfirm(r);\n  document.getElementById('question-area').innerHTML=\n    '<div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.12em;text-transform:uppercase;color:#a8a29e;margin-bottom:8px\">Differential &mdash; final weights</div>'+\n    cards+\n    (addSec?'<div class=\"dt-card\" style=\"margin-top:4px\"><div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.1em;text-transform:uppercase;color:#a8a29e;margin-bottom:10px\">Additional Confirmation</div>'+addSec+'</div>':'');\n\n  const addBtn=document.getElementById('btn-add-confirm-ok');\n  if(addBtn){addBtn.addEventListener('click',()=>{const sel=document.getElementById('add-select'),mid=sel.value;if(!mid)return;incrementCount(mid);addBtn.disabled=true;sel.disabled=true;document.getElementById('add-confirm-done').style.display='block';renderLearnPanel();});}\n\n  const steps=buildTreatmentOrder(r);\n  if(steps.length){\n    const tp=document.getElementById('treat-panel');tp.classList.add('visible');\n    document.getElementById('treat-items').innerHTML=steps.map((s,i)=>{const isW=s.edgeType==='antagonist_risk';return'<div class=\"dt-treat-item\"><div class=\"dt-treat-step\">'+(i+1)+'</div><div><div class=\"dt-treat-muscle\">'+s.label+'</div><div class=\"dt-treat-edge'+(isW?' warn':'')+'\">'+s.edgeLabel+'</div>'+(s.note&&!isW?'<div class=\"dt-treat-edge\" style=\"margin-top:2px;font-size:10px;color:#a8a29e\">'+s.note+'</div>':'')+'</div></div>';}).join('');\n  }\n  renderLearnPanel();\n}\n\nfunction advance(){renderDiff();renderTrail();renderCounter();const r=ranked(),pw=getPairwise(r);if(pw){pairwiseDone.add(pw.id);renderQuestionFull(pw,true);return;}if(!earlyDone&&shouldEarlyExit(r)){renderEarlyExit(r);return;}const q=nextQ();if(q){queueIdx++;renderQuestionFull(q,false);return;}showResult();}\n\ndocument.addEventListener('click',e=>{\n  const ab=e.target.closest('.dt-answer,.answer-btn');\n  if(ab&&ab.dataset.qid){\n    const qid=ab.dataset.qid,aid=ab.dataset.aid,qLabel=decodeURIComponent(ab.dataset.qlabel),aLabel=decodeURIComponent(ab.dataset.alabel);\n    if(qid.startsWith('__pw__')){const pw=SCHEMA.pairwise.find(x=>x.id===qid.replace('__pw__',''));const a=pw.answers.find(x=>x.id===aid);posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});earlyDone=false;}\n    else{const q=SCHEMA.questions.find(x=>x.id===qid),a=q.answers.find(x=>x.id===aid);answers[qid]=aid;posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});queueIdx++;}\n    advance();return;\n  }\n  const cb=e.target.closest('.dt-confirm-btn');\n  if(cb&&!cb.classList.contains('confirmed')){const mid=cb.dataset.muscle,label=decodeURIComponent(cb.dataset.label);incrementCount(mid);cb.classList.add('confirmed');cb.textContent='\\u2713\\u2713 '+label+' confirmed';renderLearnPanel();return;}\n});\n\ndocument.getElementById('reset-btn').addEventListener('click',()=>{answers={};trail=[];pairwiseDone=new Set();queueIdx=0;earlyDone=false;posteriors=getUpdatedPriors();document.getElementById('trail-panel').classList.remove('visible');document.getElementById('treat-panel').classList.remove('visible');advance();});\ndocument.getElementById('learning-toggle-btn').addEventListener('click',()=>{const h=document.body.classList.toggle('hide-learning');document.getElementById('learning-toggle-btn').textContent='Learning: '+(h?'OFF':'ON');});\n\nbuildRedFlags();\nbuildBroadDiff();\n</script>";
   var INTERFACE_HTML = "<div class=\"proto-shell\">\n\n<!-- MASTHEAD -->\n<header class=\"proto-masthead\">\n  <div class=\"proto-logo\">Pain<span>Wiki</span></div>\n  <div class=\"proto-right\">\n    <span class=\"proto-region\">Diagnostic Algorithm &middot; Upper Thoracic Back Pain</span>\n    <button class=\"learning-toggle\" id=\"learning-toggle-btn\">Learning: ON</button>\n  </div>\n</header>\n\n<!-- RED FLAGS -->\n<div class=\"rf-outer\">\n  <div class=\"rf-panel-wrap\" id=\"rf-emergency-panel\">\n    <div class=\"rf-col-header\">&#9888; Emergency &mdash; stop and act if any are present</div>\n    <div class=\"rf-items\" id=\"rf-emergency-items\"></div>\n  </div>\n  <div class=\"rf-panel-wrap urgent\" id=\"rf-urgent-panel\">\n    <div class=\"rf-col-header\">&#9888; Urgent &mdash; refer before myofascial assessment</div>\n    <div class=\"rf-items\" id=\"rf-urgent-items\"></div>\n    <div class=\"rf-affirm\">\n      <div class=\"rf-affirm-note\">All emergency and urgent flags screened and negative &mdash; or appropriate action taken. Safe to proceed.</div>\n      <button class=\"btn-affirm\" id=\"btn-affirm\">Proceed to clinical interview &rarr;</button>\n      <div class=\"affirmed-badge\" id=\"affirmed-badge\">Red flags cleared</div>\n    </div>\n  </div>\n</div>\n\n<!-- MAIN GRID -->\n<div id=\"main-grid\">\n  <div class=\"col-left\">\n    <div id=\"question-area\"></div>\n    <div class=\"dt-trail-panel\" id=\"trail-panel\">\n      <div class=\"dt-trail-head\">\n        <span class=\"dt-trail-title\">Clinical Interview Record</span>\n        <span class=\"dt-trail-meta\" id=\"trail-counter\"></span>\n      </div>\n      <div id=\"trail-items\"></div>\n    </div>\n  </div>\n  <div class=\"col-right\">\n    <div class=\"dt-running-panel\">\n      <div class=\"dt-running-head\">\n        <span class=\"dt-running-title\">Running Differential</span>\n        <span class=\"dt-running-meta\" id=\"q-counter\">Prior weights</span>\n      </div>\n      <div id=\"diff-rows\"></div>\n      <div class=\"dt-diff-caption\">Diagnostic weights are relative, not absolute probabilities. Initial values reflect muscle prevalence and clinical feature patterns from Travell &amp; Simons. Weights update as cases are confirmed.<br>Powered by LDA with Dirichlet-Multinomial updating.</div>\n    </div>\n    <div class=\"dt-treat-panel\" id=\"treat-panel\">\n      <div class=\"dt-treat-head\"><span class=\"dt-treat-title\">Suggested Treatment Order</span></div>\n      <div id=\"treat-items\"></div>\n      <div class=\"dt-treat-caption\">Sequence derived from Travell &amp; Simons satellite TrP and functional unit relationships, ordered by diagnostic weight.</div>\n    </div>\n    <div class=\"learn-panel\" id=\"learn-panel\">\n      <div class=\"learn-head\">\n        <span class=\"learn-title\">Confirmed Cases &mdash; This Device</span>\n        <span class=\"learn-meta\" id=\"learn-total\"></span>\n      </div>\n      <div class=\"learn-intro\"><strong>Multiple muscles can be confirmed per case.</strong> Confirm every contributing muscle &mdash; each confirmation independently updates the model.</div>\n      <div id=\"learn-rows\"></div>\n      <div class=\"learn-caption\">Confirmed diagnoses update prior weights via Dirichlet-Multinomial conjugate updating. Powered by LDA.</div>\n    </div>\n  </div>\n\n  <!-- BROAD DIFFERENTIAL \u2014 gadget style, spans full width -->\n  <div style=\"grid-column:1/-1\">\n    <div class=\"dt-broad-panel\" id=\"broad-panel\">\n      <div class=\"dt-broad-header\">\n        <div class=\"dt-broad-header-top\">\n          <div class=\"dt-broad-title\">&#9632; Broad Differential Diagnosis</div>\n          <button class=\"dt-broad-toggle\" id=\"broad-toggle-btn\">Hide</button>\n        </div>\n        <div class=\"dt-epigraph\">\n          &#8220;If he does not expect the unexpected, he will not discover it &mdash;\n          for it is difficult to discover and intractable.&#8221;\n          <cite>&mdash; Heraclitus, Fr. 18</cite>\n        </div>\n      </div>\n      <div class=\"dt-broad-grid\" id=\"broad-grid\"></div>\n    </div>\n  </div>\n</div><!-- /main-grid -->\n\n<div class=\"proto-foot\">\n  <div class=\"proto-disclaimer\"><strong>Not validated for clinical use.</strong> Research prototype for educational purposes. LR values derived from Travell &amp; Simons clinical descriptions, not formal epidemiological studies.</div>\n  <button class=\"btn-reset\" id=\"reset-btn\">&#8635; Start over</button>\n</div>\n</div><!-- /proto-shell -->\n\n<script>\nconst SCHEMA={\"model\": \"bayesian_lr_scoring\", \"version\": \"1.0\", \"description\": \"Probabilistic scoring tree for upper thoracic back pain using Bayesian likelihood ratio updating with Thurstonian pairwise tiebreakers\", \"thresholds\": {\"early_exit_posterior\": 0.55, \"early_exit_gap\": 0.18, \"pairwise_trigger\": 0.22}, \"muscles\": {\"trapezius_trp2\": {\"label\": \"Upper Trapezius\", \"prior\": 0.18, \"page\": \"Muscle:Trapezius\", \"key_trp_note\": \"Lower Trapezius TrP3 is the key TrP that most commonly induces upper trapezius satellites. Treat lower trapezius first when upper trapezius TrPs recur or respond poorly. The lower trapezius may itself be a satellite of key TrPs in the latissimus dorsi.\", \"subtitle\": \"Upper fibres \\u2014 posterolateral neck, behind ear, temple\"}, \"trapezius_trp3\": {\"label\": \"Lower Trapezius\", \"prior\": 0.13, \"page\": \"Muscle:Trapezius\", \"subtitle\": \"Lower fibres \\u2014 cervical, mastoid, suprascapular (\\\"the joker\\\")\"}, \"levator\": {\"label\": \"Levator Scapulae\", \"prior\": 0.16, \"page\": \"Muscle:Levator_Scapulae\", \"key_trp_note\": \"Check Lower Trapezius TrP3 first \\u2014 it is often the key TrP driving levator scapulae and upper trapezius satellites. If levator TrPs respond poorly to treatment, lower trapezius TrP3 has not been addressed. Upper trapezius TrP1/2 also drives levator as a satellite.\", \"subtitle\": \"Neck angle stiffness, cannot look over sore shoulder\"}, \"rhomboids\": {\"label\": \"Rhomboids\", \"prior\": 0.1, \"page\": \"Muscle:Rhomboids\", \"subtitle\": \"Medial scapular border burning, present at rest\"}, \"multifidi\": {\"label\": \"Multifidi\", \"prior\": 0.09, \"page\": \"Muscle:Multifidi\", \"subtitle\": \"Deep paraspinal ache immediately beside the spine\"}, \"scaleni\": {\"label\": \"Scaleni\", \"prior\": 0.11, \"page\": \"Muscle:Scalene\", \"key_trp_note\": \"Scaleni drive satellite TrPs in upper trapezius, triceps long head, pectoralis major and minor, and serratus posterior superior. Treat scaleni before their satellites. SCM should be treated concurrently.\", \"subtitle\": \"Upper back + arm/hand symptoms, nocturnal arm aching\"}, \"supraspinatus\": {\"label\": \"Supraspinatus\", \"prior\": 0.1, \"page\": \"Muscle:Supraspinatus\", \"key_trp_note\": \"Infraspinatus TrPs are often the key TrP source driving supraspinatus satellites. If supraspinatus TrPs recur, examine infraspinatus.\", \"subtitle\": \"Mid-deltoid ache, painful arc on shoulder abduction\"}, \"splenius\": {\"label\": \"Splenius Cervicis\", \"prior\": 0.07, \"page\": \"Muscle:Splenius_Cervicis\", \"subtitle\": \"Top of shoulder, rotation restricted away from pain\"}, \"triceps_trp1\": {\"label\": \"Triceps Brachii (Long Head)\", \"prior\": 0.04, \"page\": \"Muscle:Triceps_Brachii\", \"subtitle\": \"Posterior arm to scapula, skips the elbow\"}, \"biceps\": {\"label\": \"Biceps Brachii\", \"prior\": 0.02, \"page\": \"Muscle:Biceps_Brachii\", \"subtitle\": \"Anterior shoulder primary, suprascapular spillover\"}}, \"questions\": [{\"id\": \"q_neck_rotation\", \"text\": \"Is cervical rotation restricted or painful in any direction?\", \"sublabel\": \"Can the patient NOT turn their head to look fully over one or both shoulders?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 neck rotation is restricted or painful\", \"lr\": {\"levator\": 4.5, \"splenius\": 4.0, \"trapezius_trp2\": 1.4, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.6, \"scaleni\": 1.5, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 full pain-free cervical rotation in both directions\", \"lr\": {\"levator\": 0.15, \"splenius\": 0.18, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.4, \"multifidi\": 1.3, \"scaleni\": 0.8, \"supraspinatus\": 1.2, \"triceps_trp1\": 1.3, \"biceps\": 1.4}}]}, {\"id\": \"q_rotation_direction\", \"text\": \"In which direction is cervical rotation most restricted or painful?\", \"sublabel\": \"Turn the head toward the painful side, then away \\u2014 which direction is more limited?\", \"type\": \"choice\", \"condition\": {\"q_neck_rotation\": \"yes\"}, \"answers\": [{\"id\": \"toward\", \"label\": \"Cannot fully turn TOWARD the painful side\", \"sublabel\": \"Cannot look over the sore shoulder\", \"lr\": {\"levator\": 6.0, \"splenius\": 0.15, \"trapezius_trp2\": 1.2, \"scaleni\": 1.3, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"triceps_trp1\": 0.7, \"biceps\": 0.6}}, {\"id\": \"away\", \"label\": \"Cannot fully turn AWAY from the painful side\", \"sublabel\": \"Cannot look over the opposite shoulder; OR pain at top of shoulder\", \"lr\": {\"splenius\": 6.0, \"levator\": 0.15, \"trapezius_trp2\": 1.0, \"scaleni\": 1.2, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.6, \"biceps\": 0.5}}, {\"id\": \"both\", \"label\": \"Restricted in both directions\", \"lr\": {\"levator\": 2.0, \"splenius\": 2.0, \"scaleni\": 2.5, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.8, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}]}, {\"id\": \"q_pain_location\", \"text\": \"Where is the upper thoracic back pain most intense?\", \"sublabel\": \"Choose the location that best describes the dominant pain zone\", \"type\": \"choice\", \"answers\": [{\"id\": \"medial_border\", \"label\": \"Along the medial border of the scapula \\u2014 inner edge of the shoulder blade\", \"sublabel\": \"A burning sensation specifically along the edge of the scapula\", \"lr\": {\"rhomboids\": 6.0, \"trapezius_trp3\": 1.5, \"scaleni\": 2.0, \"levator\": 1.0, \"trapezius_trp2\": 0.6, \"multifidi\": 0.3, \"supraspinatus\": 0.5, \"splenius\": 0.4, \"triceps_trp1\": 0.5, \"biceps\": 0.3}}, {\"id\": \"interscapular\", \"label\": \"Between the shoulder blades \\u2014 diffuse interscapular or upper thoracic\", \"sublabel\": \"Aching or burning between or below the scapulae\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 3.0, \"rhomboids\": 1.5, \"scaleni\": 2.0, \"levator\": 0.8, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"splenius\": 0.6, \"triceps_trp1\": 1.5, \"biceps\": 1.5}}, {\"id\": \"paraspinal\", \"label\": \"Deep unilateral paraspinal ache \\u2014 beside the spine only\", \"sublabel\": \"Strictly beside the spine, not reaching the scapula\", \"lr\": {\"multifidi\": 5.0, \"trapezius_trp3\": 2.0, \"rhomboids\": 0.4, \"trapezius_trp2\": 0.5, \"levator\": 0.6, \"scaleni\": 0.5, \"supraspinatus\": 0.3, \"splenius\": 0.7, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder or neck angle \\u2014 not the scapular blade\", \"sublabel\": \"Pain at the peak of the shoulder slope or in the neck-shoulder angle\", \"lr\": {\"levator\": 3.5, \"splenius\": 4.0, \"trapezius_trp2\": 2.5, \"scaleni\": 1.5, \"trapezius_trp3\": 0.8, \"rhomboids\": 0.4, \"multifidi\": 0.3, \"supraspinatus\": 0.6, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"posterior_arm\", \"label\": \"Posterior arm or posterior shoulder \\u2014 back of the arm is the dominant pain\", \"sublabel\": \"Upper back aching is secondary to a posterior arm or shoulder complaint\", \"lr\": {\"triceps_trp1\": 7.0, \"biceps\": 0.3, \"supraspinatus\": 0.8, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"multifidi\": 0.2, \"scaleni\": 0.5, \"levator\": 0.4, \"splenius\": 0.3}}]}, {\"id\": \"q_arm_symptoms\", \"text\": \"Are there arm or hand symptoms alongside the upper back pain?\", \"sublabel\": \"Arm numbness, hand swelling, nocturnal arm aching, or arm heaviness\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 arm, forearm, or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"triceps_trp1\": 2.0, \"biceps\": 1.5, \"supraspinatus\": 1.3, \"levator\": 0.7, \"splenius\": 0.7, \"trapezius_trp2\": 0.6, \"trapezius_trp3\": 0.5, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain confined to the upper back and/or neck\", \"lr\": {\"scaleni\": 0.25, \"triceps_trp1\": 0.7, \"biceps\": 0.6, \"supraspinatus\": 0.9, \"levator\": 1.2, \"splenius\": 1.2, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.3, \"rhomboids\": 1.4, \"multifidi\": 1.4}}]}, {\"id\": \"q_nocturnal_arm\", \"text\": \"Is the patient woken at night by arm aching or numbness?\", \"sublabel\": \"Must hang the arm over the side of the bed or shake it for relief\", \"type\": \"binary\", \"condition\": {\"q_arm_symptoms\": \"yes\"}, \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 woken at night; arm hanging over bed side gives relief\", \"lr\": {\"scaleni\": 8.0, \"triceps_trp1\": 0.8, \"biceps\": 0.7, \"supraspinatus\": 0.6, \"levator\": 0.5, \"splenius\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 arm symptoms are daytime or positional only\", \"lr\": {\"scaleni\": 0.5, \"triceps_trp1\": 1.2, \"biceps\": 1.3, \"supraspinatus\": 1.2, \"levator\": 1.0, \"splenius\": 1.0, \"trapezius_trp2\": 1.0, \"trapezius_trp3\": 1.0, \"rhomboids\": 1.0, \"multifidi\": 1.0}}]}, {\"id\": \"q_postural\", \"text\": \"Is the pain clearly worse during or after sustained sitting, desk work, or forward head posture?\", \"sublabel\": \"Pain builds during a sitting work session; relieved by moving or lying down\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 clearly postural; builds with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp2\": 4.0, \"trapezius_trp3\": 3.5, \"levator\": 2.5, \"scaleni\": 2.0, \"splenius\": 2.0, \"multifidi\": 2.0, \"rhomboids\": 1.3, \"supraspinatus\": 1.0, \"triceps_trp1\": 1.5, \"biceps\": 1.0}}, {\"id\": \"no\", \"label\": \"No \\u2014 not clearly worse with posture or sitting\", \"lr\": {\"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.4, \"levator\": 0.6, \"scaleni\": 0.7, \"splenius\": 0.6, \"multifidi\": 0.6, \"rhomboids\": 1.5, \"supraspinatus\": 1.2, \"triceps_trp1\": 0.9, \"biceps\": 1.0}}]}, {\"id\": \"q_rest_pain\", \"text\": \"Is the pain present predominantly AT REST \\u2014 a burning or gnawing quality along the inner edge of the shoulder blade \\u2014 not clearly worse with movement?\", \"sublabel\": \"Burning along the medial scapular border that is present regardless of posture or arm movement\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 burning along the scapular border, present at rest\", \"lr\": {\"rhomboids\": 7.0, \"trapezius_trp3\": 1.3, \"scaleni\": 1.2, \"trapezius_trp2\": 0.5, \"levator\": 0.4, \"multifidi\": 0.4, \"supraspinatus\": 0.4, \"splenius\": 0.4, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain varies with posture, activity, or movement\", \"lr\": {\"rhomboids\": 0.3, \"trapezius_trp3\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.2, \"levator\": 1.2, \"multifidi\": 1.1, \"supraspinatus\": 1.2, \"splenius\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}, {\"id\": \"q_painful_arc\", \"text\": \"Is there a painful arc during shoulder abduction \\u2014 pain appearing between about 60\\u00b0 and 120\\u00b0 of raising the arm, then diminishing above that?\", \"sublabel\": \"Ask the patient to raise the arm sideways \\u2014 does pain appear and then disappear as the arm reaches overhead?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 painful arc present between 60\\u00b0 and 120\\u00b0 of abduction\", \"lr\": {\"supraspinatus\": 8.0, \"biceps\": 1.5, \"scaleni\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"levator\": 0.6, \"splenius\": 0.5, \"multifidi\": 0.3, \"triceps_trp1\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 shoulder abduction is pain-free or uniformly painful throughout\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 0.9, \"scaleni\": 1.1, \"trapezius_trp2\": 1.2, \"trapezius_trp3\": 1.2, \"rhomboids\": 1.3, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.3, \"triceps_trp1\": 1.2}}]}, {\"id\": \"q_elbow_skip\", \"text\": \"Is there posterior arm pain that SKIPS the elbow \\u2014 pain above and/or below the elbow but NOT at the elbow itself?\", \"sublabel\": \"The back of the upper arm and/or dorsal forearm ache, but the elbow region is not painful\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 posterior arm pain skipping the elbow\", \"lr\": {\"triceps_trp1\": 9.0, \"biceps\": 0.3, \"supraspinatus\": 0.5, \"scaleni\": 0.6, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.3, \"multifidi\": 0.2}}, {\"id\": \"no\", \"label\": \"No \\u2014 no posterior arm pain, or elbow IS involved\", \"lr\": {\"triceps_trp1\": 0.15, \"biceps\": 1.1, \"supraspinatus\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_anterior_shoulder\", \"text\": \"Is the anterior shoulder the PRIMARY pain location \\u2014 and can the patient lie comfortably on the affected side?\", \"sublabel\": \"Anterior shoulder aching (front of shoulder) is the main complaint; lying on that side does not worsen pain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 anterior shoulder primary; can lie on the affected side\", \"lr\": {\"biceps\": 7.0, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.2, \"scaleni\": 0.7, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 anterior shoulder is not the primary zone, or cannot lie on affected side\", \"lr\": {\"biceps\": 0.2, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.2, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_visual_blur\", \"text\": \"Does the patient report any blurring of vision on the same side as the pain?\", \"sublabel\": \"Ipsilateral visual blurring \\u2014 not bilateral, not related to eye strain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 ipsilateral visual blurring reported\", \"lr\": {\"splenius\": 8.0, \"levator\": 0.9, \"trapezius_trp2\": 0.7, \"trapezius_trp3\": 0.6, \"rhomboids\": 0.5, \"multifidi\": 0.5, \"scaleni\": 0.6, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 no visual symptoms\", \"lr\": {\"splenius\": 0.6, \"levator\": 1.1, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.1, \"multifidi\": 1.1, \"scaleni\": 1.1, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}], \"pairwise\": [{\"id\": \"pw_levator_splenius\", \"pair\": [\"levator\", \"splenius\"], \"text\": \"Tiebreaker \\u2014 Levator Scapulae vs Splenius Cervicis\", \"question\": \"Where is the pain centred \\u2014 at the angle of the neck and upper scapular corner, OR at the TOP of the shoulder slope?\", \"answers\": [{\"id\": \"neck_angle\", \"label\": \"Neck angle and upper scapular corner\", \"lr\": {\"levator\": 4.0, \"splenius\": 0.25}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder slope\", \"lr\": {\"levator\": 0.25, \"splenius\": 4.0}}]}, {\"id\": \"pw_trap2_trap3\", \"pair\": [\"trapezius_trp2\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP2 vs TrP3\", \"question\": \"Is the burning between the shoulder blades closer to the ACROMION and mid-scapular region, or closer to the THORACIC SPINE?\", \"answers\": [{\"id\": \"lateral\", \"label\": \"More lateral \\u2014 toward the acromion or mid-scapular area\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 0.3}}, {\"id\": \"medial\", \"label\": \"More medial \\u2014 close to the thoracic vertebrae\", \"lr\": {\"trapezius_trp2\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_trap_rhomboid\", \"pair\": [\"trapezius_trp3\", \"rhomboids\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP3 vs Rhomboids\", \"question\": \"Is the pain clearly worse with sustained sitting or posture, OR present equally at rest regardless of posture?\", \"answers\": [{\"id\": \"postural\", \"label\": \"Clearly worse with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp3\": 3.0, \"rhomboids\": 0.3}}, {\"id\": \"rest\", \"label\": \"Present at rest \\u2014 not clearly postural\", \"lr\": {\"trapezius_trp3\": 0.3, \"rhomboids\": 3.0}}]}, {\"id\": \"pw_scaleni_levator\", \"pair\": [\"scaleni\", \"levator\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Levator Scapulae\", \"question\": \"Are there arm or hand symptoms (numbness, swelling, nocturnal aching) alongside the upper back pain?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 4.0, \"levator\": 0.3}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.3, \"levator\": 2.5}}]}, {\"id\": \"pw_supraspinatus_biceps\", \"pair\": [\"supraspinatus\", \"biceps\"], \"text\": \"Tiebreaker \\u2014 Supraspinatus vs Biceps Brachii\", \"question\": \"Is the arm pain primarily at the LATERAL upper arm (mid-deltoid), or at the ANTERIOR shoulder (front)?\", \"answers\": [{\"id\": \"lateral_arm\", \"label\": \"Lateral upper arm \\u2014 mid-deltoid region\", \"lr\": {\"supraspinatus\": 4.0, \"biceps\": 0.2}}, {\"id\": \"anterior\", \"label\": \"Anterior shoulder \\u2014 front of the shoulder\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 4.0}}]}, {\"id\": \"pw_multifidi_trap3\", \"pair\": [\"multifidi\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Multifidi vs Trapezius TrP3\", \"question\": \"Is the paraspinal pain DEEP and immediately adjacent to the spinous processes (within 2 cm of midline), or more SUPERFICIAL and extending laterally toward the scapula?\", \"answers\": [{\"id\": \"deep_midline\", \"label\": \"Deep \\u2014 immediately beside the spinous processes, within 2 cm of midline\", \"lr\": {\"multifidi\": 4.0, \"trapezius_trp3\": 0.3}}, {\"id\": \"superficial_lateral\", \"label\": \"Superficial \\u2014 extends laterally toward the scapular border\", \"lr\": {\"multifidi\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_scaleni_trap2\", \"pair\": [\"scaleni\", \"trapezius_trp2\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Trapezius TrP2\", \"question\": \"Are there any arm or hand symptoms \\u2014 numbness, tingling, hand swelling, or nocturnal arm aching?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"trapezius_trp2\": 0.2}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.25, \"trapezius_trp2\": 3.5}}]}], \"treatment_dag\": {\"edges\": [{\"from\": \"scaleni\", \"to\": \"trapezius_trp2\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for upper trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"trapezius_trp3\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for mid-trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"triceps_trp1\", \"type\": \"key_satellite\", \"label\": \"Scaleni drive long head triceps satellite TrPs (T&S Table 3.3)\"}, {\"from\": \"trapezius_trp2\", \"to\": \"levator\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius TrP2 drives levator scapulae satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"splenius\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives splenius cervicis satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"rhomboids\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives rhomboid minor satellite TrPs\"}, {\"from\": \"levator\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Levator scapulae and upper trapezius share functional unit \\u2014 treat concurrently\"}, {\"from\": \"trapezius_trp2\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Upper and mid trapezius share functional unit \\u2014 treat in same session\"}, {\"from\": \"trapezius_trp3\", \"to\": \"rhomboids\", \"type\": \"functional_unit\", \"label\": \"Mid-trapezius and rhomboids share scapular retraction function\"}, {\"from\": \"supraspinatus\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Supraspinatus and upper trapezius share rotator cuff/shoulder girdle function\"}, {\"from\": \"multifidi\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Multifidi and mid-trapezius share thoracic stabilisation function\"}, {\"from\": \"scaleni\", \"to\": \"levator\", \"type\": \"secondary_load\", \"label\": \"Levator scapulae overloaded secondarily when scaleni are in protective splinting\"}, {\"from\": \"levator\", \"to\": \"rhomboids\", \"type\": \"secondary_load\", \"label\": \"Rhomboids overloaded secondarily with chronic levator dysfunction\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"secondary_load\", \"label\": \"Biceps develops secondary TrPs as antagonist to triceps \\u2014 treat after triceps resolves\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"antagonist_risk\", \"label\": \"Triceps/biceps antagonist pair \\u2014 treat in alternating partial cycles, not sequentially to completion\"}, {\"from\": \"supraspinatus\", \"to\": \"scaleni\", \"type\": \"antagonist_risk\", \"label\": \"Releasing supraspinatus may reactively cramp scaleni \\u2014 treat alternately\"}], \"edge_type_labels\": {\"key_satellite\": \"Treat first \\u2014 key TrP driving satellites\", \"functional_unit\": \"Treat concurrently in same session\", \"secondary_load\": \"Treat after primary resolves\", \"antagonist_risk\": \"\\u26a0 Treat in alternating cycles \\u2014 reactive cramp risk\"}}};\nconst T=SCHEMA.thresholds,MUSCLE_IDS=Object.keys(SCHEMA.muscles),DAG=SCHEMA.treatment_dag;\nconst LS_KEY='painwiki_upper_thoracic_counts_v1';\nconst RF_EMERGENCY=[{\"id\": \"rf-e1\", \"label\": \"Aortic dissection\", \"question\": \"Sudden tearing or ripping interscapular pain; hypertension or Marfan features; pulse or BP difference between arms?\"}, {\"id\": \"rf-e2\", \"label\": \"Pulmonary embolism\", \"question\": \"Sudden-onset pleuritic chest or back pain (sharp, worse on inhalation); unexplained breathlessness, tachycardia, or hypoxia; recent immobility, surgery, or long-haul travel?\"}, {\"id\": \"rf-e3\", \"label\": \"Spinal cord compression / myelopathy\", \"question\": \"Bilateral arm or leg weakness, gait disturbance, loss of hand dexterity, or bowel/bladder dysfunction alongside neck or upper back pain?\"}, {\"id\": \"rf-e4\", \"label\": \"Meningism\", \"question\": \"Neck and upper back pain with fever, photophobia, or cerebellar signs (ataxia, dysarthria, nystagmus)?\"}];\nconst RF_URGENT=[{\"id\": \"rf-u1\", \"label\": \"Vertebral fracture\", \"question\": \"History of significant trauma, or patient is osteoporotic (post-menopausal, long-term corticosteroids, age > 70) with sudden-onset upper thoracic pain?\"}, {\"id\": \"rf-u2\", \"label\": \"Serious spinal pathology (tumour / infection)\", \"question\": \"Constant, progressive upper thoracic pain unrelated to posture or movement, worse at night lying down? Unexplained weight loss, fever, or history of cancer?\"}, {\"id\": \"rf-u3\", \"label\": \"Cervical instability\", \"question\": \"History of head or neck trauma combined with bilateral upper limb symptoms, gait disturbance, or upper cervical pain?\"}, {\"id\": \"rf-u4\", \"label\": \"Cardiac angina (exertional component)\", \"question\": \"Upper thoracic or interscapular pain with a clear exertional component, relieved by rest or GTN? Left-sided with arm radiation?\"}, {\"id\": \"rf-u5\", \"label\": \"Inflammatory arthropathy\", \"question\": \"Bilateral posterior neck or thoracic stiffness WORSE in the morning and improving with movement, with peripheral joint swelling or systemic symptoms?\"}];\nconst BROAD_DIFF=[{\"condition\": \"Cervical disc herniation (C5\\u2013C7)\", \"confidence\": \"uncommon\", \"mimics\": \"Upper thoracic and interscapular referred pain via dorsal rami; arm symptoms overlapping with scaleni and trapezius TrP patterns\", \"distinguishing_feature\": \"Dermatomal arm pain; reflex change (biceps C5\\u2013C6, triceps C7); true myotomal weakness; Spurling's test positive. TrP pain does not produce reflex changes or dermatomal sensory deficit. TrPs commonly develop secondarily to radiculopathy.\", \"action\": \"MRI cervical spine if neurological signs present. Treat TrPs concurrently \\u2014 they frequently coexist with disc pathology and may be the dominant pain source.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 neurological\", \"confidence\": \"rare\", \"mimics\": \"Arm and hand symptoms with upper thoracic pain; ulnar symptoms overlap with scaleni and pectoralis minor patterns\", \"distinguishing_feature\": \"Roos test (EAST test) positive at 3 minutes; nerve conduction studies confirm. Scaleni TrPs frequently coexist with and drive TOS symptoms \\u2014 TrP inactivation often resolves or substantially reduces TOS.\", \"action\": \"Inactivate scaleni TrPs first before TOS workup. Refer for nerve conduction studies if symptoms persist after TrP treatment.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 vascular\", \"confidence\": \"rare\", \"mimics\": \"Anterior shoulder and arm pain with upper thoracic aching; overlaps with subclavius and scaleni TrP patterns\", \"distinguishing_feature\": \"Radial pulse reduction or loss with arm abduction; hand oedema and finger stiffness. Wright manoeuvre positive. Scaleni and subclavius TrPs contribute to vascular compression via taut band tension.\", \"action\": \"Check radial pulse in standard and abducted positions. Scaleni and subclavius TrP inactivation is first-line. Refer for vascular assessment if pulse loss persists after TrP treatment.\"}, {\"condition\": \"Rotator cuff tendinopathy / subacromial impingement\", \"confidence\": \"uncommon\", \"mimics\": \"Painful arc on shoulder abduction and lateral arm aching overlapping with supraspinatus TrP referral to the upper thoracic region\", \"distinguishing_feature\": \"Tenderness at greater tuberosity insertion; positive Neer's or Hawkins-Kennedy sign; imaging confirms tendon changes. TrP taut bands impose sustained enthesopathic tension \\u2014 coexistence is common and causally related.\", \"action\": \"Treat supraspinatus TrPs first and reassess tendon findings after inactivation. Both conditions require treatment when identified together.\"}, {\"condition\": \"Thoracic zygapophyseal (facet) joint pain\", \"confidence\": \"uncommon\", \"mimics\": \"Deep upper thoracic paraspinal pain in the same zone as multifidi TrP referral\", \"distinguishing_feature\": \"Pain reproduced by passive PA intersegmental pressures over the facet joints; hard end-feel on accessory movement testing. Multifidi TrPs and facet dysfunction coexist at the same segment and perpetuate each other \\u2014 soft end-feel suggests TrP predominance.\", \"action\": \"Segmental accessory movement testing. Treat multifidi TrPs first \\u2014 articular dysfunction often resolves with TrP inactivation. Mobilise the segment if hard end-feel persists.\"}, {\"condition\": \"Herpes zoster \\u2014 pre-eruptive and post-herpetic\", \"confidence\": \"uncommon\", \"mimics\": \"Unilateral burning upper thoracic pain before the rash appears \\u2014 indistinguishable from rhomboid, multifidi, or intercostal TrP patterns at onset\", \"distinguishing_feature\": \"Dermatomal distribution; allodynia (light touch painful in a band); vesicles appear 1\\u20134 days after pain onset. Age > 50 or immunocompromised increases suspicion. Post-herpetic: TrP pain coexists with neurogenic shooting pain.\", \"action\": \"Examine the skin carefully at every visit for unilateral burning thoracic pain. If vesicles present, refer urgently \\u2014 antiviral window is 72 hours from rash onset.\"}, {\"condition\": \"First rib dysfunction\", \"confidence\": \"rare\", \"mimics\": \"Upper thoracic and arm symptoms closely associated with scaleni TrP presentation \\u2014 scaleni attach to the first rib and their taut bands directly elevate it\", \"distinguishing_feature\": \"First rib elevated and tender on posterior superior palpation; restricted first rib caudal glide on accessory movement testing. Scaleni TrPs are almost universally present concurrently.\", \"action\": \"First rib mobilisation in conjunction with scaleni TrP inactivation \\u2014 the two conditions are mechanically linked and must be treated together.\"}, {\"condition\": \"Winged scapula / serratus anterior weakness\", \"confidence\": \"rare\", \"mimics\": \"Medial scapular border pain and upper thoracic aching overlapping with rhomboid TrP presentation\", \"distinguishing_feature\": \"Scapular winging visible on push-up against wall or forward arm elevation; serratus anterior weakness on manual muscle testing. Rhomboid TrPs develop secondarily to serratus anterior inhibition \\u2014 the rhomboids are overloaded trying to retract a scapula that serratus cannot stabilise.\", \"action\": \"Serratus anterior rehabilitation is the primary treatment. Treat rhomboid TrPs concurrently but address serratus anterior as the underlying driver \\u2014 rhomboid TrPs will recur without it.\"}, {\"condition\": \"Glenohumeral osteoarthritis\", \"confidence\": \"uncommon\", \"mimics\": \"Diffuse shoulder girdle and upper thoracic pain, particularly in older patients with supraspinatus and biceps TrP patterns\", \"distinguishing_feature\": \"Capsular pattern restriction (external rotation > abduction > internal rotation); crepitus on movement; radiological changes. Arthritis does not produce spot-tender taut bands. TrPs coexist and are independently treatable.\", \"action\": \"Shoulder radiograph. Inactivate TrPs concurrently with joint management \\u2014 TrP treatment can substantially reduce pain even in the presence of established arthritis.\"}, {\"condition\": \"Fibromyalgia\", \"confidence\": \"uncommon\", \"mimics\": \"Widespread upper back and shoulder girdle tenderness overlapping with all muscles in this algorithm simultaneously\", \"distinguishing_feature\": \"Widespread pain \\u2265 3 months across multiple body regions; diffuse tenderness without specific TrP referral patterns; fatigue and non-restorative sleep. TrPs produce specific referred pain patterns and are a treatable component of fibromyalgia.\", \"action\": \"Systematic TrP examination alongside fibromyalgia management. Treating active TrPs reduces the overall pain burden independently \\u2014 do not withhold TrP treatment because fibromyalgia is also present.\"}, {\"condition\": \"Residual pain after spinal manipulation or injection\", \"confidence\": \"atypical\", \"mimics\": \"Persistent upper thoracic pain after a spinal procedure \\u2014 may be re-attributed to the joint when myofascial TrPs are the active pain source\", \"distinguishing_feature\": \"After a spinal intervention, pain persisting at a lower level or shifting in character indicates TrPs masked by dominant segmental pain are now the primary source. The TrPs were pre-existing \\u2014 not created by the procedure.\", \"action\": \"Re-examine muscles systematically after any spinal intervention. Most commonly harbouring residual TrPs: trapezius (upper and mid), levator scapulae, rhomboids, multifidi.\"}];\n\n// \u2500\u2500 RED FLAGS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildRedFlags(){\n  function makeFlags(arr,id,isUrgent){\n    document.getElementById(id).innerHTML=arr.map(rf=>\n      '<div class=\"rf-item\">'+\n      '<label class=\"rf-check-wrap\" for=\"'+rf.id+'\">'+\n      '<input type=\"checkbox\" class=\"rf-cb\" id=\"'+rf.id+'\">'+\n      '</label>'+\n      '<div class=\"rf-body\">'+\n      '<div class=\"rf-label\">'+rf.label+'</div>'+\n      '<div class=\"rf-question\">'+rf.question+'</div>'+\n      '</div></div>'\n    ).join('');\n  }\n  makeFlags(RF_EMERGENCY,'rf-emergency-items',false);\n  makeFlags(RF_URGENT,'rf-urgent-items',true);\n  document.getElementById('btn-affirm').addEventListener('click',()=>{\n    document.getElementById('btn-affirm').style.display='none';\n    document.getElementById('affirmed-badge').classList.add('show');\n    const mg=document.getElementById('main-grid');\n    mg.classList.add('visible');\n    posteriors=getUpdatedPriors();advance();renderLearnPanel();\n    setTimeout(()=>mg.scrollIntoView({behavior:'smooth',block:'start'}),80);\n  });\n}\n\n// \u2500\u2500 BROAD DIFFERENTIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildBroadDiff(){\n  document.getElementById('broad-grid').innerHTML=BROAD_DIFF.map(d=>\n    '<div class=\"dt-diff-item\">'+\n    '<div class=\"dt-diff-confidence '+d.confidence+'\">'+d.confidence+'</div>'+\n    '<div class=\"dt-diff-name\">'+d.condition+'</div>'+\n    '<div class=\"dt-diff-mimics\">'+d.mimics+'</div>'+\n    '<div class=\"dt-diff-detail\">'+\n    '<div class=\"dt-diff-distinguisher\"><strong>Distinguishing features</strong>'+d.distinguishing_feature+'</div>'+\n    '<div class=\"dt-diff-action\"><strong>Action</strong>'+d.action+'</div>'+\n    '</div></div>'\n  ).join('');\n  // click to expand\n  document.getElementById('broad-grid').addEventListener('click',e=>{\n    const item=e.target.closest('.dt-diff-item');\n    if(item) item.classList.toggle('open');\n  });\n  // toggle visibility\n  document.getElementById('broad-toggle-btn').addEventListener('click',()=>{\n    const grid=document.getElementById('broad-grid');\n    const hidden=grid.classList.toggle('hidden');\n    document.getElementById('broad-toggle-btn').textContent=hidden?'Show':'Hide';\n  });\n}\n\n// \u2500\u2500 LEARNING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadCounts(){try{const r=localStorage.getItem(LS_KEY);if(r)return JSON.parse(r);}catch(e){}const c={};MUSCLE_IDS.forEach(m=>c[m]=0);return c;}\nfunction saveCounts(c){try{localStorage.setItem(LS_KEY,JSON.stringify(c));}catch(e){}}\nfunction incrementCount(mid){const c=loadCounts();c[mid]=(c[mid]||0)+1;saveCounts(c);return c;}\nfunction getUpdatedPriors(){const counts=loadCounts(),p={};MUSCLE_IDS.forEach(m=>p[m]=SCHEMA.muscles[m].prior+(counts[m]||0)*0.02);return normalise(p);}\n\n// \u2500\u2500 STATE & MATH \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet answers={},posteriors={},trail=[],pairwiseDone=new Set(),queueIdx=0,earlyDone=false;\nfunction normalise(p){let t=0;MUSCLE_IDS.forEach(m=>t+=p[m]);const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]/t);return o;}\nfunction applyLR(p,lr){const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]*(lr[m]||1.0));return normalise(o);}\nfunction ranked(){return MUSCLE_IDS.map(m=>({id:m,p:posteriors[m],label:SCHEMA.muscles[m].label,subtitle:SCHEMA.muscles[m].subtitle||'',page:SCHEMA.muscles[m].page,note:SCHEMA.muscles[m].key_trp_note||null})).sort((a,b)=>b.p-a.p);}\nfunction shouldEarlyExit(r){return r[0].p>=T.early_exit_posterior&&(r[0].p-r[1].p)>=T.early_exit_gap;}\nfunction getPairwise(r){if((r[0].p-r[1].p)>=T.pairwise_trigger)return null;const s=new Set([r[0].id,r[1].id]);for(const pw of SCHEMA.pairwise)if(!pairwiseDone.has(pw.id)&&pw.pair.every(x=>s.has(x)))return pw;return null;}\nfunction condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}\nfunction nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}\nfunction buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL.key_satellite,note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}\n\n// \u2500\u2500 RENDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction renderDiff(){const r=ranked();document.getElementById('diff-rows').innerHTML=r.map((m,i)=>{const rank=i+1,w=(m.p*100).toFixed(1),bw=Math.max(0.5,m.p*100).toFixed(1),dim=rank>6?' dimmed':'',rc=rank<=3?' r'+rank:'';return'<div class=\"dt-diff-bar-row'+rc+dim+'\"><div class=\"dt-diff-bar-rank\">'+rank+'</div><div class=\"dt-diff-bar-name\">'+m.label+'</div><div class=\"dt-diff-bar-track\"><div class=\"dt-diff-bar-fill\" style=\"width:'+bw+'%\"></div></div><div class=\"dt-diff-bar-weight\">'+w+'</div></div>';}).join('');}\nfunction renderTrail(){const el=document.getElementById('trail-panel');if(!trail.length){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('trail-counter').textContent=trail.length+' feature'+(trail.length>1?'s':'')+' recorded';document.getElementById('trail-items').innerHTML=trail.map(t=>'<div class=\"dt-trail-item\"><div class=\"dt-trail-q\">'+t.q+'</div><div class=\"dt-trail-a\">&#8627; '+t.a+'</div></div>').join('');}\nfunction renderCounter(){const tot=SCHEMA.questions.length,ans=Object.keys(answers).length;document.getElementById('q-counter').textContent=ans===0?'Prior weights':ans+'\\u202f/\\u202f'+tot+' features';}\nfunction renderLearnPanel(){const counts=loadCounts(),total=Object.values(counts).reduce((a,b)=>a+b,0);const el=document.getElementById('learn-panel');if(!total){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('learn-total').textContent=total+' confirmation'+(total>1?'s':'');document.getElementById('learn-rows').innerHTML=MUSCLE_IDS.filter(m=>counts[m]>0).sort((a,b)=>counts[b]-counts[a]).map(m=>'<div class=\"learn-row\"><div class=\"learn-muscle\">'+SCHEMA.muscles[m].label+'</div><div class=\"learn-count\">'+counts[m]+'&times;</div></div>').join('');}\nfunction renderQuestion(q,isPW){const qText=q.text||q.question;const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';const sub=q.sublabel?'<div class=\"dt-rationale\">'+q.sublabel+'</div>':'';const btns='<div class=\"dt-answers\"><button class=\"dt-answer dt-answer-yes\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"yes\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('Yes')+'\">Yes</button><button class=\"dt-answer dt-answer-no\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"no\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('No')+'\">No</button></div>';document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+btns+'</div>';}\n\n// Fix: answer buttons need correct aid from schema\nfunction renderQuestionFull(q,isPW){\n  const qText=q.text||q.question;\n  const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';\n  const sub=q.sublabel?'<div class=\"dt-rationale\" style=\"font-size:.78rem;color:#57534e;font-style:italic;margin-bottom:.55rem;line-height:1.5\">'+q.sublabel+'</div>':'';\n  const btns=q.answers.map(a=>{\n    const asub=a.sublabel?'<div style=\"font-size:.72rem;color:#57534e;font-weight:400;margin-top:2px\">'+a.sublabel+'</div>':'';\n    const cls=q.answers.length===2\n      ? (q.answers.indexOf(a)===0?'dt-answer dt-answer-yes':'dt-answer dt-answer-no')\n      : 'dt-answer dt-answer-yes';\n    return'<button class=\"'+cls+'\" style=\"'+(q.answers.length>2?'flex:none;flex-direction:column;align-items:flex-start;':'')+'\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"'+a.id+'\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent(a.label)+'\">'+a.label+asub+'</button>';\n  }).join('');\n  const ansWrap=q.answers.length>2\n    ?'<div style=\"display:flex;flex-direction:column;gap:.5rem;margin-top:1rem\">'+btns+'</div>'\n    :'<div class=\"dt-answers\">'+btns+'</div>';\n  document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+ansWrap+'</div>';\n}\n\nfunction renderEarlyExit(r){earlyDone=true;const top=r[0];document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-early-badge\">Confident result available</div><div class=\"dt-early-muscle\">'+top.label+'</div><div class=\"dt-early-sub\">'+top.subtitle+'<br>Relative weight '+(top.p*100).toFixed(0)+' &mdash; accept to view result, or continue refining.</div><div class=\"dt-early-btns\"><button class=\"dt-btn-primary\" id=\"btn-accept\">Accept result</button><button class=\"dt-btn-secondary\" id=\"btn-continue\">Continue refining</button></div></div>';document.getElementById('btn-accept').addEventListener('click',showResult);document.getElementById('btn-continue').addEventListener('click',()=>{earlyDone=true;advance();});}\n\nfunction buildAddConfirm(r){const rem=r.slice(3);if(!rem.length)return'';const opts=rem.map(m=>'<option value=\"'+m.id+'\">'+m.label+' (rank '+(r.indexOf(m)+1)+', weight '+(m.p*100).toFixed(1)+')</option>').join('');return'<div class=\"dt-add-confirm\"><div class=\"dt-add-confirm-label\">Also confirm a muscle not in the top three</div><div class=\"dt-add-confirm-row\"><select class=\"dt-add-select\" id=\"add-select\"><option value=\"\">\u2014 select muscle \u2014</option>'+opts+'</select><button class=\"dt-btn-add-confirm\" id=\"btn-add-confirm-ok\">Confirm</button></div><div class=\"dt-add-confirm-done\" id=\"add-confirm-done\">&#10003; Confirmed and model updated</div></div>';}\n\nfunction showResult(){\n  const r=ranked();\n  const ranks=['Most likely','2nd','3rd'],rk=['rk1','rk2','rk3'],card_cls=['dt-card-result','dt-card','dt-card'];\n  const cards=r.slice(0,3).map((m,i)=>{\n    const w=(m.p*100).toFixed(1);\n    const note=m.note?'<div class=\"dt-key-note\"><div class=\"dt-key-note-label\">Key TrP relationship</div>'+m.note+'</div>':'';\n    return'<div class=\"dt-card '+card_cls[i]+'\" style=\"margin-bottom:10px\">'+\n      '<div class=\"dt-result-rank '+rk[i]+'\">'+ranks[i]+'</div>'+\n      '<div class=\"dt-result-name\">'+m.label+'</div>'+\n      '<div class=\"dt-result-sub\">'+m.subtitle+'</div>'+\n      '<div class=\"dt-result-weight\">Relative weight: '+w+'</div>'+\n      '<div class=\"dt-result-actions\">'+\n      '<a class=\"dt-wiki-link\" href=\"https://painwiki.com/wiki/index.php?title='+m.page+'\" target=\"_blank\" rel=\"noopener\">'+m.label+' on PainWiki</a>'+\n      '<button class=\"dt-confirm-btn\" data-muscle=\"'+m.id+'\" data-label=\"'+encodeURIComponent(m.label)+'\">Confirm this muscle</button>'+\n      '</div>'+note+'</div>';\n  }).join('');\n  const addSec=buildAddConfirm(r);\n  document.getElementById('question-area').innerHTML=\n    '<div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.12em;text-transform:uppercase;color:#a8a29e;margin-bottom:8px\">Differential &mdash; final weights</div>'+\n    cards+\n    (addSec?'<div class=\"dt-card\" style=\"margin-top:4px\"><div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.1em;text-transform:uppercase;color:#a8a29e;margin-bottom:10px\">Additional Confirmation</div>'+addSec+'</div>':'');\n\n  const addBtn=document.getElementById('btn-add-confirm-ok');\n  if(addBtn){addBtn.addEventListener('click',()=>{const sel=document.getElementById('add-select'),mid=sel.value;if(!mid)return;incrementCount(mid);addBtn.disabled=true;sel.disabled=true;document.getElementById('add-confirm-done').style.display='block';renderLearnPanel();});}\n\n  const steps=buildTreatmentOrder(r);\n  if(steps.length){\n    const tp=document.getElementById('treat-panel');tp.classList.add('visible');\n    document.getElementById('treat-items').innerHTML=steps.map((s,i)=>{const isW=s.edgeType==='antagonist_risk';return'<div class=\"dt-treat-item\"><div class=\"dt-treat-step\">'+(i+1)+'</div><div><div class=\"dt-treat-muscle\">'+s.label+'</div><div class=\"dt-treat-edge'+(isW?' warn':'')+'\">'+s.edgeLabel+'</div>'+(s.note&&!isW?'<div class=\"dt-treat-edge\" style=\"margin-top:2px;font-size:10px;color:#a8a29e\">'+s.note+'</div>':'')+'</div></div>';}).join('');\n  }\n  renderLearnPanel();\n}\n\nfunction advance(){renderDiff();renderTrail();renderCounter();const r=ranked(),pw=getPairwise(r);if(pw){pairwiseDone.add(pw.id);renderQuestionFull(pw,true);return;}if(!earlyDone&&shouldEarlyExit(r)){renderEarlyExit(r);return;}const q=nextQ();if(q){queueIdx++;renderQuestionFull(q,false);return;}showResult();}\n\ndocument.addEventListener('click',e=>{\n  const ab=e.target.closest('.dt-answer,.answer-btn');\n  if(ab&&ab.dataset.qid){\n    const qid=ab.dataset.qid,aid=ab.dataset.aid,qLabel=decodeURIComponent(ab.dataset.qlabel),aLabel=decodeURIComponent(ab.dataset.alabel);\n    if(qid.startsWith('__pw__')){const pw=SCHEMA.pairwise.find(x=>x.id===qid.replace('__pw__',''));const a=pw.answers.find(x=>x.id===aid);posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});earlyDone=false;}\n    else{const q=SCHEMA.questions.find(x=>x.id===qid),a=q.answers.find(x=>x.id===aid);answers[qid]=aid;posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});queueIdx++;}\n    advance();return;\n  }\n  const cb=e.target.closest('.dt-confirm-btn');\n  if(cb&&!cb.classList.contains('confirmed')){const mid=cb.dataset.muscle,label=decodeURIComponent(cb.dataset.label);incrementCount(mid);cb.classList.add('confirmed');cb.textContent='\\u2713\\u2713 '+label+' confirmed';renderLearnPanel();return;}\n});\n\ndocument.getElementById('reset-btn').addEventListener('click',()=>{answers={};trail=[];pairwiseDone=new Set();queueIdx=0;earlyDone=false;posteriors=getUpdatedPriors();document.getElementById('trail-panel').classList.remove('visible');document.getElementById('treat-panel').classList.remove('visible');advance();});\ndocument.getElementById('learning-toggle-btn').addEventListener('click',()=>{const h=document.body.classList.toggle('hide-learning');document.getElementById('learning-toggle-btn').textContent='Learning: '+(h?'OFF':'ON');});\n\nbuildRedFlags();\nbuildBroadDiff();\n</script>";


   /* All scoring engine functions */
   /* All scoring engine functions */
Line 90: Line 90:
function condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}
function condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}
function nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}
function nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}
function buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL['key_satellite'],note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}
function buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL.key_satellite,note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}


// ── RENDER ─────────────────────────────────────────────────────────
// ── RENDER ─────────────────────────────────────────────────────────
Line 229: Line 229:
     if ( !treePage ) return;
     if ( !treePage ) return;


     hostEl.innerHTML = '<div style="padding:1.5em;font-family:'DM Mono',monospace;' +
     hostEl.innerHTML = '<div style="padding:1.5em;font-family:DM Mono,monospace;' +
       'font-size:0.8em;color:#a8a29e;text-align:center">Loading scoring model…</div>';
       'font-size:0.8em;color:#a8a29e;text-align:center">Loading scoring model…</div>';



Revision as of 17:27, 22 May 2026

/**
 * DiagnosticTree-Scoring.js — Upper Thoracic Probabilistic Scoring Model
 * Copy entire contents to: MediaWiki:Gadget-DiagnosticTreeScoring.js
 *
 * Embed on a wiki page with:
 *   <div class="scoring-tree-host"
 *        data-tree-page="DiagnosticTree/UpperThoracicBackPain">
 *   </div>
 *
 * Requires: mediawiki.api
 */

( function () {
  'use strict';

  /* Interface HTML template — injected into the host element on boot */
  var INTERFACE_HTML = "<div class=\"proto-shell\">\n\n<!-- MASTHEAD -->\n<header class=\"proto-masthead\">\n  <div class=\"proto-logo\">Pain<span>Wiki</span></div>\n  <div class=\"proto-right\">\n    <span class=\"proto-region\">Diagnostic Algorithm &middot; Upper Thoracic Back Pain</span>\n    <button class=\"learning-toggle\" id=\"learning-toggle-btn\">Learning: ON</button>\n  </div>\n</header>\n\n<!-- RED FLAGS -->\n<div class=\"rf-outer\">\n  <div class=\"rf-panel-wrap\" id=\"rf-emergency-panel\">\n    <div class=\"rf-col-header\">&#9888; Emergency &mdash; stop and act if any are present</div>\n    <div class=\"rf-items\" id=\"rf-emergency-items\"></div>\n  </div>\n  <div class=\"rf-panel-wrap urgent\" id=\"rf-urgent-panel\">\n    <div class=\"rf-col-header\">&#9888; Urgent &mdash; refer before myofascial assessment</div>\n    <div class=\"rf-items\" id=\"rf-urgent-items\"></div>\n    <div class=\"rf-affirm\">\n      <div class=\"rf-affirm-note\">All emergency and urgent flags screened and negative &mdash; or appropriate action taken. Safe to proceed.</div>\n      <button class=\"btn-affirm\" id=\"btn-affirm\">Proceed to clinical interview &rarr;</button>\n      <div class=\"affirmed-badge\" id=\"affirmed-badge\">Red flags cleared</div>\n    </div>\n  </div>\n</div>\n\n<!-- MAIN GRID -->\n<div id=\"main-grid\">\n  <div class=\"col-left\">\n    <div id=\"question-area\"></div>\n    <div class=\"dt-trail-panel\" id=\"trail-panel\">\n      <div class=\"dt-trail-head\">\n        <span class=\"dt-trail-title\">Clinical Interview Record</span>\n        <span class=\"dt-trail-meta\" id=\"trail-counter\"></span>\n      </div>\n      <div id=\"trail-items\"></div>\n    </div>\n  </div>\n  <div class=\"col-right\">\n    <div class=\"dt-running-panel\">\n      <div class=\"dt-running-head\">\n        <span class=\"dt-running-title\">Running Differential</span>\n        <span class=\"dt-running-meta\" id=\"q-counter\">Prior weights</span>\n      </div>\n      <div id=\"diff-rows\"></div>\n      <div class=\"dt-diff-caption\">Diagnostic weights are relative, not absolute probabilities. Initial values reflect muscle prevalence and clinical feature patterns from Travell &amp; Simons. Weights update as cases are confirmed.<br>Powered by LDA with Dirichlet-Multinomial updating.</div>\n    </div>\n    <div class=\"dt-treat-panel\" id=\"treat-panel\">\n      <div class=\"dt-treat-head\"><span class=\"dt-treat-title\">Suggested Treatment Order</span></div>\n      <div id=\"treat-items\"></div>\n      <div class=\"dt-treat-caption\">Sequence derived from Travell &amp; Simons satellite TrP and functional unit relationships, ordered by diagnostic weight.</div>\n    </div>\n    <div class=\"learn-panel\" id=\"learn-panel\">\n      <div class=\"learn-head\">\n        <span class=\"learn-title\">Confirmed Cases &mdash; This Device</span>\n        <span class=\"learn-meta\" id=\"learn-total\"></span>\n      </div>\n      <div class=\"learn-intro\"><strong>Multiple muscles can be confirmed per case.</strong> Confirm every contributing muscle &mdash; each confirmation independently updates the model.</div>\n      <div id=\"learn-rows\"></div>\n      <div class=\"learn-caption\">Confirmed diagnoses update prior weights via Dirichlet-Multinomial conjugate updating. Powered by LDA.</div>\n    </div>\n  </div>\n\n  <!-- BROAD DIFFERENTIAL \u2014 gadget style, spans full width -->\n  <div style=\"grid-column:1/-1\">\n    <div class=\"dt-broad-panel\" id=\"broad-panel\">\n      <div class=\"dt-broad-header\">\n        <div class=\"dt-broad-header-top\">\n          <div class=\"dt-broad-title\">&#9632; Broad Differential Diagnosis</div>\n          <button class=\"dt-broad-toggle\" id=\"broad-toggle-btn\">Hide</button>\n        </div>\n        <div class=\"dt-epigraph\">\n          &#8220;If he does not expect the unexpected, he will not discover it &mdash;\n          for it is difficult to discover and intractable.&#8221;\n          <cite>&mdash; Heraclitus, Fr. 18</cite>\n        </div>\n      </div>\n      <div class=\"dt-broad-grid\" id=\"broad-grid\"></div>\n    </div>\n  </div>\n</div><!-- /main-grid -->\n\n<div class=\"proto-foot\">\n  <div class=\"proto-disclaimer\"><strong>Not validated for clinical use.</strong> Research prototype for educational purposes. LR values derived from Travell &amp; Simons clinical descriptions, not formal epidemiological studies.</div>\n  <button class=\"btn-reset\" id=\"reset-btn\">&#8635; Start over</button>\n</div>\n</div><!-- /proto-shell -->\n\n<script>\nconst SCHEMA={\"model\": \"bayesian_lr_scoring\", \"version\": \"1.0\", \"description\": \"Probabilistic scoring tree for upper thoracic back pain using Bayesian likelihood ratio updating with Thurstonian pairwise tiebreakers\", \"thresholds\": {\"early_exit_posterior\": 0.55, \"early_exit_gap\": 0.18, \"pairwise_trigger\": 0.22}, \"muscles\": {\"trapezius_trp2\": {\"label\": \"Upper Trapezius\", \"prior\": 0.18, \"page\": \"Muscle:Trapezius\", \"key_trp_note\": \"Lower Trapezius TrP3 is the key TrP that most commonly induces upper trapezius satellites. Treat lower trapezius first when upper trapezius TrPs recur or respond poorly. The lower trapezius may itself be a satellite of key TrPs in the latissimus dorsi.\", \"subtitle\": \"Upper fibres \\u2014 posterolateral neck, behind ear, temple\"}, \"trapezius_trp3\": {\"label\": \"Lower Trapezius\", \"prior\": 0.13, \"page\": \"Muscle:Trapezius\", \"subtitle\": \"Lower fibres \\u2014 cervical, mastoid, suprascapular (\\\"the joker\\\")\"}, \"levator\": {\"label\": \"Levator Scapulae\", \"prior\": 0.16, \"page\": \"Muscle:Levator_Scapulae\", \"key_trp_note\": \"Check Lower Trapezius TrP3 first \\u2014 it is often the key TrP driving levator scapulae and upper trapezius satellites. If levator TrPs respond poorly to treatment, lower trapezius TrP3 has not been addressed. Upper trapezius TrP1/2 also drives levator as a satellite.\", \"subtitle\": \"Neck angle stiffness, cannot look over sore shoulder\"}, \"rhomboids\": {\"label\": \"Rhomboids\", \"prior\": 0.1, \"page\": \"Muscle:Rhomboids\", \"subtitle\": \"Medial scapular border burning, present at rest\"}, \"multifidi\": {\"label\": \"Multifidi\", \"prior\": 0.09, \"page\": \"Muscle:Multifidi\", \"subtitle\": \"Deep paraspinal ache immediately beside the spine\"}, \"scaleni\": {\"label\": \"Scaleni\", \"prior\": 0.11, \"page\": \"Muscle:Scalene\", \"key_trp_note\": \"Scaleni drive satellite TrPs in upper trapezius, triceps long head, pectoralis major and minor, and serratus posterior superior. Treat scaleni before their satellites. SCM should be treated concurrently.\", \"subtitle\": \"Upper back + arm/hand symptoms, nocturnal arm aching\"}, \"supraspinatus\": {\"label\": \"Supraspinatus\", \"prior\": 0.1, \"page\": \"Muscle:Supraspinatus\", \"key_trp_note\": \"Infraspinatus TrPs are often the key TrP source driving supraspinatus satellites. If supraspinatus TrPs recur, examine infraspinatus.\", \"subtitle\": \"Mid-deltoid ache, painful arc on shoulder abduction\"}, \"splenius\": {\"label\": \"Splenius Cervicis\", \"prior\": 0.07, \"page\": \"Muscle:Splenius_Cervicis\", \"subtitle\": \"Top of shoulder, rotation restricted away from pain\"}, \"triceps_trp1\": {\"label\": \"Triceps Brachii (Long Head)\", \"prior\": 0.04, \"page\": \"Muscle:Triceps_Brachii\", \"subtitle\": \"Posterior arm to scapula, skips the elbow\"}, \"biceps\": {\"label\": \"Biceps Brachii\", \"prior\": 0.02, \"page\": \"Muscle:Biceps_Brachii\", \"subtitle\": \"Anterior shoulder primary, suprascapular spillover\"}}, \"questions\": [{\"id\": \"q_neck_rotation\", \"text\": \"Is cervical rotation restricted or painful in any direction?\", \"sublabel\": \"Can the patient NOT turn their head to look fully over one or both shoulders?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 neck rotation is restricted or painful\", \"lr\": {\"levator\": 4.5, \"splenius\": 4.0, \"trapezius_trp2\": 1.4, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.6, \"scaleni\": 1.5, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 full pain-free cervical rotation in both directions\", \"lr\": {\"levator\": 0.15, \"splenius\": 0.18, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.4, \"multifidi\": 1.3, \"scaleni\": 0.8, \"supraspinatus\": 1.2, \"triceps_trp1\": 1.3, \"biceps\": 1.4}}]}, {\"id\": \"q_rotation_direction\", \"text\": \"In which direction is cervical rotation most restricted or painful?\", \"sublabel\": \"Turn the head toward the painful side, then away \\u2014 which direction is more limited?\", \"type\": \"choice\", \"condition\": {\"q_neck_rotation\": \"yes\"}, \"answers\": [{\"id\": \"toward\", \"label\": \"Cannot fully turn TOWARD the painful side\", \"sublabel\": \"Cannot look over the sore shoulder\", \"lr\": {\"levator\": 6.0, \"splenius\": 0.15, \"trapezius_trp2\": 1.2, \"scaleni\": 1.3, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"triceps_trp1\": 0.7, \"biceps\": 0.6}}, {\"id\": \"away\", \"label\": \"Cannot fully turn AWAY from the painful side\", \"sublabel\": \"Cannot look over the opposite shoulder; OR pain at top of shoulder\", \"lr\": {\"splenius\": 6.0, \"levator\": 0.15, \"trapezius_trp2\": 1.0, \"scaleni\": 1.2, \"trapezius_trp3\": 0.9, \"rhomboids\": 0.6, \"multifidi\": 0.7, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.6, \"biceps\": 0.5}}, {\"id\": \"both\", \"label\": \"Restricted in both directions\", \"lr\": {\"levator\": 2.0, \"splenius\": 2.0, \"scaleni\": 2.5, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.2, \"rhomboids\": 0.5, \"multifidi\": 0.8, \"supraspinatus\": 0.7, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}]}, {\"id\": \"q_pain_location\", \"text\": \"Where is the upper thoracic back pain most intense?\", \"sublabel\": \"Choose the location that best describes the dominant pain zone\", \"type\": \"choice\", \"answers\": [{\"id\": \"medial_border\", \"label\": \"Along the medial border of the scapula \\u2014 inner edge of the shoulder blade\", \"sublabel\": \"A burning sensation specifically along the edge of the scapula\", \"lr\": {\"rhomboids\": 6.0, \"trapezius_trp3\": 1.5, \"scaleni\": 2.0, \"levator\": 1.0, \"trapezius_trp2\": 0.6, \"multifidi\": 0.3, \"supraspinatus\": 0.5, \"splenius\": 0.4, \"triceps_trp1\": 0.5, \"biceps\": 0.3}}, {\"id\": \"interscapular\", \"label\": \"Between the shoulder blades \\u2014 diffuse interscapular or upper thoracic\", \"sublabel\": \"Aching or burning between or below the scapulae\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 3.0, \"rhomboids\": 1.5, \"scaleni\": 2.0, \"levator\": 0.8, \"multifidi\": 0.7, \"supraspinatus\": 0.8, \"splenius\": 0.6, \"triceps_trp1\": 1.5, \"biceps\": 1.5}}, {\"id\": \"paraspinal\", \"label\": \"Deep unilateral paraspinal ache \\u2014 beside the spine only\", \"sublabel\": \"Strictly beside the spine, not reaching the scapula\", \"lr\": {\"multifidi\": 5.0, \"trapezius_trp3\": 2.0, \"rhomboids\": 0.4, \"trapezius_trp2\": 0.5, \"levator\": 0.6, \"scaleni\": 0.5, \"supraspinatus\": 0.3, \"splenius\": 0.7, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder or neck angle \\u2014 not the scapular blade\", \"sublabel\": \"Pain at the peak of the shoulder slope or in the neck-shoulder angle\", \"lr\": {\"levator\": 3.5, \"splenius\": 4.0, \"trapezius_trp2\": 2.5, \"scaleni\": 1.5, \"trapezius_trp3\": 0.8, \"rhomboids\": 0.4, \"multifidi\": 0.3, \"supraspinatus\": 0.6, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"posterior_arm\", \"label\": \"Posterior arm or posterior shoulder \\u2014 back of the arm is the dominant pain\", \"sublabel\": \"Upper back aching is secondary to a posterior arm or shoulder complaint\", \"lr\": {\"triceps_trp1\": 7.0, \"biceps\": 0.3, \"supraspinatus\": 0.8, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"multifidi\": 0.2, \"scaleni\": 0.5, \"levator\": 0.4, \"splenius\": 0.3}}]}, {\"id\": \"q_arm_symptoms\", \"text\": \"Are there arm or hand symptoms alongside the upper back pain?\", \"sublabel\": \"Arm numbness, hand swelling, nocturnal arm aching, or arm heaviness\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 arm, forearm, or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"triceps_trp1\": 2.0, \"biceps\": 1.5, \"supraspinatus\": 1.3, \"levator\": 0.7, \"splenius\": 0.7, \"trapezius_trp2\": 0.6, \"trapezius_trp3\": 0.5, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain confined to the upper back and/or neck\", \"lr\": {\"scaleni\": 0.25, \"triceps_trp1\": 0.7, \"biceps\": 0.6, \"supraspinatus\": 0.9, \"levator\": 1.2, \"splenius\": 1.2, \"trapezius_trp2\": 1.3, \"trapezius_trp3\": 1.3, \"rhomboids\": 1.4, \"multifidi\": 1.4}}]}, {\"id\": \"q_nocturnal_arm\", \"text\": \"Is the patient woken at night by arm aching or numbness?\", \"sublabel\": \"Must hang the arm over the side of the bed or shake it for relief\", \"type\": \"binary\", \"condition\": {\"q_arm_symptoms\": \"yes\"}, \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 woken at night; arm hanging over bed side gives relief\", \"lr\": {\"scaleni\": 8.0, \"triceps_trp1\": 0.8, \"biceps\": 0.7, \"supraspinatus\": 0.6, \"levator\": 0.5, \"splenius\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 arm symptoms are daytime or positional only\", \"lr\": {\"scaleni\": 0.5, \"triceps_trp1\": 1.2, \"biceps\": 1.3, \"supraspinatus\": 1.2, \"levator\": 1.0, \"splenius\": 1.0, \"trapezius_trp2\": 1.0, \"trapezius_trp3\": 1.0, \"rhomboids\": 1.0, \"multifidi\": 1.0}}]}, {\"id\": \"q_postural\", \"text\": \"Is the pain clearly worse during or after sustained sitting, desk work, or forward head posture?\", \"sublabel\": \"Pain builds during a sitting work session; relieved by moving or lying down\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 clearly postural; builds with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp2\": 4.0, \"trapezius_trp3\": 3.5, \"levator\": 2.5, \"scaleni\": 2.0, \"splenius\": 2.0, \"multifidi\": 2.0, \"rhomboids\": 1.3, \"supraspinatus\": 1.0, \"triceps_trp1\": 1.5, \"biceps\": 1.0}}, {\"id\": \"no\", \"label\": \"No \\u2014 not clearly worse with posture or sitting\", \"lr\": {\"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.4, \"levator\": 0.6, \"scaleni\": 0.7, \"splenius\": 0.6, \"multifidi\": 0.6, \"rhomboids\": 1.5, \"supraspinatus\": 1.2, \"triceps_trp1\": 0.9, \"biceps\": 1.0}}]}, {\"id\": \"q_rest_pain\", \"text\": \"Is the pain present predominantly AT REST \\u2014 a burning or gnawing quality along the inner edge of the shoulder blade \\u2014 not clearly worse with movement?\", \"sublabel\": \"Burning along the medial scapular border that is present regardless of posture or arm movement\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 burning along the scapular border, present at rest\", \"lr\": {\"rhomboids\": 7.0, \"trapezius_trp3\": 1.3, \"scaleni\": 1.2, \"trapezius_trp2\": 0.5, \"levator\": 0.4, \"multifidi\": 0.4, \"supraspinatus\": 0.4, \"splenius\": 0.4, \"triceps_trp1\": 0.4, \"biceps\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 pain varies with posture, activity, or movement\", \"lr\": {\"rhomboids\": 0.3, \"trapezius_trp3\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.2, \"levator\": 1.2, \"multifidi\": 1.1, \"supraspinatus\": 1.2, \"splenius\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}, {\"id\": \"q_painful_arc\", \"text\": \"Is there a painful arc during shoulder abduction \\u2014 pain appearing between about 60\\u00b0 and 120\\u00b0 of raising the arm, then diminishing above that?\", \"sublabel\": \"Ask the patient to raise the arm sideways \\u2014 does pain appear and then disappear as the arm reaches overhead?\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 painful arc present between 60\\u00b0 and 120\\u00b0 of abduction\", \"lr\": {\"supraspinatus\": 8.0, \"biceps\": 1.5, \"scaleni\": 0.5, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.4, \"levator\": 0.6, \"splenius\": 0.5, \"multifidi\": 0.3, \"triceps_trp1\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 shoulder abduction is pain-free or uniformly painful throughout\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 0.9, \"scaleni\": 1.1, \"trapezius_trp2\": 1.2, \"trapezius_trp3\": 1.2, \"rhomboids\": 1.3, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.3, \"triceps_trp1\": 1.2}}]}, {\"id\": \"q_elbow_skip\", \"text\": \"Is there posterior arm pain that SKIPS the elbow \\u2014 pain above and/or below the elbow but NOT at the elbow itself?\", \"sublabel\": \"The back of the upper arm and/or dorsal forearm ache, but the elbow region is not painful\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 posterior arm pain skipping the elbow\", \"lr\": {\"triceps_trp1\": 9.0, \"biceps\": 0.3, \"supraspinatus\": 0.5, \"scaleni\": 0.6, \"trapezius_trp2\": 0.4, \"trapezius_trp3\": 0.3, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.3, \"multifidi\": 0.2}}, {\"id\": \"no\", \"label\": \"No \\u2014 no posterior arm pain, or elbow IS involved\", \"lr\": {\"triceps_trp1\": 0.15, \"biceps\": 1.1, \"supraspinatus\": 1.1, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_anterior_shoulder\", \"text\": \"Is the anterior shoulder the PRIMARY pain location \\u2014 and can the patient lie comfortably on the affected side?\", \"sublabel\": \"Anterior shoulder aching (front of shoulder) is the main complaint; lying on that side does not worsen pain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 anterior shoulder primary; can lie on the affected side\", \"lr\": {\"biceps\": 7.0, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.2, \"scaleni\": 0.7, \"trapezius_trp2\": 0.5, \"trapezius_trp3\": 0.4, \"rhomboids\": 0.3, \"levator\": 0.5, \"splenius\": 0.4, \"multifidi\": 0.3}}, {\"id\": \"no\", \"label\": \"No \\u2014 anterior shoulder is not the primary zone, or cannot lie on affected side\", \"lr\": {\"biceps\": 0.2, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.2, \"scaleni\": 1.0, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.2, \"levator\": 1.1, \"splenius\": 1.1, \"multifidi\": 1.2}}]}, {\"id\": \"q_visual_blur\", \"text\": \"Does the patient report any blurring of vision on the same side as the pain?\", \"sublabel\": \"Ipsilateral visual blurring \\u2014 not bilateral, not related to eye strain\", \"type\": \"binary\", \"answers\": [{\"id\": \"yes\", \"label\": \"Yes \\u2014 ipsilateral visual blurring reported\", \"lr\": {\"splenius\": 8.0, \"levator\": 0.9, \"trapezius_trp2\": 0.7, \"trapezius_trp3\": 0.6, \"rhomboids\": 0.5, \"multifidi\": 0.5, \"scaleni\": 0.6, \"supraspinatus\": 0.5, \"triceps_trp1\": 0.5, \"biceps\": 0.4}}, {\"id\": \"no\", \"label\": \"No \\u2014 no visual symptoms\", \"lr\": {\"splenius\": 0.6, \"levator\": 1.1, \"trapezius_trp2\": 1.1, \"trapezius_trp3\": 1.1, \"rhomboids\": 1.1, \"multifidi\": 1.1, \"scaleni\": 1.1, \"supraspinatus\": 1.1, \"triceps_trp1\": 1.1, \"biceps\": 1.1}}]}], \"pairwise\": [{\"id\": \"pw_levator_splenius\", \"pair\": [\"levator\", \"splenius\"], \"text\": \"Tiebreaker \\u2014 Levator Scapulae vs Splenius Cervicis\", \"question\": \"Where is the pain centred \\u2014 at the angle of the neck and upper scapular corner, OR at the TOP of the shoulder slope?\", \"answers\": [{\"id\": \"neck_angle\", \"label\": \"Neck angle and upper scapular corner\", \"lr\": {\"levator\": 4.0, \"splenius\": 0.25}}, {\"id\": \"top_shoulder\", \"label\": \"Top of the shoulder slope\", \"lr\": {\"levator\": 0.25, \"splenius\": 4.0}}]}, {\"id\": \"pw_trap2_trap3\", \"pair\": [\"trapezius_trp2\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP2 vs TrP3\", \"question\": \"Is the burning between the shoulder blades closer to the ACROMION and mid-scapular region, or closer to the THORACIC SPINE?\", \"answers\": [{\"id\": \"lateral\", \"label\": \"More lateral \\u2014 toward the acromion or mid-scapular area\", \"lr\": {\"trapezius_trp2\": 3.5, \"trapezius_trp3\": 0.3}}, {\"id\": \"medial\", \"label\": \"More medial \\u2014 close to the thoracic vertebrae\", \"lr\": {\"trapezius_trp2\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_trap_rhomboid\", \"pair\": [\"trapezius_trp3\", \"rhomboids\"], \"text\": \"Tiebreaker \\u2014 Trapezius TrP3 vs Rhomboids\", \"question\": \"Is the pain clearly worse with sustained sitting or posture, OR present equally at rest regardless of posture?\", \"answers\": [{\"id\": \"postural\", \"label\": \"Clearly worse with sustained sitting or forward posture\", \"lr\": {\"trapezius_trp3\": 3.0, \"rhomboids\": 0.3}}, {\"id\": \"rest\", \"label\": \"Present at rest \\u2014 not clearly postural\", \"lr\": {\"trapezius_trp3\": 0.3, \"rhomboids\": 3.0}}]}, {\"id\": \"pw_scaleni_levator\", \"pair\": [\"scaleni\", \"levator\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Levator Scapulae\", \"question\": \"Are there arm or hand symptoms (numbness, swelling, nocturnal aching) alongside the upper back pain?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 4.0, \"levator\": 0.3}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.3, \"levator\": 2.5}}]}, {\"id\": \"pw_supraspinatus_biceps\", \"pair\": [\"supraspinatus\", \"biceps\"], \"text\": \"Tiebreaker \\u2014 Supraspinatus vs Biceps Brachii\", \"question\": \"Is the arm pain primarily at the LATERAL upper arm (mid-deltoid), or at the ANTERIOR shoulder (front)?\", \"answers\": [{\"id\": \"lateral_arm\", \"label\": \"Lateral upper arm \\u2014 mid-deltoid region\", \"lr\": {\"supraspinatus\": 4.0, \"biceps\": 0.2}}, {\"id\": \"anterior\", \"label\": \"Anterior shoulder \\u2014 front of the shoulder\", \"lr\": {\"supraspinatus\": 0.2, \"biceps\": 4.0}}]}, {\"id\": \"pw_multifidi_trap3\", \"pair\": [\"multifidi\", \"trapezius_trp3\"], \"text\": \"Tiebreaker \\u2014 Multifidi vs Trapezius TrP3\", \"question\": \"Is the paraspinal pain DEEP and immediately adjacent to the spinous processes (within 2 cm of midline), or more SUPERFICIAL and extending laterally toward the scapula?\", \"answers\": [{\"id\": \"deep_midline\", \"label\": \"Deep \\u2014 immediately beside the spinous processes, within 2 cm of midline\", \"lr\": {\"multifidi\": 4.0, \"trapezius_trp3\": 0.3}}, {\"id\": \"superficial_lateral\", \"label\": \"Superficial \\u2014 extends laterally toward the scapular border\", \"lr\": {\"multifidi\": 0.3, \"trapezius_trp3\": 3.5}}]}, {\"id\": \"pw_scaleni_trap2\", \"pair\": [\"scaleni\", \"trapezius_trp2\"], \"text\": \"Tiebreaker \\u2014 Scaleni vs Trapezius TrP2\", \"question\": \"Are there any arm or hand symptoms \\u2014 numbness, tingling, hand swelling, or nocturnal arm aching?\", \"answers\": [{\"id\": \"arm_yes\", \"label\": \"Yes \\u2014 arm or hand symptoms present\", \"lr\": {\"scaleni\": 5.0, \"trapezius_trp2\": 0.2}}, {\"id\": \"arm_no\", \"label\": \"No \\u2014 upper back and neck only\", \"lr\": {\"scaleni\": 0.25, \"trapezius_trp2\": 3.5}}]}], \"treatment_dag\": {\"edges\": [{\"from\": \"scaleni\", \"to\": \"trapezius_trp2\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for upper trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"trapezius_trp3\", \"type\": \"key_satellite\", \"label\": \"Scaleni are a key TrP source for mid-trapezius satellites\"}, {\"from\": \"scaleni\", \"to\": \"triceps_trp1\", \"type\": \"key_satellite\", \"label\": \"Scaleni drive long head triceps satellite TrPs (T&S Table 3.3)\"}, {\"from\": \"trapezius_trp2\", \"to\": \"levator\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius TrP2 drives levator scapulae satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"splenius\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives splenius cervicis satellite TrPs\"}, {\"from\": \"trapezius_trp2\", \"to\": \"rhomboids\", \"type\": \"key_satellite\", \"label\": \"Upper trapezius drives rhomboid minor satellite TrPs\"}, {\"from\": \"levator\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Levator scapulae and upper trapezius share functional unit \\u2014 treat concurrently\"}, {\"from\": \"trapezius_trp2\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Upper and mid trapezius share functional unit \\u2014 treat in same session\"}, {\"from\": \"trapezius_trp3\", \"to\": \"rhomboids\", \"type\": \"functional_unit\", \"label\": \"Mid-trapezius and rhomboids share scapular retraction function\"}, {\"from\": \"supraspinatus\", \"to\": \"trapezius_trp2\", \"type\": \"functional_unit\", \"label\": \"Supraspinatus and upper trapezius share rotator cuff/shoulder girdle function\"}, {\"from\": \"multifidi\", \"to\": \"trapezius_trp3\", \"type\": \"functional_unit\", \"label\": \"Multifidi and mid-trapezius share thoracic stabilisation function\"}, {\"from\": \"scaleni\", \"to\": \"levator\", \"type\": \"secondary_load\", \"label\": \"Levator scapulae overloaded secondarily when scaleni are in protective splinting\"}, {\"from\": \"levator\", \"to\": \"rhomboids\", \"type\": \"secondary_load\", \"label\": \"Rhomboids overloaded secondarily with chronic levator dysfunction\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"secondary_load\", \"label\": \"Biceps develops secondary TrPs as antagonist to triceps \\u2014 treat after triceps resolves\"}, {\"from\": \"triceps_trp1\", \"to\": \"biceps\", \"type\": \"antagonist_risk\", \"label\": \"Triceps/biceps antagonist pair \\u2014 treat in alternating partial cycles, not sequentially to completion\"}, {\"from\": \"supraspinatus\", \"to\": \"scaleni\", \"type\": \"antagonist_risk\", \"label\": \"Releasing supraspinatus may reactively cramp scaleni \\u2014 treat alternately\"}], \"edge_type_labels\": {\"key_satellite\": \"Treat first \\u2014 key TrP driving satellites\", \"functional_unit\": \"Treat concurrently in same session\", \"secondary_load\": \"Treat after primary resolves\", \"antagonist_risk\": \"\\u26a0 Treat in alternating cycles \\u2014 reactive cramp risk\"}}};\nconst T=SCHEMA.thresholds,MUSCLE_IDS=Object.keys(SCHEMA.muscles),DAG=SCHEMA.treatment_dag;\nconst LS_KEY='painwiki_upper_thoracic_counts_v1';\nconst RF_EMERGENCY=[{\"id\": \"rf-e1\", \"label\": \"Aortic dissection\", \"question\": \"Sudden tearing or ripping interscapular pain; hypertension or Marfan features; pulse or BP difference between arms?\"}, {\"id\": \"rf-e2\", \"label\": \"Pulmonary embolism\", \"question\": \"Sudden-onset pleuritic chest or back pain (sharp, worse on inhalation); unexplained breathlessness, tachycardia, or hypoxia; recent immobility, surgery, or long-haul travel?\"}, {\"id\": \"rf-e3\", \"label\": \"Spinal cord compression / myelopathy\", \"question\": \"Bilateral arm or leg weakness, gait disturbance, loss of hand dexterity, or bowel/bladder dysfunction alongside neck or upper back pain?\"}, {\"id\": \"rf-e4\", \"label\": \"Meningism\", \"question\": \"Neck and upper back pain with fever, photophobia, or cerebellar signs (ataxia, dysarthria, nystagmus)?\"}];\nconst RF_URGENT=[{\"id\": \"rf-u1\", \"label\": \"Vertebral fracture\", \"question\": \"History of significant trauma, or patient is osteoporotic (post-menopausal, long-term corticosteroids, age > 70) with sudden-onset upper thoracic pain?\"}, {\"id\": \"rf-u2\", \"label\": \"Serious spinal pathology (tumour / infection)\", \"question\": \"Constant, progressive upper thoracic pain unrelated to posture or movement, worse at night lying down? Unexplained weight loss, fever, or history of cancer?\"}, {\"id\": \"rf-u3\", \"label\": \"Cervical instability\", \"question\": \"History of head or neck trauma combined with bilateral upper limb symptoms, gait disturbance, or upper cervical pain?\"}, {\"id\": \"rf-u4\", \"label\": \"Cardiac angina (exertional component)\", \"question\": \"Upper thoracic or interscapular pain with a clear exertional component, relieved by rest or GTN? Left-sided with arm radiation?\"}, {\"id\": \"rf-u5\", \"label\": \"Inflammatory arthropathy\", \"question\": \"Bilateral posterior neck or thoracic stiffness WORSE in the morning and improving with movement, with peripheral joint swelling or systemic symptoms?\"}];\nconst BROAD_DIFF=[{\"condition\": \"Cervical disc herniation (C5\\u2013C7)\", \"confidence\": \"uncommon\", \"mimics\": \"Upper thoracic and interscapular referred pain via dorsal rami; arm symptoms overlapping with scaleni and trapezius TrP patterns\", \"distinguishing_feature\": \"Dermatomal arm pain; reflex change (biceps C5\\u2013C6, triceps C7); true myotomal weakness; Spurling's test positive. TrP pain does not produce reflex changes or dermatomal sensory deficit. TrPs commonly develop secondarily to radiculopathy.\", \"action\": \"MRI cervical spine if neurological signs present. Treat TrPs concurrently \\u2014 they frequently coexist with disc pathology and may be the dominant pain source.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 neurological\", \"confidence\": \"rare\", \"mimics\": \"Arm and hand symptoms with upper thoracic pain; ulnar symptoms overlap with scaleni and pectoralis minor patterns\", \"distinguishing_feature\": \"Roos test (EAST test) positive at 3 minutes; nerve conduction studies confirm. Scaleni TrPs frequently coexist with and drive TOS symptoms \\u2014 TrP inactivation often resolves or substantially reduces TOS.\", \"action\": \"Inactivate scaleni TrPs first before TOS workup. Refer for nerve conduction studies if symptoms persist after TrP treatment.\"}, {\"condition\": \"Thoracic outlet syndrome \\u2014 vascular\", \"confidence\": \"rare\", \"mimics\": \"Anterior shoulder and arm pain with upper thoracic aching; overlaps with subclavius and scaleni TrP patterns\", \"distinguishing_feature\": \"Radial pulse reduction or loss with arm abduction; hand oedema and finger stiffness. Wright manoeuvre positive. Scaleni and subclavius TrPs contribute to vascular compression via taut band tension.\", \"action\": \"Check radial pulse in standard and abducted positions. Scaleni and subclavius TrP inactivation is first-line. Refer for vascular assessment if pulse loss persists after TrP treatment.\"}, {\"condition\": \"Rotator cuff tendinopathy / subacromial impingement\", \"confidence\": \"uncommon\", \"mimics\": \"Painful arc on shoulder abduction and lateral arm aching overlapping with supraspinatus TrP referral to the upper thoracic region\", \"distinguishing_feature\": \"Tenderness at greater tuberosity insertion; positive Neer's or Hawkins-Kennedy sign; imaging confirms tendon changes. TrP taut bands impose sustained enthesopathic tension \\u2014 coexistence is common and causally related.\", \"action\": \"Treat supraspinatus TrPs first and reassess tendon findings after inactivation. Both conditions require treatment when identified together.\"}, {\"condition\": \"Thoracic zygapophyseal (facet) joint pain\", \"confidence\": \"uncommon\", \"mimics\": \"Deep upper thoracic paraspinal pain in the same zone as multifidi TrP referral\", \"distinguishing_feature\": \"Pain reproduced by passive PA intersegmental pressures over the facet joints; hard end-feel on accessory movement testing. Multifidi TrPs and facet dysfunction coexist at the same segment and perpetuate each other \\u2014 soft end-feel suggests TrP predominance.\", \"action\": \"Segmental accessory movement testing. Treat multifidi TrPs first \\u2014 articular dysfunction often resolves with TrP inactivation. Mobilise the segment if hard end-feel persists.\"}, {\"condition\": \"Herpes zoster \\u2014 pre-eruptive and post-herpetic\", \"confidence\": \"uncommon\", \"mimics\": \"Unilateral burning upper thoracic pain before the rash appears \\u2014 indistinguishable from rhomboid, multifidi, or intercostal TrP patterns at onset\", \"distinguishing_feature\": \"Dermatomal distribution; allodynia (light touch painful in a band); vesicles appear 1\\u20134 days after pain onset. Age > 50 or immunocompromised increases suspicion. Post-herpetic: TrP pain coexists with neurogenic shooting pain.\", \"action\": \"Examine the skin carefully at every visit for unilateral burning thoracic pain. If vesicles present, refer urgently \\u2014 antiviral window is 72 hours from rash onset.\"}, {\"condition\": \"First rib dysfunction\", \"confidence\": \"rare\", \"mimics\": \"Upper thoracic and arm symptoms closely associated with scaleni TrP presentation \\u2014 scaleni attach to the first rib and their taut bands directly elevate it\", \"distinguishing_feature\": \"First rib elevated and tender on posterior superior palpation; restricted first rib caudal glide on accessory movement testing. Scaleni TrPs are almost universally present concurrently.\", \"action\": \"First rib mobilisation in conjunction with scaleni TrP inactivation \\u2014 the two conditions are mechanically linked and must be treated together.\"}, {\"condition\": \"Winged scapula / serratus anterior weakness\", \"confidence\": \"rare\", \"mimics\": \"Medial scapular border pain and upper thoracic aching overlapping with rhomboid TrP presentation\", \"distinguishing_feature\": \"Scapular winging visible on push-up against wall or forward arm elevation; serratus anterior weakness on manual muscle testing. Rhomboid TrPs develop secondarily to serratus anterior inhibition \\u2014 the rhomboids are overloaded trying to retract a scapula that serratus cannot stabilise.\", \"action\": \"Serratus anterior rehabilitation is the primary treatment. Treat rhomboid TrPs concurrently but address serratus anterior as the underlying driver \\u2014 rhomboid TrPs will recur without it.\"}, {\"condition\": \"Glenohumeral osteoarthritis\", \"confidence\": \"uncommon\", \"mimics\": \"Diffuse shoulder girdle and upper thoracic pain, particularly in older patients with supraspinatus and biceps TrP patterns\", \"distinguishing_feature\": \"Capsular pattern restriction (external rotation > abduction > internal rotation); crepitus on movement; radiological changes. Arthritis does not produce spot-tender taut bands. TrPs coexist and are independently treatable.\", \"action\": \"Shoulder radiograph. Inactivate TrPs concurrently with joint management \\u2014 TrP treatment can substantially reduce pain even in the presence of established arthritis.\"}, {\"condition\": \"Fibromyalgia\", \"confidence\": \"uncommon\", \"mimics\": \"Widespread upper back and shoulder girdle tenderness overlapping with all muscles in this algorithm simultaneously\", \"distinguishing_feature\": \"Widespread pain \\u2265 3 months across multiple body regions; diffuse tenderness without specific TrP referral patterns; fatigue and non-restorative sleep. TrPs produce specific referred pain patterns and are a treatable component of fibromyalgia.\", \"action\": \"Systematic TrP examination alongside fibromyalgia management. Treating active TrPs reduces the overall pain burden independently \\u2014 do not withhold TrP treatment because fibromyalgia is also present.\"}, {\"condition\": \"Residual pain after spinal manipulation or injection\", \"confidence\": \"atypical\", \"mimics\": \"Persistent upper thoracic pain after a spinal procedure \\u2014 may be re-attributed to the joint when myofascial TrPs are the active pain source\", \"distinguishing_feature\": \"After a spinal intervention, pain persisting at a lower level or shifting in character indicates TrPs masked by dominant segmental pain are now the primary source. The TrPs were pre-existing \\u2014 not created by the procedure.\", \"action\": \"Re-examine muscles systematically after any spinal intervention. Most commonly harbouring residual TrPs: trapezius (upper and mid), levator scapulae, rhomboids, multifidi.\"}];\n\n// \u2500\u2500 RED FLAGS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildRedFlags(){\n  function makeFlags(arr,id,isUrgent){\n    document.getElementById(id).innerHTML=arr.map(rf=>\n      '<div class=\"rf-item\">'+\n      '<label class=\"rf-check-wrap\" for=\"'+rf.id+'\">'+\n      '<input type=\"checkbox\" class=\"rf-cb\" id=\"'+rf.id+'\">'+\n      '</label>'+\n      '<div class=\"rf-body\">'+\n      '<div class=\"rf-label\">'+rf.label+'</div>'+\n      '<div class=\"rf-question\">'+rf.question+'</div>'+\n      '</div></div>'\n    ).join('');\n  }\n  makeFlags(RF_EMERGENCY,'rf-emergency-items',false);\n  makeFlags(RF_URGENT,'rf-urgent-items',true);\n  document.getElementById('btn-affirm').addEventListener('click',()=>{\n    document.getElementById('btn-affirm').style.display='none';\n    document.getElementById('affirmed-badge').classList.add('show');\n    const mg=document.getElementById('main-grid');\n    mg.classList.add('visible');\n    posteriors=getUpdatedPriors();advance();renderLearnPanel();\n    setTimeout(()=>mg.scrollIntoView({behavior:'smooth',block:'start'}),80);\n  });\n}\n\n// \u2500\u2500 BROAD DIFFERENTIAL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction buildBroadDiff(){\n  document.getElementById('broad-grid').innerHTML=BROAD_DIFF.map(d=>\n    '<div class=\"dt-diff-item\">'+\n    '<div class=\"dt-diff-confidence '+d.confidence+'\">'+d.confidence+'</div>'+\n    '<div class=\"dt-diff-name\">'+d.condition+'</div>'+\n    '<div class=\"dt-diff-mimics\">'+d.mimics+'</div>'+\n    '<div class=\"dt-diff-detail\">'+\n    '<div class=\"dt-diff-distinguisher\"><strong>Distinguishing features</strong>'+d.distinguishing_feature+'</div>'+\n    '<div class=\"dt-diff-action\"><strong>Action</strong>'+d.action+'</div>'+\n    '</div></div>'\n  ).join('');\n  // click to expand\n  document.getElementById('broad-grid').addEventListener('click',e=>{\n    const item=e.target.closest('.dt-diff-item');\n    if(item) item.classList.toggle('open');\n  });\n  // toggle visibility\n  document.getElementById('broad-toggle-btn').addEventListener('click',()=>{\n    const grid=document.getElementById('broad-grid');\n    const hidden=grid.classList.toggle('hidden');\n    document.getElementById('broad-toggle-btn').textContent=hidden?'Show':'Hide';\n  });\n}\n\n// \u2500\u2500 LEARNING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction loadCounts(){try{const r=localStorage.getItem(LS_KEY);if(r)return JSON.parse(r);}catch(e){}const c={};MUSCLE_IDS.forEach(m=>c[m]=0);return c;}\nfunction saveCounts(c){try{localStorage.setItem(LS_KEY,JSON.stringify(c));}catch(e){}}\nfunction incrementCount(mid){const c=loadCounts();c[mid]=(c[mid]||0)+1;saveCounts(c);return c;}\nfunction getUpdatedPriors(){const counts=loadCounts(),p={};MUSCLE_IDS.forEach(m=>p[m]=SCHEMA.muscles[m].prior+(counts[m]||0)*0.02);return normalise(p);}\n\n// \u2500\u2500 STATE & MATH \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet answers={},posteriors={},trail=[],pairwiseDone=new Set(),queueIdx=0,earlyDone=false;\nfunction normalise(p){let t=0;MUSCLE_IDS.forEach(m=>t+=p[m]);const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]/t);return o;}\nfunction applyLR(p,lr){const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]*(lr[m]||1.0));return normalise(o);}\nfunction ranked(){return MUSCLE_IDS.map(m=>({id:m,p:posteriors[m],label:SCHEMA.muscles[m].label,subtitle:SCHEMA.muscles[m].subtitle||'',page:SCHEMA.muscles[m].page,note:SCHEMA.muscles[m].key_trp_note||null})).sort((a,b)=>b.p-a.p);}\nfunction shouldEarlyExit(r){return r[0].p>=T.early_exit_posterior&&(r[0].p-r[1].p)>=T.early_exit_gap;}\nfunction getPairwise(r){if((r[0].p-r[1].p)>=T.pairwise_trigger)return null;const s=new Set([r[0].id,r[1].id]);for(const pw of SCHEMA.pairwise)if(!pairwiseDone.has(pw.id)&&pw.pair.every(x=>s.has(x)))return pw;return null;}\nfunction condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}\nfunction nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}\nfunction buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL.key_satellite,note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}\n\n// \u2500\u2500 RENDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction renderDiff(){const r=ranked();document.getElementById('diff-rows').innerHTML=r.map((m,i)=>{const rank=i+1,w=(m.p*100).toFixed(1),bw=Math.max(0.5,m.p*100).toFixed(1),dim=rank>6?' dimmed':'',rc=rank<=3?' r'+rank:'';return'<div class=\"dt-diff-bar-row'+rc+dim+'\"><div class=\"dt-diff-bar-rank\">'+rank+'</div><div class=\"dt-diff-bar-name\">'+m.label+'</div><div class=\"dt-diff-bar-track\"><div class=\"dt-diff-bar-fill\" style=\"width:'+bw+'%\"></div></div><div class=\"dt-diff-bar-weight\">'+w+'</div></div>';}).join('');}\nfunction renderTrail(){const el=document.getElementById('trail-panel');if(!trail.length){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('trail-counter').textContent=trail.length+' feature'+(trail.length>1?'s':'')+' recorded';document.getElementById('trail-items').innerHTML=trail.map(t=>'<div class=\"dt-trail-item\"><div class=\"dt-trail-q\">'+t.q+'</div><div class=\"dt-trail-a\">&#8627; '+t.a+'</div></div>').join('');}\nfunction renderCounter(){const tot=SCHEMA.questions.length,ans=Object.keys(answers).length;document.getElementById('q-counter').textContent=ans===0?'Prior weights':ans+'\\u202f/\\u202f'+tot+' features';}\nfunction renderLearnPanel(){const counts=loadCounts(),total=Object.values(counts).reduce((a,b)=>a+b,0);const el=document.getElementById('learn-panel');if(!total){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('learn-total').textContent=total+' confirmation'+(total>1?'s':'');document.getElementById('learn-rows').innerHTML=MUSCLE_IDS.filter(m=>counts[m]>0).sort((a,b)=>counts[b]-counts[a]).map(m=>'<div class=\"learn-row\"><div class=\"learn-muscle\">'+SCHEMA.muscles[m].label+'</div><div class=\"learn-count\">'+counts[m]+'&times;</div></div>').join('');}\nfunction renderQuestion(q,isPW){const qText=q.text||q.question;const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';const sub=q.sublabel?'<div class=\"dt-rationale\">'+q.sublabel+'</div>':'';const btns='<div class=\"dt-answers\"><button class=\"dt-answer dt-answer-yes\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"yes\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('Yes')+'\">Yes</button><button class=\"dt-answer dt-answer-no\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"no\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent('No')+'\">No</button></div>';document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+btns+'</div>';}\n\n// Fix: answer buttons need correct aid from schema\nfunction renderQuestionFull(q,isPW){\n  const qText=q.text||q.question;\n  const label=isPW?'<div class=\"dt-card-label pairwise\">&#9889; Tiebreaker</div>':'<div class=\"dt-card-label\">Clinical Feature '+(trail.length+1)+'</div>';\n  const sub=q.sublabel?'<div class=\"dt-rationale\" style=\"font-size:.78rem;color:#57534e;font-style:italic;margin-bottom:.55rem;line-height:1.5\">'+q.sublabel+'</div>':'';\n  const btns=q.answers.map(a=>{\n    const asub=a.sublabel?'<div style=\"font-size:.72rem;color:#57534e;font-weight:400;margin-top:2px\">'+a.sublabel+'</div>':'';\n    const cls=q.answers.length===2\n      ? (q.answers.indexOf(a)===0?'dt-answer dt-answer-yes':'dt-answer dt-answer-no')\n      : 'dt-answer dt-answer-yes';\n    return'<button class=\"'+cls+'\" style=\"'+(q.answers.length>2?'flex:none;flex-direction:column;align-items:flex-start;':'')+'\" data-qid=\"'+(isPW?'__pw__'+q.id:q.id)+'\" data-aid=\"'+a.id+'\" data-qlabel=\"'+encodeURIComponent(qText)+'\" data-alabel=\"'+encodeURIComponent(a.label)+'\">'+a.label+asub+'</button>';\n  }).join('');\n  const ansWrap=q.answers.length>2\n    ?'<div style=\"display:flex;flex-direction:column;gap:.5rem;margin-top:1rem\">'+btns+'</div>'\n    :'<div class=\"dt-answers\">'+btns+'</div>';\n  document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-card-head\">'+label+'</div><div class=\"dt-question\">'+qText+'</div>'+sub+ansWrap+'</div>';\n}\n\nfunction renderEarlyExit(r){earlyDone=true;const top=r[0];document.getElementById('question-area').innerHTML='<div class=\"dt-card\"><div class=\"dt-early-badge\">Confident result available</div><div class=\"dt-early-muscle\">'+top.label+'</div><div class=\"dt-early-sub\">'+top.subtitle+'<br>Relative weight '+(top.p*100).toFixed(0)+' &mdash; accept to view result, or continue refining.</div><div class=\"dt-early-btns\"><button class=\"dt-btn-primary\" id=\"btn-accept\">Accept result</button><button class=\"dt-btn-secondary\" id=\"btn-continue\">Continue refining</button></div></div>';document.getElementById('btn-accept').addEventListener('click',showResult);document.getElementById('btn-continue').addEventListener('click',()=>{earlyDone=true;advance();});}\n\nfunction buildAddConfirm(r){const rem=r.slice(3);if(!rem.length)return'';const opts=rem.map(m=>'<option value=\"'+m.id+'\">'+m.label+' (rank '+(r.indexOf(m)+1)+', weight '+(m.p*100).toFixed(1)+')</option>').join('');return'<div class=\"dt-add-confirm\"><div class=\"dt-add-confirm-label\">Also confirm a muscle not in the top three</div><div class=\"dt-add-confirm-row\"><select class=\"dt-add-select\" id=\"add-select\"><option value=\"\">\u2014 select muscle \u2014</option>'+opts+'</select><button class=\"dt-btn-add-confirm\" id=\"btn-add-confirm-ok\">Confirm</button></div><div class=\"dt-add-confirm-done\" id=\"add-confirm-done\">&#10003; Confirmed and model updated</div></div>';}\n\nfunction showResult(){\n  const r=ranked();\n  const ranks=['Most likely','2nd','3rd'],rk=['rk1','rk2','rk3'],card_cls=['dt-card-result','dt-card','dt-card'];\n  const cards=r.slice(0,3).map((m,i)=>{\n    const w=(m.p*100).toFixed(1);\n    const note=m.note?'<div class=\"dt-key-note\"><div class=\"dt-key-note-label\">Key TrP relationship</div>'+m.note+'</div>':'';\n    return'<div class=\"dt-card '+card_cls[i]+'\" style=\"margin-bottom:10px\">'+\n      '<div class=\"dt-result-rank '+rk[i]+'\">'+ranks[i]+'</div>'+\n      '<div class=\"dt-result-name\">'+m.label+'</div>'+\n      '<div class=\"dt-result-sub\">'+m.subtitle+'</div>'+\n      '<div class=\"dt-result-weight\">Relative weight: '+w+'</div>'+\n      '<div class=\"dt-result-actions\">'+\n      '<a class=\"dt-wiki-link\" href=\"https://painwiki.com/wiki/index.php?title='+m.page+'\" target=\"_blank\" rel=\"noopener\">'+m.label+' on PainWiki</a>'+\n      '<button class=\"dt-confirm-btn\" data-muscle=\"'+m.id+'\" data-label=\"'+encodeURIComponent(m.label)+'\">Confirm this muscle</button>'+\n      '</div>'+note+'</div>';\n  }).join('');\n  const addSec=buildAddConfirm(r);\n  document.getElementById('question-area').innerHTML=\n    '<div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.12em;text-transform:uppercase;color:#a8a29e;margin-bottom:8px\">Differential &mdash; final weights</div>'+\n    cards+\n    (addSec?'<div class=\"dt-card\" style=\"margin-top:4px\"><div style=\"font-family:\\'DM Mono\\',monospace;font-size:.65rem;letter-spacing:.1em;text-transform:uppercase;color:#a8a29e;margin-bottom:10px\">Additional Confirmation</div>'+addSec+'</div>':'');\n\n  const addBtn=document.getElementById('btn-add-confirm-ok');\n  if(addBtn){addBtn.addEventListener('click',()=>{const sel=document.getElementById('add-select'),mid=sel.value;if(!mid)return;incrementCount(mid);addBtn.disabled=true;sel.disabled=true;document.getElementById('add-confirm-done').style.display='block';renderLearnPanel();});}\n\n  const steps=buildTreatmentOrder(r);\n  if(steps.length){\n    const tp=document.getElementById('treat-panel');tp.classList.add('visible');\n    document.getElementById('treat-items').innerHTML=steps.map((s,i)=>{const isW=s.edgeType==='antagonist_risk';return'<div class=\"dt-treat-item\"><div class=\"dt-treat-step\">'+(i+1)+'</div><div><div class=\"dt-treat-muscle\">'+s.label+'</div><div class=\"dt-treat-edge'+(isW?' warn':'')+'\">'+s.edgeLabel+'</div>'+(s.note&&!isW?'<div class=\"dt-treat-edge\" style=\"margin-top:2px;font-size:10px;color:#a8a29e\">'+s.note+'</div>':'')+'</div></div>';}).join('');\n  }\n  renderLearnPanel();\n}\n\nfunction advance(){renderDiff();renderTrail();renderCounter();const r=ranked(),pw=getPairwise(r);if(pw){pairwiseDone.add(pw.id);renderQuestionFull(pw,true);return;}if(!earlyDone&&shouldEarlyExit(r)){renderEarlyExit(r);return;}const q=nextQ();if(q){queueIdx++;renderQuestionFull(q,false);return;}showResult();}\n\ndocument.addEventListener('click',e=>{\n  const ab=e.target.closest('.dt-answer,.answer-btn');\n  if(ab&&ab.dataset.qid){\n    const qid=ab.dataset.qid,aid=ab.dataset.aid,qLabel=decodeURIComponent(ab.dataset.qlabel),aLabel=decodeURIComponent(ab.dataset.alabel);\n    if(qid.startsWith('__pw__')){const pw=SCHEMA.pairwise.find(x=>x.id===qid.replace('__pw__',''));const a=pw.answers.find(x=>x.id===aid);posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});earlyDone=false;}\n    else{const q=SCHEMA.questions.find(x=>x.id===qid),a=q.answers.find(x=>x.id===aid);answers[qid]=aid;posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});queueIdx++;}\n    advance();return;\n  }\n  const cb=e.target.closest('.dt-confirm-btn');\n  if(cb&&!cb.classList.contains('confirmed')){const mid=cb.dataset.muscle,label=decodeURIComponent(cb.dataset.label);incrementCount(mid);cb.classList.add('confirmed');cb.textContent='\\u2713\\u2713 '+label+' confirmed';renderLearnPanel();return;}\n});\n\ndocument.getElementById('reset-btn').addEventListener('click',()=>{answers={};trail=[];pairwiseDone=new Set();queueIdx=0;earlyDone=false;posteriors=getUpdatedPriors();document.getElementById('trail-panel').classList.remove('visible');document.getElementById('treat-panel').classList.remove('visible');advance();});\ndocument.getElementById('learning-toggle-btn').addEventListener('click',()=>{const h=document.body.classList.toggle('hide-learning');document.getElementById('learning-toggle-btn').textContent='Learning: '+(h?'OFF':'ON');});\n\nbuildRedFlags();\nbuildBroadDiff();\n</script>";

  /* All scoring engine functions */
const T=SCHEMA.thresholds,MUSCLE_IDS=Object.keys(SCHEMA.muscles),DAG=SCHEMA.treatment_dag;
const LS_KEY='painwiki_upper_thoracic_counts_v1';
const RF_EMERGENCY=[{"id": "rf-e1", "label": "Aortic dissection", "question": "Sudden tearing or ripping interscapular pain; hypertension or Marfan features; pulse or BP difference between arms?"}, {"id": "rf-e2", "label": "Pulmonary embolism", "question": "Sudden-onset pleuritic chest or back pain (sharp, worse on inhalation); unexplained breathlessness, tachycardia, or hypoxia; recent immobility, surgery, or long-haul travel?"}, {"id": "rf-e3", "label": "Spinal cord compression / myelopathy", "question": "Bilateral arm or leg weakness, gait disturbance, loss of hand dexterity, or bowel/bladder dysfunction alongside neck or upper back pain?"}, {"id": "rf-e4", "label": "Meningism", "question": "Neck and upper back pain with fever, photophobia, or cerebellar signs (ataxia, dysarthria, nystagmus)?"}];
const RF_URGENT=[{"id": "rf-u1", "label": "Vertebral fracture", "question": "History of significant trauma, or patient is osteoporotic (post-menopausal, long-term corticosteroids, age > 70) with sudden-onset upper thoracic pain?"}, {"id": "rf-u2", "label": "Serious spinal pathology (tumour / infection)", "question": "Constant, progressive upper thoracic pain unrelated to posture or movement, worse at night lying down? Unexplained weight loss, fever, or history of cancer?"}, {"id": "rf-u3", "label": "Cervical instability", "question": "History of head or neck trauma combined with bilateral upper limb symptoms, gait disturbance, or upper cervical pain?"}, {"id": "rf-u4", "label": "Cardiac angina (exertional component)", "question": "Upper thoracic or interscapular pain with a clear exertional component, relieved by rest or GTN? Left-sided with arm radiation?"}, {"id": "rf-u5", "label": "Inflammatory arthropathy", "question": "Bilateral posterior neck or thoracic stiffness WORSE in the morning and improving with movement, with peripheral joint swelling or systemic symptoms?"}];
const BROAD_DIFF=[{"condition": "Cervical disc herniation (C5\u2013C7)", "confidence": "uncommon", "mimics": "Upper thoracic and interscapular referred pain via dorsal rami; arm symptoms overlapping with scaleni and trapezius TrP patterns", "distinguishing_feature": "Dermatomal arm pain; reflex change (biceps C5\u2013C6, triceps C7); true myotomal weakness; Spurling's test positive. TrP pain does not produce reflex changes or dermatomal sensory deficit. TrPs commonly develop secondarily to radiculopathy.", "action": "MRI cervical spine if neurological signs present. Treat TrPs concurrently \u2014 they frequently coexist with disc pathology and may be the dominant pain source."}, {"condition": "Thoracic outlet syndrome \u2014 neurological", "confidence": "rare", "mimics": "Arm and hand symptoms with upper thoracic pain; ulnar symptoms overlap with scaleni and pectoralis minor patterns", "distinguishing_feature": "Roos test (EAST test) positive at 3 minutes; nerve conduction studies confirm. Scaleni TrPs frequently coexist with and drive TOS symptoms \u2014 TrP inactivation often resolves or substantially reduces TOS.", "action": "Inactivate scaleni TrPs first before TOS workup. Refer for nerve conduction studies if symptoms persist after TrP treatment."}, {"condition": "Thoracic outlet syndrome \u2014 vascular", "confidence": "rare", "mimics": "Anterior shoulder and arm pain with upper thoracic aching; overlaps with subclavius and scaleni TrP patterns", "distinguishing_feature": "Radial pulse reduction or loss with arm abduction; hand oedema and finger stiffness. Wright manoeuvre positive. Scaleni and subclavius TrPs contribute to vascular compression via taut band tension.", "action": "Check radial pulse in standard and abducted positions. Scaleni and subclavius TrP inactivation is first-line. Refer for vascular assessment if pulse loss persists after TrP treatment."}, {"condition": "Rotator cuff tendinopathy / subacromial impingement", "confidence": "uncommon", "mimics": "Painful arc on shoulder abduction and lateral arm aching overlapping with supraspinatus TrP referral to the upper thoracic region", "distinguishing_feature": "Tenderness at greater tuberosity insertion; positive Neer's or Hawkins-Kennedy sign; imaging confirms tendon changes. TrP taut bands impose sustained enthesopathic tension \u2014 coexistence is common and causally related.", "action": "Treat supraspinatus TrPs first and reassess tendon findings after inactivation. Both conditions require treatment when identified together."}, {"condition": "Thoracic zygapophyseal (facet) joint pain", "confidence": "uncommon", "mimics": "Deep upper thoracic paraspinal pain in the same zone as multifidi TrP referral", "distinguishing_feature": "Pain reproduced by passive PA intersegmental pressures over the facet joints; hard end-feel on accessory movement testing. Multifidi TrPs and facet dysfunction coexist at the same segment and perpetuate each other \u2014 soft end-feel suggests TrP predominance.", "action": "Segmental accessory movement testing. Treat multifidi TrPs first \u2014 articular dysfunction often resolves with TrP inactivation. Mobilise the segment if hard end-feel persists."}, {"condition": "Herpes zoster \u2014 pre-eruptive and post-herpetic", "confidence": "uncommon", "mimics": "Unilateral burning upper thoracic pain before the rash appears \u2014 indistinguishable from rhomboid, multifidi, or intercostal TrP patterns at onset", "distinguishing_feature": "Dermatomal distribution; allodynia (light touch painful in a band); vesicles appear 1\u20134 days after pain onset. Age > 50 or immunocompromised increases suspicion. Post-herpetic: TrP pain coexists with neurogenic shooting pain.", "action": "Examine the skin carefully at every visit for unilateral burning thoracic pain. If vesicles present, refer urgently \u2014 antiviral window is 72 hours from rash onset."}, {"condition": "First rib dysfunction", "confidence": "rare", "mimics": "Upper thoracic and arm symptoms closely associated with scaleni TrP presentation \u2014 scaleni attach to the first rib and their taut bands directly elevate it", "distinguishing_feature": "First rib elevated and tender on posterior superior palpation; restricted first rib caudal glide on accessory movement testing. Scaleni TrPs are almost universally present concurrently.", "action": "First rib mobilisation in conjunction with scaleni TrP inactivation \u2014 the two conditions are mechanically linked and must be treated together."}, {"condition": "Winged scapula / serratus anterior weakness", "confidence": "rare", "mimics": "Medial scapular border pain and upper thoracic aching overlapping with rhomboid TrP presentation", "distinguishing_feature": "Scapular winging visible on push-up against wall or forward arm elevation; serratus anterior weakness on manual muscle testing. Rhomboid TrPs develop secondarily to serratus anterior inhibition \u2014 the rhomboids are overloaded trying to retract a scapula that serratus cannot stabilise.", "action": "Serratus anterior rehabilitation is the primary treatment. Treat rhomboid TrPs concurrently but address serratus anterior as the underlying driver \u2014 rhomboid TrPs will recur without it."}, {"condition": "Glenohumeral osteoarthritis", "confidence": "uncommon", "mimics": "Diffuse shoulder girdle and upper thoracic pain, particularly in older patients with supraspinatus and biceps TrP patterns", "distinguishing_feature": "Capsular pattern restriction (external rotation > abduction > internal rotation); crepitus on movement; radiological changes. Arthritis does not produce spot-tender taut bands. TrPs coexist and are independently treatable.", "action": "Shoulder radiograph. Inactivate TrPs concurrently with joint management \u2014 TrP treatment can substantially reduce pain even in the presence of established arthritis."}, {"condition": "Fibromyalgia", "confidence": "uncommon", "mimics": "Widespread upper back and shoulder girdle tenderness overlapping with all muscles in this algorithm simultaneously", "distinguishing_feature": "Widespread pain \u2265 3 months across multiple body regions; diffuse tenderness without specific TrP referral patterns; fatigue and non-restorative sleep. TrPs produce specific referred pain patterns and are a treatable component of fibromyalgia.", "action": "Systematic TrP examination alongside fibromyalgia management. Treating active TrPs reduces the overall pain burden independently \u2014 do not withhold TrP treatment because fibromyalgia is also present."}, {"condition": "Residual pain after spinal manipulation or injection", "confidence": "atypical", "mimics": "Persistent upper thoracic pain after a spinal procedure \u2014 may be re-attributed to the joint when myofascial TrPs are the active pain source", "distinguishing_feature": "After a spinal intervention, pain persisting at a lower level or shifting in character indicates TrPs masked by dominant segmental pain are now the primary source. The TrPs were pre-existing \u2014 not created by the procedure.", "action": "Re-examine muscles systematically after any spinal intervention. Most commonly harbouring residual TrPs: trapezius (upper and mid), levator scapulae, rhomboids, multifidi."}];

// ── RED FLAGS ──────────────────────────────────────────────────────
function buildRedFlags(){
  function makeFlags(arr,id,isUrgent){
    document.getElementById(id).innerHTML=arr.map(rf=>
      '<div class="rf-item">'+
      '<label class="rf-check-wrap" for="'+rf.id+'">'+
      '<input type="checkbox" class="rf-cb" id="'+rf.id+'">'+
      '</label>'+
      '<div class="rf-body">'+
      '<div class="rf-label">'+rf.label+'</div>'+
      '<div class="rf-question">'+rf.question+'</div>'+
      '</div></div>'
    ).join('');
  }
  makeFlags(RF_EMERGENCY,'rf-emergency-items',false);
  makeFlags(RF_URGENT,'rf-urgent-items',true);
  document.getElementById('btn-affirm').addEventListener('click',()=>{
    document.getElementById('btn-affirm').style.display='none';
    document.getElementById('affirmed-badge').classList.add('show');
    const mg=document.getElementById('main-grid');
    mg.classList.add('visible');
    posteriors=getUpdatedPriors();advance();renderLearnPanel();
    setTimeout(()=>mg.scrollIntoView({behavior:'smooth',block:'start'}),80);
  });
}

// ── BROAD DIFFERENTIAL ─────────────────────────────────────────────
function buildBroadDiff(){
  document.getElementById('broad-grid').innerHTML=BROAD_DIFF.map(d=>
    '<div class="dt-diff-item">'+
    '<div class="dt-diff-confidence '+d.confidence+'">'+d.confidence+'</div>'+
    '<div class="dt-diff-name">'+d.condition+'</div>'+
    '<div class="dt-diff-mimics">'+d.mimics+'</div>'+
    '<div class="dt-diff-detail">'+
    '<div class="dt-diff-distinguisher"><strong>Distinguishing features</strong>'+d.distinguishing_feature+'</div>'+
    '<div class="dt-diff-action"><strong>Action</strong>'+d.action+'</div>'+
    '</div></div>'
  ).join('');
  // click to expand
  document.getElementById('broad-grid').addEventListener('click',e=>{
    const item=e.target.closest('.dt-diff-item');
    if(item) item.classList.toggle('open');
  });
  // toggle visibility
  document.getElementById('broad-toggle-btn').addEventListener('click',()=>{
    const grid=document.getElementById('broad-grid');
    const hidden=grid.classList.toggle('hidden');
    document.getElementById('broad-toggle-btn').textContent=hidden?'Show':'Hide';
  });
}

// ── LEARNING ───────────────────────────────────────────────────────
function loadCounts(){try{const r=localStorage.getItem(LS_KEY);if(r)return JSON.parse(r);}catch(e){}const c={};MUSCLE_IDS.forEach(m=>c[m]=0);return c;}
function saveCounts(c){try{localStorage.setItem(LS_KEY,JSON.stringify(c));}catch(e){}}
function incrementCount(mid){const c=loadCounts();c[mid]=(c[mid]||0)+1;saveCounts(c);return c;}
function getUpdatedPriors(){const counts=loadCounts(),p={};MUSCLE_IDS.forEach(m=>p[m]=SCHEMA.muscles[m].prior+(counts[m]||0)*0.02);return normalise(p);}

// ── STATE & MATH ───────────────────────────────────────────────────
let answers={},posteriors={},trail=[],pairwiseDone=new Set(),queueIdx=0,earlyDone=false;
function normalise(p){let t=0;MUSCLE_IDS.forEach(m=>t+=p[m]);const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]/t);return o;}
function applyLR(p,lr){const o={};MUSCLE_IDS.forEach(m=>o[m]=p[m]*(lr[m]||1.0));return normalise(o);}
function ranked(){return MUSCLE_IDS.map(m=>({id:m,p:posteriors[m],label:SCHEMA.muscles[m].label,subtitle:SCHEMA.muscles[m].subtitle||'',page:SCHEMA.muscles[m].page,note:SCHEMA.muscles[m].key_trp_note||null})).sort((a,b)=>b.p-a.p);}
function shouldEarlyExit(r){return r[0].p>=T.early_exit_posterior&&(r[0].p-r[1].p)>=T.early_exit_gap;}
function getPairwise(r){if((r[0].p-r[1].p)>=T.pairwise_trigger)return null;const s=new Set([r[0].id,r[1].id]);for(const pw of SCHEMA.pairwise)if(!pairwiseDone.has(pw.id)&&pw.pair.every(x=>s.has(x)))return pw;return null;}
function condMet(q){if(!q.condition)return true;return Object.entries(q.condition).every(([k,v])=>answers[k]===v);}
function nextQ(){for(let i=queueIdx;i<SCHEMA.questions.length;i++){const q=SCHEMA.questions[i];if(answers[q.id]!==undefined)continue;if(!condMet(q))continue;queueIdx=i;return q;}return null;}
function buildTreatmentOrder(r){const active=r.filter(m=>m.p>=0.05).map(m=>m.id);if(!active.length)return[];const steps=[],placed=new Set(),eL=DAG.edge_type_labels;active.forEach(mid=>{const out=DAG.edges.filter(e=>e.from===mid&&e.type==='key_satellite'&&active.includes(e.to));if(out.length&&!placed.has(mid)){const m=r.find(x=>x.id===mid);steps.push({label:m.label,page:m.page,edgeType:'key_satellite',edgeLabel:eL.key_satellite,note:out.map(e=>e.label).join('; ')});placed.add(mid);}});active.filter(m=>!placed.has(m)).forEach(mid=>{const m=r.find(x=>x.id===mid);const inE=DAG.edges.find(e=>e.to===mid&&placed.has(e.from));const wE=DAG.edges.find(e=>e.type==='antagonist_risk'&&((e.from===mid&&active.includes(e.to))||(e.to===mid&&active.includes(e.from))));const et=wE?'antagonist_risk':(inE?inE.type:'functional_unit');steps.push({label:m.label,page:m.page,edgeType:et,edgeLabel:eL[et]||'Treat in session',note:wE?wE.label:(inE?inE.label:'')});placed.add(mid);});return steps;}

// ── RENDER ─────────────────────────────────────────────────────────
function renderDiff(){const r=ranked();document.getElementById('diff-rows').innerHTML=r.map((m,i)=>{const rank=i+1,w=(m.p*100).toFixed(1),bw=Math.max(0.5,m.p*100).toFixed(1),dim=rank>6?' dimmed':'',rc=rank<=3?' r'+rank:'';return'<div class="dt-diff-bar-row'+rc+dim+'"><div class="dt-diff-bar-rank">'+rank+'</div><div class="dt-diff-bar-name">'+m.label+'</div><div class="dt-diff-bar-track"><div class="dt-diff-bar-fill" style="width:'+bw+'%"></div></div><div class="dt-diff-bar-weight">'+w+'</div></div>';}).join('');}
function renderTrail(){const el=document.getElementById('trail-panel');if(!trail.length){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('trail-counter').textContent=trail.length+' feature'+(trail.length>1?'s':'')+' recorded';document.getElementById('trail-items').innerHTML=trail.map(t=>'<div class="dt-trail-item"><div class="dt-trail-q">'+t.q+'</div><div class="dt-trail-a">&#8627; '+t.a+'</div></div>').join('');}
function renderCounter(){const tot=SCHEMA.questions.length,ans=Object.keys(answers).length;document.getElementById('q-counter').textContent=ans===0?'Prior weights':ans+'\u202f/\u202f'+tot+' features';}
function renderLearnPanel(){const counts=loadCounts(),total=Object.values(counts).reduce((a,b)=>a+b,0);const el=document.getElementById('learn-panel');if(!total){el.classList.remove('visible');return;}el.classList.add('visible');document.getElementById('learn-total').textContent=total+' confirmation'+(total>1?'s':'');document.getElementById('learn-rows').innerHTML=MUSCLE_IDS.filter(m=>counts[m]>0).sort((a,b)=>counts[b]-counts[a]).map(m=>'<div class="learn-row"><div class="learn-muscle">'+SCHEMA.muscles[m].label+'</div><div class="learn-count">'+counts[m]+'&times;</div></div>').join('');}
function renderQuestion(q,isPW){const qText=q.text||q.question;const label=isPW?'<div class="dt-card-label pairwise">&#9889; Tiebreaker</div>':'<div class="dt-card-label">Clinical Feature '+(trail.length+1)+'</div>';const sub=q.sublabel?'<div class="dt-rationale">'+q.sublabel+'</div>':'';const btns='<div class="dt-answers"><button class="dt-answer dt-answer-yes" data-qid="'+(isPW?'__pw__'+q.id:q.id)+'" data-aid="yes" data-qlabel="'+encodeURIComponent(qText)+'" data-alabel="'+encodeURIComponent('Yes')+'">Yes</button><button class="dt-answer dt-answer-no" data-qid="'+(isPW?'__pw__'+q.id:q.id)+'" data-aid="no" data-qlabel="'+encodeURIComponent(qText)+'" data-alabel="'+encodeURIComponent('No')+'">No</button></div>';document.getElementById('question-area').innerHTML='<div class="dt-card"><div class="dt-card-head">'+label+'</div><div class="dt-question">'+qText+'</div>'+sub+btns+'</div>';}

// Fix: answer buttons need correct aid from schema
function renderQuestionFull(q,isPW){
  const qText=q.text||q.question;
  const label=isPW?'<div class="dt-card-label pairwise">&#9889; Tiebreaker</div>':'<div class="dt-card-label">Clinical Feature '+(trail.length+1)+'</div>';
  const sub=q.sublabel?'<div class="dt-rationale" style="font-size:.78rem;color:#57534e;font-style:italic;margin-bottom:.55rem;line-height:1.5">'+q.sublabel+'</div>':'';
  const btns=q.answers.map(a=>{
    const asub=a.sublabel?'<div style="font-size:.72rem;color:#57534e;font-weight:400;margin-top:2px">'+a.sublabel+'</div>':'';
    const cls=q.answers.length===2
      ? (q.answers.indexOf(a)===0?'dt-answer dt-answer-yes':'dt-answer dt-answer-no')
      : 'dt-answer dt-answer-yes';
    return'<button class="'+cls+'" style="'+(q.answers.length>2?'flex:none;flex-direction:column;align-items:flex-start;':'')+'" data-qid="'+(isPW?'__pw__'+q.id:q.id)+'" data-aid="'+a.id+'" data-qlabel="'+encodeURIComponent(qText)+'" data-alabel="'+encodeURIComponent(a.label)+'">'+a.label+asub+'</button>';
  }).join('');
  const ansWrap=q.answers.length>2
    ?'<div style="display:flex;flex-direction:column;gap:.5rem;margin-top:1rem">'+btns+'</div>'
    :'<div class="dt-answers">'+btns+'</div>';
  document.getElementById('question-area').innerHTML='<div class="dt-card"><div class="dt-card-head">'+label+'</div><div class="dt-question">'+qText+'</div>'+sub+ansWrap+'</div>';
}

function renderEarlyExit(r){earlyDone=true;const top=r[0];document.getElementById('question-area').innerHTML='<div class="dt-card"><div class="dt-early-badge">Confident result available</div><div class="dt-early-muscle">'+top.label+'</div><div class="dt-early-sub">'+top.subtitle+'<br>Relative weight '+(top.p*100).toFixed(0)+' &mdash; accept to view result, or continue refining.</div><div class="dt-early-btns"><button class="dt-btn-primary" id="btn-accept">Accept result</button><button class="dt-btn-secondary" id="btn-continue">Continue refining</button></div></div>';document.getElementById('btn-accept').addEventListener('click',showResult);document.getElementById('btn-continue').addEventListener('click',()=>{earlyDone=true;advance();});}

function buildAddConfirm(r){const rem=r.slice(3);if(!rem.length)return'';const opts=rem.map(m=>'<option value="'+m.id+'">'+m.label+' (rank '+(r.indexOf(m)+1)+', weight '+(m.p*100).toFixed(1)+')</option>').join('');return'<div class="dt-add-confirm"><div class="dt-add-confirm-label">Also confirm a muscle not in the top three</div><div class="dt-add-confirm-row"><select class="dt-add-select" id="add-select"><option value="">— select muscle —</option>'+opts+'</select><button class="dt-btn-add-confirm" id="btn-add-confirm-ok">Confirm</button></div><div class="dt-add-confirm-done" id="add-confirm-done">&#10003; Confirmed and model updated</div></div>';}

function showResult(){
  const r=ranked();
  const ranks=['Most likely','2nd','3rd'],rk=['rk1','rk2','rk3'],card_cls=['dt-card-result','dt-card','dt-card'];
  const cards=r.slice(0,3).map((m,i)=>{
    const w=(m.p*100).toFixed(1);
    const note=m.note?'<div class="dt-key-note"><div class="dt-key-note-label">Key TrP relationship</div>'+m.note+'</div>':'';
    return'<div class="dt-card '+card_cls[i]+'" style="margin-bottom:10px">'+
      '<div class="dt-result-rank '+rk[i]+'">'+ranks[i]+'</div>'+
      '<div class="dt-result-name">'+m.label+'</div>'+
      '<div class="dt-result-sub">'+m.subtitle+'</div>'+
      '<div class="dt-result-weight">Relative weight: '+w+'</div>'+
      '<div class="dt-result-actions">'+
      '<a class="dt-wiki-link" href="https://painwiki.com/wiki/index.php?title='+m.page+'" target="_blank" rel="noopener">'+m.label+' on PainWiki</a>'+
      '<button class="dt-confirm-btn" data-muscle="'+m.id+'" data-label="'+encodeURIComponent(m.label)+'">Confirm this muscle</button>'+
      '</div>'+note+'</div>';
  }).join('');
  const addSec=buildAddConfirm(r);
  document.getElementById('question-area').innerHTML=
    '<div style="font-family:\'DM Mono\',monospace;font-size:.65rem;letter-spacing:.12em;text-transform:uppercase;color:#a8a29e;margin-bottom:8px">Differential &mdash; final weights</div>'+
    cards+
    (addSec?'<div class="dt-card" style="margin-top:4px"><div style="font-family:\'DM Mono\',monospace;font-size:.65rem;letter-spacing:.1em;text-transform:uppercase;color:#a8a29e;margin-bottom:10px">Additional Confirmation</div>'+addSec+'</div>':'');

  const addBtn=document.getElementById('btn-add-confirm-ok');
  if(addBtn){addBtn.addEventListener('click',()=>{const sel=document.getElementById('add-select'),mid=sel.value;if(!mid)return;incrementCount(mid);addBtn.disabled=true;sel.disabled=true;document.getElementById('add-confirm-done').style.display='block';renderLearnPanel();});}

  const steps=buildTreatmentOrder(r);
  if(steps.length){
    const tp=document.getElementById('treat-panel');tp.classList.add('visible');
    document.getElementById('treat-items').innerHTML=steps.map((s,i)=>{const isW=s.edgeType==='antagonist_risk';return'<div class="dt-treat-item"><div class="dt-treat-step">'+(i+1)+'</div><div><div class="dt-treat-muscle">'+s.label+'</div><div class="dt-treat-edge'+(isW?' warn':'')+'">'+s.edgeLabel+'</div>'+(s.note&&!isW?'<div class="dt-treat-edge" style="margin-top:2px;font-size:10px;color:#a8a29e">'+s.note+'</div>':'')+'</div></div>';}).join('');
  }
  renderLearnPanel();
}

function advance(){renderDiff();renderTrail();renderCounter();const r=ranked(),pw=getPairwise(r);if(pw){pairwiseDone.add(pw.id);renderQuestionFull(pw,true);return;}if(!earlyDone&&shouldEarlyExit(r)){renderEarlyExit(r);return;}const q=nextQ();if(q){queueIdx++;renderQuestionFull(q,false);return;}showResult();}

document.addEventListener('click',e=>{
  const ab=e.target.closest('.dt-answer,.answer-btn');
  if(ab&&ab.dataset.qid){
    const qid=ab.dataset.qid,aid=ab.dataset.aid,qLabel=decodeURIComponent(ab.dataset.qlabel),aLabel=decodeURIComponent(ab.dataset.alabel);
    if(qid.startsWith('__pw__')){const pw=SCHEMA.pairwise.find(x=>x.id===qid.replace('__pw__',''));const a=pw.answers.find(x=>x.id===aid);posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});earlyDone=false;}
    else{const q=SCHEMA.questions.find(x=>x.id===qid),a=q.answers.find(x=>x.id===aid);answers[qid]=aid;posteriors=applyLR(posteriors,a.lr);trail.push({q:qLabel,a:aLabel});queueIdx++;}
    advance();return;
  }
  const cb=e.target.closest('.dt-confirm-btn');
  if(cb&&!cb.classList.contains('confirmed')){const mid=cb.dataset.muscle,label=decodeURIComponent(cb.dataset.label);incrementCount(mid);cb.classList.add('confirmed');cb.textContent='\u2713\u2713 '+label+' confirmed';renderLearnPanel();return;}
});

/* reset and toggle listeners wired in bootScoringInterface() below */

buildRedFlags();
buildBroadDiff();

  /* ── Boot: inject HTML then wire everything up ── */
  function bootScoringInterface( hostEl, SCHEMA ) {
    // Inject the full interface into the host element
    hostEl.innerHTML = INTERFACE_HTML;

    // Re-initialise all state with loaded schema
    var MUSCLE_IDS_LOCAL = Object.keys( SCHEMA.muscles );
    // Override module-level vars with schema values
    // (the functions reference these via closure)
    posteriors = (function() {
      var counts = loadCounts();
      var p = {};
      MUSCLE_IDS_LOCAL.forEach( function(m) {
        p[m] = SCHEMA.muscles[m].prior + (counts[m] || 0) * 0.02;
      });
      var total = 0;
      MUSCLE_IDS_LOCAL.forEach(function(m){ total += p[m]; });
      MUSCLE_IDS_LOCAL.forEach(function(m){ p[m] /= total; });
      return p;
    })();

    buildRedFlags();
    buildBroadDiff();
    advance();
    renderLearnPanel();

    // Wire reset and toggle buttons
    var resetBtn = document.getElementById('reset-btn');
    var learnBtn = document.getElementById('learning-toggle-btn');

    if ( resetBtn ) {
      resetBtn.addEventListener( 'click', function() {
        answers = {}; trail = [];
        pairwiseDone = new Set();
        queueIdx = 0; earlyDone = false;
        posteriors = getUpdatedPriors();
        var tp  = document.getElementById('trail-panel');
        var tp2 = document.getElementById('treat-panel');
        if ( tp  ) tp.classList.remove('visible');
        if ( tp2 ) tp2.classList.remove('visible');
        advance();
      });
    }
    if ( learnBtn ) {
      learnBtn.addEventListener( 'click', function() {
        var h = document.body.classList.toggle('hide-learning');
        learnBtn.textContent = 'Learning: ' + ( h ? 'OFF' : 'ON' );
      });
    }
  }

  /* ── Load schema from wiki data page ── */
  function bootHost( hostEl ) {
    var treePage = hostEl.getAttribute( 'data-tree-page' );
    if ( !treePage ) return;

    hostEl.innerHTML = '<div style="padding:1.5em;font-family:DM Mono,monospace;' +
      'font-size:0.8em;color:#a8a29e;text-align:center">Loading scoring model…</div>';

    var api = new mw.Api();
    api.get({
      action:   'query',
      titles:   treePage,
      prop:     'revisions',
      rvprop:   'content',
      rvslots:  'main',
      format:   'json'
    }).done( function( data ) {
      var pages  = data.query.pages;
      var pageId = Object.keys( pages )[0];
      if ( pageId === '-1' ) {
        hostEl.innerHTML = '<div style="color:#b91c1c;padding:1em;font-family:monospace">' +
          'Scoring model not found: ' + treePage + '</div>';
        return;
      }
      var raw = pages[ pageId ].revisions[0].slots.main['*'];
      var schema;
      try { schema = JSON.parse( raw ); }
      catch ( e ) {
        hostEl.innerHTML = '<div style="color:#b91c1c;padding:1em;font-family:monospace">' +
          'Invalid JSON in ' + treePage + ': ' + e.message + '</div>';
        return;
      }
      bootScoringInterface( hostEl, schema );
    }).fail( function() {
      hostEl.innerHTML = '<div style="color:#b91c1c;padding:1em;font-family:monospace">' +
        'Failed to load scoring model: ' + treePage + '</div>';
    });
  }

  /* ── MediaWiki entry point ── */
  function init() {
    document.querySelectorAll( '.scoring-tree-host' ).forEach( function( el ) {
      bootHost( el );
    });
  }

  if ( typeof mw !== 'undefined' ) {
    mw.hook( 'wikipage.content' ).add( init );
  }

}() );