iT邦幫忙

2026 iThome 鐵人賽

DAY 22
0
Build on Google AI

《解構生命暗物質:用 AlphaGenome Atlas 破譯 98% 非編碼基因組的 30 天實戰》系列 第 22 篇

AlphaGenome Atlas 實戰 | Day 22 | 數據前處理與初篩 —— 使用 Python 提取與過濾候選非編碼變異

  • 分享至 

  • xImage
  •  

💡 導讀:為什麼有一筆低品質變異,報告卻沒有寫「低品質」?

​
想像機場安檢有六道關卡:證件、目的地、資料比對、特殊行李、資料完整性、品質門檻。如果旅客在第二關就被攔下,後面四關根本還沒進行。你不能因為報表沒有記載「第五關不合格」,就認定他通過了第五關。
​
昨天 Day 21,我們用七列人工 VCF 拆出八筆 ALT 候選,整理成 3 筆 SELECTED、2 筆 REVIEW_REQUIRED、3 筆 REJECTED。今天不增加新患者或新變異,反而要問一個更細的問題:程式是按照什麼順序判斷的?什麼時候應停止?哪些條件其實尚未檢查?
​

一、核心概念:六道 QC 關卡不是六個「同時出現」的標籤

​
固定的教學檢查順序是:
​
FILTER → BED → REF → INDEL → MISSING_QUAL → LOW_QUAL
​

關卡 Python 實際確認的問題 本篇的限制
FILTER VCF 的 FILTER 欄是否為 PASS? PASS 只表示來源變異呼叫流程的過濾狀態,不保證功能正常
BED POS−1 是否落在人工 BED 的 [start,end)? 本篇只比對 POS 錨點,不是完整 indel 覆蓋範圍
REF VCF 的 REF 是否符合本地人工參考序列? 人工參考是 200 個 A,不是 GRCh38
INDEL REF/ALT 是否都是單一鹼基? Indel 標成 INDEL_NOT_NORMALIZED,交人工審閱
MISSING_QUAL QUAL 是否為缺失值 .? 缺值不是 0
LOW_QUAL QUAL 是否達到本例人工門檻 20? 20 是教學門檻,不是通用臨床標準

依照 VCF 格式,POS 是 1-based;BED 常見的區間座標是 0-based、左含右不含。例如人工 BED [90,115),用本篇的 POS 錨點演示:POS=91 對應 0-based 90(在區間內),POS=115 對應 114(仍在內),POS=116 對應 115(已在外)。這一座標制度可以從 UCSC 的官方說明獨立查證。
​

重要補充:用 POS−1 判斷「錨點是否在 BED」是本篇最小教學規則。真正研究 indel 或複雜變異時,還需正規化變異並檢查 REF 長度、區間覆蓋、參考基因組版本等;不能把本篇小工具當成完整 VCF/BED 分析器。
​

二、揭開 Day 21 的「被前面關卡遮住」的理由

​
本篇直接重用 Day 21 的相同人工 VCF、人工參考與 BED,三個檔案的 SHA-256 已依 Day 21 審閱封包確認一致。七列 VCF 之中 chr1:110 有兩個 ALT,展開後共八筆候選。
​

人工候選 QC 結果 讀者應注意的地方
chr1:100:A>C、chr1:110:A>G、chr1:110:A>C 3 × SELECTED 只表示通過本例門檻,不代表致病
chr1:120:A>T(QUAL=10) OUTSIDE_TEACHING_BED 先被 BED 排除,所以未到 LOW_QUAL
chr1:130:A>G(FILTER=q10) FILTER_NOT_PASS 第一關就停止
chr1:140:G>T(REF=G) OUTSIDE_TEACHING_BED 先被 BED 排除,所以未到 REF_MISMATCH
chr1:150:A>AT INDEL_NOT_NORMALIZED REVIEW_REQUIRED,沒有假裝已正規化
chr1:160:A>T(QUAL=.) MISSING_QUAL REVIEW_REQUIRED,沒有把缺值視為 0

這張表告訴我們:報表的 reason 是第一個決定性原因,不是所有可能問題的完整清單。

