Skip to content

Commit 720d6ae

Browse files
committed
fix: tighten graph concept linking
1 parent 2959945 commit 720d6ae

3 files changed

Lines changed: 133 additions & 65 deletions

File tree

backend/main.py

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2478,13 +2478,119 @@ def concept_breadth(limit: int = Query(50, ge=1, le=200)):
24782478
con.close()
24792479

24802480

2481+
def _is_high_signal_graph_chunk(text: str) -> bool:
2482+
"""Suppress glossary/index-style chunks when mining related graph concepts."""
2483+
compact = re.sub(r"\s+", " ", text or "").strip()
2484+
if len(compact) < 40:
2485+
return False
2486+
2487+
title_count = compact.count("《")
2488+
latin_token_count = len(re.findall(r"[A-Za-z]{3,}", compact))
2489+
number_count = len(re.findall(r"\b\d+\b", compact))
2490+
2491+
if title_count >= 3 and (latin_token_count >= 6 or number_count >= 6):
2492+
return False
2493+
2494+
if re.search(r"[。!?;]", compact):
2495+
return True
2496+
2497+
return bool(re.search(r"[,、::;;]", compact)) and len(compact) >= 80 and title_count < 3
2498+
2499+
2500+
GRAPH_GENERIC_TERMS = {
2501+
"描写", "引用", "命题", "修辞", "叙事", "抒情",
2502+
"阅读", "思考", "写作",
2503+
}
2504+
2505+
2506+
def _fetch_graph_local_related(con, center_term: str, center_subjects: set[str], limit: int = 15) -> list[dict]:
2507+
"""Mine related graph concepts from the center term's own high-signal chunks."""
2508+
try:
2509+
chunk_rows = con.execute("""
2510+
SELECT c.subject, c.text
2511+
FROM chunks c JOIN chunks_fts ON chunks_fts.rowid = c.id
2512+
WHERE chunks_fts MATCH ? AND c.source = 'mineru'
2513+
LIMIT 80
2514+
""", (center_term,)).fetchall()
2515+
except Exception:
2516+
return []
2517+
2518+
signal_chunks = [row for row in chunk_rows if _is_high_signal_graph_chunk(row["text"] or "")]
2519+
if not signal_chunks:
2520+
signal_chunks = chunk_rows
2521+
if not signal_chunks:
2522+
return []
2523+
2524+
curated_rows = con.execute("SELECT term, subject_count, total_count FROM curated_keywords").fetchall()
2525+
concept_rows = con.execute("SELECT concept, subject FROM concept_map").fetchall()
2526+
2527+
concept_subjects: dict[str, set[str]] = {}
2528+
for row in concept_rows:
2529+
concept_subjects.setdefault(row["concept"], set()).add(row["subject"])
2530+
2531+
candidates = []
2532+
for row in curated_rows:
2533+
term = row["term"]
2534+
if term == center_term:
2535+
continue
2536+
if term in GRAPH_GENERIC_TERMS:
2537+
continue
2538+
2539+
term_subjects = concept_subjects.get(term, set())
2540+
overlap = center_subjects & term_subjects
2541+
if len(overlap) < 2:
2542+
continue
2543+
2544+
local_hits = 0
2545+
local_subjects = set()
2546+
for chunk in signal_chunks:
2547+
chunk_text = chunk["text"] or ""
2548+
if term in chunk_text:
2549+
local_hits += 1
2550+
local_subjects.add(chunk["subject"])
2551+
2552+
if local_hits == 0:
2553+
continue
2554+
2555+
subject_count = int(row["subject_count"] or len(term_subjects))
2556+
total_count = int(row["total_count"] or 0)
2557+
2558+
if local_hits < 2 and total_count > 20:
2559+
continue
2560+
2561+
score = local_hits * 10 + len(local_subjects) * 4 + len(overlap) - subject_count
2562+
candidates.append({
2563+
"term": term,
2564+
"shared_subjects": sorted(overlap),
2565+
"overlap": len(overlap),
2566+
"source": "local_chunks",
2567+
"local_hits": local_hits,
2568+
"local_subjects": sorted(local_subjects),
2569+
"subject_count": subject_count,
2570+
"total_count": total_count,
2571+
"score": score,
2572+
})
2573+
2574+
candidates.sort(
2575+
key=lambda item: (
2576+
item["score"],
2577+
item["local_hits"],
2578+
len(item["local_subjects"]),
2579+
item["overlap"],
2580+
-item["subject_count"],
2581+
-item["total_count"],
2582+
),
2583+
reverse=True,
2584+
)
2585+
return candidates[:limit]
2586+
2587+
24812588
@app.get("/api/graph/search")
24822589
def graph_search(q: str = Query(..., min_length=1)):
24832590
"""Return a concept subgraph centered on the search term."""
24842591
con = get_db()
24852592
try:
24862593
q_clean = q.strip()
2487-
curated = {r["term"] for r in con.execute("SELECT term FROM curated_keywords").fetchall()}
24882594

