Skip to content

Commit 8657216

Browse files
SonAIengineclaude
andcommitted
feat: QA 스위트 — Wikipedia 169건 + GitHub 368건 실제 데이터로 검증
실제 데이터: - Wikipedia 한국어 기술 문서 169건 (16개 카테고리: 프로그래밍, DB, AI, 보안 등) - GitHub hive-corp 커밋 168건 (한/영 혼합) - GitHub vscode 이슈 200건 (영어, 라벨 포함) QA 테스트 (24건): - 검색 품질: 8개 쿼리별 relevance 검증, precision@5, resonance 정렬 - Hebbian 학습: reinforce 후 순위 상승, 실패 후 resonance 하락, co-activation edge 생성 - Consolidation: 3회 접근 후 L0→L1 승격 검증 - 성능: P95 < 100ms, batch 50+ nodes/sec, 캐시 효과 - 크로스소스: Wikipedia + GitHub 혼합 검색 버그 수정: - graph.get() cache hit 시 access_count 미갱신 → consolidation 승격 실패 → 캐시 히트에도 backend.update_node() 호출하도록 수정 Total: 145 unit+QA + 13 integration = 158 tests passed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 329b5e8 commit 8657216

11 files changed

Lines changed: 4549 additions & 1 deletion

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ packages = ["src/synaptic"]
4040
[tool.pytest.ini_options]
4141
asyncio_mode = "auto"
4242
testpaths = ["tests"]
43+
markers = [
44+
"integration: PostgreSQL integration tests (require running PG server)",
45+
"qa: Quality assurance tests with real external data",
46+
]
4347

4448
[tool.ruff]
4549
target-version = "py312"