補充實驗:兩筆真正落在 BED 內的人工候選

​
為了讓讀者親眼看到另外兩個 QC 分支,我們另造兩筆不加入原本八筆統計的教學案例:
​

  • chr1:105:A>T、QUAL=10、FILTER=PASS、人工 REF=A → LOW_QUAL。
  • chr1:106:G>T、QUAL=60、FILTER=PASS、人工 REF 實際是 A → REF_MISMATCH。
    ​
    它們不是新的真實研究資料。你也可以在報告裡追蹤每個案例實際走到了哪一道關卡。
    ​

💡 三、Google AI Studio:先做出能「看見 QC 路徑」的 MVP

​
還記得第一次打開空白的 VS Code 嗎?我們先不急著手寫表格,而是讓 Google AI Studio Build Mode 用固定的人工 JSON 做一個查詢介面,再回頭拆解 Python 怎麼準備這些資料。
​
在 Google AI Studio 使用下面的提示詞;此提示詞同時附在教學包的 AI_STUDIO_DAY22_PROMPT.txt:
​

請建立一個「Day 22 — VCF QC 決策追蹤」中文教學 MVP,參考醫學研究儀表板的清楚資訊設計。
​
資料來源限定人工合成資料,不要要求或存取真實病人資料、API 金鑰、Google/AlphaGenome SDK 或外部伺服器。
​
介面包括:
1. 頂部 8 筆 ALT 候選、3 SELECTED、2 REVIEW_REQUIRED、3 REJECTED 的摘要卡。
2. QC 順序視覺流程:FILTER → BED → REF → INDEL → MISSING_QUAL → LOW_QUAL。
3. 逐筆 VCF QC 表:variant_id、QUAL、FILTER、status、reason;可以展開「已檢查步驟」與「未檢查步驟」。
4. 兩個獨立教學案例:chr1:105:A>T 的 LOW_QUAL,以及 chr1:106:G>T 的 REF_MISMATCH。
5. BED 端點演示:[90,115) 對應 1-based 的 POS=91–115,不含 POS=116。
6. 明顯的 SYNTHETIC EDUCATIONAL DEMO、TOY_NOT_GRCH38、No real API call 標籤。
​
介面數值必須能追溯至預先固定的人工 JSON;不要自行捏造 API 回傳,也不要宣稱這份 UI 已實作真實 AlphaGenome API。
提供每個 UI 區塊與 Python 資料欄位的對照表,讓初學者之後可以從 MVP 回頭理解 Python 模組。

​
逆向拆解時看三件事:畫面上的狀態卡來自 candidate_count 等欄位;逐筆表格來自 qc_rows;展開的 QC 過程來自 checked_steps 與 not_checked_steps。AI Studio 可能先產生 React/JavaScript 介面;我們接下來用 Python 重建資料處理模組,並不宣稱下載程式碼就會自動轉成 Python。
​

💻 四、Python 實作:直接重用 Day 21 的 QC,不另外發明規則

​
這是 Day 21 → Day 22 的重點:我們把既有的 day21_offline_vcf_review_lab.py 視為已測試的基礎模組,再加入 trace_qc(),讓程式顯示每一步的結果。若 Day 22 的決策與 Day 21 check_qc() 不一致,程式會主動報錯,避免兩天的同一批資料有兩種解釋。
​
下載教學包後,資料夾需同時存在以下內容:
​

Day22 Lab/
├── day22_offline_qc_explorer_lab.py     # 本篇新程式
├── day21_offline_vcf_review_lab.py      # Day21 已測試模組;本篇直接匯入
├── day21_synthetic.vcf                 # Day21 原樣人工 VCF
├── day21_toy_reference.json            # Day21 原樣人工參考
├── day21_synthetic_regions.bed         # Day21 原樣人工 BED
└── test_day22_offline_qc_explorer_lab.py

​
請勿只複製下面一支 Python 就期待完整運作:這篇刻意演示模組重用,必須一起取得 ZIP 裡的 Day21 模組及三份合成資料。整包可在沒有 API Key、沒有網路的電腦上執行。
​

完整 day22_offline_qc_explorer_lab.py