24892595
# Use FTS for precise subject distribution (not LIKE)
24902596
try:
@@ -2525,26 +2631,13 @@ def graph_search(q: str = Query(..., min_length=1)):
25252631
except Exception:
25262632
pass
25272633

2528-
# ── Priority 2: curated concepts with overlap >= 2 ──────────
2634+
# ── Priority 2: local co-mentions in high-signal center chunks ─────
25292635
curated_related = []
25302636
seen_terms = {r["term"] for r in cluster_related}
2531-
for term in curated:
2532-
if term == q_clean or term in seen_terms:
2637+
for item in _fetch_graph_local_related(con, q_clean, center_subjects, limit=20):
2638+
if item["term"] == q_clean or item["term"] in seen_terms:
25332639
continue
2534-
term_subjects_row = con.execute(
2535-
"SELECT DISTINCT subject FROM concept_map WHERE concept = ?", (term,)
2536-
).fetchall()
2537-
term_subjects = {r["subject"] for r in term_subjects_row}
2538-
overlap = center_subjects & term_subjects
2539-
if len(overlap) >= 2: # stricter threshold
2540-
curated_related.append({
2541-
"term": term,
2542-
"shared_subjects": list(overlap),
2543-
"overlap": len(overlap),
2544-
"source": "curated",
2545-
})
2546-
2547-
curated_related.sort(key=lambda x: x["overlap"], reverse=True)
2640+
curated_related.append(item)
25482641

25492642
# Merge: clusters first, then curated (max 15 total)
25502643
related = cluster_related + curated_related[:15 - len(cluster_related)]
@@ -2575,6 +2668,8 @@ def graph_search(q: str = Query(..., min_length=1)):
25752668
"subjects": r["shared_subjects"],
25762669
"strength": r["overlap"],
25772670
}
2671+
if r.get("local_hits"):
2672+
link_data["evidence_hits"] = r["local_hits"]
25782673
ai_rel = get_ai_relation(con, q_clean, r["term"])
25792674
if ai_rel:
25802675
link_data["relation"] = ai_rel["type"]

