Open the whole manual on one page

10. Method

How the engine decides.

The evaluation, step by step

This is what the engine does every time you change a condition, in order. Nothing else happens; there is no hidden step.

  1. Read the evidence. The condition records come from PSI tables and documents, one record per measured value with the quote and the place it came from. Records flagged "insufficient evidence" by the extraction are never counted. (How records are extracted and checked is a separate process, described in the extraction chapters.)
  2. Read your condition. Gravity, fuel phase, diluent and flame type (regimes), and numbers such as oxygen and pressure. A gravity level is turned into a regime and only shown.
  3. Group the tests. The tests of each investigation that share the same regime values form a cluster.
  4. Check the regimes. A regime you set that differs from the cluster's gives "Not comparable". One that is missing or contradictory gives "Insufficient evidence".
  5. Check each number. Is your value inside the range the cluster's tests covered (with a 0.5 % tolerance)?
  6. Check oxygen and pressure together. Is the pair inside the region those tests cover (their convex hull, with the same tolerance)?
  7. Decide. Applicable if all is inside; Extrapolation if something is outside, with its level; Insufficient evidence if the records do not allow the check.
  8. Pick the best cluster for each investigation, attach the reasons and, when not comparable, the blockers.
  9. Count and summarize, and flag when no investigation was tested in your gravity regime.

Every sentence the engine writes comes from a fixed template, filled with numbers rounded for reading. The full-precision numbers stay in the structured result.

## For engineers

evaluate(records, target, options) (engine/src/verdict.ts:523):

  1. validateTarget(target); options minimumStatus: 'extracted' | 'verified' (default 'extracted'). Records come from parseConditionCsv (engine/src/records.ts): fields accession, test_id, variable, value, unit, value_original, unit_original, method, source_file, source_location, quote, status, note; variables are the regime axes, the continuous axes, fuel, material, geometry, flow_configuration, platform and outcome:*; status extracted | verified | insufficient_evidence; values lo..hi are intervals.
  2. canonicalTarget.
  3. buildCorpus (engine/src/evidence.ts:170): normalization (AxisScale), tests, clusters.
  4. judgeCluster (verdict.ts:149) per cluster: regime axes; continuous axes (complete tests = tests with values on every stated axis; axisChecks); plane check; verdict; distance; level; reasons (each Reason { code, informational, axis, message, technicalNote?, citations }).
  5. Investigation verdict: clusters sorted by compareRanked; first is the deciding cluster; blockers when INSUFFICIENT_EVIDENCE or REGIME_BARRIER; extrapolationLevel when EXTRAPOLATION.
  6. Evaluation { engine, target, openRegimeAxes, regimeUntested?, options, normalization, counts, investigations, ranked, unranked }; ranked = APPLICABLE then EXTRAPOLATION clusters, unranked = INSUFFICIENT_EVIDENCE then REGIME_BARRIER clusters.

Engine version 0.2.0 (engine/src/types.ts:301). Tests: engine/test/verdict.test.ts, inputs.test.ts, judge.test.ts.

Sources: engine/src/verdict.ts; engine/src/evidence.ts; engine/src/records.ts

The evidence map

The map behind the chart shows, for every pair of oxygen and pressure on a grid, how many investigations are Applicable and how many Extrapolation, for the regime you selected. A darker cell has more investigations; extrapolation cells are shaded in three tones by how far outside the closest investigation is.

The map is not a separate model. Each cell gives exactly what the engine would answer if you typed that oxygen and pressure: this is tested on hundreds of random cells. The map is fast because the tested regions are computed once and each cell is only a quick check against them.

It only varies oxygen and pressure. If you also set flow, thickness or temperature, those are not part of the map.

## For engineers

evidenceMap(records, regime, { o2: [min, max, n], pressure: [min, max, n] }, options) (engine/src/navigator.ts:419) returns { regime, o2Values, pressureValues, cells[], maxApplicable, maxExtrapolation }; cells are row-major with pressure outer (cells[i * o2Values.length + j]), each { o2, pressure, applicable[], extrapolation[], extrapolationLevel? }.

Implementation: buildCorpus once; per cluster precompute the fixed regime state (barrier / unknown / incomplete / ok), per-axis ranges over the complete tests and the convex hull of the paired points (PreparedCluster); per cell apply the same rules as judgeCluster (per-axis tolerance inRange, hullContains || hullNearBox), the investigation takes its best cluster; extrapolationLevel uses relativeGap and levelFromRelativeGaps. linspace gives n values from min to max inclusive (n = 1 gives min); invalid grids throw RangeError.