​

"""Day 22 — inspect the first-decision FILTER/BED/REF/indel/QUAL QC chain.
​
This offline educational program REUSES the verified Day 21 parsing and QC
functions, on the byte-identical synthetic fixtures shipped alongside it.
It does not implement the full VCF standard, variant normalization, genome
alignment, clinical interpretation, or any AlphaGenome network call.
Python 3.12+ standard library only.
"""
from __future__ import annotations
​
import argparse
from collections import Counter
from html import escape
import hashlib
import json
import math
from pathlib import Path
​
from day21_offline_vcf_review_lab import (
    ASSEMBLY, QC_ORDER, check_qc, in_bed,
    load_toy_reference, parse_synthetic_vcf, parse_toy_bed,
)
​
EVIDENCE_CLASS = "SYNTHETIC_EDUCATIONAL_DEMO"
FAIL_REASON = {
    "FILTER": ("REJECTED", "FILTER_NOT_PASS"),
    "BED": ("REJECTED", "OUTSIDE_TEACHING_BED"),
    "REF": ("REJECTED", "REF_MISMATCH"),
    "INDEL": ("REVIEW_REQUIRED", "INDEL_NOT_NORMALIZED"),
    "MISSING_QUAL": ("REVIEW_REQUIRED", "MISSING_QUAL"),
    "LOW_QUAL": ("REJECTED", "LOW_QUAL"),
}
​
​
def validate_min_qual(value):
    """Validate a teaching threshold; it is NOT a clinical decision cutoff."""
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0:
        raise ValueError("min_qual must be a finite nonnegative number")
    return float(value)
​
​
def trace_qc(row, reference, intervals, min_qual=20.0):
    """Explain *only* the checks reached before Day21's deciding QC result.
​
    Checks later in the chain are deliberately NOT evaluated once a prior check
    makes the decision. This is central to the Day22 lesson.
    """
    min_qual = validate_min_qual(min_qual)
    steps = []
    status = "SELECTED"
    reason = "TEACHING_QC_PASS"
​
    for step in QC_ORDER:
        if step == "FILTER":
            passed = row["filter"] == "PASS"
            detail = "FILTER=PASS" if passed else "FILTER is not PASS"
        elif step == "BED":
            passed = in_bed(row["chrom"], row["pos_1based"], intervals)
            detail = f"VCF POS {row['pos_1based']} -> BED anchor {row['pos_1based'] - 1}"
        elif step == "REF":
            seq = reference.get(row["chrom"], "")
            start = row["pos_1based"] - 1
            expected = seq[start:start + len(row["ref"])]
            passed = expected == row["ref"]
            detail = "REF matches artificial reference" if passed else "REF mismatch / absent toy reference"
        elif step == "INDEL":
            passed = len(row["ref"]) == 1 and len(row["alt"]) == 1
            detail = "SNV teaching subset" if passed else "indel requires independent normalization/review"
        elif step == "MISSING_QUAL":
            passed = row["qual"] is not None
            detail = "QUAL present" if passed else "QUAL missing (dot in synthetic VCF)"
        elif step == "LOW_QUAL":
            passed = row["qual"] >= min_qual
            detail = f"QUAL compared against artificial threshold {min_qual:g}"
        else:
            raise RuntimeError(f"Unknown QC step in Day21 source module: {step}")
​
        steps.append({"check": step, "passed": passed, "detail": detail})
        if not passed:
            status, reason = FAIL_REASON[step]
            break
​
    original_status, original_reason = check_qc(row, reference, intervals, min_qual=min_qual)
    if (status, reason) != (original_status, original_reason):
        raise AssertionError("Day22 trace differs from the tested Day21 QC decision")
    return {"variant_id": row["variant_id"],
            "qc_status": status, "reason": reason,
            "bed_anchor_0based": row["pos_1based"] - 1,
            "checked_steps": steps,
            "not_checked_steps": QC_ORDER[len(steps):],
            "source_line": row.get("source_line"),
            "qual": row["qual"], "filter": row["filter"]}
