Chapter IV paragraph applicability
This document explains how to decide whether a Chapter IV paragraph could apply to a given patient/doctor for a given prescription. Applicability is not stored on the paragraph — it is derived from the tree of verses and their individual restrictions.
The companion C# API lives in samparse/Applicability.cs (PatientContext, DoctorContext, ChapterIvIndex, ApplicabilityChecker).
1. Where restrictions live
Every restriction is attached to a VerseDataType (the versioned payload of a VerseFullDataType), never to the paragraph itself. The relevant fields:
| Field | Meaning |
|---|---|
SexRestricted (+ Specified) |
F or M; verse only applies to that sex |
MinimumAgeAuthorized / MaximumAgeAuthorized (+ Unit + Specified) |
Age bounds expressed in D / W / M / Y. Min is inclusive (≥ N); max is strict (< N+1, i.e. patient has not yet reached the next anniversary). Compared via calendar arithmetic against the patient's DateOfBirth and the reference date — values are integral in the reference data despite the schema's Decimal3d1Type |
PurchasingAdvisorQualList |
FK (string key) to a QualificationListFullDataType listing authorised prescriber codes |
RequestType (+ Specified) |
N = new prescription only, P = renewal only, absent = either |
VerseType (+ Specified) |
Currently only E = exclusion verse (legislation phrased as "X is not authorised") |
AndClauseNum (+ Specified) |
Couples this verse to other verses sharing the same number across non-contiguous branches |
From / To |
Validity window (from DataPeriodType) — pick the one data row active for the reference date |
A paragraph like "only for females" is therefore an emergent property of the combination of restrictions on verses that must be satisfied.
2. Verse tree
Within a paragraph, verses form a tree built from:
VerseSeq— id within the paragraph (onVerseFullDataType, inherited fromVerseKeyType)VerseSeqParent— parent'sVerseSeq(onVerseDataType)VerseLevel— depth hint (redundant with the parent chain)
VerseSeqParent = 0 typically points at a synthetic root; any verse whose parent is not itself present (after date filtering) is treated as a root in the walk.
3. Verse roles — three kinds
| Role | Signal | Effect on applicability |
|---|---|---|
| Narrative / header | CheckBoxInd == false |
Descriptive text. Own restrictions still apply to it and anyone "entering" its subtree. Not a choice point. |
| Mandatory choice group | A parent verse has MinCheckNum > 0, and its children with CheckBoxInd == true are the alternatives |
At least MinCheckNum checkbox children must be satisfiable for the patient/doctor. Fewer ⇒ paragraph not applicable. |
| Optional choice | Parent has MinCheckNum == 0 |
Children don't constrain the paragraph. |
From the XSD:
Every verse with a checkbox has to have a parent verse with a granted
MIN_CHECK_NUM. And vice versa, every verse withMIN_CHECK_NUM > 0has to have minimum two children verses withCHECK_BOX_IND = true.
Frequency in the reference export (April 2026):
| Field | Occurrences |
|---|---|
<CheckBoxInd>true</CheckBoxInd> |
19 648 |
<MinCheckNum> |
9 841 |
<RequestType>N</RequestType> |
40 816 |
<RequestType>P</RequestType> |
10 661 |
<VerseType>E</VerseType> |
426 |
<AndClauseNum> |
8 |
4. Qualification lists (doctor side)
VerseDataType.PurchasingAdvisorQualListis a short string key such as"962".- It resolves against
ExportChapterIvType.QualificationList→QualificationListFullDataTypewhoseQualificationListattribute matches. - That list contains a collection of
ProfessionalAuthorisationentries. Each entry'sDatais versioned (From/To) and carries aProfessionalCv(the professional code, e.g."668"). - A doctor with any
ProfessionalCvin the list's active entries for the reference date qualifies. QualificationListDataType.ExclusiveInddefines AND/OR semantics in the schema ("1"= OR,"2"= AND) but no AND lists exist in the reference export. Treated as OR; a real AND list would need a logic change.
5. RequestType — new vs. renewal
RequestType marks which procedural branch a verse describes — the new-request path (N) or the renewal path (P). Crucially this is a scenario filter, not a constraint to satisfy: a verse tagged for the off-scenario isn't "violated" by a different prescription, it simply describes a different path through the paragraph that doesn't apply.
This matters because paragraphs frequently have two narrative siblings under the same parent — one tagged N describing the entry procedure, one tagged P describing the renewal procedure (e.g. paragraph 8410000's c/d clauses). AND-combining them as constraints would make every concrete prescription type produce NotApplicable, since one of the two will always fail.
Behaviour:
- A verse with
RequestType = Ndescribes the new-request branch. - A verse with
RequestType = Pdescribes the renewal branch. - A verse without
RequestTypeapplies to both.
CouldApplyTo handles this by pruning the verse tree before evaluation: when isRenewal is non-null, any verse whose RequestType disagrees is removed along with its entire subtree (descendants are scoped to the same branch). The remaining tree is evaluated with the usual narrative-AND / checkbox-MinCheck logic. When isRenewal is null no pruning happens and CheckRequestType yields Unknown, propagating up.
VerseCompatible (the per-verse predicate used by the viewer to highlight individual rows) keeps the literal "tag mismatch ⇒ NotApplicable" semantics so off-scenario rows can be visually flagged. The pruning lives only at the paragraph-rollup level.
Distribution in the reference export (2026-04-29)
| Pattern | Paragraphs |
|---|---|
Both N and P verses |
473 |
N-only (no renewal procedure) |
500 |
P-only (renewal-tagged only) |
24 |
Neither (no RequestType tags) |
809 |
The 24 "P-only" paragraphs are an artefact of incomplete tagging — sampling shows their entry procedure exists in untagged narrative verses (e.g. 2380000), with only the renewal-specific clause carrying RequestType=P. Pruning is correct in either case: untagged verses survive both scenarios, scenario-tagged verses survive only their own.
6. AndClauseNum — cross-branch coupling
Verses sharing the same AndClauseNum value must be co-selectable: the legislation expects the prescriber to engage with all of them together, even when they live in non-contiguous branches of the tree.
ApplicabilityChecker models this by precomputing, for each AndClauseNum group with two or more members, the combined per-verse compatibility (worst-of) and folding that result back into every member's effective compat. Effect: if any single member of an AndClauseNum group is NotApplicable, all members become NotApplicable, and the constraint propagates naturally through the tree walk.
The reference export has only 8 occurrences (a single group, value 220, on paragraph 1880100), so this is rarely load-bearing in practice — but the implementation is in place for future exports.
7. VerseType = E — exclusion verses
Per the XSD, VerseType = E marks a verse whose legal text is phrased as a prohibition ("simultaneous reimbursement of X is not authorised") rather than a condition. The reference export has 426 such verses.
The applicability check treats them identically to other verses: an exclusion verse with RequestType=N and SexRestricted=F still contributes a NotApplicable for a male renewal patient via the normal predicates. In practice the sampled exclusion verses describe medicine-level interactions and don't carry sex/age/qual restrictions, so they're functionally informational — the viewer surfaces them with a distinct marker so a reader doesn't mistake "thou shalt not" for "thou shalt".
If a future export contains exclusion verses where the natural reading is inverted (i.e. the verse's restrictions describe a prohibited combination rather than a required one), the predicates would need conditional logic. No such verses are observed today.
8. The algorithm
Top-level pseudocode (see samparse/Applicability.cs for the real thing):
CouldApplyTo(paragraph, patient, doctor, isRenewal, date, index):
activeByDate = pick the VerseDataType per verse active on `date`
active = top-down BFS from roots, dropping any verse whose RequestType
disagrees with isRenewal (and its whole subtree). When isRenewal
is null, no scenario-pruning happens.
childrenBy = group by VerseSeqParent (over `active`)
rootSeqs = verses whose parent isn't in `active`
# Precompute AndClauseNum group adjustments
for each group of verses sharing AndClauseNum (size > 1):
combined = Worst over VerseCompatible(member) for each member
record andAdj[memberSeq] = combined
return Worst over EvaluateChoice(r) for r in rootSeqs
EvaluateChoice(verseSeq):
self = VerseCompatible(verseData, patient, doctor, isRenewal, date, index)
if andAdj has verseSeq: self = Worst(self, andAdj[verseSeq])
if self == NotApplicable: return NotApplicable
return Worst(self, EvaluateSubtree(verseSeq))
EvaluateSubtree(verseSeq):
narrativeKids, checkboxKids = split children by CheckBoxInd
narrative = Worst over EvaluateChoice(k) for k in narrativeKids
if narrative == NotApplicable: return NotApplicable
mandatory = Applicable
if MinCheckNum > 0 and checkboxKids not empty:
results = EvaluateChoice(k) for k in checkboxKids
definitely = count Applicable
possibly = count != NotApplicable
if possibly < MinCheckNum: return NotApplicable
if definitely < MinCheckNum: mandatory = Unknown
return Worst(narrative, mandatory)
VerseCompatible runs four independent predicates (sex / age / qualification / request type) and combines with Worst. Each predicate returns Applicable when the restriction is absent or satisfied, NotApplicable when it is violated, and Unknown when the restriction exists but the relevant patient/doctor/prescription datum is missing.
Worst(a, b) = NotApplicable if either is NotApplicable, else Unknown if either is Unknown, else Applicable. This makes the three-valued logic monotonic.
9. Interpretation & caveats
Applicablemeans "not provably unreachable", not "this patient definitely qualifies". Natural-language criteria in the verse text (diagnosis, prior treatments, etc.) aren't evaluable here.- Non-checkbox verses are treated as mandatory narrative. If a narrative verse carries e.g.
SexRestricted = Fand the patient is male, the whole paragraph is reportedNotApplicable. This matches the working assumption that narrative headers set the scope for their subtree. If you find real paragraphs where this is too strict, relax it. - Unknown inputs propagate to
Unknown. Missing patient sex + a sex-restricted verse ⇒ no committed verdict. Same for age, doctor qualifications, and request type. AndClauseNuminterpretation is conservative (§6): worst-of within a group propagates to every member. This is a reasonable reading of "must be co-selectable" but is not exhaustively validated against the legislation, since only one group exists in the reference data.VerseType = Eexclusion verses are flagged in the UI but treated identically by the predicates (§7). Logic only needs to change if a future export carries exclusion verses with inverted-restriction semantics.- Time-versioned constraints.
VerseDataTypeandQualificationListDataTypeboth carryFrom/To; the reference date filters both. - Qualification list semantics. OR, per §4. Switch to AND if you encounter a real list with
ExclusiveInd = "2". - Paragraph-level
Exclusion(medicine-level) is out of scope. The XSD allows paragraphs to carry exclusions pointing at ATC codes, VMP clusters, AMPP IDs, or prior paragraphs — this models whether a medicine falls under the paragraph, not whether a patient/doctor does. Zero entries populated in the reference export, and answering "does this medicine apply" would require an additional input (the medicine being prescribed).
10. Smoke test
Hand-checked against the April 2026 SAM export (CHAPTERIV-1775613877335.xml):
| Paragraph | Input | Result | Why |
|---|---|---|---|
12850000 (osteoporosis) |
anything | Applicable |
No mandatory restrictive choice group at the top level — patient-agnostic at the paragraph level |
13780000 (CLL) |
female, age 10 | NotApplicable |
Age < 18 fails a mandatory age check |
13780000 |
female, age 70, CV "001" |
NotApplicable |
Doctor CV not in qualification list 962 |
13780000 |
female, age 70, CV "668" |
Applicable |
CV "668" is in qualification list 962 |
13780000 |
female, age 70, no doctor | Unknown |
Qualification requirement exists, doctor info missing |
8410000 (heart failure) |
adult male, cardiologist (CV 730), isRenewal=false |
Applicable |
RequestType-pruning drops the renewal-only sibling (verse d); the new-request branch (verse c, qual list 422) holds |
8410000 |
adult male, cardiologist, isRenewal=true |
Applicable |
Same in reverse: new-request sibling pruned, renewal branch (qual list 625) holds |
8410000 |
adult male, cardiologist, isRenewal=null |
Unknown |
Both RequestType-tagged siblings remain and both yield Unknown for an unspecified prescription |