如果一張航海圖只畫出順暢的航線,卻不標示暗礁與淺灘,船長可能會面臨觸礁風險。在生醫與基因研究領域,AI 的分析報告也是一樣——不僅要呈現預測結果,更要清楚寫明資料從哪裡來、有哪些缺漏,以及 AI 目前「還做不到什麼」。
null)並標註 MISSING_SCORE。若為了方便隨便補個 0,可能會讓讀者誤以為「這項指標經過測試且確定沒有影響」。manual_review = REQUIRED。| 核心概念 | 報告欄位(本例輸出) | 用途 |
|---|---|---|
| Evidence provenance | source_id |
記錄資料來源,方便追溯輸入是來自哪個 fixture 或資料集。 |
| SHA256 | evidence_provenance.input_sha256 | 為輸入內容建立唯一指紋,確保後續可驗證與重現同一份資料。 |
| Missing values | score=null、MISSING_SCORE |
保留缺值,明確區分「沒有分數」與「分數為 0」,避免錯誤解讀。 |
| Limitation on Disclosure(限制揭露) | model_limitations | 主動揭露模型能力邊界,提醒結果不代表完整生物學證據。 |
| Manual review | manual_review =REQUIRED |
標示待審,不代表已批准 |
| 臨床分類 | clinical_classification = null、confidence_calibrated = false | 避免將輸出誤解為疾病風險評估、診斷結果或臨床分 |
總結:
這個範例最重要的不是產生一個分數,而是示範如何讓結果具備可追溯性(Provenance)、可驗證性(SHA256)、限制揭露(Limitations)與人工審查(Manual Review)等資訊,避免輸出被誤用為臨床判讀或超出證據範圍的結論。
文章中附帶的 Python 程式(DAY28_INLINE_DEMO.py),就像是一個「透明報告自動生成器」:
day28_report.json 檔案,以及人類方便閱讀的 Markdown 文章。報告中會完整包含上述的數位指紋、缺值標示、三條模型限制以及待審審查狀態。
"""Small offline evidence report inspired by the separate Day28 Reporter MVP."""
import argparse
import hashlib
import json
import math
import re
from pathlib import Path
# 教學用途的合成資料輸入
# 故意限制為 synthetic,避免被誤解為真實生醫分析結果
INPUT = {"data_kind": "SYNTHETIC_EDUCATIONAL_DEMO",
"variant_id": "synthetic-example-001", "score": None,
"provenance": {"source_type": "synthetic",
"source_id": "day28-inline-fixture-001"}}
# 除了輸出結果,也需明確揭露模型限制
LIMITATIONS = ["No trans-regulatory or tissue-specific mechanism validation",
"No reference-allele or ancestry-performance validation",
"No calibrated disease probability or clinical classification"]
# 限制識別碼格式,避免特殊字元污染報告內容
IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}\Z", re.ASCII)
def valid_identifier(value, field):
"""Allow short ASCII identifiers that cannot add Markdown syntax or lines."""
if not isinstance(value, str) or IDENTIFIER.fullmatch(value) is None:
raise ValueError(f"{field} must be 1-64 ASCII letters, digits, dot, underscore or hyphen; start alphanumeric")
return value
def build_report(record=INPUT):
# 基本輸入格式驗證
if not isinstance(record, dict):
raise ValueError("Record must be a JSON object")
provenance = record.get("provenance", {})
if not isinstance(provenance, dict):
raise ValueError("Provenance must be a JSON object")
variant_id = valid_identifier(record.get("variant_id"), "variant_id")
source_id = valid_identifier(provenance.get("source_id"), "source_id")
# 僅接受明確標示的合成資料
if record.get("data_kind") != "SYNTHETIC_EDUCATIONAL_DEMO" or \
provenance.get("source_type") != "synthetic":
raise ValueError("Only explicit synthetic teaching input is accepted")
score = record.get("score")
# score 允許數值或 null、排除 NaN、Infinity 等特殊值
if score is not None and (not isinstance(score, (int, float)) or
isinstance(score, bool) or not math.isfinite(score)):
raise ValueError("Score must be finite numeric or null")
# 建立輸入資料的 SHA-256 指紋,方便後續追蹤與重現
canonical = json.dumps(record, ensure_ascii=False, sort_keys=True,
separators=(",", ":")).encode("utf-8")
return {"report_kind": "SYNTHETIC_EDUCATIONAL_DEMO",
"variant_id": variant_id,
# 資料來源與證據追蹤資訊
"evidence_provenance": {"source_type": "synthetic",
"source_id": source_id,
"input_sha256": hashlib.sha256(canonical).hexdigest()},
"score": score,
# 明確區分缺漏分數與示範分數
"score_status": "MISSING_SCORE" if score is None else "SYNTHETIC_SCORE",
# 已知限制必須一併呈現
"model_limitations": LIMITATIONS,
# 強制要求人工審查
"manual_review": "REQUIRED",
# 不聲稱信心校準或臨床分類能力
"confidence_calibrated": False,
# 本範例不呼叫任何外部服務
"clinical_classification": None,
"real_api_calls": 0}
def markdown(report):
# 報告格式驗證
if not isinstance(report, dict) or not isinstance(report.get("evidence_provenance"), dict):
raise ValueError("Report and evidence_provenance must be JSON objects")
variant_id = valid_identifier(report.get("variant_id"), "variant_id")
source_id = valid_identifier(report["evidence_provenance"].get("source_id"), "source_id")
score_text = "missing (JSON null)" if report["score"] is None else str(report["score"])
limitations = "\n".join(f"- {item}" for item in report["model_limitations"])
# 產生人類可閱讀的 Markdown 報告
return (f"# Synthetic Evidence Report: {variant_id}\n\n"
f"Source ID: {source_id}\n\n"
f"Input SHA-256: {report['evidence_provenance']['input_sha256']}\n\n"
f"Score: {score_text} ({report['score_status']})\n\n"
f"Manual review: {report['manual_review']}\n\n"
f"## Model limitations\n\n{limitations}\n\n"
"Educational synthetic data only; no model call or clinical decision.\n")
def write_report(output_dir, record=INPUT):
report = build_report(record)
folder = Path(output_dir)
folder.mkdir(parents=True, exist_ok=True)
# 同時輸出 JSON 與 Markdown
(folder / "day28_report.json").write_text(
json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
(folder / "day28_report.md").write_text(markdown(report), encoding="utf-8")
return {"status": "PASS", "report_mode": "SEPARATE_MVP_INSPIRED_INLINE_DEMO",
"score_status": report["score_status"], "manual_review": report["manual_review"],
"json_file": str(folder / "day28_report.json"),
"markdown_file": str(folder / "day28_report.md"), "real_api_calls": 0}
def main():
# CLI 入口
parser = argparse.ArgumentParser(description="Synthetic evidence report lesson")
parser.add_argument("--output-dir", default="day28_output")
args = parser.parse_args()
print(json.dumps(write_report(args.output_dir), ensure_ascii=False, sort_keys=True))
if __name__ == "__main__":
main()
python3.12 DAY28_INLINE_DEMO.py --output-dir day28_output
{"json_file": "day28_output/day28_report.json", "manual_review": "REQUIRED", "markdown_file": "day28_output/day28_report.md", "real_api_calls": 0, "report_mode": "SEPARATE_MVP_INSPIRED_INLINE_DEMO", "score_status": "MISSING_SCORE", "status": "PASS"}
evidence_provenance.source_id 與輸入 SHA-256。score=null、score_status=MISSING_SCORE、manual_review=REQUIRED。model_limitations,驗證非法識別碼被拒絕,並說明 Reporter 仍是 SEPARATE_MVP。這篇教學的核心目的,是希望研究者在開發或使用生醫 AI 工具時,建立一套負責任的科學態度:數據公開透明、保留原始缺值、誠實標示限制,並且永遠由人類進行最後的把關。
明天 Day29 將從證據界線走向基因資料的倫理與隱私;Day30 再整理整個專欄的知識與程式對照。