​
​
def make_probe(label, pos, ref, alt, qual=60.0, filt="PASS", chrom="chr1"):
    return {"label": label, "variant_id": f"{chrom}:{pos}:{ref}>{alt}",
            "chrom": chrom, "pos_1based": pos, "ref": ref, "alt": alt,
            "qual": qual, "filter": filt, "source_line": None}
​
​
def extra_probe_rows():
    """Explicit isolated examples that DO exercise otherwise shadowed branches."""
    return [
        make_probe("LOW_QUAL_INSIDE_BED", 105, "A", "T", qual=10),
        make_probe("REF_MISMATCH_INSIDE_BED", 106, "G", "T"),
        make_probe("LOW_QUAL_OUTSIDE_BED", 120, "A", "T", qual=10),
        make_probe("REF_MISMATCH_OUTSIDE_BED", 140, "G", "T"),
        make_probe("BED_FIRST_INCLUDED_POS", 91, "A", "C"),
        make_probe("BED_LAST_INCLUDED_POS", 115, "A", "C"),
        make_probe("BED_FIRST_EXCLUDED_POS", 116, "A", "C"),
        make_probe("SECOND_BED_FIRST_INCLUDED_POS", 146, "A", "C"),
        make_probe("INDEL_NEEDS_REVIEW", 150, "A", "AT"),
        make_probe("MISSING_QUAL_NEEDS_REVIEW", 160, "A", "T", qual=None),
    ]
​
​
def qc_report(vcf_text, reference, intervals, min_qual=20.0):
    validate_min_qual(min_qual)
    if QC_ORDER != ["FILTER", "BED", "REF", "INDEL", "MISSING_QUAL", "LOW_QUAL"]:
        raise RuntimeError("Unexpected Day21 QC order; do not silently change lesson")
    input_rows = parse_synthetic_vcf(vcf_text)
    rows = [trace_qc(row, reference, intervals, min_qual) for row in input_rows]
    probes = [{"label": row["label"], **trace_qc(row, reference, intervals, min_qual)}
              for row in extra_probe_rows()]
    counts = Counter(row["qc_status"] for row in rows)
    reasons = Counter(row["reason"] for row in rows)
    assert sum(counts.values()) == len(input_rows)
    return {
        "data_kind": EVIDENCE_CLASS,
        "assembly": ASSEMBLY,
        "mode": "OFFLINE_ONLY",
        "qc_policy": "FIRST_DECIDING_REASON_ONLY",
        "qc_order": list(QC_ORDER),
        "bed_policy": "VCF_1BASED_POS_MINUS_ONE_ANCHOR_FOR_TOY_BED",
        "min_qual_artificial_threshold": float(min_qual),
        "candidate_count": len(input_rows),
        "selected_count": counts.get("SELECTED", 0),
        "review_required_count": counts.get("REVIEW_REQUIRED", 0),
        "rejected_count": counts.get("REJECTED", 0),
        "reason_counts": dict(sorted(reasons.items())),
        "qc_rows": rows,
        "supplementary_probe_cases": probes,
        "fixture_source": "DAY21_BYTE_IDENTICAL_SYNTHETIC_FIXTURES",
        "source_parser_and_qc_module": "day21_offline_vcf_review_lab.py",
        "real_genome_used": False,
        "real_api_calls_this_run": 0,
        "atlas_query_performed": False,
        "clinical_interpretation": False,
    }
​
​
def render_html(report):
    """Single-file static HTML dashboard; every data-derived string is escaped."""
    def q(value):
        return escape("null" if value is None else str(value), quote=True)
