From 2444b3d54824b4b1d87910f59c90b0cb8ba006d0 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:55:26 +0100 Subject: [PATCH 1/3] docs(live): describe the family report, the Family card and the re-check lines as main draws them The family page is a report in five chapters and a person's page carries a Family card with a link per family; the re-check banner sits in its card under the step timeline, in section 10 and in the line under the timeline. Bead: pgp-5bja --- docs/multi-sample.md | 14 +++++++------- docs/recheck.md | 2 +- docs/run-state.md | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/multi-sample.md b/docs/multi-sample.md index e51e8adb..ab8c44a7 100644 --- a/docs/multi-sample.md +++ b/docs/multi-sample.md @@ -114,15 +114,15 @@ The Nextflow pipeline writes them to `/family/` (`//` with ### On the live site -The live site (`bin/owngenome_live.py`) lists each family folder under `GENOME_DIR` as a run of its own, of kind `family`: any folder holding `members.tsv` or `pedigree.tsv`. The plugin `bin/owngenome_live_family.py` serves the family page (`assets/live/family.html`, drawn by `assets/live/family-page.js` from `/runs//family.json`); its card script `assets/live/plugins/family.js` adds the Family card to the run's progress view: +The live site (`bin/owngenome_live.py`) lists each family folder under `GENOME_DIR` as a run of its own, of kind `family`: any folder holding `members.tsv` or `pedigree.tsv`. The home page lists everyone by family, with a small pedigree and one tile per person. The plugin `bin/owngenome_live_family.py` serves the family page (`assets/live/family.html`, drawn by `assets/live/family-page.js` from `/runs//family.json`), a family report in five chapters opened by a lead ([Watch a run live](live-site.md)), and after them the plan and the family's reports: -- **The pedigree.** `bin/pedigree_svg.py` draws it from `members.tsv`, or from `pedigree.tsv` when step 38 has not run, each time the page asks. A square is a male, a circle a female, a diamond a sex not known; a filled symbol is a sequenced person (with only `pedigree.tsv`, a person with a row of their own). A line joins partners, and the children hang from it, one generation per row. It draws what the file declares; the relationship check below is what tests it. -- **Each child**: the stretches of `_segments.tsv` painted per chromosome, the copy from the father above the copy from the mother, coloured by grandparent where step 38 could tell, else by parent; then the child's de novo candidates (`de_novo.tsv`) and compound-het pairs (`compound_het.tsv`, in trans first). Without `_segments.tsv` the page shows `_segments.svg`, or a line saying step 38 has not drawn one; a missing de novo or compound-het file is named with the reason, never shown as an empty list. -- **The relationship check** (`relationships.tsv`) and **the shared segments** (`ibd_pairs.tsv`) as one grid of every pair, with ⚑ where the measured relationship differs from the declared one (a list of pairs on a phone), and **the plan** (`family_plan.tsv`, when there is one), row by row as the steps wrote them. -- **The members side by side**: each sequenced member's CYP2D6 calls, CPIC phenotypes, HLA types, haplogroups and blood groups from their `summary.json`, one column each; `.` where a member's summary has no such row, "no summary yet" for a member without one. -- **The couple carrier screen**, per couple: how many genes `couple__.tsv` lists as `shared` and as `x_linked`, and a link to `couple__.md`. A count of 0 is not an all-clear; the report says what the screen does not see. +- **I, the pedigree and members.** `bin/pedigree_svg.py` draws it from `members.tsv`, or from `pedigree.tsv` when step 38 has not run, each time the page asks. A square is a male, a circle a female, a diamond a sex not known; a filled symbol is a sequenced person (with only `pedigree.tsv`, a person with a row of their own). A line joins partners, and the children hang from it, one generation per row. It draws what the file declares; the relationship check below is what tests it. Beside it, the members and the analyses step 38 planned (`family_plan.tsv`). +- **II, who is related how**: the relationship check (`relationships.tsv`) and the shared segments (`ibd_pairs.tsv`) as one grid of every pair, with ⚑ where the measured relationship differs from the declared one (a list of pairs on a phone). +- **III, the couple carrier screen**, per couple: how many genes `couple__.tsv` lists as `shared`, as `x_linked` and as carried by one partner, each gene both carry with its chance per pregnancy (drawn as four children when it is the carrier pattern), the whole table, and a link to `couple__.md`. A count of 0 is not an all-clear; the report says what the screen does not see. +- **IV, each child**: the stretches of `_segments.tsv` painted per chromosome, the copy from the father above the copy from the mother, coloured by grandparent where step 38 could tell, else by parent; then the child's de novo candidates (`de_novo.tsv`) and compound-het pairs (`compound_het.tsv`, in trans first). Without `_segments.tsv` the page shows `_segments.svg`, or a line saying step 38 has not drawn one; a missing de novo or compound-het file is named with the reason, never shown as an empty list. +- **V, the members side by side**: each sequenced member's CYP2D6 calls, CPIC phenotypes, HLA types, haplogroups and blood groups from their `summary.json`, one column each; `.` where a member's summary has no such row, "no summary yet" for a member without one. -A sample's own page gets a link to the family it is in. The plugin reads the files the steps wrote and nothing else: it calls no tool and computes no genetics. It serves only the pedigree it draws and the result files named above, at `/family//`, behind the site's login; any other file of the folder, such as `family.vcf.gz`, is 404. `tests/test_pedigree_svg.py` and `tests/test_owngenome_live_family.py` check the drawing and the card on an invented family (`tests/fixtures/families_live/`), with a swapped parent as the control that must fail; `tests/test_live_family_depth.py` checks the painting, the de novo and compound-het tables and the side by side on the demo's invented trio (`tests/fixtures/live/demo/DEMO-FAMILY/`). +A person's page gets a Family card (`assets/live/plugins/family.js`) with a link to each family the person is in. The plugin reads the files the steps wrote and nothing else: it calls no tool and computes no genetics. It serves only the pedigree it draws and the result files named above, at `/family//`, behind the site's login; any other file of the folder, such as `family.vcf.gz`, is 404. `tests/test_pedigree_svg.py` and `tests/test_owngenome_live_family.py` check the drawing and the family page on an invented family (`tests/fixtures/families_live/`), with a swapped parent as the control that must fail; `tests/test_live_family_depth.py` checks the painting, the de novo and compound-het tables and the side by side on the demo's invented trio (`tests/fixtures/live/demo/DEMO-FAMILY/`). ```bash python3 bin/pedigree_svg.py "$GENOME_DIR/my_family" > pedigree.svg # the same drawing, without the site diff --git a/docs/recheck.md b/docs/recheck.md index 4fef29fc..95d0e606 100644 --- a/docs/recheck.md +++ b/docs/recheck.md @@ -91,7 +91,7 @@ python3 bin/owngenome_recheck.py --launch-dir /path/to/launch/dir ## In the reports and on the live site -The next reports read `recheck/state.json`: both show one "Database re-check" line in the run facts. A check older than the sample's `run_manifest.tsv` describes an earlier run, so the reports leave it out until the next check. When the sample has a previous run in `history/` ([the delta report](delta-report.md)), a section whose results changed since it carries a badge such as "New since 2026-10-01T09:15:00Z", in both reports, and the delta report heads its changes the same way. The live site's re-check plugin (`bin/owngenome_live_recheck.py`, `assets/live/plugins/recheck.js`) shows the same line as a banner, puts a badge (↻) such as "newer ClinVar available" in the header of every result card of a named step on the person page, and marks each named step on the run timeline with a dot at the end of its line, whose tooltip says the same or "redone 2026-10-05". +The next reports read `recheck/state.json`: both show one "Database re-check" line in the run facts. A check older than the sample's `run_manifest.tsv` describes an earlier run, so the reports leave it out until the next check. When the sample has a previous run in `history/` ([the delta report](delta-report.md)), a section whose results changed since it carries a badge such as "New since 2026-10-01T09:15:00Z", in both reports, and the delta report heads its changes the same way. The live site's re-check plugin (`bin/owngenome_live_recheck.py`, `assets/live/plugins/recheck.js`) shows the same line as a banner in its card under the run timeline (the person page repeats it in section 10, Sources and files, and the line under the step timeline ends with "Databases re-checked" and the date), puts a badge (↻) such as "newer ClinVar available" in the header of every result card of a named step on the person page, and marks each named step on the run timeline with a dot at the end of its line, whose tooltip says the same or "redone 2026-10-05". The Nextflow `HTML_REPORT` process sees no `history/` folder, so its report has no "New since" badge. diff --git a/docs/run-state.md b/docs/run-state.md index ad5c0747..7cbdb29b 100644 --- a/docs/run-state.md +++ b/docs/run-state.md @@ -100,4 +100,4 @@ It prints plain lines and exits 0. Standard library only. ## The page built on it -`bin/run_state.py` serves no page itself. The live site's server, `bin/owngenome_live.py`, reads the run's messages with it and serves its state as `state.json` to the run page ([Watch a Run Live](live-site.md)). +`bin/run_state.py` serves no page itself. The live site's server, `bin/owngenome_live.py`, reads the run's messages with it and serves its state as `state.json` to each person's page, whose step timeline and run band draw it ([Watch a Run Live](live-site.md)). From 08f5701ad02e300758db437ae029889c908d58c0 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:55:29 +0100 Subject: [PATCH 2/3] fix(cn): call a low MAPQ 0 share a possible sign of an aligner that drops multi-mapping reads A low share can have other causes; the message and the doc no longer state it as the only one. Bead: pgp-5bja --- bin/copy_number.py | 2 +- docs/cn-loci.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/copy_number.py b/bin/copy_number.py index 177ecd02..84374c59 100755 --- a/bin/copy_number.py +++ b/bin/copy_number.py @@ -590,7 +590,7 @@ def psv_message(row, b): targets = " and ".join(n.removesuffix("_BODY") for n, *_ in row["_targets"]) return (f"not assessed: the BAM has few or no multi-mapping reads here (reads of mapping quality 0 make " f"up {100 * b['mapq0_share']:.1f}% of the depth over {targets}, under " - f"{100 * row['_mapq0_min']:g}%), the sign of an aligner that leaves out the reads that fit both " + f"{100 * row['_mapq0_min']:g}%), a possible sign of an aligner that leaves out the reads that fit both " "copies (as novoalign does by default); the module total may then come out low and read as a " "deletion; realign with minimap2 (step 02) or BWA-MEM") return f"not assessed: {b['reason'] or '.'}" diff --git a/docs/cn-loci.md b/docs/cn-loci.md index 6c1d898a..cada3a10 100644 --- a/docs/cn-loci.md +++ b/docs/cn-loci.md @@ -18,7 +18,7 @@ Before it calls, the engine checks, in this order: 2. **Broken control**: a control segment has no read while the other controls have at least the row's minimum depth. The engine stops with `BROKEN_CONTROL` and exit code 3. Either the row's coordinates are wrong, or the sample lacks that stretch. A number from such a run would mean nothing. 3. **Control depth** under `min_control_depth`: not assessed. With `control_depth` `flanks`, each flank must reach it. 4. **Flanks disagree** by more than `max_control_ratio` (`flanks` only): not assessed. A deletion that reaches one flank would skew the ratio. -5. **MAPQ 0 share** of the target depth under `mapq0_min`: not assessed. The reference holds every copy of a duplicated gene, so an aligner that reports every read gives reads that fit several copies mapping quality 0. With no such read, the aligner left them out (novoalign does by default) and the count would come out low. The check is skipped when a row without an offset rounds to no copy, since no read is left to judge by. A share over `mapq0_max` means the target is mostly misplaced reads: not assessed. +5. **MAPQ 0 share** of the target depth under `mapq0_min`: not assessed. The reference holds every copy of a duplicated gene, so an aligner that reports every read gives reads that fit several copies mapping quality 0. Few or none of them is a possible sign that the aligner left them out (novoalign does by default), and then the count would come out low. The check is skipped when a row without an offset rounds to no copy, since no read is left to judge by. A share over `mapq0_max` means the target is mostly misplaced reads: not assessed. 6. **Below the floor**: a row with an offset whose estimate is 0 or less: not assessed. Then the row's `call` column decides: From f1ec115b3c7e2fcfb7aa68ee6fff07c100481778 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Mon, 9 Nov 2026 10:55:33 +0100 Subject: [PATCH 3/3] docs(interpreting): explain the SMN1 silent-carrier marker and the Lp(a) section with its 4925G>A line Bead: pgp-5bja --- docs/interpreting-results.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/interpreting-results.md b/docs/interpreting-results.md index 8d71f2ee..9aa0a7bd 100644 --- a/docs/interpreting-results.md +++ b/docs/interpreting-results.md @@ -659,6 +659,14 @@ CYP2D6 is the hard gene: a nearby pseudogene and frequent copy-number changes co One SMN1 copy suggests SMA carrier status; two do not rule it out (two copies on one chromosome, none on the other). Trust a value only with quality 20 or more and filter `PASS`, and confirm anything that matters with a clinical SMN1 test. [Step 35](35-paralogs.md#interpreting-results) explains the columns. +**The silent-carrier marker:** `${SAMPLE}/paralogs/${SAMPLE}_smn1_marker.tsv` reads SMN1 g.27134T>G (rs143838139), and both reports and the live person page print it right under the SMN1 copies: `present`, `uncertain`, `not seen`, `too little depth` or `not assessed`. With two SMN1 copies, `present` raises the chance that both sit on one chromosome (2+0, a silent carrier) and `not seen` lowers it; it proves neither, and the figures depend on ancestry. A carrier test in a clinical laboratory confirms it. [The SMN1 silent-carrier marker](35-paralogs.md#the-smn1-silent-carrier-marker) gives the thresholds. + +## Lp(a) and the LPA KIV-2 Count (Step 46, opt-in) + +**Where to look:** `${SAMPLE}/lpa/${SAMPLE}_lpa_kiv2.tsv`, the KIV-2 repeat count over both chromosomes with its 95% interval and class, and `${SAMPLE}/lpa/${SAMPLE}_lpa_kiv2_4925.tsv`, the KIV-2 4925G>A splice variant. Both reports put the count beside the two LPA risk variants of step 42 (rs10455872, rs3798220), and print the 4925G>A line right under the count: `present` or `uncertain` with the reads that carry it and the copies, `not seen`, `too little depth` or `not assessed`. + +Fewer repeats or a risk allele point to a higher Lp(a); a 4925G>A copy lowers the Lp(a) made from the chromosome that carries it. Neither tells which chromosome carries what, and `not seen` is not proof of absence at low depth. The count is a research estimate: the Lp(a) blood test is the measure, once in every adult. [Step 46](46-lpa.md) explains the columns. + ## Mobile Element Insertions (Step 50, opt-in) **Where to look:** `${SAMPLE}/mei/${SAMPLE}_mei_sites.tsv` for the three known disease insertions (SVA insertions in FKTN, TAF1 and BBS1), and `${SAMPLE}/mei/${SAMPLE}_mei.tsv` for every Alu, L1 and SVA insertion MEGAnE called, with one or two copies.