설계 문서 · 구현 진행 중
eDiscovery 커뮤니케이션 관계망을 온톨로지 기반 지식그래프로 재구성하고, 실무자가 탐색할 수 있게 만드는 도구.
브레인스토밍에서 결정된 전제. 뒤집으려면 아래 설계도 함께 바뀝니다.
| 항목 | 결정 |
|---|---|
| 목적 | 실무자가 실제로 쓰는 제품. 논문용 벤치마크가 아님 |
| 공략 지점 | 인물·조직 관계망 분석 — 엔티티 해소와 커뮤니케이션 네트워크 |
| 입력 데이터 | Enron 공개 코퍼스로 시작. 실데이터 연동은 이후 |
| 핵심 동작 | 그래프 시각적 탐색 |
| 온톨로지 수준 | 하이브리드 — RDF/OWL로 정의, property graph로 투영 |
| v1 범위 | 수직 슬라이스. 커스토디언 상위 N명을 끝까지 관통 — N은 아래 실측을 보고 확정 중 |
설계 단계의 추정치를 실제 데이터로 대체한 결과. 하나는 크게 틀렸습니다.
| 항목 | 값 |
|---|---|
| 배포본 | enron_mail_20150507.tar.gz · 443,254,787 bytes |
| 커스토디언 | 150명 |
| 전체 파일 | 517,401 |
| 상위 15명 합계 | 240,577 (전체의 46.5%) |
스펙의 v1 규모 추정이 5배 틀렸습니다.
설계 문서에 "커스토디언 15~20명 ≈ 5만 통"이라 적었지만, 실제 상위 15명은
24만 파일입니다. 검증 없이 넣은 숫자였습니다.
상위 5명이 약 12.5만, 상위 3명이 약 8.2만, 상위 2명이 약 5.7만입니다.
N은 15로 확정했습니다 — 추론기가 상한을 정하는 구조였는데,
owlrl 을 버리면서 그 제약이 사라졌습니다 (아래 06 의 P2).
| # | 커스토디언 | 파일 |
|---|---|---|
| 1 | kaminski-v | 28,465 |
| 2 | dasovich-j | 28,234 |
| 3 | kean-s | 25,351 |
| 4 | mann-k | 23,381 |
| 5 | jones-t | 19,950 |
| 6 | shackleton-s | 18,687 |
| 7 | taylor-m | 13,875 |
| 8 | farmer-d | 13,032 |
| 9 | germany-c | 12,436 |
| 10 | beck-s | 11,830 |
| 11 | symes-k | 10,827 |
| 12 | nemec-g | 10,655 |
| 13 | scott-s | 8,022 |
| 14 | rogers-b | 8,009 |
| 15 | bass-e | 7,823 |
상위권이 임원이 아니라 리서치·대외협력·법무 담당이라는 점은 짚어둘 만합니다. 메일 수가 많은 사서함과 사건의 중심 인물은 다릅니다. 관계망 탐색 데모로는 문제없지만, "핵심 인물 자동 랭킹" 같은 기능을 나중에 붙일 때는 통신량만으로 중요도를 매길 수 없다는 근거가 됩니다.
7단계 독립 모듈. 각 단계가 결과를 디스크에 남기므로 한 단계를 고쳐도 앞 단계를 다시 돌리지 않습니다.
Enron maildir
│
├─[1] ingest 파싱 → 정규화된 Message 레코드 → SQLite
├─[2] extract 주소·표시이름·서명에서 Actor 후보 추출 → SQLite
├─[3] resolve Actor 후보 → Person 클러스터 → SQLite
├─[4] ontology RDF 트리플 생성 + OWL 추론 (owlrl) → graph.ttl
├─[5] project RDF → property graph 투영 + 지표 계산 → graph.pkl
├─[6] serve FastAPI: ego-network 서브그래프 JSON
└─[7] ui Cytoscape.js 렌더링, 클릭 시 확장
하이브리드가 실현되는 지점은 [4]→[5]입니다. RDF/OWL이 온톨로지의 단일 진실 원천이고 추론도 여기서 일어납니다. property graph는 그 결과를 탐색용으로 투영한 것입니다. 단방향이라 두 모델을 양방향 동기화하는 문제가 생기지 않습니다.
v1 규모(약 2만 노드 / 20만 엣지)는 networkx 인메모리로 충분합니다.
Neo4j를 넣는 순간 Docker·드라이버·Cypher·RDF↔Neo4j 동기화가 따라옵니다.
성능이 실제로 아플 때 [5]와 [6]만 교체하면 되도록 경계를 그어둡니다.
edisco-graph/
├── ontology/ediscovery.ttl # OWL 스키마 — 사람이 읽고 고치는 파일
├── src/edisco/
│ ├── ingest.py extract.py resolve.py
│ └── ontology.py project.py api.py
├── web/index.html # Cytoscape.js 단일 페이지
├── design/index.html # 이 문서
├── data/ # gitignore — 원본·중간산출물
└── tests/
프론트엔드는 React 없이 단일 HTML + Cytoscape.js. 빌드 도구도 없습니다. 필요해지면 그때 올립니다.
"온톨로지 썼습니다"가 장식이 되지 않으려면, 추론기가 없으면 직접 짜야 했을 코드를 대체해야 합니다.
① 별칭 클러스터링 — 핵심, 그리고 측정이 뒤집은 곳
:sameActorAs a owl:SymmetricProperty, owl:TransitiveProperty .
엔티티 해소는 쌍 단위 판단만 내놓습니다.
bsmith@enron.com ↔ bob.smith@enron.com,
bob.smith@enron.com ↔ robert.smith@ect.enron.com.
이걸 사람 단위 클러스터로 묶는 일을 원래는 이 두 줄 선언으로 추론기에
맡길 계획이었습니다. 실측 결과 그 계획은 폐기했습니다 —
아래 06 섹션의 P2 를 보십시오.
폐포는 ontology.person_clusters 가 union-find 로 직접 계산하고,
결과는 쌍별 완전 그래프가 아니라 Person → resolvesTo → Actor
트리플로 저장합니다. 선언 두 줄은 온톨로지에 남깁니다 —
그게 sameActorAs 의 의미를 규정하는 명세이고, 추론기를 가진 외부
소비자가 우리 결과를 검증할 근거이기 때문입니다. 우리는 그 의미를 직접 구현합니다.
② 하위 속성으로 질의 단순화
:sentTo rdfs:subPropertyOf :addressed .
:ccTo rdfs:subPropertyOf :addressed .
:bccTo rdfs:subPropertyOf :addressed .
"A가 B에게 보낸 모든 것"을 물을 때 세 속성을 OR로 묶지 않습니다. :replyTo가 추가돼도 질의는 그대로입니다.
③ 하위 클래스로 역할 계층 — :Executive ⊑ :Employee.
"임원과 소통한 사람"에 CEO·CFO·VP가 자동 포함됩니다.
:Counsel은 나중에 특권 문서 탐지로 확장할 때 재사용됩니다.
④ domain/range로 오류 검출 — :sentMessage의 domain이 :Person인데
:Organization이 들어오면 파이프라인 버그입니다. 일관성 검사가 무료 테스트가 됩니다.
| 클래스 | 의미 |
|---|---|
:Actor | 원본에서 관찰된 신원 표현 — 이메일 주소 1개 + 표시이름들 |
:Person | 해소된 실제 사람. 여러 Actor가 하나로 |
:Organization | 조직·부서 |
:Message | 개별 이메일 |
:Thread | In-Reply-To/References로 묶인 대화 |
:Role | Executive / Trader / Counsel 등 |
Actor와 Person을 분리하는 게 중요합니다. 원본에 있던 것(관찰)과
우리가 판단한 것(추론)을 섞으면, 나중에 "왜 이 둘을 같은 사람으로 봤나"에 답할 수 없습니다.
커스토디언 15명이면 actor 가 만 단위로 나옵니다. 모든 쌍을 비교하면 억 단위이고 쌍마다 SQL 을 때리므로 파이프라인이 끝나지 않습니다. 같은 블록에 든 쌍만 비교합니다.
블록 키는 표시이름의 성, 로컬파트 토큰, 그리고 로컬파트에서 첫 글자를 뗀
형태입니다. 마지막 것이 핵심입니다 — jskilling 이
skilling 블록에 들어가서 표시이름 "Skilling, Jeff" 를
가진 jeff.skilling 과 만납니다. 60명을 넘는 블록은 버립니다
(enron 같은 토큰이 만드는 블록은 아무것도 구별해주지 않습니다).
블로킹은 재현율을 깎습니다. 버려진 블록 말고는 아무 키도 공유하지
않는 참 쌍은 후보에 아예 들어오지 못하고, 그러면 점수화 규칙을 어떻게 고쳐도
잡히지 않습니다. 그래서 평가가 재현율을 두 조각으로 나눠 보고합니다 —
blocked_out(블로킹이 후보로 만들지도 않은 쌍)과 나머지(점수화가
놓친 쌍)입니다. 섞어놓으면 어디를 고쳐야 하는지 알 수 없습니다.
@ect.enron.com → @enron.com), 표시이름을 성, 이름으로bsmith ↔ Bob Smith),
그리고 공통 연락처 중복도 — 같은 사람들과 소통하면 동일인 확률이 오릅니다.
그래프 구조를 해소에 되먹이는 부분이고, 순수 문자열 매칭 대비 개선을 보여줄 지점입니다:sameActorAs 생성 → union-find 가 Person 클러스터로 닫음.
클러스터가 상한(MAX_CLUSTER_SIZE)을 넘으면 자동 수용하지 않고
과병합 의심으로 따로 보고합니다 — 이행 폐포는 잘못된 쌍 하나로 두 클러스터를
이어붙이므로, 상한이 없으면 "이 500개 계정이 다 같은 사람"이라는 주장을
조용히 받아들이게 됩니다:merge_0142 a prov:Activity ;
prov:used :actor_bsmith, :actor_robert_smith ;
:rule "localpart_name_match" ;
:confidence 0.87 .
실무자가 "이 두 계정을 왜 동일인으로 묶었냐"고 물으면 답할 수 있어야 합니다. 답 못 하는 자동화는 법률 영역에서 안 쓰입니다. 그리고 이 기록이 있으면 신뢰도 낮은 병합만 골라 사람이 검토하는 화면을 만들 수 있습니다.
수작업 라벨링한 골드셋 250쌍으로 precision/recall을 측정합니다.
표본은 세 갈래입니다 — 신뢰도 최하위 병합 100쌍(정밀도), 후보에는 들어왔지만
임계값을 못 넘은 100쌍(점수화 재현율), 그리고 블로킹 밖의 동일-성
50쌍(블로킹 재현율). 세 번째가 없으면 blocked_out 이 셀
대상 자체가 존재하지 않습니다.
픽스처의 precision 1.0 은 근거가 아닙니다.
미니 픽스처를 실제로 돌리면 후보 쌍이 1개(jeff.skilling ~
jskilling)이고, 골드셋의 음성 4쌍은 블로킹에서 걸러져
점수화 함수에 닿지도 않습니다. tp=1 · fp=0 의 산술일 뿐입니다.
픽스처가 확인하는 것은 배선이 이어졌는지이고, 실제 정밀도·재현율은 아직
측정되지 않았습니다 — Task 8 이 돌아야 나옵니다.
그리고 이 픽스처의 유일한 양성 쌍이 하필 가장 위험한 규칙의 사례입니다.
{이름 첫 글자}{성} 형태를 0.90 으로 인정하는데(임계값 0.85),
jsmith 는 John Smith 일 수도 Jane Smith 일 수도 있습니다.
규칙을 조이면 픽스처의 양성이 사라지므로, 조이기 전에 실데이터에서 이 규칙이
만드는 오탐 비율을 먼저 봐야 합니다. 오탐 경로 자체는 테스트로 못 박아두었습니다.
노드를 그대로 다 그리면 아무것도 읽을 수 없는 털뭉치가 됩니다. 세 안을 놓고 A(앵커 + 점진 확장) 로 정했습니다.
인물을 검색해 하나 고르면 그 사람의 이웃만. 클릭할 때마다 한 겹씩 자람. 화면엔 사용자가 직접 펼친 것만.
부서 덩어리 10~20개로 시작. 선 굵기가 통신량. 전체 구도를 먼저 잡고 조직을 열어 개인으로.
왼쪽 검색·필터·인물 랭킹이 주인공. 오른쪽 그래프는 근거를 확인하는 창.
A를 고른 이유: 엔티티 해소의 가치가 화면에 바로 드러나고, B는 조직 소속 추론이 선행돼야 해서 v1 범위가 커지며, A가 가장 작습니다(API 2개). 다만 "실무자가 찾을 사람을 이미 알고 시작한다"는 전제는 검증되지 않은 추측입니다. 실사용 피드백에서 뒤집히면 B를 재검토합니다.
① 이웃을 무엇으로 자를 것인가 — 1-hop 이웃이 수백 명일 때 상위 15명만
보여주는데, 기준이 단순 메시지 수면 안 됩니다. Enron 에서 그렇게 하면
상위권이 메일링리스트·자동알림·뉴스레터로 채워집니다. 기본값은
양방향 가중치 min(A→B, B→A) — 한쪽으로만 쏟아지는
브로드캐스트는 자동으로 밀려나고 실제로 주고받은 관계가 올라옵니다.
② 확장 폭발 방지 — 노드 상한 150개. 초과하면 확장을 막고 접기를 안내합니다. 확장 취소(undo)를 제공하고, 자동으로 접지 않습니다 — 무엇이 사라졌는지 모르면 혼란스럽습니다.
③ 비교 장치를 UI에 내장 — 우측 상단 온톨로지 토글.
ON 이면 Person 단위(별칭 통합), OFF 면 Actor 단위(이메일 주소
그대로). 같은 인물을 검색한 채로 토글하면 노드가 합쳐지고 엣지가 굵어지는 게 눈앞에서
일어납니다. 성능 향상을 말로 설명할 필요가 없어지고, 동시에
엔티티 해소가 틀렸을 때 바로 보이는 디버깅 도구가 됩니다.
GET /api/search?q=skilling
→ [{ person_id, display_name, alias_count, msg_count }, …]
GET /api/ego?person=P0042&limit=15&resolved=true
→ { nodes: [...], edges: [...] }
전체 그래프는 클라이언트로 보내지 않습니다. 각 Person 의 상위 이웃 리스트는
[5] project 단계에서 미리 계산해두므로 요청 시엔 조회만 합니다.
태스크마다 새 에이전트가 구현하고, 별도 리뷰어가 diff 를 검증합니다. 리뷰가 통과할 때까지 다음 태스크로 넘어가지 않습니다.
| # | 태스크 | 상태 | 비고 |
|---|---|---|---|
| 1 | 프로젝트 뼈대 · 경로 | ✅ 완료 | 수정 1라운드 |
| 2 | maildir 파싱 | ✅ 완료 | 수정 1라운드 · Critical 2건 |
| 3 | 정규화 · Actor 추출 | ✅ 완료 | 수정 1라운드 · Critical 1건 · Important 1건 |
| 4 | 엔티티 해소 | ✅ 완료 | 블로킹 · Jaro-Winkler · 골드셋 평가 |
| 5 | 온톨로지 · 추론 | ✅ 완료 | union-find 폐포 · Person 노드 · RDFS 는 선택적 |
| 6 | property graph 투영 | ✅ 완료 | weight = min · 일방향은 합계로 살린다 |
| 7 | API | ✅ 완료 | ego 서브그래프 · 노드 상한 · 모드별 프로필 |
| 8 | 실데이터 파이프라인 | 🔄 진행 중 | 파이프라인 완주 · 골드셋 라벨링 전 |
| 9 | 탐색 UI | 대기 | |
| 10 | 문서 갱신 · 배포 | 대기 |
현재 테스트 126개 통과 (경로 3 · 인제스트 22 · 정규화 8 · 추출 3 · 해소 32 · 온톨로지 8 · 투영 14 · API 12 · CLI 8 · 코퍼스 픽스처 8 · 골드셋 후보 8) + JS 30개.
픽스처가 코퍼스를 대표하지 않으면 테스트는 아무것도 보장하지
않습니다. Task 1~7 이 전부 초록인 동안 파이프라인은 실데이터에서
0건을 넣고 0건을 해소했습니다. 미니 픽스처가 코퍼스와 두 번 멀어졌기
때문입니다 — 파일명에서 마침표를 지웠고, 코퍼스에 거의 없는 표시이름을
넣었습니다. 두 번 다 테스트를 통과시키려는 수정이었습니다.
지금은 코퍼스 성질을 재현하는 픽스처를 따로 두고 (마침표 파일 · 맨 주소
헤더 · 깨진 로컬파트 · 사본 · 함정 5종) 그 위에서 확인합니다.
전체 경위는 docs/reflexion/2026-09-08-process-reflexion.yaml
에 실패 12건 · 패턴 4개 · 조치 5개로 정리했습니다.
세 번 돌렸습니다. 첫 실행은 해소가 0건이었고, 규칙을 고친 두 번째에 4,299쌍이 나왔고, 내용 해시 중복 제거를 넣은 세 번째가 현재 수치입니다.
| 항목 | 1차 | 현재 |
|---|---|---|
| 메시지 | 240,577 | 100,827 (사본 139,750 접음) |
| custody 행 | 240,577 | 240,577 (사본 전부 보존) |
| 참여(participation) | 2,203,610 | 843,040 |
| actor | 36,288 | 36,288 |
| same_actor 쌍 | 0 | 4,299 |
| Person | 36,288 (= actor) | 32,840 |
| person / actor 노드 | 36,069 / 36,069 | 32,643 / 36,069 |
| 가장 두꺼운 엣지 | 5,441 | 1,856 |
| RDF 트리플 | 2,626,470 · 180MB | 1,145,048 · 75MB |
| 과병합 클러스터 | 0 | 0 |
| 전체 소요 | 약 27분 | 약 13분 |
토글이 드디어 차이를 보여줍니다. person 노드 32,643 대 actor 노드 36,069 — 3,426개 차이입니다. 1차 실행에서는 두 그래프가 같은 크기여서, 이 제품의 핵심 주장이 실데이터에서 아무것도 보여주지 못했습니다. 해소가 무엇을 고쳐서 살아났는지는 아래 06 에 적었습니다.
아직 정밀도는 모릅니다. 4,299쌍이 나왔다는 것과 그것이 옳다는 것은 다른 문제입니다. 지금 믿을 수 있는 것은 과병합 클러스터가 0개이고 클러스터 최대 크기가 8명이라는 것 정도입니다.
계획의 세 층 중 둘이 실데이터에서 비어 있었습니다.
| 층 | 수 | 뽑는 법 |
|---|---|---|
| 정밀도 | 150 | 규칙별 층화 — 신뢰도 낮은 순으로 뽑으면 0.85 규칙 하나로만 채워집니다 |
| 재현율 | 100 | 이름 형태 · 토큰 공유 · 미연결, 로컬파트 유사도 내림차순 |
재현율 층의 상위가 규칙의 빈 곳을 그대로 보여줍니다 —
philip ~ phillip,
pete ~ peter,
stace ~ stacey,
edyeland ~ eydeland.
전부 같은 사람인데, 지금 규칙은 오타와 단축형을 전혀 다루지
않습니다. 이게 다음에 손댈 곳입니다.
행마다 판정 근거를 함께 싣습니다 — 로컬파트 유사도, 공통 상대방 수, 양쪽의
발신·수신 수, 표시이름. 주소만 주면 사람도 규칙과 같은 추측을 하기 때문입니다.
adel.rizkalla ~ rizkalla.adel 은 공통 상대방이
74명이고, aagrawal@cba.ua.edu ~
aagrawal@haas.berkeley.edu 는 0명입니다.
구현을 시작한 뒤 드러난 결함들. 대부분 계획 문서 자체의 오류였고, 구현자가 계획을 충실히 옮긴 결과 그대로 코드에 들어왔습니다. 각각 판정하고 계획을 고쳤습니다.
localpart_name_match 규칙이 실제로 검증되게 함import email 만으로는 email.message 가
바인딩되지 않음. pytest 가 부수효과로 끌어와서 테스트는 통과했지만,
Task 8 의 python -m edisco.cli 는 로직 실행 전에 죽었을 것FileNotFoundError → 관례를 버림
eDiscovery 에서 custody 는 증거입니다. 누가 그 문서를 보관하고
있었는지는 사건에 관한 사실이고, 한 번 버리면 복원할 수 없습니다.
message_custodian 테이블을 추가했습니다 —
지금 넣는 비용은 테이블 하나와 삽입 한 줄이고,
Task 8 이 돈 뒤에 넣는 비용은 코퍼스 전체 재처리입니다.
name_key 가 중간 이름에서 두 형식이 어긋남.
"Mary Jo Watkins" → watkins, mary 이지만
"Watkins, Mary Jo" → watkins, mary jo.
중간 이름은 헤더마다 있다 없다 하므로, 실제 코퍼스에서 같은 사람이 두 키로 갈라짐.
Task 4 가 전적으로 이 불변식에 기댐 → 이름은 첫 토큰만 쓰도록 통일notenron.com 이 enron.com 으로 접힘.
endswith("enron.com") 에 점 경계 검사가 없었음.
주소가 곧 신원인 단계에서 외부인을 Enron 네임스페이스로 편입시키는 provenance 결함resolve.py docstring 이 "클러스터로 닫는 건 ontology.py 의
OWL 추론이다"로 되어 있었습니다 — Task 5 를 union-find 로 재설계한 뒤
지우지 못한 문장입니다. 구현자가 브랜치 히스토리를 읽고 모순을 찾아 고쳤습니다score_pair 가 participation 셀프 조인을
actor 당 두 번 돌았음. 채점 쌍당 쿼리 6개. 실데이터에서 후보 쌍은
수십만 개이므로 4개로 줄였습니다파이프라인이 끝까지 돌았지만 해소가 아무것도 찾지 못했습니다. 성능 문제가 아니라 설계가 없는 데이터를 전제하고 있었습니다.
From: 의 0.2%, To: 의 1.1% 만
이름 <주소> 형태입니다. 2015년 배포본이 헤더를 정규화한
결과로 보입니다. 그래서 표시이름을 가진 actor 가 36,288명 중
774명(2.1%) 뿐입니다display_name_exact·name_similarity 는 양쪽,
localpart_name_match 는 한쪽의 이름이 필요하고,
shared_correspondents 조차 best_name ≥ 0.8 로
걸려 있습니다. 이름이 없으면 구조적으로 0건입니다 —
후보 400쌍을 표본 채점했더니 400쌍 전부 무규칙이었습니다jeff.skilling 에 표시이름을 넣었습니다. 옳은 조치처럼 보였지만,
결과적으로 코퍼스에는 없는 조건을 픽스처에 만들어 넣은 것
이었습니다. 테스트가 초록이었던 이유가 거기 있습니다vince.kaminski@enron.com 이
vkaminski@aol.com 으로 메일을 보냅니다. 로컬파트끼리 비교하면
잡히지만, 지금 규칙은 로컬파트↔이름 만 봅니다
고친 결과: 주소 구조로 판단하는 규칙 5개를 넣고
(localpart_punctuation 0.95 · same_localpart_multi_token
0.93 · localpart_reversed 0.92 · initial_plus_last 0.88 ·
same_localpart_single_token 0.85) 4,299쌍이 나왔습니다.
가드가 먼저 필요했습니다 — 로컬파트가 깨진 actor 가 1,903명(5.2%) 있고,
가드 없이 돌리면 .aaron@enron.com 이 서로 다른 세 명의 Aaron 을
한 사람으로 묶습니다.
shared_correspondents 의 이름 게이트는 떼지 않았습니다.
떼자는 안을 검토했다가 측정 결과로 철회했습니다 —
douglas.clifford ~ douglas.dunn,
alison.keogh ~ alison.moss 가 병합됩니다.
최소 상대방 수를 걸어도 남습니다. 동료는 필연적으로 상대방을 공유하므로,
구조 신호만으로는 "같은 사람"과 "같은 팀"이 구별되지 않습니다.
JavaMail.evans@thyme).
240,577 파일에 고유 ID 가 정확히 240,577개입니다all_documents 와 inbox/sent 에 같은 메일),
커스토디언 간은 3.1%뿐입니다
키는 내용 해시로 정했습니다 —
blake2b(date, from, to, cc, subject, body).
message_id 와 source_path 는 뺍니다. 사본마다 다르고,
그게 이 키가 필요한 이유입니다. 후보 네 개가 58.1~58.6% 로 수렴하므로
더 접는 쪽이 아니라 덜 틀리는 쪽을 골랐습니다 — 본문만으로
접으면 1,214건을 더 접는데, 그건 내용이 같지만 다른 시점에 보낸 메일까지
합치는 것입니다.
접되 custody 는 사본마다 남깁니다. message 100,827행에
custody 240,577행 — 원본 파일 수와 같습니다. 그래야
message_custodian 이 비로소 계획이 의도한 일을 합니다:
"Organizational Changes 라는 이 한 통을 12명이 35개 사본으로 보관하고
있었다". 지금까지는 메시지당 정확히 1행이라 아무 일도 하지 않았습니다.
그리고 사본이 접히면서 가장 두꺼운 엣지가 5,441 → 1,856 로
떨어졌습니다 — 부풀림이 실재했다는 증거입니다.
build_profiles 가 Person 당 3쿼리.
합성 20,000 actor 규모에서 Person 18,000명에 5.4만 쿼리가 돌아 이 함수 하나가
모듈 전체보다 12배 느렸습니다 (14.81초 대 1.2초). 전체를 두 번 훑는 방식으로
바꿔 0.61초. Task 4 리뷰에서 잡은 "쌍마다 쿼리"와 같은 패턴이 계획에
그대로 남아 있었습니다sent/received
0 이 됐습니다. UI 는 노드 크기를 이 합으로 매기므로, 토글이 보여주려던
비교의 한쪽이 통째로 사라집니다. actor 자신의 수치만 담은 별도 프로필을
만들어 해결했습니다 — ON 은 skilling 이 별칭 2개에 발신 2통, OFF 는
발신 1통에 jskilling 이 발신 1통인 별도 노드입니다Task 5 재설계가 Task 6·7·8 에 온전히 반영됐는지, 그리고 계획에 적힌 숫자가 맞는지 픽스처에서 재계산했습니다.
sameActorAs 1쌍,
Task 6 의 엣지 합계 3 · weight 1, Task 7 의 3 vs 2. 모두 실측 확인materialize_rdfs 설명이 "파이프라인 기본
경로에서는 부른다"로 끝나 Task 8 의 실제 코드와 모순이었습니다 (부르지 않습니다)owlrl 은 못 씁니다. 측정 완료.
범위를 좁히는 완화책으로는 구제되지 않았습니다 — 메시지 트리플을 다 빼고
actor 만 남겨도 마찬가지입니다:
actors=2,000 msgs=0
OWL-RL 4,217 -> 50,571 71.2s
RDFS 4,217 -> 8,240 0.7s
union-find 4,217 -> 44,217 0.3s
세 가지가 드러났습니다. OWL-RL 은 2,000 actor 에 71초이고 초선형입니다.
RDFS 는 폐포를 계산하지 않습니다 — 8,240개만 나온 게 그 증거이고,
대칭·이행은 OWL 구문이라 RDFS 프로파일에 없습니다.
그리고 완전 그래프를 심는 것 자체가 틀렸습니다 — union-find 가
20,000 actor 에서 400만 트리플을 만든 건 seed pair 가 사슬이 되어 2,001명
클러스터 하나로 병합된 결과입니다.개발 머신이 기관 프록시 뒤에 있어 도구가 기본 설정으로 동작하지 않습니다.
| 증상 | 대응 |
|---|---|
pip 가 SSLCertVerificationError 로 죽음 |
--trusted-host pypi.org --trusted-host files.pythonhosted.org --trusted-host pypi.python.org |
curl 이 exit 35 (SSL) 로 실패 |
PowerShell Invoke-WebRequest — Windows 인증서 저장소를 씀 |
파일명 끝 마침표를 pathlib 이 못 봄 |
열거는 os.walk, 열기는 확장 경로 (src/edisco/fsutil.py) |
프록시가 TLS 를 가로채기 때문입니다. CI 로 옮기면 이 우회는 불필요해집니다.
매번 플래그를 붙이는 대신 pip.ini 에 기록하거나
기관 CA 인증서를 PIP_CERT 로 지정하는 편이 낫습니다.
마침표 문제는 픽스처 관례가 아니라 코퍼스 전체의 문제였습니다.
Enron 코퍼스의 메일 파일은 전부 1. 처럼 마침표로 끝납니다
(allen-p 한 명만 봐도 628/628). Windows 는 경로를 정규화하면서
끝의 마침표를 떼기 때문에 Path(...).is_file() 이 False 가 되고,
rglob("*") 로 훑으면 파일이 하나도 안 잡힙니다.
예외가 나지 않는다는 것이 이 문제의 핵심입니다 —
파이프라인이 0 건을 넣고 성공한 것처럼 끝납니다.
실제로 첫 실행에서 커스토디언 파일 수가 전부 0 으로 나왔고, 동점이 이름
역순으로 갈려 상위 15명이 zufferli-j, zipper-a, …
로 뽑혔습니다. 조용히 틀린 답을 내는 종류의 결함입니다.
owlrl 을 버렸으므로 추론기
성능이 상한을 정하는 구조도 사라졌습니다.ttl 을 만들 때만 선택적으로 씁니다blocked_out 은 블로킹 손실의 하한입니다 — 성이 서로 다르게
적힌 별칭은 동일-성 표본으로도 안 잡힙니다blake2b(date, from, to, cc, subject, body).
사본 139,750건이 접혀 message 240,577 → 100,827, participation
2,203,610 → 843,040. custody 는 사본마다 보존합니다 (240,577행)shared_correspondents 의 이름 게이트는 유지합니다weight 0 — 실데이터에서 엣지의
91.4%가 여기 해당합니다 (147,246개 중 134,594개).
min 은 유지합니다
(합계로 순위를 매기면 브로드캐스트가 상위를 차지함). 대신 API 가 합계를 같이
싣고, UI 는 굵기를 합계로 그리고 왕복이 아닌 관계는 점선으로 구별합니다.
weight > 0 으로 필터하면 그래프의 대부분이 사라집니다 —
픽스처에서도 엣지 5개 중 4개가 0 입니다vkaminski ↔ vince.kaminski)과
shared_correspondents 의 이름 게이트 제거가 후보입니다.
규칙이 아무것도 못 잡는 상태에서 골드셋을 라벨링하는 건 의미가 없으므로
이것이 먼저입니다(date, subject, from, to) 는 측정용으로 쓴 것이지 검증된 키가
아닙니다. 본문 해시를 넣을지, 날짜 정밀도를 어떻게 볼지 정해야 합니다initial_plus_last 의 오탐 비율 — 4,299쌍 중
2,108쌍이 이 규칙입니다. jsmith 가 John 인지 Jane 인지
주소만으로는 알 수 없으므로, 골드셋이 여기를 먼저 봐야 합니다.
same_localpart_single_token(1,142쌍)도 기관이 다른 동명이인이
섞일 수 있습니다답변은 생성형 모델이 만듭니다. 틀릴 수 있으니 중요한 내용은 위 문서 본문으로 확인하세요. 시간당 20개까지 질문할 수 있습니다.