​
    rows_html = "\n".join(
        "<tr>" + "".join(f"<td>{q(value)}</td>" for value in (
            row["variant_id"], row["qc_status"], row["reason"], row["bed_anchor_0based"],
            row["qual"], " → ".join(s["check"] for s in row["checked_steps"]))) + "</tr>"
        for row in report["qc_rows"]
    )
    probes_html = "\n".join(
        "<tr>" + "".join(f"<td>{q(value)}</td>" for value in (
            row["label"], row["variant_id"], row["qc_status"], row["reason"],
            " → ".join(s["check"] for s in row["checked_steps"]))) + "</tr>"
        for row in report["supplementary_probe_cases"]
    )
    details_html = "\n".join(
        "<details><summary>" + q(row["variant_id"]) + " · " + q(row["reason"]) + "</summary>" +
        "<ol>" + "".join("<li>" + q(s["check"]) + ":" +
                            ("PASS" if s["passed"] else "STOP") + " — " + q(s["detail"]) + "</li>"
                            for s in row["checked_steps"]) + "</ol>" +
        "<p>未繼續檢查:" + q(", ".join(row["not_checked_steps"]) or "無") + "</p></details>"
        for row in report["qc_rows"]
    )
    return f'''<!doctype html><html lang="zh-Hant"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Day22 | Offline VCF QC Inspector</title>
<style>:root{{font:16px/1.6 system-ui,"Noto Sans TC",sans-serif;color:#263b51;background:#f1f4f8}}
body{{max-width:1140px;margin:auto;padding:32px 18px 64px}}h1{{margin:0;font-size:1.8rem}}.lead{{color:#526477}}
.notice{{background:#fff4dc;border-left:6px solid #bc833b;padding:16px;margin:20px 0;border-radius:8px}}
.stats{{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:12px}}
.stats div,section{{background:white;border:1px solid #dce4ed;border-radius:14px;padding:16px;margin-top:15px}}
.number{{font-size:2rem;font-weight:800;color:#346b97}}.sub{{font-size:.85rem;color:#667789}}
table{{width:100%;border-collapse:collapse}}th,td{{padding:9px;text-align:left;border-bottom:1px solid #e9ebed;vertical-align:top}}
th{{background:#e9eff5}}section{{overflow-x:auto}}details{{padding:12px;border-bottom:1px solid #dce4ed}}
summary{{cursor:pointer;font-weight:600}}code{{background:#f2f5f8;padding:2px 4px}}.warn{{color:#8d3639}}
footer{{font-size:.84rem;color:#53657b;margin-top:24px}}</style></head><body>
<h1>Day 22|VCF 資料前處理 — FILTER、QUAL、REF 與 BED QC</h1>
<p class="lead">從 Day21 同一批人工候選資料,觀察「第一個決定性原因」如何產生。</p>
<div class="notice"><strong>OFFLINE · SYNTHETIC EDUCATIONAL DEMO</strong><br>
參考序列是 200 個人工 A,基因組標記 TOY_NOT_GRCH38。沒有患者、真實模型、AlphaGenome API 或臨床判讀。</div>
<div class="stats"><div><span class="number">{report['candidate_count']}</span><br><span class="sub">ALT 候選</span></div>
<div><span class="number">{report['selected_count']}</span><br><span class="sub">SELECTED</span></div>
<div><span class="number">{report['review_required_count']}</span><br><span class="sub">QC 待審</span></div>
<div><span class="number">{report['rejected_count']}</span><br><span class="sub">REJECTED</span></div></div>
<section><h2>1|QC 主表</h2><p>判定順序:{q(' → '.join(report['qc_order']))}。</p>
<p>只記錄第一個決定性原因,不表示其他尚未執行的檢查已通過。</p>
<table><thead><tr><th>變異</th><th>狀態</th><th>原因</th><th>BED anchor</th><th>QUAL</th><th>已檢查步驟</th></tr></thead><tbody>{rows_html}</tbody></table></section>
<section><h2>2|打開 QC 決策路徑</h2>{details_html}</section>
<section><h2>3|另外建立、位於 BED 內的人工探針</h2>
<p>預設 POS 120 的低 QUAL、POS 140 的 REF mismatch 都先被 BED 排除。用 POS 105/106 才能真正檢查 LOW_QUAL 和 REF_MISMATCH。</p>
<table><thead><tr><th>探針</th><th>變異</th><th>狀態</th><th>原因</th><th>檢查路徑</th></tr></thead><tbody>{probes_html}</tbody></table></section>
<footer>資料來源:DAY21_BYTE_IDENTICAL_SYNTHETIC_FIXTURES · {q(report['assembly'])} · atlas_query_performed=false · real_api_calls_this_run=0。<br>
僅練習教學 QC;此處用 POS−1 作 BED anchor 判定,indel 仍須正常化及更嚴謹的跨區間處理。</footer></body></html>'''
​
​
def execute_offline(vcf_path, reference_path, regions_path, output_dir, min_qual=20.0):
    paths = {"vcf": Path(vcf_path), "reference": Path(reference_path),
             "regions": Path(regions_path)}
    raw = {kind: path.read_text(encoding="utf-8") for kind, path in paths.items()}
    report = qc_report(raw["vcf"], load_toy_reference(raw["reference"]),
                       parse_toy_bed(raw["regions"]), min_qual=min_qual)
    report["input_provenance"] = {
        kind: {"filename": path.name, "sha256": hashlib.sha256(path.read_bytes()).hexdigest()}
        for kind, path in paths.items()
    }
    folder = Path(output_dir)
    folder.mkdir(parents=True, exist_ok=True)
    json_path = folder / "day22_qc_report.json"
    html_path = folder / "day22_qc_dashboard.html"
    json_path.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    html_path.write_text(render_html(report), encoding="utf-8")
    return {"status": "PASS", "mode": "OFFLINE_ONLY",
            "candidate_count": report["candidate_count"],
            "selected_count": report["selected_count"],
            "review_required_count": report["review_required_count"],
            "rejected_count": report["rejected_count"],
            "low_qual_probe": next(p["reason"] for p in report["supplementary_probe_cases"]
                                   if p["label"] == "LOW_QUAL_INSIDE_BED"),
            "ref_mismatch_probe": next(p["reason"] for p in report["supplementary_probe_cases"]
                                       if p["label"] == "REF_MISMATCH_INSIDE_BED"),
            "json_file": str(json_path), "html_file": str(html_path),
            "real_api_calls_this_run": 0}
