Engine · 6

Prediction

For each of a set of candidate observations, predict projects its effect on the recognition result and ranks the candidates by how much they change it. Where frontier chains backward from one goal, predict projects forward across many.

Information gain

The default mode scores each candidate by information gain — the reduction in the coverage a diagnosis still leaves unexplained (its residual) — after feeding it through evaluation's fixpoint, so an observation that completes a derived anchor counts. --fast skips the fixpoint for a single-hop re-score. Against this corpus and a chart in which the patient has only presented:

// tropes/cystitis.trl — Dysuria carries evidence weight 4 (a likelihood ratio)
// @prevalence 5
rule Cystitis { when: evt $e [&Presents(agent=$p)] $p [=Dysuria*4] then: $p [+Cystitis] }

// tropes/pyelo.trl — Pyuria weighted 9
// @prevalence 5
rule Pyelo { when: evt $e [&Presents(agent=$p)] $p [=Pyuria*9] then: $p [+Pyelo] }
// chart.trl — the patient has presented; nothing distinguishing yet
char pt
scene visit { beat 1 { evt e [&Presents(agent=pt)] } }
$ tropelang predict chart.trl --observe Pyuria,Dysuria --corpus file://trl

== predict (through-derivation) — by info gain ==
  Pyuria    infoGain 0.75    Pyelo     absent → confirmed 1.00
  Dysuria   infoGain 0.75    Cystitis  absent → confirmed 1.00

Each observation confirms its own diagnosis — equal gain here, since each is the sole anchor of a live hypothesis.

The Bayesian posterior

--bayesian swaps coverage-residual for the normalized posterior over the differential and scores each observation by its value of information — how much it sharpens that posterior. It reads the [=Sign*w] weights as likelihood ratios and the @prevalence tier as the prior:

$ tropelang predict chart.trl --observe Pyuria,Dysuria --bayesian --corpus file://trl

== predict (bayesian) — posterior over the differential ==
  Cystitis   0.500
  Pyelo      0.500
-- value of information, by observation --
  Pyuria    VOI 0.368    Pyelo     0.500 → 0.900
  Dysuria   VOI 0.193    Cystitis  0.500 → 0.800
  Nothing   VOI 0.000

With a uniform prior and no distinguishing evidence yet, the differential sits at 0.5 / 0.5. Because Pyuria is weighted 9 against Dysuria's 4, acquiring it sharpens the posterior further, to 0.90, ranking it first. The posterior is P(dx | chart) ∝ prior(tier) · ∏ present-anchor weights, normalized.

Interpreting the output

signed VOI
A negative VOI means the observation would muddy the differential — lift a trailing hypothesis toward the leader — rather than sharpen it. Surfaced so a workup can avoid it.
exclusion
A diagnosis the chart rules out — a missing !-necessary anchor, or a type violation — is dropped from the differential entirely; it isn't a live hypothesis.
unweighted = LR 1
An anchor with no *w is uninformative in Bayesian mode. With no authored weights the posterior is just the normalized prior (correctly: no evidence, no update) — so weight the facets that actually distinguish competing diagnoses.

Two coarse, domain-neutral modeling defaults a consumer can refine: the prior is linear in the (log) prevalence tier (only ratios matter, normalized), and the *w weight is the likelihood ratio. The engine evaluates the corpus's own rules and learns no domain concept. Authoring for reasoning uses the recognition marks plus these weights and @prevalence; the style guide covers where to put them.

Interfaces

CLI
tropelang predict <chart> --observe A,B,C [--corpus <url>…] [--fast | --bayesian] [--json].
WASM
predict(src, observables_json, fast) — info-gain mode; predict_bayesian(src, observables_json) — posterior + VOI. Both return { predictions, posterior, chartParseError }.