Typed Westgard QC rule evaluation, sigma mappings, and planning helpers for Python.
Project description
Westgard-Python
westgard-python is a pure-Python package for evaluating Westgard rules, named multirule QC procedures, Westgard Sigma Rules mappings, and reference-backed planning guidance.
The package is built directly from the canonical reference in the master reference. It treats QC semantics as explicit API surface rather than implicit chart logic.
Status
The project is in active early development and targets 0.3.0.
Installation
pip install westgard-python
Optional dataframe helpers:
pip install "westgard-python[pandas]"
API Layers
Top-level API (import westgard):
Low-level rule primitives:
get_rule_definition(rule)evaluate_rule(rule, observations, *, scope, stream_name)
Mid-level current-run preset evaluation:
get_preset_definition(preset)evaluate_multirule(*, context, preset, interpretation_mode=InterpretationMode.MODERN)evaluate_custom_rules(*, context, rule_set)
High-level historical builders and scanners:
observation_from_value(*, analyte, material, run_id, sequence, value, mean, standard_deviation, metadata=None)series_from_rows(rows)evaluate_multirule_sequence(*, series, preset, interpretation_mode=InterpretationMode.MODERN, enabled_scopes=None)scan_multirule_series(*, series, preset, interpretation_mode=InterpretationMode.MODERN, enabled_scopes=None, emit=HistoricalEmitMode.ALL_TRIGGERS)evaluate_custom_rule_sequence(*, series, rule_set)scan_custom_rule_series(*, series, rule_set, emit=HistoricalEmitMode.ALL_TRIGGERS)custom_rule_set_from_rules(*, rules, enabled_scopes=(within_run, within_material), interpretation_mode=modern, name=None)validate_custom_rule_set(rule_set)
Stable JSON serializers:
serialize_multirule_result(result, *, rule_name_mode=RuleNameOutputMode.CANONICAL)serialize_event_multirule_result(result, *, rule_name_mode=RuleNameOutputMode.CANONICAL)serialize_series_multirule_result(result, *, rule_name_mode=RuleNameOutputMode.CANONICAL)serialize_series_multirule_report(result, *, rule_name_mode=RuleNameOutputMode.CANONICAL)format_rule_name(rule, mode=RuleNameOutputMode.CANONICAL)
Sigma and planning helpers:
calculate_sigma_metrics(*, total_allowable_error, bias, coefficient_of_variation, analyte=None, level=None)recommend_sigma_rules(*, sigma, control_levels)plan_qc_strategy(*, sigma_metrics, control_levels, mode=PlanningMode.MONITOR, residual_risk_target=1.0)SigmaDesignOptionfor structured alternative sigma-rule event designs
Optional adapter API (from westgard.adapters import ...):
dataframe_to_context(frame, *, current_run_id, across_material=False)dataframe_to_series(frame)
Choosing The Right API
- Use
evaluate_rule()when you already know the exact stream and exact rule to test. - Use
evaluate_multirule()when you need one decision for one current QC event and you already have explicit current-run and history streams. - Use custom rule sets when you need validated, user-selected rule bundles rather than named preset families.
- Use
series_from_rows()plusevaluate_multirule_sequence()when you need one result per event across a chronological history. - Use
series_from_rows()plusscan_multirule_series()when you need retrospective trigger discovery for reports, chart annotations, or persistence.
Raw Values And Multi-Material Runs
- Raw QC values are supported through
observation_from_value()or row inputs containingvalue,mean, andstandard_deviation. The package computesz_score = (value - mean) / standard_deviationinternally. standard_deviationmust be positive. Invalid or nonpositive SDs raiseValueError.- Multi-material runs are represented by sharing the same
run_idacross one observation per material. Sequences should remain chronological across the series. - High-level row and dataframe builders auto-group mixed-analyte tables into one
QCSeriesper analyte.
Interpretation And Output Notes
modernevaluates configured rejection rules directly.classicpreserves the historical1_2swarning gate. In sequence/retrospective APIs, downstream rules appear explicitly asskipped_by_warning_gatewhen the gate does not trigger.- For custom rule sets in
classicmode, the warning gate is applied only if1_2sis explicitly selected incurrent_run_rules. - Rule outcomes distinguish
violated,not_violated,insufficient_data, andskipped_by_warning_gate. - Rule metadata includes severity classification:
warning,rejection, orpreset_dependent. - Historical APIs default to
enabled_scopes={within_run, within_material}.across_materialis explicit opt-in. - Same-run across-material current-event rules such as
2_2s,2of3_2s, and3_1sare derived automatically fromcurrent_runwhen the configured rule set includes them. - Retrospective scans support
emitmodes:all_triggers,first_trigger_per_rule,first_trigger_per_rule_per_scope, andall_triggers_grouped_by_window. - Serializer functions provide the stable JSON contract. Rule-bearing payloads always include canonical machine identifiers in
ruleand display labels inrule_label. - Rule-bearing serializer payloads also include
stream_id,stream_scope, andstream_materials.stream_nameremains a backward-compatible alias ofstream_id. rule_name_modecontrols labels only (canonical,clinical, orcompact) and does not affect keys, IDs, grouping, or comparisons.- Result identity field
presetnow distinguishes named presets and custom evaluation (custom). - Serializers attach a nested
custom_rule_setobject only whenpreset == "custom":serialize_multirule_resultserialize_event_multirule_resultserialize_series_multirule_resultserialize_series_multirule_report
Historical Scope Semantics
- Supported evaluator scopes are
within_run,within_material, andacross_material. across_runremains a compatibility enum member but is reserved and not directly evaluable.within_runevaluates current-event streams such ascurrent_run:<run_id>.- Same-run across-material current-event evaluation uses
current_run_across_material:<run_id>. within_materialevaluates per-material history streams such asmaterial_across_runs:L1.across_materialhistory evaluation uses an explicit combined-material stream, defaulting topaired_materials_across_runs, and remains opt-in in historical APIs.- “Across runs” is represented by historical stream construction, not by a separate evaluator scope.
End-To-End Historical Example
from westgard import (
InterpretationMode,
MultiruleVariant,
scan_multirule_series,
series_from_rows,
serialize_series_multirule_result,
)
rows = [
{
"analyte": "glucose",
"material": "L1",
"run_id": "run-001",
"sequence": 1,
"value": 102.0,
"mean": 100.0,
"standard_deviation": 2.0,
},
{
"analyte": "glucose",
"material": "L2",
"run_id": "run-001",
"sequence": 1,
"z_score": -0.3,
},
{
"analyte": "glucose",
"material": "L1",
"run_id": "run-002",
"sequence": 2,
"z_score": 2.2,
},
{
"analyte": "glucose",
"material": "L2",
"run_id": "run-002",
"sequence": 2,
"z_score": 0.2,
},
]
series = series_from_rows(rows)["glucose"]
retrospective = scan_multirule_series(
series=series,
preset=MultiruleVariant.MODERN_2_CONTROL,
interpretation_mode=InterpretationMode.MODERN,
)
payload = serialize_series_multirule_result(retrospective)
print(payload["accepted"])
print(payload["violations"])
print(payload["first_violations_by_rule"])
Common Setup
All examples below use these shared imports/helpers.
from westgard import (
ControlObservation,
EvaluationScope,
InterpretationMode,
MultiruleContext,
MultiruleVariant,
PlanningMode,
RuleName,
RunIdentifier,
calculate_sigma_metrics,
evaluate_multirule,
evaluate_rule,
get_preset_definition,
get_rule_definition,
plan_qc_strategy,
recommend_sigma_rules,
)
def obs(run_id: str, material: str, sequence: int, z: float) -> ControlObservation:
return ControlObservation(
analyte="glucose",
material=material,
run_id=RunIdentifier(run_id),
sequence=sequence,
z_score=z,
)
How-To: get_rule_definition
Signature:
get_rule_definition(rule: RuleName) -> RuleDefinition
Use when you need static metadata for one primitive rule: taxonomy, minimum observations, default scope, intended error type, and description.
definition = get_rule_definition(RuleName.R_4S)
print(definition.minimum_observations) # 2
print(definition.default_scope.value) # within_run
print(definition.intended_error) # random error
Raises:
KeyErrorif the rule is not present inRULE_DEFINITIONS.
How-To: evaluate_rule
Signature:
evaluate_rule(
rule: RuleName,
observations: tuple[ControlObservation, ...],
*,
scope: EvaluationScope,
stream_name: str,
) -> EvaluationResult
Use when you want to evaluate exactly one rule against one explicit stream.
result = evaluate_rule(
RuleName.TWO_2S,
(
obs("run-001", "L1", 1, 2.2),
obs("run-002", "L1", 2, 2.3),
),
scope=EvaluationScope.WITHIN_MATERIAL,
stream_name="material_across_runs:L1",
)
print(result.violated) # True
print(result.enough_data) # True
print(result.reason)
Behavior notes:
- Observations are sorted by
sequencebefore evaluation. - If there are too few observations, it returns
violated=False,enough_data=False. - Limit rules use strict inequality. Boundary values at exactly
+2,-2,+3, or-3do not violate. - Unsupported scope/rule combinations raise
ValueError. across_runis reserved and not directly evaluable.
Raises:
ValueErrorfor invalid rule/scope combinations (for exampleR_4soutsidewithin_run).
How-To: get_preset_definition
Signature:
get_preset_definition(preset: MultiruleVariant) -> PresetDefinition
Use when you need the exact rule bundle for a named preset before evaluating.
preset = get_preset_definition(MultiruleVariant.MODERN_2_CONTROL)
print(preset.current_run_rules) # (1_3s, 2_2s, R_4s)
print(preset.history_rules) # (2_2s, 4_1s, 8_x)
print(preset.classic_warning_gate) # False
Raises:
KeyErrorif the preset key is not defined.
How-To: evaluate_multirule
Signature:
evaluate_multirule(
*,
context: MultiruleContext,
preset: MultiruleVariant,
interpretation_mode: InterpretationMode = InterpretationMode.MODERN,
) -> MultiruleResult
Use when you want end-to-end multirule evaluation with preset rule families.
l1 = (
obs("run-001", "L1", 1, 0.2),
obs("run-002", "L1", 3, 0.4),
obs("run-003", "L1", 5, 3.2),
)
l2 = (
obs("run-001", "L2", 2, -0.1),
obs("run-002", "L2", 4, 0.2),
obs("run-003", "L2", 6, 0.1),
)
context = MultiruleContext(
current_run=(l1[-1], l2[-1]),
material_streams={"L1": l1, "L2": l2},
)
result = evaluate_multirule(
context=context,
preset=MultiruleVariant.MODERN_2_CONTROL,
interpretation_mode=InterpretationMode.MODERN,
)
print(result.accepted)
print([v.rule.value for v in result.violations])
Behavior notes:
modernevaluates configured rejection rules directly.classicfirst evaluates a current-run1_2swarning gate.- If
classicand no warning gate trigger, the run is accepted early. - Same-run across-material current-event rules are derived automatically from
context.current_run. - Cross-material history is evaluated only if
context.across_material_streamis supplied.
How-To: calculate_sigma_metrics
Signature:
calculate_sigma_metrics(
*,
total_allowable_error: float,
bias: float,
coefficient_of_variation: float,
analyte: str | None = None,
level: str | None = None,
) -> SigmaMetrics
Use when you have TEa/bias/CV and need sigma for rule-selection workflows.
metrics = calculate_sigma_metrics(
total_allowable_error=10.0,
bias=-2.0,
coefficient_of_variation=2.0,
analyte="glucose",
level="L1",
)
print(metrics.sigma) # 4.0
Formula:
sigma = (TEa - abs(bias)) / CV
Raises:
ValueErroriftotal_allowable_error <= 0ValueErrorifcoefficient_of_variation <= 0
How-To: recommend_sigma_rules
Signature:
recommend_sigma_rules(*, sigma: float, control_levels: int) -> SigmaRuleMapping
Use when you need a reference-backed sigma-band mapping to a rule set.
mapping = recommend_sigma_rules(sigma=4.5, control_levels=2)
print(mapping.sigma_band) # 4 to <5
print(mapping.recommended_rules) # 1_3s, 2_2s, R_4s, 4_1s
print(mapping.control_measurements_per_event)
print(mapping.max_runs_between_qc_events)
print(mapping.alternative_designs)
Raises:
ValueErrorifcontrol_levelsis not2or3.
How-To: plan_qc_strategy
Signature:
plan_qc_strategy(
*,
sigma_metrics: SigmaMetrics,
control_levels: int,
mode: PlanningMode = PlanningMode.MONITOR,
residual_risk_target: float = 1.0,
) -> PlanningRecommendation
Use when you need deterministic planning notes around startup vs routine monitor usage.
metrics = calculate_sigma_metrics(
total_allowable_error=8.0,
bias=1.0,
coefficient_of_variation=1.0,
)
recommendation = plan_qc_strategy(
sigma_metrics=metrics,
control_levels=2,
mode=PlanningMode.STARTUP,
residual_risk_target=1.0,
)
print(recommendation.mode.value) # startup
print(recommendation.sigma_mapping.sigma_band) # based on computed sigma
print(recommendation.qc_event_note)
print(recommendation.out_of_control_recovery)
Raises:
ValueErrorifresidual_risk_target <= 0.ValueErrorifcontrol_levelsis invalid (propagated fromrecommend_sigma_rules).
How-To: dataframe_to_context (Optional pandas Adapter)
Signature:
dataframe_to_context(
frame,
*,
current_run_id: str,
across_material: bool = False,
) -> MultiruleContext
Use when your QC data starts as a pandas DataFrame and you want to feed it into evaluate_multirule.
import pandas as pd
from westgard.adapters import dataframe_to_context
frame = pd.DataFrame(
[
{"analyte": "glu", "material": "L1", "run_id": "run-1", "sequence": 1, "z_score": 0.1},
{"analyte": "glu", "material": "L2", "run_id": "run-1", "sequence": 2, "z_score": -0.1},
{"analyte": "glu", "material": "L1", "run_id": "run-2", "sequence": 3, "z_score": 2.3},
{"analyte": "glu", "material": "L2", "run_id": "run-2", "sequence": 4, "z_score": 0.2},
]
)
context = dataframe_to_context(frame, current_run_id="run-2", across_material=True)
print(len(context.current_run)) # 2
Required columns:
analytematerialrun_idsequencez_score
Raises:
ImportErrorif pandas is not installed.TypeErrorifframeis not a pandas DataFrame.ValueErrorif required columns are missing.ValueErrorif no rows matchcurrent_run_id.
How-To: EvaluationResult.to_violation (Method)
Signature:
EvaluationResult.to_violation() -> RuleViolation | None
Use when you want a normalized violation payload for downstream reporting.
result = evaluate_rule(
RuleName.ONE_3S,
(obs("run-009", "L1", 1, 3.5),),
scope=EvaluationScope.WITHIN_RUN,
stream_name="current_run:run-009",
)
violation = result.to_violation()
print(violation.rule.value if violation else None)
Behavior notes:
- Returns
Nonewhenresult.violatedisFalse. - Returns
RuleViolationwith rule/scope/stream and triggering observation IDs when violated.
Design Principles
moderninterpretation is the default for computerized evaluation.classicinterpretation is supported explicitly and preserves1_2swarning-gate behavior.R_4sis enforced as within-run only.- Same-run across-material rules are explicit current-event evaluations, not hidden behind the optional history stream.
- The package treats
6_xas the canonical 3-control mean rule while still documenting longer-lookback alternatives such as9_x. - Cross-run history and cross-material history are explicit inputs to the engine. The package does not infer run boundaries from timestamps.
Documentation
- Canonical reference: master reference
- Release runbook: publishing guide
- Package docs: MkDocs Material site under
docs/.
Local Release Validation
Run the strict local deployability gate with:
python scripts/validate_release.py
This command runs linting, typing, tests with branch coverage gate (>=95%), build, twine checks, and wheel smoke install. It writes a local summary report to reports/test-results-summary.md.
License
Apache-2.0
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file westgard_python-0.3.0.tar.gz.
File metadata
- Download URL: westgard_python-0.3.0.tar.gz
- Upload date:
- Size: 32.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6eb4b592fb09623b01f2a38110c365f77a68115d5b8bf0b7c97be436c4482a2a
|
|
| MD5 |
a934276c58526f672b111b5735767e72
|
|
| BLAKE2b-256 |
356ed740be2ac94d964d17b566e3e4355bb14b604b4df39c040c59d7fc86b8e9
|
Provenance
The following attestation bundles were made for westgard_python-0.3.0.tar.gz:
Publisher:
publish.yml on jouno53/Westgard-Python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
westgard_python-0.3.0.tar.gz -
Subject digest:
6eb4b592fb09623b01f2a38110c365f77a68115d5b8bf0b7c97be436c4482a2a - Sigstore transparency entry: 1357362712
- Sigstore integration time:
-
Permalink:
jouno53/Westgard-Python@4eccc6c7cf49ccdfb4bcd2667ac1e9d6a20c7a20 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/jouno53
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4eccc6c7cf49ccdfb4bcd2667ac1e9d6a20c7a20 -
Trigger Event:
push
-
Statement type:
File details
Details for the file westgard_python-0.3.0-py3-none-any.whl.
File metadata
- Download URL: westgard_python-0.3.0-py3-none-any.whl
- Upload date:
- Size: 36.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3adc10db88eeb209c4beb153c1a65500fe9e2273aa24d820ba27bea456f0113b
|
|
| MD5 |
d0dcd1b8c06d64349000c081e20f4173
|
|
| BLAKE2b-256 |
b79913313d834e5fb1a6bc8f77680bdff7ef3cb2915abc1511709abe98fc069d
|
Provenance
The following attestation bundles were made for westgard_python-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on jouno53/Westgard-Python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
westgard_python-0.3.0-py3-none-any.whl -
Subject digest:
3adc10db88eeb209c4beb153c1a65500fe9e2273aa24d820ba27bea456f0113b - Sigstore transparency entry: 1357362769
- Sigstore integration time:
-
Permalink:
jouno53/Westgard-Python@4eccc6c7cf49ccdfb4bcd2667ac1e9d6a20c7a20 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/jouno53
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4eccc6c7cf49ccdfb4bcd2667ac1e9d6a20c7a20 -
Trigger Event:
push
-
Statement type: