Files
kemal 7bd73700b2 P1 validated on 1000 Genomes Omni2.5 (2141 vendor GTCs + IDATs)
All 2141 vendor-written GTC v3 files match BeadArrayFiles field by field
and re-encode byte-identically; all IDAT pairs parse; the 2.45M-locus BPM,
dbv-7 EGT and CSV manifest agree with BeadArrayFiles and each other.
Run on a self-destructing AWS spot instance (42 min, ~$0.20).
2026-09-27 20:22:35 +01:00

72 lines
4.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Phase P0/P1 validation report
Date: 2026-09-27 · Machine: Raspberry Pi 5 (aarch64, 4 cores, 8 GB) · Python 3.13, NumPy 2.2.
Commands: `pytest tests/unit` (every CI run), `pytest tests/oracle -m "not slow"` and `-m slow` (needs
`scripts/fetch_data.sh p0`, `scripts/bootstrap_refs.sh`, `scripts/make_oracle_gtcs.sh`).
## Scope delivered
| Plan §18 P1 item | Module | Status |
|---|---|---|
| Little-endian binary + .NET string codec | `formats/binutil.py` | done |
| IDAT v3 reader (gzip, metadata, RunInfo → ISO date, encrypted detection) | `formats/idat.py` | done |
| BPM reader (v3–5, locus entries v4/6/7/8, norm-bin rule incl. mod-256 wrap) | `formats/bpm.py` | done |
| CSV manifest reader + BPM↔CSV consistency (IN-042) | `formats/csv_manifest.py` | done |
| EGT reader (data-block variants, count cross-check) | `formats/egt.py` | done |
| GTC v3–v5 reader and writer (layout-preserving) | `formats/gtc.py` | done |
| Sample sheet (IN-001 … IN-014) | `formats/samplesheet.py` | done |
| IDAT discovery, pairing, duplicates, input-set rule (IN-020 … IN-022, CLI-006) | `formats/discovery.py` | done |
| Message catalogue, exit codes, JSON logging, rate limiting | `core/errors.py`, `core/log.py` | done |
| Atomic writes, overwrite policy, run manifest | `core/atomic.py`, `core/provenance.py` | done (used from P4 on) |
| `simurg version`, `simurg inspect [--json]` | `cli/main.py` | done |
| Synthetic IDAT/BPM/EGT writers for tests | `synth/files.py` | done |
## Acceptance results (plan §18 P1 gate)
| Criterion | Data | Result |
|---|---|---|
| GTC fields equal to BeadArrayFiles (23 fields incl. raw X/Y, genotypes, scores, BAF, LRR, base calls, sample stats, percentiles, 48 normalization transforms) | 18 GSA v4 GTCs from gtc2vcf `idat2gtc` (preset: Array Analysis CLI) | **EXACT, 18/18** |
| GTC read → write byte-identical | same 18 files; synthetic v3/v5 incl. non-canonical layouts; 25 random property cases | **EXACT** |
| BPM equal to BeadArrayFiles (names, SNP, Chr, MapInfo, AddressA, assay type, normalization lookups, RefStrand, manifest name, control config) | GSA v4 PGx BPM (683 054 loci) | **EXACT** |
| EGT equal to BeadArrayFiles (N, θ/R mean/SD ×3, intensity threshold, GenTrain score, cluster separation, address) | GSA v4 (683 054 records) and GDA v1 (1 904 599 records), ~5 000 evenly spaced records each | **EXACT** |
| BPM ↔ CSV consistency (IN-042: names, counts, Chr, MapInfo, SNP, AddressA/B) | GSA v4 | **0 differences** |
| All IDATs parse and pair; Red/Grn address sets equal; same address set on every chip | 72 GSA IDATs (36 samples, 2 chips) | **pass** |
| Manifest addresses present in IDATs | GSA v4 | **100 %** |
| Bioconductor fixtures: 381 079 / 622 399 addresses (plain + gzip); 2 encrypted files → `E_IDAT_ENCRYPTED` | IlluminaDataTestFiles 1.50.0 | **pass** |
| Malformed inputs never raise uncaught exceptions (truncation, bad magic, bad TOC offsets, bad string lengths, duplicates) | synthetic matrix (EDGE-002, EDGE-004) | **pass** |
| Sample-sheet matrix EDGE-007 (27 cases) incl. 10 000 rows < 2 s (PERF-007) | synthetic | **pass** |
| Locale independence of `inspect --json` (C, tr_TR, de_DE) | synthetic | **pass** |
## Large-scale run: 1000 Genomes Omni2.5 (AWS, 2026-09-27)
Self-destructing spot instance (`scripts/cloud/trial.sh`, m7g.4xlarge, 16 vCPU), run `run-20260927T183712Z`,
summary in `P1_omni2141_summary.json`. 107 GB archive streamed from `s3://1000genomes` (extracted 170 GB) in
22 min; validation 18 min; instance lifetime 42 min; cost ≈ $0.20.
| Criterion | Result |
|---|---|
| 2141 **vendor-written** GTCs (v3, AutoCall 1.6.2.2): every field equal to BeadArrayFiles | **2141/2141 EXACT** |
| Same GTCs: read → write byte-identical | **2141/2141** |
| 2141 IDAT pairs (BeadChip 4x10) parse and pair; MidBlock int32 | **2141/2141** |
| BPM HumanOmni2.5-4v1-Multi_B (2 450 000 loci, 51 normalization bins) vs BeadArrayFiles | **EXACT** |
| EGT (data block v7, 2 450 000 records) vs BeadArrayFiles: all θ/R means and SDs, N | **0 mismatches** |
| BPM ↔ CSV consistency | **0 differences** |
| GTC transforms per sample = BPM bins (51) | **pass** |
## Performance on the Pi (single thread, pure Python/NumPy)
| Operation | Time | Peak RSS |
|---|---|---|
| IDAT read (734 656 addresses) | 3 ms | – |
| GTC read (683 054 loci) | 34 ms | – |
| EGT read GSA / GDA | 2.2 s / 6.1 s | – |
| CSV manifest read GSA | 12.5 s | 713 MB |
| BPM read GSA | 17.8 s | 665 MB |
BPM/CSV parsing is the only slow step; it is a one-off per run and moves to Rust in P9 (or is cached).
## Not yet covered (needs the larger machine / later phases)
- GEO sweep of 254 genotyping IDAT series (other chip generations, IDAT variants) — still open.
- IN-027 (IDAT ↔ manifest coverage gate) is P2; the coverage computation is already exercised by a test.