Tests (engine/test/evidencemap.test.ts, levels.test.ts): 200+ random cells equal evaluate() on fixtures and on the real records; 60 × 60 on the real records, median of 5 warm runs about 100 ms here (design target 150 ms); the timing assertion is < 1000 ms and skipped when CI is set.

Sources: engine/src/navigator.ts

Units and conversions: every factor

Language models are bad at arithmetic: in a benchmark they put wrong numbers into an oxygen partial pressure. So models never convert units in PYRION; code does, with one table of factors that the engine shares with the validation script.

Pressure goes to kilopascals (kPa): 1 Pa = 0.001 kPa; 1 atm = 101.325 kPa; 1 bar = 100 kPa; 1 mbar = 0.1 kPa; 1 psi = 1 psia = 6.894757 kPa; 1 mmHg = 1 torr = 0.1333224 kPa. Gauge pressure (psig, barg, kPag) is refused, because the engine needs absolute pressure; plain "psi" is taken as absolute and says so.

Oxygen goes to a mole fraction: "34 %", "34 vol%", "34 percent" and "34 por ciento" are 0.34. A bare number above 1 is read as a percent and one up to 1 as a fraction, and the result says which rule it applied; a written unit always wins ("0.34 %" is 0.0034, with a warning).

Flow: 1 m/s = 100 cm/s; 1 mm/s = 0.1 cm/s. Thickness in millimetres: 1 cm = 10 mm; 1 µm = 0.001 mm; 1 in = 25.4 mm; 1 m = 1000 mm. Temperature in kelvin: K = °C + 273.15.

Values that look like a unit slip (for example 200 % oxygen, or a negative pressure) are errors with a plain sentence, not guesses. Values outside a plausible window for a habitat (oxygen 5 to 60 %, pressure 20 to 120 kPa) are accepted but come with a warning. Those windows are PYRION's, not NASA's.

## For engineers

Table in engine/src/quantity.ts (copy of TO_KPA, CONVERSIONS, BOUNDS of validation/run_all.py; a test compares them):

  • TO_KPA (line 88): kpa 1.0, pa 0.001, atm 101.325, bar 100.0, mbar 0.1, psi 6.894757, psia 6.894757, mmhg 0.1333224, torr 0.1333224.
  • TO_FRACTION (line 99): '' and '1' and 'mole fraction' and 'fraction' 1.0; '%', 'percent', '% by vol', '% by volume', 'percent by volume', 'vol%', 'vol %' 0.01. Aliases: 'mol%', 'mol %', 'mole%', 'mole %', 'vol. %' as %; 'per cent', 'mol percent', 'mole percent', 'por ciento', 'porciento' as percent. A trailing "O2", "oxygen", "of oxygen", "de oxígeno" is ignored.
  • TO_CM_S (line 112): cm/s 1.0, mm/s 0.1, m/s 100.0.
  • TO_MM (line 113): mm 1.0, cm 10.0, um and µm and micron(s) 0.001, in 25.4, m 1000.0.
  • TO_K_OFFSET (line 123): k 0, c and °c and degc 273.15 ("degrees C", "Celsius" also).
  • BOUNDS (line 125, inclusive): o2_mole_fraction [0, 1]; pressure [0.1, 2000] kPa; flow_velocity [0, 1000] cm/s; sample_thickness [0, 200] mm; ambient_temperature [100, 4000] K; gravity_level [0, 3] g. Outside: error out_of_range (for oxygen, percent_out_of_range), message "<axis> <typed> (<converted>) is outside the plausible range A to B; this usually means the wrong unit."
  • HABITAT_WINDOWS = { o2_mole_fraction: [0.05, 0.6], pressure: [20, 120] } (line 184): warnings only, labelled "PYRION plausibility window, not a NASA value".
  • KPA_PER_PSI = 6.894757293168361 (engine/src/presets.ts:8) is used to show psia next to kPa in sentences; the table factor 6.894757 differs from it by less than 1e-7 (relative); the table is the authority for conversions.

Decimal comma: 0,165 and 8,2 are decimals (a comma after a leading 0, or followed by 1 or 2 digits, or 4 or more); "101,325" and "1,000" (nonzero integer part and exactly 3 digits) or several commas are refused with "write 101.325 (decimal point) or 101325 (no separator)". Vocabulary: "1 atm", "1 atmosphere", "sea level", "normal air pressure" = 101.325 kPa with a note; "kPa abs", "psia (absolute)" accepted; "psi(g)" refused.

