构造 wiki 链接图谱 JSON。 返回 {nodes: [...], edges: [...]} : - 节点字段 path / title / type / status / tags / inbound_count / outbound_count - 边字段 from / to / link_type / anchor? link_type 取值:白名单关系(SUPPORTS / REFUTES / ...)或 'REFERENCES'(默认)。 self-link 排除(与 build_link_graph / getBack
(
pages,
backlinks,
*,
filter_type: str | None = None,
filter_tag: str | None = None,
include_archive: bool = False,
)
| 598 | |
| 599 | |
| 600 | def build_graph_data( |
| 601 | pages, |
| 602 | backlinks, |
| 603 | *, |
| 604 | filter_type: str | None = None, |
| 605 | filter_tag: str | None = None, |
| 606 | include_archive: bool = False, |
| 607 | ) -> dict: |
| 608 | """构造 wiki 链接图谱 JSON。 |
| 609 | |
| 610 | 返回 {nodes: [...], edges: [...]} : |
| 611 | - 节点字段 path / title / type / status / tags / inbound_count / outbound_count |
| 612 | - 边字段 from / to / link_type / anchor? |
| 613 | |
| 614 | link_type 取值:白名单关系(SUPPORTS / REFUTES / ...)或 'REFERENCES'(默认)。 |
| 615 | self-link 排除(与 build_link_graph / getBacklinks 一致)。 |
| 616 | 归档区 (wiki/_archive_*) 默认排除——历史快照不应主导图谱视觉。 |
| 617 | """ |
| 618 | # ---- 过滤节点集 ---- |
| 619 | def page_visible(p: Page) -> bool: |
| 620 | if not include_archive and "_archive" in p.path: |
| 621 | return False |
| 622 | if filter_type and p.type != filter_type: |
| 623 | return False |
| 624 | if filter_tag and filter_tag not in p.tags: |
| 625 | return False |
| 626 | return True |
| 627 | |
| 628 | visible_pages = [p for p in pages if page_visible(p)] |
| 629 | visible_paths = {p.path for p in visible_pages} |
| 630 | # 基名 → 全路径(用于无前缀的 [[basename]] 链接解析) |
| 631 | visible_basenames = {Path(p.path).stem: p.path for p in visible_pages} |
| 632 | |
| 633 | # 入度计数(基于过滤后子图)——用 backlinks 全量再交集 |
| 634 | inbound_count: dict[str, int] = {} |
| 635 | outbound_count: dict[str, int] = {} |
| 636 | |
| 637 | edges: list[dict] = [] |
| 638 | seen_edges: set[tuple[str, str, str, str]] = set() # (from, to, link_type, anchor) |
| 639 | |
| 640 | for page in visible_pages: |
| 641 | for link in parse_wikilinks(page.raw_content): |
| 642 | target_norm = normalize_link_target(link.target) |
| 643 | # 解析 target:先看完整路径,再 fallback basename |
| 644 | if target_norm in visible_paths: |
| 645 | resolved = target_norm |
| 646 | else: |
| 647 | stem = Path(target_norm).stem |
| 648 | resolved = visible_basenames.get(stem) |
| 649 | if resolved is None: |
| 650 | continue |
| 651 | if resolved == page.path: |
| 652 | continue # self-link 排除 |
| 653 | link_type = link.relation or "REFERENCES" |
| 654 | anchor = link.anchor or "" |
| 655 | dedup_key = (page.path, resolved, link_type, anchor) |
| 656 | if dedup_key in seen_edges: |
| 657 | continue |
no test coverage detected