src/synaptic/graph.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ async def search(
9999
async def get(self, node_id: str) -> Node | None:
100100
cached = self._cache.get(node_id)
101101
if cached is not None:
102+
# Still track access in backend for consolidation
103+
cached.access_count += 1
104+
cached.updated_at = time()
105+
await self._backend.update_node(cached)
102106
return cached
103107
node = await self._store.get_node(node_id)
104108
if node is not None:

tests/qa/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""QA tests — real data validation for search quality, learning, and performance."""

tests/qa/conftest.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""QA test fixtures — real data ingestion + graph setup."""
2+
3+
from __future__ import annotations
4+
5+
import json
6+
from collections.abc import AsyncGenerator
7+
from pathlib import Path
8+
9+
import pytest
10+
11+
from synaptic.backends.memory import MemoryBackend
12+
from synaptic.extensions.tagger_regex import RegexTagExtractor
13+
from synaptic.graph import SynapticGraph
14+
from synaptic.models import NodeKind
15+
16+
DATA_DIR = Path(__file__).parent / "data"
17+
18+
19+
def _load_json(name: str) -> list[dict[str, object]]:
20+
path = DATA_DIR / name
21+
if not path.exists():
22+
pytest.skip(f"Data file not found: {path}")
23+
with open(path) as f:
24+
data = json.load(f)
25+
if not data:
26+
pytest.skip(f"Data file is empty: {path}")
27+
return data # type: ignore[no-any-return]
28+
29+
30+
@pytest.fixture
31+
async def wiki_graph() -> AsyncGenerator[SynapticGraph]:
32+
"""Graph populated with Korean Wikipedia tech articles."""
33+
articles = _load_json("wikipedia_ko_tech.json")
34+
35+
backend = MemoryBackend()
36+
await backend.connect()
37+
tagger = RegexTagExtractor()
38+
graph = SynapticGraph(backend, tag_extractor=tagger)
39+
40+
for article in articles:
41+
title = str(article.get("title", ""))
42+
content = str(article.get("content", ""))
43+
if not title or not content:
44+
continue
45+
# Truncate very long articles for performance
46+
if len(content) > 2000:
47+
content = content[:2000]
48+
cats = article.get("categories", [])
49+
tags = list(cats) if isinstance(cats, list) else []
50+
await graph.add(
51+
title=title,
52+
content=content,
53+
kind=NodeKind.CONCEPT,
54+
tags=[str(t) for t in tags],
55+
source="wikipedia:ko",
56+
)
57+
58+
yield graph
59+
await backend.close()
60+
61+
62+
@pytest.fixture
63+
async def github_graph() -> AsyncGenerator[SynapticGraph]:
64+
"""Graph populated with GitHub commits + issues."""
65+
backend = MemoryBackend()
66+
await backend.connect()
67+
tagger = RegexTagExtractor()
68+
graph = SynapticGraph(backend, tag_extractor=tagger)
69+
70+
# Ingest commits
71+
commits = _load_json("github_commits.json")
72+
for commit in commits:
73+
msg = str(commit.get("message", ""))
74+
if not msg or len(msg) < 10:
75+
continue
76+
first_line = msg.split("\n", maxsplit=1)[0]
77+
await graph.add(
78+
title=first_line[:100],
79+
content=msg,
80+
kind=NodeKind.ARTIFACT,
81+
tags=["commit"],
82+
source="github:commit",
83+
)
84+
85+
# Ingest issues
86+
try:
87+
issues = _load_json("github_issues.json")
88+
except Exception:
89+
issues = []
90+
91+
for issue in issues:
92+
title = str(issue.get("title", ""))
93+
body = str(issue.get("body", "") or "")
94+
if not title:
95+
continue
96+
labels = issue.get("labels", [])
97+
tag_list = ["issue"]
98+
if isinstance(labels, list):
99+
tag_list.extend(str(lb) for lb in labels[:5])
100+
content = body[:1500] if body else title
101+
await graph.add(
102+
title=title[:100],
103+
content=content,
104+
kind=NodeKind.ENTITY,
105+
tags=tag_list,
106+
source="github:issue",
107+
)
108+
109+
yield graph
110+
await backend.close()
111+
112+
113+
@pytest.fixture
114+
async def combined_graph() -> AsyncGenerator[SynapticGraph]:
115+
"""Graph with both Wikipedia + GitHub data combined."""
116+
backend = MemoryBackend()
117+
await backend.connect()
118+
tagger = RegexTagExtractor()
119+
graph = SynapticGraph(backend, tag_extractor=tagger)
120+
121+
# Wikipedia
122+
try:
123+
articles = _load_json("wikipedia_ko_tech.json")
124+
for article in articles[:50]: # Limit for performance
125+
title = str(article.get("title", ""))
126+
content = str(article.get("content", ""))[:1500]
127+
if title and content:
128+
await graph.add(
129+
title=title,
130+
content=content,
131+
kind=NodeKind.CONCEPT,
132+
source="wikipedia:ko",
133+
)
134+
except Exception: # noqa: S110
135+
pass
136+
137+
# GitHub commits
138+
try:
139+
commits = _load_json("github_commits.json")
140+
for commit in commits[:50]:
141+
msg = str(commit.get("message", ""))
142+
if msg and len(msg) >= 10:
143+
await graph.add(
144+
title=msg.split("\n", maxsplit=1)[0][:100],
145+
content=msg,
146+
kind=NodeKind.ARTIFACT,
147+
source="github:commit",
148+
)
149+
except Exception: # noqa: S110
150+
pass
151+
152+
# GitHub issues
153+
try:
154+
issues = _load_json("github_issues.json")
155+
for issue in issues[:50]:
156+
title = str(issue.get("title", ""))
157+
body = str(issue.get("body", "") or "")[:1500]
158+
if title:
159+
await graph.add(
160+
title=title[:100],
161+
content=body or title,
162+
kind=NodeKind.ENTITY,
163+
source="github:issue",
164+
)
165+
except Exception: # noqa: S110
166+
pass
167+
168+
yield graph
169+
await backend.close()

0 commit comments

Comments
 (0)