feat: refine gene family result report
This commit is contained in:
@@ -15354,6 +15354,10 @@ async def get_results(
|
||||
if group_stage not in visible_stage_names:
|
||||
result_groups.pop(group_name, None)
|
||||
|
||||
gene_family_report_group = result_groups.get("gene_family_report")
|
||||
if isinstance(gene_family_report_group, dict):
|
||||
stages["gene_family_report"] = gene_family_report_group
|
||||
|
||||
stages["_result_groups"] = result_groups
|
||||
stages["_scope"] = {
|
||||
"rnaseq_scope_id": active_rnaseq_scope_id,
|
||||
|
||||
@@ -14,6 +14,12 @@ from gfa_core.gene_family_report_data import (
|
||||
ReportSection,
|
||||
build_report_data,
|
||||
)
|
||||
from gfa_core.gene_family_report_html import (
|
||||
REPORT_RENDERER_VERSION,
|
||||
collect_stage_figures,
|
||||
markdown_to_html,
|
||||
render_stage_figures,
|
||||
)
|
||||
from gfa_core.mas.agents.interpretation_evidence import load_stage_manifests
|
||||
|
||||
|
||||
@@ -40,6 +46,7 @@ class GeneFamilyReportResult:
|
||||
generation_mode: str
|
||||
included_stages: tuple[str, ...]
|
||||
reused: bool = False
|
||||
html_path: Path | None = None
|
||||
|
||||
|
||||
def _normalized_stages(stages: Iterable[str] | None) -> list[str]:
|
||||
@@ -185,12 +192,32 @@ def _arranged_content(section: ReportSection, arranged: Mapping[str, Any]) -> st
|
||||
return "\n\n".join(paragraphs)
|
||||
|
||||
|
||||
def _render_completion_table(report_data: ReportData) -> str:
|
||||
supported_stages = {section.stage for section in report_data.sections if section.stage}
|
||||
included = set(report_data.included)
|
||||
status_labels = {"success": "成功", "partial_success": "部分完成", "failed": "失败", "missing": "未完成"}
|
||||
lines = [
|
||||
f"本次计划 {len(report_data.planned)} 个阶段,纳入解读 {len(report_data.included)} 个,未纳入 {len(report_data.planned) - len(report_data.included)} 个。",
|
||||
"",
|
||||
"| 分析阶段 | 运行状态 | 模板支持 | 纳入报告 |",
|
||||
"|---|---|---|---|",
|
||||
]
|
||||
for stage in report_data.planned:
|
||||
status = str(report_data.statuses.get(stage, "missing"))
|
||||
lines.append(
|
||||
f"| `{stage}` | {status_labels.get(status, status)} | "
|
||||
f"{'是' if stage in supported_stages else '否'} | {'是' if stage in included else '否'} |"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _render_markdown(
|
||||
report_data: ReportData,
|
||||
*,
|
||||
job_id: str,
|
||||
generation_mode: str,
|
||||
arrangement: Mapping[str, Any],
|
||||
figures_by_stage: Mapping[str, list[dict[str, str]]],
|
||||
) -> str:
|
||||
section_data = {section.section_id: section for section in report_data.sections}
|
||||
arranged = {section["section_id"]: section for section in arrangement["sections"]}
|
||||
@@ -198,7 +225,15 @@ def _render_markdown(
|
||||
for section_id, block in _template_blocks():
|
||||
if section_id not in section_data:
|
||||
continue
|
||||
rendered.append(block.replace("{{SECTION_CONTENT}}", _arranged_content(section_data[section_id], arranged[section_id])))
|
||||
content = (
|
||||
_render_completion_table(report_data)
|
||||
if section_id == "common.completeness"
|
||||
else _arranged_content(section_data[section_id], arranged[section_id])
|
||||
)
|
||||
stage = section_id.removeprefix("stage.") if section_id.startswith("stage.") else ""
|
||||
if stage and figures_by_stage.get(stage):
|
||||
content += render_stage_figures(stage, figures_by_stage[stage])
|
||||
rendered.append(block.replace("{{SECTION_CONTENT}}", content))
|
||||
markdown = "\n\n".join(rendered)
|
||||
fallback_notice = (
|
||||
"> 生成模式:候补脚本。模型 API 不可用或输出越过语言编排边界;报告事实仍来自确定性结果数据。"
|
||||
@@ -237,9 +272,14 @@ def generate_gene_family_report(
|
||||
raise ValueError("action must be 'generate' or 'open'")
|
||||
final_dir = Path(workspace).expanduser().resolve() / "final"
|
||||
report_path = final_dir / f"{prefix}.gene_family_report.md"
|
||||
html_path = final_dir / f"{prefix}.gene_family_report.html"
|
||||
manifest_path = final_dir / f"{prefix}.gene_family_report.manifest.json"
|
||||
if action == "open" and report_path.is_file() and manifest_path.is_file():
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
if not html_path.is_file():
|
||||
html_path.write_text(markdown_to_html(report_path.read_text(encoding="utf-8"), report_path, f"{prefix} 基因家族分析结果解读报告"), encoding="utf-8")
|
||||
manifest.setdefault("artifacts", {})["gene_family_report_html"] = {"path": f"final/{html_path.name}", "full_path": str(html_path), "type": "html"}
|
||||
manifest_path.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
||||
metrics = manifest.get("metrics") or {}
|
||||
return GeneFamilyReportResult(
|
||||
report_path,
|
||||
@@ -248,6 +288,7 @@ def generate_gene_family_report(
|
||||
str(metrics.get("generation_mode") or "fallback_script"),
|
||||
tuple(metrics.get("included_stages") or []),
|
||||
reused=True,
|
||||
html_path=html_path,
|
||||
)
|
||||
|
||||
planned = _normalized_stages(expected_stages)
|
||||
@@ -283,14 +324,17 @@ def generate_gene_family_report(
|
||||
"reason": "api_unavailable_or_boundary_violation",
|
||||
}
|
||||
|
||||
figures_by_stage = collect_stage_figures(final_dir, report_data.included, manifests)
|
||||
markdown = _render_markdown(
|
||||
report_data,
|
||||
job_id=job_id,
|
||||
generation_mode=generation_mode,
|
||||
arrangement=arrangement,
|
||||
figures_by_stage=figures_by_stage,
|
||||
)
|
||||
final_dir.mkdir(parents=True, exist_ok=True)
|
||||
report_path.write_text(markdown, encoding="utf-8")
|
||||
html_path.write_text(markdown_to_html(markdown, report_path, f"{prefix} 基因家族分析结果解读报告"), encoding="utf-8")
|
||||
manifest = {
|
||||
"manifest_version": "1.0",
|
||||
"stage": REPORT_STAGE,
|
||||
@@ -308,6 +352,8 @@ def generate_gene_family_report(
|
||||
"included_stages": list(report_data.included),
|
||||
"unsupported_stages": list(report_data.unsupported_stages),
|
||||
"failed_or_missing_stages": [stage for stage in planned if stage not in report_data.included],
|
||||
"report_renderer_version": REPORT_RENDERER_VERSION,
|
||||
"embedded_figure_count": sum(len(items) for items in figures_by_stage.values()),
|
||||
},
|
||||
"model": model_metadata,
|
||||
"artifacts": {
|
||||
@@ -316,6 +362,9 @@ def generate_gene_family_report(
|
||||
"full_path": str(report_path),
|
||||
"type": "markdown",
|
||||
},
|
||||
"gene_family_report_html": {
|
||||
"path": f"final/{html_path.name}", "full_path": str(html_path), "type": "html",
|
||||
},
|
||||
"gene_family_report_manifest": {
|
||||
"path": f"final/{manifest_path.name}",
|
||||
"full_path": str(manifest_path),
|
||||
@@ -330,4 +379,5 @@ def generate_gene_family_report(
|
||||
report_data.report_status,
|
||||
generation_mode,
|
||||
report_data.included,
|
||||
html_path=html_path,
|
||||
)
|
||||
|
||||
@@ -719,13 +719,7 @@ def build_report_data(
|
||||
)
|
||||
for index, sentence in enumerate(integrated_observations, start=1)
|
||||
),
|
||||
interpretations=(
|
||||
_sentence(
|
||||
"common.integrated.boundary",
|
||||
"综合结论只并列上述已提取事实;当前源结果不足以无偏地指定候选基因,也不据此建立功能因果。",
|
||||
"interpretation",
|
||||
),
|
||||
),
|
||||
interpretations=(),
|
||||
),
|
||||
ReportSection(
|
||||
"common.limitations",
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# HTML rendering and representative-figure selection for gene-family reports.
|
||||
from __future__ import annotations
|
||||
import base64
|
||||
import html
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
import re
|
||||
from typing import Any, Iterable, Mapping
|
||||
|
||||
REPORT_RENDERER_VERSION = "gene_family_report_html_v4"
|
||||
IMAGE_SUFFIXES = {".png", ".jpg", ".jpeg", ".webp", ".svg"}
|
||||
STAGE_FIGURE_TEXT = {
|
||||
"phylogeny": ("系统发育图展示目标成员与参考成员之间的聚类关系。", "结合分支拓扑、节点支持率和成员标签阅读,不把邻近关系直接等同于功能一致。", "目标与参考序列的聚类关系见图,具体分支以图中节点和标签为准。"),
|
||||
"reference_classification": ("参考分类图展示目标成员与已知参考亚家族的相对位置。", "结合参考成员标签、目标成员所在分支及支持率判断分类线索。", "目标成员的参考辅助分类位置见图,分类结论以本节确定性结果为准。"),
|
||||
"motif": ("Motif图展示各成员短保守片段的组成、顺序和相对位置。", "按成员逐行比较Motif颜色、编号、数量和排列,不把Motif相似直接写成功能相同。", "本次成员的Motif组成与排列见图,数量统计以本节结果数据为准。"),
|
||||
"domain": ("Domain图展示蛋白主要功能区域的类型和位置。", "结合结构域名称、覆盖区间和成员间差异阅读。", "各成员的结构域组成见图,覆盖数量和比例以本节结果数据为准。"),
|
||||
"structure": ("基因结构图展示外显子、内含子和基因长度的相对关系。", "结合图例比较外显子数量、结构排列和异常长基因。", "目标成员的基因结构差异见图,具体统计以本节结果数据为准。"),
|
||||
"combo": ("综合组合图并列展示系统发育、Motif、Domain和基因结构信息。", "沿同一成员横向比较多个证据面板,关注一致模式与明显差异。", "多类结构证据的对应关系见图,不据此单独确认功能改变。"),
|
||||
"promoter": ("启动子图展示顺式作用元件的类型、数量或位置分布。", "结合元件图例、位置坐标和成员标签比较,不把预测位点写成已验证调控。", "目标成员启动子元件的分布见图,数量和类别统计以本节结果数据为准。"),
|
||||
"synteny": ("共线性图展示染色体区段和基因对之间的连线关系。", "结合染色体标签、连线和家族成员标记阅读,区分全基因组背景与家族证据。", "本次共线性背景见图,家族专属关系以本节可追溯结果为准。"),
|
||||
"karyotype": ("染色体分布图展示家族成员在染色体上的物理位置。", "结合染色体编号、位置刻度和基因标签查看分布范围。", "目标家族成员的染色体位置见图,成员数和染色体数以本节结果数据为准。"),
|
||||
"interspecies_synteny": ("种间共线性图展示目标物种与参考物种之间的保守区块和基因连线。", "结合物种、染色体、标记连线和成员标签阅读,不直接移植参考物种功能。", "跨物种共线性关系见图,目标与参考家族配对数以本节结果数据为准。"),
|
||||
"pep": ("蛋白特征图展示理化性质、跨膜区域或亚细胞定位预测结果。", "根据具体图例比较成员分布,区分不同预测子任务。", "本次蛋白预测结果见图,具体数量与分类以本节结果数据为准。"),
|
||||
"tfbs": ("Motif统计图展示不同基因的位点计数、全部Motif计数和高频Motif。", "结合基因标签、颜色图例、计数刻度和Motif名称比较分布。", "本次gene、all和top motif统计结果见图,具体计数以图例和源结果为准。"),
|
||||
}
|
||||
|
||||
def _artifact_image(final_dir: Path, artifact: Any) -> Path | None:
|
||||
if not isinstance(artifact, Mapping): return None
|
||||
raw = str(artifact.get("full_path") or artifact.get("path") or "").strip()
|
||||
target = final_dir / Path(raw).name if raw else None
|
||||
return target if target and target.is_file() and target.suffix.lower() in IMAGE_SUFFIXES else None
|
||||
|
||||
def _kind(role: str, filename: str) -> str:
|
||||
value = f"{role} {filename}".lower()
|
||||
groups = (("gene_motif_count", ("gene_motif_heatmap",)), ("all_motif_count", ("all_motif_count_heatmap",)), ("top_motif", ("top_motifs_barplot",)), ("tree", ("tree",)), ("msa", ("msa_plot", "conserved_region")), ("synteny", ("synteny", "circos")), ("motif", ("motif",)), ("domain", ("domain",)), ("structure", ("structure",)), ("combo", ("combo",)), ("promoter_combined", ("promoter_v6_combined",)), ("promoter_position", ("promoter_position_distribution",)), ("promoter", ("promoter",)), ("karyotype", ("karyotype",)), ("tmhmm", ("tmhmm",)), ("subcellular", ("subcellular",)), ("properties", ("properties",)))
|
||||
return next((kind for kind, tokens in groups if any(token in value for token in tokens)), role.lower())
|
||||
|
||||
def _rank(role: str, path: Path) -> tuple[int, int, str]:
|
||||
value = f"{role} {path.name}".lower()
|
||||
role_lower = role.lower()
|
||||
preferred = -5 if role_lower == "synteny_marked_png" else -4 if "circular" in value else -2 if "marked" in value else -1 if "summary" in value else 0
|
||||
return (preferred, 0, path.name)
|
||||
|
||||
def collect_stage_figures(final_dir: Path, stages: Iterable[str], manifests: Mapping[str, Mapping[str, Any]]) -> dict[str, list[dict[str, str]]]:
|
||||
result = {}
|
||||
for stage in stages:
|
||||
selected = {}; seen = set()
|
||||
for role, artifact in ((manifests.get(stage) or {}).get("artifacts") or {}).items():
|
||||
target = _artifact_image(final_dir, artifact)
|
||||
if target is None or target.name in seen: continue
|
||||
seen.add(target.name); kind = _kind(str(role), target.name)
|
||||
candidate = (_rank(str(role), target), {"role": str(role), "filename": target.name})
|
||||
if kind not in selected or candidate[0] < selected[kind][0]: selected[kind] = candidate
|
||||
if stage == "tfbs":
|
||||
for role, pattern in (
|
||||
("gene_motif_heatmap", "*gene_motif_heatmap.png"),
|
||||
("all_motif_count_heatmap", "*all_motif_count_heatmap.png"),
|
||||
("top_motifs_barplot", "*top_motifs_barplot.png"),
|
||||
):
|
||||
matches = sorted(final_dir.glob(pattern))
|
||||
if not matches:
|
||||
continue
|
||||
target = matches[0]
|
||||
kind = _kind(role, target.name)
|
||||
selected[kind] = (_rank(role, target), {"role": role, "filename": target.name})
|
||||
figures = [entry[1] for _, entry in sorted(selected.items(), key=lambda item: (item[1][0], item[0]))][:4]
|
||||
if figures: result[stage] = figures
|
||||
return result
|
||||
|
||||
def render_stage_figures(stage: str, figures: list[dict[str, str]]) -> str:
|
||||
meaning, reading, result = STAGE_FIGURE_TEXT.get(stage, ("结果图展示该阶段的主要计算产物。", "结合图题、坐标轴、图例和成员标签阅读。", "本阶段代表结果见图,具体事实以本节确定性数据为准。"))
|
||||
lines = ["", "### 结果图"]
|
||||
for index, figure in enumerate(figures, 1):
|
||||
title = figure["role"]
|
||||
lines.extend(["", f"#### 图 {index}. {title}", "", f"", "", f"图形含义:{meaning}", "", f"阅读方法:{reading}", "", f"本次结果:{result}"])
|
||||
return "\n".join(lines)
|
||||
|
||||
def _inline(value: str, report_path: Path) -> str:
|
||||
tokens = []
|
||||
def stash(content):
|
||||
token = f"@@GFAHTML{len(tokens)}@@"; tokens.append(content); return token
|
||||
def image(match):
|
||||
alt = match.group(1).strip() or "基因家族结果图"; target = report_path.parent / Path(match.group(2).strip().replace("\\", "/")).name
|
||||
source = target.name; mime = mimetypes.guess_type(target.name)[0] or ""
|
||||
if target.is_file() and mime.startswith("image/"): source = f"data:{mime};base64,{base64.b64encode(target.read_bytes()).decode('ascii')}"
|
||||
return stash(f'<figure><img src="{html.escape(source, quote=True)}" alt="{html.escape(alt, quote=True)}" loading="lazy"><figcaption>{html.escape(alt)}</figcaption></figure>')
|
||||
def link(match):
|
||||
label = html.escape(match.group(1).strip()); target = Path(match.group(2).strip().replace("\\", "/")).name
|
||||
return stash(f'<a href="{html.escape(target, quote=True)}" target="_blank" rel="noopener">{label}</a>')
|
||||
value = re.sub(r"!\[([^\]]*)\]\(([^)]+)\)", image, value); value = re.sub(r"(?<!!)\[([^\]]+)\]\(([^)]+)\)", link, value)
|
||||
value = re.sub(r"`([^`]+)`", lambda m: stash(f"<code>{html.escape(m.group(1))}</code>"), value); value = html.escape(value); value = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", value)
|
||||
for index, content in enumerate(tokens): value = value.replace(f"@@GFAHTML{index}@@", content)
|
||||
return value
|
||||
|
||||
def markdown_to_html(markdown: str, report_path: Path, title: str) -> str:
|
||||
output = []; list_tag = ""; table_rows = []
|
||||
def close_list():
|
||||
nonlocal list_tag
|
||||
if list_tag: output.append(f"</{list_tag}>"); list_tag = ""
|
||||
def flush_table():
|
||||
nonlocal table_rows
|
||||
if not table_rows: return
|
||||
rows, table_rows = table_rows, []
|
||||
if len(rows) >= 2 and all(re.fullmatch(r":?-{3,}:?", cell.replace(" ", "")) for cell in rows[1]):
|
||||
output.append('<div class="table-wrap"><table><thead><tr>' + ''.join(f"<th>{_inline(c, report_path)}</th>" for c in rows[0]) + '</tr></thead><tbody>'); output.extend('<tr>' + ''.join(f"<td>{_inline(c, report_path)}</td>" for c in row) + '</tr>' for row in rows[2:]); output.append('</tbody></table></div>')
|
||||
else: output.extend(f"<p>{_inline(' | '.join(row), report_path)}</p>" for row in rows)
|
||||
for raw in markdown.splitlines():
|
||||
line = raw.strip()
|
||||
if line.startswith("|") and line.endswith("|"): close_list(); table_rows.append([c.strip() for c in line.strip("|").split("|")]); continue
|
||||
flush_table()
|
||||
if not line: close_list(); continue
|
||||
heading = re.match(r"^(#{1,6})\s+(.+)$", line)
|
||||
if heading: close_list(); level=len(heading.group(1)); output.append(f"<h{level}>{_inline(heading.group(2), report_path)}</h{level}>"); continue
|
||||
if line.startswith("> "): close_list(); output.append(f"<blockquote>{_inline(line[2:], report_path)}</blockquote>"); continue
|
||||
item = re.match(r"^([-*]|\d+\.)\s+(.+)$", line)
|
||||
if item:
|
||||
tag = "ol" if item.group(1)[0].isdigit() else "ul"
|
||||
if list_tag != tag: close_list(); list_tag=tag; output.append(f"<{tag}>")
|
||||
output.append(f"<li>{_inline(item.group(2), report_path)}</li>"); continue
|
||||
close_list(); rendered=_inline(line, report_path); output.append(rendered if line.startswith("![") else f"<p>{rendered}</p>")
|
||||
close_list(); flush_table(); body="\n".join(output); safe_title=html.escape(title)
|
||||
return f'''<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>{safe_title}</title><style>:root{{--ink:#17352c;--paper:#fffdf7;--line:#dce5dc;--accent:#157a57;--soft:#edf6ef;--warm:#f2e5c7}}*{{box-sizing:border-box}}html{{background:#e9efe9}}body{{margin:0;color:var(--ink);font-family:"Noto Serif SC","Source Han Serif SC","Microsoft YaHei",serif;line-height:1.85;background:radial-gradient(circle at top left,#f8edcf 0,transparent 28rem),linear-gradient(150deg,#edf4ec,#f6f2e8)}}main{{width:min(1120px,calc(100% - 32px));margin:32px auto 64px;padding:clamp(28px,5vw,72px);background:var(--paper);border:1px solid rgba(23,53,44,.12);border-radius:22px;box-shadow:0 24px 70px rgba(35,62,48,.14);overflow:hidden}}h1{{font-size:clamp(2rem,5vw,3.5rem);line-height:1.2;color:#0d4f39}}h2{{margin:3.2rem 0 1.1rem;padding-top:.7rem;border-top:3px solid var(--warm);font-size:clamp(1.45rem,3vw,2rem)}}h3{{color:var(--accent)}}p,h1,h2,h3,h4,a,figcaption,code{{overflow-wrap:anywhere;word-break:break-word}}blockquote{{margin:1.4rem 0;padding:1rem 1.25rem;border-left:5px solid var(--accent);background:var(--soft)}}code{{padding:.14em .4em;border-radius:5px;background:#edf0eb}}figure{{margin:1.6rem 0 2.2rem;padding:clamp(8px,2vw,18px);border:1px solid var(--line);border-radius:16px;background:#fff;box-shadow:0 10px 30px rgba(28,62,48,.09)}}img{{display:block;max-width:100%;height:auto;margin:auto;border-radius:10px}}figcaption{{margin-top:.7rem;text-align:center;color:#60726b}}a{{color:#087553}}.table-wrap{{overflow-x:auto;margin:1.4rem 0;border:1px solid var(--line);border-radius:12px}}table{{width:100%;border-collapse:collapse;background:#fff}}th,td{{padding:.72rem .85rem;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}}th{{background:var(--soft)}}@media(max-width:640px){{main{{width:100%;margin:0;padding:24px 18px 48px;border:0;border-radius:0}}}}@media print{{html,body{{background:#fff}}main{{width:100%;margin:0;padding:0;border:0;box-shadow:none}}figure{{break-inside:avoid}}}}</style></head><body><main>{body}</main></body></html>'''
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
- 任务:{{JOB_ID}}
|
||||
- 分析前缀:{{PREFIX}}
|
||||
- 生成模式:{{GENERATION_MODE}}
|
||||
- 生成时间:{{GENERATED_AT}}
|
||||
|
||||
{{FALLBACK_NOTICE}}
|
||||
|
||||
## 一、报告首页
|
||||
|
||||
@@ -51,91 +49,91 @@
|
||||
报告按计划逐阶段选择成功 Manifest;当各阶段使用的成员集合不同,正文明确区分目标成员、目标加参考成员和全基因组背景。
|
||||
|
||||
<!-- GFA_SECTION stage.identify -->
|
||||
## 四、基因家族鉴定(Identify) (`identify`)
|
||||
## 四、基因家族鉴定(Identify)
|
||||
|
||||
### 4.3 最终成员数量与命名
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.phylogeny -->
|
||||
## 五、亲缘关系分析(Phylogeny) (`phylogeny`)
|
||||
## 五、亲缘关系分析(Phylogeny)
|
||||
|
||||
### 5.5 亲缘关系阶段小结
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.reference_classification -->
|
||||
## 六、参考物种辅助的小组分类(Reference Classification) (`reference_classification`)
|
||||
## 六、参考物种辅助的小组分类(Reference Classification)
|
||||
|
||||
### 6.2 分类可信度表述
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.motif -->
|
||||
## 七、短保守片段分析(Motif) (`motif`)
|
||||
## 七、短保守片段分析(Motif)
|
||||
|
||||
### 7.2 Motif 数量与出现频率
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.domain -->
|
||||
## 八、主要功能区域分析(Domain) (`domain`)
|
||||
## 八、主要功能区域分析(Domain)
|
||||
|
||||
### 8.1 Domain 整体统计
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.structure -->
|
||||
## 九、基因内部结构分析(Structure) (`structure`)
|
||||
## 九、基因内部结构分析(Structure)
|
||||
|
||||
### 9.1 结构覆盖与异常结构
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.combo -->
|
||||
## 十、综合组合图(Combo) (`combo`)
|
||||
## 十、综合组合图(Combo)
|
||||
|
||||
### 10.1 树 + Motif + Domain + Structure 组合图
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.promoter -->
|
||||
## 十一、启动子元件分析(Promoter) (`promoter`)
|
||||
## 十一、启动子元件分析(Promoter)
|
||||
|
||||
### 11.1 元件总量、类别与高级统计
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.tfbs -->
|
||||
## 十二、转录因子结合位点预测(TFBS) (`tfbs`)
|
||||
## 十二、转录因子结合位点预测(TFBS)
|
||||
|
||||
### 12.1 高频结合片段与统计边界
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.synteny -->
|
||||
## 十三、种内共线性分析(Synteny) (`synteny`)
|
||||
## 十三、种内共线性分析(Synteny)
|
||||
|
||||
### 13.1 全基因组共线性背景与家族证据缺口
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.karyotype -->
|
||||
## 十四、染色体分布(Karyotype) (`karyotype`)
|
||||
## 十四、染色体分布(Karyotype)
|
||||
|
||||
### 14.1 染色体位置概述
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.interspecies_synteny -->
|
||||
## 十五、种间共线性与跨物种关系(Interspecies Synteny) (`interspecies_synteny`)
|
||||
## 十五、种间共线性与跨物种关系(Interspecies Synteny)
|
||||
|
||||
### 15.6 Interspecies Synteny 阶段小结
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION stage.pep -->
|
||||
## 十六、蛋白基本特征分析(PEP) (`pep`)
|
||||
## 十六、蛋白基本特征分析(PEP)
|
||||
|
||||
### 16.1 蛋白理化性质分布图
|
||||
|
||||
@@ -151,20 +149,3 @@
|
||||
### 17.5 综合结论
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION common.limitations -->
|
||||
## 十八、主要限制
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION common.recommendations -->
|
||||
## 十九、后续验证建议
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
<!-- GFA_SECTION common.checklist -->
|
||||
## 二十、报告交付前检查清单
|
||||
|
||||
### 20.1 可追溯性与语言检查
|
||||
|
||||
{{SECTION_CONTENT}}
|
||||
|
||||
Reference in New Issue
Block a user