​
​
def main(argv=None):
    p = argparse.ArgumentParser(description="Day22 offline synthetic QC inspector (never LIVE)")
    p.add_argument("--mode", choices=["offline"], required=True)
    p.add_argument("--vcf", default="day21_synthetic.vcf")
    p.add_argument("--reference", default="day21_toy_reference.json")
    p.add_argument("--regions", default="day21_synthetic_regions.bed")
    p.add_argument("--output-dir", default="day22_output")
    p.add_argument("--min-qual", type=float, default=20.0,
                   help="Artificial QC threshold for educational purposes only")
    args = p.parse_args(argv)
    summary = execute_offline(args.vcf, args.reference, args.regions,
                              args.output_dir, min_qual=args.min_qual)
    print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
    return summary
​
​
if __name__ == "__main__":
    main()

​

▶ 在你的 Windows/WSL 執行

​
因為你先前的 WSL 沒有安裝 unzip,這裡直接使用 Python 標準函式庫內建的 zipfile,不必 apt install 任何套件。
​
以下程式碼貼在 Ubuntu Bash 終端機,勿貼到 Python 提示符號 >>>。
​

cd /mnt/c/Users/user/Downloads
mkdir -p ~/alphagenome-day22-qa
​
python3.12 -m zipfile -e \
  AlphaGenome_Day22_VCF_QC_API_Bridge_Author_Review_Pack.zip \
  ~/alphagenome-day22-qa
​
cd ~/alphagenome-day22-qa
ls -l day22_offline_qc_explorer_lab.py \
      day21_offline_vcf_review_lab.py \
      test_day22_offline_qc_explorer_lab.py \
      day21_synthetic.vcf day21_toy_reference.json day21_synthetic_regions.bed
​
python3.12 -m unittest -v test_day22_offline_qc_explorer_lab
​
python3.12 day22_offline_qc_explorer_lab.py \
  --mode offline \
  --vcf day21_synthetic.vcf \
  --reference day21_toy_reference.json \
  --regions day21_synthetic_regions.bed \
  --output-dir day22_output

​

▶ 預期與實測輸出(本次開發環境 Python 3.13.5)

​

