diff --git a/.github/workflows/deploy_site.yml b/.github/workflows/deploy_site.yml new file mode 100644 index 0000000..79ab0ae --- /dev/null +++ b/.github/workflows/deploy_site.yml @@ -0,0 +1,51 @@ +name: Deploy injection site + +on: + push: + branches: [dev] + paths: + - scripts/injection/data/** + - scripts/injection/build_site.py + - scripts/injection/README.md + - .github/workflows/deploy_site.yml + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install pandas markdown + + - name: Build site + run: | + mkdir -p _site + python3 scripts/injection/build_site.py \ + --data-dir scripts/injection/data \ + --out-dir _site/injection + + - uses: actions/configure-pages@v4 + + - uses: actions/upload-pages-artifact@v3 + with: + path: _site + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index c000fa2..b1db2de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,15 @@ finngen_qc/test/ -/scripts/ +/scripts/test/ + +# injection site — generated HTML (rebuilt by CI) +scripts/injection/html/ +# injection intermediate pipeline cache files (not needed for site) +scripts/injection/data/test_name_counts.tsv +scripts/injection/data/test_name_details.tsv +scripts/injection/data/omop_unit_table.tsv +scripts/injection/data/unambiguous_results.tsv +scripts/injection/data/ambiguous_results.tsv +scripts/injection/data/plot_name_level.tsv #EMACS FILES \#*\# diff --git a/README.md b/README.md index f2a068f..6565bf7 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,26 @@ The raw to output column mapping is as follows: ![Summary of the wdl pipeline](./kanta_pipeline.png) +# UNIT INJECTION + +Many lab records carry a numeric value but no unit (the `MEASUREMENT_UNIT_PRE_FIX` field is NULL). The unit injection pipeline ([`scripts/injection/`](/scripts/injection/)) identifies these records and, where possible, assigns a unit by comparing their value distribution against records of the same `TEST_NAME` that already have a unit. + +Each `TEST_NAME` is classified into one of three categories based on the amount of reference data available (records that have both a value and a unit): + +| Category | Condition | How a unit is assigned | +|---|---|---| +| `UNAMBIGUOUS` | One unit accounts for ≥ threshold% of reference records | Distribution comparison against that dominant unit via KS / t / MAD tests | +| `AMBIGUOUS` | Multiple units present; no single one dominates | Per-unit comparison, optionally after splitting a bimodal distribution | +| `NO_DATA` | Fewer than `--min-target-n` reference records with any unit | No engine run; unit rescued via OMOP (see below) | + +## NO_DATA handling + +`NO_DATA` TEST_NAMEs have too little reference data for a statistical distribution comparison. Instead, they are enriched using the [OMOP unit table](/scripts/injection/omop_unit_table.tsv): if the OMOP concept mapped to a `NO_DATA` test has category `SINGLE` (only one unit ever observed) or `EQUIVALENT` (multiple units but all mutually convertible), the canonical OMOP unit is injected directly. Tests with OMOP category `MULTIPLE` or with no OMOP mapping receive no unit. + +Results are written to `no_data_results.tsv`. At the active 98% threshold (~500 min-count), ~28% of TEST_NAMEs (≈202) fall into this category, representing ~3.5% of measurements. + +For full details on the injection engine, bimodality detection, and output columns, see the [injection README](/scripts/injection/README.md). + # MERGE OF RAW DATA Inputs reports and responses are sorted over the same keys and merged diff --git a/scripts/injection/README.md b/scripts/injection/README.md new file mode 100644 index 0000000..1d3752f --- /dev/null +++ b/scripts/injection/README.md @@ -0,0 +1,223 @@ +# Unit injection exploration + +Site: https://finngen.github.io/kanta_lab_preprocessing/injection/ + +The goal is to identify lab measurements that have a numeric value but are missing a unit, and to characterise the unit distribution of the matching records that do have a unit — so that a unit can be confidently assigned to the missing ones. + +## Summary + +![scatter](data/test_names_exploration_scatter.png) + +*Active threshold: 98% — min count: 500 — 715 TEST_NAMEs, 31,962,504 measurements* + +| Threshold | UNAMBIGUOUS test names | UNAMBIGUOUS measurements | AMBIGUOUS test names | AMBIGUOUS measurements | NO_DATA test names | NO_DATA measurements | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 95% | 434 (60.7%) | 26,508,342 (82.9%) | 79 (11.0%) | 4,345,264 (13.6%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 98% \* | 413 (57.8%) | 24,979,594 (78.2%) | 100 (14.0%) | 5,874,012 (18.4%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 99% | 392 (54.8%) | 22,954,996 (71.8%) | 121 (16.9%) | 7,898,610 (24.7%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 100% | 275 (38.5%) | 12,745,902 (39.9%) | 238 (33.3%) | 18,107,704 (56.7%) | 202 (28.3%) | 1,108,898 (3.5%) | +| **TOTAL** | **715** | | | || | + +--- + +## Usage + +```bash +python3 explore_test_name.py [options] +``` + +- ``: path to the input parquet file (INJECT.parquet) +- `--min-count INT`: minimum no-unit records a TEST_NAME must have to be included in plots and injection (default: 1000) +- `--prevalence-threshold FLOAT`: min dominant-unit prevalence (%) to classify a TEST_NAME as UNAMBIGUOUS (default: 98) +- `--min-target-n INT`: minimum reference records a unit must have; tests below this are classified NO_DATA (default: 30) +- `--dump-dir PATH`: cache directory for per-test `.npy` arrays (default: `/mnt/disks/data/kanta/inject/tmp/`) +- `--omop-unit-table PATH`: cache path for the OMOP unit table; built automatically on first run (default: `omop_unit_table.tsv`) +- `--inject`: run the injection engine (unambiguous + ambiguous + no_data passes) +- `--dip-threshold FLOAT`: Hartigan dip test p-value threshold for bimodality (default: 0.05) +- `--split-threshold FLOAT`: minimum relative KS improvement required to prefer a score-based split over the global fit (default: 0.15) +- `--test`: small-sample mode — one test per COUNT decile (unambiguous) and top 10 by volume (ambiguous) + +### Caching behaviour + +`test_name_counts.tsv` and `test_name_details.tsv` are cached after the first ClickHouse query and reused on subsequent runs — delete them to force a re-query. They are always queried with a fixed baseline of `COUNT > 50`; `--min-count` is applied as a post-load filter in Python, so changing it never requires a re-query. + +`plot_name_level.tsv`, `test_names_exploration_scatter.png`, and `summary_table.md` are always recomputed from the filtered data. + +Per-test `.npy` arrays are always cached in `--dump-dir` and reused across runs. + +--- + +## Step 1 — Injection targets (`test_name_counts.tsv`) + +Counts records where at least one value is present (`MEASUREMENT_VALUE_EXTRACTED IS NOT NULL OR MEASUREMENT_VALUE_SOURCE IS NOT NULL`) but the unit prefix is absent (`MEASUREMENT_UNIT_PRE_FIX IS NULL`), grouped by `TEST_NAME`. Cached with `COUNT > 50`; further filtered to `--min-count` at runtime. + +Columns: `TEST_NAME`, `COUNT` + +--- + +## Step 2 — Reference population (`test_name_details.tsv`) + +For every TEST_NAME in Step 1, describes the unit distribution of the records that already have both a value and a unit (`MEASUREMENT_VALUE_SOURCE IS NOT NULL AND MEASUREMENT_UNIT_PRE_FIX IS NOT NULL`). + +Columns: `TEST_NAME`, `COUNT`, `UNIT`, `PREVALENCE_DICT` + +- `COUNT`: total reference records for this TEST_NAME (across all units) +- `UNIT`: the most frequent unit +- `PREVALENCE_DICT`: top-3 units and their percentage share, e.g. `{mmol/l:98.5,umol/l:1.5}` + +--- + +## Step 3 — Plotting table, scatter plot, and classification + +Steps 1 and 2 are merged in Python to produce `plot_name_level.tsv`, a scatter plot, and a summary table. Each TEST_NAME is assigned a `CATEGORY` that drives which injection pass it enters. + +### `plot_name_level.tsv` + +One row per TEST_NAME. Columns include `COUNT`, `N_WITH_UNIT`, `top_prevalence`, `CATEGORY`, and exploratory `CATEGORY_{95,98,99,100}` columns. + +- `COUNT`: no-unit record count +- `N_WITH_UNIT`: total reference records with any unit +- `top_prevalence`: prevalence (%) of the dominant unit; 0 if no reference records exist + +### Classification (`CATEGORY`) + +| Category | Condition | +|---|---| +| `NO_DATA` | `N_WITH_UNIT < --min-target-n` OR `top_prevalence == 0` | +| `UNAMBIGUOUS` | `top_prevalence >= --prevalence-threshold` | +| `AMBIGUOUS` | `0 < top_prevalence < --prevalence-threshold` | + +The injection engine only runs tests classified as UNAMBIGUOUS or AMBIGUOUS. NO_DATA tests are written directly to `no_data_results.tsv`. + +### OMOP unit table (`omop_unit_table.tsv`) + +Built once from the OMOP LAB mapping files and enriched with per-concept record counts queried from the parquet file. Cached at `--omop-unit-table`. Each OMOP concept is assigned a `CATEGORY`: + +- `SINGLE` — only one unit observed in the OMOP data +- `EQUIVALENT` — multiple units present but all mutually convertible; a `CANONICAL_UNIT` is available +- `MULTIPLE` — multiple non-equivalent units; no single canonical unit can be assigned + +This table is used to enrich `no_data_results.tsv` with canonical units. + +Two summary tables are written: +- `summary_table.md` — all TEST_NAMEs +- `omop_summary_table.md` — OMOP-mapped subset only + +--- + +## Step 4 — Injection engine (`--inject`) + +With `--inject`, three passes are run and a coverage check validates that every TEST_NAME in `plot_name_level.tsv` appears in exactly one output file. + +### Unambiguous pass → `unambiguous_results.tsv` + +TEST_NAMEs with `CATEGORY == UNAMBIGUOUS`. The candidate distribution (no-unit records) is compared against the reference distribution for the dominant unit. + +One row per TEST_NAME. Columns: `TEST_NAME`, `UNIT`, `PREVALENCE_DICT`, `N_CANDIDATE`, `N_TARGET`, `CAND_DECILES`, `TARG_DECILES`, `KS_STAT`, `KS_MLOGP`, `KS_PASS`, `T_STAT`, `T_MLOGP`, `T_PASS`, `MAD_DIST`, `MAD_THRESHOLD`, `MAD_PASS`, `OUTCOME`, `NOTES`. + +### Ambiguous pass → `ambiguous_results.tsv` + +TEST_NAMEs with `CATEGORY == AMBIGUOUS`. Only units with prevalence > 1% and at least `--min-target-n` reference records are considered. + +Pipeline per TEST_NAME: + +1. **Pre-check**: run the full (unsplit) candidate distribution against each qualifying unit. +2. **Bimodality check** (always runs, even if pre-check passed): test the candidate distribution for bimodality and compute `split_improvement` — the relative KS gain when the candidate is split at the GMM separator vs. treated globally. +3. **Split decision**: a split is preferred if `split_improvement > --split-threshold` AND the two halves favour different best units (`same_best_unit == False`). If splitting is not preferred and any pre-check passed, the global result is kept. +4. **Sub-distribution engine** (only if splitting is preferred): the candidate is split into low/high halves at the GMM separator and the engine is re-run on each half × unit. + +One row per `(TEST_NAME, SUB_DIST)` — the best unit only. Best unit selection per sub-distribution: + +1. Deciding test quality: `PASS_at_KS` > `PASS_at_T` > `PASS_at_MAD` > `FAIL` +2. KS statistic ascending (lower = better distributional fit) as tiebreaker within the same quality tier +3. `UNIT_PREVALENCE` descending as final tiebreaker — prefer the clinically dominant unit when two units are otherwise equivalent + +A `split_eval_{tag}.png` decision-tree figure is saved to `--dump-dir` for every TEST_NAME. + +Columns: `TEST_NAME`, `BIMODAL_STATUS`, `BIMODAL_SEP`, `BIMODAL_BC`, `BIMODAL_DIP_P`, `BIMODAL_OVERLAP`, `SCORE_GLOBAL`, `SCORE_SPLIT`, `SCORE_IMPROVEMENT`, `SUB_DIST`, `UNIT`, `UNIT_PREVALENCE`, `PREVALENCE_DICT`, `N_CANDIDATE`, `N_TARGET`, `CAND_DECILES`, `TARG_DECILES`, `KS_STAT`, `KS_MLOGP`, `KS_PASS`, `T_STAT`, `T_MLOGP`, `T_PASS`, `MAD_DIST`, `MAD_THRESHOLD`, `MAD_PASS`, `OUTCOME`, `NOTES`. + +- `SCORE_GLOBAL`: best KS statistic achieved across all units on the full candidate distribution (lower = better fit) +- `SCORE_SPLIT`: size-weighted mean of the best KS statistics for the low and high sub-distributions +- `SCORE_IMPROVEMENT`: `(SCORE_GLOBAL − SCORE_SPLIT) / SCORE_GLOBAL` — relative improvement from splitting +- `BIMODAL_OVERLAP`: GMM overlap coefficient expressed as a percentage. Computed as ∫ min(w₁·f₁(x), w₂·f₂(x)) dx × 100 on a fine grid covering ±4σ from each component mean, where f₁, f₂ are the Gaussian component densities and w₁, w₂ their mixture weights. A value of 0 % means the two modes are perfectly separated; values above ~5–15 % indicate meaningful overlap where values near the separator cannot be confidently assigned to either unit, with direct clinical implications (e.g. a low-mg value mislabelled as g would change the clinical interpretation by orders of magnitude). NA for unambiguous tests and for ambiguous tests where no split was performed. + +### No-data pass → `no_data_results.tsv` + +TEST_NAMEs with `CATEGORY == NO_DATA`. No engine is run. The result is enriched with OMOP unit table information; a canonical unit is injected where the OMOP concept has category SINGLE or EQUIVALENT. + +Columns: `TEST_NAME`, `COUNT`, `OMOP_CONCEPT_ID`, `OMOP_QUANTITY`, `CATEGORY`, `N_UNITS`, `UNITS`, `CONVERSIONS`, `OMOP_TOTAL_N`, `UNIT`, `PREVALENCE`. + +- `CATEGORY`: OMOP unit category (`SINGLE`, `EQUIVALENT`, `MULTIPLE`, or NA) +- `UNIT`: canonical unit from the OMOP table if available, otherwise blank +- `PREVALENCE`: fraction of OMOP concept records that are no-unit (`COUNT / OMOP_TOTAL_N`) + +After writing, a breakdown is printed: +- no OMOP — TEST_NAMEs with no concept ID +- OMOP, unit injected — concept has SINGLE/EQUIVALENT category +- OMOP, no unit — concept mapped but MULTIPLE or unknown unit + +### Unified output → `injection_results.tsv` + +Merge of the unambiguous and ambiguous results with a `TYPE` column (`unambiguous` / `ambiguous`). Does not include no_data rows. + +### Coverage check + +After all three passes, a checksum validates that the union of the three output files equals the full set of TEST_NAMEs in `plot_name_level.tsv`, with no overlaps and no missing entries. Skipped in `--test` mode. + +### Assignment summary + +Printed at the end of `--inject`. Shows, for each category (UNAMBIGUOUS, AMBIGUOUS, NO_DATA) and in total, how many TEST_NAMEs and measurements received a unit (PASS rows), broken out for all TEST_NAMEs and the OMOP-mapped subset. + +--- + +## Injection engine pipeline + +Each comparison runs three tests in order. All three always run; the first to decide the outcome wins. + +1. **KS test** — two-sample Kolmogorov–Smirnov. PASS = stat < 0.3 AND p < 0.05. Uses a fast binned approximation (100k bins, Hodges-corrected asymptotic p) above 500k samples; exact scipy otherwise. +2. **Welch t-test** — fallback if KS fails. PASS = p ≥ 0.05 (means not significantly different). +3. **MAD test** — last resort. PASS = |median(candidate) − median(target)| ≤ 3 × MAD(target). + +Decision rule: KS PASS → PASS. KS FAIL, T PASS → PASS. Both fail → MAD decides. `NOTES` records the deciding test and how many of the three passed, e.g. `PASS_at_T_(2/3)`. + +P-values are stored as −log10(p) throughout (`KS_MLOGP`, `T_MLOGP`). + +Each comparison produces a 3-panel diagnostic plot saved to `--dump-dir`: +- **Panel 1**: ECDFs + KS distance marked in red + KS annotation +- **Panel 2**: KDE (linear scale) + dotted mean lines + t-test annotation +- **Panel 3**: KDE (log scale) + MAD band (green = PASS, salmon = FAIL) + median lines + distance arrow + +KDE/ECDF rendering downsamples to 50k points. All statistics use the full arrays. + +--- + +## Bimodal check + +Before the ambiguous sub-distribution pass, the candidate distribution is tested for bimodality using two statistics: + +The candidate array is subsampled to 50,000 points before the dip test and GMM fitting (the dip test is unreliable above ~72k samples and GMM fitting is also faster at this size). All statistics and the separator are computed on this subsample. + +**Hartigan's dip test** (primary gate): p-value < `--dip-threshold` (default 0.05) declares non-unimodal. + +**Bimodality coefficient** (BC): `(skew² + 1) / (excess_kurtosis + 3(n−1)²/((n−2)(n−3)))`. Values above ~0.555 suggest bimodality. + +Both are computed in the space (linear or log) where a 2-component GMM achieves lower BIC. The GMM separator is used to split the candidate distribution. + +| dip p | BC | Status | +|---|---|---| +| ≥ threshold | — | `unimodal` | +| < threshold | ≥ 0.555 | `bimodal` — split into low/high | +| < threshold | < 0.555 | `bimodal_cautious` — split, modes may overlap | +| — | — | `skipped` — pre-check passed and split not preferred | + +`split_by_score` is a separate label assigned when score improvement alone drives the split (`SCORE_IMPROVEMENT > --split-threshold` and the two halves favour different best units) but the dip test did not find bimodality. When the dip test does confirm bimodality, the `bimodal` or `bimodal_cautious` label takes priority even if score improvement is also large — so `split_by_score` strictly means "split was justified by score despite a unimodal dip result." + +The `NOTES` column in the unified output records the split rationale followed by `DIP` and `OL` metrics: +- `split_by_bimodal at Y | DIP:p,BL:x%` — dip-test-confirmed bimodal split at separator Y +- `split_by_bimodal_cautious at Y | DIP:p,BL:x%` — as above, with overlapping modes +- `split_by_score (+X%) at Y | DIP:p,BL:x%` — score-driven split despite unimodal dip result +- `NO_SPLIT` — no split performed (pre-check passed, or unimodal with no score preference) + +`DIP` is the Hartigan dip test p-value; `BL` is the bimodal overlap percentage (see `BIMODAL_OVERLAP` above). + +`BIMODAL_OVERLAP` (see above) quantifies how cleanly separated the two modes are after any split. A diagnostic plot (`bimodal_{tag}.png`) is saved to `--dump-dir`. diff --git a/scripts/injection/build_site.py b/scripts/injection/build_site.py new file mode 100644 index 0000000..33b79c5 --- /dev/null +++ b/scripts/injection/build_site.py @@ -0,0 +1,995 @@ +#!/usr/bin/env python3 +""" +build_site.py + +Generate a local HTML site from injection pipeline outputs. + +Usage: + python3 build_site.py [--out-dir PATH] + +Reads from {out_dir}/data/ and {out_dir}/plots/, writes index.html and doc.html +to {out_dir}/. Pass --out-dir to match what you used with explore_test_name.py. +""" + +import argparse +import gzip +import os +import re +import json +import shutil +from pathlib import Path + +import markdown +import pandas as pd + + +# --------------------------------------------------------------------------- +# Column label overrides and preferred display order. +# Columns not listed here are appended after these in their original TSV order. +# --------------------------------------------------------------------------- + +COL_LABELS = { + "TEST_NAME": "Test name", + "TYPE": "Type", + "CUTOFF": "Cutoff", + "UNIT": "Unit", + "OUTCOME": "Outcome", + "NOTES": "Notes", + "TESTS_PASSED": "Tests passed", + "UNIT_PREVALENCE": "Unit prev (%)", + "N_CANDIDATE": "N cand", + "N_TARGET": "N target", + "KS_STAT": "KS stat", + "KS_MLOGP": "KS −log10p", + "KS_PASS": "KS pass", + "T_STAT": "T stat", + "T_MLOGP": "T −log10p", + "T_PASS": "T pass", + "MAD_DIST": "MAD dist", + "MAD_THRESHOLD": "MAD thr", + "MAD_PASS": "MAD pass", + "BIMODAL_OVERLAP": "Overlap (%)", +} + +COL_ORDER = list(COL_LABELS.keys()) + +# Pixel widths for columns that are inherently short +COL_WIDTHS = { + "TYPE": 80, + "CUTOFF": 80, + "OUTCOME": 75, + "KS_PASS": 70, + "T_PASS": 70, + "MAD_PASS": 70, + "KS_STAT": 75, + "KS_MLOGP": 85, + "T_STAT": 75, + "T_MLOGP": 85, + "MAD_DIST": 80, + "MAD_THRESHOLD": 80, + "UNIT_PREVALENCE": 90, + "BIMODAL_OVERLAP": 85, + "TESTS_PASSED": 80, + "N_CANDIDATE": 85, + "N_TARGET": 75, + "SUB_DIST": 70, +} + +# Columns to drop entirely from the landing table (reserved for per-test pages) +EXCLUDE_COLS = { + "SUB_DIST", + "BIMODAL_STATUS", "BIMODAL_SEP", "BIMODAL_BC", "BIMODAL_DIP_P", + "SCORE_GLOBAL", "SCORE_SPLIT", "SCORE_IMPROVEMENT", + "CAND_DECILES", "TARG_DECILES", + "PREVALENCE_DICT", +} + + +# --------------------------------------------------------------------------- +# HTML templates (use __MARKER__ substitution to avoid Python/JS brace clash) +# --------------------------------------------------------------------------- + +_INDEX = """ + + + + + Unit injection results + + + + + + +
+

Unit injection results

+ + __SCATTER_BLOCK__ + + __SUMMARY_SECTION__ + +

Per-column search: text columns support regex (e.g. ^PASS$, + mmol). Numeric columns support comparisons: <3, + >=20, !=0. The global box top-right searches all columns with regex.

+ +
+ + + __FOOTERS__ + __HEADERS__ + + +
+
+ + + + + + + +""" + +_DOC = """ + + + + + Documentation — unit injection + + + + + +
+ __README_HTML__ + + +""" + + +_AMBIG_PAGE = """ + + + + + __TEST_NAME__ + + + + + +
+ +
+

__TEST_NAME__

+ ambiguous +
+ +
+
Bimodal status
+
__BIMODAL_STATUS__
+
Split improvement
+
__SCORE_IMPROVEMENT__
+
Distribution overlap
+
__OVERLAP_PCT__%
+
+ +

__EXPLANATION__

+ + +
+
Bimodal check + + winner: __BIM_WINNER__  |  sep=__BIM_SEP__  |  + BC=__BIM_BC__  |  dip_p=__BIM_DIP_P__  |  + overlap=__OVERLAP_PCT__% + +
+
+
+ + + __ENGINE_SECTIONS__ + + + + + +""" + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _find_scatter(data_dir: Path, out_dir: Path) -> str: + p = data_dir / "test_names_exploration_scatter.png" + if p.exists(): + dest = out_dir / "test_names_exploration_scatter.png" + if not dest.exists(): + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(p, dest) + data_dest = out_dir / "data" / "test_names_exploration_scatter.png" + if not data_dest.exists(): + data_dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(p, data_dest) + return "test_names_exploration_scatter.png" + return "" + + +def _md_to_html(path: Path) -> str: + if not path.exists(): + return "" + return markdown.markdown(path.read_text(), extensions=["tables", "fenced_code"]) + + +def _summary_section(data_dir: Path) -> str: + parts = [] + for fname, title in (("summary_table.md", "Category summary"), + ("assignment_summary.md", "Assignment summary")): + html = _md_to_html(data_dir / fname) + if html: + parts.append(f'

{title}

{html}') + if not parts: + return "" + return '
' + "".join(parts) + "
" + + +def _scatter_block(rel_path: str) -> str: + if not rel_path: + return '

Scatter plot not found.

' + return (f'
' + f'Scatter: TEST_NAMEs by count and prevalence' + f'
') + + +# --------------------------------------------------------------------------- +# Page builders +# --------------------------------------------------------------------------- + +def build_index(out_dir: Path, data_dir: Path, + inj_path: Path = None, scatter_override: str = None) -> None: + inj = inj_path or data_dir / "injection_results.tsv" + if not inj.exists(): + print(f"Warning: {inj} not found — table will be empty") + df = pd.DataFrame(columns=COL_ORDER) + else: + df = pd.read_csv(inj, sep="\t", na_values=["NA"]) + + # COL_ORDER first (if present), then any remaining TSV columns, minus excluded ones + ordered = [c for c in COL_ORDER if c in df.columns] + remaining = [c for c in df.columns if c not in set(ordered) and c not in EXCLUDE_COLS] + all_cols = ordered + remaining + cols = [(c, COL_LABELS.get(c, c)) for c in all_cols] + + df_out = df[all_cols].copy().fillna("") + if "CUTOFF" in df_out.columns: + df_out["CUTOFF"] = df_out["CUTOFF"].replace(float("inf"), "∞") + rows = df_out.to_dict(orient="records") + + # Which column indices hold numeric data + numeric_set = {i for i, c in enumerate(all_cols) + if pd.api.types.is_numeric_dtype(df[c])} + + # Build columns array with widths embedded; mark wide columns for CSS + wide_cols = {"TEST_NAME", "NOTES", "UNIT"} + def _col_def(k, l): + d = {"data": k, "title": l} + if k in COL_WIDTHS: + d["width"] = f"{COL_WIDTHS[k]}px" + if k in wide_cols: + d["className"] = "col-wide" + if k == "TEST_NAME": + d["render"] = "__TEST_NAME_RENDER__" + return d + + headers = "".join(f"{label}" for _, label in cols) + footers = "".join(f'' + for _, label in cols) + data_json = json.dumps(rows, allow_nan=False) + columns_json = json.dumps([_col_def(k, l) for k, l in cols]) + numeric_cols_json = json.dumps({i: True for i in numeric_set}) + outcome_idx = next((i for i, (k, _) in enumerate(cols) if k == "OUTCOME"), 3) + scatter_path = scatter_override if scatter_override is not None else _find_scatter(data_dir, out_dir) + + # TEST_NAME render function: link to per-test page + test_name_render = ("function(data,type){if(type!=='display')return data;" + "var slug=data.replace(/[^a-zA-Z0-9_-]/g,'_');" + "return ''+data+'';}") + + html = (_INDEX + .replace("__SCATTER_BLOCK__", _scatter_block(scatter_path)) + .replace("__SUMMARY_SECTION__", _summary_section(data_dir)) + .replace("__HEADERS__", headers) + .replace("__FOOTERS__", footers) + .replace("__DATA_JSON__", data_json) + .replace("__COLUMNS_JSON__", columns_json) + .replace("__NUMERIC_COLS_JSON__", numeric_cols_json) + .replace("__OUTCOME_COL_IDX__", str(outcome_idx)) + .replace('"__TEST_NAME_RENDER__"', test_name_render)) + + out = out_dir / "index.html" + out.write_text(html, encoding="utf-8") + print(f"Wrote {out} ({len(rows)} rows)") + + +def _slugify(name: str) -> str: + return re.sub(r"[^a-zA-Z0-9_-]", "_", name) + + +_TEST_PAGE = """ + + + + + __TEST_NAME__ + + + + + +
+ +
+

__TEST_NAME__

+ __OUTCOME__ + __TYPE__ +
+ +
+
Unit
__UNIT__
+
Unit prevalence
__UNIT_PREV__%
+
N candidate
__N_CAND__
+
N target
__N_TARGET__
+
Decided by
__DECIDED_BY__
+
+ +

__EXPLANATION__

+ +
+ + + + + +""" + + +def _explanation(row, pd) -> str: + outcome = row.get("OUTCOME", "") + decided_by = pd.get("decided_by", "") + unit = row.get("UNIT", "") + prev = row.get("UNIT_PREVALENCE", "") + + if outcome == "PASS": + by_map = { + "KS": f"The KS test confirmed the distributions match (stat={row.get('KS_STAT',''):.3g}, −log10p={row.get('KS_MLOGP',''):.1f}).", + "T": f"The KS test was inconclusive but the Welch t-test confirmed similar means (−log10p={row.get('T_MLOGP',''):.1f}).", + "MAD": f"KS and t-tests were inconclusive, but the MAD test confirmed medians are within the threshold (dist={row.get('MAD_DIST',''):.3g}).", + } + desc = by_map.get(decided_by, "") + return (f"{prev:.1f}% of reference records use unit {unit}. " + f"{desc} Unit {unit} assigned.") + else: + return (f"No unit could be confidently assigned. " + f"The candidate distribution did not match any reference unit " + f"(KS stat={row.get('KS_STAT',''):.3g}, MAD dist={row.get('MAD_DIST',''):.3g}).") + + +def _ambig_explanation(rows, bim_data: dict) -> str: + status = str(rows.iloc[0].get("BIMODAL_STATUS", "")) + sep = rows.iloc[0].get("BIMODAL_SEP") + bc = rows.iloc[0].get("BIMODAL_BC") + dip_p = rows.iloc[0].get("BIMODAL_DIP_P") + impr = rows.iloc[0].get("SCORE_IMPROVEMENT") + + # Bimodal sentence + if status == "skipped": + bim_sentence = "No split was performed — the pre-check passed globally without needing to split the distribution." + elif status == "split_by_score": + impr_str = f"{impr:+.1%}" if pd.notna(impr) else "?" + sep_str = f"{sep:.4g}" if pd.notna(sep) else "?" + bim_sentence = (f"The distribution was split by score improvement ({impr_str}) at cutoff {sep_str}, " + f"even though the dip test did not flag bimodality.") + elif status in ("bimodal", "bimodal_cautious"): + dip_str = f"{dip_p:.3g}" if pd.notna(dip_p) else "?" + bc_str = f"{bc:.3f}" if pd.notna(bc) else "?" + sep_str = f"{sep:.4g}" if pd.notna(sep) else "?" + cautious = " (modes overlap)" if status == "bimodal_cautious" else "" + bim_sentence = (f"Hartigan's dip test detected bimodality{cautious} " + f"(dip p={dip_str}, BC={bc_str}). Distribution split at {sep_str}.") + else: + bim_sentence = "" + + # Per sub-dist sentences + sub_sentences = [] + for _, row in rows.iterrows(): + sub = str(row.get("SUB_DIST", "all")) + unit = str(row.get("UNIT", "")) + outcome = str(row.get("OUTCOME", "")) + decided = ("KS" if row.get("KS_PASS") == "PASS" + else "T" if row.get("T_PASS") == "PASS" + else "MAD" if row.get("OUTCOME") not in ("SKIP", "") + else "") + prev = row.get("UNIT_PREVALENCE") + prev_str = f"{prev:.1f}%" if pd.notna(prev) else "?" + if sub == "all": + label = "The full candidate distribution" + else: + label = f"The {sub} sub-distribution" + if outcome == "PASS": + sub_sentences.append(f"{label} matched unit {unit} ({prev_str} prevalence, decided by {decided}).") + else: + sub_sentences.append(f"{label} did not match any unit confidently (outcome: {outcome}).") + + return " ".join(filter(None, [bim_sentence] + sub_sentences)) + + +def _print_progress(current: int, total: int, prefix: str = "Building test pages") -> None: + width = 40 + filled = int(width * current / total) if total else width + bar = "█" * filled + "░" * (width - filled) + pct = 100 * current / total if total else 100 + print(f"\r{prefix} [{bar}] {current}/{total} ({pct:.0f}%)", + end="\n" if current == total else "", flush=True) + + +def build_test_pages(out_dir: Path, data_dir: Path) -> None: + plot_data_path = data_dir / "plot_data.json.gz" + inj_path = data_dir / "injection_results.tsv" + + if not plot_data_path.exists(): + print(f"Skipping test pages: {plot_data_path} not found") + return + if not inj_path.exists(): + print(f"Skipping test pages: {inj_path} not found") + return + + with gzip.open(plot_data_path, "rb") as fh: + plot_data = json.loads(fh.read()) + df = pd.read_csv(inj_path, sep="\t", na_values=["NA"]) + + # Only unambiguous rows have the ecdf key directly + unamb = df[df["TYPE"] == "unambiguous"].copy() + + tests_dir = out_dir / "tests" + tests_dir.mkdir(exist_ok=True) + + # Pre-filter so total count is accurate + unamb_rows = [(row, plot_data[row["TEST_NAME"]]) + for _, row in unamb.iterrows() + if row["TEST_NAME"] in plot_data and "ecdf" in plot_data[row["TEST_NAME"]]] + ambig_names = [n for n in df[df["TYPE"] == "ambiguous"]["TEST_NAME"].unique() + if n in plot_data and "engine" in plot_data[n]] + total = len(unamb_rows) + len(ambig_names) + + written = 0 + for row, pd_ in unamb_rows: + written += 1 + _print_progress(written, total) + name = row["TEST_NAME"] + slug = _slugify(name) + outcome = str(row.get("OUTCOME", "")) + html = (_TEST_PAGE + .replace("__TEST_NAME__", name) + .replace("__OUTCOME_CLASS__", "badge-pass" if outcome == "PASS" else "badge-fail") + .replace("__OUTCOME__", outcome) + .replace("__TYPE__", str(row.get("TYPE", ""))) + .replace("__UNIT__", str(row.get("UNIT", ""))) + .replace("__UNIT_PREV__", f"{row.get('UNIT_PREVALENCE', 0):.1f}") + .replace("__N_CAND__", f"{int(row.get('N_CANDIDATE', 0)):,}") + .replace("__N_TARGET__", f"{int(row.get('N_TARGET', 0)):,}") + .replace("__DECIDED_BY__", str(pd_.get("decided_by", ""))) + .replace("__EXPLANATION__", _explanation(row, pd_)) + .replace("__PLOT_DATA_JSON__", json.dumps(pd_))) + (tests_dir / f"{slug}.html").write_text(html, encoding="utf-8") + + # Ambiguous pages + for name in ambig_names: + written += 1 + _print_progress(written, total) + pd_ = plot_data[name] + rows = df[df["TEST_NAME"] == name] + bim = pd_.get("bimodal") or {} + first = rows.iloc[0] + + # Build one engine section per winning row + engine_sections_html = "" + sections_data = [] + for _, row in rows.iterrows(): + sub = str(row.get("SUB_DIST", "all")) + unit = str(row.get("UNIT", "")) + key = f"{sub}_{unit}" + epd = pd_["engine"].get(key) + outcome = str(row.get("OUTCOME", "")) + badge = "badge-pass" if outcome == "PASS" else ("badge-fail" if outcome == "FAIL" else "badge-skip") + chart_id = _slugify(key) + label = f"{sub} — {unit}" + engine_sections_html += ( + f'
' + f'
{label} {outcome}' + f'' + f'N cand={int(row.get("N_CANDIDATE",0)):,}   N target={int(row.get("N_TARGET",0)):,}  ' + f'unit prev={row.get("UNIT_PREVALENCE",0):.1f}%
' + f'
' + f'
' + ) + sections_data.append({"chart_id": chart_id, "plot_data": epd}) + + bim_sep = f"{bim.get('separator'):.4g}" if bim.get('separator') is not None else "NA" + bim_bc = f"{bim.get('bc'):.3f}" if bim.get('bc') is not None else "NA" + bim_dip = f"{bim.get('dip_p'):.3g}" if bim.get('dip_p') is not None else "NA" + score_impr = first.get("SCORE_IMPROVEMENT") + score_str = f"{score_impr:+.1%}" if pd.notna(score_impr) else "NA" + overlap_val = first.get("BIMODAL_OVERLAP") + overlap_str = f"{overlap_val:.1f}" if pd.notna(overlap_val) else "NA" + overlap_cls = ("overlap-low" if pd.notna(overlap_val) and overlap_val < 5 + else "overlap-mid" if pd.notna(overlap_val) and overlap_val < 15 + else "overlap-high" if pd.notna(overlap_val) + else "") + + page_data = {"bimodal": bim, "sections": sections_data} + html = (_AMBIG_PAGE + .replace("__TEST_NAME__", name) + .replace("__EXPLANATION__", _ambig_explanation(rows, bim)) + .replace("__BIMODAL_STATUS__", str(first.get("BIMODAL_STATUS", "NA"))) + .replace("__SCORE_IMPROVEMENT__", score_str) + .replace("__OVERLAP_CLASS__", overlap_cls) + .replace("__OVERLAP_PCT__", overlap_str) + .replace("__BIM_WINNER__", bim.get("winner", "NA")) + .replace("__BIM_SEP__", bim_sep) + .replace("__BIM_BC__", bim_bc) + .replace("__BIM_DIP_P__", bim_dip) + .replace("__ENGINE_SECTIONS__", engine_sections_html) + .replace("__PAGE_DATA_JSON__", json.dumps(page_data))) + slug = _slugify(name) + (tests_dir / f"{slug}.html").write_text(html, encoding="utf-8") + + print(f"Wrote {written} test pages → {tests_dir}") + + +def build_doc(out_dir: Path, readme_path: Path) -> None: + text = readme_path.read_text() + readme_html = markdown.markdown(text, extensions=["tables", "fenced_code"]) + html = _DOC.replace("__README_HTML__", readme_html) + out = out_dir / "doc.html" + out.write_text(html) + print(f"Wrote {out}") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def main(): + p = argparse.ArgumentParser( + description="Build HTML site from injection pipeline outputs.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + _here = Path(__file__).parent + p.add_argument("--data-dir", default=str(_here / "data"), metavar="PATH", + help="Directory containing all pipeline outputs (TSVs, PNGs, MDs)") + p.add_argument("--out-dir", default=None, metavar="PATH", + help="Directory where index.html and doc.html are written " + "(default: same as --data-dir)") + p.add_argument("--injection-results", default=None, metavar="PATH", + help="Override path for injection_results.tsv") + p.add_argument("--scatter", default=None, metavar="PATH", + help="Scatter plot filename relative to --data-dir (default: auto-detected)") + p.add_argument("--readme", default=None, metavar="PATH", + help="Path to README.md (default: README.md next to this script)") + args = p.parse_args() + + data_dir = Path(args.data_dir) + out_dir = Path(args.out_dir) if args.out_dir else Path(".") + readme = Path(args.readme) if args.readme else _here / "README.md" + inj = Path(args.injection_results) if args.injection_results else None + scatter = args.scatter + + build_index(out_dir, data_dir, inj_path=inj, scatter_override=scatter) + build_doc(out_dir, readme) + build_test_pages(out_dir, data_dir) + + +if __name__ == "__main__": + main() diff --git a/scripts/injection/data/assignment_summary.md b/scripts/injection/data/assignment_summary.md new file mode 100644 index 0000000..dee15ee --- /dev/null +++ b/scripts/injection/data/assignment_summary.md @@ -0,0 +1,11 @@ +``` + +Assignment summary (successfully assigned) + + UNAMBIGUOUS AMBIGUOUS TOTAL + names meas names meas names meas + ------- --------------------- ------- --------------------- ------- --------------------- +All 403/413 24,956,931/24,979,594 100/100 5,874,012/5,874,012 503/513 30,830,943/30,853,606 +OMOP-mapped 359/367 24,839,646/24,860,173 82/82 5,815,545/5,815,545 441/449 30,655,191/30,675,718 + ------- --------------------- ------- --------------------- ------- --------------------- +``` diff --git a/scripts/injection/data/injection_results.tsv b/scripts/injection/data/injection_results.tsv new file mode 100644 index 0000000..c575ddd --- /dev/null +++ b/scripts/injection/data/injection_results.tsv @@ -0,0 +1,530 @@ +TEST_NAME TYPE SUB_DIST CUTOFF UNIT OUTCOME NOTES TESTS_PASSED UNIT_PREVALENCE PREVALENCE_DICT BIMODAL_STATUS BIMODAL_SEP BIMODAL_BC BIMODAL_DIP_P BIMODAL_OVERLAP SCORE_GLOBAL SCORE_SPLIT SCORE_IMPROVEMENT N_CANDIDATE N_TARGET CAND_DECILES TARG_DECILES KS_STAT KS_MLOGP KS_PASS T_STAT T_MLOGP T_PASS MAD_DIST MAD_THRESHOLD MAD_PASS +b-eryt unambiguous all inf e12/l PASS KS,MAD 99.99 {e12/l:99.99,%:0.01,e9/l:0} NA NA NA NA NA NA NA NA 2702234 8026175 [3.25,3.63,3.9,4.1,4.28,4.44,4.6,4.79,5.04] [3.3,3.7,3.99,4.2,4.35,4.5,4.67,4.83,5.1] 0.05101 300 PASS -81.68 300 FAIL 0.07 1.35 PASS +b-leuk unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100,g/l:0,form:0} NA NA NA NA NA NA NA NA 2610059 7601140 [4.4,5.2,5.8,6.4,7,7.7,8.6,9.7,11.7] [4.3,5.1,5.7,6.3,6.9,7.5,8.3,9.5,11.5] 0.03003 300 PASS 40.72 300 FAIL 0.1 4.8 PASS +b-trom unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100,%:0,form:0} NA NA NA NA NA NA NA NA 2413303 7894206 [150,183,207,227,246,267,291,324,381] [154,187,209,229,247,267,291,321,374] 0.01194 228.8 PASS 15.29 52.07 FAIL 1 156 PASS +p-inr unambiguous all inf inr PASS KS,MAD 99.17 {inr:99.17,ng:0.52,form:0.31} NA NA NA NA NA NA NA NA 1094306 73174 [1.5,1.9,2.1,2.3,2.4,2.6,2.7,3,3.3] [1,1.3,1.9,2.1,2.3,2.4,2.6,2.8,3.2] 0.1414 300 PASS 69.28 300 FAIL 0.1 1.5 PASS +b-hb unambiguous all inf g/l PASS KS,MAD 99.99 {g/l:99.99,e9/l:0.01,form:0} NA NA NA NA NA NA NA NA 895512 10047206 [106,116,124,129,133,137,142,147,154] [99,110,119,125,130,135,140,145,152] 0.07401 300 PASS 177.8 300 FAIL 3 39 PASS +e-mcv unambiguous all inf fl PASS KS,MAD 99.96 {fl:99.96,%:0.03,pg:0} NA NA NA NA NA NA NA NA 863209 10019368 [84.6,87,89,90,91,93,94,96,99] [85,87,89,90,91,93,94,96,99] 0.009422 61 PASS -18.11 72.59 FAIL 0 9 PASS +e-mch unambiguous all inf pg PASS KS,MAD 99.78 {pg:99.78,pg/cell:0.21,fl:0} NA NA NA NA NA NA NA NA 859269 10022306 [27,29,29,30,30,31,31,32,33] [28,29,29,30,30,31,31,32,33] 0.01713 201.5 PASS -5.64 7.768 FAIL 0 3 PASS +pt-gfreepi unambiguous all inf ml/min/173m2 PASS KS,T,MAD 99.11 {ml/min/173m2:99.11,form:0.89} NA NA NA NA NA NA NA NA 752397 1889100 [39,51,59,67,74,80,86,92,101] [38,54,65,73,80,86,91,97,106] 0.09026 300 PASS 0.6265 0.2749 PASS 6 48 PASS +p-k unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol/mol:0,nmol/l:0} NA NA NA NA NA NA NA NA 701228 6736993 [3.5,3.7,3.8,3.9,4,4.1,4.2,4.3,4.5] [3.5,3.7,3.8,3.9,4,4.1,4.2,4.3,4.6] 0.04774 300 PASS 8.691 17.45 FAIL 0 0.9 PASS +b-neut unambiguous all inf e9/l PASS KS,MAD 98.61 {e9/l:98.61,%:1.39} NA NA NA NA NA NA NA NA 678979 1796762 [1.7,2.32,2.81,3.27,3.74,4.29,4.97,5.95,7.77] [1.68,2.28,2.73,3.19,3.63,4.17,4.8,5.74,7.5] 0.02295 225.1 PASS 26.64 155.6 FAIL 0.11 3.87 PASS +p-na unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol/:0,1:0} NA NA NA NA NA NA NA NA 615417 6700235 [134,137,138,139,140,141,142,142,144] [134,136,138,139,140,140,141,142,143] 0.08943 300 PASS 110.5 300 FAIL 0 6 PASS +u-ph unambiguous all inf form PASS KS,MAD 98.78 {form:98.78,ph:1.22} NA NA NA NA NA NA NA NA 614126 18003 [5.5,5.5,6,6,6.5,7,7,7,7.5] [5,5,5,5.5,6,6,6.5,7,7] 0.2368 300 PASS 78.83 300 FAIL 0.5 3 PASS +p-krea unambiguous all inf umol/l PASS KS,MAD 99.98 {umol/l:99.98,umol:0.02,ml/min/173m2:0} NA NA NA NA NA NA NA NA 597169 7087388 [56,63,68,73,79,86,94,106,128] [53,60,65,70,76,82,90,104,135] 0.05594 300 PASS -81.6 300 FAIL 3 45 PASS +p-alat unambiguous all inf u/l PASS KS,MAD 100 {u/l:100,umol/l:0,mmol/l:0} NA NA NA NA NA NA NA NA 484108 4339404 [13,16,18,21,24,27,32,40,56] [13,16,18,21,24,28,33,42,61] 0.01793 121.3 PASS -61.36 300 FAIL 0 24 PASS +e-rdw unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 445075 5838138 [13,13,13,14,14,14,15,15,17] [12.6,13,13,13.5,14,14,15,16,17] 0.04145 300 PASS -18.65 76.9 FAIL 0 3 PASS +fp-gluk unambiguous all inf mmol/l PASS T,MAD 99.96 {mmol/l:99.96,nmol/l:0.04,mmol/:0} NA NA NA NA NA NA NA NA 377861 1527267 [5.2,5.5,5.7,5.9,6.1,6.4,6.7,7.2,8.2] [5,5.3,5.5,5.7,5.8,6.1,6.4,6.9,7.8] 5.211e-06 -0 FAIL -0.9174 0.445 PASS 0.3 1.5 PASS +b-baso unambiguous all inf e9/l PASS KS,MAD 98.99 {e9/l:98.99,%:1.01} NA NA NA NA NA NA NA NA 358577 1025695 [0.01,0.02,0.03,0.03,0.04,0.04,0.05,0.06,0.08] [0,0.02,0.02,0.03,0.04,0.04,0.05,0.07,0.1] 0.06985 300 PASS 17 64.07 FAIL 0 0.06 PASS +b-hba1c unambiguous all inf mmol/mol PASS KS,MAD 99.68 {mmol/mol:99.68,mmol/m:0.17,mmol:0.07} NA NA NA NA NA NA NA NA 356117 1619587 [34,37,38,40,42,44,47,52,62] [33,35,37,38,40,42,44.8,50,60] 0.1172 300 PASS 68.38 300 FAIL 2 15 PASS +p-crp unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100,mg/ml:0,alle:0} NA NA NA NA NA NA NA NA 329485 3909221 [2,4,6,10,15,23,37,58,99] [3,5,8,13,21,34,52,81,133] 0.08114 300 PASS -122 300 FAIL 6 54 PASS +b-erblast unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 314346 313463 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.003567 1.435 PASS 8.538 16.86 FAIL 0 0 PASS +e-mchc unambiguous all inf g/l PASS KS,MAD 100 {g/l:100,e9/l:0,form:0} NA NA NA NA NA NA NA NA 306131 6813807 [314,320,326,330,333,336,339,343,348] [315,321,325,328,331,333,337,340,346] 0.07143 300 PASS 35.87 280.8 FAIL 2 24 PASS +fp-kol-ldl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol/:0,mmoil/l:0} NA NA NA NA NA NA NA NA 300160 1258918 [1.6,2,2.2,2.5,2.8,3.1,3.4,3.78,4.2] [1.5,1.8,2.1,2.38,2.6,2.9,3.2,3.6,4.1] 0.07917 300 PASS 78.61 300 FAIL 0.2 2.1 PASS +fp-kol unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol:0,mmol/:0} NA NA NA NA NA NA NA NA 246075 1160366 [3.4,3.8,4.1,4.5,4.8,5.1,5.4,5.8,6.3] [3.2,3.6,4,4.3,4.6,4.9,5.2,5.6,6.2] 0.08252 300 PASS 72.12 300 FAIL 0.2 2.4 PASS +fp-trigly unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol/:0} NA NA NA NA NA NA NA NA 239400 1104071 [0.7,0.85,1,1.1,1.25,1.41,1.67,1.99,2.55] [0.7,0.82,0.96,1.1,1.21,1.4,1.6,1.89,2.4] 0.03363 193.1 PASS 21.77 104.2 FAIL 0.04 1.17 PASS +fp-kol-hdl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mml/l:0,mmol:0} NA NA NA NA NA NA NA NA 235506 1110652 [0.96,1.1,1.2,1.31,1.42,1.54,1.68,1.85,2.11] [0.95,1.09,1.2,1.3,1.4,1.5,1.63,1.8,2.03] 0.03251 178.1 PASS 30.77 206.9 FAIL 0.02 0.84 PASS +pt-gfre unambiguous all inf ml/min/173m2 PASS KS,MAD 99.63 {ml/min/173m2:99.63,form:0.37,mi/min/173m2:0} NA NA NA NA NA NA NA NA 216785 1096392 [42,54,63,71,77,83,88,93,102] [41,57,67,75,82,87,92,98,106.1] 0.08308 300 PASS -56.47 300 FAIL 5 45 PASS +p-t4-v unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100,nmol/l:0,pmo/l:0} NA NA NA NA NA NA NA NA 203419 904186 [12,13,14,14.9,15.5,16.2,17,18.1,19.9] [12,13,14,14.7,15.2,16,17,18,19.5] 0.0401 231.7 PASS 21.9 105.5 FAIL 0.3 5.7 PASS +p-afos unambiguous all inf u/l PASS KS,MAD 100 {u/l:100,umol/l:0} NA NA NA NA NA NA NA NA 179506 2452551 [47,55,61,67,74,81,91,106,134] [49,57,64,70,77,85,95,111,151] 0.04779 300 PASS -42.81 300 FAIL 3 57 PASS +vb-ph unambiguous all inf ph PASS KS,T,MAD 99.15 {ph:99.15,form:0.64,1:0.21} NA NA NA NA NA NA NA NA 174741 931 [7.3,7.33,7.35,7.37,7.38,7.39,7.41,7.42,7.45] [7.31,7.34,7.36,7.37,7.38,7.39,7.4,7.42,7.44] 0.06548 3.169 PASS -1.378 0.7736 PASS 0 0.09 PASS +b-erybla unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 169159 1041960 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.02442 75.13 PASS 19.22 81.51 FAIL 0 0 PASS +u-eryt unambiguous all inf e6/l PASS KS,MAD 98.22 {e6/l:98.22,u/field:0.92,form:0.43} NA NA NA NA NA NA NA NA 157713 273727 [1,2,4,5,8,13,23,52,193] [2.2,4,6,7.8,10,13,19,30.9,86.7] 0.1553 300 PASS -5.124 6.523 FAIL 2 19.5 PASS +u-leuk unambiguous all inf e6/l PASS KS,MAD 99.11 {e6/l:99.11,u/field:0.6,/sunf:0.27} NA NA NA NA NA NA NA NA 156801 294204 [1,1,2,4,7,13,29,88,402] [0.8,1,2,3,5,8,16.8,46,216] 0.07322 300 PASS -3.965 4.134 FAIL 2 12 PASS +b-la unambiguous all inf mm/h PASS KS,T,MAD 99.95 {mm/h:99.95,mmol:0.03,form:0.01} NA NA NA NA NA NA NA NA 156217 1232795 [2,5,6,8,10,15,21,28,41] [2,4,5,7,9,12,16,25,38] 0.06215 300 PASS 1.287 0.7029 PASS 1 18 PASS +u-lier unambiguous all inf e6/l PASS KS,MAD 100 {e6/l:100,u/field:0} NA NA NA NA NA NA NA NA 155357 195506 [0,0,0,0,0,0,0,1,3] [0,0,0,0,0,0,0.13,0.81,1.22] 0.09197 300 PASS 48.69 300 FAIL 0 0 PASS +u-levyep unambiguous all inf e6/l PASS KS,MAD 99.53 {e6/l:99.53,u/field:0.47} NA NA NA NA NA NA NA NA 135481 81771 [0,0,0,0,0,1,2,4,11] [0,0,0,0,1,1,2,4,9] 0.09894 300 PASS 5.548 7.539 FAIL 1 3 PASS +u-pienep unambiguous all inf e6/l PASS KS,T,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 135163 65516 [0,0,0,0,0,1,1,2,4] [0,0,0,0,0,0.9,1,2,4] 0.05883 132.5 PASS -1.225 0.6567 PASS 0 0 PASS +cb-ph unambiguous all inf ph PASS KS,T,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 104918 9565 [7.35,7.38,7.39,7.41,7.42,7.43,7.44,7.45,7.47] [7.35,7.37,7.39,7.41,7.42,7.43,7.44,7.46,7.48] 0.02779 5.587 PASS -0.9305 0.4533 PASS 0 0.09 PASS +p-gt unambiguous all inf u/l PASS KS,MAD 100 {u/l:100,mg/ml:0,mg/l:0} NA NA NA NA NA NA NA NA 103122 716550 [15,20,26,33,44,58,78,114,200] [15,19,23,29,36,47,65,100,195] 0.07536 300 PASS 5.969 8.621 FAIL 8 57 PASS +u-krea unambiguous all inf mmol/l PASS KS,MAD 98.91 {mmol/l:98.91,umol/l:1.09,g/l:0} NA NA NA NA NA NA NA NA 102588 389195 [3.3,4.4,5.3,6.3,7.4,8.7,10.3,12.8,18.2] [3.1,4.1,4.9,5.8,6.8,7.9,9.3,11.1,14.2] 0.06325 282.1 PASS 71.26 300 FAIL 0.6 7.8 PASS +hdl/kol unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 89962 130585 [21,25,28,30,33,36,39,42,47] [21,24,27,30,32,35,38,41,46] 0.033 50.09 PASS 15.48 53.27 FAIL 1 21 PASS +p-psa unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100,µh/l:0} NA NA NA NA NA NA NA NA 85734 353554 [0.36,0.61,0.89,1.2,1.62,2.2,3.02,4.4,6.9] [0.25,0.52,0.82,1.2,1.74,2.54,3.8,5.9,10.6] 0.06999 293.6 PASS -32.53 231.1 FAIL 0.12 4.08 PASS +fb-leuk unambiguous all inf e9/l PASS KS,T,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 78470 326274 [4.5,5.2,5.7,6.2,6.7,7.3,8,8.9,10.4] [4.5,5.1,5.6,6.1,6.6,7.2,7.9,8.8,10.4] 0.01482 11.77 PASS 0.9066 0.4382 PASS 0.1 4.2 PASS +p-kol-ldl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 76361 340811 [1.3,1.5,1.7,2,2.2,2.5,2.9,3.3,3.8] [1.3,1.5,1.8,2,2.3,2.6,2.9,3.3,3.8] 0.01497 11.84 PASS -8.846 18.04 FAIL 0.1 2.1 PASS +b-tromb unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 75012 323035 [168,193,212,229,246,264,286,314,359] [163,190,210,228,245,263,284,313,361] 0.01738 15.68 PASS 4.206 4.584 FAIL 1 144 PASS +p-ferrit unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 65282 414832 [12,19,28,39,54,77,111,168,282] [14,23,34,48,67,96,142,224,419] 0.06433 202.6 PASS -32.04 224.1 FAIL 13 147 PASS +p-kol unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 63596 235312 [3,3.4,3.7,4,4.3,4.6,4.9,5.3,5.9] [3,3.4,3.7,4,4.3,4.6,5,5.4,5.9] 0.02577 28.58 PASS -10.74 26.15 FAIL 0 2.4 PASS +p-kol-hdl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 61142 221234 [0.97,1.09,1.2,1.3,1.4,1.51,1.64,1.8,2.03] [0.96,1.09,1.2,1.3,1.4,1.51,1.63,1.79,2.02] 0.006651 1.542 PASS 3.266 2.963 FAIL 0 0.84 PASS +p-trigly unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 60090 219531 [0.72,0.88,1,1.12,1.28,1.44,1.66,1.97,2.51] [0.7,0.84,0.97,1.1,1.24,1.4,1.62,1.92,2.49] 0.02559 26.55 PASS 3.328 3.058 FAIL 0.04 1.17 PASS +u-alb unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100,mg/ml:0,mg/mmol:0} NA NA NA NA NA NA NA NA 54491 245848 [3,4,5,7,9.4,14,24.3,45.9,123] [3.7,5,6,9,13,21.5,42,102,337] 0.09064 300 PASS -43.91 300 FAIL 3.6 27 PASS +ab-be unambiguous all inf mmol/l PASS MAD 99.98 {mmol/l:99.98,ml:0.01,%:0} NA NA NA NA NA NA NA NA 52627 328149 [-7.2,-4.9,-3.7,-2.8,-2.2,-1.6,-1.1,-0.7,-0.3] [-4.6,-2.4,-0.8,0.2,1,1.9,2.9,4.2,6.3] 0.5981 300 FAIL -216.4 300 FAIL 3.2 7.5 PASS +p-alb unambiguous all inf g/l PASS KS,MAD 100 {g/l:100,%:0} NA NA NA NA NA NA NA NA 51547 870387 [26,30,33,34.4,36,37,38,39.2,41] [25,29,32,34,35.6,37,38,39.4,41] 0.04602 89.32 PASS 17.96 71.18 FAIL 0.4 10.8 PASS +u-albkre unambiguous all inf mg/mmol PASS KS,T,MAD 100 {mg/mmol:100,g/mol:0} NA NA NA NA NA NA NA NA 51403 242629 [0.3,0.4,0.6,0.8,1.2,1.9,3.4,7,20.5] [0.3,0.5,0.7,1,1.6,2.9,6.2,16.1,60.6] 0.08722 280.5 PASS -1.503 0.8765 PASS 0.4 3.9 PASS +l-lymf(a) unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 50542 588720 [14,19,22,26,28,31,34,38,44] [12,17,21,24,27,30,34,38,44] 0.04828 94.06 PASS 21.39 100.4 FAIL 1 24 PASS +u-gluk-o unambiguous all inf estimate PASS KS,MAD 99.32 {estimate:99.32,form:0.67,A:0.01} NA NA NA NA NA NA NA NA 50039 123942 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.01171 3.946 PASS 4.074 4.334 FAIL 0 0 PASS +l-mono(a) unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 49676 588958 [6,7,8,8,9,10,11,12,13] [6,7,8,8,9,10,11,12,13] 0.01509 8.768 PASS -0.3816 0.1532 PASS 0 6 PASS +l-eos(a) unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 49313 92061 [0,1,2,2,2,3,4,5,6] [0,1,2,2,2,3,4,4,6] 0.0186 9.351 PASS 7.754 14.05 FAIL 0 3 PASS +l-neut(a) unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 48854 590444 [41,47,51,54,58,61,64,68,74] [41,48,52,55,59,62,66,70,77] 0.0498 96.99 PASS -20.59 93.1 FAIL 1 27 PASS +u-nitr-o unambiguous all inf estimate PASS MAD 99.34 {estimate:99.34,form:0.66,A:0} NA NA NA NA NA NA NA NA 48542 124895 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.0008223 -0 FAIL 3.955 4.116 FAIL 0 0 PASS +l-baso(a) unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 48061 93247 [0,0,0,1,1,1,1,1,1] [0,0,1,1,1,1,1,1,1] 0.1053 300 PASS -33.15 238.8 FAIL 0 0 PASS +vb-be unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 46427 126442 [-7.4,-5.3,-4,-3.1,-2.4,-1.8,-1.2,-0.7,-0.3] [-3.4,-0.8,0.3,1,1.7,2.4,3.2,4.2,5.9] 0.7185 300 FAIL -239 300 FAIL 4.1 5.4 PASS +s-b12-tc2 unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100,>35:0} NA NA NA NA NA NA NA NA 46070 176282 [51,65,76,87,97,107,117,130,160] [52,66,76.7,86,95.1,105,114,124,138] 0.07646 185.4 PASS 24.8 134.4 FAIL 1.9 71.7 PASS +p-bil unambiguous all inf umol/l PASS KS,T,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 45706 1372979 [5,6,7,8,9,11,13,17,26] [5,6,7,8,9,11,13,16.4,25] 0.03003 34.39 PASS -1.03 0.5186 PASS 0 9 PASS +u-suhtih unambiguous all inf kg/l PASS KS,MAD 99.91 {kg/l:99.91,ratio:0.09} NA NA NA NA NA NA NA NA 41978 4621 [1.009,1.011,1.013,1.015,1.016,1.018,1.02,1.022,1.025] [1.01,1.01,1.01,1.01,1.015,1.015,1.015,1.02,1.02] 0.2869 300 PASS 19.31 81.92 FAIL 0.001 0.015 PASS +p-probnp unambiguous all inf ng/l PASS KS,MAD 100 {ng/l:100} NA NA NA NA NA NA NA NA 41763 317468 [81,133,211,338,555,907,1400,2220,4148] [81,146,246,412,695,1144,1849,3080,6216] 0.06289 126.6 PASS -36.62 290.5 FAIL 140 1800 PASS +u-alb-o unambiguous all inf estimate PASS T,MAD 99.75 {estimate:99.75,form:0.25,A:0} NA NA NA NA NA NA NA NA 41443 115398 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.0002578 -0 FAIL 0.961 0.4729 PASS 0 0 PASS +u-eryt-o unambiguous all inf estimate PASS KS,MAD 99.32 {estimate:99.32,form:0.68,eru/ul:0} NA NA NA NA NA NA NA NA 40464 103663 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.01896 8.797 PASS 7.44 12.99 FAIL 0 0 PASS +u-leuk-o unambiguous all inf estimate PASS KS,MAD 99.05 {estimate:99.05,form:0.86,u/field:0.05} NA NA NA NA NA NA NA NA 39068 102815 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.04043 39.92 PASS 8.452 16.53 FAIL 0 0 PASS +p-psa-v unambiguous all inf ug/l PASS KS,MAD 99.99 {ug/l:99.99,form:0.01,%:0} NA NA NA NA NA NA NA NA 38748 112760 [0.22,0.34,0.44,0.54,0.65,0.78,0.92,1.11,1.54] [0.23,0.34,0.45,0.56,0.69,0.8,1,1.24,1.7] 0.03829 36.44 PASS -14.73 48.34 FAIL 0.04 0.96 PASS +p-d-25 unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 38104 55698 [47,58,66,74,80,88,96,107,124] [47,57,64,71,77,84,93,103,120] 0.04497 39.47 PASS 12.91 37.36 FAIL 3 54 PASS +s-k/l-s-v unambiguous all inf ratio PASS KS,T,MAD 99.15 {ratio:99.15,1:0.69,mg/l:0.16} NA NA NA NA NA NA NA NA 37831 2440 [0.17,0.77,1.02,1.21,1.4,1.63,2.01,3.43,14.3] [0.12,0.79,0.987,1.19,1.445,1.76,3.09,10,30.9] 0.09989 19.63 PASS 1.874 1.215 PASS 0.045 2.445 PASS +p-uraat unambiguous all inf umol/l PASS KS,MAD 98.74 {umol/l:98.74,mmol/l:1.26} NA NA NA NA NA NA NA NA 37337 226132 [246,288,322,353,383,416,450,492,549] [225,266,298,327,355,385,420,463,526] 0.09345 243.3 PASS 37.11 296.8 FAIL 28 234 PASS +cb-be unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 36614 63821 [-6.7,-4.6,-3.3,-2.4,-1.6,-1,-0.6,-0.2,2] [-2.8,0,0.7,1.3,2,2.8,3.8,5.2,7.7] 0.6474 300 FAIL -155.1 300 FAIL 3.6 5.7 PASS +p-tfr unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 35728 152596 [2.1,2.5,2.9,3.2,3.6,4.1,4.8,5.8,7.5] [0.8,1.3,2.1,2.5,2.9,3.3,3.8,4.6,6.2] 0.2209 300 PASS 57.88 300 FAIL 0.7 3.9 PASS +p-gluk unambiguous all inf mmol/l PASS KS,MAD 99.96 {mmol/l:99.96,mmol:0.04,%:0} NA NA NA NA NA NA NA NA 34995 1187642 [5,5.5,5.8,6,6.4,7,7.6,8.8,11.4] [5.1,5.5,5.8,6.2,6.6,7.1,7.8,8.8,10.8] 0.05953 104.5 PASS 3.159 2.8 FAIL 0.2 3.6 PASS +p-tnt unambiguous all inf ng/l PASS KS,MAD 99.98 {ng/l:99.98,ug/l:0.02} NA NA NA NA NA NA NA NA 32887 400470 [7,9,11,15,18,23,30,42,74] [7,9,12,15,19,25,34,51,105] 0.04291 48.34 PASS -29.16 185.5 FAIL 1 33 PASS +l-monos unambiguous all inf % PASS KS,MAD 99.4 {%:99.4,e9/l:0.6} NA NA NA NA NA NA NA NA 31974 434312 [6,7,8,8,9,10,11,12,13] [5,7,7.4,8,9,10,10.1,11.7,13] 0.03102 24.6 PASS 2.538 1.953 FAIL 0 6 PASS +fs-ph unambiguous all inf ph PASS T,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 30367 986 [7.34,7.36,7.37,7.38,7.39,7.4,7.41,7.42,7.44] [7.38,7.4,7.41,7.42,7.43,7.43,7.44,7.45,7.47] 0.3802 123.9 FAIL 0.9998 0.4983 PASS 0.04 0.06 PASS +ca++/7.40 unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 27423 98719 [1.18,1.2,1.2,1.22,1.23,1.25,1.27,1.3,1.33] [1.14,1.18,1.21,1.22,1.23,1.24,1.26,1.27,1.31] 0.1306 300 PASS 27.76 167.5 FAIL 0 0.12 PASS +ca-ion unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 27272 99169 [1.18,1.2,1.21,1.23,1.24,1.26,1.28,1.3,1.32] [1.14,1.18,1.21,1.23,1.24,1.26,1.27,1.29,1.33] 0.1066 211.3 PASS 3.516 3.358 FAIL 0 0.12 PASS +fe-folaat unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 27167 76563 [313.6,396.2,479,591,763,1023,1309,1617,2032] [354,452,568,727,968,1302,1580,1854,2290] 0.09833 168.5 PASS -31.8 219.1 FAIL 205 1626 PASS +fs-folaat unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 26471 163828 [6.9,8.9,10.9,13.2,16.3,21,27.9,37.2,51] [7.5,9.7,11.9,14.2,17,20.8,25.9,32.9,42.44] 0.05394 57.34 PASS 11.64 30.51 FAIL 0.7 23.1 PASS +b-retik unambiguous all inf e9/l PASS KS,T,MAD 99.94 {e9/l:99.94,%:0.06} NA NA NA NA NA NA NA NA 26123 65705 [36.9,46,53.5,60.7,68,76.3,87.8,103.3,134.1] [37,47,54,61,68,76,86,101,130] 0.02002 6.213 PASS 1.564 0.9285 PASS 0 60 PASS +p-amyl unambiguous all inf u/l PASS KS,MAD 100 {u/l:100,umol/l:0} NA NA NA NA NA NA NA NA 25904 342833 [29,37,43,49,54,60,68,79,100] [26,34,40,46,52,59,68,80,104] 0.04508 42.25 PASS -10.49 25 FAIL 2 51 PASS +p-ca unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 25414 398611 [2.19,2.25,2.28,2.31,2.34,2.37,2.4,2.44,2.51] [2.21,2.27,2.31,2.34,2.37,2.4,2.44,2.47,2.53] 0.1137 268.8 PASS -28.26 172.6 FAIL 0.03 0.24 PASS +s-ca-ion unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol:0} NA NA NA NA NA NA NA NA 25366 460023 [1.15,1.19,1.21,1.22,1.24,1.25,1.27,1.3,1.34] [1.14,1.18,1.2,1.21,1.23,1.24,1.26,1.28,1.31] 0.08483 150.2 PASS 17.13 64.7 FAIL 0.01 0.12 PASS +p-psa-suh unambiguous all inf % PASS KS,MAD 98.61 {%:98.61,form:1.12,ug/l:0.26} NA NA NA NA NA NA NA NA 24327 93796 [0.22,8,12,15,18,20.6,24,28,33.4] [9,12,14,16.8,19,22,25,29,36] 0.1437 300 PASS -34.01 248.8 FAIL 1 21 PASS +l-neut unambiguous all inf % PASS KS,MAD 98.82 {%:98.82,e9/l:1.18,osuus:0} NA NA NA NA NA NA NA NA 24285 378348 [3,5,26,44,51,56,60,65,72] [42,48,52,55.5,59,62,65.8,70,76] 0.2856 300 PASS -95.32 300 FAIL 8 27 PASS +oma-crp unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 21682 1347 [6,8,12,16,22,30,42,61,96] [6,8,10,13.4,19,26,36,53,81] 0.0552 3.073 PASS 3.631 3.535 FAIL 3 36 PASS +p-asat unambiguous all inf u/l PASS KS,MAD 99.95 {u/l:99.95,umol/l:0.05,u/l37:0} NA NA NA NA NA NA NA NA 21421 444746 [18,21,24,26,29,34,40,50,73] [17,19,22,24,27,30,35,44,70] 0.07457 98.55 PASS -6.633 10.48 FAIL 2 24 PASS +pt-glomerulussuodosnopeus,ckd-epi-kaava unambiguous all inf ml/min/173m2 PASS KS,MAD 99.79 {ml/min/173m2:99.79,form:0.21} NA NA NA NA NA NA NA NA 21275 5606 [37,50,60,68,76,82,88,94,102] [38,49,58,65,72,79,84,89,95] 0.08282 26.2 PASS 7.857 14.36 FAIL 4 48 PASS +l-eos unambiguous all inf % PASS KS,MAD 99.56 {%:99.56,e9/l:0.44} NA NA NA NA NA NA NA NA 21090 942601 [0,0.9,1,1.9,2,3,3.2,4.5,6.8] [0,1,1,2,2,3,3.9,4,6] 0.07833 109.8 PASS -5.632 7.745 FAIL 0 3 PASS +l-neutr unambiguous all inf % PASS KS,MAD 99.66 {%:99.66,e9/l:0.34,e9:0} NA NA NA NA NA NA NA NA 20755 86909 [43,49,53,56,59,63,66,70,75] [44,49,53,56,59,62.4,66,70,76] 0.01054 1.318 PASS -1.996 1.338 FAIL 0 24 PASS +p-t4v unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100} NA NA NA NA NA NA NA NA 20621 90249 [12.2,13.4,14.2,14.9,15.6,16.3,17.2,18.3,20] [12.7,13.8,14.6,15.3,16,16.7,17.5,18.5,20] 0.05243 39.81 PASS -10.15 23.44 FAIL 0.4 5.7 PASS +s-b12-vit unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100,u:0,pmol/:0} NA NA NA NA NA NA NA NA 20314 76509 [227,280,325,370,417,476,551,650,835] [232,281,321,361,403,452,513,601,768] 0.05142 36.61 PASS 9.714 21.55 FAIL 14 354 PASS +l-eosin unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 19838 82317 [1,1,2,2,3,3,4,5,7] [1,1,2,2,3,3,4,5,6] 0.01263 1.919 PASS 1.953 1.294 PASS 0 3 PASS +l-lymfos unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 19480 83981 [13,17,21,24,26,29,32,36,41] [13,18,21,24,27,30,33,36,42] 0.01782 4.064 PASS -1.254 0.6779 PASS 1 21 PASS +b-basof unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 19447 4729 [0.02,0.03,0.03,0.04,0.04,0.05,0.06,0.07,0.08] [0.02,0.02,0.03,0.04,0.04,0.05,0.05,0.06,0.08] 0.04398 6.105 PASS 7.268 12.4 FAIL 0 0.06 PASS +b-eosin unambiguous all inf e9/l PASS T,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 19437 12142 [0.04,0.08,0.11,0.14,0.17,0.2,0.25,0.32,0.44] [0.04,0.08,0.11,0.14,0.17,0.21,0.25,0.31,0.44] 0.009323 0.2745 FAIL 0.6328 0.2783 PASS 0 0.27 PASS +l-basof unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 19372 84273 [0,0,1,1,1,1,1,1,1] [0,0,1,1,1,1,1,1,1] 0.02437 7.833 PASS 9.941 22.52 FAIL 0 0 PASS +p-ca-albk unambiguous all inf mmol/l PASS KS,MAD 99.95 {mmol/l:99.95,form:0.05,mmol:0} NA NA NA NA NA NA NA NA 18846 165475 [2.29,2.34,2.37,2.4,2.43,2.46,2.5,2.54,2.61] [2.34,2.39,2.42,2.44,2.47,2.5,2.53,2.57,2.63] 0.1538 300 PASS -33.69 242.1 FAIL 0.04 0.21 PASS +oma-gluk unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 18525 53 [5.4,6.1,6.8,7.5,8.4,9.3,10.5,12.1,14.7] [5.22,5.74,6.1,6.4,6.7,7.12,8,8.6,11.12] 0.2829 3.519 PASS 5.572 6.066 FAIL 1.7 3.3 PASS +u-epit unambiguous all inf e6/l PASS KS,MAD 99.79 {e6/l:99.79,u/field:0.19,u:0.02} NA NA NA NA NA NA NA NA 18155 168750 [0,1,1,1.5,2,3,4,7,14] [0.2,1,1,1.9,2.1,3.2,5,8.1,16.9] 0.04272 25.71 PASS -6.438 9.907 FAIL 0.1 5.1 PASS +l-lymf unambiguous all inf % PASS KS,MAD 99.19 {%:99.19,e9/l:0.81} NA NA NA NA NA NA NA NA 17952 287233 [10,15.82,19,23,27,31,35,40.7,49] [12.3,18,21.2,25,28,31,34,38,44] 0.0618 55.82 PASS 2.945 2.49 FAIL 1 24 PASS +p-ck unambiguous all inf u/l PASS KS,MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 17899 260925 [46,62,75,90,108,133,170,242,409] [43,58,71,86,104,129,169,252,545] 0.03098 13.67 PASS -14.94 49.58 FAIL 4 150 PASS +l-baso unambiguous all inf % PASS KS,T,MAD 99.56 {%:99.56,e9/l:0.43,e/l:0} NA NA NA NA NA NA NA NA 17874 929196 [0,0,0,0.5,1,1,1,1,1] [0,0,0,1,1,1,1,1,1] 0.06496 64.1 PASS -1.288 0.7036 PASS 0 0 PASS +s-ph(akt) unambiguous all inf ph PASS KS,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 16686 97114 [7.35,7.37,7.38,7.39,7.4,7.4,7.41,7.42,7.44] [7.34,7.36,7.38,7.39,7.4,7.41,7.42,7.43,7.45] 0.08186 82.73 PASS -6.569 10.29 FAIL 0 0.09 PASS +s-ca(7.4) unambiguous all inf mmol/l PASS KS,MAD 99.92 {mmol/l:99.92,nmol/l:0.08} NA NA NA NA NA NA NA NA 16482 115493 [1.18,1.2,1.22,1.23,1.24,1.25,1.27,1.28,1.32] [1.12,1.16,1.19,1.21,1.22,1.24,1.25,1.27,1.31] 0.1685 300 PASS 42.01 300 FAIL 0.02 0.12 PASS +b-be unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 15770 19712 [-7.5,-5.5,-4.3,-3.4,-2.6,-2,-1.4,-0.9,-0.4] [0.5,1.1,1.7,2.3,3,3.9,5,6.6,9.2] 0.9739 300 FAIL -193.4 300 FAIL 5.6 6 PASS +s-d-25 unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100,mmol/l:0,nmol/:0} NA NA NA NA NA NA NA NA 15496 380163 [46,55,63,70,76,83,91,102,119] [46,56,63,69,76,82,90,100,116] 0.01766 3.739 PASS 3.216 2.885 FAIL 0 51 PASS +f-calpro unambiguous all inf ug/g PASS KS,MAD 100 {ug/g:100,ug/l:0,<100:0} NA NA NA NA NA NA NA NA 13490 130628 [17,27,39,57,85,129,202,355,794.1] [14,23,35,54,81,128,213,394,911] 0.03985 16.58 PASS -2.582 2.007 FAIL 4 195 PASS +u-osmole unambiguous all inf mosm/kgh2o PASS KS,MAD 100 {mosm/kgh2o:100} NA NA NA NA NA NA NA NA 13152 70655 [364,436,491,542,590,642,700,769,869] [347,415,470,522,570,625,687,762,869] 0.04625 20.33 PASS 7.362 12.72 FAIL 20 411 PASS +psa-v/psa unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 13127 25356 [9.6,12.5,14.9,17.2,19.4,21.8,24.4,27.9,33] [8.4,11,13.3,15.6,17.8,20.2,23,26.6,32.4] 0.07657 43.82 PASS 11.36 29.1 FAIL 1.6 18.3 PASS +fp-transf unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 12905 144990 [1.8,2,2.1,2.25,2.4,2.56,2.7,2.98,3.2] [1.53,1.74,1.9,2.04,2.2,2.31,2.5,2.7,3] 0.1737 300 PASS 40.95 300 FAIL 0.2 1.2 PASS +s-cdt unambiguous all inf % PASS KS,MAD 99.96 {%:99.96,u/l:0.04,mg/l:0} NA NA NA NA NA NA NA NA 12434 89543 [1.2,1.4,1.5,1.6,1.6,1.7,1.9,2.1,2.9] [0.8,1.1,1.3,1.4,1.5,1.6,1.7,1.9,2.3] 0.1676 267.7 PASS 30.97 203.7 FAIL 0.1 0.9 PASS +s-ca-iona unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 12323 382345 [1.13,1.17,1.19,1.21,1.23,1.24,1.26,1.28,1.32] [1.14,1.18,1.2,1.22,1.23,1.25,1.26,1.29,1.32] 0.03902 15.51 PASS -8.946 18.38 FAIL 0 0.12 PASS +e-retik unambiguous all inf % PASS KS,MAD 99.98 {%:99.98,pg:0.01,e9/l:0} NA NA NA NA NA NA NA NA 12082 121560 [0.8,1,1.2,1.3,1.5,1.7,2,2.3,3] [0.9,1.1,1.3,1.5,1.7,1.9,2.2,2.7,3.7] 0.09935 94.16 PASS -17.22 65.1 FAIL 0.2 1.5 PASS +p-fidd unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100,mikrog/ml:0,ug/l:0} NA NA NA NA NA NA NA NA 12060 116811 [0.29,0.3,0.4,0.5,0.6,0.8,1,1.6,3] [0.22,0.3,0.4,0.6,0.7,1,1.44,2.2,4.5] 0.09284 81.71 PASS -14.73 48.09 FAIL 0.1 1.2 PASS +p-mg unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 11982 247648 [0.64,0.7,0.74,0.78,0.81,0.83,0.86,0.9,0.95] [0.64,0.7,0.74,0.77,0.8,0.83,0.85,0.89,0.94] 0.03404 11.21 PASS 2.421 1.81 FAIL 0.01 0.24 PASS +s-ca(akt) unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 11963 86215 [1.18,1.2,1.22,1.23,1.24,1.26,1.27,1.29,1.33] [1.11,1.15,1.18,1.2,1.22,1.23,1.25,1.27,1.31] 0.2276 300 PASS 45.35 300 FAIL 0.02 0.15 PASS +hdl/kol. unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 11551 1944 [23,27,30,33,36,38,41,45,49] [21,24,28,31,33,36,40,44,49] 0.08936 11.28 PASS 6.189 9.153 FAIL 3 24 PASS +fp-fe unambiguous all inf umol/l PASS KS,T,MAD 100 {umol/l:100,µmol/l:0,ug/l:0} NA NA NA NA NA NA NA NA 11441 141841 [5,7,8.3,10,11.9,13.9,16,18.6,22.5] [5,7,8.8,10.3,11.9,13.5,15.5,18,22] 0.0343 10.53 PASS 1.835 1.177 PASS 0 12.9 PASS +fs-ca-ion unambiguous all inf mmol/l PASS KS,T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 10667 11278 [1.19,1.22,1.23,1.24,1.25,1.27,1.28,1.3,1.33] [1.16,1.2,1.21,1.23,1.24,1.26,1.27,1.3,1.35] 0.1108 58.37 PASS 1 0.4985 PASS 0.01 0.12 PASS +b-lymf unambiguous all inf e9/l PASS KS,MAD 98.42 {e9/l:98.42,%:1.58} NA NA NA NA NA NA NA NA 10416 275628 [0.97,1.2,1.41,1.6,1.81,2.02,2.29,2.66,3.315] [0.86,1.14,1.36,1.55,1.74,1.95,2.19,2.5,2.99] 0.04474 17.17 PASS 2.462 1.859 FAIL 0.07 1.59 PASS +b-erytroblastit unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 10388 151903 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.03874 12.39 PASS -16.36 59.39 FAIL 0 0 PASS +u-prot-o unambiguous all inf form PASS MAD 99.89 {form:99.89,mg/dl:0.11} NA NA NA NA NA NA NA NA 9860 936 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,1] 0.04588 1.277 FAIL -3.484 3.287 FAIL 0 0 PASS +s-t4-v unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100,pmol:0} NA NA NA NA NA NA NA NA 9709 242498 [11,12,12,13,13,14,14,15,17] [11,12,12.9,13.1,14,14.5,15.1,16.01,17.5] 0.1618 213.2 PASS -15.97 56 FAIL 1 5.1 PASS +cp-gluk-hy unambiguous all inf mmol/l PASS KS,T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 9537 6395 [5.3,6.5,7.5,8.5,9.5,10.6,11.9,13.4,15.6] [5.4,6.5,7.5,8.5,9.5,10.4,11.6,13.3,15.6] 0.02434 1.677 PASS -1.27 0.6904 PASS 0 7.8 PASS +nu-albkrea unambiguous all inf mg/mmol PASS KS,MAD 100 {mg/mmol:100} NA NA NA NA NA NA NA NA 9489 11439 [0.2,0.4,0.5,0.7,1,1.6,2.8,6,17.9] [0.3,0.5,0.7,0.9,1.3,2.1,3.8,8.2,27.3] 0.09281 38.61 PASS -6.811 11 FAIL 0.3 2.7 PASS +p-iga unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 9343 52529 [1.06,1.4,1.63,1.9,2.1,2.4,2.74,3.2,4.16] [0.74,1.16,1.46,1.73,2.03,2.37,2.77,3.35,4.51] 0.08621 51.01 PASS -6.038 8.799 FAIL 0.07 2.46 PASS +p-b12-vit unambiguous all inf pmol/l PASS KS,MAD 100 {pmol/l:100} NA NA NA NA NA NA NA NA 9340 37066 [194,243,285,326,371,423,487,577,720] [208,258,299,339,381,429,489,575,748] 0.03999 10.07 PASS -6.574 10.3 FAIL 10 351 PASS +p-crp-o unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100,u/l:0} NA NA NA NA NA NA NA NA 9102 29537 [7,9,13,18,24,33,45,65,98] [6,8,11,14,20,28,40,58,91] 0.07448 33.29 PASS 8.707 17.46 FAIL 4 39 PASS +cb-pco2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 9039 107718 [4.2,4.7,5,5,5,5.3,5.7,6,6.7] [4.2,4.5,4.7,4.9,5.1,5.3,5.6,5.9,6.6] 0.1277 118.3 PASS 3.557 3.424 FAIL 0.1 1.5 PASS +fp-pi unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 8841 175027 [0.8,0.9,0.98,1.02,1.1,1.15,1.21,1.32,1.54] [0.78,0.9,0.99,1.07,1.16,1.25,1.38,1.56,1.85] 0.1446 153.3 PASS -25.19 135.2 FAIL 0.06 0.72 PASS +p-inr-vt unambiguous all inf inr FAIL 100 {inr:100} NA NA NA NA NA NA NA NA 8487 1439 [1.3,1.8,2,2.1,2.3,2.4,2.6,2.9,3.3] [1,1,1,1,1.1,1.3,1.9,2.4,2.9] 0.5129 297.5 FAIL 23.72 115.7 FAIL 1.2 0.3 FAIL +fp-ca unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 8418 53236 [2.23,2.29,2.31,2.35,2.38,2.4,2.43,2.49,2.55] [2.19,2.25,2.29,2.33,2.35,2.38,2.42,2.45,2.51] 0.1007 63.87 PASS 18.9 77.73 FAIL 0.03 0.24 PASS +s-pappa unambiguous all inf mu/l PASS KS,MAD 99.6 {mu/l:99.6,form:0.4} NA NA NA NA NA NA NA NA 8226 33727 [255,396,527,664,822,1000,1220,1550,2150] [274,426,564,707,859.8,1045,1280,1622,2232] 0.02933 4.649 PASS -2.662 2.11 FAIL 37.85 1286 PASS +p-t3-v unambiguous all inf pmol/l PASS KS,T,MAD 100 {pmol/l:100} NA NA NA NA NA NA NA NA 8195 73828 [3.5,3.9,4.1,4.3,4.5,4.8,5,5.4,6.2] [3.5,3.8,4.1,4.3,4.6,4.8,5.1,5.5,6.2] 0.0259 4.005 PASS -0.7015 0.3161 PASS 0.1 1.8 PASS +s-iglck-v unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 7877 39042 [9.6,13.9,18.4,23.5,30,40.1,55.8,89.68,220] [7.8,12.1,16.2,21,27.1,35.7,50.3,81.2,172] 0.05025 14.1 PASS 3.239 2.92 FAIL 2.9 49.8 PASS +cb-hco3-st unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 7860 64090 [20.9,22.4,23.9,24.7,25.3,26.1,27.2,28.82,31.2] [21,22.4,23.3,24,25,25.6,26.3,27.5,29.5] 0.1021 63.24 PASS 12.93 37.18 FAIL 0.3 6 PASS +s-iglcl-v unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 7669 38822 [6.5,9.7,12.8,16,19.6,25,32.9,49.8,124] [6.1,9.2,12,15.1,19,23.9,31.8,48.6,118] 0.03176 5.319 PASS -2.897 2.424 FAIL 0.6 31.5 PASS +crp unambiguous all inf mg/l PASS T,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 7205 71 [6,9,12,16,22,30,42,58,90] [6,8,12,16,25,38,58,64,88] 0.1079 0.4437 FAIL -0.6403 0.2806 PASS 3 57 PASS +p-ck-mbm unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 7160 69802 [1.3,1.7,2,2.3,2.6,3.1,3.8,4.9,7.81] [1.1,1.5,1.9,2.2,2.8,3.4,4.5,7.1,18] 0.0901 45.59 PASS -19.06 79.34 FAIL 0.2 4.2 PASS +s-testo unambiguous all inf nmol/l PASS KS,MAD 99.93 {nmol/l:99.93,pmol/l:0.03,form:0.03} NA NA NA NA NA NA NA NA 6954 77552 [2.9,6.5,8.1,9.5,11,13,15.7,19,23.87] [1.5,6.6,9,11,12.8,14.7,17,19.8,24.4] 0.09349 48.27 PASS -2.242 1.603 FAIL 1.8 15.6 PASS +p-pi unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 6629 179614 [0.69,0.81,0.9,0.98,1.05,1.14,1.23,1.37,1.6] [0.74,0.87,0.97,1.05,1.13,1.23,1.35,1.51,1.79] 0.105 61.07 PASS -18.9 77.09 FAIL 0.08 0.72 PASS +u-osmest unambiguous all inf mosm/kgh2o PASS KS,MAD 100 {mosm/kgh2o:100,mo/kg/h2o:0,mo/kgh2o:0} NA NA NA NA NA NA NA NA 6305 89312 [291,363,418,465,515,573,634.2,703,803.4] [262,327,378,425,472,523,584,656,768] 0.08677 38.3 PASS 14.79 48.04 FAIL 43 390 PASS +fp-krea unambiguous all inf umol/l PASS KS,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 6255 759945 [62,70,77,87,96,103,110,121,146] [56,62,68,73,78,84,92,105,140] 0.246 300 PASS 6.138 9.052 FAIL 18 45 PASS +p-tt unambiguous all inf % PASS KS,MAD 100 {%:100,form:0} NA NA NA NA NA NA NA NA 6023 465481 [41,57,67,77,86,94,102,112,126] [50,65,74,82,88,95,102,110,121] 0.08411 36.31 PASS -5.824 8.218 FAIL 2 54 PASS +p-ld unambiguous all inf u/l PASS KS,MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 5895 215599 [163,180,193,207,221,238,258,289,357] [166,184,198,212,226,244,269,310,413] 0.04455 9.609 PASS -10.44 24.61 FAIL 5 126 PASS +p-bil-kj unambiguous all inf umol/l PASS KS,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 5806 124636 [2,3,3,4,5,6,7,9,15] [3,3,4,4,5,6,8,11,26] 0.06147 17.94 PASS -9.79 21.76 FAIL 0 6 PASS +b-leukosyytit unambiguous all inf e9/l PASS MAD 100 {e9/l:100,1:0} NA NA NA NA NA NA NA NA 5646 370214 [4.8,6,7,8.3,8.7,9.2,9.9,10.8,13] [4.4,5.1,5.7,6.2,6.8,7.4,8.2,9.3,11.3] 0.3046 300 FAIL 21.83 100.9 FAIL 1.9 4.8 PASS +p-urea unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 5615 168276 [4.1,5.4,6.8,8.5,10.5,12.7,15.3,18.8,24.7] [4.2,5.5,6.9,8.7,10.9,13.4,16.4,20,25.3] 0.0386 6.745 PASS -3.726 3.708 FAIL 0.4 16.2 PASS +crp-pika unambiguous all inf mg/l PASS KS,T,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 5400 3081 [6,8,11,16,23,33,47,69,103] [7,9,13,18,25,34,47,69,106] 0.05675 5.211 PASS -1.678 1.029 PASS 2 51 PASS +-trfesat unambiguous all inf % PASS KS,MAD 100 {%:100,g/l:0} NA NA NA NA NA NA NA NA 5367 49060 [8,12,16,19,22,26,29,34,41] [9.2,13.8,17,20,23,26.26,30,34.7,42] 0.05622 13.01 PASS -6.034 8.775 FAIL 1 24 PASS +p-amylp unambiguous all inf u/l PASS KS,MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 5279 91217 [13,18,21,24,28,31,35,43,60] [14,20,23,26,30,34,40,51,85] 0.07912 26.89 PASS -8.148 15.35 FAIL 2 30 PASS +s-prot unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 5242 87590 [59,62.5,65,67,68.4,70,71.5,73.9,77] [57,61,63,65,67,69,70.6,73,76.2] 0.09527 38.8 PASS 10.78 26.12 FAIL 1.4 15 PASS +fp-urea unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 5232 124584 [5,6.6,8,9.7,11.4,13.6,16.1,19.88,25] [5,6.9,8.9,11.3,13.9,16.5,19.2,22.4,27.3] 0.1204 63.19 PASS -14.53 46.26 FAIL 2.5 18.9 PASS +14272 unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 5212 79059 [1.5,2,2.5,2.9,3.4,3.9,4.7,5.8,8.1] [1.06,1.72,2.24,2.72,3.24,3.83,4.61,5.76,8.02] 0.07552 23.97 PASS 3.429 3.214 FAIL 0.16 4.38 PASS +pt-glomerulussuodosnopeus,estimoitu,ckd-epi-kaava unambiguous all inf ml/min/173m2 PASS MAD 99.81 {ml/min/173m2:99.81,1:0.19} NA NA NA NA NA NA NA NA 5151 202854 [30,38.2,44,49,53,57,62,69,82] [34,48,59,68,75,82,87,93,101] 0.3791 300 FAIL -57.84 300 FAIL 22 51 PASS +-nt unambiguous all inf mm PASS KS,MAD 100 {mm:100} NA NA NA NA NA NA NA NA 5019 11772 [0.8,0.9,1,1.1,1.2,1.2,1.3,1.5,1.7] [0.9,1,1.1,1.2,1.27,1.34,1.49,1.6,1.8] 0.1319 53.08 PASS -11.64 30.4 FAIL 0.07 0.75 PASS +s-hcgbv/d unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 4990 14513 [22.4,30.3,37.1,44.7,52.7,62.64,74.9,91.5,120] [22.6,31.2,38.7,46.3,55,64.9,77.8,94.1,125.5] 0.02665 1.997 PASS -3.451 3.251 FAIL 2.3 70.5 PASS +s-mkomp-1 unambiguous all inf g/l PASS KS 100 {g/l:100} NA NA NA NA NA NA NA NA 4885 29826 [0,0,0,1.1,2.5,4.2,7.1,11.4,19.1] [0,0,0,0,0,0.3,1.8,5.1,11.2] 0.2583 246.7 PASS 21.27 96.01 FAIL 2.5 0 FAIL +s-li unambiguous all inf mmol/l PASS KS,T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 4651 33555 [0.4,0.5,0.58,0.6,0.7,0.72,0.8,0.9,1] [0.4,0.5,0.54,0.6,0.69,0.7,0.8,0.9,1] 0.02917 2.728 PASS 1.317 0.726 PASS 0.01 0.51 PASS +p-ca-iona unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,%:0,kpa:0} NA NA NA NA NA NA NA NA 4581 404577 [1.1,1.14,1.16,1.17,1.19,1.21,1.23,1.25,1.29] [1.04,1.08,1.11,1.13,1.15,1.17,1.19,1.21,1.25] 0.2532 255.7 PASS 33.65 230.1 FAIL 0.04 0.15 PASS +s-valpr unambiguous all inf umol/l PASS KS,MAD 99.9 {umol/l:99.9,%:0.1} NA NA NA NA NA NA NA NA 4307 31514 [197,257,299.8,340.4,382,423,465,517,588.4] [221,285,332,372,409,446,487,533,601] 0.0818 21.78 PASS -10.07 22.95 FAIL 27 300 PASS +p-ca-ion. unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 4300 356672 [1.11,1.14,1.16,1.18,1.2,1.21,1.23,1.26,1.29] [1.03,1.08,1.11,1.13,1.16,1.18,1.2,1.22,1.25] 0.251 235.6 PASS 41.48 300 FAIL 0.04 0.18 PASS +u-albkrea unambiguous all inf mg/mmol FAIL 99.24 {mg/mmol:99.24,mg/mmol/l:0.76} NA NA NA NA NA NA NA NA 4132 10589 [0.4,0.7,1.1,1.9,3.4,4.9,7.7,14.4,36.99] [0.3,0.4,0.6,0.7,1,1.3,1.8,3,9.12] 0.343 300 FAIL 9.367 20 FAIL 2.4 1.8 FAIL +fs-trigly unambiguous all inf mmol/l PASS KS,MAD 99.99 {mmol/l:99.99,mmol/:0.01,form:0} NA NA NA NA NA NA NA NA 3980 258906 [0.6,0.72,0.83,0.93,1.06,1.2,1.39,1.64,2.11] [0.7,0.84,0.98,1.11,1.27,1.45,1.68,2,2.57] 0.1417 68.43 PASS -20.61 89.3 FAIL 0.21 1.26 PASS +b-crp-vt unambiguous all inf mg/l PASS KS,T,MAD 99.98 {mg/l:99.98,crp:0.02,g/l:0.01} NA NA NA NA NA NA NA NA 3965 16204 [2.64,5.7,8,12,18,26,40,57,89] [6,8,11,14,19,27,38,54,88] 0.1545 66.18 PASS -1.14 0.5947 PASS 1 36 PASS +sy-leuk unambiguous all inf e6/l PASS KS,T,MAD 99.75 {e6/l:99.75,e9/l:0.25} NA NA NA NA NA NA NA NA 3964 11394 [117.3,262.6,500,1123,2804,5405,1.011e+04,1.868e+04,3.801e+04] [100.3,225,510,1400,3320,6503,1.12e+04,1.91e+04,3.72e+04] 0.03253 2.413 PASS -0.3365 0.1328 PASS 516 9654 PASS +b-ghba1cp unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 3927 888 [6,6.3,6.6,6.9,7.2,7.4,7.8,8.2,8.8] [5.9,6.1,6.5,6.8,7.1,7.52,7.9,8.3,9.4] 0.06144 2.095 PASS -3.643 3.545 FAIL 0.1 2.7 PASS +fs-kol-hdl unambiguous all inf mmol/l PASS KS,T,MAD 100 {mmol/l:100,form:0,mmol/:0} NA NA NA NA NA NA NA NA 3887 254480 [1.03,1.16,1.25,1.34,1.43,1.53,1.64,1.77,1.96] [1,1.1,1.2,1.3,1.4,1.5,1.6,1.74,2] 0.07186 16.91 PASS 1.839 1.18 PASS 0.03 0.75 PASS +fs-kol unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mmol/:0,form:0} NA NA NA NA NA NA NA NA 3794 255206 [3.5,4,4.3,4.6,4.9,5.2,5.5,5.8,6.4] [3.7,4.2,4.5,4.8,5,5.3,5.6,5.9,6.4] 0.06004 11.43 PASS -6.382 9.711 FAIL 0.1 2.1 PASS +fs-kol-ldl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,form:0,mmol/:0} NA NA NA NA NA NA NA NA 3772 255211 [1.7,2.2,2.5,2.8,3.1,3.4,3.671,3.996,4.5] [1.9,2.3,2.6,2.9,3.1,3.4,3.7,4,4.5] 0.04679 6.785 PASS -3.555 3.417 FAIL 0 2.1 PASS +p-bnp unambiguous all inf ng/l PASS KS,MAD 99.98 {ng/l:99.98,ng/ml:0.02} NA NA NA NA NA NA NA NA 3770 88507 [42,72,109,144,194,258,352,511.4,899.1] [20,36,58,88,131,195,292,462,895] 0.1578 78.37 PASS 2.555 1.973 FAIL 63 312 PASS +p-igg unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 3736 73214 [6,7.4,8.6,9.5,10.4,11.4,12.6,14.5,17.95] [5,6.6,7.7,8.7,9.6,10.6,11.7,13.3,16.6] 0.1016 31.71 PASS 8.605 16.97 FAIL 0.8 7.8 PASS +cp-glukoosi,ihopistosn,vieritestihoitoyksikössä unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 3716 1349 [5.3,6.2,7.1,8,9.1,10.3,11.9,13.9,16.9] [5.2,6.2,7,7.9,8.9,10.2,11.9,14.2,17.2] 0.0217 0.1369 FAIL 0.5007 0.21 PASS 0.2 8.4 PASS +ab-emäsylimäärä unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 3701 9136 [-9.2,-6.4,-4.8,-3.7,-2.8,-2.1,-1.6,-1.1,-0.5] [-0.8,0.4,1.1,1.7,2.4,3.3,4.5,6.1,8.9] 0.8699 300 FAIL -89.69 300 FAIL 5.2 6 PASS +u-sg unambiguous all inf kg/l PASS KS,MAD 100 {kg/l:100} NA NA NA NA NA NA NA NA 3697 130 [1.005,1.005,1.01,1.01,1.015,1.015,1.02,1.02,1.025] [1.01,1.01,1.01,1.015,1.015,1.017,1.02,1.02,1.025] 0.182 3.385 PASS 2 1.341 FAIL 0 0.015 PASS +vb-emäsylimäärä unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 3632 12064 [-9.1,-6.3,-4.6,-3.5,-2.6,-2,-1.3,-0.8,-0.3] [-2.6,-0.2,0.7,1.3,2.1,2.8,3.6,4.7,6.7] 0.7533 300 FAIL -77.35 300 FAIL 4.7 5.7 PASS +b-ph unambiguous all inf ph PASS KS,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 3554 35592 [7.31,7.34,7.36,7.38,7.39,7.41,7.43,7.44,7.46] [7.35,7.38,7.4,7.41,7.42,7.43,7.44,7.46,7.48] 0.2071 121.3 PASS -23.05 109.9 FAIL 0.03 0.09 PASS +vb-pco2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 3514 187248 [4.523,5,5.2,5.4,5.7,5.9,6.2,6.5,7.1] [4.7,5.1,5.3,5.6,5.8,6,6.3,6.6,7.1] 0.06585 12.72 PASS -5.987 8.629 FAIL 0.1 1.8 PASS +vb-po2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 3497 134116 [3.2,3.8,4.386,4.8,5.2,5.6,6.2,7.4,10] [2.8,3.4,3.9,4.3,4.7,5.1,5.7,6.5,8.3] 0.1189 41.73 PASS 13.92 42.21 FAIL 0.5 3.6 PASS +mb-ph unambiguous all inf ph PASS KS,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 3444 279 [7.29,7.32,7.34,7.36,7.373,7.39,7.401,7.419,7.435] [7.318,7.34,7.36,7.37,7.38,7.4,7.41,7.42,7.44] 0.13 3.535 PASS -3.9 3.936 FAIL 0.007 0.09 PASS +b-crp unambiguous all inf mg/l PASS KS,T,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 3350 2366 [5,7,9,12,17,24,35,54,87] [6,8,9,13,18,25,35,55,90] 0.09378 10.34 PASS -1.146 0.5987 PASS 1 33 PASS +p-gluk-vt unambiguous all inf mmol/l FAIL 98.08 {mmol/l:98.08,1:1.92} NA NA NA NA NA NA NA NA 3313 2048 [5,5.7,6.4,7.2,8.2,9.5,11,12.8,15.9] [4.5,4.7,4.9,5.1,5.4,5.8,6.5,7.56,9.63] 0.3966 177.7 FAIL 20.93 92.33 FAIL 2.8 2.4 FAIL +ab-po2 unambiguous all inf kpa PASS KS,MAD 99.99 {kpa:99.99,mmol/l:0,%:0} NA NA NA NA NA NA NA NA 3269 391556 [6.7,7.7,8.2,8.8,9.4,10,10.9,12,14.2] [8.1,9.1,9.9,10.6,11.4,12.4,13.8,16.2,21.5] 0.2746 215.8 PASS -43.56 300 FAIL 2 7.2 PASS +ab-pco2 unambiguous all inf kpa PASS KS,MAD 99.99 {kpa:99.99,mmol/l:0,%:0} NA NA NA NA NA NA NA NA 3239 392179 [3.9,4.3,4.6,4.8,5,5.3,5.7,6.3,7.4] [4.1,4.5,4.7,5,5.2,5.4,5.6,5.9,6.5] 0.07603 15.87 PASS 3.66 3.592 FAIL 0.2 1.8 PASS +li-leuk unambiguous all inf e6/l PASS KS,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 3193 10896 [0,0,1,1,2,3,4,10,65.6] [0,0,1,1,2,3,5,11,58] 0.03269 2 PASS -2.785 2.27 FAIL 0 6 PASS +li-eryt unambiguous all inf e6/l PASS KS,T,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 3189 11741 [0,0,0,1,3,10,43.6,199.4,1766] [0,0,1,2,4,12,45,232,2250] 0.02869 1.501 PASS 0.6696 0.2983 PASS 1 12 PASS +p-crp-vt unambiguous all inf mg/l PASS KS,T,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 3141 6868 [5,6.9,9,14,20,28,40,59,91] [5,7,10,14,20,28,40,61,94] 0.0366 2.22 PASS -0.7086 0.3201 PASS 0 42 PASS +b-crp-o unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 3125 1516 [7,10,14,19,26,35,47,67,98] [6,8,11,15,21,28,40,59,90] 0.08443 6.057 PASS 3.708 3.672 FAIL 5 42 PASS +s-psa unambiguous all inf ug/l PASS KS,MAD 99.99 {ug/l:99.99,mg/l:0.01,%:0} NA NA NA NA NA NA NA NA 3123 88424 [0.37,0.57,0.73,0.95,1.3,1.7,2.6,4.2,7.5] [0.39,0.57,0.75,0.97,1.24,1.63,2.26,3.3,5.4] 0.05689 8.201 PASS 2.235 1.593 FAIL 0.06 2.22 PASS +p-apcres. unambiguous all inf form PASS KS,T,MAD 100 {form:100} NA NA NA NA NA NA NA NA 3090 64 [2.5,2.7,2.8,2.9,2.9,3,3,3.1,3.2] [2.7,2.8,2.8,2.8,2.9,2.9,2.9,3,3] 0.1794 1.512 PASS -0.7183 0.3234 PASS 0 0.3 PASS +cp-gluk unambiguous all inf mmol/l PASS KS,T,MAD 99.85 {mmol/l:99.85,mmol/:0.15} NA NA NA NA NA NA NA NA 3068 1987 [5.9,6.8,7.8,9,10.25,11.9,13.89,15.96,19.2] [5.1,6.3,7.7,9.1,10.6,12.7,14.4,16.4,19.3] 0.06829 4.613 PASS 0.1958 0.07327 PASS 0.35 12.6 PASS +lieriöt unambiguous all inf e6/l PASS KS,T,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 2938 23942 [0,0,0,0,0,0,0.1,0.3,0.9] [0,0,0,0,0,0.1,0.3,0.42,1] 0.1694 65.4 PASS -0.1685 0.06237 PASS 0 0 PASS +crp(päiv.hoitaja) unambiguous all inf mg/l PASS KS,MAD 99.18 {mg/l:99.18,g/l:0.27,mg/:0.27} NA NA NA NA NA NA NA NA 2927 361 [6,8,10,14,19,27,39.2,56,91.4] [6,7,9,12,15,21,28,47,71] 0.09255 2.122 PASS 3.49 3.279 FAIL 4 27 PASS +u-happamuusaste(kval) unambiguous all inf form PASS T,MAD 100 {form:100} NA NA NA NA NA NA NA NA 2872 32 [5,5.5,5.5,5.5,6,6,6,6.5,7] [5,5.5,5.5,5.5,5.5,6,6,6.5,7] 0.09706 0.04638 FAIL 0.1506 0.05489 PASS 0.5 1.5 PASS +p-cea unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 2813 49240 [1.2,1.7,2,2.2,3,3.1,4,5,6.9] [1.6,2.1,2.4,2.8,3.4,4.3,5.9,10,33] 0.181 76.06 PASS -8.645 17.17 FAIL 0.4 4.5 PASS +happamuusaste,laskimoveri unambiguous all inf ph PASS MAD 100 {ph:100} NA NA NA NA NA NA NA NA 2733 819 [7.32,7.35,7.36,7.38,7.39,7.4,7.41,7.43,7.45] [7.3,7.34,7.36,7.37,7.39,7.4,7.41,7.43,7.45] 0.05006 1.088 FAIL 2.574 1.993 FAIL 0 0.09 PASS +tffesat unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 2726 900 [7.6,10.8,13.7,16.6,19.4,22.3,25.9,30.3,37] [8.19,11.68,15,18.1,21.2,24.34,27.63,31.84,38.41] 0.07481 3.017 PASS -2.62 2.051 FAIL 1.8 23.7 PASS +u-lieriöt unambiguous all inf e6/l PASS MAD 99.54 {e6/l:99.54,u/field:0.46,u/ul:0} NA NA NA NA NA NA NA NA 2719 59778 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0.14,0.42,1] 0.3489 282.7 FAIL -27.96 168.5 FAIL 0 0 PASS +s-gamma unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,%:0.04} NA NA NA NA NA NA NA NA 2700 28537 [5.6,6.68,7.5,8.2,9.2,10.1,11.2,12.7,15.9] [4.5,6.3,7.5,8.5,9.5,10.5,11.7,13.5,17.3] 0.06216 8.004 PASS -2.571 1.992 FAIL 0.3 8.1 PASS +erytroblastit unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 2691 141098 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.04834 5.075 PASS -6.487 9.984 FAIL 0 0 PASS +ab-hco3-st unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 2612 255830 [20,22,23,24,25,26,27,28,30] [21,22,23,24,25,25,26,27,29] 0.05808 7.299 PASS 3.353 3.091 FAIL 0 6 PASS +p-kysc unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 2572 52852 [0.76,0.9,1.013,1.13,1.27,1.44,1.64,1.91,2.359] [0.85,0.96,1.08,1.23,1.42,1.64,1.93,2.35,3.15] 0.1083 24.79 PASS -18.23 69.86 FAIL 0.15 1.41 PASS +s-alfa-2 unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,%:0.04} NA NA NA NA NA NA NA NA 2555 27644 [5.7,6.2,6.5,6.8,7.1,7.5,7.9,8.4,9.4] [5.5,5.9,6.3,6.7,7,7.4,7.8,8.4,9.37] 0.06415 8.084 PASS 4.402 4.955 FAIL 0.1 3 PASS +s-alfa-1 unambiguous all inf g/l PASS MAD 99.96 {g/l:99.96,%:0.04} NA NA NA NA NA NA NA NA 2552 27708 [1.5,1.7,1.9,2.1,2.4,2.6,2.9,3.2,3.8] [2.2,2.4,2.6,2.7,2.9,3,3.2,3.5,4.1] 0.3316 228.8 FAIL -23.7 113 FAIL 0.5 1.2 PASS +s-album unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 2547 25430 [31.4,35,37.3,39,40.3,41.5,43,44.5,46.3] [31,34.3,36.2,37.6,38.7,39.8,40.9,42.1,43.7] 0.1625 53.15 PASS 12.94 36.55 FAIL 1.6 9 PASS +s-beta-2 unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,%:0.04} NA NA NA NA NA NA NA NA 2541 27334 [2.1,2.4,2.6,2.8,3,3.3,3.6,4.1,4.8] [2.3,2.6,2.8,3.1,3.4,3.6,4,4.5,5.5] 0.1285 33.22 PASS -15.11 49.61 FAIL 0.4 2.1 PASS +s-beta-1 unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,%:0.04} NA NA NA NA NA NA NA NA 2540 27387 [3.4,3.7,3.9,4,4.2,4.4,4.6,4.8,5.2] [3.3,3.5,3.7,3.9,4,4.2,4.4,4.6,5] 0.1268 32.31 PASS 5.524 7.448 FAIL 0.2 1.2 PASS +s-norklot unambiguous all inf nmol/l PASS KS,MAD 99.98 {nmol/l:99.98,umol/l:0.02} NA NA NA NA NA NA NA NA 2538 22343 [321,458.4,572,704.8,826,970,1111,1326,1687] [344,489,614,734,863,1000,1166,1390,1713] 0.03663 2.367 PASS -2.018 1.36 FAIL 37 1041 PASS +p-korsol unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100,pmol/l:0} NA NA NA NA NA NA NA NA 2505 51414 [185,261,314,350,394,434,482,539,637.6] [84,204.6,266,312,352,391,434,486,567] 0.1124 26 PASS 13.05 37.1 FAIL 42 330 PASS +s-igg4 unambiguous all inf g/l PASS MAD 99.98 {g/l:99.98,form:0.02} NA NA NA NA NA NA NA NA 2503 9778 [0.119,0.2164,0.3326,0.4408,0.58,0.7958,1.1,1.69,2.93] [0.11,0.209,0.31,0.439,0.59,0.787,1.06,1.56,2.623] 0.02386 0.6934 FAIL 4.122 4.413 FAIL 0.01 1.209 PASS +p-igm unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 2460 38211 [0.4,0.6,0.76,0.94,1,1.2,1.58,2.29,5.028] [0.26,0.4,0.56,0.7,0.88,1.09,1.35,1.8,3.34] 0.1456 42.47 PASS 2.462 1.858 FAIL 0.12 1.44 PASS +l-nlt unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 2422 7143 [16,28,38,45,52,59,65,72,81] [25,38,46,53,59,65,70,76,83] 0.1145 20.38 PASS -11.13 27.64 FAIL 7 45 PASS +s-iga unambiguous all inf g/l PASS KS,MAD 100 {g/l:100,mg/l:0} NA NA NA NA NA NA NA NA 2410 35623 [0.99,1.32,1.55,1.8,2.05,2.33,2.69,3.27,4.331] [1.05,1.34,1.56,1.77,1.99,2.23,2.53,2.93,3.66] 0.05965 6.697 PASS 6.321 9.512 FAIL 0.06 1.8 PASS +s-van unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 2406 34483 [7.3,9,10.5,11.7,13,14.1,15.6,17.8,21.4] [6.9,8.6,10,11.1,12.4,13.7,15,17,19.8] 0.04717 4.062 PASS 5.626 7.692 FAIL 0.6 10.2 PASS +s-cea unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100,<5:0} NA NA NA NA NA NA NA NA 2353 87770 [1.1,1.3,1.6,1.9,2.2,2.7,3.5,4.66,7.3] [1.1,1.4,1.7,2,2.4,3.1,4.1,6.3,17.4] 0.07905 12.18 PASS -16.63 61.01 FAIL 0.2 3.3 PASS +zb-be unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 2350 2420 [-6.1,-4.1,-3.2,-2.6,-2,-1.5,-1.2,-0.7,-0.4] [0.4,0.8,1.2,1.7,2.2,2.9,3.8,4.9,7.4] 1 300 FAIL -70.65 300 FAIL 4.2 4.2 PASS +p-crp-hy unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 2326 6913 [7,10,13,18,24,33,46,66,101.5] [6,8,11,15,21,29,41,59,91] 0.06881 6.886 PASS 4.653 5.47 FAIL 3 42 PASS +fp-trfesat unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 2313 88543 [8,11,14,17,20,24,28,34,41.8] [9,13,16,19,22,25,28,33,40] 0.08891 15.23 PASS -2.173 1.525 FAIL 2 24 PASS +s-k unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,mml/l:0} NA NA NA NA NA NA NA NA 2255 138249 [3.8,3.9,4.1,4.1,4.2,4.3,4.3,4.4,4.6] [3.8,4,4.1,4.2,4.2,4.3,4.4,4.5,4.6] 0.05636 5.841 PASS -4.525 5.198 FAIL 0 0.6 PASS +fp-laktaat unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 2205 299118 [0.9,1,1.1,1.2,1.4,1.6,1.8,2.2,3] [0.5,0.7,0.8,0.9,1,1.2,1.4,1.7,2.3] 0.2916 164.6 PASS 13.23 37.77 FAIL 0.4 1.2 PASS +pf-eryt unambiguous all inf e6/l PASS KS,T,MAD 99.86 {e6/l:99.86,e9/l:0.14} NA NA NA NA NA NA NA NA 2155 3474 [300,700,1121,1900,3200,5600,1.11e+04,2.434e+04,7.96e+04] [300,800,1740,3300,6000,1.04e+04,2e+04,4.594e+04,1.3e+05] 0.1203 16.49 PASS -1.241 0.6679 PASS 2800 1.694e+04 PASS +vb-happamuusaste unambiguous all inf 1 PASS T,MAD 100 {1:100} NA NA NA NA NA NA NA NA 2150 59 [7.31,7.34,7.36,7.37,7.388,7.4,7.41,7.43,7.45] [7.288,7.35,7.38,7.39,7.4,7.41,7.426,7.434,7.452] 0.1619 1.058 FAIL -1.075 0.5427 PASS 0.012 0.09 PASS +pf-leuk unambiguous all inf e6/l PASS T,MAD 99.02 {e6/l:99.02,e9/l:0.98} NA NA NA NA NA NA NA NA 2145 4958 [143.8,262.8,405.6,585,804,1129,1638,2613,4980] [123.7,230,367,548.8,780.5,1120,1674,2547,4805] 0.0346 1.268 FAIL 1.1 0.5666 PASS 23.5 1804 PASS +pt-gfre-md unambiguous all inf ml/min/173m2 PASS KS,MAD 99.94 {ml/min/173m2:99.94,form:0.06} NA NA NA NA NA NA NA NA 2116 26941 [34,44,51,56,60,69,77,85,96] [34,45,52,57,61,72,81,90,101] 0.0529 4.485 PASS -4.037 4.253 FAIL 1 54 PASS +u-lieriö unambiguous all inf e6/l FAIL 100 {e6/l:100} NA NA NA NA NA NA NA NA 2104 5122 [0,0,0,0,0.11,0.11,0.23,0.34,0.81] [0,0,0,0,0,0,0,0.1,1] 0.3671 178.8 FAIL -2.826 2.326 FAIL 0.11 0 FAIL +l-myel unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 2051 13362 [0,0,0,0,0,1,1,2,4] [0,0,0,0,1,1,1,2,4] 0.06706 6.669 PASS -0.8266 0.3888 PASS 1 3 PASS +s-tfr unambiguous all inf mg/l PASS KS,MAD 99.99 {mg/l:99.99,mg:0.01} NA NA NA NA NA NA NA NA 2018 75736 [1.8,2.2,2.6,3.1,3.6,4.2,5.1,6.3,8.1] [1,1.2,1.5,1.9,2.4,2.8,3.3,4.1,5.6] 0.2886 144.8 PASS 20.34 83.32 FAIL 1.2 3.3 PASS +s-trigly unambiguous all inf mmol/l PASS KS,MAD 99.99 {mmol/l:99.99,mmol/:0.01,mmol:0} NA NA NA NA NA NA NA NA 1993 30438 [0.62,0.76,0.87,0.99,1.11,1.27,1.484,1.736,2.19] [0.7,0.8,0.95,1.1,1.2,1.4,1.6,1.9,2.5] 0.09324 13.88 PASS -8.974 18.25 FAIL 0.09 1.2 PASS +b-tacro unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 1993 43350 [4.3,5.1,6,6.8,7.5,8.5,9.8,11,13] [4.5,5.3,6,6.6,7.2,8,8.8,9.9,11.7] 0.09282 14.01 PASS 5.29 6.871 FAIL 0.3 5.4 PASS +s-igg1 unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,form:0.04} NA NA NA NA NA NA NA NA 1991 4826 [4.27,4.96,5.5,5.97,6.55,7.08,7.72,8.66,10.1] [3.7,4.5,5.08,5.6,6.14,6.7,7.31,8.2,9.73] 0.08725 9.061 PASS 5.43 7.219 FAIL 0.41 4.32 PASS +s-igg2 unambiguous all inf g/l PASS KS,T,MAD 99.96 {g/l:99.96,form:0.04} NA NA NA NA NA NA NA NA 1988 4814 [1.42,1.854,2.14,2.41,2.67,3.01,3.35,3.9,4.64] [1.27,1.72,2.08,2.39,2.69,3.06,3.43,4,4.89] 0.04364 2.043 PASS -0.7587 0.3487 PASS 0.02 2.61 PASS +l-nst unambiguous all inf % PASS T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1987 7096 [0,0,0,0,1,1,1,2,5] [0,0,0,0,1,1,1,2,5] 0.009132 0.0002801 FAIL 0.05908 0.02096 PASS 0 3 PASS +s-kol-hdl unambiguous all inf mmol/l PASS KS,MAD 99.99 {mmol/l:99.99,mmol/:0.01,mmol/mol:0} NA NA NA NA NA NA NA NA 1987 33871 [0.99,1.12,1.22,1.31,1.39,1.49,1.6,1.74,1.94] [1,1.1,1.2,1.3,1.4,1.5,1.6,1.79,2] 0.03836 2.11 PASS -4.253 4.66 FAIL 0.01 0.78 PASS +s-dnanab. unambiguous all inf kiu/l PASS MAD 100 {kiu/l:100} NA NA NA NA NA NA NA NA 1975 1572 [0.6,0.8,0.9,1.1,1.3,1.7,2.3,3.2,5] [0.7,0.9,1.3,2.2,6.55,14,22,37,76] 0.4659 171.5 FAIL -18.91 71.35 FAIL 5.25 17.85 PASS +p-luakptt unambiguous all inf ratio PASS KS,MAD 99.45 {ratio:99.45,form:0.55} NA NA NA NA NA NA NA NA 1952 1258 [0.97,1,1.01,1.03,1.04,1.06,1.08,1.11,1.17] [0.98,1,1.02,1.04,1.05,1.07,1.1,1.13,1.2] 0.07757 3.729 PASS -2.819 2.315 FAIL 0.01 0.15 PASS +s-igg3 unambiguous all inf g/l PASS KS,MAD 99.96 {g/l:99.96,form:0.04} NA NA NA NA NA NA NA NA 1951 4826 [0.13,0.17,0.2,0.24,0.27,0.31,0.37,0.45,0.58] [0.13,0.18,0.22,0.26,0.31,0.36,0.44,0.55,0.72] 0.09618 10.91 PASS -7.247 12.32 FAIL 0.04 0.39 PASS +p-fsh unambiguous all inf u/l PASS KS,MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 1935 8372 [3.3,4.6,5.7,6.9,8.4,13,23.74,45,71] [3.41,4.8,5.9,7.2,9.4,15,30,52,73] 0.04243 2.172 PASS -2.927 2.462 FAIL 1 17.7 PASS +p-luakrvv unambiguous all inf ratio PASS KS,MAD 99.45 {ratio:99.45,form:0.55} NA NA NA NA NA NA NA NA 1926 1256 [0.89,0.92,0.94,0.96,0.98,1,1.03,1.06,1.14] [0.89,0.93,0.95,0.97,0.99,1.01,1.04,1.08,1.18] 0.06371 2.404 PASS -2.024 1.366 FAIL 0.01 0.15 PASS +p-prl unambiguous all inf mu/l PASS MAD 99.81 {mu/l:99.81,nmol/l:0.19} NA NA NA NA NA NA NA NA 1914 8542 [150,186,222,261.2,306,364,439.1,550,784] [138.1,185,224,261,306,361,436,572,938.9] 0.03245 1.14 FAIL -5.431 7.242 FAIL 0 372 PASS +l-bas unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1904 7218 [0.2,0.3,0.4,0.4,0.5,0.6,0.7,0.8,1] [0.1,0.2,0.3,0.4,0.4,0.5,0.6,0.7,0.9] 0.1096 15.48 PASS 7.773 13.97 FAIL 0.1 0.6 PASS +cp-crp-hy unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1836 10332 [1.8,3.2,5,7,9,13,19.5,34,61] [4.7,7,10,14,20,29,42,62,95] 0.2423 80.31 PASS -17.14 62.17 FAIL 11 42 PASS +p-haptog unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1821 18301 [0.65,0.9,1.09,1.3,1.5,1.69,1.96,2.26,2.78] [0.41,0.7,0.92,1.12,1.33,1.54,1.8,2.14,2.66] 0.09576 12.94 PASS 7.152 11.94 FAIL 0.17 1.68 PASS +ab-t unambiguous all inf °c PASS KS,MAD 100 {°c:100} NA NA NA NA NA NA NA NA 1813 1617 [36.2,36.5,36.7,36.9,37,37,37.1,37.4,37.8] [36.3,36.5,36.8,37,37,37,37.3,37.5,38] 0.0633 2.698 PASS -2.93 2.467 FAIL 0 1.2 PASS +p-aptt unambiguous all inf s PASS KS,MAD 100 {s:100} NA NA NA NA NA NA NA NA 1806 143489 [24,25,26,27,29,30,32,35,39] [25,26,27,28.5,30,31,33,36,46] 0.09448 13.59 PASS -13.92 41.3 FAIL 1 12 PASS +p-prealb unambiguous all inf g/l PASS KS,MAD 98.99 {g/l:98.99,mg/l:1.01} NA NA NA NA NA NA NA NA 1802 17703 [0.13,0.16,0.19,0.2,0.22,0.24,0.27,0.31,138.8] [0.09,0.13,0.16,0.18,0.2,0.22,0.24,0.26,0.29] 0.1505 32.11 PASS 16.05 53.57 FAIL 0.02 0.15 PASS +s-kol-ldl unambiguous all inf mmol/l PASS KS,MAD 99.99 {mmol/l:99.99,mmool/l:0,mmol/:0} NA NA NA NA NA NA NA NA 1798 30028 [1.6,2.1,2.4,2.7,2.9,3.3,3.6,3.9,4.5] [1.8,2.2,2.4,2.7,2.9,3.1,3.4,3.7,4.1] 0.09459 12.93 PASS 4.63 5.409 FAIL 0 1.8 PASS +cu-alb unambiguous all inf ug/min PASS KS,T,MAD 99.93 {ug/min:99.93,mg:0.07} NA NA NA NA NA NA NA NA 1778 5389 [3,5,8,15,26,47,85,173.2,428.6] [4,6,10,18,29,48.8,84,161,427.4] 0.04503 2.07 PASS 0.05861 0.02079 PASS 3 75 PASS +b-retind unambiguous all inf osuus PASS KS,MAD 100 {osuus:100} NA NA NA NA NA NA NA NA 1744 9157 [0.4,0.5,0.6,0.7,0.8,0.9,1,1.2,1.5] [0.4,0.5,0.7,0.8,0.9,1,1.2,1.5,1.9] 0.1196 17.99 PASS -10.57 24.99 FAIL 0.1 0.9 PASS +cp-gluk-po unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1737 782 [4.7,5.9,7,8.1,9.2,10.6,12.4,14.68,18.3] [4.6,5.82,7.3,8.4,9.4,10.7,12,14.18,17.49] 0.03385 0.2577 FAIL 0.504 0.2116 PASS 0.2 9.75 PASS +f-elast1 unambiguous all inf ug/g PASS KS,MAD 99.93 {ug/g:99.93,âug/g:0.07} NA NA NA NA NA NA NA NA 1732 10975 [61.1,124,185,235,292,348,398.7,448,494.9] [48,95,144.2,197,248,302.4,357,412,470] 0.0885 9.918 PASS 7.52 13.11 FAIL 44 396 PASS +l-prom unambiguous all inf % PASS MAD 100 {%:100} NA NA NA NA NA NA NA NA 1731 9986 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,1] 0.02093 0.276 FAIL -2.235 1.594 FAIL 0 0 PASS +e-erytrosyytit,kokojakauma unambiguous all inf % PASS MAD 100 {%:100} NA NA NA NA NA NA NA NA 1729 117212 [14,15,15,15,16,16,17,18,19] [13,13,13,13,14,14,15,16,17] 0.5122 300 FAIL 37.93 230.7 FAIL 2 3 PASS +s-mkomp-2 unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1703 27016 [0,0,0,0,0,0,0,0,0.3] [0,0,0,0,0,0,0,0,0] 0.0506 3.277 PASS 4.632 5.413 FAIL 0 0 PASS +ly-t-cd4/8 unambiguous all inf ratio PASS T,MAD 100 {ratio:100} NA NA NA NA NA NA NA NA 1701 115 [0.5,0.8,1.1,1.3,1.5,1.8,2.1,2.6,3.6] [0.6,0.8,1,1.26,1.6,1.84,2.08,2.42,3.16] 0.05797 0.07576 FAIL 1.434 0.8134 PASS 0.1 2.1 PASS +l-metam unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1617 7291 [0,0,0,0,0,1,1,1,2] [0,0,0,0,0,1,1,2,3] 0.06055 3.936 PASS -5.99 8.629 FAIL 0 0 PASS +s-crp unambiguous all inf mg/l PASS KS,MAD 99.97 {mg/l:99.97,form:0.03,mg/ml:0} NA NA NA NA NA NA NA NA 1601 153116 [2.1,6,8,11,14,19,28,43,77] [1,2,5,7,10,14,20,32,58] 0.1583 34.43 PASS 7.877 14.22 FAIL 4 24.3 PASS +b-peth unambiguous all inf umol/l PASS KS,MAD 99.99 {umol/l:99.99,umol/:0.01} NA NA NA NA NA NA NA NA 1586 18209 [0.07,0.12,0.18,0.27,0.36,0.5,0.69,0.98,1.52] [0.06,0.09,0.14,0.2,0.29,0.41,0.59,0.88,1.41] 0.0811 8.068 PASS 4.388 4.917 FAIL 0.07 0.63 PASS +l-bla unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1582 9301 [0,0,0,0,0,0,0,0,1] [0,0,0,0,0,0,0,0,0] 0.05241 2.943 PASS 1.476 0.8539 PASS 0 0 PASS +s-igg unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1567 20871 [5.912,7.672,8.67,9.5,10.27,11.1,12,13.61,17.09] [5.59,7.17,8.2,9.1,9.9,10.8,11.8,13.2,16] 0.05565 3.639 PASS 4.038 4.25 FAIL 0.37 6.9 PASS +p-glukpoc unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1562 957 [5.9,6.5,7,7.6,8.3,9.1,9.8,11.1,13.1] [6.06,6.7,7.3,7.8,8.4,9.2,10.2,11.2,12.94] 0.0537 1.205 FAIL -3.181 2.822 FAIL 0.1 5.1 PASS +s-mkomp-3 unambiguous all inf g/l PASS T,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1542 25589 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.003735 -0 FAIL 1.195 0.6341 PASS 0 0 PASS +nu-alb unambiguous all inf ug/min PASS KS,MAD 100 {ug/min:100} NA NA NA NA NA NA NA NA 1512 442 [1,2.2,4,8,20,33,54,100.8,231.8] [3,6.2,8.3,12,15,18,26,72,195.4] 0.1773 9.15 PASS 2.014 1.354 FAIL 5 27 PASS +cu-alb-mi unambiguous all inf ug/min PASS KS,T,MAD 100 {ug/min:100} NA NA NA NA NA NA NA NA 1510 5748 [2,4,8,19,31.05,54,96,164.2,347.7] [2,3,4,6,9.9,17,34.39,79,218] 0.2215 51.18 PASS 1.206 0.6418 PASS 21.15 23.7 PASS +s-korsol unambiguous all inf nmol/l PASS KS,MAD 99.99 {nmol/l:99.99,nmol/:0,umol/l:0} NA NA NA NA NA NA NA NA 1508 43706 [80.7,202.4,273,322,370.5,414,471.9,542.6,663.6] [138,244,308,360,409,460,517,589,699] 0.09016 10.04 PASS -7.142 11.86 FAIL 38.5 408 PASS +b-inrpika unambiguous all inf inr PASS MAD 100 {inr:100} NA NA NA NA NA NA NA NA 1503 1540 [2,2.2,2.4,2.5,2.6,2.8,2.9,3.2,3.5] [2,2.1,2.3,2.4,2.6,2.7,2.9,3.1,3.4] 0.04325 0.9526 FAIL 2.358 1.734 FAIL 0 1.2 PASS +myel unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1491 4482 [0,0,0,0,0,0,1,1,3.5] [0,0,0,0,0,0,1,1,3] 0.0565 2.823 PASS 1.955 1.295 PASS 0 0 PASS +prom unambiguous all inf % PASS MAD 100 {%:100} NA NA NA NA NA NA NA NA 1475 4479 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.02467 0.3016 FAIL 3.061 2.652 FAIL 0 0 PASS +u-epipien unambiguous all inf e6/l PASS KS,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 1465 16238 [0,0.1,0.44,0.9,1.5,2.3,3.4,5.1,8.7] [0.1,0.2,0.4,0.6,0.8,1,1.6,2.5,4.8] 0.2005 47.09 PASS 8.468 16.24 FAIL 0.7 1.8 PASS +happamuusaste,kapillaariveri unambiguous all inf ph PASS KS,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 1457 122 [7.33,7.36,7.38,7.39,7.4,7.42,7.43,7.44,7.46] [7.35,7.38,7.39,7.41,7.42,7.43,7.44,7.45,7.48] 0.1377 1.599 PASS -3.045 2.558 FAIL 0.02 0.09 PASS +bla unambiguous all inf % PASS T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1446 3320 [0,0,0,0,0,0,0,0,1] [0,0,0,0,0,0,0,0,0] 0.03304 0.6665 FAIL -1.082 0.5537 PASS 0 0 PASS +u-leuk-bv unambiguous all inf e6/l PASS KS,T,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 1442 17865 [1,1.6,2.43,3.7,5.95,12.12,34.37,128.6,621] [0.9,1.7,2.7,4.2,7.3,14.04,34.2,115.4,596.2] 0.0376 1.349 PASS -0.1062 0.03837 PASS 1.35 19.2 PASS +fs-sappih unambiguous all inf umol/l PASS KS,T,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 1438 3976 [2,2.5,3,4,4.8,6,6.9,8.2,12.2] [1.6,2,2.4,3,3.7,4.5,5.7,7.6,13.3] 0.1293 15.12 PASS -0.9085 0.4393 PASS 1.1 5.1 PASS +s-igg-sum unambiguous all inf g/l PASS T,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1434 477 [7.15,8.28,9.149,9.91,10.74,11.57,12.7,14.21,17.21] [6.898,8.282,9.118,9.748,10.9,11.91,13.04,14.71,18.14] 0.03981 0.219 FAIL -0.4105 0.1665 PASS 0.16 7.14 PASS +s-mkomp-4 unambiguous all inf g/l PASS T,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1434 25572 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.000743 -0 FAIL -1.039 0.5247 PASS 0 0 PASS +i-statcrea unambiguous all inf umol/l PASS KS,MAD 98.17 {umol/l:98.17,mmol/l:1.83} NA NA NA NA NA NA NA NA 1433 161 [55,65,71,79,88,98,110.4,129,166.6] [54,61,66,72,82,88,95,108,121] 0.1425 2.304 PASS 4.047 4.162 FAIL 6 63 PASS +s-ige unambiguous all inf u/ml PASS T,MAD 99.97 {u/ml:99.97,u/l:0.03} NA NA NA NA NA NA NA NA 1427 34187 [8,14.92,24,35,57.3,93.04,149,250.8,536.4] [8,15,23.7,36,54.7,84,137,248,607] 0.02957 0.7485 FAIL -1.366 0.764 PASS 2.6 134.1 PASS +nlt unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1410 4470 [15,24,35,43,49,54,60,68,76] [25,40,48,53,57.75,62,67,73,80] 0.1787 29.65 PASS -11.72 30.23 FAIL 8.75 38.25 PASS +p-k-vt unambiguous all inf mmol/l PASS T,MAD 98.21 {mmol/l:98.21,1:1.79} NA NA NA NA NA NA NA NA 1404 2199 [3.4,3.6,3.7,3.8,3.9,4,4.1,4.3,4.5] [3.4,3.6,3.8,3.9,4,4,4.2,4.3,4.5] 0.02996 0.3822 FAIL -0.1062 0.03836 PASS 0.1 0.9 PASS +metam unambiguous all inf % PASS KS,T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1398 3264 [0,0,0,0,0,0,0,1,3] [0,0,0,0,0,0,1,1,3] 0.1022 8.638 PASS 0.008348 0.002902 PASS 0 0 PASS +nst unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 1385 3292 [0,0,0,0,0,1,1,2,5] [0,0,0,0,1,1,1,2,5] 0.08924 6.481 PASS -2.057 1.401 FAIL 1 3 PASS +fp-glukoosi unambiguous all inf mmol/l PASS MAD 99.93 {mmol/l:99.93,nmol/l:0.07} NA NA NA NA NA NA NA NA 1356 21503 [6.1,6.2,6.4,6.6,6.8,7,7.4,7.9,9.2] [5.1,5.3,5.5,5.7,5.9,6.1,6.5,7,8] 0.5693 300 FAIL 19.53 75 FAIL 0.9 1.8 PASS +s-igf1sds unambiguous all inf nmol/l PASS MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 1323 85 [-2.4,-1.5,-0.9,-0.6,-0.2,0.3,0.8,1.3,2.2] [0.4,0.6,0.82,0.96,1.2,1.4,1.5,2.3,2.72] 0.5314 20.76 FAIL -9.756 15.71 FAIL 1.4 1.5 PASS +tromboplastiiniaika,inr-tulostus,plasmasta unambiguous all inf inr PASS KS,T,MAD 99.91 {inr:99.91,1:0.09} NA NA NA NA NA NA NA NA 1310 15979 [1,1.1,1.2,1.5,2,2.2,2.4,2.7,3] [1,1,1.1,1.4,1.9,2.2,2.5,2.7,3.1] 0.055 2.898 PASS 0.1715 0.06354 PASS 0.1 2.4 PASS +b-ly/abs unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 1306 797 [1.08,1.32,1.54,1.71,1.89,2.08,2.24,2.53,2.95] [0.916,1.15,1.35,1.53,1.75,1.93,2.14,2.418,3.028] 0.1056 4.535 PASS 2.854 2.359 FAIL 0.14 1.56 PASS +b-mo/abs unambiguous all inf e9/l PASS T,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 1306 797 [0.37,0.43,0.48,0.53,0.57,0.63,0.69,0.77,0.91] [0.36,0.43,0.49,0.54,0.58,0.65,0.71,0.79,0.93] 0.03945 0.3865 FAIL -0.2467 0.09411 PASS 0.01 0.42 PASS +b-ba/abs unambiguous all inf e9/l PASS KS,T,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 1306 797 [0.02,0.02,0.03,0.03,0.03,0.04,0.05,0.05,0.07] [0.02,0.02,0.03,0.03,0.04,0.04,0.05,0.05,0.06] 0.06841 1.737 PASS 1.33 0.7357 PASS 0.01 0.03 PASS +b-hb-vt unambiguous all inf g/l PASS KS,MAD 99.66 {g/l:99.66,hb:0.34} NA NA NA NA NA NA NA NA 1301 2922 [98,111,119,124,129,134,140,146,154] [106,116,122,127,131,135,140,146,154] 0.07122 3.694 PASS -3.499 3.322 FAIL 2 36 PASS +u-epilevy unambiguous all inf e6/l PASS KS,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 1298 14634 [0.1,0.3,0.7,1.3,2.2,3.7,7.2,12.36,25.2] [0,0.1,0.3,0.5,0.9,1.3,2.2,4.3,10.4] 0.2038 43.16 PASS 9.13 18.63 FAIL 1.3 2.4 PASS +u-a1miglo unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1294 344 [7.1,9.5,12.09,15.52,19.5,25.6,33.31,43.44,64.98] [6.03,7.5,9.49,12,16,24.48,37.1,55.08,97.23] 0.1196 3.123 PASS -2.659 2.09 FAIL 3.5 29.7 PASS +emäsylimäärä,laskimoveri unambiguous all inf mmol/l FAIL 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1291 2438 [-7.8,-5.5,-4,-3.2,-2.4,-1.7,-1.2,-0.8,-0.4] [0.5,1.2,1.7,2.3,2.9,3.5,4.3,5.3,6.9] 0.9852 300 FAIL -59.48 300 FAIL 5.3 4.8 FAIL +du-ca unambiguous all inf mmol PASS T,MAD 98.36 {mmol:98.36,mmol/24h:1.59,*sai:0.05} NA NA NA NA NA NA NA NA 1285 12333 [1.7,2.8,3.728,4.54,5.67,6.588,7.682,8.782,10.3] [1.77,2.74,3.61,4.5,5.39,6.3,7.4,8.7,10.76] 0.03778 1.153 FAIL -0.2523 0.09644 PASS 0.28 7.08 PASS +crp(vo:nyhteydessäotettu),tth unambiguous all inf mg/l PASS T,MAD 99.37 {mg/l:99.37,alle:0.63} NA NA NA NA NA NA NA NA 1267 158 [5,5,5,8,8,8,13,22,44] [5,5,5,6,8,9,13.9,18,37.9] 0.09585 0.8513 FAIL 1.094 0.5602 PASS 0 9 PASS +pika-crp unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1257 3592 [6,8.92,12,19,26,35,50,69.8,101.4] [6,8,11,15,21,30,42,61,93] 0.06889 3.564 PASS 3.121 2.738 FAIL 5 42 PASS +cb-hb unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1251 15173 [94,105,112,119,124,130,135,142,152] [98,107,115,122,128,134,140,147,157] 0.08193 6.471 PASS -5.985 8.565 FAIL 4 48 PASS +s-ana unambiguous all inf titre PASS KS,MAD 99.99 {titre:99.99,mmol/mol:0.01} NA NA NA NA NA NA NA NA 1248 16685 [80,160,320,320,320,320,640,640,1280] [80,160,160,320,320,320,400,640,1280] 0.1359 18.43 PASS 2.543 1.955 FAIL 0 480 PASS +cb-po2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 1248 47686 [6.3,7.1,7.9,8.5,9.2,10,10.8,11.7,12.83] [6.3,7,7.6,8.2,8.7,9.3,10,10.8,11.9] 0.1074 11.93 PASS 7.035 11.49 FAIL 0.5 4.5 PASS +p-fibr unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1233 38558 [2,2.3,2.6,2.9,3.1,3.4,3.8,4.3,5.18] [1.7,2.2,2.5,2.8,3.2,3.5,4,4.6,5.6] 0.06094 3.573 PASS -3.062 2.65 FAIL 0.1 2.91 PASS +s-ca-i7.4 unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1232 45578 [1.18,1.21,1.23,1.25,1.26,1.27,1.29,1.3,1.33] [1.17,1.2,1.22,1.24,1.25,1.27,1.28,1.3,1.34] 0.05625 3.015 PASS 3.286 2.982 FAIL 0.01 0.12 PASS +i-statna unambiguous all inf mmol/l PASS T,MAD 98.26 {mmol/l:98.26,mmol/:1.16,umol/l:0.58} NA NA NA NA NA NA NA NA 1231 169 [132,135,137,138,139,140,141,142,143] [134,136,137,139,140,140,141,142,143] 0.0551 0.1344 FAIL -0.9083 0.438 PASS 1 6 PASS +i-statk unambiguous all inf mmol/l PASS T,MAD 98.81 {mmol/l:98.81,umol/l:0.6,mmol/:0.6} NA NA NA NA NA NA NA NA 1230 166 [3.3,3.6,3.7,3.8,3.9,4,4.2,4.3,4.6] [3.3,3.5,3.6,3.8,3.9,4,4.2,4.3,4.5] 0.06251 0.2274 FAIL -0.9169 0.4431 PASS 0 0.9 PASS +p-omagluk unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1212 255 [5.4,5.9,6.4,6.8,7.3,7.9,8.4,9.5,12.1] [5.8,6.2,6.6,6.9,7.3,7.8,8.7,9.6,11.36] 0.07709 0.8125 FAIL 0.3778 0.1514 PASS 0 3.6 PASS +p-gfreepi unambiguous all inf ml/min/173m2 PASS KS,MAD 100 {ml/min/173m2:100} NA NA NA NA NA NA NA NA 1212 6373 [18.72,36.14,45.36,51.5,55.85,59.66,65.24,69.9,76.9] [23.24,37.04,43.5,48.7,53,56.3,59.6,65.8,73.1] 0.1066 9.811 PASS 3.509 3.335 FAIL 2.85 33.6 PASS +li-igg/alb unambiguous all inf osuus PASS KS,MAD 99.76 {osuus:99.76,ratio:0.24} NA NA NA NA NA NA NA NA 1212 823 [0.09,0.1,0.11,0.13,0.14,0.15,0.17,0.2,0.28] [0.09,0.1,0.11,0.12,0.13,0.14,0.15,0.17,0.238] 0.08623 2.899 PASS 2.772 2.249 FAIL 0.01 0.09 PASS +p-kreatiniini unambiguous all inf umol/l PASS MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 1210 116933 [59,98,104,110,117,127,143,166,209] [55,61,66,71,77,83,91,103,130] 0.5727 300 FAIL 20.52 79.93 FAIL 40 45 PASS +p-hcg-tot unambiguous all inf iu/l PASS T,MAD 100 {iu/l:100} NA NA NA NA NA NA NA NA 1197 3737 [4.8,11.8,27.38,66.42,147.2,352,1044,3432,1.564e+04] [4,10.32,28.4,72.58,183.5,425.9,1037,3053,1.542e+04] 0.03315 0.5771 FAIL -0.2025 0.07594 PASS 36.3 540.9 PASS +s-shbg unambiguous all inf nmol/l PASS KS,T,MAD 100 {nmol/l:100,g/l:0} NA NA NA NA NA NA NA NA 1192 28134 [15,20,24,28,33,37,44,52,64] [17,21,25,29,33,38,44,52,65] 0.04081 1.366 PASS -0.7427 0.3393 PASS 0 33 PASS +u-sakka,eryt unambiguous all inf u/field PASS T,MAD 100 {u/field:100} NA NA NA NA NA NA NA NA 1161 85 [0,0,0,0,1,1,2,3,8] [0,1,1,1,1,2,2.8,4,7] 0.3415 8.008 FAIL -0.516 0.2169 PASS 0 3 PASS +u-alb-mi unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1149 6339 [1,3,4,6,8,11,18,40,143] [3,4,5.5,7.576,11,18.48,35.4,84.52,321] 0.1507 19.01 PASS -3.853 3.919 FAIL 3 24 PASS +s-haptog unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 1142 10330 [0.68,0.97,1,1.064,1.345,1.65,2,2.05,2.93] [0.47,0.7,0.9,1.13,1.31,1.54,1.8,2.18,2.7] 0.1397 17.24 PASS 3.316 3.028 FAIL 0.035 1.65 PASS +s-alb unambiguous all inf g/l PASS KS,MAD 99.91 {g/l:99.91,%:0.05,mg/g:0.03} NA NA NA NA NA NA NA NA 1141 23178 [32,35,36.4,37.5,38.6,39.6,40.9,42,44] [34,36.9,38,39,40,41,42,43,45] 0.191 34.48 PASS -9.29 19.17 FAIL 1.4 9 PASS +gluk-pika unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1140 727 [5.5,6.1,6.5,7.1,7.7,8.5,9.4,10.7,12.8] [5,5.5,5.9,6.2,6.6,7.2,7.9,9.08,11.4] 0.1982 15.02 PASS 5.046 6.295 FAIL 1.1 3.6 PASS +crppika unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1122 869 [6,9,13,19.4,27,37,51,74.8,111] [6,7,10,13,18,26,39,56,88.2] 0.126 6.51 PASS 5.477 7.312 FAIL 9 36 PASS +vb-hco3-st unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1122 104777 [18,20,21,23,23,24,25,26,28] [20,21,22,23,24,25,25,26,28] 0.092 7.899 PASS -4.621 5.371 FAIL 1 6 PASS +s-pölyer unambiguous all inf u/ml PASS KS,MAD 99.02 {u/ml:99.02,form:0.79,estimate:0.19} NA NA NA NA NA NA NA NA 1120 7301 [0.03,0.05,0.09,0.216,0.575,1.624,3.573,8.788,22.06] [0.03,0.04,0.05,0.09,0.19,0.54,1.84,5.68,16.4] 0.1304 14.12 PASS 3.206 2.861 FAIL 0.385 0.51 PASS +b-gluk/pi unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 1119 295 [5.3,5.8,6.3,6.7,7.4,8.1,9.2,10.4,12.84] [4.9,5.4,5.8,6.2,6.6,7.2,7.8,9.22,12.54] 0.1551 4.644 PASS 2.703 2.148 FAIL 0.8 3.6 PASS +s-t3-v unambiguous all inf pmol/l PASS KS,MAD 99.99 {pmol/l:99.99,pmol/:0.01} NA NA NA NA NA NA NA NA 1116 18326 [3.5,3.8,4,4.2,4.4,4.6,4.8,5.2,5.8] [3.7,4.1,4.3,4.5,4.7,4.9,5.1,5.4,6.095] 0.1496 20.28 PASS -8.972 18.04 FAIL 0.3 1.5 PASS +s-mpoabg unambiguous all inf iu/ml PASS KS,MAD 98.8 {iu/ml:98.8,u/ml:1.2} NA NA NA NA NA NA NA NA 1105 1315 [4.2,5.6,7.2,9.1,11,16,23,35,57] [3.6,4.8,6.2,7.7,9.7,13,18.8,28.2,49] 0.08196 3.236 PASS 2.755 2.228 FAIL 1.3 16.8 PASS +b-crp-pik unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1059 353 [7,10,14,19,25,34,46,60,97] [9,13,17,23,30,40,51.8,72,105.8] 0.08782 1.486 PASS -2.634 2.061 FAIL 5 54 PASS +b-pvk+tkd,leuk unambiguous all inf e9/l PASS KS,MAD 100 {e9/l:100} NA NA NA NA NA NA NA NA 1051 3390 [4.4,5.1,5.6,6.1,6.7,7.4,8,8.7,10.2] [4.6,5.3,5.9,6.5,7.1,7.7,8.4,9.4,10.9] 0.06786 2.933 PASS -4.69 5.535 FAIL 0.4 4.8 PASS +s-sappih unambiguous all inf umol/l PASS KS,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 1039 9064 [2,3,3,4,4.4,5.1,7,10,18] [2,2.9,3.5,4.4,6,7.6,10.7,17,32.07] 0.1419 16.11 PASS -3.752 3.738 FAIL 1.6 10.5 PASS +fs-c-pept unambiguous all inf nmol/l PASS MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 1029 14049 [0.2316,0.4,0.543,0.66,0.778,0.9108,1.08,1.329,1.705] [0.23,0.4,0.54,0.67,0.79,0.9318,1.13,1.4,1.95] 0.04121 1.126 FAIL -4.433 4.998 FAIL 0.012 1.11 PASS +s-lzm unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 1022 24683 [5.21,7.42,8.73,10,11,12,13.2,15,18.28] [1.8,8,10,11,12,13.2,14.9,16,19] 0.1322 14.68 PASS -2.802 2.287 FAIL 1 9 PASS +u-sakka,leuk unambiguous all inf u/field PASS T,MAD 100 {u/field:100} NA NA NA NA NA NA NA NA 1002 85 [0,0,0,0,0,1,2,5,11.9] [0,0,0,0,0,1,1,2,5] 0.1061 0.4997 FAIL 1.483 0.8506 PASS 0 0 PASS +s-infli unambiguous all inf mg/l PASS KS,MAD 98.47 {mg/l:98.47,ug/l:1.41,âug/ml:0.11} NA NA NA NA NA NA NA NA 998 4322 [2,3.6,5.046,6.2,7.68,8.88,10.92,13.8,18] [2.4,4.2,5.76,7.2,8.76,10.56,12.36,15,19.68] 0.09631 6.274 PASS -5.465 7.272 FAIL 1.08 12.42 PASS +emäsylimäärä,kapillaariveri unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 991 3926 [-8.7,-6.9,-5.1,-4,-3.2,-2.5,-1.8,-1.3,-0.7] [-5.3,-2.5,-0.5,0.6,1.6,2.6,4.1,6,9.2] 0.6661 300 FAIL -40.74 275.6 FAIL 4.8 9.6 PASS +b-hba1c/pika unambiguous all inf mmol/mol PASS KS,MAD 100 {mmol/mol:100} NA NA NA NA NA NA NA NA 980 452 [39,42,45,49,52,55,60,65,74] [39,42,44,46,49.5,52,55,60,67] 0.1206 3.658 PASS 3.882 3.96 FAIL 2.5 19.5 PASS +pt-gfre-cg unambiguous all inf ml/min/173m2 PASS MAD 100 {ml/min/173m2:100} NA NA NA NA NA NA NA NA 967 10063 [40.88,49.9,56.98,62,67,73.96,81.7,90.28,106.2] [66,74,80,84.4,89,94,98,102,108] 0.4095 133.5 FAIL -20.03 75.1 FAIL 22 33 PASS +c-reaktiivinenproteiini,pikatesti,veri unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 949 170 [6.88,10,13,19,28,39,52.6,73,108.2] [6,7,9,10.6,16.5,22.4,36,53.2,95] 0.1756 3.634 PASS 2.61 2.016 FAIL 11.5 31.5 PASS +p-psagen unambiguous all inf ug/l PASS T,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 935 36 [0.017,0.04,0.0772,0.1444,0.27,0.634,2.58,5.7,7.478] [0.043,0.09,0.135,0.21,0.325,1.36,6.91,8.74,11.7] 0.1932 0.8855 FAIL -1.625 0.9464 PASS 0.055 0.9525 PASS +p-lh unambiguous all inf u/l PASS MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 927 4717 [2.76,3.9,5,5.8,6.9,8.1,10.02,15,27] [2.7,4,5,6,7.2,8.7,11,17,31] 0.03709 0.6375 FAIL -1.983 1.323 FAIL 0.3 10.5 PASS +u-kemseul unambiguous all inf form PASS KS,T,MAD 98.04 {form:98.04,nken:0.98,leu/ul:0.98} NA NA NA NA NA NA NA NA 912 100 [0,1.01,1.015,1.02,1.022,5,5.5,6,6.5] [0,1.01,1.013,1.015,1.025,5,5,5.04,6.55] 0.2036 3.029 PASS -1 0.4952 PASS 0.0025 3.075 PASS +e-punasolujenkokojakauma unambiguous all inf % PASS MAD 100 {%:100,1:0} NA NA NA NA NA NA NA NA 897 196955 [15,15,16,16,16,16,17,18,20] [12,13,13,13,14,14,14,15,16] 0.7021 300 FAIL 10.41 23.3 FAIL 2 3 PASS +s-rnpab unambiguous all inf u/ml PASS KS,MAD 99.79 {u/ml:99.79,titre:0.21} NA NA NA NA NA NA NA NA 871 2847 [1,1.2,1.4,1.6,1.9,2,2.6,3.3,6] [1,1.3,1.9,2,2,3,4,7,22] 0.2087 25.21 PASS -8.793 17.61 FAIL 0.1 3 PASS +-s.yht. unambiguous all inf e6 FAIL 100 {e6:100} NA NA NA NA NA NA NA NA 845 660 [1.44e+06,7.92e+06,1.8e+07,3.676e+07,5.8e+07,8.4e+07,1.19e+08,1.575e+08,2.325e+08] [1.89,13.28,34.35,60,84.5,110.7,146.5,212.2,288.8] 0.9278 300 FAIL 23.2 91.76 FAIL 5.8e+07 207.8 FAIL +as-leuk unambiguous all inf e6/l PASS KS,T,MAD 99.9 {e6/l:99.9,e9/l:0.1} NA NA NA NA NA NA NA NA 844 2089 [89.3,136.6,190,260,354,496.2,754.1,1241,3053] [54,91,133,184,255,357,502.8,880,1893] 0.1183 7.059 PASS -1.079 0.5521 PASS 99 540 PASS +vb-o2sat unambiguous all inf % PASS KS,MAD 100 {%:100} NA NA NA NA NA NA NA NA 834 36026 [35.73,44.06,51.1,58.52,64.9,71.7,77.7,84.14,90.97] [41.1,54.1,61.4,66.5,70.7,75.2,80.3,87.8,94.7] 0.1483 15.38 PASS -7.379 12.43 FAIL 5.8 38.4 PASS +nu-albkre unambiguous all inf mg/mmol PASS T,MAD 100 {mg/mmol:100} NA NA NA NA NA NA NA NA 830 438 [0.4,0.5,0.7,0.9,1.2,1.8,2.9,6.42,19] [0.3,0.5,0.6,0.9,1.3,2.02,4.09,8.4,22.59] 0.05622 0.5085 FAIL -1.258 0.6801 PASS 0.1 2.7 PASS +as-eryt unambiguous all inf e6/l PASS KS,T,MAD 99.84 {e6/l:99.84,e9/l:0.16} NA NA NA NA NA NA NA NA 828 1263 [100,213.2,400,642.4,1000,1700,2714,5620,1.76e+04] [44.2,133.8,300,528,960,1412,2500,4560,1.318e+04] 0.09147 3.371 PASS 0.9835 0.4875 PASS 40 2655 PASS +u-bakt unambiguous all inf e6/l PASS MAD 99.54 {e6/l:99.54,estimate:0.36,u/field:0.08} NA NA NA NA NA NA NA NA 821 12886 [0,0,0,0,0,0,0,0,0] [1,2,4,6,13,30,94,551,5237] 0.8832 300 FAIL -28.33 170.4 FAIL 13 36 PASS +s-igm unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 817 10860 [0.32,0.482,0.66,0.824,1,1.2,1.492,2.298,5.16] [0.3,0.47,0.62,0.77,0.93,1.11,1.36,1.72,2.531] 0.08061 4.016 PASS 5.405 7.075 FAIL 0.07 1.32 PASS +b-gluk unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 806 623 [5.1,5.5,5.8,6.2,6.7,7.4,8.4,10.1,13.35] [5,5.4,5.7,6.1,6.5,7.2,8.14,9.4,11.98] 0.05152 0.5296 FAIL 2.202 1.555 FAIL 0.2 3.6 PASS +fs-pi unambiguous all inf mmol/l PASS KS,T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 803 1919 [0.93,0.998,1.05,1.1,1.14,1.19,1.23,1.28,1.36] [0.9,0.99,1.05,1.1,1.14,1.19,1.24,1.31,1.4] 0.0568 1.306 PASS -1.103 0.5685 PASS 0 0.39 PASS +b-crp-pika unambiguous all inf mg/l PASS T,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 798 2088 [6,8,11,15,21,30,43,63,94.3] [6,8,11,15,21,30,44,64,102] 0.04682 0.8151 FAIL -0.7583 0.3483 PASS 0 43.5 PASS +cu-tilav unambiguous all inf ml PASS KS,MAD 100 {ml:100} NA NA NA NA NA NA NA NA 792 2543 [320,430,500,580,667.5,760,870,988,1260] [340,440,550,630,710,800,950,1100,1350] 0.08097 3.169 PASS -4.234 4.617 FAIL 42.5 780 PASS +p-tpoab unambiguous all inf u/ml PASS MAD 99.62 {u/ml:99.62,iu/ml:0.38} NA NA NA NA NA NA NA NA 790 2613 [7,8,10,11,13,16,36,91.4,229] [11,16,19,30,57,97,150,228,337] 0.3897 82.19 FAIL -10.57 24.93 FAIL 44 138 PASS +cu-kesto unambiguous all inf min PASS KS,MAD 100 {min:100} NA NA NA NA NA NA NA NA 785 2606 [399.4,435,460,479.6,498,517.4,540,567,614] [417,447,473,491,510,532,548,575,619] 0.07564 2.725 PASS -3.683 3.621 FAIL 12 150 PASS +p-crppika unambiguous all inf mg/l PASS MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 784 947 [6,8.6,12,17,25,36,51,75.4,118] [7,9,12,18,24,33,45,59.8,90] 0.06372 1.236 FAIL 3.284 2.979 FAIL 1 48 PASS +b-pvk+tkd unambiguous all inf % PASS MAD 98.44 {%:98.44,e9/l:0.87,pg:0.69} NA NA NA NA NA NA NA NA 782 567 [1,1,1,1,1,1,1,1,1] [7.16,8.6,10.9,25.3,32.1,38,46.48,52,59.38] 1 300 FAIL -35.92 147.4 FAIL 31.1 62.7 PASS +fp-nh4-ion unambiguous all inf umol/l PASS KS,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 782 22888 [19,24,30,37,45.5,56,65,79,102] [17,23,27,32,38,45,54,69,93] 0.1278 10.49 PASS 4.248 4.619 FAIL 7.5 48 PASS +s-ferrit unambiguous all inf ug/l PASS MAD 99.98 {ug/l:99.98,umol/l:0.01,μg/l:0} NA NA NA NA NA NA NA NA 771 228321 [6,8,10,15,27,70,145,228,382] [14,22,30,39,49.7,63.4,83.9,117.7,192] 0.3013 61.63 FAIL 4.055 4.258 FAIL 22.7 87.9 PASS +pt-gfreep unambiguous all inf ml/min/173m2 PASS KS,MAD 100 {ml/min/173m2:100} NA NA NA NA NA NA NA NA 770 12674 [48,58,66,72,78,82,87,92.2,100] [43,54,62,68,74,80,85,90,96] 0.07235 3.024 PASS 5.294 6.82 FAIL 4 42 PASS +s-kysc unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 766 5489 [0.895,1.06,1.21,1.34,1.52,1.74,1.94,2.26,2.935] [0.81,0.9,0.98,1.06,1.17,1.32,1.52,1.81,2.33] 0.2412 34.15 PASS 8.276 15.33 FAIL 0.35 0.9 PASS +p-crp-p unambiguous all inf mg/l PASS KS,MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 763 188 [4.34,6,9,13,18,25.2,37,53.6,88.6] [1.2,2.4,4.51,8.38,15,21,30,45.6,67.9] 0.2116 5.68 PASS 2.473 1.857 FAIL 3 39.3 PASS +p-na-vt unambiguous all inf mmol/l PASS KS,MAD 98.62 {mmol/l:98.62,1:1.38} NA NA NA NA NA NA NA NA 755 2849 [132,135,136,138,139,140,141,142,144] [134,137,138,139,140,141,142,143,144] 0.1129 6.354 PASS -4.246 4.627 FAIL 1 6 PASS +s-alat unambiguous all inf u/l PASS KS,MAD 99.92 {u/l:99.92,iu/l:0.08,u/i:0} NA NA NA NA NA NA NA NA 749 323438 [14,17,21,24,27,33.8,40,49,67] [14,17,20,23,26,30,35,43,57] 0.07972 3.85 PASS 3.27 2.949 FAIL 1 27 PASS +s-e2 unambiguous all inf nmol/l PASS KS,MAD 99.99 {nmol/l:99.99,nmol/:0.01} NA NA NA NA NA NA NA NA 749 9705 [0.06,0.08,0.1,0.1206,0.15,0.19,0.2562,0.3482,0.562] [0.07,0.1,0.13,0.16,0.2,0.27,0.3794,0.55,0.99] 0.1326 10.39 PASS -8.29 15.57 FAIL 0.05 0.336 PASS +pd-leuk unambiguous all inf e6/l PASS T,MAD 99.94 {e6/l:99.94,%:0.06} NA NA NA NA NA NA NA NA 745 1688 [9,17,38,84.6,157,368.6,720.8,1786,4461] [10,24,47,79,145.5,280,593.6,1324,3906] 0.049 0.7948 FAIL -0.2935 0.114 PASS 11.5 406.5 PASS +s-cmvavi unambiguous all inf index FAIL 100 {index:100} NA NA NA NA NA NA NA NA 742 119 [0.6,9.76,71,80.64,84,87,89,91,94] [0.086,0.34,0.4858,0.55,0.594,0.624,0.66,0.69,0.73] 0.8288 74.23 FAIL 49.4 236 FAIL 83.41 0.288 FAIL +cu-vm unambiguous all inf ml PASS T,MAD 100 {ml:100} NA NA NA NA NA NA NA NA 733 2761 [283.6,375.4,448,516.6,596,673.2,788.4,938,1165] [298,375,450,524,605,683,783,909,1100] 0.02735 0.1155 FAIL 1.485 0.8602 PASS 9 615 PASS +happamuusaste,valtimoveri unambiguous all inf ph PASS T,MAD 100 {ph:100} NA NA NA NA NA NA NA NA 725 55 [7.31,7.35,7.38,7.4,7.41,7.42,7.43,7.45,7.47] [7.344,7.378,7.39,7.4,7.42,7.42,7.43,7.442,7.466] 0.1057 0.2374 FAIL -1.264 0.6769 PASS 0.01 0.09 PASS +p-shbg unambiguous all inf nmol/l PASS KS,MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 720 790 [17,22,26,31,35,41,47,56,71] [18,23,27,30,34,38,43.3,51,64] 0.07611 1.624 PASS 2.647 2.086 FAIL 1 30 PASS +p-myogl unambiguous all inf ug/l PASS KS,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 715 34478 [34,45.8,60,80,109,145.4,223,363.8,936.8] [35,48,66.81,93,136,212,361,694,1847] 0.101 5.942 PASS -13.18 38.4 FAIL 27 297 PASS +p-c3 unambiguous all inf g/l PASS T,MAD 99.99 {g/l:99.99,form:0.01} NA NA NA NA NA NA NA NA 708 14270 [0.797,0.88,0.96,1.018,1.1,1.15,1.23,1.33,1.48] [0.75,0.87,0.95,1.02,1.1,1.18,1.26,1.35,1.49] 0.04087 0.6904 FAIL 0.04098 0.01443 PASS 0 0.6 PASS +s-psa-suh unambiguous all inf % PASS MAD 99.24 {%:99.24,ug/l:0.75,ug:0.01} NA NA NA NA NA NA NA NA 699 15340 [0.33,7.684,10,11,13,14,15,20,28] [11,14.18,17,19,21,24,26.95,30.83,37] 0.4731 137.3 FAIL -23.09 89.42 FAIL 8 18 PASS +mb-be unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 691 1371 [-5.1,-3.5,-2.7,-2.2,-1.7,-1.3,-1,-0.7,-0.4] [0,0.3,0.6,1,1.4,1.8,2.5,3.2,4.3] 0.9104 300 FAIL -36.05 204.5 FAIL 3.1 3.3 PASS +s-hcgbv unambiguous all inf ug/l PASS T,MAD 99.79 {ug/l:99.79,pmol/l:0.21} NA NA NA NA NA NA NA NA 691 3397 [26.5,34.6,42.5,49.7,57.7,66,79.4,95.3,122] [23,32.8,41.4,49.2,58.1,68.06,80.12,99,128.4] 0.04573 0.7577 FAIL -0.4514 0.1859 PASS 0.4 71.7 PASS +s-gt unambiguous all inf u/l PASS MAD 99.87 {u/l:99.87,iu/l:0.12,u/i:0} NA NA NA NA NA NA NA NA 690 178336 [12,16,19,22,27,31,43,57.2,98.3] [13,16,19,22,27,33,41,55,86] 0.03053 0.2726 FAIL 2.44 1.826 FAIL 0 36 PASS +p-fidd-o unambiguous all inf mg/l PASS KS,MAD 99.64 {mg/l:99.64,form:0.36} NA NA NA NA NA NA NA NA 670 13692 [0.2,0.2,0.3,0.3,0.4,0.5,0.7,0.9,1.6] [0.1,0.2,0.2,0.2,0.3,0.3,0.4,0.6,1] 0.2459 33.78 PASS 7.556 12.93 FAIL 0.1 0.3 PASS +p-c4 unambiguous all inf g/l PASS T,MAD 99.99 {g/l:99.99,form:0.01} NA NA NA NA NA NA NA NA 663 13878 [0.1,0.14,0.16,0.188,0.2,0.23,0.25,0.28,0.32] [0.1,0.13,0.16,0.18,0.2,0.22,0.24,0.27,0.31] 0.03463 0.3724 FAIL 1.638 0.9924 PASS 0 0.18 PASS +vb-be-vt unambiguous all inf mmol/l PASS MAD 99.15 {mmol/l:99.15,1:0.85} NA NA NA NA NA NA NA NA 644 350 [-3.27,-2,-1.2,-0.6,-0.2,0.4,1.2,1.8,3.2] [-2,0,0.4,1,1.25,2,2.7,3.62,5] 0.3721 27.77 FAIL -8.151 14.82 FAIL 1.45 3.75 PASS +s-koivue unambiguous all inf u/ml PASS KS,T,MAD 99.97 {u/ml:99.97,form:0.03} NA NA NA NA NA NA NA NA 642 6644 [0.01,0.102,0.39,1.01,2.11,4.248,8.665,16.28,35.74] [0.01,0.03,0.15,0.52,1.39,3.288,6.98,15,34.47] 0.07874 2.879 PASS 0.1735 0.06434 PASS 0.72 4.14 PASS +s-d-1,25 unambiguous all inf pmol/l PASS T,MAD 100 {pmol/l:100} NA NA NA NA NA NA NA NA 632 695 [55,72,84,94,103,114.6,127,142.8,169.8] [54.4,70,82,89,101,110,124,138,163] 0.04486 0.3021 FAIL 1.387 0.7804 PASS 2 81 PASS +s-afos unambiguous all inf u/l FAIL 99.62 {u/l:99.62,iu/l:0.38,iu/i:0} NA NA NA NA NA NA NA NA 631 62974 [68,104,114,124,133,142,153,179,243] [49,57,63,68,74,81,90,104,130] 0.5984 213.6 FAIL 11.45 27.01 FAIL 59 51 FAIL +u-na unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 627 8849 [27,36,43,52,61,69,78,93,111] [25,32,40,48,57,68,82,99,129] 0.05533 1.274 FAIL -1.844 1.183 PASS 4 75 PASS +hb-pika unambiguous all inf g/l PASS KS,T,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 619 1803 [95,111.6,120.4,126,130,135,140,146,155] [98,110,116,122,128,132,138,144,153] 0.07493 1.974 PASS 0.9116 0.4411 PASS 2 42 PASS +s-psa-v/t unambiguous all inf % PASS T,MAD 100 {%:100} NA NA NA NA NA NA NA NA 617 13330 [9,12,14.98,17,20,23,26,29.88,36] [10,13,15,18,20,23,26,30,37] 0.0252 0.07657 FAIL -0.9926 0.4931 PASS 0 21 PASS +s-ace unambiguous all inf u/l PASS MAD 100 {u/l:100} NA NA NA NA NA NA NA NA 610 2199 [22,31,35,39,43,47,52,58,66] [20,28,34,38,43,48,54,61,75] 0.05903 1.165 FAIL -2.455 1.847 FAIL 0 39 PASS +s-dnaab unambiguous all inf iu/ml PASS KS,MAD 100 {iu/ml:100} NA NA NA NA NA NA NA NA 609 733 [12,14,18,22,27,35,46,58,79] [11,14,18,24,31,40,56,73,109] 0.08283 1.714 PASS -4.837 5.826 FAIL 4 54 PASS +s-testovl unambiguous all inf pmol/l PASS T,MAD 100 {pmol/l:100} NA NA NA NA NA NA NA NA 607 14452 [73.8,125.2,151,174,193,215,235.2,276,344.6] [80,127,152,173,195,217,243,281,360] 0.02711 0.1111 FAIL -0.4877 0.2035 PASS 2 177 PASS +p-pc unambiguous all inf % PASS T,MAD 99.83 {%:99.83,form:0.17} NA NA NA NA NA NA NA NA 599 7136 [85.8,98,105.4,111,117,123,131,140,156] [82,96,103,110,117,123,130,139,152] 0.03729 0.382 FAIL 0.7975 0.3712 PASS 0 51 PASS +s-timotee unambiguous all inf u/ml PASS KS,T,MAD 99.9 {u/ml:99.9,form:0.08,umol/l:0.02} NA NA NA NA NA NA NA NA 594 5945 [0.02,0.14,0.378,0.73,1.15,1.938,3.472,6.654,17.45] [0.01,0.04,0.16,0.37,0.72,1.5,3.04,6.072,15] 0.1067 5.081 PASS 1.611 0.9684 PASS 0.43 2.13 PASS +p-hco3 unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 592 267727 [20.21,22.02,23.5,25,25.9,27.1,28.47,30.68,34.2] [20,22,23,24,25,26,27,28,30] 0.1674 14.23 PASS 7.046 11.29 FAIL 0.9 6 PASS +b-pco2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 580 34914 [4.2,4.5,4.7,4.9,5.1,5.4,5.7,6.2,6.9] [4.2,4.5,4.7,4.9,5.1,5.3,5.6,5.9,6.6] 0.06258 1.66 PASS 2.451 1.838 FAIL 0 1.5 PASS +b-po2 unambiguous all inf kpa PASS KS,MAD 100 {kpa:100} NA NA NA NA NA NA NA NA 579 35207 [6.18,6.8,7.2,7.6,8,8.4,8.9,9.5,10.3] [6.2,7,7.6,8,8.5,8.9,9.5,10.2,11.3] 0.1205 6.94 PASS -6.349 9.371 FAIL 0.5 3.6 PASS +crpvieri unambiguous all inf mg/l PASS MAD 100 {mg/l:100} NA NA NA NA NA NA NA NA 575 4875 [1.3,1.78,2.4,3.16,4.3,5.6,7.38,9.3,28.6] [4,6,10,14,20,28,42,62,94] 0.5386 138.3 FAIL -20.37 76.12 FAIL 15.7 45 PASS +fp-kolesteroli unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 574 14065 [5.1,5.2,5.3,5.5,5.6,5.8,6,6.2,6.6] [3,3.4,3.7,4,4.3,4.6,4.9,5.3,5.9] 0.6939 264.8 FAIL 44.52 205.2 FAIL 1.3 2.4 PASS +i-statcl unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 574 46 [96,99,100,101,102,103,104,105,107] [97,99,99.5,101,103,104,105,105,106.5] 0.06454 0.004833 FAIL -0.9852 0.485 PASS 1 6 PASS +i-statglu unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 573 45 [5.3,5.64,5.96,6.28,6.6,6.9,7.5,8.4,10] [4.9,5.38,5.62,5.9,6.1,6.84,7.1,8.52,10.58] 0.1443 0.4984 FAIL 1.46 0.83 PASS 0.5 2.7 PASS +b-co unambiguous all inf ug/l PASS KS,T,MAD 100 {ug/l:100} NA NA NA NA NA NA NA NA 571 3759 [1,1.2,1.4,1.9,2.4,3.1,4.1,5.6,8.5] [0.6,0.8,1,1.3,1.8,2.656,4.1,6.5,11] 0.2505 27.12 PASS -1.361 0.76 PASS 0.6 3.6 PASS +p-osmol unambiguous all inf mosm/kgh2o PASS KS,MAD 100 {mosm/kgh2o:100} NA NA NA NA NA NA NA NA 568 493 [251,261,268,277,283,289,293,297,306] [265.2,278.4,287,291.8,296,299.2,305,316,334] 0.2712 16.86 PASS -10.64 24.36 FAIL 13 39 PASS +b-laktevt unambiguous all inf mmol/l PASS T,MAD 99.03 {mmol/l:99.03,1:0.97} NA NA NA NA NA NA NA NA 567 408 [0.726,0.872,1,1.15,1.33,1.54,1.74,2.112,2.844] [0.74,0.85,1,1.168,1.345,1.534,1.77,2.2,2.895] 0.03316 0.02394 FAIL -0.2293 0.08689 PASS 0.015 1.425 PASS +pt-gfrekys unambiguous all inf ml/min/173m2 PASS MAD 100 {ml/min/173m2:100} NA NA NA NA NA NA NA NA 567 6725 [22,32,39,48,55,62,70,78,93] [12,16,20,25,32,38,47,59,78] 0.3228 48.19 FAIL 15.24 44.48 FAIL 23 48 PASS +u-hyalier unambiguous all inf e6/l PASS KS,MAD 99.14 {e6/l:99.14,u/field:0.86} NA NA NA NA NA NA NA NA 565 11469 [0,0,0,0,0,0,0,0,0.3] [0,0,0,0,0,0.1,0.1,0.3,0.6] 0.2489 29.13 PASS -2.641 2.076 FAIL 0 0 PASS +b-cr unambiguous all inf ug/l PASS KS,T,MAD 99.91 {ug/l:99.91,umol/l:0.09} NA NA NA NA NA NA NA NA 564 3296 [1,1.2,1.3,1.6,1.9,2.2,2.81,3.4,4.9] [0.7,1,1.2,1.4,1.7,2.1,2.7,3.7,5.6] 0.1733 12.39 PASS -0.5174 0.2182 PASS 0.2 2.4 PASS +s-smab unambiguous all inf u/ml PASS KS,MAD 99.82 {u/ml:99.82,form:0.18} NA NA NA NA NA NA NA NA 560 1653 [1,1,1.2,1.4,1.7,2,2,2.82,4] [1,1,1,1.5,2,2,2.2,3,10] 0.1243 5.37 PASS -2.97 2.519 FAIL 0.3 3 PASS +p-alaniiniaminotransferaasi unambiguous all inf u/l FAIL 100 {u/l:100} NA NA NA NA NA NA NA NA 560 56147 [38,41,47,52,57,63,71,84,118] [13,16,18,21,23,27,31,39,54] 0.7524 300 FAIL 9.624 19.68 FAIL 34 24 FAIL +inrpika unambiguous all inf inr FAIL 100 {inr:100} NA NA NA NA NA NA NA NA 558 227 [1.8,1.9,2.1,2.2,2.3,2.5,2.6,2.8,3.1] [1,1,1,1,1.1,1.1,1.1,1.2,1.74] 0.8452 119.5 FAIL 3.253 2.917 FAIL 1.2 0.3 FAIL +s-antitry unambiguous all inf g/l PASS KS,MAD 100 {g/l:100} NA NA NA NA NA NA NA NA 549 6546 [1,1.15,1.294,1.3,1.4,1.5,1.6,1.8,2] [1.11,1.23,1.3,1.4,1.45,1.5,1.6,1.73,1.97] 0.09968 4.108 PASS -2.698 2.145 FAIL 0.05 0.57 PASS +u-muulier unambiguous all inf e6/l PASS KS,MAD 100 {e6/l:100} NA NA NA NA NA NA NA NA 541 11537 [0,0,0,0,0,0,0,0,0.13] [0,0,0,0,0,0,0.13,0.14,0.41] 0.2081 19.39 PASS -4.755 5.692 FAIL 0 0 PASS +fp-c-pept unambiguous all inf nmol/l PASS KS,T,MAD 100 {nmol/l:100} NA NA NA NA NA NA NA NA 531 3064 [0.25,0.39,0.52,0.67,0.83,1,1.3,1.5,2] [0.293,0.46,0.58,0.7,0.83,0.98,1.13,1.38,1.8] 0.07173 1.746 PASS 0.8511 0.4034 PASS 0 1.05 PASS +b-fosfatidyylietanoli unambiguous all inf umol/l PASS KS,MAD 100 {umol/l:100} NA NA NA NA NA NA NA NA 530 4781 [0.08,0.13,0.197,0.3,0.435,0.6,0.82,1.22,1.95] [0.06,0.1,0.15,0.22,0.3,0.44,0.64,0.94,1.59] 0.1098 4.744 PASS 4.028 4.199 FAIL 0.135 0.66 PASS +s-anaab unambiguous all inf titre PASS KS,T,MAD 100 {titre:100} NA NA NA NA NA NA NA NA 512 701 [80,80,80,80,320,320,320,1280,1280] [80,80,80,320,320,320,320,640,1280] 0.1873 8.83 PASS 0.3732 0.1493 PASS 0 720 PASS +fs-fe unambiguous all inf umol/l PASS KS,MAD 99.99 {umol/l:99.99,ug/l:0,mmol/l:0} NA NA NA NA NA NA NA NA 511 41742 [4.9,6.9,8.6,10,12,13.4,15,18,23.1] [5,7.2,9,11,13,15,17,20,24] 0.09457 3.655 PASS -2.078 1.418 FAIL 1 15 PASS +cb-emäsylimäärä unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 510 10576 [-7.8,-5.7,-4.03,-3.1,-2.5,-1.8,-1.3,-0.7,-0.2] [-4.7,-2.3,-0.8,0.1,0.9,1.8,2.8,4.3,6.8] 0.5633 145.6 FAIL -22.2 78.76 FAIL 3.4 7.5 PASS +p-reninm unambiguous all inf mu/l PASS T,MAD 99.93 {mu/l:99.93,µiu/ml:0.07} NA NA NA NA NA NA NA NA 510 7339 [3,5.1,8,14,19.7,27.94,42.71,84.44,191.3] [3,5.9,9.8,15,21,31.7,48.76,84,184.3] 0.05585 1.009 FAIL 0.8063 0.3763 PASS 1.3 50.7 PASS +li-igg unambiguous all inf mg/l PASS MAD 99.07 {mg/l:99.07,g/l:0.93} NA NA NA NA NA NA NA NA 510 3921 [0.02,0.026,0.035,0.054,0.136,18,26.62,36.44,51.46] [14,18,22,26,30.3,36,44,55,80] 0.5196 112.3 FAIL -8.79 16.94 FAIL 30.16 36.9 PASS +li-gluk unambiguous all inf mmol/l PASS T,MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 509 13371 [3,3.1,3.2,3.4,3.5,3.7,4,4.1,4.82] [2.9,3.1,3.3,3.4,3.5,3.7,3.8,4.1,4.8] 0.05553 1.028 FAIL 0.382 0.1533 PASS 0 1.2 PASS +ca++/ph7.4 unambiguous all inf mmol/l PASS MAD 100 {mmol/l:100} NA NA NA NA NA NA NA NA 507 18482 [1.08,1.12,1.13,1.14,1.15,1.16,1.21,1.26,1.34] [1.11,1.15,1.18,1.2,1.22,1.23,1.25,1.27,1.31] 0.3842 65.24 FAIL -7.092 11.37 FAIL 0.07 0.15 PASS +fs-krea unambiguous all inf umol/l PASS KS,MAD 99.99 {umol/l:99.99,umol:0.01,mmol/l:0} NA NA NA NA NA NA NA NA 504 277757 [63,66,70,73,76,80,84,89,96.7] [57,62,66,69,73,77,81,87,95] 0.1243 6.511 PASS 4.432 4.942 FAIL 3 30 PASS +p-cl unambiguous all inf mmol/l PASS KS,MAD 100 {mmol/l:100,%:0,kpa:0} NA NA NA NA NA NA NA NA 503 441201 [96.2,100,102,104,105,106,107,108,110] [98,101,103,104,105,107,108,109,111] 0.07716 2.32 PASS -3.598 3.453 FAIL 0 9 PASS +b-hkr ambiguous low 4.309 osuus PASS split_by_bimodal at 4.309 | DIP:0,BL:0.0% KS,MAD 46.72 {%:53.16,osuus:46.72,fl:0.08} bimodal 4.309 0.9841 0 4.767e-56 0.1094 0.04121 0.6232 1558908 4240703 [0.32,0.35,0.37,0.386,0.4,0.41,0.42,0.44,0.46] [0.31,0.34,0.36,0.38,0.39,0.41,0.42,0.44,0.46] 0.03919 300 PASS 10.5 25.06 FAIL 0.01 0.12 PASS +b-hkr ambiguous high inf % PASS split_by_bimodal at 4.309 | DIP:0,BL:0.0% KS,MAD 53.16 {%:53.16,osuus:46.72,fl:0.08} bimodal 4.309 0.9841 0 4.767e-56 0.1094 0.04121 0.6232 191555 4824770 [32,34,37,38,39,41,42,43,45] [30,33,36,38,39,41,42,43,45] 0.05766 300 PASS 53.68 300 FAIL 0 12 PASS +p-tt-inr ambiguous all inf inr PASS NO_SPLIT KS,MAD 93.35 {inr:93.35,form:6.65,1:0.01} skipped NA NA NA NA 0.1171 0.21 -0.7933 1026823 339780 [1,1.1,1.2,1.6,2,2.2,2.5,2.7,3.1] [1,1,1.1,1.2,1.5,2,2.3,2.6,3] 0.1171 300 PASS 97.96 300 FAIL 0.5 1.5 PASS +s-ph ambiguous all inf 1 PASS NO_SPLIT KS,MAD 83.33 {1:83.33,ph:16.67} skipped NA NA NA NA 0.1458 0.1458 -1.236e-05 396270 140 [7.34,7.36,7.37,7.38,7.39,7.41,7.42,7.43,7.45] [7.36,7.38,7.39,7.4,7.405,7.42,7.42,7.43,7.45] 0.1458 2.334 PASS -2.552 1.929 FAIL 0.015 0.045 PASS +b-eos ambiguous all inf e9/l PASS NO_SPLIT KS,MAD 97.68 {e9/l:97.68,%:2.3,e6/l:0.02} skipped NA NA NA NA 0.06737 0.1114 -0.6536 382440 1092967 [0.01,0.04,0.08,0.11,0.14,0.18,0.22,0.29,0.42] [0.02,0.07,0.1,0.12,0.15,0.19,0.23,0.3,0.4] 0.06737 300 PASS 2.498 1.903 FAIL 0.01 0.24 PASS +ab-ph ambiguous all inf ph PASS NO_SPLIT KS,MAD 72.83 {ph:72.83,form:26.24,mmol/l:0.44} skipped NA NA NA NA 0.1419 0.4037 -1.845 380651 2975 [7.33,7.36,7.39,7.4,7.42,7.43,7.45,7.46,7.49] [7.37,7.39,7.41,7.42,7.43,7.44,7.45,7.47,7.48] 0.1419 51.61 PASS -14.52 45.54 FAIL 0.01 0.09 PASS +b-monos ambiguous all inf e9/l PASS NO_SPLIT KS,MAD 97.82 {e9/l:97.82,%:2.18} skipped NA NA NA NA 0.05309 0.4982 -8.385 373740 965697 [0.34,0.42,0.47,0.53,0.58,0.65,0.72,0.82,0.98] [0.32,0.4,0.46,0.5,0.57,0.61,0.7,0.78,0.92] 0.05309 300 PASS 41.14 300 FAIL 0.01 0.45 PASS +p-tsh ambiguous all inf mu/l PASS NO_SPLIT KS,MAD 97.24 {mu/l:97.24,miu/l:2.41,mlu/l:0.35} skipped NA NA NA NA 0.03165 0.1393 -3.401 344154 1315114 [0.48,0.93,1.26,1.56,1.89,2.2,2.62,3.27,4.4] [0.53,0.92,1.21,1.5,1.8,2.1,2.51,3.1,4.2] 0.03165 237.1 PASS -24.22 128.8 FAIL 0.09 2.43 PASS +b-ly ambiguous all inf e9/l PASS NO_SPLIT KS,T,MAD 97.41 {e9/l:97.41,%:2.59} skipped NA NA NA NA 0.05066 0.4815 -8.504 335323 784182 [0.8,1.07,1.28,1.48,1.67,1.89,2.13,2.44,2.97] [0.9,1.16,1.38,1.56,1.74,1.94,2.18,2.48,2.94] 0.05066 300 PASS -1.136 0.592 PASS 0.07 1.56 PASS +u-suhti ambiguous all inf g/ml PASS NO_SPLIT KS,MAD 6.83 {kg/l:91.82,g/ml:6.83,ratio:1.26} skipped NA NA NA NA 0.08946 0.09035 -0.01002 261486 3080 [1.01,1.013,1.015,1.015,1.018,1.02,1.02,1.024,1.026] [1.01,1.015,1.015,1.015,1.02,1.02,1.02,1.025,1.025] 0.08946 20.92 PASS 5.787 8.143 FAIL 0.002 0.015 PASS +u-keto-o ambiguous all inf l PASS NO_SPLIT KS,MAD 4.7 {estimate:94.61,l:4.7,form:0.69} skipped NA NA NA NA 0.00291 0.00291 -2.615e-05 49568 6640 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.03553 6.131 PASS -11.24 28.42 FAIL 0 0 PASS +u-ph-o ambiguous all inf form PASS NO_SPLIT KS,MAD 47.78 {N:52.17,form:47.78,70:0.03} skipped NA NA NA NA 0.1243 0.2773 -1.23 48516 1901 [5,5.5,6,6,6,6.5,7,7,7.5] [5,5.5,5.5,6,6,6,6.5,7,7] 0.1243 24.37 PASS 12.75 35.19 FAIL 0 1.5 PASS +b-ghb-a1c ambiguous all inf % PASS NO_SPLIT KS,MAD 95.93 {%:95.93,mmol/mol:4.07} skipped NA NA NA NA 0.1436 0.132 0.0803 39036 48557 [5.2,5.5,5.6,5.8,6.1,6.3,6.7,7.4,8.5] [5.2,5.4,5.5,5.7,5.8,6,6.3,6.8,7.8] 0.1436 300 PASS 28 170.3 FAIL 0.3 1.2 PASS +p-tromboplastiiniaika,inr-tulostus ambiguous all inf inr PASS NO_SPLIT KS,MAD 57.5 {inr:57.5,1:40,form:2.5} skipped NA NA NA NA 0.1638 0.2432 -0.4845 38382 92 [1.1,1.4,1.9,2.1,2.3,2.5,2.7,2.9,3.3] [1.8,2.1,2.2,2.3,2.5,2.66,2.7,2.9,3.1] 0.1638 1.899 PASS -2.477 1.821 FAIL 0.2 0.9 PASS +mono ambiguous low 2.627 e9/l PASS split_by_bimodal at 2.627 | DIP:0,BL:0.0% KS,MAD 15.13 {%:84.87,e9/l:15.13} bimodal 2.627 0.7429 0 0.03335 0.4638 0.1053 0.7729 17003 7454 [0.39,0.45,0.5,0.57,0.62,0.7,0.78,0.88,1] [0.36,0.42,0.47,0.52,0.57,0.62,0.68,0.74,0.86] 0.148 98.76 PASS 22.25 107.5 FAIL 0.05 0.39 PASS +mono ambiguous high inf % PASS split_by_bimodal at 2.627 | DIP:0,BL:0.0% KS,MAD 84.87 {%:84.87,e9/l:15.13} bimodal 2.627 0.7429 0 0.03335 0.4638 0.1053 0.7729 13637 41798 [6,7,8,8,9,10,11,12,14] [5.1,7,7.7,8,9,10,10.4,12,13.9] 0.05219 24.05 PASS 9.37 20.1 FAIL 0 6 PASS +baso ambiguous all inf e9/l PASS NO_SPLIT MAD 13.73 {%:86.27,e9/l:13.73} skipped NA NA NA NA 0.3044 0.2677 0.1206 30199 8002 [0,0.02,0.03,0.04,0.05,0.08,1,1,1] [0.02,0.02,0.03,0.04,0.04,0.05,0.06,0.07,0.08] 0.3413 300 FAIL 103 300 FAIL 0.01 0.06 PASS +lymf ambiguous low 7.03 e9/l PASS split_by_bimodal at 7.03 | DIP:0,BL:0.2% KS,MAD 13.98 {%:86.02,e9/l:13.98} bimodal 7.03 0.7018 0 0.1962 0.4628 0.06494 0.8597 12928 7619 [0.95,1.16,1.35,1.54,1.75,1.96,2.19,2.51,3.04] [1.06,1.34,1.51,1.68,1.84,2.03,2.23,2.5,2.92] 0.09616 38.31 PASS -2.645 2.087 FAIL 0.09 1.35 PASS +lymf ambiguous high inf % PASS split_by_bimodal at 7.03 | DIP:0,BL:0.2% KS,MAD 86.02 {%:86.02,e9/l:13.98} bimodal 7.03 0.7018 0 0.1962 0.4628 0.06494 0.8597 13769 46895 [14,18,22,25,28,31,34,38,45] [13,18,22,25,28,31,34,37,43] 0.03562 11.45 PASS 11.49 29.71 FAIL 0 24 PASS +s-tsh ambiguous all inf miu/l PASS NO_SPLIT KS,MAD 27.73 {mu/l:72.2,miu/l:27.73,u/l:0.04} skipped NA NA NA NA 0.05581 0.1729 -2.098 26229 95397 [0.41,0.863,1.17,1.42,1.7,2.04,2.46,3.1,4.41] [0.57,0.91,1.16,1.38,1.62,1.89,2.23,2.71,3.6] 0.05581 55.42 PASS 7.877 14.46 FAIL 0.08 2.01 PASS +b-lymfos ambiguous all inf e9/l PASS NO_SPLIT KS,T,MAD 96.27 {e9/l:96.27,%:3.73} skipped NA NA NA NA 0.09359 0.4998 -4.34 19817 4466 [0.89,1.12,1.3,1.48,1.65,1.83,2.05,2.34,2.83] [1.02,1.29,1.46,1.63,1.81,2,2.22,2.53,3.01] 0.09359 27.51 PASS -0.4248 0.1733 PASS 0.16 1.44 PASS +b-neutr ambiguous all inf e9/l PASS NO_SPLIT KS,MAD 76.51 {e9/l:76.51,%:23.49} skipped NA NA NA NA 0.07016 0.5006 -6.136 19426 7723 [2.07,2.6,3.02,3.43,3.85,4.32,4.9,5.73,7.24] [1.9,2.34,2.756,3.158,3.59,4.07,4.67,5.41,6.87] 0.07016 23.37 PASS 4.47 5.104 FAIL 0.26 3.57 PASS +b-hb-a1c ambiguous all inf % PASS NO_SPLIT KS,MAD 85.06 {%:85.06,mmol/mol:14.85,%/mmol:0.06} skipped NA NA NA NA 0.05016 0.3337 -5.652 18914 43586 [5.4,5.5,5.6,5.8,6,6.2,6.5,7.2,8.1] [5.3,5.4,5.6,5.8,5.9,6.1,6.4,6.9,7.7] 0.05016 28.55 PASS 7.633 13.63 FAIL 0.1 1.5 PASS +u-suhteellinentiheys ambiguous all inf 1 PASS NO_SPLIT KS,T,MAD 78.44 {1:78.44,kg/l:21.56} skipped NA NA NA NA NA NA NA 17548 262 [1.01,1.012,1.015,1.015,1.017,1.02,1.02,1.023,1.025] [1.01,1.015,1.015,1.015,1.02,1.02,1.02,1.025,1.025] 0.1136 2.631 PASS -0.06127 0.02174 PASS 0.003 0.015 PASS +eos ambiguous all inf % PASS NO_SPLIT KS,MAD 91.96 {%:91.96,e9/l:8.04} skipped NA NA NA NA 0.03345 0.4656 -12.92 14491 49739 [0,1,2,2,3,3,4,5,7] [0,1,2,2,3,3,4,5,6] 0.03345 10.61 PASS 5.809 8.197 FAIL 0 6 PASS +s-ttgaba ambiguous all inf u/ml PASS NO_SPLIT KS,MAD 91.82 {u/ml:91.82,eliau/ml:7.73,form:0.44} skipped NA NA NA NA 0.01981 0.1299 -5.556 13768 32224 [0.2,0.3,0.4,0.5,0.6,0.7,0.8,1.1,1.9] [0.2,0.3,0.4,0.5,0.6,0.7,0.8,1.1,1.9] 0.01981 2.992 PASS -4.377 4.917 FAIL 0 0.9 PASS +neut ambiguous all inf % PASS NO_SPLIT KS,MAD 93.42 {%:93.42,e9/l:6.58} skipped NA NA NA NA 0.03737 0.4535 -11.14 12259 61798 [42,48,52,55,58,61,64,68,74] [43.1,49,52.8,56,59,62,65,69.4,75.4] 0.03737 12.12 PASS -8.523 16.78 FAIL 1 24 PASS +fp-pth ambiguous all inf ng/l PASS NO_SPLIT KS,MAD 80.05 {ng/l:80.05,pmol/l:19.95} skipped NA NA NA NA 0.2661 0.2357 0.1139 11194 122007 [7,15,36,50,63,77,92,116,169] [35,51,66,84,106,135,179,253,408] 0.2661 300 PASS -67.71 300 FAIL 43 177 PASS +b-erytrosyytit,tilavuusosuus ambiguous all inf osuus PASS NO_SPLIT KS,T,MAD 77.47 {osuus:77.47,%:22.53,1:0} skipped NA NA NA NA 0.1015 0.1017 -0.0023 10999 292478 [0.34,0.37,0.38,0.4,0.41,0.42,0.43,0.44,0.46] [0.31,0.34,0.37,0.38,0.4,0.41,0.42,0.44,0.46] 0.1015 94.72 PASS -0.7254 0.3295 PASS 0.01 0.12 PASS +p-antifxa ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 56.03 {iu/ml:56.03,u/ml:43.91,umol/l:0.06} skipped NA NA NA NA 0.04087 0.2483 -5.077 10859 13789 [0.13,0.17,0.21,0.26,0.31,0.38,0.45,0.54,0.7] [0.13,0.16,0.21,0.25,0.3,0.36,0.42,0.51,0.68] 0.04087 8.526 PASS 4.094 4.371 FAIL 0.01 0.39 PASS +fs-trfesat ambiguous all inf osuus PASS NO_SPLIT MAD 18.85 {%:80.03,osuus:18.85,1:1.13} skipped NA NA NA NA 0.03586 0.0439 -0.2241 10835 451 [0.09,0.13,0.16,0.19,0.22,0.25,0.29,0.34,0.42] [0.08,0.12,0.16,0.19,0.23,0.26,0.28,0.33,0.4] 0.03586 0.2073 FAIL 8.469 16.55 FAIL 0.01 0.27 PASS +u-happamuusaste ambiguous low 6.204 1 PASS split_by_bimodal_cautious at 6.204 | DIP:0,BL:13.2% MAD 91.97 {1:91.97,5170:7.63,form:0.4} bimodal_cautious 6.204 0.1646 0 13.16 0.395 0.5763 -0.4589 2901 229 [5,5.5,5.5,5.5,6,6,6,6,6] [5.5,6,6,6,6,6,6.5,6.5,7] 0.3319 20.58 FAIL -13.77 31.58 FAIL 0 0 PASS +u-happamuusaste ambiguous high inf 1 FAIL split_by_bimodal_cautious at 6.204 | DIP:0,BL:13.2% 91.97 {1:91.97,5170:7.63,form:0.4} bimodal_cautious 6.204 0.1646 0 13.16 0.395 0.5763 -0.4589 7722 229 [6.5,6.5,7,7,7,7,7.5,7.5,8] [5.5,6,6,6,6,6,6.5,6.5,7] 0.6681 97.2 FAIL 24.78 67.43 FAIL 1 0 FAIL +p-tromboplastiiniaika,inr-tulos ambiguous all inf 1 PASS NO_SPLIT KS,T,MAD 47.95 {inr:52.05,1:47.95} skipped NA NA NA NA 0.02462 0.372 -14.11 8123 5842 [1.7,2,2.2,2.3,2.5,2.6,2.8,3,3.4] [1.8,2,2.2,2.3,2.5,2.6,2.8,3,3.4] 0.02462 1.497 PASS -1.209 0.6444 PASS 0 1.2 PASS +b-pvk+t ambiguous all inf % PASS NO_SPLIT T,MAD 43.01 {%:43.01,e9/l:23.04,g/l:13.71} skipped NA NA NA NA 0.5945 0.5969 -0.003928 7646 8614 [1,1,1,1,1,1,1,1,1] [6,10,12,13,14,28,39,52,62] 0.9975 300 FAIL 1.556 0.9216 PASS 13 27 PASS +s-ccpab ambiguous all inf u/ml PASS NO_SPLIT KS,MAD 83.24 {u/ml:83.24,eliau/ml:15.54,form:1.22} skipped NA NA NA NA 0.06054 0.1375 -1.272 7611 30305 [0.6,0.7,0.9,1,1.1,1.3,1.5,1.8,5] [0.6,0.8,0.9,1,1.2,1.4,1.6,2.2,21] 0.06054 19.1 PASS -8.189 15.55 FAIL 0.1 1.2 PASS +l-mono ambiguous all inf % PASS NO_SPLIT KS,MAD 97.66 {%:97.66,e9/l:2.34} skipped NA NA NA NA 0.07021 0.1202 -0.7127 7071 58379 [2.6,5,6.9,7.6,8.1,9,10,11.3,13] [4.6,6,7,8,9,9.3,10,11.7,14] 0.07021 26.75 PASS -8.86 18.02 FAIL 0.9 6 PASS +l-ly ambiguous low 6.662 e9/l PASS split_by_bimodal at 6.662 | DIP:0,BL:0.1% KS,MAD 2.01 {%:97.98,e9/l:2.01,osuus:0} bimodal 6.662 0.7783 0 0.07061 0.2055 0.05513 0.7318 1408 2846 [1.097,1.38,1.59,1.76,1.93,2.1,2.31,2.59,2.98] [1.1,1.3,1.5,1.7,1.83,2,2.2,2.5,2.9] 0.07858 4.785 PASS 2.455 1.849 FAIL 0.1 1.29 PASS +l-ly ambiguous high inf % PASS split_by_bimodal at 6.662 | DIP:0,BL:0.1% KS,T,MAD 97.98 {%:97.98,e9/l:2.01,osuus:0} bimodal 6.662 0.7783 0 0.07061 0.2055 0.05513 0.7318 5054 138412 [15.63,19.6,23,25.9,28.2,31,34,37.7,43.57] [13,18,22,25.1,28.1,31.2,34.9,38.9,45] 0.04859 9.719 PASS 1.478 0.8553 PASS 0.1 24.3 PASS +p-rf ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 92.76 {iu/ml:92.76,u/ml:7.21,iu/l:0.03} skipped NA NA NA NA 0.2662 0.4652 -0.7473 6213 10583 [5,6,7,9,11,14,20,34,86] [7,10,11,12.7,15,19,27,48.3,105] 0.2662 244.7 PASS -6.241 9.351 FAIL 4 18 PASS +s-fsh ambiguous all inf u/l PASS NO_SPLIT KS,MAD 48.88 {iu/l:51.12,u/l:48.88} skipped NA NA NA NA 0.03077 0.4035 -12.12 6137 14214 [3.1,4.6,5.9,7.1,8.9,13,23.6,44.08,69.4] [3.5,5,6.2,7.4,9.3,13.6,26,47.9,73.27] 0.03077 3.233 PASS -2.615 2.049 FAIL 0.4 16.8 PASS +u-osmoest ambiguous all inf mosm/kgh2o PASS NO_SPLIT KS,MAD 60.17 {mosm/kgh2o:60.17,mosm/l:34.05,e6/l:5.78} skipped NA NA NA NA 0.02943 0.4084 -12.88 5941 25256 [340,440,520,550,630,700,740,850,960] [330,440,520,550,630,700,740,850,1000] 0.02943 3.326 PASS -5.525 7.464 FAIL 0 450 PASS +neutrofiilit ambiguous low 53.09 e9/l FAIL split_by_bimodal at 53.09 | DIP:0,BL:5.5% 29.31 {%:70.69,e9/l:29.31,52%:0} bimodal 53.09 0.6531 0 5.482 0.4699 0.505 -0.0748 2762 10477 [1,3,5,7,11,16,23,31,42] [1.5,2.16,2.68,3.19,3.72,4.37,5.19,6.46,9.32] 0.4635 300 FAIL 39.34 271.5 FAIL 7.28 4.59 FAIL +neutrofiilit ambiguous high inf % PASS split_by_bimodal at 53.09 | DIP:0,BL:5.5% MAD 70.69 {%:70.69,e9/l:29.31,52%:0} bimodal 53.09 0.6531 0 5.482 0.4699 0.505 -0.0748 1797 25265 [60,67,73,78,82,85,89,91,94] [43,48,52,56,59,62,65,69,75] 0.5689 300 FAIL 69.36 300 FAIL 23 24 PASS +s-klotsa ambiguous low 35.23 umol/l PASS split_by_bimodal at 35.23 | DIP:0,BL:0.0% KS,MAD 20.06 {nmol/l:79.87,umol/l:20.06,âumol/l:0.07} bimodal 35.23 0.9066 0 3.351e-06 0.1707 0.0597 0.6502 733 9448 [0.644,1,1.1,1.4,1.6,1.9,2,2.3,2.78] [0.5,0.7,0.9,1.1,1.3,1.5,1.7,2,2.5] 0.1583 14.62 PASS -3.248 2.933 FAIL 0.3 1.5 PASS +s-klotsa ambiguous high inf nmol/l PASS split_by_bimodal at 35.23 | DIP:0,BL:0.0% KS,MAD 79.87 {nmol/l:79.87,umol/l:20.06,âumol/l:0.07} bimodal 35.23 0.9066 0 3.351e-06 0.1707 0.0597 0.6502 3675 37623 [526,787.4,1014,1237,1458,1687,1930,2238,2697] [586,870,1100,1312,1520,1740,1986,2304,2797] 0.04005 4.375 PASS -4.016 4.221 FAIL 62 1704 PASS +s-digoks ambiguous all inf nmol/l PASS NO_SPLIT KS,MAD 96.59 {nmol/l:96.59,ug/l:3.41} skipped NA NA NA NA 0.03224 0.3919 -11.16 3559 18447 [0.5,0.6,0.6,0.7,0.8,0.9,1,1.1,1.3] [0.4,0.5,0.6,0.7,0.8,0.9,1,1.1,1.4] 0.03224 2.401 PASS -3.499 3.327 FAIL 0 0.6 PASS +s-tshrab ambiguous all inf iu/l PASS NO_SPLIT KS,MAD 84.17 {iu/l:84.17,u/l:15.54,iu/ml:0.2} skipped NA NA NA NA 0.1548 0.2543 -0.6425 3513 6615 [1.7,2.5,3,3.5,4.1,4.9,6.3,9,16] [1.7,2,2.4,2.9,3.3,3.9,5,7.3,14] 0.1548 47.72 PASS 5.067 6.382 FAIL 0.8 4.2 PASS +na ambiguous all inf mmol/l PASS NO_SPLIT T,MAD 19.19 {mmol/l:19.19,%:15.12,e9/l:14.7} skipped NA NA NA NA 0.5017 0.5639 -0.124 3419 8463 [1.7,2,2.1,2.3,2.4,2.6,2.9,3.2,4.4] [1.3,2,3.1,3.8,4.1,4.82,6,11.7,139] 0.5017 300 FAIL 1.791 1.134 PASS 1.7 6 PASS +b-hba1cvt ambiguous all inf mmol/mol PASS NO_SPLIT KS,MAD 58.36 {mmol/mol:58.36,%:41.64} skipped NA NA NA NA 0.2325 0.5238 -1.252 3196 10683 [46,51,55,58,61,64,68,74,82] [41,45,48,51,54,57,61,66,75] 0.2325 116.7 PASS 18.64 73.96 FAIL 7 24 PASS +s-prealb ambiguous low 6.265 g/l PASS split_by_bimodal at 6.265 | DIP:0,BL:0.0% KS,T,MAD 46.45 {mg/l:53.55,g/l:46.45} bimodal 6.265 0.9397 0 3.903e-12 0.3007 0.05444 0.819 2235 33018 [0.1,0.13,0.16,0.18,0.2,0.22,0.24,0.26,0.3] [0.09,0.12,0.16,0.18,0.2,0.22,0.24,0.26,0.29] 0.03626 2.1 PASS -0.5916 0.2564 PASS 0 0.18 PASS +s-prealb ambiguous high inf mg/l PASS split_by_bimodal at 6.265 | DIP:0,BL:0.0% KS,MAD 53.55 {mg/l:53.55,g/l:46.45} bimodal 6.265 0.9397 0 3.903e-12 0.3007 0.05444 0.819 955 38068 [98.4,139,161.2,188,207,229,247,269,294] [122,158,184,205,224,241,260,281,312] 0.09698 7.354 PASS -6.33 9.431 FAIL 17 147 PASS +s-rf ambiguous all inf kiu/l PASS NO_SPLIT KS,T,MAD 25.76 {iu/ml:72.43,kiu/l:25.76,u/ml:1.74} skipped NA NA NA NA 0.03602 0.2922 -7.111 3025 4037 [5,6,7,8,9,10,11,15,40.6] [4,5,6,8,9,10,12,16,32] 0.1082 17.37 PASS 1.587 0.9485 PASS 0 9 PASS +p-inr-pika ambiguous all inf % PASS NO_SPLIT KS,MAD 96.06 {%:96.06,inr:3.94} skipped NA NA NA NA 0.08404 0.4409 -4.247 2999 829 [1.9,2.1,2.3,2.4,2.6,2.7,2.9,3.2,3.6] [1.8,2.1,2.2,2.4,2.5,2.6,2.8,3,3.3] 0.08404 3.716 PASS 4.052 4.269 FAIL 0.1 1.2 PASS +s-prl ambiguous all inf mu/l PASS NO_SPLIT KS,T,MAD 94.27 {mu/l:94.27,miu/l:5.51,nmol/l:0.19} skipped NA NA NA NA 0.101 0.2868 -1.839 2987 28193 [137,180,218,259,308,369,452,588.8,900.6] [115,152.1,185,220,262,314,389,522.6,840] 0.101 23.73 PASS -0.08868 0.03183 PASS 46 342 PASS +s-mypnabg ambiguous low 26.53 au/ml PASS split_by_bimodal at 26.53 | DIP:0,BL:2.2% KS,MAD 12.74 {eiu:82.35,au/ml:12.74,ru/ml:4.32} bimodal 26.53 0.6575 0 2.195 0.3707 0.07466 0.7986 1163 1133 [0.5,0.9,1.4,2,2.7,3.7,5.3,7.5,13.18] [0.5,1.2,1.86,2.7,3.8,5.7,8.9,16.36,34.1] 0.1444 10.18 PASS -10.56 24.29 FAIL 1.1 9 PASS +s-mypnabg ambiguous high inf eiu PASS split_by_bimodal at 26.53 | DIP:0,BL:2.2% T,MAD 82.35 {eiu:82.35,au/ml:12.74,ru/ml:4.32} bimodal 26.53 0.6575 0 2.195 0.3707 0.07466 0.7986 1764 7323 [51,63,77,92.68,108,131,155,191,246.4] [51,65,79,95,113,135,160,200,259.8] 0.0287 0.7263 FAIL -1.654 1.008 PASS 5 144 PASS +u-osmol ambiguous all inf mosm/kgh2o PASS NO_SPLIT T,MAD 95.64 {mosm/kgh2o:95.64,mo/kgh2o:3.88,form:0.35} skipped NA NA NA NA 0.01897 0.4316 -21.75 2913 7279 [175,233.4,281,323,368,412,464,534,638] [171,228,276,320,367,417,474,544,645] 0.01897 0.3605 FAIL -0.5542 0.237 PASS 1 375 PASS +li-igg-ind ambiguous all inf osuus PASS NO_SPLIT KS,MAD 80.28 {osuus:80.28,index:10.18,ratio:8.34} skipped NA NA NA NA 0.1172 0.2758 -1.353 2901 741 [0.41,0.44,0.47,0.49,0.51,0.54,0.59,0.69,0.99] [0.44,0.47,0.48,0.49,0.5,0.52,0.55,0.61,0.83] 0.1172 6.8 PASS 3.409 3.18 FAIL 0.01 0.12 PASS +b-mono ambiguous all inf e9/l PASS NO_SPLIT KS,MAD 93.53 {e9/l:93.53,%:6.47} skipped NA NA NA NA 0.1804 0.1629 0.0972 2899 66813 [0.4,0.5,0.5,0.6,0.6,0.7,0.83,0.92,1.1] [0.32,0.4,0.48,0.5,0.59,0.61,0.7,0.8,0.92] 0.1804 78.87 PASS 10.97 26.73 FAIL 0.01 0.45 PASS +p-cmvnh ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 82.84 {iu/ml:82.84,u/ml:17.16} skipped NA NA NA NA 0.07093 0.2561 -2.61 2630 7076 [45,64,90.7,130,180,270,530,1100,4600] [41,54,72,101,153,234,410.5,849,2735] 0.07093 8.109 PASS 3.115 2.73 FAIL 27 327 PASS +u-veri-o ambiguous all inf form PASS NO_SPLIT MAD 97.62 {form:97.62,u/field:2.38} skipped NA NA NA NA 0.01783 0.01783 0 2524 41 [0,0,0,0,0,0,0,0,0] [0,0,0,0,0,0,0,0,0] 0.01783 -0 FAIL 2.933 2.469 FAIL 0 0 PASS +s-tpoab ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 58.56 {iu/ml:58.56,u/ml:41.28,form:0.11} skipped NA NA NA NA 0.1186 0.4801 -3.048 2449 7169 [0.4,6,13.74,37,70,140.9,250,418.2,695.4] [4,8,13,31,41,64,140,271.4,546] 0.1186 22.1 PASS 7.955 14.63 FAIL 29 108 PASS +p-tni ambiguous low 0.3025 ug/l PASS split_by_bimodal_cautious at 0.3025 | DIP:0,BL:3.0% KS,MAD 10.3 {ng/l:89.7,ug/l:10.3} bimodal_cautious 0.3025 0.3568 0 2.976 0.2752 0.1191 0.5671 520 25059 [0,0.006,0.0197,0.04,0.05,0.06,0.07,0.1,0.15] [0.01,0.013,0.02,0.024,0.031,0.05,0.079,0.16,0.736] 0.204 18.33 PASS -20.65 93.2 FAIL 0.019 0.063 PASS +p-tni ambiguous high inf ng/l PASS split_by_bimodal_cautious at 0.3025 | DIP:0,BL:3.0% KS,MAD 89.7 {ng/l:89.7,ug/l:10.3} bimodal_cautious 0.3025 0.3568 0 2.976 0.2752 0.1191 0.5671 1889 218179 [3,4,6,9,13,23,36.6,73,201.4] [4,5,7,10,15,24,46,119,764] 0.09576 14.67 PASS -14.21 43.5 FAIL 2 33 PASS +u-prot ambiguous low 11.23 g/l PASS split_by_bimodal at 11.23 | DIP:0,BL:0.4% KS,MAD 44.55 {mg/l:55.44,g/l:44.55,mg/dl:0} bimodal 11.23 0.6347 0 0.3624 0.2275 0.1665 0.2681 2060 12027 [0,0.09,0.1,0.2,0.3,0.4,0.7,1,1.807] [0.06,0.08,0.1,0.11,0.2,0.2096,0.35,0.528,1.1] 0.1507 34.63 PASS 3.691 3.649 FAIL 0.1 0.36 PASS +u-prot ambiguous high inf mg/l PASS split_by_bimodal at 11.23 | DIP:0,BL:0.4% KS,MAD 55.44 {mg/l:55.44,g/l:44.55,mg/dl:0} bimodal 11.23 0.6347 0 0.3624 0.2275 0.1665 0.2681 259 14966 [34,50,71.4,76.2,89,101.8,131,196.2,412.4] [65,82,99,119,144,181,241,386,961] 0.292 19.04 PASS -7.437 12.28 FAIL 55 207 PASS +s-hcg-b-v ambiguous all inf ug/l PASS NO_SPLIT T,MAD 93.46 {ug/l:93.46,pmol/l:6.48,iu/l:0.02} skipped NA NA NA NA 0.01979 0.3981 -19.11 2184 15974 [21,30,37,45,53,63,76,95,127.4] [22.69,30.8,38,45.3,54,63.9,76,93,123] 0.01979 0.3631 FAIL 1.734 1.08 PASS 1 69 PASS +s-mypnabm ambiguous all inf index PASS NO_SPLIT KS,MAD 56.62 {index:56.62,s/co:42.67,form:0.63} skipped NA NA NA NA 0.1865 0.3576 -0.9176 2145 1445 [1,1.58,2,2.5,3,3.5,4.5,5.8,8.16] [1.5,2.2,2.8,3.4,4.2,5.2,6.6,8.4,11] 0.1865 25.99 PASS -11.33 28.4 FAIL 1.2 6.3 PASS +l-mon ambiguous all inf % PASS NO_SPLIT MAD 88.17 {%:88.17,e9/l:11.83} skipped NA NA NA NA 0.5543 0.7387 -0.3327 2104 12735 [4,8,11,12,12,13,14,15,16] [6,7,7.5,8,8.7,9,10,10.7,12] 0.5543 300 FAIL 24.12 114.2 FAIL 3.3 4.8 PASS +pt-ekg-12 ambiguous all inf form PASS NO_SPLIT MAD 80.56 {form:80.56,tehtu:16.67,u:2.78} skipped NA NA NA NA 0.3239 0.3516 -0.08572 2052 58 [0,0,0,0,0,0,1,1,1] [0,0,0,0,0,0,48.5,1.029e+05,2.109e+05] 0.3239 5.039 FAIL -3.733 3.359 FAIL 0 0 PASS +s-enaab ambiguous all inf u/ml PASS NO_SPLIT KS,MAD 87.7 {u/ml:87.7,ratio:12.2,titre:0.1} skipped NA NA NA NA 0.05344 0.1531 -1.865 1973 1690 [0.1,0.1,0.16,0.2,0.2,0.2,0.3,0.4,0.7] [0.1,0.1,0.2,0.2,0.2,0.2,0.3,0.4,1.2] 0.05344 1.978 PASS -3.039 2.622 FAIL 0 0.3 PASS +s-dmklots ambiguous low 22.05 umol/l PASS split_by_bimodal at 22.05 | DIP:0,BL:0.0% KS,MAD 38.41 {nmol/l:61.43,umol/l:38.41,âumol/l:0.14} bimodal 22.05 0.9235 0 5.957e-08 0.266 0.1124 0.5773 516 8521 [0.4,0.5,0.69,0.8,0.9,1,1,1.2,1.4] [0.3,0.4,0.5,0.6,0.7,0.8,1,1.2,1.5] 0.1833 14.05 PASS -6.816 11 FAIL 0.2 0.9 PASS +s-dmklots ambiguous high inf nmol/l PASS split_by_bimodal at 22.05 | DIP:0,BL:0.0% KS,MAD 61.43 {nmol/l:61.43,umol/l:38.41,âumol/l:0.14} bimodal 22.05 0.9235 0 5.957e-08 0.266 0.1124 0.5773 1421 13627 [386,542,698,816,928,1061,1226,1420,1707] [349,489.2,603,718,842,973,1127,1319,1617] 0.0867 8.139 PASS 5.861 8.258 FAIL 86 981 PASS +p-pct ambiguous all inf ug/l PASS NO_SPLIT KS,T,MAD 93.91 {ug/l:93.91,ng/ml:6.09} skipped NA NA NA NA 0.2222 0.401 -0.8045 1917 23100 [0.05,0.07,0.09,0.11,0.14,0.18,0.28,0.51,1.33] [0.07,0.1,0.14,0.2,0.28,0.42,0.72,1.5,5.461] 0.2222 76.53 PASS -1.105 0.5702 PASS 0.14 0.6 PASS +s-pr3ab ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 75.45 {iu/ml:75.45,u/ml:23.28,kiu/l:1.27} skipped NA NA NA NA 0.04796 0.4704 -8.809 1866 2324 [2.55,3.6,5,6.9,9.85,15,26,39,66] [2.5,4.1,6.1,8.7,13,19,27,41,73] 0.07762 5.149 PASS -2.413 1.799 FAIL 3.15 29.1 PASS +s-crp-o ambiguous all inf mg/l PASS NO_SPLIT KS,MAD 88.8 {mg/l:88.8,form:10.67,u/ml:0.35} skipped NA NA NA NA 0.05621 0.3942 -6.012 1736 2521 [5,7,10.05,14,19,28,38,57,94] [5,5,5.6,8,10,15,22,35,64] 0.2009 36.05 PASS 10.38 24.11 FAIL 9 15 PASS +b-tbifng ambiguous all inf iu/ml PASS NO_SPLIT KS 96.03 {iu/ml:96.03,iu/l:3.93,form:0.03} skipped NA NA NA NA 0.0544 0.2592 -3.765 1724 2858 [0,0,0,0,0.01,0.01,0.02,0.04,0.36] [0,0,0,0,0,0.01,0.01,0.03,0.09] 0.1051 10.07 PASS 3.322 3.043 FAIL 0.01 0 FAIL +du-prot ambiguous low 15.1 g PASS split_by_bimodal at 15.1 | DIP:0,BL:0.3% KS,MAD 32.31 {mg:65.4,g:32.31,mg/24h:1.32} bimodal 15.1 0.6975 0 0.2746 0.4669 0.128 0.7258 915 4909 [0.13,0.2,0.3,0.436,0.6,0.8,1.1,1.71,3] [0.12,0.2,0.35,0.56,0.84,1.29,1.8,2.71,4.32] 0.1541 15.73 PASS -8.56 16.61 FAIL 0.24 2.04 PASS +du-prot ambiguous high inf mg PASS split_by_bimodal at 15.1 | DIP:0,BL:0.3% KS,MAD 65.4 {mg:65.4,g:32.31,mg/24h:1.32} bimodal 15.1 0.6975 0 0.2746 0.4669 0.128 0.7258 802 9936 [101,155.2,243.9,362,500,688.4,1027,1782,3480] [98,151,250,415,666,1040,1630,2570,4429] 0.1102 7.577 PASS -5.619 7.608 FAIL 166 1659 PASS +s-chpnabm ambiguous low 0.9363 index PASS split_by_bimodal at 0.9363 | DIP:0,BL:0.3% KS,T,MAD 96.11 {index:96.11,u/ml:2.63,titre:0.69} bimodal 0.9363 0.6431 0 0.3249 0.5095 0.6149 -0.2067 682 841 [0.08,0.102,0.13,0.144,0.17,0.2,0.25,0.298,0.37] [0.05,0.07,0.08,0.1,0.12,0.14,0.17,0.21,0.29] 0.2482 20.15 PASS -0.676 0.3017 PASS 0.05 0.15 PASS +s-chpnabm ambiguous high inf index FAIL split_by_bimodal at 0.9363 | DIP:0,BL:0.3% 96.11 {index:96.11,u/ml:2.63,titre:0.69} bimodal 0.9363 0.6431 0 0.3249 0.5095 0.6149 -0.2067 672 841 [2.3,3,3.5,4,4.5,5.2,6.2,7.5,9.99] [0.05,0.07,0.08,0.1,0.12,0.14,0.17,0.21,0.29] 0.9869 300 FAIL 19.23 65.74 FAIL 4.38 0.15 FAIL +b-b-cd19 ambiguous all inf e9/l PASS NO_SPLIT MAD 66.67 {e9/l:66.67,e6/l:33.33} skipped NA NA NA NA 0.02213 0.1969 -7.895 1316 1826 [0,0,0,0.02,0.065,0.13,0.2,0.31,0.46] [0,0,0,0.01,0.06,0.13,0.2,0.29,0.47] 0.02213 0.07716 FAIL -2.042 1.384 FAIL 0.005 0.18 PASS +s-hepyabg ambiguous all inf u/ml PASS NO_SPLIT MAD 81.11 {u/ml:81.11,index:11,eiu:4.08} skipped NA NA NA NA 0.3368 0.5047 -0.4986 1308 4091 [0.927,2.812,10,14,30,76.2,166.5,410,1600] [0.1,0.1,0.4,10,10.7,11.6,13.8,22.9,83] 0.3368 99.57 FAIL 12.38 32.66 FAIL 19.3 30.9 PASS +s-chpnabg ambiguous all inf bu/ml PASS NO_SPLIT KS,MAD 10.86 {eiu:42.53,u/ml:35.3,bu/ml:10.86} skipped NA NA NA NA 0.2142 0.2368 -0.1056 1294 467 [4.2,7.8,10.2,12.5,15.8,32,64,81.8,128] [1,3,5,8,11,23,32,50,77.4] 0.2142 13.56 PASS 7.43 12.7 FAIL 4.8 30 PASS +i-stathct ambiguous low 3.066 %pcv FAIL split_by_bimodal at 3.066 | DIP:0,BL:0.0% 60.91 {%pcv:60.91,%:35.45,mmol/l:1.82} bimodal 3.066 0.9809 0 1.252e-31 0.8234 0.8339 -0.01275 1020 67 [0.33,0.36,0.39,0.4,0.41,0.43,0.44,0.46,0.49] [34.6,37,38,40,41,42,44,46,46.4] 1 107.7 FAIL -53 55.1 FAIL 40.59 12 FAIL +i-stathct ambiguous high inf %pcv PASS split_by_bimodal at 3.066 | DIP:0,BL:0.0% T,MAD 60.91 {%pcv:60.91,%:35.45,mmol/l:1.82} bimodal 3.066 0.9809 0 1.252e-31 0.8234 0.8339 -0.01275 220 67 [32.9,36,38,40,41,42,44,46,47] [34.6,37,38,40,41,42,44,46,46.4] 0.0637 0.01126 FAIL -0.4063 0.1641 PASS 0 12 PASS +u-epitel ambiguous low 1.01 /sunf PASS split_by_bimodal at 1.01 | DIP:0,BL:0.0% KS,T,MAD 95.22 {/sunf:95.22,sunf:4.78} bimodal 1.01 0.7048 0 0.03994 0.3115 0.3384 -0.08642 902 1316 [0,0,0,0,0,1,1,1,1] [0,0,0,0,0,0,0,1,2] 0.2103 20.48 PASS -1.413 0.8017 PASS 0 0 PASS +u-epitel ambiguous high inf /sunf FAIL split_by_bimodal at 1.01 | DIP:0,BL:0.0% 95.22 {/sunf:95.22,sunf:4.78} bimodal 1.01 0.7048 0 0.03994 0.3115 0.3384 -0.08642 218 1316 [2,2,2,2,2,3,3,4,5] [0,0,0,0,0,0,0,1,2] 0.8685 154 FAIL 19.8 52.93 FAIL 2 0 FAIL +c-reaktiivinenproteiini,vieritutkimus,plasmasta ambiguous all inf mg/l PASS NO_SPLIT T,MAD 95 {mg/l:95,1:5} skipped NA NA NA NA 0.1044 0.5 -3.789 1118 38 [3.2,6,9.01,14,20,29,41,62,95] [5.85,6.64,8.47,11,15,22.8,56.2,59.6,94.4] 0.1044 0.1088 FAIL 0.1372 0.04983 PASS 5 27.75 PASS +s-gadab ambiguous all inf iu/ml PASS NO_SPLIT KS,T,MAD 77.97 {iu/ml:77.97,ru:20.5,u/ml:1.16} skipped NA NA NA NA 0.2763 0.4557 -0.6491 1035 1685 [0.1,0.1,1,1,1,1,3.8,22.2,136] [1,1,1,1,1,2,11.8,55,207.6] 0.2763 42.88 PASS -1.673 1.025 PASS 0 0 PASS +s-hbsab ambiguous all inf iu/l PASS NO_SPLIT KS,MAD 16.15 {miu/ml:70.21,iu/l:16.15,u/l:12.92} skipped NA NA NA NA 0.0578 0.4865 -7.417 1022 1584 [15.87,27,43,69.72,100.1,153.2,230,330.8,560] [19,32,54,84,122.5,176.8,260.2,398.4,600.2] 0.0578 1.523 PASS -2.227 1.585 FAIL 22.4 289.5 PASS +s-epo ambiguous all inf u/l PASS NO_SPLIT KS,MAD 69.26 {u/l:69.26,iu/l:30.18,pmol/l:0.57} skipped NA NA NA NA 0.06366 0.1866 -1.932 963 5377 [4.636,6.7,8.46,10.2,11.9,13.8,17,22.42,39.4] [4.442,6.49,8.1,9.9,11.9,14.5,19,29.6,61] 0.06366 2.596 PASS -4.672 5.506 FAIL 0 17.4 PASS +b-leuk-vt ambiguous all inf e9/l PASS NO_SPLIT KS,MAD 94.85 {e9/l:94.85,ng/l:5.15} skipped NA NA NA NA 0.06559 0.4512 -5.879 902 2468 [5.1,5.8,6.6,7.216,7.9,8.6,9.5,10.6,12.5] [4.8,5.6,6.2,6.8,7.4,8,8.9,10,11.6] 0.08759 4.137 PASS 3.35 3.078 FAIL 0.5 5.1 PASS +s-osmol ambiguous all inf mo/kgh2o PASS NO_SPLIT KS,T,MAD 4.25 {mosm/kgh2o:95.25,mo/kgh2o:4.25,form:0.37} skipped NA NA NA NA 0.1024 0.3786 -2.699 886 218 [253,260,266,272,277,284,289,293,298] [254.7,260.4,266.1,271,276.5,285,290,297,309] 0.1024 1.327 PASS -1.475 0.85 PASS 0.5 45 PASS +hb(päiv.hoitaja) ambiguous all inf g/l PASS NO_SPLIT T,MAD 94.55 {g/l:94.55,mg/l:3.64,\':1.82} skipped NA NA NA NA 0.08663 0.4351 -4.022 816 52 [101,112,119,125,131,135,139,146,152] [107.2,115.2,117.6,125.2,134,136.6,143.7,147.8,158] 0.08663 0.08402 FAIL -0.4867 0.2018 PASS 3 45 PASS +p-hcg ambiguous all inf iu/l PASS NO_SPLIT KS,T,MAD 6.87 {u/l:93.13,iu/l:6.87} skipped NA NA NA NA 0.06667 0.4737 -6.105 807 859 [2,12,34,92.4,261,801,2803,7190,3.203e+04] [3.56,10.4,27.66,67.48,193.2,518.5,1489,5316,1.811e+04] 0.06667 1.332 PASS 1.95 1.289 PASS 67.8 571.2 PASS +s-afp ambiguous all inf u/ml PASS NO_SPLIT KS,MAD 87.73 {u/ml:87.73,ug/l:12.27} skipped NA NA NA NA 0.08914 0.1688 -0.894 783 17947 [2,2,3,3,4,4,5,6.3,9.86] [1.8,2.1,2.6,3,3.6,4.3,5.6,7.7,22.24] 0.08914 4.909 PASS -6.246 9.365 FAIL 0.4 4.8 PASS +p-krea-vt ambiguous all inf umol/l PASS NO_SPLIT KS,MAD 97.09 {umol/l:97.09,mmol/l:1.52,1:1.39} skipped NA NA NA NA 0.1401 0.3778 -1.697 749 2804 [53,61,70,77,84,93.8,105,122,153.4] [50,58,64,69,75,82,91,104,137] 0.1401 9.855 PASS 3.337 3.059 FAIL 9 51 PASS +s-borrabm ambiguous all inf responseequivalent/ml PASS NO_SPLIT KS,MAD 32.93 {au/ml:40.04,responseequivalent/ml:32.93,ru/ml:26.71} skipped NA NA NA NA 0.08807 0.4195 -3.763 721 2780 [9,10,11,13,14,16,18,23,30] [10,10,11,12,13,14,16,19,25] 0.08807 3.591 PASS 4.337 4.788 FAIL 1 9 PASS +s-etoh ambiguous all inf promille PASS NO_SPLIT MAD 89.71 {promille:89.71,g/l:5.5,mmol/l:4.78} skipped NA NA NA NA 0.6116 0.659 -0.0776 713 1369 [0,0,0,0,0,0,0,0,1] [0,0,0.1,0.584,1.4,1.9,2.4,2.8,3.5] 0.6116 163.9 FAIL -24.04 111.7 FAIL 1.4 3.9 PASS +c-reaktiivinenproteiini ambiguous all inf mg/l PASS NO_SPLIT KS,MAD 89.59 {mg/l:89.59,1:10.41} skipped NA NA NA NA 0.02736 0.496 -17.13 691 7963 [6,9,11,17,24,37,52,73,106] [4,6,9,14,23,35,52,78,126] 0.1274 8.726 PASS -3.097 2.696 FAIL 1 54 PASS +vb-pco2-vt ambiguous all inf kpa PASS NO_SPLIT MAD 97.67 {kpa:97.67,1:2.33} skipped NA NA NA NA 0.08151 0.09709 -0.1911 686 336 [4.35,4.73,5.05,5.32,5.54,5.73,6.03,6.42,7.125] [4.29,4.68,4.945,5.23,5.405,5.6,5.89,6.12,6.605] 0.08151 1.03 FAIL 2.307 1.67 FAIL 0.135 1.755 PASS +s-lh ambiguous all inf u/l PASS NO_SPLIT KS,T,MAD 55.18 {u/l:55.18,iu/l:44.82} skipped NA NA NA NA 0.07821 0.4403 -4.629 684 7086 [1.9,3,3.8,4.9,5.95,7.7,9.9,15.2,24.8] [2.1,3.2,4,4.8,5.7,6.8,8.3,11.1,20] 0.07821 3.041 PASS 1.675 1.026 PASS 0.25 7.8 PASS +s-chpnaba ambiguous all inf u/ml PASS NO_SPLIT MAD 83.03 {u/ml:83.03,eiu:16.52,au/ml:0.45} skipped NA NA NA NA 0.576 0.6875 -0.1936 677 1649 [2.2,2.6,3.1,3.5,4,4.7,5.4,6.4,8.5] [0.1,5.8,7.6,9.92,12.5,16.1,20.26,29,44.02] 0.576 147.6 FAIL -20.72 85.09 FAIL 8.5 21 PASS +b-6-me-mp ambiguous all inf pmol/02ml PASS NO_SPLIT T,MAD 75.82 {pmol/02ml:75.82,umol/l:24.18} skipped NA NA NA NA 0.06799 0.5076 -6.466 637 345 [110.8,228.4,357.8,556,865,1420,2366,3990,7698] [148.6,253.4,413.6,652.4,943,1476,2268,3870,6949] 0.06799 0.6233 FAIL 0.2731 0.1052 PASS 78 2286 PASS +s-hcg ambiguous all inf u/l PASS NO_SPLIT KS,MAD 75.96 {u/l:75.96,iu/l:24.04} skipped NA NA NA NA 0.08149 0.4631 -4.683 630 5922 [8,18,39.4,108.9,305.1,882.7,3133,9862,3.876e+04] [6,20.1,67,173,365,665.6,1493,4279,1.685e+04] 0.08149 3.011 PASS 3.185 2.821 FAIL 59.95 1074 PASS +glukoosi(päiv.hoitaja) ambiguous all inf mmol/l PASS NO_SPLIT T,MAD 94.83 {mmol/l:94.83,mg/l:1.72,mmol/mol:1.72} skipped NA NA NA NA 0.1072 0.3436 -2.207 611 55 [4.8,5.3,5.6,6,6.5,7.1,8.1,9.3,12] [4.64,5.56,6,6.42,6.9,7.68,8.68,10.92,12.3] 0.1072 0.2451 FAIL -0.2648 0.1013 PASS 0.4 4.5 PASS +b-6-tgn ambiguous all inf pmol/02ml PASS NO_SPLIT T,MAD 76.99 {pmol/02ml:76.99,umol/l:23.01} skipped NA NA NA NA 0.04105 0.4988 -11.15 609 368 [75,109.6,128.4,151.2,180,208.8,233,280,373.4] [77.7,103,129,148.8,176,201.2,226,270.6,361.6] 0.04105 0.08917 FAIL 1.002 0.4993 PASS 4 192 PASS +p-bkvnh ambiguous all inf iu/ml PASS NO_SPLIT KS,MAD 63.26 {iu/ml:63.26,u/ml:36.74} skipped NA NA NA NA 0.1023 0.3688 -2.607 608 260 [37,75,170,310,590,1300,3590,1.56e+04,6.26e+04] [33.9,51.8,97,203,457,1040,2660,6988,2.86e+04] 0.1023 1.39 PASS 2.359 1.73 FAIL 133 1278 PASS +s-dnanab ambiguous all inf iu/ml PASS NO_SPLIT KS,T,MAD 49.21 {iu/ml:49.21,u/ml:32.3,kiu/l:17.77} skipped NA NA NA NA 0.1624 0.3624 -1.231 584 3166 [1.13,2,6,12,15.5,22,32,44.4,64] [1,2,3,4.9,9,13,19,30,59] 0.1624 11.11 PASS 1.767 1.111 PASS 6.5 23.1 PASS +u-hb-o ambiguous all inf form PASS NO_SPLIT T 77.38 {form:77.38,A:22.62} skipped NA NA NA NA 0.4009 0.4216 -0.05158 584 65 [0,0,0,0,1,1,1,2,3] [0,0,0,0,0,0,0,0,1] 0.4009 8.226 FAIL -0.9449 0.4581 PASS 1 0 FAIL +s-hepyaba ambiguous all inf responseequivalent PASS NO_SPLIT KS,MAD 1.35 {u/ml:97.89,responseequivalent:1.35,form:0.73} skipped NA NA NA NA 0.2831 0.3522 -0.2439 551 57 [10,10.9,11.9,13,16,20.4,28,44,69] [10,11,11.8,12,13,15,16.2,20,26.4] 0.2831 3.423 PASS 8.599 15.9 FAIL 3 6 PASS +pt-fibrosis-4indeksi ambiguous all inf 1 PASS NO_SPLIT T,MAD 54.1 {1:54.1,u/l:45.9} skipped NA NA NA NA 0.1764 0.5468 -2.101 550 33 [0.64,0.79,0.94,1.096,1.24,1.41,1.613,1.982,2.591] [0.796,1.01,1.124,1.31,1.38,1.466,1.61,1.732,2.068] 0.1764 0.5916 FAIL 0.8331 0.3883 PASS 0.14 0.99 PASS +i-staturea ambiguous all inf mmol/l PASS NO_SPLIT T,MAD 97.5 {mmol/l:97.5,\':2.5} skipped NA NA NA NA 0.1905 0.5058 -1.655 522 39 [3.7,4.5,5.23,6.1,6.9,7.8,8.8,10.5,13.49] [4.82,5.32,5.54,6.22,6.9,7.78,8.56,10.28,11.14] 0.1905 0.9034 FAIL 1.581 0.9314 PASS 0 5.1 PASS +s-dgpabg ambiguous all inf u/ml PASS NO_SPLIT KS,MAD 23.62 {eliau/ml:48.32,au:28.01,u/ml:23.62} skipped NA NA NA NA 0.05991 0.4106 -5.854 506 500 [2,3,6,9,12,17,23,33,74] [0.6,0.9,1.7,6.28,9,12,15,27.2,47.2] 0.2527 13.9 PASS 3.654 3.55 FAIL 3 23.85 PASS diff --git a/scripts/injection/data/plot_data.json.gz b/scripts/injection/data/plot_data.json.gz new file mode 100644 index 0000000..a62c2cb Binary files /dev/null and b/scripts/injection/data/plot_data.json.gz differ diff --git a/scripts/injection/data/summary_table.md b/scripts/injection/data/summary_table.md new file mode 100644 index 0000000..4c30f25 --- /dev/null +++ b/scripts/injection/data/summary_table.md @@ -0,0 +1,9 @@ +*Active threshold: 98% — min count: 500 — 715 TEST_NAMEs, 31,962,504 measurements* + +| Threshold | UNAMBIGUOUS test names | UNAMBIGUOUS measurements | AMBIGUOUS test names | AMBIGUOUS measurements | NO_DATA test names | NO_DATA measurements | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| 95% | 434 (60.7%) | 26,508,342 (82.9%) | 79 (11.0%) | 4,345,264 (13.6%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 98% \* | 413 (57.8%) | 24,979,594 (78.2%) | 100 (14.0%) | 5,874,012 (18.4%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 99% | 392 (54.8%) | 22,954,996 (71.8%) | 121 (16.9%) | 7,898,610 (24.7%) | 202 (28.3%) | 1,108,898 (3.5%) | +| 100% | 275 (38.5%) | 12,745,902 (39.9%) | 238 (33.3%) | 18,107,704 (56.7%) | 202 (28.3%) | 1,108,898 (3.5%) | +| **TOTAL** | **715** | | | || | diff --git a/scripts/injection/data/test_names_exploration_scatter.png b/scripts/injection/data/test_names_exploration_scatter.png new file mode 100644 index 0000000..a26d8ab Binary files /dev/null and b/scripts/injection/data/test_names_exploration_scatter.png differ diff --git a/scripts/injection/explore_test_name.py b/scripts/injection/explore_test_name.py new file mode 100644 index 0000000..fe2cb44 --- /dev/null +++ b/scripts/injection/explore_test_name.py @@ -0,0 +1,1104 @@ +#!/usr/bin/env python3 +""" +explore_test_name.py + +Identifies lab measurements that have a numeric value but no unit, characterises +the unit distribution of matching records that do have a unit, and optionally +runs the injection engine to validate each candidate TEST_NAME. + +Usage +----- + python3 explore_test_name.py PARQUET [options] +""" + +import argparse +import gzip +import json +import os +import re +import subprocess +import sys +from io import StringIO +from pathlib import Path + +import numpy as np +import pandas as pd + + +import injection_engine +import split_eval +from plot_exploration import make_scatter_plot + + +# --------------------------------------------------------------------------- +# ClickHouse +# --------------------------------------------------------------------------- + +def clickhouse(query, **params): + cmd = ["clickhouse", "-q", query] + for k, v in params.items(): + cmd.append(f"--param_{k}={v}") + proc = subprocess.run(cmd, capture_output=True, text=True) + if proc.returncode != 0: + print(proc.stderr, file=sys.stderr) + raise RuntimeError("ClickHouse query failed") + return proc.stdout + + +# --------------------------------------------------------------------------- +# Queries +# --------------------------------------------------------------------------- + +_COUNTS_MIN = 50 # fixed baseline for the cached query; --min-count filters post-load + + +def query_counts(parquet, out="test_name_counts.tsv"): + if Path(out).exists(): + df = pd.read_csv(out, sep="\t", na_values=["\\N"]) + if "OMOP_CONCEPT_ID" in df.columns: + df["OMOP_CONCEPT_ID"] = pd.to_numeric(df["OMOP_CONCEPT_ID"], errors="coerce").astype("Int64") + print(f"{out} already exists, skipping.") + return df + print(f"{out} exists but missing OMOP_CONCEPT_ID — re-querying.") + + result = clickhouse(f""" + SELECT TEST_NAME, count() AS COUNT, any(OMOP_CONCEPT_ID) AS OMOP_CONCEPT_ID + FROM file('{parquet}') + WHERE (MEASUREMENT_VALUE_EXTRACTED IS NOT NULL OR MEASUREMENT_VALUE_SOURCE IS NOT NULL) + AND MEASUREMENT_UNIT_PRE_FIX IS NULL + GROUP BY TEST_NAME + HAVING COUNT > {_COUNTS_MIN} + ORDER BY COUNT DESC + FORMAT TSVWithNames + """) + Path(out).write_text(result) + df = pd.read_csv(out, sep="\t", na_values=["\\N"]) + df["OMOP_CONCEPT_ID"] = pd.to_numeric(df["OMOP_CONCEPT_ID"], errors="coerce").astype("Int64") + print(f"Wrote {out} ({len(df)} rows)") + return df + + +def query_details(parquet, counts_file="test_name_counts.tsv", out="test_name_details.tsv"): + if Path(out).exists(): + print(f"{out} already exists, skipping.") + return pd.read_csv(out, sep="\t") + + counts_abs = str(Path(counts_file).resolve()) + result = clickhouse(f""" + WITH + global_names AS ( + SELECT DISTINCT TEST_NAME + FROM file('{counts_abs}', TSVWithNames) + ), + top3_units AS ( + SELECT + TEST_NAME, + argMax(MEASUREMENT_UNIT_PRE_FIX, unit_cnt) AS UNIT, + concat('{{', arrayStringConcat(groupArray(unit_json), ','), '}}') AS PREVALENCE_DICT + FROM ( + SELECT TEST_NAME, MEASUREMENT_UNIT_PRE_FIX, unit_cnt, unit_json + FROM ( + SELECT + TEST_NAME, MEASUREMENT_UNIT_PRE_FIX, unit_cnt, + ROW_NUMBER() OVER (PARTITION BY TEST_NAME ORDER BY unit_cnt DESC) AS rn, + concat( + MEASUREMENT_UNIT_PRE_FIX, ':', + toString(round(100.0 * unit_cnt / SUM(unit_cnt) OVER (PARTITION BY TEST_NAME), 2)) + ) AS unit_json + FROM ( + SELECT TEST_NAME, MEASUREMENT_UNIT_PRE_FIX, count() AS unit_cnt + FROM file('{parquet}') + WHERE MEASUREMENT_VALUE_SOURCE IS NOT NULL + AND MEASUREMENT_UNIT_PRE_FIX IS NOT NULL + GROUP BY TEST_NAME, MEASUREMENT_UNIT_PRE_FIX + ) AS sub + ) AS ranked + WHERE rn <= 3 + ) AS top3 + GROUP BY TEST_NAME + ), + total_counts AS ( + SELECT TEST_NAME, count() AS COUNT + FROM file('{parquet}') + WHERE MEASUREMENT_VALUE_SOURCE IS NOT NULL + AND MEASUREMENT_UNIT_PRE_FIX IS NOT NULL + GROUP BY TEST_NAME + ) + SELECT + t.TEST_NAME AS TEST_NAME, + t.COUNT AS COUNT, + u.UNIT AS UNIT, + u.PREVALENCE_DICT AS PREVALENCE_DICT + FROM total_counts t + LEFT JOIN top3_units u USING (TEST_NAME) + INNER JOIN global_names g USING (TEST_NAME) + ORDER BY t.COUNT DESC + FORMAT TSVWithNames + """) + Path(out).write_text(result) + df = pd.read_csv(out, sep="\t") + print(f"Wrote {out} ({len(df)} rows)") + return df + + +# --------------------------------------------------------------------------- +# Plot table +# --------------------------------------------------------------------------- + + +def build_plot_table(counts, details): + def parse_top_prevalence(d): + if pd.isna(d): + return None + pcts = re.findall(r':(\d+\.?\d*)[,}]', str(d)) + return max(float(p) for p in pcts) if pcts else None + + details = details.copy() + details["top_prevalence"] = details["PREVALENCE_DICT"].apply(parse_top_prevalence) + + plot_name = counts.merge( + details[["TEST_NAME", "top_prevalence", "COUNT"]].rename(columns={"COUNT": "N_WITH_UNIT"}), + on="TEST_NAME", how="left", + ) + plot_name["top_prevalence"] = plot_name["top_prevalence"].fillna(0) + plot_name["N_WITH_UNIT"] = plot_name["N_WITH_UNIT"].fillna(0).astype(int) + return plot_name + + +# --------------------------------------------------------------------------- +# Summary table +# --------------------------------------------------------------------------- + +_SUMMARY_THRESHOLDS = [95, 98, 99, 100] + + +def _cat_at(df, t, min_target_n): + return np.select( + [ + df["N_WITH_UNIT"] < min_target_n, + df["top_prevalence"] >= t, + df["top_prevalence"] > 0, + ], + ["NO_DATA", "UNAMBIGUOUS", "AMBIGUOUS"], + default="NO_DATA", + ) + + +def _add_category(plot_name, threshold, min_target_n): + """Add CATEGORY (operational) and CATEGORY_{t} (exploratory) columns.""" + df = plot_name.copy() + df["CATEGORY"] = _cat_at(df, threshold, min_target_n) + for t in _SUMMARY_THRESHOLDS: + df[f"CATEGORY_{t}"] = _cat_at(df, t, min_target_n) + return df + + +def print_summary(plot_name, threshold): + total = len(plot_name) + total_n = int(plot_name["COUNT"].sum()) + + tw = 13 # test_names cell width + mw = 18 # measurements cell width + + cats = ["UNAMBIGUOUS", "AMBIGUOUS", "NO_DATA"] + + # Header + print() + h1 = f"{'':>12}" + h2 = f"{'Threshold':>10} " + sep = f"{'':>12}" + for cat in cats: + h1 += f" {cat:^{tw+mw+2}}" + h2 += f" {'TEST_NAMES':>{tw}} {'MEASUREMENTS':>{mw}}" + sep += f" {'-'*tw} {'-'*mw}" + print(h1) + print(h2) + print(sep) + + for t in _SUMMARY_THRESHOLDS: + marker = " *" if t == threshold else " " + row = f"{t:>9}%{marker}" + for cat in cats: + sub = plot_name[plot_name[f"CATEGORY_{t}"] == cat] + n = len(sub) + m = int(sub["COUNT"].sum()) + row += f" {f'{n:,} ({100*n/total:.1f}%)':>{tw}} {f'{m:,} ({100*m/total_n:.1f}%)':>{mw}}" + print(row) + + print(sep) + print(f"{'TOTAL':>12} {total:>{tw},}{'':>{mw+4}}" + f" {'':>{tw}} {total_n:>{mw},}") + print(f"\n * = active threshold ({threshold}%)\n") + + +def print_assignment_summary(udf, adf, plot_name, out_doc=None, out_tsv=None): + """Fraction of TEST_NAMEs/measurements successfully assigned a unit (UNAMBIGUOUS + AMBIGUOUS only).""" + count_map = plot_name.set_index("TEST_NAME")["COUNT"].to_dict() + omop_names = set(plot_name[plot_name["OMOP_CONCEPT_ID"].notna()]["TEST_NAME"]) + + def _safe(df): + return df if df is not None and len(df) else None + + udf_ = _safe(udf) + adf_ = _safe(adf) + + assigned = { + "UNAMBIGUOUS": set(udf_[udf_["OUTCOME"] == "PASS"]["TEST_NAME"]) if udf_ is not None else set(), + "AMBIGUOUS": set(adf_[adf_["OUTCOME"] == "PASS"]["TEST_NAME"]) if adf_ is not None else set(), + } + processed = { + "UNAMBIGUOUS": set(udf_["TEST_NAME"]) if udf_ is not None else set(), + "AMBIGUOUS": set(adf_["TEST_NAME"]) if adf_ is not None else set(), + } + + def _meas(names): + return sum(count_map.get(n, 0) for n in names) + + def _build_row(name_filter=None): + cells = [] + all_asgn = set() + all_proc = set() + for cat in ["UNAMBIGUOUS", "AMBIGUOUS"]: + asgn = assigned[cat] if name_filter is None else assigned[cat] & name_filter + proc = processed[cat] if name_filter is None else processed[cat] & name_filter + all_asgn |= asgn + all_proc |= proc + cells.append((f"{len(asgn):,}/{len(proc):,}", f"{_meas(asgn):,}/{_meas(proc):,}")) + cells.append((f"{len(all_asgn):,}/{len(all_proc):,}", f"{_meas(all_asgn):,}/{_meas(all_proc):,}")) + return cells + + rows = [ + ("All", _build_row()), + ("OMOP-mapped", _build_row(omop_names)), + ] + + nw = max(len(ns) for _, cells in rows for ns, _ in cells) + mw = max(len(ms) for _, cells in rows for _, ms in cells) + lw = 12 + cw = nw + 2 + mw + cols = ["UNAMBIGUOUS", "AMBIGUOUS", "TOTAL"] + + text_lines = [ + "\nAssignment summary (successfully assigned)\n", + f"{'':>{lw}}" + "".join(f" {c:^{cw}}" for c in cols), + f"{'':>{lw}}" + "".join(f" {'names':>{nw}} {'meas':>{mw}}" for _ in cols), + ] + div = f"{'':>{lw}}" + "".join(f" {'-'*nw} {'-'*mw}" for _ in cols) + text_lines.append(div) + for label, cells in rows: + text_lines.append(f"{label:<{lw}}" + "".join(f" {ns:>{nw}} {ms:>{mw}}" for ns, ms in cells)) + text_lines.append(div) + + for line in text_lines: + print(line) + + if out_doc: + Path(out_doc).write_text("```\n" + "\n".join(text_lines) + "\n```\n") + print(f"Wrote {out_doc}") + + if out_tsv: + tsv_rows = [] + for label, cells in rows: + row = {"subset": label} + for col, (ns, ms) in zip(cols, cells): + row[f"{col}_names"] = ns + row[f"{col}_meas"] = ms + tsv_rows.append(row) + pd.DataFrame(tsv_rows).to_csv(out_tsv, sep="\t", index=False) + print(f"Wrote {out_tsv}") + + +def dump_summary_md(plot_name, threshold, min_count, out="summary_table.md", out_tsv=None): + total = len(plot_name) + total_n = int(plot_name["COUNT"].sum()) + cats = ["UNAMBIGUOUS", "AMBIGUOUS", "NO_DATA"] + + lines = [] + lines.append(f"*Active threshold: {threshold}% — min count: {min_count:,} — " + f"{total:,} TEST_NAMEs, {total_n:,} measurements*\n") + + header = "| Threshold | " + " | ".join( + f"{c} test names | {c} measurements" for c in cats + ) + " |" + sep = "| --- | " + " | ".join(["---: | ---:" for _ in cats]) + " |" + lines.append(header) + lines.append(sep) + + tsv_rows = [] + for t in _SUMMARY_THRESHOLDS: + marker = " \\*" if t == threshold else "" + row = f"| {t}%{marker} |" + tsv_row = {"threshold": f"{t}%"} + for cat in cats: + sub = plot_name[plot_name[f"CATEGORY_{t}"] == cat] + n = len(sub) + m = int(sub["COUNT"].sum()) + row += f" {n:,} ({100*n/total:.1f}%) | {m:,} ({100*m/total_n:.1f}%) |" + tsv_row[f"{cat}_names"] = n + tsv_row[f"{cat}_meas"] = m + lines.append(row) + tsv_rows.append(tsv_row) + + lines.append(f"| **TOTAL** | **{total:,}** | | " + "| |" * (len(cats) - 1)) + + Path(out).write_text("\n".join(lines) + "\n") + print(f"Wrote {out}") + + if out_tsv: + pd.DataFrame(tsv_rows).to_csv(out_tsv, sep="\t", index=False) + print(f"Wrote {out_tsv}") + + +# --------------------------------------------------------------------------- +# Injection engine +# --------------------------------------------------------------------------- + +def _query_test_values(parquet, name, mode, unit=None): + if mode == "candidate": + q = f""" + SELECT coalesce(MEASUREMENT_VALUE_EXTRACTED, MEASUREMENT_VALUE_SOURCE) AS value + FROM file('{parquet}') + WHERE TEST_NAME = {{name:String}} + AND MEASUREMENT_UNIT_PRE_FIX IS NULL + AND isNotNull(coalesce(MEASUREMENT_VALUE_EXTRACTED, MEASUREMENT_VALUE_SOURCE)) + FORMAT TSV + """ + out = clickhouse(q, name=name) + else: + q = f""" + SELECT MEASUREMENT_VALUE_SOURCE AS value + FROM file('{parquet}') + WHERE TEST_NAME = {{name:String}} + AND MEASUREMENT_UNIT_PRE_FIX = {{unit:String}} + AND MEASUREMENT_VALUE_SOURCE IS NOT NULL + FORMAT TSV + """ + out = clickhouse(q, name=name, unit=unit) + if not out.strip(): + return np.array([]) + vals = [] + for line in out.strip().split("\n"): + try: + vals.append(float(line.strip())) + except ValueError: + pass + return np.sort(np.array(vals, dtype=float)) + + +def _sample_deciles(df): + """Return one row per COUNT decile (by rank), up to 10 rows.""" + df = df.reset_index(drop=True) + n = len(df) + indices = sorted({int(round((i / 9) * (n - 1))) for i in range(10)}) + return df.iloc[indices] + + +_PCTS = [10, 20, 30, 40, 50, 60, 70, 80, 90] + + +def _fmt_deciles(arr): + vals = np.percentile(arr, _PCTS) + return "[" + ",".join(f"{v:.4g}" for v in vals) + "]" + + +def _run_engine(c_vals, t_vals, name, dump_dir, plots_dir=None, prevalence=None, tag_override=None): + """Run KS / T / MAD pipeline, save diagnostic plot, return (updates, ks, t, mad).""" + ks_step = injection_engine._ks(c_vals, t_vals, ks_threshold=0.3, sig_level=0.05) + t_step = injection_engine._t(c_vals, t_vals, sig_level=0.05) + mad_step = injection_engine._mad(c_vals, t_vals, n_mad=3.0) + + ks_mlogp = -np.log10(np.clip(ks_step.details["pval"], 1e-300, 1.0)) + t_mlogp = -np.log10(np.clip(t_step.details["pval"], 1e-300, 1.0)) + + if ks_step.passed: + decided_by, outcome = "KS", "PASS" + elif t_step.passed: + decided_by, outcome = "T", "PASS" + else: + decided_by = "MAD" + outcome = "PASS" if mad_step.passed else "FAIL" + + result = injection_engine.PipelineResult(outcome, decided_by, + [ks_step, t_step, mad_step]) + injection_engine.plot_result(c_vals, t_vals, result, name, plots_dir or dump_dir, + prevalence=prevalence, tag_override=tag_override) + plot_data = injection_engine.compute_plot_data(c_vals, t_vals, result, + prevalence=prevalence) + + passed_tests = ",".join(n for n, s in [("KS", ks_step), ("T", t_step), ("MAD", mad_step)] if s.passed) + updates = dict( + KS_STAT=ks_step.details["stat"], KS_MLOGP=ks_mlogp, + KS_PASS="PASS" if ks_step.passed else "FAIL", + T_STAT=t_step.details["stat"], T_MLOGP=t_mlogp, + T_PASS="PASS" if t_step.passed else "FAIL", + MAD_DIST=mad_step.details["distance"], + MAD_THRESHOLD=mad_step.details["threshold"], + MAD_PASS="PASS" if mad_step.passed else "FAIL", + OUTCOME=outcome, + NOTES=passed_tests, + CAND_DECILES=_fmt_deciles(c_vals), + TARG_DECILES=_fmt_deciles(t_vals), + ) + return updates, ks_step, t_step, mad_step, plot_data + + +def run_unambiguous(parquet, plot_name, details, dump_dir, plots_dir=None, test_mode=False, + out="unambiguous_results.tsv"): + plot_data = {} + os.makedirs(dump_dir, exist_ok=True) + + unambig = plot_name[plot_name["CATEGORY"] == "UNAMBIGUOUS"].sort_values("COUNT", ascending=False) + if test_mode: + unambig = _sample_deciles(unambig) + test_names = unambig["TEST_NAME"].tolist() + print(f"Unambiguous TEST_NAMEs: {len(test_names)}") + + dom_unit = dict(zip(details["TEST_NAME"], details["UNIT"])) + prev_dict = dict(zip(details["TEST_NAME"], details["PREVALENCE_DICT"])) + + rows = [] + n_pass = n_fail = n_skip = 0 + + for i, name in enumerate(test_names, 1): + print(f" [{i:>4}/{len(test_names)}] {name:<40}", end=" ", flush=True) + + tag = name.replace("/", "_").replace(" ", "_") + cand_npy = os.path.join(dump_dir, f"cand_{tag}.npy") + targ_npy = os.path.join(dump_dir, f"targ_{tag}.npy") + + unit = dom_unit.get(name, "NA") + prev = prev_dict.get(name, "NA") + + if os.path.exists(cand_npy): + c_vals = np.load(cand_npy) + print("cand=cache", end=" ", flush=True) + else: + print("cand=query", end=" ", flush=True) + c_vals = _query_test_values(parquet, name, "candidate") + np.save(cand_npy, c_vals) + + if os.path.exists(targ_npy): + t_vals = np.load(targ_npy) + print(f"targ=cache({unit})", end=" ", flush=True) + else: + print(f"targ=query({unit})", end=" ", flush=True) + t_vals = _query_test_values(parquet, name, "target", unit=unit) + np.save(targ_npy, t_vals) + + print(f"N={len(c_vals):,}/{len(t_vals):,}", end=" ", flush=True) + + row = dict(TEST_NAME=name, UNIT=unit, PREVALENCE_DICT=prev, + N_CANDIDATE=len(c_vals), N_TARGET=len(t_vals), + KS_STAT=np.nan, KS_MLOGP=np.nan, KS_PASS="NA", + T_STAT=np.nan, T_MLOGP=np.nan, T_PASS="NA", + MAD_DIST=np.nan, MAD_THRESHOLD=np.nan, MAD_PASS="NA", + OUTCOME="SKIP", NOTES="") + + if len(c_vals) >= 2 and len(t_vals) >= 2: + print("engine...", end=" ", flush=True) + updates, ks, t, mad, pd_ = _run_engine(c_vals, t_vals, name, dump_dir, + plots_dir=plots_dir, prevalence=prev) + row.update(updates) + plot_data[name] = pd_ + print( + f"KS={'P' if ks.passed else 'F'}" + f"(stat={ks.details['stat']:.3g},mlogp={updates['KS_MLOGP']:.1f},{ks.details['method']})" + f" T={'P' if t.passed else 'F'}(mlogp={updates['T_MLOGP']:.1f})" + f" MAD={'P' if mad.passed else 'F'}" + f"(dist={mad.details['distance']:.3f},thr={mad.details['threshold']:.3f})", + end=" ", flush=True, + ) + + if row["OUTCOME"] == "PASS": n_pass += 1 + elif row["OUTCOME"] == "FAIL": n_fail += 1 + else: n_skip += 1 + + rows.append(row) + print(f"{row['OUTCOME']} ({row['NOTES']}) " + f"[running: {n_pass}P/{n_fail}F/{n_skip}S] {prev}") + + cols = ["TEST_NAME", "UNIT", "PREVALENCE_DICT", "N_CANDIDATE", "N_TARGET", + "CAND_DECILES", "TARG_DECILES", + "KS_STAT", "KS_MLOGP", "KS_PASS", + "T_STAT", "T_MLOGP", "T_PASS", + "MAD_DIST", "MAD_THRESHOLD", "MAD_PASS", + "OUTCOME", "NOTES"] + results = pd.DataFrame(rows, columns=cols).rename(columns={"NOTES": "TESTS_PASSED"}) + for col in ("TEST_NAME", "UNIT", "OUTCOME"): + results[col] = results[col].str.replace(" ", "_", regex=False) + if out is not None: + results.to_csv(out, sep="\t", index=False, float_format="%.4g", na_rep="NA") + print(f"\nWrote {out} ({len(results)} rows)") + return results, plot_data + + +# --------------------------------------------------------------------------- +# Ambiguous injection +# --------------------------------------------------------------------------- + +def parse_top_units(prevalence_dict, n=3): + """Parse '{unit1:pct1,unit2:pct2,...}' → list of (unit, pct) sorted by pct desc.""" + if pd.isna(prevalence_dict) or str(prevalence_dict) in ("", "NA", "nan"): + return [] + matches = re.findall(r'([^:{},\s][^:{},]*):(\d+\.?\d*)', str(prevalence_dict)) + pairs = [(u.strip(), float(p)) for u, p in matches if u.strip()] + pairs.sort(key=lambda x: x[1], reverse=True) + return pairs[:n] + + +def _unit_tag(unit): + return unit.replace("/", "_").replace(" ", "_").replace("%", "pct") + + +def run_ambiguous(parquet, plot_name, details, dump_dir, plots_dir=None, + dip_threshold=0.05, min_target_n=30, test_mode=False, + split_threshold=0.15, out="ambiguous_results.tsv"): + os.makedirs(dump_dir, exist_ok=True) + plot_data = {} + + ambig = plot_name[plot_name["CATEGORY"] == "AMBIGUOUS"].sort_values("COUNT", ascending=False) + if test_mode: + ambig = ambig.head(10) + n_names = len(ambig) + print(f"\nAmbiguous TEST_NAMEs: {n_names}") + + prev_dict = dict(zip(details["TEST_NAME"], details["PREVALENCE_DICT"])) + + rows = [] + + for i, name in enumerate(ambig["TEST_NAME"].tolist(), 1): + tag = name.replace("/", "_").replace(" ", "_") + cand_npy = os.path.join(dump_dir, f"cand_{tag}.npy") + + print(f"\n[{i:>4}/{n_names}] {name}") + + # Candidate values (reuse cache from unambiguous run if present) + if os.path.exists(cand_npy): + c_vals = np.load(cand_npy) + print(f" cand=cache N={len(c_vals):,}") + else: + c_vals = _query_test_values(parquet, name, "candidate") + np.save(cand_npy, c_vals) + print(f" cand=query N={len(c_vals):,}") + + if len(c_vals) < 2: + print(" SKIP: too few candidate values") + continue + + # Filter to units with prevalence > 1% (top 3) + top_units = [(u, p) for u, p in parse_top_units(prev_dict.get(name, ""), n=3) if p > 1.0] + if not top_units: + print(" SKIP: no units with prevalence > 1%") + continue + + # Load & cache target arrays once (applies min_target_n guard) + unit_data = {} # unit -> t_vals + for unit, upct in top_units: + utag = _unit_tag(unit) + targ_npy = os.path.join(dump_dir, f"targ_{tag}_{utag}.npy") + if os.path.exists(targ_npy): + t_vals_u = np.load(targ_npy) + else: + t_vals_u = _query_test_values(parquet, name, "target", unit=unit) + np.save(targ_npy, t_vals_u) + if len(t_vals_u) < min_target_n: + print(f" SKIP unit {unit}({upct:.1f}%): N_TARGET={len(t_vals_u)}<{min_target_n}") + else: + unit_data[unit] = t_vals_u + + if not unit_data: + print(" SKIP: no units with sufficient reference data") + continue + + # Pre-check: run engine on full candidate vs each qualifying unit. + # If any unit already passes, record those results and skip bimodality. + print(f" pre-check (full candidate) units={list(unit_data)}") + precheck = [] # (unit, upct, t_vals, updates, ks_step, t_step, mad_step) + for unit, upct in [(u, p) for u, p in top_units if u in unit_data]: + t_vals_u = unit_data[unit] + upd, ks, t, mad, pd_ = _run_engine( + c_vals, t_vals_u, + f"{name} [all] [{unit}]", dump_dir, plots_dir=plots_dir, + prevalence=f"{upct:.1f}%", + tag_override=f"{tag}_all_{_unit_tag(unit)}", + ) + plot_data.setdefault(name, {}).setdefault("engine", {})[f"all_{unit}"] = pd_ + precheck.append((unit, upct, t_vals_u, upd, ks, t, mad)) + print(f" [all][{unit}({upct:.1f}%)]" + f" N={len(c_vals):,}/{len(t_vals_u):,}" + f" KS={'P' if ks.passed else 'F'}(stat={ks.details['stat']:.3g})" + f" T={'P' if t.passed else 'F'}" + f" MAD={'P' if mad.passed else 'F'}" + f" → {upd['OUTCOME']}") + + any_precheck_pass = any(upd["OUTCOME"] == "PASS" for _, _, _, upd, *_ in precheck) + + # Bimodal check always runs — needed for split score even when pre-check passed + bim = injection_engine.bimodal_check(c_vals, dip_threshold=dip_threshold) + injection_engine.plot_bimodal_check(bim, name, plots_dir or dump_dir) + plot_data.setdefault(name, {})["bimodal"] = injection_engine.compute_bimodal_plot_data(bim) + print(f" bimodal={bim.status} sep={bim.separator:.4g}" + f" BC={bim.bc:.3f} dip_p={bim.dip_p:.3g}") + + # Evaluate whether splitting improves the fit + prefer_split = False + si = {} + sep = bim.separator + c_low = c_vals[c_vals <= sep] if not np.isnan(sep) else np.array([]) + c_high = c_vals[c_vals > sep] if not np.isnan(sep) else np.array([]) + if len(c_low) >= 2 and len(c_high) >= 2: + si = injection_engine.split_improvement(c_vals, c_low, c_high, unit_data) + print(f" split_improvement={si['improvement']:+.1%}" + f" same_unit={si['same_best_unit']}" + f" (global_KS={si['global_score']:.4f}" + f" split_KS={si['split_score']:.4f})") + if si["improvement"] > split_threshold and not si["same_best_unit"]: + prefer_split = True + print(f" → SPLIT preferred (threshold={split_threshold:.0%})") + + # Decision-tree figure + unit_data_fig = {unit: (unit_data[unit], upct) + for unit, upct in top_units if unit in unit_data} + global_ranks = split_eval.rank_units(c_vals, unit_data_fig) + low_ranks = split_eval.rank_units(c_low, unit_data_fig) if len(c_low) >= 2 else [] + high_ranks = split_eval.rank_units(c_high, unit_data_fig) if len(c_high) >= 2 else [] + split_eval.make_figure( + name=name, c_vals=c_vals, unit_data=unit_data_fig, + global_ranks=global_ranks, low_ranks=low_ranks, high_ranks=high_ranks, + c_low=c_low, c_high=c_high, sep=sep, bim=bim, + g_score=global_ranks[0][2] if global_ranks else np.nan, + s_score=si.get("split_score", np.nan), + improvement=si.get("improvement", 0.0), + out_path=os.path.join(plots_dir or dump_dir, f"split_eval_{tag}.png"), + ) + + if any_precheck_pass and not prefer_split: + print(f" pre-check passed → global result kept") + for unit, upct, t_vals_u, upd, ks, t, mad in precheck: + rows.append(dict( + TEST_NAME=name, + BIMODAL_STATUS="skipped", + BIMODAL_SEP=np.nan, BIMODAL_BC=np.nan, BIMODAL_DIP_P=np.nan, BIMODAL_OVERLAP=np.nan, + SCORE_GLOBAL=si.get("global_score", np.nan), + SCORE_SPLIT=si.get("split_score", np.nan), + SCORE_IMPROVEMENT=si.get("improvement", np.nan), + SUB_DIST="all", + UNIT=unit, UNIT_PREVALENCE=upct, + PREVALENCE_DICT=prev_dict.get(name, "NA"), + N_CANDIDATE=len(c_vals), N_TARGET=len(t_vals_u), + **upd, + )) + continue + + # Decide whether to split: bimodal detected, or split preferred by score + # bimodal/bimodal_cautious takes priority over split_by_score when dip confirms bimodality + do_split = prefer_split or bim.status in ("bimodal", "bimodal_cautious") + bimodal_label = (bim.status if bim.status in ("bimodal", "bimodal_cautious") + else ("split_by_score" if prefer_split else bim.status)) + + if not do_split: + # Unimodal, pre-check failed, split not preferred — reuse pre-check results + for unit, upct, t_vals_u, upd, ks, t, mad in precheck: + rows.append(dict( + TEST_NAME=name, + BIMODAL_STATUS=bim.status, + BIMODAL_SEP=bim.separator, BIMODAL_BC=bim.bc, BIMODAL_DIP_P=bim.dip_p, BIMODAL_OVERLAP=bim.overlap_pct, + SCORE_GLOBAL=si.get("global_score", np.nan), + SCORE_SPLIT=si.get("split_score", np.nan), + SCORE_IMPROVEMENT=si.get("improvement", np.nan), + SUB_DIST="all", + UNIT=unit, UNIT_PREVALENCE=upct, + PREVALENCE_DICT=prev_dict.get(name, "NA"), + N_CANDIDATE=len(c_vals), N_TARGET=len(t_vals_u), + **upd, + )) + else: + sep = bim.separator + low = c_vals[c_vals <= sep] + high = c_vals[c_vals > sep] + sub_dists = [] + if len(low) >= 2: sub_dists.append(("low", low)) + if len(high) >= 2: sub_dists.append(("high", high)) + if not sub_dists: + sub_dists = [("all", c_vals)] + + print(f" sub_dists={[s for s, _ in sub_dists]}") + for sub_name, c_sub in sub_dists: + for unit, upct in [(u, p) for u, p in top_units if u in unit_data]: + t_vals_u = unit_data[unit] + upd, ks, t, mad, pd_ = _run_engine( + c_sub, t_vals_u, + f"{name} [{sub_name}] [{unit}]", dump_dir, plots_dir=plots_dir, + prevalence=f"{upct:.1f}%", + tag_override=f"{tag}_{sub_name}_{_unit_tag(unit)}", + ) + plot_data.setdefault(name, {}).setdefault("engine", {})[f"{sub_name}_{unit}"] = pd_ + rows.append(dict( + TEST_NAME=name, + BIMODAL_STATUS=bimodal_label, + BIMODAL_SEP=bim.separator, BIMODAL_BC=bim.bc, BIMODAL_DIP_P=bim.dip_p, BIMODAL_OVERLAP=bim.overlap_pct, + SCORE_GLOBAL=si.get("global_score", np.nan), + SCORE_SPLIT=si.get("split_score", np.nan), + SCORE_IMPROVEMENT=si.get("improvement", np.nan), + SUB_DIST=sub_name, + UNIT=unit, UNIT_PREVALENCE=upct, + PREVALENCE_DICT=prev_dict.get(name, "NA"), + N_CANDIDATE=len(c_sub), N_TARGET=len(t_vals_u), + **upd, + )) + print(f" [{sub_name}][{unit}({upct:.1f}%)]" + f" N={len(c_sub):,}/{len(t_vals_u):,}" + f" KS={'P' if ks.passed else 'F'}(stat={ks.details['stat']:.3g})" + f" T={'P' if t.passed else 'F'}" + f" MAD={'P' if mad.passed else 'F'}" + f" → {upd['OUTCOME']}") + + if not rows: + print("No ambiguous results to write.") + return pd.DataFrame(), {} + + results = pd.DataFrame(rows) + + # Best unit per (TEST_NAME, SUB_DIST): + # Rank by deciding test quality (KS > T > MAD > FAIL > SKIP), + # then by KS stat (lower = better fit), then by UNIT_PREVALENCE (prefer dominant unit). + def _pass_rank(row): + if row["KS_PASS"] == "PASS": return 0 + if row["T_PASS"] == "PASS": return 1 + if row["OUTCOME"] == "PASS": return 2 # MAD decided + if row["OUTCOME"] == "FAIL": return 3 + return 4 + + results["_rank"] = results.apply(_pass_rank, axis=1) + best = (results + .sort_values(["_rank", "KS_STAT", "UNIT_PREVALENCE"], + ascending=[True, True, False]) + .groupby(["TEST_NAME", "SUB_DIST"])["UNIT"] + .first() + .reset_index()) + results = results.merge(best.rename(columns={"UNIT": "_best_unit"}), + on=["TEST_NAME", "SUB_DIST"], how="left") + results = results[results["UNIT"] == results["_best_unit"]].drop(columns=["_rank", "_best_unit"]) + + col_order = [ + "TEST_NAME", "BIMODAL_STATUS", "BIMODAL_SEP", "BIMODAL_BC", "BIMODAL_DIP_P", "BIMODAL_OVERLAP", + "SCORE_GLOBAL", "SCORE_SPLIT", "SCORE_IMPROVEMENT", + "SUB_DIST", + "UNIT", "UNIT_PREVALENCE", "PREVALENCE_DICT", + "N_CANDIDATE", "N_TARGET", + "CAND_DECILES", "TARG_DECILES", + "KS_STAT", "KS_MLOGP", "KS_PASS", + "T_STAT", "T_MLOGP", "T_PASS", + "MAD_DIST", "MAD_THRESHOLD", "MAD_PASS", + "OUTCOME", "TESTS_PASSED", + ] + results = results.rename(columns={"NOTES": "TESTS_PASSED"})[col_order] + if out is not None: + results.to_csv(out, sep="\t", index=False, float_format="%.4g", na_rep="NA") + + _print_ambig_summary(results, out) + return results, plot_data + + +def _print_ambig_summary(results, out=None): + n_tot = results["TEST_NAME"].nunique() + tested = results[results["OUTCOME"] != "SKIP"] + + best_rows = tested.copy() + + n_pass = (best_rows["OUTCOME"] == "PASS").sum() + n_fail = (best_rows["OUTCOME"] == "FAIL").sum() + n_skip = n_tot - best_rows["TEST_NAME"].nunique() + + print(f"\n{'=' * 72}") + print("AMBIGUOUS INJECTION SUMMARY") + print(f"{'=' * 72}") + print(f" TEST_NAMEs: {n_tot} " + f"PASS={n_pass} FAIL={n_fail} SKIP={n_skip}") + print() + + w = 40 + print(f" {'TEST_NAME':<{w}} {'BIMODAL':<18} {'SUB':<5} {'UNIT':<14} OUTCOME") + print(f" {'-'*w} {'-'*18} {'-'*5} {'-'*14} -------") + for _, r in best_rows.iterrows(): + print(f" {r['TEST_NAME']:<{w}} {r['BIMODAL_STATUS']:<18} {r['SUB_DIST']:<5}" + f" {r['UNIT']:<14} {r['OUTCOME']}") + + if out is not None: + print(f"\nWrote {out} ({len(results)} rows, {n_tot} TEST_NAMEs)") + + +# --------------------------------------------------------------------------- +# Unified output +# --------------------------------------------------------------------------- + +_UNIFIED_COLS = [ + "TEST_NAME", + "TYPE", + "SUB_DIST", + "CUTOFF", + "UNIT", + "OUTCOME", + "NOTES", + "TESTS_PASSED", + "UNIT_PREVALENCE", "PREVALENCE_DICT", + "BIMODAL_STATUS", "BIMODAL_SEP", "BIMODAL_BC", "BIMODAL_DIP_P", "BIMODAL_OVERLAP", + "SCORE_GLOBAL", "SCORE_SPLIT", "SCORE_IMPROVEMENT", + "N_CANDIDATE", "N_TARGET", + "CAND_DECILES", "TARG_DECILES", + "KS_STAT", "KS_MLOGP", "KS_PASS", + "T_STAT", "T_MLOGP", "T_PASS", + "MAD_DIST", "MAD_THRESHOLD", "MAD_PASS", +] + + +def _make_notes(row): + """Generate a human-readable NOTES entry describing how the split decision was made.""" + if str(row.get("TYPE", "")) == "unambiguous": + return "" + status = str(row.get("BIMODAL_STATUS", "")) + impr = row.get("SCORE_IMPROVEMENT") + sep = row.get("BIMODAL_SEP") + dip = row.get("BIMODAL_DIP_P") + ovl = row.get("BIMODAL_OVERLAP") + sep_str = f"{sep:.4g}" if pd.notna(sep) else "?" + impr_str = f"{impr:+.1%}" if pd.notna(impr) else "?" + + if status == "split_by_score": + core = f"split_by_score ({impr_str}) at {sep_str}" + elif status == "bimodal": + core = f"split_by_bimodal at {sep_str}" + elif status == "bimodal_cautious": + core = f"split_by_bimodal_cautious at {sep_str}" + elif status == "skipped": + return "NO_SPLIT" + else: + return "NO_SPLIT" + + metrics = [] + if pd.notna(dip): + metrics.append(f"DIP:{dip:.3g}") + if pd.notna(ovl): + metrics.append(f"BL:{ovl:.1f}%") + return core + (" | " + ",".join(metrics) if metrics else "") + + +def _write_unified(udf, adf, out="injection_results.tsv"): + parts = [] + + if udf is not None and len(udf): + u = udf.copy() + u["TYPE"] = "unambiguous" + u["SUB_DIST"] = "all" + # Derive UNIT_PREVALENCE from the dominant unit in PREVALENCE_DICT + def _top_pct(row): + pairs = parse_top_units(row["PREVALENCE_DICT"], n=10) + for unit, pct in pairs: + if unit == row["UNIT"]: + return pct + return np.nan + u["UNIT_PREVALENCE"] = u.apply(_top_pct, axis=1) + parts.append(u) + + if adf is not None and len(adf): + a = adf.copy() + a["TYPE"] = "ambiguous" + parts.append(a) + + if not parts: + return None + + combined = pd.concat(parts, ignore_index=True) + combined["CUTOFF"] = np.where( + combined["SUB_DIST"] == "low", + combined["BIMODAL_SEP"], + np.inf, + ) + combined["NOTES"] = combined.apply(_make_notes, axis=1) + combined = combined.reindex(columns=_UNIFIED_COLS) + if out is not None: + combined.to_csv(out, sep="\t", index=False, float_format="%.4g", na_rep="NA") + print(f"\nWrote {out} ({len(combined)} rows)") + return combined + + +def _print_result_rows(df): + """Print injection_results rows vertically, one column per line.""" + if df is None or df.empty: + return + skip = {"PREVALENCE_DICT", "CAND_DECILES", "TARG_DECILES"} + for i, (_, row) in enumerate(df.iterrows()): + print(f"\n{'─'*60}") + print(f" injection_results row {i+1}/{len(df)}") + print(f"{'─'*60}") + for col in df.columns: + if col in skip: + continue + val = row[col] + if isinstance(val, float): + s = f"{val:.4g}" if not pd.isna(val) else "NA" + else: + s = str(val) if not pd.isna(val) else "NA" + print(f" {col:<22} {s}") + + +# --------------------------------------------------------------------------- +# Coverage check +# --------------------------------------------------------------------------- + +def _check_coverage(plot_name, udf, adf): + expected = set(plot_name[plot_name["CATEGORY"].isin(["UNAMBIGUOUS", "AMBIGUOUS"])]["TEST_NAME"]) + + covered = set() + for label, df in [("unambiguous", udf), ("ambiguous", adf)]: + if df is not None and len(df): + names = set(df["TEST_NAME"]) + overlap = covered & names + if overlap: + print(f" COVERAGE ERROR: {len(overlap)} TEST_NAME(s) appear in multiple files: " + f"{sorted(overlap)[:5]}{'...' if len(overlap) > 5 else ''}") + covered |= names + + missing = expected - covered + extra = covered - expected + + print(f"\nCoverage check: {len(expected)} expected " + f"{len(covered)} covered " + f"{len(missing)} missing " + f"{len(extra)} extra") + + if missing: + print(f" MISSING: {sorted(missing)[:10]}{'...' if len(missing) > 10 else ''}") + if extra: + print(f" EXTRA: {sorted(extra)[:10]}{'...' if len(extra) > 10 else ''}") + if not missing and not extra: + print(" OK: all TEST_NAMEs accounted for across 3 output files") + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def build_parser(): + p = argparse.ArgumentParser( + description="Explore unit injection targets in Kanta lab data.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("parquet", help="Input parquet file (INJECT.parquet)") + p.add_argument("--min-count", type=int, default=1000, metavar="INT", + help="Minimum no-unit records per TEST_NAME to include") + p.add_argument("--prevalence-threshold", type=float, default=98, metavar="FLOAT", + help="Min top-unit prevalence (%%) for a TEST_NAME to be unambiguous") + p.add_argument("--dump-dir", default="/mnt/disks/data/kanta/inject/tmp/", metavar="PATH", + help="Cache directory for per-test .npy arrays") + p.add_argument("--inject", action="store_true", + help="Run the injection engine on all tests (unambiguous + ambiguous)") + p.add_argument("--dip-threshold", type=float, default=0.05, metavar="FLOAT", + help="Hartigan dip test p-value threshold for bimodality detection") + p.add_argument("--split-threshold", type=float, default=0.15, metavar="FLOAT", + help="Minimum relative KS improvement to prefer a split over the global fit") + p.add_argument("--min-target-n", type=int, default=30, metavar="INT", + help="Minimum reference records a unit must have to be tested (ambiguous pass)") + p.add_argument("--test", nargs="?", const=True, default=None, metavar="TEST_NAME", + help="Test mode. Bare --test: small sample (one per COUNT decile for " + "unambiguous, top 10 for ambiguous), writes to _test-suffixed files. " + "--test NAME: run only that TEST_NAME, print to screen, no file output.") + p.add_argument("--out-dir", default=".", metavar="PATH", + help="Output directory for injection results/ (and test/results/ in --test mode). " + "Exploration outputs (counts, summary, scatter) go to --dump-dir.") + return p + + +def main(): + args = build_parser().parse_args() + + out_dir = Path(args.out_dir) + # test (sample mode) → test/; named test (screen only) and normal → results/ + work_dir = out_dir / ("test" if args.test is True else "results") + out_dir.mkdir(parents=True, exist_ok=True) + work_dir.mkdir(parents=True, exist_ok=True) + + # Cached: only written on first run or when missing + counts = query_counts(args.parquet, out=str(out_dir / "test_name_counts.tsv")) + effective_min = 0 if isinstance(args.test, str) else args.min_count + counts = counts[counts["COUNT"] > effective_min].reset_index(drop=True) + details = query_details(args.parquet, + counts_file=str(out_dir / "test_name_counts.tsv"), + out=str(out_dir / "test_name_details.tsv")) + + # Recomputed every run → work_dir + plot_name = build_plot_table(counts, details) + plot_name = _add_category(plot_name, args.prevalence_threshold, args.min_target_n) + plot_name.to_csv(work_dir / "plot_name_level.tsv", sep="\t", index=False) + print(f"Built plot_name_level.tsv ({len(plot_name)} rows, " + f"min_count={args.min_count}, threshold={args.prevalence_threshold}%)") + + make_scatter_plot(plot_name, output=str(work_dir / "test_names_exploration_scatter.png")) + print_summary(plot_name, args.prevalence_threshold) + dump_summary_md(plot_name, args.prevalence_threshold, args.min_count, + out=str(work_dir / "summary_table.md"), + out_tsv=str(work_dir / "summary_table.tsv")) + + if args.inject: + if isinstance(args.test, str): + plot_name = plot_name[plot_name["TEST_NAME"] == args.test].reset_index(drop=True) + if plot_name.empty: + print(f"'{args.test}' not found in any category — check name spelling") + return + + def _out(name): + return None if isinstance(args.test, str) else str(work_dir / name) + + plots_dir = None + sample_mode = args.test is True + + udf, udf_plots = run_unambiguous(args.parquet, plot_name, details, args.dump_dir, + plots_dir=plots_dir, + test_mode=sample_mode, + out=_out("unambiguous_results.tsv")) + adf, adf_plots = run_ambiguous(args.parquet, plot_name, details, args.dump_dir, + plots_dir=plots_dir, + dip_threshold=args.dip_threshold, + min_target_n=args.min_target_n, test_mode=sample_mode, + split_threshold=args.split_threshold, + out=_out("ambiguous_results.tsv")) + combined = _write_unified(udf, adf, out=_out("injection_results.tsv")) + if isinstance(args.test, str): + _print_result_rows(combined) + + all_plot_data = {**udf_plots, **adf_plots} + plot_data_out = _out("plot_data.json.gz") + if plot_data_out is not None: + def _round(obj, sig=5): + if isinstance(obj, float): + return float(f"{obj:.{sig}g}") + if isinstance(obj, dict): + return {k: _round(v, sig) for k, v in obj.items()} + if isinstance(obj, list): + return [_round(v, sig) for v in obj] + if isinstance(obj, (np.bool_,)): return bool(obj) + if isinstance(obj, np.integer): return int(obj) + if isinstance(obj, np.floating): return _round(float(obj), sig) + return obj + payload = json.dumps(_round(all_plot_data)).encode("utf-8") + with gzip.open(plot_data_out, "wb") as fh: + fh.write(payload) + print(f"Wrote {plot_data_out} ({len(all_plot_data)} TEST_NAMEs, " + f"{len(payload)/1024:.0f} KB → {Path(plot_data_out).stat().st_size/1024:.0f} KB gz)") + + if not args.test: + _check_coverage(plot_name, udf, adf) + + print_assignment_summary(udf, adf, plot_name, + out_doc=None if args.test else str(work_dir / "assignment_summary.md"), + out_tsv=None if args.test else str(work_dir / "assignment_summary.tsv")) + + # Copy injection_results.tsv to OUT/ for quick access after a full run + if not args.test: + import shutil + shutil.copy2(work_dir / "injection_results.tsv", out_dir / "injection_results.tsv") + print(f"Copied injection_results.tsv → {out_dir}/") + + +if __name__ == "__main__": + main() diff --git a/scripts/injection/html/doc.html b/scripts/injection/html/doc.html new file mode 100644 index 0000000..dae5028 --- /dev/null +++ b/scripts/injection/html/doc.html @@ -0,0 +1,268 @@ + + + + + + Documentation — unit injection + + + + + +
+

Unit injection exploration

+

The goal is to identify lab measurements that have a numeric value but are missing a unit, and to characterise the unit distribution of the matching records that do have a unit — so that a unit can be confidently assigned to the missing ones.

+

Summary

+

scatter

+

Active threshold: 98% — min count: 500 — 715 TEST_NAMEs, 31,962,504 measurements

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ThresholdUNAMBIGUOUS test namesUNAMBIGUOUS measurementsAMBIGUOUS test namesAMBIGUOUS measurementsNO_DATA test namesNO_DATA measurements
95%434 (60.7%)26,508,342 (82.9%)79 (11.0%)4,345,264 (13.6%)202 (28.3%)1,108,898 (3.5%)
98% *413 (57.8%)24,979,594 (78.2%)100 (14.0%)5,874,012 (18.4%)202 (28.3%)1,108,898 (3.5%)
99%392 (54.8%)22,954,996 (71.8%)121 (16.9%)7,898,610 (24.7%)202 (28.3%)1,108,898 (3.5%)
100%275 (38.5%)12,745,902 (39.9%)238 (33.3%)18,107,704 (56.7%)202 (28.3%)1,108,898 (3.5%)
TOTAL715
+
+

Usage

+
python3 explore_test_name.py <parquet_file> [options]
+
+ +

Caching behaviour

+

test_name_counts.tsv and test_name_details.tsv are cached after the first ClickHouse query and reused on subsequent runs — delete them to force a re-query. They are always queried with a fixed baseline of COUNT > 50; --min-count is applied as a post-load filter in Python, so changing it never requires a re-query.

+

plot_name_level.tsv, test_names_exploration_scatter.png, and summary_table.md are always recomputed from the filtered data.

+

Per-test .npy arrays are always cached in --dump-dir and reused across runs.

+
+

Step 1 — Injection targets (test_name_counts.tsv)

+

Counts records where at least one value is present (MEASUREMENT_VALUE_EXTRACTED IS NOT NULL OR MEASUREMENT_VALUE_SOURCE IS NOT NULL) but the unit prefix is absent (MEASUREMENT_UNIT_PRE_FIX IS NULL), grouped by TEST_NAME. Cached with COUNT > 50; further filtered to --min-count at runtime.

+

Columns: TEST_NAME, COUNT

+
+

Step 2 — Reference population (test_name_details.tsv)

+

For every TEST_NAME in Step 1, describes the unit distribution of the records that already have both a value and a unit (MEASUREMENT_VALUE_SOURCE IS NOT NULL AND MEASUREMENT_UNIT_PRE_FIX IS NOT NULL).

+

Columns: TEST_NAME, COUNT, UNIT, PREVALENCE_DICT

+ +
+

Step 3 — Plotting table, scatter plot, and classification

+

Steps 1 and 2 are merged in Python to produce plot_name_level.tsv, a scatter plot, and a summary table. Each TEST_NAME is assigned a CATEGORY that drives which injection pass it enters.

+

plot_name_level.tsv

+

One row per TEST_NAME. Columns include COUNT, N_WITH_UNIT, top_prevalence, CATEGORY, and exploratory CATEGORY_{95,98,99,100} columns.

+ +

Classification (CATEGORY)

+ + + + + + + + + + + + + + + + + + + + + +
CategoryCondition
NO_DATAN_WITH_UNIT < --min-target-n OR top_prevalence == 0
UNAMBIGUOUStop_prevalence >= --prevalence-threshold
AMBIGUOUS0 < top_prevalence < --prevalence-threshold
+

The injection engine only runs tests classified as UNAMBIGUOUS or AMBIGUOUS. NO_DATA tests are written directly to no_data_results.tsv.

+

OMOP unit table (omop_unit_table.tsv)

+

Built once from the OMOP LAB mapping files and enriched with per-concept record counts queried from the parquet file. Cached at --omop-unit-table. Each OMOP concept is assigned a CATEGORY:

+ +

This table is used to enrich no_data_results.tsv with canonical units.

+

Two summary tables are written: +- summary_table.md — all TEST_NAMEs +- omop_summary_table.md — OMOP-mapped subset only

+
+

Step 4 — Injection engine (--inject)

+

With --inject, three passes are run and a coverage check validates that every TEST_NAME in plot_name_level.tsv appears in exactly one output file.

+

Unambiguous pass → unambiguous_results.tsv

+

TEST_NAMEs with CATEGORY == UNAMBIGUOUS. The candidate distribution (no-unit records) is compared against the reference distribution for the dominant unit.

+

One row per TEST_NAME. Columns: TEST_NAME, UNIT, PREVALENCE_DICT, N_CANDIDATE, N_TARGET, CAND_DECILES, TARG_DECILES, KS_STAT, KS_MLOGP, KS_PASS, T_STAT, T_MLOGP, T_PASS, MAD_DIST, MAD_THRESHOLD, MAD_PASS, OUTCOME, NOTES.

+

Ambiguous pass → ambiguous_results.tsv

+

TEST_NAMEs with CATEGORY == AMBIGUOUS. Only units with prevalence > 1% and at least --min-target-n reference records are considered.

+

Pipeline per TEST_NAME:

+
    +
  1. Pre-check: run the full (unsplit) candidate distribution against each qualifying unit.
  2. +
  3. Bimodality check (always runs, even if pre-check passed): test the candidate distribution for bimodality and compute split_improvement — the relative KS gain when the candidate is split at the GMM separator vs. treated globally.
  4. +
  5. Split decision: a split is preferred if split_improvement > --split-threshold AND the two halves favour different best units (same_best_unit == False). If splitting is not preferred and any pre-check passed, the global result is kept.
  6. +
  7. Sub-distribution engine (only if splitting is preferred): the candidate is split into low/high halves at the GMM separator and the engine is re-run on each half × unit.
  8. +
+

One row per (TEST_NAME, SUB_DIST) — the best unit only. Best unit selection: PASS > FAIL > SKIP, then highest UNIT_PREVALENCE as tiebreaker — we prefer the dominant clinical unit when two units both pass.

+

A split_eval_{tag}.png decision-tree figure is saved to --dump-dir for every TEST_NAME.

+

Columns: TEST_NAME, BIMODAL_STATUS, BIMODAL_SEP, BIMODAL_BC, BIMODAL_DIP_P, SCORE_GLOBAL, SCORE_SPLIT, SCORE_IMPROVEMENT, SUB_DIST, UNIT, UNIT_PREVALENCE, PREVALENCE_DICT, BEST_UNIT, N_CANDIDATE, N_TARGET, CAND_DECILES, TARG_DECILES, KS_STAT, KS_MLOGP, KS_PASS, T_STAT, T_MLOGP, T_PASS, MAD_DIST, MAD_THRESHOLD, MAD_PASS, OUTCOME, NOTES.

+ +

No-data pass → no_data_results.tsv

+

TEST_NAMEs with CATEGORY == NO_DATA. No engine is run. The result is enriched with OMOP unit table information; a canonical unit is injected where the OMOP concept has category SINGLE or EQUIVALENT.

+

Columns: TEST_NAME, COUNT, OMOP_CONCEPT_ID, OMOP_QUANTITY, CATEGORY, N_UNITS, UNITS, CONVERSIONS, OMOP_TOTAL_N, UNIT, PREVALENCE.

+ +

After writing, a breakdown is printed: +- no OMOP — TEST_NAMEs with no concept ID +- OMOP, unit injected — concept has SINGLE/EQUIVALENT category +- OMOP, no unit — concept mapped but MULTIPLE or unknown unit

+

Unified output → injection_results.tsv

+

Merge of the unambiguous and ambiguous results with a TYPE column (unambiguous / ambiguous). Does not include no_data rows.

+

Coverage check

+

After all three passes, a checksum validates that the union of the three output files equals the full set of TEST_NAMEs in plot_name_level.tsv, with no overlaps and no missing entries. Skipped in --test mode.

+

Assignment summary

+

Printed at the end of --inject. Shows, for each category (UNAMBIGUOUS, AMBIGUOUS, NO_DATA) and in total, how many TEST_NAMEs and measurements received a unit (PASS rows), broken out for all TEST_NAMEs and the OMOP-mapped subset.

+
+

Injection engine pipeline

+

Each comparison runs three tests in order. All three always run; the first to decide the outcome wins.

+
    +
  1. KS test — two-sample Kolmogorov–Smirnov. PASS = stat < 0.3 AND p < 0.05. Uses a fast binned approximation (100k bins, Hodges-corrected asymptotic p) above 500k samples; exact scipy otherwise.
  2. +
  3. Welch t-test — fallback if KS fails. PASS = p ≥ 0.05 (means not significantly different).
  4. +
  5. MAD test — last resort. PASS = |median(candidate) − median(target)| ≤ 3 × MAD(target).
  6. +
+

Decision rule: KS PASS → PASS. KS FAIL, T PASS → PASS. Both fail → MAD decides. NOTES records the deciding test and how many of the three passed, e.g. PASS_at_T_(2/3).

+

P-values are stored as −log10(p) throughout (KS_MLOGP, T_MLOGP).

+

Each comparison produces a 3-panel diagnostic plot saved to --dump-dir: +- Panel 1: ECDFs + KS distance marked in red + KS annotation +- Panel 2: KDE (linear scale) + dotted mean lines + t-test annotation +- Panel 3: KDE (log scale) + MAD band (green = PASS, salmon = FAIL) + median lines + distance arrow

+

KDE/ECDF rendering downsamples to 50k points. All statistics use the full arrays.

+
+

Bimodal check

+

Before the ambiguous sub-distribution pass, the candidate distribution is tested for bimodality using two statistics:

+

The candidate array is subsampled to 50,000 points before the dip test and GMM fitting (the dip test is unreliable above ~72k samples and GMM fitting is also faster at this size). All statistics and the separator are computed on this subsample.

+

Hartigan's dip test (primary gate): p-value < --dip-threshold (default 0.05) declares non-unimodal.

+

Bimodality coefficient (BC): (skew² + 1) / (excess_kurtosis + 3(n−1)²/((n−2)(n−3))). Values above ~0.555 suggest bimodality.

+

Both are computed in the space (linear or log) where a 2-component GMM achieves lower BIC. The GMM separator is used to split the candidate distribution.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
dip pBCStatus
≥ thresholdunimodal
< threshold≥ 0.555bimodal — split into low/high
< threshold< 0.555bimodal_cautious — split, modes overlap
skipped — pre-check passed and split not preferred
+

split_by_score is a separate label (not dip-test-derived) assigned when the split is triggered by score improvement (SCORE_IMPROVEMENT > --split-threshold and the two halves favour different units), regardless of dip test outcome.

+

A diagnostic plot (bimodal_{tag}.png) is saved to --dump-dir.

+ + diff --git a/scripts/injection/html/index.html b/scripts/injection/html/index.html new file mode 100644 index 0000000..c25f724 --- /dev/null +++ b/scripts/injection/html/index.html @@ -0,0 +1,203 @@ + + + + + + Unit injection results + + + + + + +
+

Unit injection results

+ +
Scatter: TEST_NAMEs by count and prevalence
+ +

Category summary

Active threshold: 98% — min count: 500 — 715 TEST_NAMEs, 31,962,504 measurements

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ThresholdUNAMBIGUOUS test namesUNAMBIGUOUS measurementsAMBIGUOUS test namesAMBIGUOUS measurementsNO_DATA test namesNO_DATA measurements
95%434 (60.7%)26,508,342 (82.9%)79 (11.0%)4,345,264 (13.6%)202 (28.3%)1,108,898 (3.5%)
98% *413 (57.8%)24,979,594 (78.2%)100 (14.0%)5,874,012 (18.4%)202 (28.3%)1,108,898 (3.5%)
99%392 (54.8%)22,954,996 (71.8%)121 (16.9%)7,898,610 (24.7%)202 (28.3%)1,108,898 (3.5%)
100%275 (38.5%)12,745,902 (39.9%)238 (33.3%)18,107,704 (56.7%)202 (28.3%)1,108,898 (3.5%)
TOTAL715

Assignment summary


+Assignment summary (successfully assigned)
+
+                      UNAMBIGUOUS                   AMBIGUOUS                     NO_DATA                       TOTAL           
+               names                 meas   names                 meas   names                 meas   names                 meas
+              ------  -------------------  ------  -------------------  ------  -------------------  ------  -------------------
+All            10/10  2,800,937/2,800,937   10/10  5,300,918/5,300,918  28/202    285,698/1,108,898  48/222  8,387,553/9,210,753
+OMOP-mapped      9/9  2,800,434/2,800,434   10/10  5,300,918/5,300,918   28/66      285,698/802,091   47/85  8,387,050/8,903,443
+              ------  -------------------  ------  -------------------  ------  -------------------  ------  -------------------
+
+ +

Per-column search: text columns support regex (e.g. ^PASS$, + mmol). Numeric columns support comparisons: <3, + >=20, !=0. The global box top-right searches all columns with regex.

+ +
+ + + + + + +
Test nameTypeCutoffOutcomeUnitUnit prev (%)N candN targetKS statKS −log10pKS passT statT −log10pT passMAD distMAD thrMAD passNotes
+
+ + + + + + + diff --git a/scripts/injection/injection_engine.py b/scripts/injection/injection_engine.py new file mode 100644 index 0000000..6c997ec --- /dev/null +++ b/scripts/injection/injection_engine.py @@ -0,0 +1,886 @@ +#!/usr/bin/env python3 +""" +injection_engine.py + +Tests whether a candidate value distribution is compatible with a target +(reference) distribution for unit injection. + +Pipeline +-------- +1. KS test : stat < ks_threshold AND p < sig_level → PASS + (small effect size + sufficient data to trust the assessment) +2. Welch t : runs when KS fails; p ≥ sig_level (not significantly different) → PASS +3. MAD : last resort — |median(candidate) − median(target)| ≤ n_mad × MAD(target) → PASS + → FAIL + +All three tests always run (for reporting); decision order is KS → T → MAD. + +Usage +----- + injection_engine.py CANDIDATE TARGET [options] + injection_engine.py --test-mode [options] +""" + +import argparse +import os +import sys +from dataclasses import dataclass, field + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +from scipy import stats +from scipy.special import kolmogorov as _kolmogorov + +try: + from sklearn.mixture import GaussianMixture as _GaussianMixture + _GMM_AVAILABLE = True +except ImportError: + _GMM_AVAILABLE = False + + +# --------------------------------------------------------------------------- +# Result types +# --------------------------------------------------------------------------- + +def _fmt(v): + if not isinstance(v, float): + return str(v) + if v == 0.0 or abs(v) < 1e-3 or abs(v) >= 1e5: + return f"{v:.4e}" + return f"{v:.4f}" + + +@dataclass +class StepResult: + name: str + passed: bool + details: dict = field(default_factory=dict) # raw values (floats/ints) + + def __str__(self): + kv = " ".join(f"{k}={_fmt(v)}" for k, v in self.details.items()) + return f"[{self.name:<4}] {'PASS' if self.passed else 'FAIL'} {kv}" + + +@dataclass +class PipelineResult: + outcome: str # "PASS" or "FAIL" + decided_by: str # "KS", "T", or "MAD" + steps: list = field(default_factory=list) + + def __str__(self): + lines = [f"Outcome : {self.outcome} (decided by {self.decided_by})"] + for s in self.steps: + lines.append(f" {s}") + return "\n".join(lines) + + +# --------------------------------------------------------------------------- +# Individual tests +# --------------------------------------------------------------------------- + +_BINNED_KS_N = 500_000 # switch to binned approximation above this size + + +def _binned_ks(data1, data2, bins=100_000): + """ + Fast approximate two-sample KS test via histograms. + + Stat approximation error ≤ 1/bins ≈ 1e-5 (negligible vs threshold 0.3). + P-value uses the Hodges-corrected asymptotic Kolmogorov distribution, + identical to scipy ks_2samp(method='asymp'). + """ + n1, n2 = len(data1), len(data2) + lo = min(data1.min(), data2.min()) + hi = max(data1.max(), data2.max()) + + if lo == hi: # all values identical — distributions are the same + return 0.0, 1.0 + + hist1, _ = np.histogram(data1, bins=bins, range=(lo, hi)) + hist2, _ = np.histogram(data2, bins=bins, range=(lo, hi)) + + ecdf1 = np.cumsum(hist1) / n1 + ecdf2 = np.cumsum(hist2) / n2 + + stat = float(np.max(np.abs(ecdf1 - ecdf2))) + + # Hodges (1958) correction — matches scipy's asymptotic formula exactly + en = np.sqrt(n1 * n2 / (n1 + n2)) + pval = float(np.clip(_kolmogorov((en + 0.12 + 0.11 / en) * stat), 0.0, 1.0)) + + return stat, pval + + +def _ks(candidate, target, ks_threshold, sig_level): + if max(len(candidate), len(target)) > _BINNED_KS_N: + stat, pval = _binned_ks(candidate, target) + method = "binned" + else: + stat, pval = stats.ks_2samp(candidate, target) + method = "exact" + passed = (stat < ks_threshold) and (pval < sig_level) + return StepResult("KS", passed, { + "stat": float(stat), + "ks_threshold": ks_threshold, + "pval": float(pval), + "sig_level": sig_level, + "method": method, + }) + + +def _t(candidate, target, sig_level): + stat, pval = stats.ttest_ind(candidate, target, equal_var=False) + passed = pval >= sig_level # PASS = means NOT significantly different → proceed to MAD + return StepResult("T", passed, { + "stat": float(stat), + "pval": float(pval), + "sig_level": sig_level, + }) + + +def _mad(candidate, target, n_mad): + t_median = np.median(target) + t_mad = float(stats.median_abs_deviation(target)) + c_median = np.median(candidate) + distance = abs(float(c_median) - float(t_median)) + threshold = n_mad * t_mad + return StepResult("MAD", distance <= threshold, { + "cand_median": float(c_median), + "target_median": float(t_median), + "MAD": t_mad, + "distance": distance, + "threshold": threshold, + "n_mad": n_mad, + }) + + +# --------------------------------------------------------------------------- +# Bimodal check +# --------------------------------------------------------------------------- + +@dataclass +class BimodalResult: + status: str # "unimodal" | "bimodal" | "bimodal_cautious" + separator: float # split point in original space (nan if unimodal) + bc: float + dip_p: float + lognormal: bool # which space had lower BIC + fit: dict # raw GMM output for the winning fit (empty dict if unavailable) + fit_alt: dict # raw GMM output for the losing fit (empty dict if unavailable) + overlap_pct: float = np.nan # GMM overlap coefficient as % (0=perfectly separated) + + def __str__(self): + ovl = f" overlap={self.overlap_pct:.1f}%" if not np.isnan(self.overlap_pct) else "" + return (f"[BIMODAL] {self.status} sep={self.separator:.4g}" + f" BC={self.bc:.3f} dip_p={self.dip_p:.3g}" + f" space={'log' if self.lognormal else 'linear'}{ovl}") + + +def _bimodality_coefficient(arr): + n = len(arr) + g = float(stats.skew(arr)) + k = float(stats.kurtosis(arr, fisher=True)) + if n > 3: + return (g**2 + 1) / (k + 3.0 * (n - 1)**2 / ((n - 2) * (n - 3))) + return np.nan + + +def _gmm_fit(arr, lognormal): + if not _GMM_AVAILABLE: + return None + if lognormal: + pos = arr[arr > 0] + if len(pos) < 10: + return None + x = np.log10(pos) + else: + x = arr + pos = arr + try: + gmm = _GaussianMixture(n_components=2, random_state=0, max_iter=300) + gmm.fit(x.reshape(-1, 1)) + except Exception: + return None + means = gmm.means_.flatten() + sigmas = np.sqrt(gmm.covariances_).flatten() + weights = gmm.weights_ + + # Separator: grid search between the two means + linear interpolation. + # fsolve is avoided because it can diverge badly with a poor starting point. + lo_m, hi_m = float(np.min(means)), float(np.max(means)) + span = max(hi_m - lo_m, 1e-10) + grid = np.linspace(lo_m - 0.1 * span, hi_m + 0.1 * span, 2000) + diff = (weights[0] * stats.norm.pdf(grid, means[0], sigmas[0]) + - weights[1] * stats.norm.pdf(grid, means[1], sigmas[1])) + inner = (grid >= lo_m) & (grid <= hi_m) + g_in, d_in = grid[inner], diff[inner] + cx = np.where(np.diff(np.sign(d_in)))[0] + if len(cx) > 0: + i = cx[0] + x0, x1, d0, d1 = g_in[i], g_in[i + 1], d_in[i], d_in[i + 1] + sep_native = float(x0 - d0 * (x1 - x0) / (d1 - d0)) + else: + sep_native = float(np.mean(means)) + # Overlap coefficient: ∫ min(w1·f1, w2·f2) dx on a fine grid covering ±4σ from each mean + lo_grid = min(means[0] - 4*sigmas[0], means[1] - 4*sigmas[1]) + hi_grid = max(means[0] + 4*sigmas[0], means[1] + 4*sigmas[1]) + g = np.linspace(lo_grid, hi_grid, 2000) + overlap_coef = float(np.trapz( + np.minimum(weights[0] * stats.norm.pdf(g, means[0], sigmas[0]), + weights[1] * stats.norm.pdf(g, means[1], sigmas[1])), + g, + )) + + return dict( + separator=float(10**sep_native) if lognormal else float(sep_native), + sep_native=sep_native, + means_native=means, sigmas_native=sigmas, weights=weights, + bic=float(gmm.bic(x.reshape(-1, 1))), + bc=float(_bimodality_coefficient(x)), + overlap_pct=float(overlap_coef * 100), + lognormal=lognormal, + x_fit=x, x_orig=pos, + ) + + +_BIMODAL_MAX_N = 50_000 # dip test is unreliable above 72k; GMM also faster + + +def bimodal_check(arr, dip_threshold=0.05, bc_threshold=0.555): + """ + Classify a distribution as unimodal / bimodal / bimodal_cautious. + + Decision logic: + - dip p >= dip_threshold → unimodal + - dip p < dip_threshold, BC >= bc_threshold → bimodal + - dip p < dip_threshold, BC < bc_threshold → bimodal_cautious (overlap) + - dip unavailable: fall back to BC alone + """ + arr = np.asarray(arr, dtype=float) + arr = arr[np.isfinite(arr)] + + if len(arr) > _BIMODAL_MAX_N: + rng = np.random.default_rng(42) + arr = arr[rng.choice(len(arr), size=_BIMODAL_MAX_N, replace=False)] + + fit_norm = _gmm_fit(arr, lognormal=False) + fit_log = _gmm_fit(arr, lognormal=True) + fits = [f for f in (fit_norm, fit_log) if f is not None] + + if not fits: + return BimodalResult("unimodal", np.nan, np.nan, np.nan, False, {}, {}) + + best = min(fits, key=lambda f: f["bic"]) + + # Dip test in the same space as the best GMM fit (log or linear) + dip_p = np.nan + try: + from diptest import diptest + _, dip_p = diptest(best["x_fit"]) + dip_p = float(dip_p) + except ImportError: + pass + bc = best["bc"] + sep = best["separator"] + + if not np.isnan(dip_p): + if dip_p >= dip_threshold: + status = "unimodal" + else: + status = "bimodal" if bc >= bc_threshold else "bimodal_cautious" + else: + status = "bimodal" if bc >= bc_threshold else "unimodal" + + alt = next((f for f in fits if f is not best), {}) + return BimodalResult(status, sep, bc, dip_p, best["lognormal"], best, alt, + overlap_pct=best.get("overlap_pct", np.nan)) + + +def compute_bimodal_plot_data(result): + """Extract compact data for the bimodal diagnostic plot.""" + if not result.fit: + return None + + def _fit_data(fit): + if not fit: + return None + x = fit["x_fit"] + lo, hi = float(np.percentile(x, 0.5)), float(np.percentile(x, 99.5)) + xs = np.linspace(lo, hi, _KDE_PTS) + curves = [] + for m, s, w in zip(fit["means_native"], fit["sigmas_native"], fit["weights"]): + from scipy import stats as _stats + curves.append({"y": (w * _stats.norm.pdf(xs, m, s)).tolist(), + "mean": float(m), "sigma": float(s), "weight": float(w)}) + # histogram + counts, edges = np.histogram(x, bins=40, range=(lo, hi), density=True) + return { + "lognormal": fit["lognormal"], + "bic": float(fit["bic"]), + "sep_native": float(fit["sep_native"]) if not np.isnan(fit["sep_native"]) else None, + "x": xs.tolist(), + "hist_counts": counts.tolist(), + "hist_edges": edges.tolist(), + "curves": curves, + } + + return { + "status": result.status, + "separator": float(result.separator) if not np.isnan(result.separator) else None, + "bc": float(result.bc), + "dip_p": float(result.dip_p) if not np.isnan(result.dip_p) else None, + "winner": "log" if result.lognormal else "linear", + "linear": _fit_data(result.fit if not result.fit.get("lognormal") else result.fit_alt), + "log": _fit_data(result.fit if result.fit.get("lognormal") else result.fit_alt), + } + + +def _plot_bimodal_panel(ax, fit, is_winner, result_separator): + """Draw one GMM panel (linear or log space) onto ax.""" + if not fit: + ax.text(0.5, 0.5, "fit unavailable", ha="center", va="center", + transform=ax.transAxes, color="grey") + return + + x_fit = fit["x_fit"] + x_orig = fit["x_orig"] + means = fit["means_native"] + sigmas = fit["sigmas_native"] + weights = fit["weights"] + lognormal = fit["lognormal"] + + lo, hi = np.percentile(x_fit, [0.5, 99.5]) + ax.hist(x_fit, bins=60, density=True, alpha=0.45, color="steelblue", range=(lo, hi)) + xf = np.linspace(lo, hi, 400) + for m, s, w in zip(means, sigmas, weights): + ax.plot(xf, w * stats.norm.pdf(xf, m, s), alpha=0.8) + if not np.isnan(fit["sep_native"]): + ax.axvline(fit["sep_native"], color="red", ls="--", + label=f"sep={fit['sep_native']:.3g}") + + space = "log" if lognormal else "linear" + ax.set_xlabel("log₁₀(value)" if lognormal else "value") + title = f"{space} space BIC={fit['bic']:.1f}" + ax.set_title(title, fontweight="bold" if is_winner else "normal", + color="black" if is_winner else "grey") + ax.legend(fontsize=8) + + if is_winner: + ax.spines[["top", "right", "bottom", "left"]].set_linewidth(2.5) + for spine in ax.spines.values(): + spine.set_edgecolor("#2ca02c") + ax.text(0.98, 0.97, "✓ winner", ha="right", va="top", + transform=ax.transAxes, fontsize=9, color="#2ca02c", fontweight="bold") + + +def plot_bimodal_check(result, name, dump_dir): + """ + Save bimodal diagnostic to dump_dir/bimodal_{tag}.png. + Always shows both linear and log panels; winner is highlighted in green. + No-op if GMM failed entirely. + """ + if not result.fit: + return + tag = name.replace("/", "_").replace(" ", "_") + out_path = os.path.join(dump_dir, f"bimodal_{tag}.png") + if os.path.exists(out_path): + return + + fit_win = result.fit + fit_alt = result.fit_alt + + # Assign panels: left=linear, right=log + fit_linear = fit_win if not fit_win["lognormal"] else fit_alt + fit_log = fit_win if fit_win["lognormal"] else fit_alt + + fig, axes = plt.subplots(1, 2, figsize=(12, 4)) + fig.suptitle( + f"{name} — {result.status} sep={result.separator:.4g}" + f" BC={result.bc:.3f} dip_p={result.dip_p:.3g}" + f" winner={'log' if result.lognormal else 'linear'}", + fontsize=10, fontweight="bold", + ) + + _plot_bimodal_panel(axes[0], fit_linear, is_winner=not result.lognormal, + result_separator=result.separator) + _plot_bimodal_panel(axes[1], fit_log, is_winner=result.lognormal, + result_separator=result.separator) + + plt.tight_layout() + plt.savefig(out_path, dpi=120, bbox_inches="tight") + plt.close(fig) + + +# --------------------------------------------------------------------------- +# Split improvement scoring +# --------------------------------------------------------------------------- + +def _ks_stat_only(a, b): + """KS statistic as a float (no p-value).""" + if max(len(a), len(b)) > _BINNED_KS_N: + return _binned_ks(a, b)[0] + return float(stats.ks_2samp(a, b).statistic) + + +def rank_units_by_ks(c, unit_vals): + """ + c : candidate array + unit_vals : {unit: t_vals} + Returns list of (unit, ks_stat) sorted ascending by ks_stat. + """ + return sorted( + ((unit, _ks_stat_only(c, t)) for unit, t in unit_vals.items()), + key=lambda x: x[1], + ) + + +def split_improvement(c_vals, c_low, c_high, unit_vals): + """ + Compare size-weighted split KS against the global best KS. + + unit_vals : {unit: t_vals} + + Returns dict: + global_score, global_ranks, low_ranks, high_ranks, + split_score, improvement (positive = split is better), + same_best_unit (True suggests intrinsic bimodality) + """ + global_ranks = rank_units_by_ks(c_vals, unit_vals) + global_score = global_ranks[0][1] if global_ranks else np.nan + + low_ranks = rank_units_by_ks(c_low, unit_vals) if len(c_low) >= 2 else [] + high_ranks = rank_units_by_ks(c_high, unit_vals) if len(c_high) >= 2 else [] + + if not low_ranks or not high_ranks or np.isnan(global_score): + return dict( + global_score=global_score, global_ranks=global_ranks, + low_ranks=low_ranks, high_ranks=high_ranks, + split_score=np.nan, improvement=0.0, same_best_unit=True, + ) + + n_total = len(c_low) + len(c_high) + s_score = (len(c_low) * low_ranks[0][1] + len(c_high) * high_ranks[0][1]) / n_total + impr = (global_score - s_score) / global_score if global_score > 0 else 0.0 + + return dict( + global_score=global_score, global_ranks=global_ranks, + low_ranks=low_ranks, high_ranks=high_ranks, + split_score=s_score, improvement=impr, + same_best_unit=low_ranks[0][0] == high_ranks[0][0], + ) + + +# --------------------------------------------------------------------------- +# Pipeline +# --------------------------------------------------------------------------- + +def run_pipeline(candidate, target, ks_threshold=0.3, sig_level=0.05, n_mad=3.0): + """ + Run the injection compatibility pipeline. + Returns a PipelineResult with outcome ("PASS"/"FAIL") and per-step details. + details dicts contain raw floats, suitable for direct programmatic access. + """ + ks = _ks(candidate, target, ks_threshold, sig_level) + t = _t(candidate, target, sig_level) + mad = _mad(candidate, target, n_mad) + steps = [ks, t, mad] + + if ks.passed: + decided_by, outcome = "KS", "PASS" + elif t.passed: + decided_by, outcome = "T", "PASS" + else: + decided_by = "MAD" + outcome = "PASS" if mad.passed else "FAIL" + + return PipelineResult(outcome, decided_by, steps) + + +# --------------------------------------------------------------------------- +# Test generation +# --------------------------------------------------------------------------- + +def _generate_test_cases(n=1000, seed=42): + """ + Synthetic cases designed to exercise each outcome in the pipeline. + + Expected results with default thresholds (ks=0.3, sig=0.05, n_mad=3): + + pass_ks → PASS via KS : N(0.3,1) vs N(0,1) — small stat (~0.12), p << 0.05 + fail_all → FAIL via MAD : N(5,1) vs N(0,1) — different means, median also far + pass_T → PASS via T : N(0,0.1) vs N(0,1) — different shape, same mean (T gates before MAD) + pass_T2 → PASS via T : lognormal(0,σ=2) shifted to mean=0 vs N(0,1) + mean=0 by construction so T passes; MAD would fail (median≈−6.4) + """ + rng = np.random.default_rng(seed) + target = rng.normal(0.0, 1.0, n) + + c_pass_ks = rng.normal(0.3, 1.0, n) + c_fail_t = rng.normal(5.0, 1.0, n) + c_pass_mad = rng.normal(0.0, 0.1, n) + + # lognormal(μ=0, σ=2): E[X] = e^2 ≈ 7.39, median = 1 + # subtract mean → mean=0, median = 1 − e^2 ≈ −6.39 + c_fail_mad = rng.lognormal(mean=0.0, sigma=2.0, size=n) - np.exp(2.0) + + return [ + ("pass_ks [expect: PASS via KS ]", c_pass_ks, target), + ("fail_all [expect: FAIL via MAD]", c_fail_t, target), + ("pass_T [expect: PASS via T ]", c_pass_mad, target), + ("pass_T2 [expect: PASS via T ]", c_fail_mad, target), + ] + + +# --------------------------------------------------------------------------- +# I/O +# --------------------------------------------------------------------------- + +def _load_values(path): + """Load a 1-D float array from a .npy file or a text/TSV file.""" + if path.endswith(".npy"): + return np.load(path) + values = [] + with open(path) as f: + for line in f: + line = line.strip() + if line: + values.append(float(line.split("\t")[0])) + return np.array(values) + + +# --------------------------------------------------------------------------- +# Diagnostics plot +# --------------------------------------------------------------------------- + +_KDE_PTS = 80 # points per KDE curve for HTML rendering +_ECDF_PTS = 100 # points sampled from each ECDF for HTML rendering + + +def compute_plot_data(candidate, target, result, prevalence=None): + """ + Extract the minimal numeric data needed to reproduce the 3-panel diagnostic + plot in a browser. Returns a plain dict suitable for JSON serialisation. + """ + rng = np.random.default_rng(42) + c_plot = _plot_sample(candidate, rng) + t_plot = _plot_sample(target, rng) + + ks_step = next((s for s in result.steps if s.name == "KS"), None) + t_step = next((s for s in result.steps if s.name == "T"), None) + mad_step = next((s for s in result.steps if s.name == "MAD"), None) + + # --- Panel 1: ECDF (subsample to _ECDF_PTS evenly-spaced quantiles) --- + def _ecdf_compact(arr): + x, y = _ecdf(arr) + idx = np.unique(np.linspace(0, len(x) - 1, _ECDF_PTS, dtype=int)) + return x[idx].tolist(), y[idx].tolist() + + cx, cy = _ecdf_compact(c_plot) + tx, ty = _ecdf_compact(t_plot) + + ks_marker = None + if ks_step: + all_x = np.sort(np.unique(np.concatenate([np.array(cx), np.array(tx)]))) + c_full = np.searchsorted(np.array(cx), all_x, side="right") / len(cx) + t_full = np.searchsorted(np.array(tx), all_x, side="right") / len(tx) + i_max = int(np.argmax(np.abs(c_full - t_full))) + ks_marker = {"x": float(all_x[i_max]), + "y_lo": float(min(c_full[i_max], t_full[i_max])), + "y_hi": float(max(c_full[i_max], t_full[i_max]))} + + # --- Panel 2: KDE linear --- + def _kde_compact(arr, n=_KDE_PTS): + if len(arr) < 2 or np.std(arr) == 0: + return [], [] + kde = stats.gaussian_kde(arr) + xs = np.linspace(arr.min(), arr.max(), n) + return xs.tolist(), kde(xs).tolist() + + c_kde_x, c_kde_y = _kde_compact(c_plot) + t_kde_x, t_kde_y = _kde_compact(t_plot) + + # --- Panel 3: KDE log --- + c_pos = c_plot[c_plot > 0] + t_pos = t_plot[t_plot > 0] + c_log_x, c_log_y = _kde_compact(np.log10(c_pos)) if len(c_pos) > 1 else ([], []) + t_log_x, t_log_y = _kde_compact(np.log10(t_pos)) if len(t_pos) > 1 else ([], []) + + mad_info = None + if mad_step: + d = mad_step.details + mad_info = { + "c_median": float(d["cand_median"]), + "t_median": float(d["target_median"]), + "threshold": float(d["threshold"]), + "distance": float(d["distance"]), + "MAD": float(d["MAD"]), + "n_mad": float(d["n_mad"]), + "passed": mad_step.passed, + } + + return { + "outcome": result.outcome, + "decided_by": result.decided_by, + "prevalence": str(prevalence) if prevalence else None, + "n_candidate": len(candidate), + "n_target": len(target), + "ecdf": { + "c_x": cx, "c_y": cy, + "t_x": tx, "t_y": ty, + "ks_marker": ks_marker, + "ks": {"stat": float(ks_step.details["stat"]), + "mlogp": float(-np.log10(np.clip(ks_step.details["pval"], 1e-300, 1.0))), + "passed": ks_step.passed} if ks_step else None, + }, + "kde_linear": { + "c_x": c_kde_x, "c_y": c_kde_y, + "t_x": t_kde_x, "t_y": t_kde_y, + "c_mean": float(np.mean(c_plot)), + "t_mean": float(np.mean(t_plot)), + "t": {"stat": float(t_step.details["stat"]), + "mlogp": float(-np.log10(np.clip(t_step.details["pval"], 1e-300, 1.0))), + "passed": t_step.passed} if t_step else None, + }, + "kde_log": { + "c_x": c_log_x, "c_y": c_log_y, + "t_x": t_log_x, "t_y": t_log_y, + "mad": mad_info, + }, + } + +def _ecdf(arr): + x = np.sort(arr) + y = np.arange(1, len(x) + 1) / len(x) + return x, y + + +def _kde(ax, arr, label, color): + if len(arr) < 2 or np.std(arr) == 0: + return + kde = stats.gaussian_kde(arr) + xs = np.linspace(arr.min(), arr.max(), 400) + ys = kde(xs) + ax.plot(xs, ys, color=color, label=label, alpha=0.85) + ax.fill_between(xs, ys, color=color, alpha=0.15) + + +_PLOT_MAX_N = 50_000 # downsample to this many points for KDE/ECDF plots only + + +def _plot_sample(arr, rng): + if len(arr) > _PLOT_MAX_N: + return arr[np.sort(rng.choice(len(arr), size=_PLOT_MAX_N, replace=False))] + return arr + + +def plot_result(candidate, target, result, name, dump_dir, prevalence=None, + tag_override=None): + """ + Save a 3-panel diagnostic figure to dump_dir/plot_.png. + Skips if the file already exists. + + Panels + ------ + 1. Empirical CDFs with KS distance marked + 2. KDE in linear scale + t-test statistics + 3. KDE in log scale + MAD statistics + """ + if tag_override: + tag = tag_override + else: + tag = name.replace("/", "_").replace(" ", "_") + out_path = os.path.join(dump_dir, f"plot_{tag}.png") + if os.path.exists(out_path): + return + + rng = np.random.default_rng(42) + c_plot = _plot_sample(candidate, rng) + t_plot = _plot_sample(target, rng) + sampled = len(candidate) > _PLOT_MAX_N or len(target) > _PLOT_MAX_N + + ks_step = next((s for s in result.steps if s.name == "KS"), None) + t_step = next((s for s in result.steps if s.name == "T"), None) + mad_step = next((s for s in result.steps if s.name == "MAD"), None) + + fig, axes = plt.subplots(1, 3, figsize=(18, 5)) + sample_note = f" [plot: {_PLOT_MAX_N:,} sampled]" if sampled else "" + prev_note = f"\n{prevalence}" if prevalence and str(prevalence) not in ("NA", "nan") else "" + fig.suptitle(f"{name} — {result.outcome} at {result.decided_by}{sample_note}{prev_note}", + fontsize=11, fontweight="bold") + + _TXT = dict(transform=None, va="top", fontsize=9, + bbox=dict(boxstyle="round", facecolor="lightyellow", alpha=0.8)) + _TXT_GREY = dict(transform=None, va="top", fontsize=9, + bbox=dict(boxstyle="round", facecolor="lightgrey", alpha=0.6)) + + # ---- Panel 1: ECDFs + KS ---------------------------------------- + ax = axes[0] + cx, cy = _ecdf(c_plot) + tx, ty = _ecdf(t_plot) + ax.step(cx, cy, where="post", color="steelblue", label="candidate", alpha=0.85) + ax.step(tx, ty, where="post", color="darkorange", label="target", alpha=0.85) + + if ks_step: + d = ks_step.details + stat = d["stat"] + pval = d["pval"] + all_x = np.sort(np.unique(np.concatenate([cx, tx]))) + c_cdf = np.searchsorted(cx, all_x, side="right") / len(cx) + t_cdf = np.searchsorted(tx, all_x, side="right") / len(tx) + i_max = np.argmax(np.abs(c_cdf - t_cdf)) + x_ks = all_x[i_max] + y_lo, y_hi = sorted([c_cdf[i_max], t_cdf[i_max]]) + ax.plot([x_ks, x_ks], [y_lo, y_hi], color="red", lw=2.5, label=f"D = {stat:.4f}") + mlogp = -np.log10(np.clip(pval, 1e-300, 1.0)) + kw = {**_TXT, "transform": ax.transAxes} + ax.text(0.04, 0.96, + f"KS stat = {stat:.3g}\n-log10p = {mlogp:.1f}\n→ {'PASS' if ks_step.passed else 'FAIL'}", + **kw) + + ax.set_xlabel("value") + ax.set_ylabel("cumulative probability") + ax.set_title("Empirical CDFs") + ax.legend(fontsize=8) + + # ---- Panel 2: KDE linear + t-test ---------------------------------- + ax = axes[1] + _kde(ax, c_plot, "_nolegend_", "steelblue") + _kde(ax, t_plot, "_nolegend_", "darkorange") + c_mean = float(np.mean(c_plot)) + t_mean = float(np.mean(t_plot)) + ax.axvline(c_mean, color="steelblue", ls=":", lw=1.5, alpha=0.9, label=f"{c_mean:.3g}") + ax.axvline(t_mean, color="darkorange", ls=":", lw=1.5, alpha=0.9, label=f"{t_mean:.3g}") + + if t_step: + d = t_step.details + mlogp = -np.log10(np.clip(d["pval"], 1e-300, 1.0)) + kw = {**_TXT, "transform": ax.transAxes} + ax.text(0.04, 0.96, + f"t = {d['stat']:.3g}\n-log10p = {mlogp:.1f}\n→ {'PASS' if t_step.passed else 'FAIL'}", + **kw) + else: + kw = {**_TXT_GREY, "transform": ax.transAxes} + ax.text(0.04, 0.96, "t-test\nnot reached", **kw) + + ax.set_xlabel("value") + ax.set_ylabel("density") + ax.set_title("Distributions — linear scale") + ax.legend(fontsize=8) + + # ---- Panel 3: KDE log + MAD ---------------------------------------- + ax = axes[2] + c_pos = c_plot[c_plot > 0] + t_pos = t_plot[t_plot > 0] + + if mad_step: + d = mad_step.details + c_med = d["cand_median"] + t_med = d["target_median"] + thr = d["threshold"] + + lo = t_med - thr + hi = t_med + thr + all_pos = np.concatenate([c_pos, t_pos]) if len(c_pos) and len(t_pos) else np.array([1e-6]) + lo_log = np.log10(lo) if lo > 0 else np.log10(all_pos.min()) - 1 + hi_log = np.log10(hi) if hi > 0 else np.log10(all_pos.max()) + 1 + band_color = "limegreen" if mad_step.passed else "salmon" + ax.axvspan(lo_log, hi_log, alpha=0.18, color=band_color, + label=f"±{d['n_mad']:.0f}×MAD", zorder=0) + + if len(c_pos) > 1: + _kde(ax, np.log10(c_pos), "_nolegend_", "steelblue") + if len(t_pos) > 1: + _kde(ax, np.log10(t_pos), "_nolegend_", "darkorange") + + if mad_step: + lc = np.log10(c_med) if c_med > 0 else None + lt = np.log10(t_med) if t_med > 0 else None + if lc is not None: + ax.axvline(lc, color="steelblue", ls=":", lw=1.5, alpha=0.9, + label=f"{c_med:.3g}") + if lt is not None: + ax.axvline(lt, color="darkorange", ls=":", lw=1.5, alpha=0.9, + label=f"{t_med:.3g}") + if lc is not None and lt is not None: + ax.annotate("", xy=(lc, 0), xytext=(lt, 0), + arrowprops=dict(arrowstyle="<->", color="grey", lw=1.5), + annotation_clip=False) + ax.text((lc + lt) / 2, 0, f" {d['distance']:.3g}", + fontsize=7, va="bottom", color="grey") + kw = {**_TXT, "transform": ax.transAxes} + ax.text(0.04, 0.96, + f"MAD = {d['MAD']:.3g}\ndist = {d['distance']:.3g}\nthr = {d['threshold']:.3g}\n" + f"→ {'PASS' if mad_step.passed else 'FAIL'}", + **kw) + else: + kw = {**_TXT_GREY, "transform": ax.transAxes} + ax.text(0.04, 0.96, "MAD test\nnot reached", **kw) + + ax.set_xlabel("log₁₀(value)") + ax.set_ylabel("density") + ax.set_title("Distributions — log scale") + ax.legend(fontsize=8) + + plt.tight_layout() + plt.savefig(out_path, dpi=120, bbox_inches="tight") + plt.close(fig) + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def _build_parser(): + p = argparse.ArgumentParser( + description="Test whether a candidate distribution is compatible with a target for unit injection.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("candidate", nargs="?", + help="File with candidate values (.npy, one float per line, or first TSV column)") + p.add_argument("target", nargs="?", + help="File with target (reference) values") + p.add_argument("--ks-threshold", type=float, default=0.3, metavar="FLOAT", + help="KS statistic must be below this for KS PASS") + p.add_argument("--sig-level", type=float, default=0.05, metavar="FLOAT", + help="Significance level for KS p-value and Welch t-test") + p.add_argument("--n-mad", type=float, default=3.0, metavar="FLOAT", + help="Candidate median must lie within this many MADs of the target median") + p.add_argument("--test-mode", action="store_true", + help="Run synthetic test cases designed to fail at each pipeline step") + p.add_argument("--quiet", action="store_true", + help="Print only the final PASS/FAIL outcome") + return p + + +def main(): + args = _build_parser().parse_args() + + if args.test_mode: + cases = _generate_test_cases() + for label, candidate, target in cases: + result = run_pipeline(candidate, target, args.ks_threshold, args.sig_level, args.n_mad) + print(f"\n{'─' * 64}") + print(f"Case : {label}") + print(result) + print() + return + + if not args.candidate or not args.target: + _build_parser().error("provide CANDIDATE and TARGET files, or use --test-mode") + + candidate = _load_values(args.candidate) + target = _load_values(args.target) + result = run_pipeline(candidate, target, args.ks_threshold, args.sig_level, args.n_mad) + + if args.quiet: + print(result.outcome) + else: + print(result) + + sys.exit(0 if result.outcome == "PASS" else 1) + + +if __name__ == "__main__": + main() diff --git a/scripts/injection/omop_unit_table.py b/scripts/injection/omop_unit_table.py new file mode 100644 index 0000000..c6de758 --- /dev/null +++ b/scripts/injection/omop_unit_table.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +""" +omop_unit_table.py + +Build a per-OMOP-concept unit conversion table. + +Logic +----- +1. LABfi_ALL.usagi.csv → OMOP_CONCEPT_ID → omopQuantity (only these two fields matter) +2. quantity_source_unit_conversion.tsv → for each omopQuantity, what units exist + and what are their pairwise conversions? + +CATEGORY +-------- + SINGLE — only one unit defined for this quantity + EQUIVALENT — multiple units, all pairwise conversions = 1 + AMBIGUOUS — multiple units, at least one conversion ≠ 1 + NO_CONV — quantity known but not in conversion table + NO_QUANTITY — concept has no omopQuantity in LABfi + +Usage +----- + python3 omop_unit_table.py [--approved-only] [--out omop_unit_table.tsv] +""" + +import argparse +from pathlib import Path + +import pandas as pd + +DATA_DIR = Path("~/Dropbox/Projects/kanta_lab_preprocessing/src/kanta/finngen_qc/data").expanduser() + + +# --------------------------------------------------------------------------- +# Loaders +# --------------------------------------------------------------------------- + +def load_lab(path, approved_only=False): + """OMOP_CONCEPT_ID → (CONCEPT_NAME, omopQuantity). Units ignored.""" + df = pd.read_csv(path) + df = df[df["domainId"] == "Measurement"] + if approved_only: + df = df[df["mappingStatus"] == "APPROVED"] + df = df.rename(columns={ + "conceptId": "OMOP_CONCEPT_ID", + "conceptName": "CONCEPT_NAME", + "ADD_INFO:omopQuantity": "omop_quantity", + }) + return df[["OMOP_CONCEPT_ID", "CONCEPT_NAME", "omop_quantity"]].drop_duplicates() + + +def load_conversions(path): + df = pd.read_csv(path, sep="\t") + df = df.rename(columns={ + "omop_quantity": "omop_quantity", + "source_unit_valid": "unit_from", + "to_source_unit_valid": "unit_to", + "conversion": "factor", + }) + df = df[df["unit_from"].notna() & df["unit_to"].notna()] + df["factor"] = pd.to_numeric(df["factor"], errors="coerce") + return df[["omop_quantity", "unit_from", "unit_to", "factor"]] + + +# --------------------------------------------------------------------------- +# Core +# --------------------------------------------------------------------------- + +def build_table(lab_df, conv_df): + # Build per-quantity: valid units and conversion lookup + qty_units = {} # omop_quantity -> sorted list of units + conv_lookup = {} # (omop_quantity, unit_from, unit_to) -> factor + + for _, row in conv_df.iterrows(): + q = row["omop_quantity"] + qty_units.setdefault(q, set()) + if pd.notna(row["unit_from"]): + qty_units[q].add(row["unit_from"]) + if pd.notna(row["unit_to"]): + qty_units[q].add(row["unit_to"]) + if pd.notna(row["factor"]): + conv_lookup[(q, row["unit_from"], row["unit_to"])] = float(row["factor"]) + + qty_units = {q: sorted(us) for q, us in qty_units.items()} + + rows = [] + + for omop_id, grp in lab_df.groupby("OMOP_CONCEPT_ID"): + concept_name = grp["CONCEPT_NAME"].iloc[0] + omop_quantity = grp["omop_quantity"].iloc[0] + + if pd.isna(omop_quantity) or str(omop_quantity).strip() == "": + rows.append(dict( + OMOP_CONCEPT_ID=omop_id, CONCEPT_NAME=concept_name, + OMOP_QUANTITY=omop_quantity, N_UNITS=0, + UNITS="", CONVERSIONS="", CATEGORY="NO_QUANTITY", + CANONICAL_UNIT=pd.NA, + )) + continue + + units = qty_units.get(str(omop_quantity).strip(), []) + n_units = len(units) + + if n_units == 0: + rows.append(dict( + OMOP_CONCEPT_ID=omop_id, CONCEPT_NAME=concept_name, + OMOP_QUANTITY=omop_quantity, N_UNITS=0, + UNITS="", CONVERSIONS="", CATEGORY="NO_CONV", + CANONICAL_UNIT=pd.NA, + )) + continue + + if n_units == 1: + rows.append(dict( + OMOP_CONCEPT_ID=omop_id, CONCEPT_NAME=concept_name, + OMOP_QUANTITY=omop_quantity, N_UNITS=1, + UNITS=units[0], CONVERSIONS="1", CATEGORY="SINGLE", + CANONICAL_UNIT=units[0], + )) + continue + + # Multiple units — check pairwise conversions + factors = [] + missing = [] + for i, u1 in enumerate(units): + for u2 in units[i + 1:]: + fwd = conv_lookup.get((omop_quantity, u1, u2)) + rev = conv_lookup.get((omop_quantity, u2, u1)) + if fwd is not None: + factors.append(fwd) + elif rev is not None: + factors.append(rev) + else: + missing.append(f"{u1}↔{u2}") + + if missing: + category = "NO_CONV" + canonical = pd.NA + elif all(abs(f - 1.0) < 1e-9 for f in factors): + category = "EQUIVALENT" + canonical = units[0] # alphabetically first — all are equivalent + else: + category = "AMBIGUOUS" + canonical = pd.NA + + unique_factors = sorted(set(round(f, 8) for f in factors)) + rows.append(dict( + OMOP_CONCEPT_ID=omop_id, CONCEPT_NAME=concept_name, + OMOP_QUANTITY=omop_quantity, N_UNITS=n_units, + UNITS="|".join(units), + CONVERSIONS="|".join(str(f) for f in unique_factors), + CATEGORY=category, + CANONICAL_UNIT=canonical, + )) + + df = pd.DataFrame(rows).sort_values(["CATEGORY", "OMOP_CONCEPT_ID"]).reset_index(drop=True) + df["N_UNITS"] = df["N_UNITS"].astype("Int64") + return df + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + p = argparse.ArgumentParser( + description="Build per-OMOP-concept unit conversion table.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("--lab-file", default=str(DATA_DIR / "LABfi_ALL.usagi.csv")) + p.add_argument("--conv-file", default=str(DATA_DIR / "quantity_source_unit_conversion.tsv")) + p.add_argument("--out", default="omop_unit_table.tsv") + p.add_argument("--approved-only", action="store_true", + help="Only include APPROVED mappings") + args = p.parse_args() + + print(f"Loading lab mapping ({'APPROVED only' if args.approved_only else 'all statuses'})...") + lab_df = load_lab(args.lab_file, approved_only=args.approved_only) + print(f" {lab_df['OMOP_CONCEPT_ID'].nunique():>6} unique OMOP concepts") + + print("Loading conversion table...") + conv_df = load_conversions(args.conv_file) + print(f" {len(conv_df):>6} conversion entries") + + print("Building table...") + result = build_table(lab_df, conv_df) + + print(f"\nCategory breakdown ({len(result)} concepts total):") + for cat, n in result["CATEGORY"].value_counts().items(): + print(f" {cat:<15} {n:>5} ({100*n/len(result):.1f}%)") + + result.to_csv(args.out, sep="\t", index=False, na_rep="NA") + print(f"\nWrote {args.out}") + + +if __name__ == "__main__": + main() diff --git a/scripts/injection/parquet/build.sh b/scripts/injection/parquet/build.sh new file mode 100644 index 0000000..6e08ef9 --- /dev/null +++ b/scripts/injection/parquet/build.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +# build.sh +# Runs the two-step pipeline: +# 1. munged TSV.gz → clean TSV.gz (make_tsv_gzipped.sql) +# 2. clean TSV.gz → INJECT.parquet (make_parquet.sql) +# Outputs are written next to the input file. + +set -euo pipefail + +DEFAULT_INPUT=~/fg-3/kanta_v3/core/kanta_dev_2026_03_09.txt.gz + +if [[ $# -gt 1 ]]; then + echo "Usage: $0 [input.tsv.gz]" >&2 + exit 1 +fi + +INPUT="$(realpath "${1:-$DEFAULT_INPUT}")" +CLEAN_TSV="$PWD/clean.tsv.gz" +PARQUET="$PWD/INJECT.parquet" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "==> Step 1: $INPUT → $CLEAN_TSV" +clickhouse local \ + --query "$(cat "$SCRIPT_DIR/make_tsv_gzipped.sql")" \ + --param_filePathMungedTxtGz="$INPUT" \ + | gzip > "$CLEAN_TSV" + +echo "==> Step 2: $CLEAN_TSV → $PARQUET" +clickhouse local \ + --query "$(cat "$SCRIPT_DIR/make_parquet.sql")" \ + --param_filePathCleanTxtGz="$CLEAN_TSV" \ + > "$PARQUET" + +echo "Done. Output: $PARQUET" diff --git a/scripts/injection/parquet/make_parquet.sql b/scripts/injection/parquet/make_parquet.sql new file mode 100644 index 0000000..c909fb4 --- /dev/null +++ b/scripts/injection/parquet/make_parquet.sql @@ -0,0 +1,39 @@ +SELECT + ROW_ID :: Int64 AS ROW_ID, -- Cast to Int64 to ensure numerical sorting + FINNGENID, + SEX, + EVENT_AGE :: Float64 AS EVENT_AGE, + concat(APPROX_EVENT_DATETIME, ':00') :: DateTime64(3, 'UTC') AS APPROX_EVENT_DATETIME, + + -- OMOP harmonization + nullIf(OMOP_CONCEPT_ID, 'NA') :: Nullable(String) AS OMOP_CONCEPT_ID, + + -- Test identification + TEST_ID, + TEST_ID_IS_NATIONAL :: Bool AS TEST_ID_IS_NATIONAL, + + -- Test names + TEST_NAME, + TEST_NAME_SOURCE, + + -- Measurement values + nullIf(MEASUREMENT_UNIT_HARMONIZED, 'NA') :: Nullable(String) AS MEASUREMENT_UNIT_HARMONIZED, + nullIf(MEASUREMENT_VALUE_HARMONIZED, 'NA') :: Nullable(Float64) AS MEASUREMENT_VALUE_HARMONIZED, + nullIf(MEASUREMENT_VALUE_EXTRACTED, 'NA') :: Nullable(Float64) AS MEASUREMENT_VALUE_EXTRACTED, + nullIf(MEASUREMENT_VALUE_MERGED, 'NA') :: Nullable(Float64) AS MEASUREMENT_VALUE_MERGED, + nullIf(MEASUREMENT_UNIT_CLEANED, 'NA') :: Nullable(String) AS MEASUREMENT_UNIT_CLEANED, + nullIf(MEASUREMENT_UNIT_PRE_FIX, 'NA') :: Nullable(String) AS MEASUREMENT_UNIT_PRE_FIX, + nullIf(MEASUREMENT_VALUE_SOURCE, 'NA') :: Nullable(Float64) AS MEASUREMENT_VALUE_SOURCE, + nullIf(MEASUREMENT_UNIT_SOURCE, 'NA') :: Nullable(String) AS MEASUREMENT_UNIT_SOURCE, + + -- Coding systems + nullIf(CODING_SYSTEM_ORG, 'NA') :: Nullable(String) AS CODING_SYSTEM_ORG, + nullIf(CODING_SYSTEM_OID, 'NA') :: Nullable(String) AS CODING_SYSTEM_OID + +FROM file({filePathCleanTxtGz:String}, TSVWithNames) + +FORMAT Parquet +SETTINGS + input_format_tsv_use_best_effort_in_schema_inference = 0, + output_format_parquet_compression_method = 'zstd', + output_format_parquet_string_as_string = 1; diff --git a/scripts/injection/parquet/make_tsv_gzipped.sql b/scripts/injection/parquet/make_tsv_gzipped.sql new file mode 100644 index 0000000..4b5c785 --- /dev/null +++ b/scripts/injection/parquet/make_tsv_gzipped.sql @@ -0,0 +1,36 @@ +SELECT + ROW_ID :: Int64 AS ROW_ID, -- Cast to Int64 to ensure numerical sorting + FINNGENID, + SEX, + EVENT_AGE, + APPROX_EVENT_DATETIME, + + -- OMOP harmonization + if(`harmonization_omop::OMOP_ID` IN ('-1', '0'), 'NA', `harmonization_omop::OMOP_ID`) AS OMOP_CONCEPT_ID, + -- Test identification + TEST_ID, + TEST_ID_IS_NATIONAL, + + -- Test names (both cleaned and source) + `cleaned::TEST_NAME_ABBREVIATION` AS TEST_NAME, + `source::TEST_NAME_ABBREVIATION` AS TEST_NAME_SOURCE, + + `cleaned::MEASUREMENT_UNIT` AS MEASUREMENT_UNIT_CLEANED, + `cleaned-pre-fix::MEASUREMENT_UNIT` AS MEASUREMENT_UNIT_PRE_FIX, + + -- Measurement values (harmonized, extracted, merged, and source) + `harmonization_omop::MEASUREMENT_UNIT` AS MEASUREMENT_UNIT_HARMONIZED, + `harmonization_omop::MEASUREMENT_VALUE` AS MEASUREMENT_VALUE_HARMONIZED, + `extracted::MEASUREMENT_VALUE` AS MEASUREMENT_VALUE_EXTRACTED, + `extracted::MEASUREMENT_VALUE_MERGED` AS MEASUREMENT_VALUE_MERGED, + `source::MEASUREMENT_VALUE` AS MEASUREMENT_VALUE_SOURCE, + `source::MEASUREMENT_UNIT` AS MEASUREMENT_UNIT_SOURCE, + -- Coding systems + CODING_SYSTEM_MAP AS CODING_SYSTEM_ORG, + CODING_SYSTEM AS CODING_SYSTEM_OID + FROM file({filePathMungedTxtGz:String}, TSVWithNames) kanta_lab_table +-- Set output format to TSV-gzipped with a header +FORMAT TSVWithNames + +-- Disable data type inference from TSV text file +SETTINGS input_format_tsv_use_best_effort_in_schema_inference = 0 diff --git a/scripts/injection/plot_exploration.py b/scripts/injection/plot_exploration.py new file mode 100644 index 0000000..eb60384 --- /dev/null +++ b/scripts/injection/plot_exploration.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +plot_exploration.py + +Scatter plot for unit injection exploration (TEST_NAME level). + +Importable: call make_scatter_plot(plot_name). +Standalone: reads plot_name_level.tsv from the current directory. + +If plot_name contains a PLOT_CATEGORY column it is used to color points; +otherwise all points are drawn in a single colour. +""" + +import argparse +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np +import pandas as pd +from matplotlib.gridspec import GridSpec +from scipy.stats import gaussian_kde + +DEFAULT_OUTPUT = "test_names_exploration_scatter.png" + +_COLORS = {"has OMOP": "steelblue", "no OMOP": "tomato"} + + +def make_scatter_plot(plot_name, output=DEFAULT_OUTPUT): + fig = plt.figure(figsize=(12, 7)) + gs = GridSpec(1, 2, width_ratios=[4, 1], wspace=0.05) + ax_scatter = fig.add_subplot(gs[0]) + ax_kde = fig.add_subplot(gs[1], sharey=ax_scatter) + + if "OMOP_CONCEPT_ID" in plot_name.columns: + groups = { + "has OMOP": plot_name[plot_name["OMOP_CONCEPT_ID"].notna()], + "no OMOP": plot_name[plot_name["OMOP_CONCEPT_ID"].isna()], + } + else: + groups = {"all": plot_name} + _COLORS["all"] = "steelblue" + + for label, sub in groups.items(): + ax_scatter.scatter(sub["COUNT"], sub["top_prevalence"], + alpha=0.5, s=20, + color=_COLORS[label], label=f"{label} (n={len(sub)})") + handles, labels = ax_scatter.get_legend_handles_labels() + ax_kde.legend(handles, labels, fontsize=8, markerscale=1.5, loc="center right") + ax_scatter.set_xlabel("Count (value, no unit)") + ax_scatter.set_ylabel("Top unit prevalence (%)") + ax_scatter.set_title("TEST_NAME level — injection candidates") + ax_scatter.set_xscale("log") + + for label, sub in groups.items(): + prev = sub["top_prevalence"].dropna().values + if len(prev) < 2: + continue + ys = np.linspace(prev.min(), prev.max(), 400) + kde = gaussian_kde(prev, bw_method="scott") + ax_kde.fill_betweenx(ys, kde(ys), alpha=0.2, color=_COLORS[label]) + ax_kde.plot(kde(ys), ys, color=_COLORS[label], lw=1.2) + + ax_kde.set_xlabel("density") + ax_kde.tick_params(labelleft=False) + ax_kde.set_xlim(left=0) + + plt.savefig(output, dpi=150, bbox_inches="tight") + plt.close(fig) + print(f"Saved {output}") + + +def main(): + p = argparse.ArgumentParser( + description="Generate exploration scatter plot from plot_name_level.tsv.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("--tsv", default="plot_name_level.tsv") + p.add_argument("--output", default=DEFAULT_OUTPUT) + p.add_argument("--force", action="store_true", help="Overwrite existing plot") + args = p.parse_args() + + if args.force: + Path(args.output).unlink(missing_ok=True) + + make_scatter_plot(pd.read_csv(args.tsv, sep="\t"), output=args.output) + + +if __name__ == "__main__": + main() diff --git a/scripts/injection/split_eval.py b/scripts/injection/split_eval.py new file mode 100644 index 0000000..991b453 --- /dev/null +++ b/scripts/injection/split_eval.py @@ -0,0 +1,489 @@ +#!/usr/bin/env python3 +""" +split_eval.py + +For a single TEST_NAME, evaluate whether splitting the candidate distribution +by a GMM separator yields a better unit fit than treating it as a whole. + +Metric: size-weighted average KS statistic + global_score = best (lowest) KS over qualifying units for c_all + split_score = (n_low * best_KS(c_low, units) + + n_high * best_KS(c_high, units)) / n_total + improvement = (global_score − split_score) / global_score + +Usage +----- + python3 split_eval.py TEST_NAME --units "mmol/l:85.2,µmol/l:12.1" [options] + + Assumes candidate and target .npy arrays already exist in --dump-dir + (produced by explore_test_name.py). +""" + +import argparse +import os +import sys + +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import numpy as np +from scipy import stats + +import injection_engine + + +def _unit_tag(unit): + return unit.replace("/", "_").replace(" ", "_").replace("%", "pct") + + +def _name_tag(name): + return name.replace("/", "_").replace(" ", "_") + + +def _parse_units_arg(units_str): + """Parse 'unit1:pct1,unit2:pct2,...' → list of (unit, pct). pct defaults to 0.0.""" + pairs = [] + for item in units_str.split(","): + item = item.strip().strip("{}") + if not item: + continue + if ":" in item: + u, p = item.rsplit(":", 1) + pairs.append((u.strip(), float(p))) + else: + pairs.append((item.strip(), 0.0)) + return pairs + + +def load_candidate(name, dump_dir): + tag = _name_tag(name) + path = os.path.join(dump_dir, f"cand_{tag}.npy") + arr = np.load(path) + print(f" candidate: N={len(arr):,} ({path})") + return arr + + +def load_target(name, unit, dump_dir): + tag = _name_tag(name) + path = os.path.join(dump_dir, f"targ_{tag}_{_unit_tag(unit)}.npy") + return np.load(path) + + +# --------------------------------------------------------------------------- +# KS helper (stat only — no p-value needed for scoring) +# --------------------------------------------------------------------------- + +def _ks_stat(a, b): + n1, n2 = len(a), len(b) + if max(n1, n2) > 500_000: + lo, hi = min(a.min(), b.min()), max(a.max(), b.max()) + if lo == hi: + return 0.0 + bins = 100_000 + h1, _ = np.histogram(a, bins=bins, range=(lo, hi)) + h2, _ = np.histogram(b, bins=bins, range=(lo, hi)) + e1 = np.cumsum(h1) / n1 + e2 = np.cumsum(h2) / n2 + return float(np.max(np.abs(e1 - e2))) + return float(stats.ks_2samp(a, b).statistic) + + +# --------------------------------------------------------------------------- +# Comparison logic +# --------------------------------------------------------------------------- + +def rank_units(c, unit_data): + """ + For a candidate array c, compute KS against every unit. + Returns list of (unit, pct, ks_stat) sorted by ks_stat ascending. + """ + results = [] + for unit, (t_vals, pct) in unit_data.items(): + results.append((unit, pct, _ks_stat(c, t_vals))) + results.sort(key=lambda x: x[2]) + return results + + +def split_score(n_low, ks_low, n_high, ks_high): + """Size-weighted average KS across sub-distributions.""" + return (n_low * ks_low + n_high * ks_high) / (n_low + n_high) + + +# --------------------------------------------------------------------------- +# Full pipeline for display +# --------------------------------------------------------------------------- + +def run_full_pipeline(c, t, label): + """Run KS+T+MAD and return a one-line summary string + PipelineResult.""" + result = injection_engine.run_pipeline(c, t) + ks = next(s for s in result.steps if s.name == "KS") + t_ = next(s for s in result.steps if s.name == "T") + m = next(s for s in result.steps if s.name == "MAD") + summary = ( + f"{label:<40} " + f"KS={'P' if ks.passed else 'F'}({ks.details['stat']:.4f}) " + f"T={'P' if t_.passed else 'F'} " + f"MAD={'P' if m.passed else 'F'} " + f"→ {result.outcome} at {result.decided_by}" + ) + return summary, result + + +# --------------------------------------------------------------------------- +# Plotting +# --------------------------------------------------------------------------- + +_PLOT_MAX_N = 50_000 + + +def _sample(arr): + if len(arr) > _PLOT_MAX_N: + rng = np.random.default_rng(42) + return arr[np.sort(rng.choice(len(arr), _PLOT_MAX_N, replace=False))] + return arr + + +def _ecdf(arr): + x = np.sort(arr) + return x, np.arange(1, len(x) + 1) / len(x) + + +def _kde(ax, arr, label, color, lw=1.6, fill=True, ls="-"): + arr = _sample(arr) + if len(arr) < 2 or np.std(arr) == 0: + return + kde = stats.gaussian_kde(arr) + xs = np.linspace(arr.min(), arr.max(), 400) + ys = kde(xs) + ax.plot(xs, ys, color=color, label=label, lw=lw, ls=ls, alpha=0.9) + if fill: + ax.fill_between(xs, ys, color=color, alpha=0.12) + + +def _ecdf_plot(ax, arr, label, color): + x, y = _ecdf(_sample(arr)) + ax.step(x, y, where="post", color=color, label=label, alpha=0.85) + + +def _ks_marker(ax, c, t, ks_stat): + cs = _sample(c) + ts = _sample(t) + cx, _ = _ecdf(cs) + tx, _ = _ecdf(ts) + all_x = np.sort(np.unique(np.concatenate([cx, tx]))) + c_cdf = np.searchsorted(cx, all_x, side="right") / len(cx) + t_cdf = np.searchsorted(tx, all_x, side="right") / len(tx) + i = np.argmax(np.abs(c_cdf - t_cdf)) + y_lo, y_hi = sorted([c_cdf[i], t_cdf[i]]) + ax.plot([all_x[i], all_x[i]], [y_lo, y_hi], color="red", lw=2.5, + label=f"D={ks_stat:.4f}") + + +_COLORS = ["steelblue", "darkorange", "seagreen", "mediumpurple", "saddlebrown"] + + +def make_figure(name, c_vals, unit_data, + global_ranks, low_ranks, high_ranks, + c_low, c_high, sep, bim, + g_score, s_score, improvement, + out_path): + """ + 2 rows × 3 cols: + Row 0 — Global: KDE (all units) | ECDF (best unit) | GMM fit + Row 1 — Split: KDE low vs best | KDE high vs best | Score summary + """ + unit_colors = {u: _COLORS[i % len(_COLORS)] for i, u in enumerate(unit_data)} + + best_g_unit, _, best_g_ks = global_ranks[0] + best_l_unit, _, best_l_ks = low_ranks[0] if low_ranks else (None, 0, np.nan) + best_h_unit, _, best_h_ks = high_ranks[0] if high_ranks else (None, 0, np.nan) + + t_global = unit_data[best_g_unit][0] + t_low = unit_data[best_l_unit][0] if best_l_unit else np.array([]) + t_high = unit_data[best_h_unit][0] if best_h_unit else np.array([]) + + verdict = "SPLIT BETTER" if improvement > 0 else "GLOBAL BETTER" + same_unit = best_l_unit == best_h_unit + + fig, axes = plt.subplots(2, 3, figsize=(21, 10)) + fig.suptitle( + f"{name}" + f" global KS={g_score:.4f} split KS={s_score:.4f}" + f" improvement={improvement:+.1%}" + f" → {verdict}" + + (" [same unit → likely intrinsic]" if same_unit else ""), + fontsize=12, fontweight="bold", + ) + + # ── (0,0) Global KDE: candidate + all unit targets ────────────────── + ax = axes[0, 0] + _kde(ax, c_vals, "candidate (all)", "black", lw=2.2) + for unit, (t_vals, pct) in unit_data.items(): + is_best = (unit == best_g_unit) + _kde(ax, t_vals, + f"{unit} ({pct:.1f}%)", + unit_colors[unit], + lw=2.2 if is_best else 1.2, + ls="-" if is_best else "--", + fill=is_best) + ax.set_title("Global: candidate vs all units (KDE)") + ax.set_xlabel("value"); ax.legend(fontsize=8) + + # ── (0,1) Global ECDF: candidate vs best unit ──────────────────────── + ax = axes[0, 1] + _ecdf_plot(ax, c_vals, "candidate (all)", "black") + _ecdf_plot(ax, t_global, f"{best_g_unit} (global best)", "steelblue") + _ks_marker(ax, c_vals, t_global, best_g_ks) + ax.set_title(f"Global ECDF best={best_g_unit} KS={best_g_ks:.4f}") + ax.set_xlabel("value"); ax.set_ylabel("CDF"); ax.legend(fontsize=8) + + # ── (0,2) GMM bimodal plot ──────────────────────────────────────────── + ax = axes[0, 2] + if bim.fit: + fit = bim.fit + x_fit = fit["x_fit"] + lo_f, hi_f = np.percentile(x_fit, [0.5, 99.5]) + ax.hist(x_fit, bins=60, density=True, alpha=0.4, color="grey", + range=(lo_f, hi_f), label="candidate") + xf = np.linspace(lo_f, hi_f, 400) + for m, s, w in zip(fit["means_native"], fit["sigmas_native"], fit["weights"]): + ax.plot(xf, w * stats.norm.pdf(xf, m, s), lw=1.8, alpha=0.85) + if not np.isnan(fit.get("sep_native", np.nan)): + ax.axvline(fit["sep_native"], color="red", ls="--", + label=f"sep={sep:.4g}") + ax.set_xlabel("log₁₀(value)" if bim.lognormal else "value") + ax.set_title( + f"GMM status={bim.status}" + f" BC={bim.bc:.3f} dip_p={bim.dip_p:.3g}" + f" n_low={len(c_low):,} n_high={len(c_high):,}" + ) + ax.legend(fontsize=8) + + # ── (1,0) Split KDE: low sub vs its best unit ──────────────────────── + ax = axes[1, 0] + if len(c_low) >= 2: + _kde(ax, c_low, f"low (N={len(c_low):,})", "steelblue") + if len(t_low) >= 2: + _kde(ax, t_low, f"{best_l_unit} (best for low)", "darkorange", fill=False, ls="--") + ax.set_title(f"Split LOW best={best_l_unit} KS={best_l_ks:.4f}") + ax.set_xlabel("value"); ax.legend(fontsize=8) + + # ── (1,1) Split KDE: high sub vs its best unit ─────────────────────── + ax = axes[1, 1] + if len(c_high) >= 2: + _kde(ax, c_high, f"high (N={len(c_high):,})", "seagreen") + if len(t_high) >= 2: + _kde(ax, t_high, f"{best_h_unit} (best for high)", "darkorange", fill=False, ls="--") + ax.set_title(f"Split HIGH best={best_h_unit} KS={best_h_ks:.4f}") + ax.set_xlabel("value"); ax.legend(fontsize=8) + + # ── (1,2) Score summary text panel ────────────────────────────────── + ax = axes[1, 2] + ax.axis("off") + n_total = len(c_vals) + n_low = len(c_low) + n_high = len(c_high) + + lines = [ + "── SCORE SUMMARY ──────────────────────", + "", + f"GLOBAL KS = {g_score:.4f}", + ] + for u, p, k in global_ranks: + marker = " ←" if u == best_g_unit else "" + lines.append(f" {u:<18} KS={k:.4f}{marker}") + + lines += [ + "", + f"SPLIT weighted KS = {s_score:.4f}", + f" low N={n_low:,} ({100*n_low/n_total:.1f}%)", + ] + for u, p, k in low_ranks: + marker = " ←" if u == best_l_unit else "" + lines.append(f" {u:<16} KS={k:.4f}{marker}") + lines.append(f" high N={n_high:,} ({100*n_high/n_total:.1f}%)") + for u, p, k in high_ranks: + marker = " ←" if u == best_h_unit else "" + lines.append(f" {u:<16} KS={k:.4f}{marker}") + + lines += [ + "", + f"improvement = {improvement:+.1%}", + f"same unit? = {same_unit}", + f" low → {best_l_unit}", + f" high → {best_h_unit}", + "", + f"VERDICT: {verdict}", + ] + + ax.text(0.04, 0.97, "\n".join(lines), + transform=ax.transAxes, fontsize=9.5, va="top", family="monospace", + bbox=dict(boxstyle="round", facecolor="lightyellow", alpha=0.85)) + + plt.tight_layout() + plt.savefig(out_path, dpi=120, bbox_inches="tight") + plt.close(fig) + print(f" figure → {out_path}") + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + p = argparse.ArgumentParser( + description="Evaluate global vs split unit fit for a single TEST_NAME.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + p.add_argument("test_name", help="TEST_NAME to evaluate") + p.add_argument("--units", required=True, + help="Comma-separated units to evaluate, optionally with pct: " + "'mmol/l:85.2,µmol/l:12.1'") + p.add_argument("--dump-dir", default="/mnt/disks/data/kanta/inject/tmp/", + help="Cache directory containing existing .npy arrays") + p.add_argument("--min-target-n", type=int, default=30, + help="Minimum records for a unit target to qualify") + p.add_argument("--dip-threshold",type=float, default=0.05, + help="Hartigan dip test p-value threshold for bimodality check") + p.add_argument("--out-dir", default=".", + help="Directory for output figure") + args = p.parse_args() + + name = args.test_name + + SEP = "=" * 72 + print(f"\n{SEP}") + print(f"SPLIT EVALUATION: {name}") + print(SEP) + + # ── 1. Candidate values ─────────────────────────────────────────────── + print("\n[1] Candidate values") + c_vals = load_candidate(name, args.dump_dir) + if len(c_vals) < 2: + sys.exit("ERROR: too few candidate values") + print(f" N={len(c_vals):,} " + f"median={np.median(c_vals):.4g} " + f"p5={np.percentile(c_vals,5):.4g} " + f"p95={np.percentile(c_vals,95):.4g}") + + # ── 2. Units (passed in) ────────────────────────────────────────────── + print(f"\n[2] Units") + top_units = _parse_units_arg(args.units) + if not top_units: + sys.exit("ERROR: --units produced no entries") + for u, pct in top_units: + print(f" {u:<22} {pct:>6.2f}%") + + # ── 3. Target arrays ────────────────────────────────────────────────── + print(f"\n[3] Target arrays (min_target_n={args.min_target_n})") + unit_data = {} # unit → (t_vals, pct) + for unit, pct in top_units: + t_vals = load_target(name, unit, args.dump_dir) + status = f"N={len(t_vals):,}" + if len(t_vals) < args.min_target_n: + status += " SKIP (too few)" + print(f" {unit:<22} {status}") + if len(t_vals) >= args.min_target_n: + unit_data[unit] = (t_vals, pct) + if not unit_data: + sys.exit("ERROR: no qualifying target distributions") + + # ── 4. Global comparison ────────────────────────────────────────────── + print("\n[4] Global comparison KS(c_all, unit)") + global_ranks = rank_units(c_vals, unit_data) + for unit, pct, ks in global_ranks: + tag_mark = " ← best" if unit == global_ranks[0][0] else "" + print(f" {unit:<22} KS={ks:.4f}{tag_mark}") + g_score = global_ranks[0][2] + + print("\n Full pipeline (global best unit):") + best_g_unit = global_ranks[0][0] + t_best_g = unit_data[best_g_unit][0] + summary, _ = run_full_pipeline(c_vals, t_best_g, f"c_all vs {best_g_unit}") + print(f" {summary}") + + # ── 5. Bimodal check + split ────────────────────────────────────────── + print(f"\n[5] Bimodal check (dip_threshold={args.dip_threshold})") + bim = injection_engine.bimodal_check(c_vals, dip_threshold=args.dip_threshold) + print(f" status={bim.status} sep={bim.separator:.4g}" + f" BC={bim.bc:.3f} dip_p={bim.dip_p:.3g}" + f" space={'log' if bim.lognormal else 'linear'}") + + sep = bim.separator + c_low = c_vals[c_vals <= sep] if not np.isnan(sep) else np.array([]) + c_high = c_vals[c_vals > sep] if not np.isnan(sep) else np.array([]) + n_low, n_high = len(c_low), len(c_high) + print(f" low: N={n_low:,} ({100*n_low/len(c_vals):.1f}%)" + f" high: N={n_high:,} ({100*n_high/len(c_vals):.1f}%)") + if n_low < 2 or n_high < 2: + print(" WARNING: one sub-distribution is too small — split metrics may be unreliable") + + # ── 6. Split comparison ─────────────────────────────────────────────── + print("\n[6] Split comparison KS(c_sub, unit)") + low_ranks = rank_units(c_low, unit_data) if n_low >= 2 else [] + high_ranks = rank_units(c_high, unit_data) if n_high >= 2 else [] + + print(" LOW sub-distribution:") + for unit, pct, ks in low_ranks: + tag_mark = " ← best" if unit == low_ranks[0][0] else "" + print(f" {unit:<22} KS={ks:.4f}{tag_mark}") + + print(" HIGH sub-distribution:") + for unit, pct, ks in high_ranks: + tag_mark = " ← best" if unit == high_ranks[0][0] else "" + print(f" {unit:<22} KS={ks:.4f}{tag_mark}") + + print("\n Full pipeline (best unit per sub):") + if low_ranks: + best_l_unit = low_ranks[0][0] + s, _ = run_full_pipeline(c_low, unit_data[best_l_unit][0], f"c_low vs {best_l_unit}") + print(f" {s}") + if high_ranks: + best_h_unit = high_ranks[0][0] + s, _ = run_full_pipeline(c_high, unit_data[best_h_unit][0], f"c_high vs {best_h_unit}") + print(f" {s}") + + # ── 7. Aggregated score ─────────────────────────────────────────────── + print(f"\n[7] Score aggregation") + if low_ranks and high_ranks: + best_l_ks = low_ranks[0][2] + best_h_ks = high_ranks[0][2] + s_score = split_score(n_low, best_l_ks, n_high, best_h_ks) + improvement = (g_score - s_score) / g_score if g_score > 0 else 0.0 + else: + s_score = np.nan + improvement = 0.0 + + best_l_unit = low_ranks[0][0] if low_ranks else "—" + best_h_unit = high_ranks[0][0] if high_ranks else "—" + same_unit = best_l_unit == best_h_unit + + print(f" global_score = {g_score:.4f} (best unit: {global_ranks[0][0]})") + print(f" split_score = {s_score:.4f} (size-weighted avg of per-sub bests)") + print(f" improvement = {improvement:+.1%}") + print(f" same best unit for both subs? {same_unit}" + f" (low={best_l_unit}, high={best_h_unit})") + + verdict = "SPLIT BETTER" if improvement > 0 else "GLOBAL BETTER" + print(f"\n{SEP}") + print(f"VERDICT: {verdict} (improvement={improvement:+.1%})") + if same_unit: + print(" NOTE: both sub-distributions prefer the same unit" + " — likely intrinsic bimodality, not a unit mix") + print(SEP) + + # ── 8. Figure ───────────────────────────────────────────────────────── + out_path = os.path.join(args.out_dir, f"split_eval_{_name_tag(name)}.png") + print(f"\n[8] Generating figure") + make_figure( + name=name, c_vals=c_vals, unit_data=unit_data, + global_ranks=global_ranks, + low_ranks=low_ranks, high_ranks=high_ranks, + c_low=c_low, c_high=c_high, sep=sep, bim=bim, + g_score=g_score, s_score=s_score, improvement=improvement, + out_path=out_path, + ) + + +if __name__ == "__main__": + main()