The calculators the chat uses (web/lib/expert/calculators.ts) call the same table.

Sources: validation/run_all.py; engine/src/quantity.ts

Reading a question: code first

When you type a question, code reads it first. A deterministic extractor looks for what it can recognise with certainty: an oxygen figure next to the word oxygen, a pressure with its unit, a gravity as "0.165 g" or a word like "Moon", "Luna", "microgravity", "freefall" or "on the ground". It hands back the words exactly as you wrote them; the unit conversion is done afterwards by the conversion table, never by a model.

The extractor never guesses. If a number cannot be assigned, it is set aside as "left over" and reported. Some examples of what it will not assume:

  • "a 2 mm sheet" is not a thickness (a length may be a width); only "2 mm thick" is.
  • "ppO2 of 21 kPa" is a partial pressure, not the total pressure the engine needs: it is not used, and it says why.
  • Two different pressures in one question are not resolved by picking one; both are reported.
  • A gravity number that disagrees with a gravity word ("lunar base at 1 g") is reported, not resolved.
  • Typos ("oxigen") are not corrected.

What the extractor cannot read is left for the next step, where TARS SI (a language model) may help to understand the question, but never to convert or compute: its numbers must come from code.

## For engineers

extractQuantities(question) (engine/src/extract.ts:83) returns { gravity?, o2?, pressure?, flow_velocity?, sample_thickness?, ambient_temperature?, leftover[], issues[] } (raw substrings of the question), ready for targetFromQuantities (engine/src/quantity.ts:682). Text is folded (lower case, accents removed, same length) for matching only; the raw string is sliced from the original.

Rules:

  • Numbers: NUMBER_RE excludes numbers inside words and codes ("PSI-12", "ppO2", "H2O").
  • Oxygen: a number followed by %, percent, per cent, por ciento, vol%, mol% with an oxygen word (`o2`, `oxygen`, `oxigeno`) within 30 characters; or "oxygen fraction of 0.34" / "fracción molar de oxígeno de 0,34" (the number alone).
  • Pressure: number + kpa|pa|atm|atmosphere(s)|bar|mbar|psia|psig|psid|psi|mmhg|torr|barg|kpag (+ "abs" / "(g)" qualifiers; a gauge unit is kept so the parser refuses it); phrases "sea level pressure", "normal/standard air pressure", "nivel del mar", "presión normal". A number within 25 characters after pp O2, "partial pressure" or "presión parcial" is an issue partial_pressure and goes to leftover.
  • Gravity: number + g/gE/g0 or 1/6 g; keywords as whole words (GRAVITY_KEYWORDS, engine/src/quantity.ts): moon, lunar, luna; mars, martian, marte, marciano; earth, ground, terrestrial, tierra, terrestre, normal gravity; microgravity, microgravedad, freefall, free fall, weightless, orbit, orbital, ISS, ingravidez, caída libre, gravedad cero, zero-g, micro-g, µg. Two classes: issue ambiguous_gravity; number and keyword disagreeing: issue gravity_conflict.
  • Flow, thickness, ambient temperature: number + unit and a context word within 40, 30 and 40 characters (flow|wind|velocity|flujo, thick|espesor|grosor, ambient|room temperature|cabin temperature|ambiente).
  • Several values for one quantity: kept only if all parse and agree within 0.5 %; otherwise issue multiple_values and all go to leftover.

parseQuantity also finds gravity keywords anywhere in a text that does not start with a number (gravityFromKeywords); two classes give ambiguous_gravity. Tests: engine/test/extract.test.ts over the 31 questions and 11 traps of validation/judge_battery/questions.json (English and Spanish).

Sources: engine/src/extract.ts

Coverage is not safety (Method)

What it is. A standing sentence about what coverage means.

What it shows. That covered means inside the region spanned by measured test points (between tests, within the 0.5 % tolerance; not necessarily a test at that exact condition), not that flammability behaves the same there.

How to use it. Read it before quoting any coverage figure.

Example. 0 % covered says no PSI test region reaches there; it does not say a flame is safe or unsafe.

What it does not do. It is not a button and it does not change any number.

The three-part answer: what is known, unknown, and next