{"candidate_count": 8, "html_file": "day22_output/day22_qc_dashboard.html", "json_file": "day22_output/day22_qc_report.json", "low_qual_probe": "LOW_QUAL", "mode": "OFFLINE_ONLY", "real_api_calls_this_run": 0, "ref_mismatch_probe": "REF_MISMATCH", "rejected_count": 3, "review_required_count": 2, "selected_count": 3, "status": "PASS"}

​
該指令產生:
​

  • day22_output/day22_qc_report.json:八筆候選的來源與判定、每一關檢查歷程、十筆補充教學案例、輸入 SHA-256。
  • day22_output/day22_qc_dashboard.html:可在瀏覽器打開的離線展示頁;點開每筆候選,可以看到已執行與尚未執行的 QC 關卡。
    ​
    Windows 檔案總管可開啟:
    ​
    \\wsl.localhost\Ubuntu-24.04\home\user\alphagenome-day22-qa\day22_output
    ​
    此處 LOW_QUAL、REF_MISMATCH 來自另外建立的補充案例;原本八筆候選的 3/2/3 統計沒有被偷偷改掉。
    ​

五、結果怎麼讀?一張小表就懂

​

情況 不是什麼 今天真正學到的
FILTER=PASS 不代表生物機制正常 只是本例能繼續檢查其他條件
OUTSIDE_TEACHING_BED 不代表 REF、QUAL 都良好 表示此筆已在較早關卡停止
LOW_QUAL 不等於「必然沒有功能」 是人工門檻下的 QC 排除原因
REF_MISMATCH 不等於變異有致病性 需要核對 assembly、參考序列與資料來源
REVIEW_REQUIRED 不等於高風險或已確認異常 代表資料尚有未解決的品質或格式議題

和真實 AlphaGenome API 的關係:今天不需要查詢 Atlas。未來進入真實 API 階段前,必須先有可追溯的 GRCh38/REF 核驗、正確變異表示方式與已獲准的查詢流程;不能將 TOY_NOT_GRCH38 的候選直接當成真實模型輸入,也不能用真實服務的回傳分數覆蓋這份人工 QC 證據。
​

✅ 本日 Checklist

​

  • [ ] 我知道 VCF POS 是 1-based,BED 是 0-based、右端不包含。
  • [ ] 我能解釋 [90,115) 為何包含 POS=115,卻不包含 POS=116。
  • [ ] 我重跑八筆候選並看見 selected=3、review_required=2、rejected=3。
  • [ ] 我能找出 POS=120、140 為何都先被 BED 排除,而不是直接出現 LOW_QUAL/REF_MISMATCH。
  • [ ] 我能從補充案例得到 LOW_QUAL、REF_MISMATCH,並區分它們和原本八筆結果。
  • [ ] 我知道 HTML 與 JSON 都是人工合成結果,真實 API 呼叫為 0。
    ​

六、科學證據與限制

​
證據層級:SYNTHETIC EDUCATIONAL DEMO。 本篇程式不是完整 VCF reader、Genome Assembly 比對工具、變異正規化流程、族群變異註解或臨床致病性解讀。借用 Day21 的八欄、無樣本 VCF 小型子集;正式 VCF 還可包含 FORMAT、樣本基因型與其他複雜表示法。真正研究還需要可靠的參考 FASTA、assembly、複本與資料品質紀錄,以及正確的 variant normalization。
​
此次測試只驗證程式是否符合人工資料契約與已知邊界。本輪沒有呼叫 AlphaGenome Atlas/Model API,沒有患者資料,也沒有得到新的 AVI 或分子預測。
​

七、明日預告

​
明天 Day 23,我們把 QC 通過的候選和人工註釋欄位接起來,練習保留 null、追蹤資料來源並產生結構化標註。主題是:「自動化批次標註 —— 從缺失值到結構化欄位設計」。
​

​

延伸閱讀與參考資料

​


上一篇
AlphaGenome Atlas 實戰 | Day 21 | 從 VCF 到研究審閱封包 —— QC、註釋與證據界線
下一篇
AlphaGenome Atlas 實戰 | Day 23 | 自動化批次標註 —— 從缺失值到結構化欄位設計
系列文
《解構生命暗物質:用 AlphaGenome Atlas 破譯 98% 非編碼基因組的 30 天實戰》 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言