frontend/assets/version.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
{
2-
"frontend_refactor_version": "2026.03.06-r8",
2+
"frontend_refactor_version": "2026.03.06-r9",
33
"updated_at": "2026-03-06",
44
"history": [
5+
{
6+
"version": "2026.03.06-r9",
7+
"date": "2026-03-06",
8+
"summary": "tightened graph search around local high-signal chunk evidence and simplified the About page to open-source feedback only"
9+
},
510
{
611
"version": "2026.03.06-r8",
712
"date": "2026-03-06",

frontend/index.html

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<script defer src="https://unpkg.com/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
2424
<script src="https://d3js.org/d3.v7.min.js"></script>
2525

26-
<link rel="stylesheet" href="assets/style.css?v=20260306g">
26+
<link rel="stylesheet" href="assets/style.css?v=20260306h">
2727
</head>
2828

2929
<body>
@@ -265,50 +265,18 @@ <h2>🔗 知识图谱</h2>
265265
<!-- About View -->
266266
<main id="view-about" class="view">
267267
<div class="about-content">
268-
<div class="about-intro">
269-
<h2>关于本平台</h2>
270-
<p class="about-lead">把教材检索、真题关联、知识图谱和 AI 对话接到同一套可回溯证据底座上,让跨学科学习不再只靠记忆跳跃。</p>
271-
</div>
272-
<div class="about-grid">
273-
<div class="about-card about-card-wide">
274-
<h3>🎯 它在解决什么问题?</h3>
275-
<p>同一个概念会在 9 个学科里被拆开讲。学生知道它们都重要,却很难判断这些内容是不是在谈同一件事、哪些知识点可以互相迁移。</p>
276-
<p>这个平台把教材段落、高考真题、概念图谱和 AI 对话放到同一个检索底座里。每次搜索和追问都尽量回到具体教材页、题目与证据,而不是只给一段悬空答案。</p>
277-
<p>目标不是替代教材,而是把原本分散的知识重新接成一张网,帮助学生更快建立整体知识观。</p>
278-
</div>
279-
280-
<div class="about-card">
281-
<h3>📚 当前线上底座</h3>
282-
<div id="about-metrics" class="about-metrics">
283-
<span class="about-metric-chip">数据加载中…</span>
284-
</div>
285-
<p id="about-scale-text"><strong>教材规模加载中…</strong></p>
286-
<p id="about-corpus-text">结构化语料规模加载中…</p>
287-
<p id="about-ai-text">AI 与检索底座状态加载中…</p>
288-
<p id="about-precompute-text">AI 预计算数据加载中…</p>
289-
</div>
290-
291-
<div class="about-card">
292-
<h3>🧱 运行与发布</h3>
293-
<p id="about-runtime-text">运行态说明加载中…</p>
294-
<p id="about-deploy-text">发布链路加载中…</p>
295-
<p class="about-note">生产运行不依赖 GPU;OCR、FAISS 重建和大批量补数在离线机器完成,线上只负责检索与对话服务。</p>
296-
</div>
297-
298-
<div class="about-card">
299-
<h3>🔓 开源与反馈</h3>
300-
<p>代码、部署链和数据处理流程都保持可追溯;图片资源走 Cloudflare R2,AI 入口统一走 Worker 自定义域名 <code>ai.bdfz.net</code></p>
301-
<p><a href="https://github.com/ieduer/cross-subject-knowledge" target="_blank">🐙 GitHub — ieduer/cross-subject-knowledge</a></p>
302-
<p>
303-
<a class="about-feedback-btn"
304-
href="https://github.com/ieduer/cross-subject-knowledge/issues/new?title=%5BFeedback%5D%20sun.bdfz.net%20&body=%23%23%20%E9%97%AE%E9%A2%98%E6%88%96%E5%BB%BA%E8%AE%AE%0A%0A%23%23%20%E6%A3%80%E7%B4%A2%E8%AF%8D%0A%0A%23%23%20%E5%AF%B9%E5%BA%94%E9%A1%B5%E9%9D%A2%2F%E4%B9%A6%E7%B1%8D%0A%0A%23%23%20%E5%A4%8D%E7%8E%B0%E6%AD%A5%E9%AA%A4"
305-
target="_blank" rel="noopener noreferrer">🛠️ 反馈问题 / 提交建议</a>
306-
</p>
307-
<div class="about-signoff">
308-
<span>作者</span>
309-
<strong>孙玉磊 · 北大附中</strong>
310-
<a href="https://bdfz.net/posts/sun/" target="_blank">🏫 bdfz.net/posts/sun/</a>
311-
</div>
268+
<div class="about-card">
269+
<h3>🔓 开源与反馈</h3>
270+
<p><a href="https://github.com/ieduer/cross-subject-knowledge" target="_blank" rel="noopener noreferrer">🐙 GitHub — ieduer/cross-subject-knowledge</a></p>
271+
<p>
272+
<a class="about-feedback-btn"
273+
href="https://github.com/ieduer/cross-subject-knowledge/issues/new?title=%5BFeedback%5D%20sun.bdfz.net%20&body=%23%23%20%E9%97%AE%E9%A2%98%E6%88%96%E5%BB%BA%E8%AE%AE%0A%0A%23%23%20%E6%A3%80%E7%B4%A2%E8%AF%8D%0A%0A%23%23%20%E5%AF%B9%E5%BA%94%E9%A1%B5%E9%9D%A2%2F%E4%B9%A6%E7%B1%8D%0A%0A%23%23%20%E5%A4%8D%E7%8E%B0%E6%AD%A5%E9%AA%A4"
274+
target="_blank" rel="noopener noreferrer">🛠️ 反馈问题 / 提交建议</a>
275+
</p>
276+
<div class="about-signoff">
277+
<span>作者</span>
278+
<strong>孙玉磊 · 北大附中</strong>
279+
<a href="https://bdfz.net/posts/sun/" target="_blank" rel="noopener noreferrer">🏫 bdfz.net/posts/sun/</a>
312280
</div>
313281
</div>
314282
</div>
@@ -318,7 +286,7 @@ <h3>🔓 开源与反馈</h3>
318286
<p id="footer-version-line">AI 高中教材 · 开源项目 · MIT License · 前端版本加载中…</p>
319287
</footer>
320288
</div>
321-
<script src="assets/app.js?v=20260306g"></script>
289+
<script src="assets/app.js?v=20260306h"></script>
322290
</body>
323291

324292
</html>

0 commit comments

Comments
 (0)