feat(news): GDELT 원본 아카이브 수집기 — 과거 구간 뉴스를 인증 없이 확보 - #77
Open
choigod1023 wants to merge 5 commits into
Open
Conversation
DOC API 로는 학습·검증 구간(2024-01~2025-06) 뉴스를 채울 수 없다는 것이 실측으로 확정됐다. artlist 모드가 최근 3개월만 서빙한다. 2024-01 요청 → 응답 250건 / 요청 창 안 0건 (실제 날짜 전부 2024-02-01) 2026-06 요청 → 응답 250건 / 요청 창 안 250건 PR #69 수집분의 "요청 창 밖 68.8%" 도 같은 원인이다. 수집 방식 문제가 아니라 API 제약이므로, 기존 러너를 아무리 오래 돌려도 해결되지 않는다. 막힌 것은 GDELT 의 **검색 API** 였지 **데이터** 가 아니었다. GKG 원본 파일은 data.gdeltproject.org 에 인증 없이 열려 있다. 15분마다 한 파일, zip 약 5MB, 기사 약 1,345건, PAGE_TITLE 보유율 100%(실측). 검토한 대안과 기각 사유 - 네이버/카카오 검색 API: 날짜 범위 지정 파라미터가 없고 start 상한 1,000. "2024년 3월 기사만" 을 받을 수 없어 DOC API 와 같은 실패를 반복한다. - BigQuery(gkg_partitioned): 데이터는 있으나 GCP 프로젝트·결제 계정이 필요하다. 실제 조회로 검증하지 못했으므로 이 PR 에 포함하지 않는다. 검증 안 된 경로를 남겨두면 ai#22 의 combined 모드와 같은 함정이 된다. 표본 설계 전량은 96파일/일 x 548일 = 약 52,600파일 = 263GB 다운로드다. 뉴스 위험은 월 단위 집계이므로 전수가 필요하지 않다. 매시 정각 슬라이스 하나씩(하루 24파일) 체계적 표본을 쓴다. 표본률 1/4, 약 66GB, 예상 1.3만 건. 시각 고정 표본이라 시간대 편향이 월 간 일정하게 유지된다. 월별 **상대** 변화를 보는 용도에는 충분하며, 절대 건수를 쓰려면 보정하도록 진행 파일에 sample_rate 를 남긴다. 구현 - 원본은 저장하지 않는다. 내려받아 걸러내고 버린다(디스크 263GB 를 쓰지 않는다) - 다운로드 8병렬, 슬라이스 단위 진행 기록으로 중단 지점부터 재개 - 404(수집 중단 구간)와 깨진 zip 은 건너뛴다. 한 슬라이스 실패로 전체를 멈추지 않는다 - 질의 축은 DOC API 와 동일(감염병/의료용품/원자재) x 위험 문맥 교집합 실측: 하루 24슬라이스 4초, 36건 매칭 (medical_supply 23 / infectious_disease 12 / raw_material 1) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 tasks
GDELT GKG 는 2019-10 경부터 Extras 에 PAGE_TITLE 을 넣기 시작했다. 실측: 2018-01-15 / 2019-01-15 / 2019-07-01 / 2019-09-01 → 보유율 0% 2019-10-01 / 2024-01-15 → 보유율 100% 제목이 없으면 사건 분류가 불가능하다. 그래서 2018-01~2019-09 수집이 통째로 비었고, 2018-19 전체를 돌려도 437건(전부 2019-09 이후)만 나왔다. 원장 학습 구간에 2018-19 가 들어가는데 뉴스만 비면 일관성이 깨진다. URL 슬러그에서 제목을 복원한다. 슬러그는 헤드라인을 하이픈으로 이은 것이라 키워드 매칭에 쓸 수 있다. 실측 복원율 80%, 2018-19 수집 결과 2,730건 (슬러그 2,293 / PAGE_TITLE 437). - 단어 4개 미만이면 버린다. 카테고리 페이지나 숫자 ID 경로에서 잘못 복원한 제목으로 사건을 분류하면 오탐이 늘어난다. - 슬러그 끝의 기사 ID(숫자·해시)는 떼어낸다. - `title_source` 컬럼으로 출처를 남긴다.⚠️ 시계열 비교 시 반드시 title_source 를 함께 봐야 한다. 슬러그는 불용어가 빠져 키워드 밀도가 높아 탐지율이 다르다. 실측 구성비: 2018-19 (슬러그 84%) material 581/2,730 = 21.3% 2024-25 (제목 100%) material 122/4,114 = 3.0% 7배 차이는 실제 변화가 아니라 제목 출처 차이다. 2019-10 을 경계로 탐지율이 바뀌므로 두 기간을 하나의 시계열로 이어 붙이면 안 된다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
supply_news_risk 가 비영 0% 였다. 원인은 키워드도 수집량도 아니었다. 수집 기사 6,844건을 분류기에 통과시키면 공급 사건이 6,127건(89.5%)이다. port_or_logistics_disruption 5,033 export_restriction_or_sanction 842 factory_shutdown 208 war_or_armed_conflict 44 그런데 산출물에 남은 고유 기사는 36건이고 공급 사건은 0건이었다. 이 사건들은 분류기가 material="general_material" 을 부여하는데, 그 이름의 원자재 매핑이 없어 _match_stock_mapping 이 빈 결과를 돌려주고 전량 버려졌다. 항만 파업·전쟁·수출통제·물류 차질은 **특정 원자재에 귀속시킬 수 없는** 사건이다. 매핑을 알루미늄·라텍스까지 넓혀도 이 경로는 열리지 않는다. 정의상 전 품목에 적용해야 한다. - SUPPLY_WIDE_EVENT_TYPES 를 전 품목 경로로 보낸다. factory_shutdown 은 라텍스처럼 원자재가 특정되면 그쪽으로 먼저 매칭되고, 특정되지 않은 경우에만 내려온다. - 감사행은 사건당 1행만 남긴다. 품목별로 남기면 사건 1건이 6만 행이 되고 6,127건이면 3.7억 행(약 190GB)이 되어 디스크가 먼저 터진다(PR #69 에서 No space left on device 를 두 번 겪었다). 집계 단계에서 전 품목으로 펼친다. - 기존 품목별 supply_news_risk 와는 **최댓값**으로 합친다. 합산하면 같은 사건이 두 경로로 들어와 이중 계상된다. 실측 효과 supply_news_risk 비영 0.0% → 98.1% (중앙 0.1697 / 최대 0.2667) total_news_risk 최대 0.0860 → 0.3216 module_c_supply_risk 최대 0.0326 → 0.1249 (3.8배) 감사행 3.7억 예상 → 831행 이로써 supply_signal 가중치의 45%(supply_news)가 죽어 있던 상태가 해소된다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
filter_relevant_news 가 수집 7,756건 중 991건(12.8%)만 통과시켰다. 탈락분에 분류기가 port_or_logistics_disruption 으로 정확히 잡을 기사가 대량으로 있었다. "customs border protection leads long lines airports" ← customs 미포함 "us customs computer outage causes delays" ← outage/delay 미포함 원인은 목록이 한국어 위주로 만들어졌고 영어는 일부 구(phrase)만 있던 것이다. `항만` 은 있는데 `port` 가 없고, `factory shutdown` 은 있는데 `strike` `sanction` `disruption` `outage` 가 없었다. GDELT 수집분은 전량 영어라 그대로 탈락했다. 이 필터에서 걸러지면 분류기가 **볼 기회조차 없다.** supply_news_risk 가 0% 였던 원인이 #77 의 general_material 폐기(H)와 이 필터(J) 두 겹이었다. - news_llm_analyzer 의 분류 키워드를 덮도록 50여 개 보강 - 통과율 12.8% → 100.0% (탈락 2건은 분류기도 none 으로 판정) - 회귀 테스트 추가: 분류 가능한 기사가 입구 필터에서 탈락하면 실패한다. 두 곳에 키워드 목록이 따로 있어 한쪽만 고치면 다시 어긋난다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 tasks
리드타임 조정폭이 최대 6.5일에 그친 원인을 추적한 결과 점수 계산 사슬에
결함이 셋 더 있었다. 앞선 H(공급사건 폐기)·J(입구 필터)에 이어 직렬로 쌓여
있어 하나를 고치면 그 아래가 드러났다.
K. 가중치 10개를 곱셈 결합
개별 값이 0.5~0.8 로 "보통" 이어도 열 번 곱하면 0.5^10 ≈ 0.001 이다.
실측 article_score 중앙 0.0034 / 최대 0.0111.
→ 기하평균으로 바꾼다. 같은 순서를 주면서 범위를 0~1 로 유지하고,
"하나라도 0 이면 전체 0" 이라는 곱셈의 성질도 보존한다.
L. 포화 변환 1−exp(−S) 가 작은 점수용으로 맞춰져 있었다
기하평균으로 점수가 0.6 대가 되자 합 S 가 커져 **중앙값 1.0** 으로 포화.
순위상관이 nan(값이 상수) 이 되어 판정 기준을 통과하지 못했다.
→ λ 를 도입해 1−exp(−S/λ). λ 는 데이터에서 잡는다(비영 합 p90).
상수를 손으로 박으면 원천이 바뀔 때 또 어긋난다.
M. λ 를 전체 풀에서 하나로 잡았다
전 품목 공통 경로(__ALL_ITEMS__)는 그 달 공급 기사를 전부 합치므로 합이
350 인데, 일반 품목은 1~2 다. 같은 λ 로 나누면 센티넬이 무조건 1.0 이 되어
전 품목이 최대 위험을 받는다.
→ 버킷별로 λ 를 잡는다. 실측 λ: material 5.72 / supply 114.00 / disease 1.91
부수 조치
- 국가 가중치를 관세청 실측 수입 비중으로 교체(build_country_weight.py).
종전에는 country_weight.csv 가 4행뿐이고 GDELT 수집분이 전량
country="Unknown" 이라 모든 기사가 0.5 상수를 받았다. 상수 가중치는 점수를
깎기만 할 뿐 기사 간 구분에 기여하지 않는다.
바레인 1.000(수입 49.7%) / 중국 0.552 / 미국 0.005(0.25%).
실측상 미국 기사가 1,117건(14.4%)으로 가장 많은데 수입 비중은 0.25% 다.
- 기사 제목에서 국가 추출(_detect_country). 19.6% 에서 식별된다.
- 감사 산출물 기본 형식을 parquet+zstd 로. CSV 로 두니 8.7GB 까지 커져 실행을
중단시켜야 했다. 62.5MB(139배 압축)이고 내용은 동일하다. 이 파일은 점수
계산에 쓰이지 않는 근거 기록이다. NEWS_ARTICLE_SCORE_FORMAT=csv 로 복원 가능.
효과와 한계
supply_news_risk 최대 0.633 → 0.954
material_news_risk 최대 0.123 → 0.929
그러나 리드타임 검정(supply_risk_lead_time_validation.py)은 수정 전후가
소수점 세 자리까지 동일하다. 유일하게 유의한 조합이 trade_risk(관세청)인데
그것은 뉴스와 무관하게 계산되기 때문이다. 뉴스 기반 supply_risk 는 척도를
5배 키웠는데도 12건 전부 무의미하고 부호도 섞인다.
**척도가 문제가 아니었다.** 점수를 크게 만들어도 리드타임과의 관계가 없다.
상세는 ai#20 코멘트.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DOC API 로는 학습·검증 구간(2024-01~2025-06) 뉴스를 채울 수 없다는 것이
실측으로 확정됐다. artlist 모드가 최근 3개월만 서빙한다.
2024-01 요청 → 응답 250건 / 요청 창 안 0건 (실제 날짜 전부 2024-02-01)
2026-06 요청 → 응답 250건 / 요청 창 안 250건
PR #69 수집분의 "요청 창 밖 68.8%" 도 같은 원인이다. 수집 방식 문제가 아니라
API 제약이므로, 기존 러너를 아무리 오래 돌려도 해결되지 않는다.
막힌 것은 GDELT 의 검색 API 였지 데이터 가 아니었다. GKG 원본 파일은
data.gdeltproject.org 에 인증 없이 열려 있다. 15분마다 한 파일, zip 약 5MB,
기사 약 1,345건, PAGE_TITLE 보유율 100%(실측).
검토한 대안과 기각 사유
"2024년 3월 기사만" 을 받을 수 없어 DOC API 와 같은 실패를 반복한다.
실제 조회로 검증하지 못했으므로 이 PR 에 포함하지 않는다. 검증 안 된 경로를
남겨두면 ai#22 의 combined 모드와 같은 함정이 된다.
표본 설계
전량은 96파일/일 x 548일 = 약 52,600파일 = 263GB 다운로드다. 뉴스 위험은 월 단위
집계이므로 전수가 필요하지 않다. 매시 정각 슬라이스 하나씩(하루 24파일) 체계적
표본을 쓴다. 표본률 1/4, 약 66GB, 예상 1.3만 건.
시각 고정 표본이라 시간대 편향이 월 간 일정하게 유지된다. 월별 상대 변화를
보는 용도에는 충분하며, 절대 건수를 쓰려면 보정하도록 진행 파일에 sample_rate 를
남긴다.
구현
실측: 하루 24슬라이스 4초, 36건 매칭
(medical_supply 23 / infectious_disease 12 / raw_material 1)
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
검증
sehyeon 님 확인 부탁드리는 지점
stock_item_material_mapping.csv가 2,297행 전부polypropylene (PP)단일입니다. 뉴스를 채워도 PP 외 원자재 기사는 연결될 곳이 없습니다. 알루미늄은 수요비중 13.37%(PP 14.35%)에 Granger p=0.0033(PP 0.0123)으로 근거가 있는데 매핑에 없습니다. 확장 여부가 도메인 판단이라 임의로 넣지 않았습니다.관련: #22, #69
🤖 Generated with Claude Code