TARS answers in three parts, built from the engine's results:

  1. What PSI supports. The investigations that are Applicable for your condition, each with its receipts (the records behind the statement, with DOI). If there are none, the answer says so: "Nothing in PSI applies directly to this condition."
  2. Where the evidence ends, and why. Whether PSI has any test in your gravity regime; which investigations are just outside, outside or far outside the tested data; and which are not comparable for which reason, grouped with counts.
  3. What to do next. At most three of the nearest routes (regime switches labelled), and the "What would close this gap" proposal.

A fingerprint and the list of steps (which engine tools actually ran) close the answer, so it can be re-checked.

The same facts come out in the easy and the advanced wording. Easy is shorter, uses no numbers beyond oxygen %, pressure and gravity, and avoids acronyms; advanced adds numbers with units, levels and counts. Take away the words and the two answers are identical.

## For engineers

threePartAnswer(records, target, { catalog?, mode: 'easy' | 'advanced', minimumStatus? }) (engine/src/answer.ts:87) returns { mode, engine, target, known[], unknown[], next[], fingerprint, steps[] }.

  • known[]: { sentence, citations: [{ accession, acronym?, doi?, recordIds[] }] }; recordIds are accession|test|variable|source file|location from the inside-range and inside-hull reasons of the deciding cluster.
  • unknown[]: kinds regime (regimeUntested first, then barrier blockers by axis), outside (one item per extrapolation level, EXTRAPOLATION_LEVEL_ORDER), insufficient (blockers by code|axis), each with count and accessions.
  • next[]: up to 3 routes (routesToEvidence), then { kind: 'proposal', action: { label: EXPERIMENT_LABEL, gapKind, pointCount, firstPriority? } }.
  • steps: evaluate, then routesToEvidence and experimentsNeeded only when some investigation is not APPLICABLE.
  • fingerprint: sha256Hex(canonicalJson(evaluation)).

Easy-mode rules (tested, engine/test/answer.test.ts): digits only for oxygen %, pressure (kPa/psia) and gravity; no acronym except "NASA"; counts as words ("one", "two", "three", "several"). Facts identical across modes: removing every sentence field makes the two answers deep-equal.

Sources: engine/src/answer.ts

Method page overview

What it is. The page that explains how PYRION decides.

What it shows. Axes, verdicts, ranking, the receipt, checks and the role of a language model.

How to use it. Read the sections in order.

Example. Read "The four verdicts".

What it does not do. It documents the engine; it does not run it.

Two kinds of axes

What it is. A section.

What it shows. Regime axes (barriers) and continuous axes (compared inside hulls).

How to use it. Read it first.

Example. Gravity is a regime axis.

What it does not do. It explains why different regimes are never compared.

The four verdicts

What it is. A section.

What it shows. Applicable, Extrapolation, Insufficient evidence, Not comparable.

How to use it. Read the definitions.

Example. See what makes a verdict insufficient.

What it does not do. It defines words; the cards apply them.

Ranking

What it is. A section.

What it shows. How investigations of the same verdict are ordered by normalized distance: condition similarity on the axes you set, not scientific relevance.

How to use it. Read it.

Example. See why nearest comes first.

What it does not do. Rank is not quality or relevance. Equal distances are ordered by PSI accession number. A regime barrier means not directly applicable; for gravity in particular, microgravity and normal-gravity results could bound intermediate gravity (challenge statement).

The receipt

What it is. A section.

What it shows. What a receipt contains.

How to use it. Read it.

Example. See the file and location.

What it does not do. A receipt is evidence of extraction, not of correctness of the experiment.

What the checks cover and what they do not

What it is. A section.

What it shows. The validation checks and their limits.

How to use it. Read it.

Example. See what the validator cannot catch.

What it does not do. It lists limits honestly.

What an LLM does here

What it is. A section.

What it shows. The language model only translates questions and adds checked context.

How to use it. Read it.

Example. See that verdicts come from code.

What it does not do. It does not describe future features as working.

Back to the envelope

What it is. A link.

What it shows. Goes to the Envelope page.

How to use it. Click it.

Example. Return after reading.

What it does not do. It does not keep a condition.

See the data coverage

What it is. A link.

What it shows. Goes to the Data page.

How to use it. Click it.

Example. Check coverage.

What it does not do. It does not change anything.

Help center

Every page, button and number explained. Loading the topics… Press the ? menu, then Help mode, to point at anything.

Pick a topic on the left, or search.