MyGo 云音乐
用 Go 与 MyGo 写成的第三方网易云音乐桌面客户端。原生界面,没有 WebView;边下边播,无损与 Hi-Res,逐字歌词与「律动」,刘海播放器。
Translation pending
아스날 이적 · 팀 소식을 BBC · 스카이스포츠 · 가디언 등 여러 매체에서 모아 한국어로 정리합니다. 보도마다 공신력 등급과 이적 진행 단계를 함께 표시합니다.
Translation pending
MediaPython · JinjaGitHub @benidjorOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Bullet-in: 아스날 FC 뉴스를 3시간마다 수집하여 (영국 현지 언론, ITK) 한국어로 번역 · 요약하고 신뢰도순으로 보여 주는 서비스 및 데이터 파이프라인
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
영국 현지 언론과 ITK (X) 에 흩어진 Arsenal FC 소식을 하루 8회 병렬 수집하고 공신력 스코어링과 중복 제거를 거쳐 LLM 으로 번역 · 요약한 뒤 신뢰도순으로 제공하는 뉴스 데이터 파이프라인입니다. 공개 서비스: https://bullet-in.pages.dev · 2026-08-29 공개 · Airflow 가 3시간마다 파이프라인을 실행해 수집하고 검증하고 배포합니다. 현재 가동 상태는 수집 현황 대시보드 의 페이지 생성 시각과 SLO 표에서 확인할 수 있습니다. Bullet-in = bulletin (단신) + bullet (병기고 Arsenal) 의 언어유희입니다. 방문자가 처음 보는 홈 화면입니다. 대표 기사와 주요 소식을 신문 레이아웃으로 배치하고, 그 아래에 최신 뉴스와 가십을 나눠 싣습니다. 제목은 모두 원문을 한국어로 옮긴 것이며 매체명과 공신력 등급을 함께 표시합니다. 기사 상세 화면입니다. 3줄 요약, 한국어로 옮긴 본문, 원문 링크, 기자 바이라인을 표시합니다. 위 수치는 수집 현황 대시보드 가 실행할 때마다 새로 계산합니다. 설계 판단만 빠르게 확인하려면 8. 설계 결정과 트레이드오프 로 바로 이동해도 됩니다.Read the full README ↗
영국 현지 언론과 ITK (X) 에 흩어진 Arsenal FC 소식을 하루 8회 병렬 수집하고 공신력 스코어링과 중복 제거를 거쳐 LLM 으로 번역 · 요약한 뒤 신뢰도순으로 제공하는 뉴스 데이터 파이프라인입니다.
공개 서비스: https://bullet-in.pages.dev · 2026-08-29 공개 · Airflow 가 3시간마다 파이프라인을 실행해 수집하고 검증하고 배포합니다. 현재 가동 상태는 수집 현황 대시보드 의 페이지 생성 시각과 SLO 표에서 확인할 수 있습니다.
Bullet-in = bulletin (단신) + bullet (병기고 Arsenal) 의 언어유희입니다.
방문자가 처음 보는 홈 화면입니다. 대표 기사와 주요 소식을 신문 레이아웃으로 배치하고, 그 아래에 이적 뉴스 · 선수 · 팀 뉴스 · 가십을 나눠 싣습니다. 제목은 모두 원문을 한국어로 옮긴 것이며 매체명과 공신력 등급을 함께 표시합니다.
기사 상세 화면입니다. 3줄 요약, 한국어로 옮긴 본문, 원문 링크, 기자 바이라인을 표시합니다.
| 항목 | 내용 |
|---|---|
| 서비스 | 2026-08-29 공개 · 공개일 688명 · 첫 주 827명 |
| 규모 | 활성 소스 9종 (그중 수집 8종) · 기사 1,302건 (2026-10-07 기준) · 3시간 간격 실행 |
| 신뢰성 | 완주율 99.4% (2026-07-20 이후 628회 중 624회 · 2026-10-07 기준) · 중복 적재율 0% · 필수 필드 완전성 100% |
| 품질 게이트 | 실행 마지막 단계의 dbt 데이터 계약 테스트 22종 · 하나라도 실패하면 배포 중단과 Discord 알림 |
| 배포 | 머지된 코드를 다음 실행이 자동으로 내려받아 배포 · 운영 환경에서 반영 검증 · 불일치 시 이전 커밋으로 롤백 |
| 복구 | 매일 GCS 백업 · 별도 DB 에 복원해 테이블 6개와 raw_items 의 행 수를 백업 매니페스트와 대조 (2026-09-01 복구 테스트 전부 일치) |
| 비용 | Iceberg 테이블은 GCS · 카탈로그만 Google Lakehouse runtime catalog · 매니지드 Iceberg 테이블의 시간당 요금 없이 GCS 저장 · 작업 요금만 발생 (금액은 GCP 결제 보고서에서 조회) |
위 수치는 수집 현황 대시보드 가 실행할 때마다 새로 계산합니다. 설계 판단만 빠르게 확인하려면 8. 설계 결정과 트레이드오프 로 바로 이동해도 됩니다.
이 문서 읽는 법
아스날 관련 소식을 확인하려면 영국 언론 5~6곳과 ITK (In The Know) 트위터 계정을 각각 방문해야 합니다. 매체와 계정마다 공신력 편차가 커서, 같은 이적설이라도 어디서 나왔는지에 따라 신뢰도가 크게 달라집니다. 이 불편을 해결하기 위해 신뢰할 수 있는 소스만 선별해 한곳에 모으고, 한국어로 번역 · 요약해 신뢰도순으로 제공하는 서비스를 만들었습니다.
단순 수집 스크립트가 아니라 신뢰성 · 멱등성 · 데이터 품질 · 관측성을 갖춘 데이터 프로덕트로 설계했습니다. 공개일에 688명, 공개 첫 주 (08-29 ~ 09-04) 에 827명이 방문했습니다 (순 사용자 기준이며, 광고 차단 환경의 방문은 집계되지 않습니다).
메달리온 아키텍처 (Bronze → Silver → Gold) 위에 LLM 번역 · 요약, 데이터 품질 게이트, 배포 자동화를 올린 구조입니다. 파이프라인 1회 실행은 Airflow DAG 의 태스크 8개로 나뉩니다.
왼쪽이 입력 (수집 소스 · 코드 저장소 · GA4 사이트 태그), 가운데가 Oracle Cloud VM 에서 도는 수집 · 저장과 Airflow DAG, 아래가 Google Cloud 의 레이크하우스, 오른쪽이 서빙과 알림입니다. 글자가 작으면 그림을 누르면 3배 크기로 열립니다. 그림의 「소스 10종」 은 설정에 등재된 수를 말하며 이 가운데 9종이 활성이고 8종에서 수집합니다 (§3).
DAG 안에서 태스크는 이 순서로 돕니다.
advance -> collect -> enrich -> publish -> gate -> deploy_site -> judge
|
+-> warehouse_load (publish 이후 병렬)
| 태스크 | 하는 일 |
|---|---|
advance | origin/main 내려받기 (사람이 운영 서버에서 pull 하지 않는 구조) |
collect | 수집 소스 8종을 어댑터 5종으로 asyncio 병렬 수집 → 정규화 → URL · content_hash 기준 중복 제거 → 공신력 tier 산출 → Bronze · Silver 적재 |
enrich | Gemini API 로 번역 · 요약 · 영입 단계 분류 (신규 행만 처리하는 멱등 설계) |
publish | 정적 HTML 렌더 (기사 · 선수 · 대시보드 2종) · 실행 기록 · 신선도 판정 |
gate | dbt build 와 테스트 22종 (DuckDB 가 MariaDB 를 attach) · 실패 시 배포 중단 |
deploy_site | Cloudflare Pages 업로드 (산출물이 비정상이면 중단) |
judge | 라이브 build.json 으로 반영 확인 · 불일치 시 이전 커밋으로 롤백 · Discord 알림 |
warehouse_load | MariaDB 변경분 · 스냅샷과 GA4 행동 로그를 Iceberg (GCS) 에 적재 · Gold 재작성 |
실행 주기는 3시간이고 실행기는 LocalExecutor 입니다 (§5).
systemd 는 파이프라인 외부의 부가 작업만 담당합니다: 선수 워치리스트 (귀속된 선수를 fmkorea 에서 재검색) · 일일 백업 (GCS) · 레이크하우스 유지보수 (컴팩션 · 스냅샷 만료 · 고아 데이터 파일 청소) · Airflow 감시 (하트비트 · 실행 지연).
httpx, RSS = BBC Sport, 구단 공홈 = 사이트맵으로 후보를 찾고 GraphQL 로 본문 조회, X = 쿠키 주입 Playwright).asyncio 팬아웃으로 소스를 동시에 호출하며, 한 소스가 실패해도 나머지 수집은 정상 진행됩니다.content_hash 와 URL 정규화에 DB UNIQUE 제약을 더해 애플리케이션과 DB 양쪽에서 중복을 막습니다.confidence 값으로 정렬합니다.
기자 단위 tier 가 매체 tier 보다 우선 적용됩니다 (전담 기자의 기사만 등급 상향).전체 기사 화면 (
all.html) 입니다. 날짜별 시간순으로 정렬하고, 왼쪽 레일에서 뉴스 종류 · 공신력 · 소스 · 기자 · 영입 단계로 범위를 좁힐 수 있습니다. facet 옆의 숫자는 현재 적재된 기사가 각 갈래에 몇 건씩 있는지 보여 줍니다.
활성 소스는 9종이며 고정 tier 7종과 항목별 동적 tier 2종으로 구성했습니다 (X 와 커뮤니티는 게시물에 언급된 기자 · 매체의 공신력으로 tier 를 산출합니다).
아래 표에는 2026-07 에 비활성으로 돌린 1종 (football.london) 과 2026-08-15 에 수집을 멈춘 1종 (Goal.com) 을 포함해 10종을 싣습니다.
언론 5종은 공통 이적 키워드 필터를 공유합니다 (config/sources.yaml).
그중 수집 중인 3종 (BBC Sport · Sky Sports · The Guardian) 은 명단의 선수 · 감독 이름과 팀 낱말로 선수 · 팀 소식과 경기 평점까지 받습니다 (2026-10 개정).
| 소스 | tier | 어댑터 | 비고 |
|---|---|---|---|
| Arsenal.com | 0 | arsenal_api | 공식: 공홈 사이트맵으로 후보 발견 + GraphQL 로 본문 조회, taxonomy 필터 (이적 · 1군 재계약) · 2026-10 부터 1군 선수 · 팀 소식 (대표팀 · 수상 · 인터뷰) |
| BBC Sport | 1.5 | rss | 아스날 RSS (2026-10 팀 페이지에서 전환) · 비전담 기준선: 전담 기자 (Mokbel) 는 tier 1 로 상향 |
| Sky Sports | 2 | html | 비전담 기준선: 전담 기자 (Sheth) 는 tier 1.5 로 상향 |
| The Guardian | 3 | html | 2026-07 에 Open Platform API 에서 전환 (무료 등급 API 가 독점 기사를 제외) |
| Goal.com | 4 | html | 2026-08-15 수집 중단 (다른 소스 기사와 72% 중복 · collect: false) · 이미 적재된 기사는 그대로 서빙 |
| BBC Football Gossip | 4 | html | 타 매체 루머 라운드업 |
| football.london | 4 | html | 2026-07 이후 비활성 (enabled: false) · 과거 실행 기록에는 남아 있음 |
| afcstuff (X) | 동적 | x_playwright | 트윗에 언급된 기자 · 매체 tier 로 라우팅 (fallback 4) |
| David Ornstein (X) | 1 | x_playwright | 기자 본인 계정: 트윗에 @핸들이 없어 고정 tier |
| fmkorea 축구 소식통 | 동적 | fmkorea | 한국 커뮤니티: 언급된 기자 · 매체 tier 로 라우팅 (기본 4) |
기자 · ITK 공신력: 동적 소스는 항목마다 tier 를 개별 산출합니다. 기자를 먼저 조회하고, 없으면 매체를 조회하고, 둘 다 없으면 기본값을 적용합니다.
항목 1건의 tier 산출
1. 게시물에 언급된 기자가 레지스트리에 있는가?
있으면 -> 기자 tier (전담 기자는 매체보다 높다)
2. 없으면, 해당 매체가 레지스트리에 있는가?
있으면 -> 매체 tier
3. 기자도 매체도 레지스트리에 없으면
기본값 -> tier 4 (출처를 특정하지 못한 루머)
레지스트리 전체 (기자 · ITK 105명 · 매체 55곳 · 별칭) 는 config/credibility.yaml 에 있으며 화면의 기자 필터도 같은 파일을 참조합니다.
목표치와 측정 방법입니다. 번호는 수집 현황 대시보드 의 SLO 표와 동일하며 SLO-2 부터 6 까지는 실행마다 해당 대시보드에 갱신됩니다. 병렬화 측정 절차는 SLO-1 벤치마크 런북, 지표 정의는 SLO 측정 런북 에 정리했습니다.
| 번호 | 지표 | 목표 | 측정 방법 | 실측 (2026-10-07) |
|---|---|---|---|---|
| SLO-1 | 병렬화 수집 시간 단축 | 순차 대비 55% 이상 단축 (실측 기반 재조정¹) | metrics.benchmark() (concurrency=1 vs N 벤치마크) | 56.5% 단축 (2026-07-15 · 3회 중앙값 · 실행마다 측정하지 않음) |
| SLO-2 | 실행 성공률 | 99% 이상 | pipeline_runs.success_rate 최근 30회 평균 (재시도 · 소스 격리 포함) | 99.2% (최근 30회 중 2회 아스날 공식 소스 사이트맵 응답 타임아웃 · 사이트맵 재시도로 대응) |
| SLO-3 | 중복 적재율 | 0% | content_hash · URL UNIQUE 제약 + dbt unique 테스트 5종 | 0% (기사 1,302건) |
| SLO-4 | 필수 필드 완전성 | 99% 이상 | dbt not_null 테스트 10종 | 100% |
| SLO-5 | 소스 신선도 | 끊긴 소스 0 | 목록 페이지 응답 여부 (2회 연속 무응답) · 목록 변경 여부 (48시간 초과 미변경) 로 판정한 source_freshness.state | 0 (BBC Sport 를 아스날 RSS 로 전환해 끊김 해소) |
| SLO-6 | 수집량 이상 감지 | 이상 소스 0 · ±2σ 알림 | quality.volume_anomalies (직전 실행 대비) | 0 · 가동 중 (실발송 검증 2026-07-13) |
¹ 초기 목표 70% 를 도달 불가로 판정하고 55% 로 하향한 경위는 8. 설계 결정과 트레이드오프 에 있습니다.
실행 · 배포 · 감시를 모두 자동화했습니다. 2026-09-04 에 systemd 타이머에서 Airflow DAG 로 마이그레이션했습니다. 첫 24시간 동안 정규 실행 8회가 모두 성공했습니다 (소요 3.5~6.0분 · 재시도 0 · 오탐 0 · 런북 §6.5).
LocalExecutor · Postgres 메타 DB · DAG 1개 · 태스크 8개 (§2)catchup=False: 밀린 실행은 1회만 수행max_active_runs=1: 동시 실행 금지dagrun_timeout 30분advance 가 origin/main 을 내려받고, 파이프라인 실행 후 judge 가 라이브의 build.json 으로 반영 여부를 확인합니다.
설계는 배포 자동화 스펙 에 있습니다.
gate (dbt 테스트 22종)
|
+-- 통과
| +-- deploy_site -> judge 가 라이브 build.json 의 커밋 해시를 대조
| |
| +-- 일치 -> 반영 완료 + 리뷰 채널 알림
| +-- 불일치 -> 이전 커밋으로 롤백 + 사고 채널 알림
|
+-- 차단
+-- deploy_site 를 건너뛴다 (배포 없음) + 사고 채널 알림
게이트 결과 파일이 없으면 통과로 간주하지 않고 차단으로 판정합니다 (8. 설계 결정과 트레이드오프).
Discord 채널 2개 (사고 · 리뷰) 를 운영합니다. 모든 알림에 「무엇이 · 어디서 · 다음에 확인할 로그」 를 함께 담습니다.
raw_items 의 행 수를 백업 매니페스트와 대조
원본을 Bronze 에 불변으로 보존하므로 판정 규칙이 바뀌면 전체 재처리가 가능합니다.
src/bullet_in/ 의 backfill_*.py 15개와 migrate_*.py 2개
schema.sql 의 ALTER TABLE … IF NOT EXISTS 30건으로 멱등하게 처리
ensure_schema() 가 다시 적용합니다.정적 HTML 로 실행할 때마다 새로 생성하며 검색 엔진에는 노출하지 않습니다.
행동 지표: DAU · 퍼널 (진입 → 카드 클릭 → 반복 → 재방문) · 요일 × 시각 히트맵 · 관심 지수 · 리텐션 · 화면별 클릭 · 페이지 · 상위 기사 · 선수 페이지를 보여 줍니다. GA4 이벤트를 Iceberg Gold 테이블로 집계해 렌더링합니다.
행동 로그의 출처 (GA4 → BigQuery → Iceberg)
행동 지표 대시보드의 수치는 GA4 가 BigQuery 로 매일 내보낸 이벤트 원본을 warehouse_load 태스크가 Iceberg Bronze 로 적재해 집계한 결과입니다.
bi_entry (유입) · bi_card_click (카드 클릭) · bi_filter_apply (필터) · bi_origin_exit (원문 이탈)
src/bullet_in/serve/static/app.js 에 있습니다.bi_card_click 의 매개변수 card_hash 가 기사의 content_hash 와 같습니다.
수집 현황: SLO 6개 행 · 완주율 타일 · 일별 신규 · 실행 수 캘린더 · 소스 × 주차 · 처리량 · 소요 시간 분포 · 발행에서 수집까지의 지연 · 선수 축 · 공신력 · 단계 구성 · 소스 신선도를 한 화면에 모았습니다.
수집 현황의 출처 (MariaDB Silver → 화면)
행동 지표와 달리 별도의 집계 저장소를 두지 않고 서빙 DB 를 직접 읽습니다. 완주율 타일만 예외로, 감시 타이머가 남기는 상태 파일을 읽습니다.
pipeline_runs: 실행마다 한 행 · 신규 · 중복 · 에러 · 소요 시간 · 소스별 건수articles: 등급 · 이적 단계 · 발행 시각source_freshness: 실행 × 소스의 마지막 수집 시각과 임계unique · not_null 테스트 결과 (SLO-3 과 SLO-4 의 값)state/completion.json): 매시 감시 타이머가 Airflow 실행 목록과 이전 systemd 실행 기록에서 센 완주 · 시작 수 (「완주율」 타일의 값)화면 맨 위의 「데이터 원천」 절이 앞의 네 가지를 밝히고 있어 열어서 대조할 수 있습니다 (완주율 상태 파일은 그 절에 없습니다).
데이터 계약을 코드로 선언해 두고, 통과하지 못하면 배포를 중단합니다.
gate 태스크가 dbt build 로 스테이징 5개와 Gold 3개를 생성하고 테스트 22종을 돌립니다.
unique 5 · not_null 10 · accepted_values 5 · relationships 2deploy_site 가 실행되지 않고 알림이 발송됩니다.LLM 이 만든 산출물을 LLM 없이 규칙 코드로 검사합니다. 경로는 둘이고, 경로마다 검사 항목과 다시 생성하는 방식이 다릅니다.
| 경로 | 대상 | 검사 | 걸렸을 때 |
|---|---|---|---|
| 번역 | 영문 원문을 번역하는 기사 | 번역 제목에 들어간, 원문에 없는 인명 · 제목 오역 (인명 누락 · 근거 없는 「임대」) · 라운드업 단신 누락 · 원문에 없는 구단명 | 다음 실행에서 사유를 프롬프트에 붙여 한 번 다시 번역합니다. 그래도 걸리면 번역 결과를 채택하고 경고를 남깁니다. |
| 게시글 재작성 | 이미 한국어인 커뮤니티 게시글 본문 | 숫자 누락 · 수치 주입 · 인용 보존 · 구단명 주입 · 인명 주입 · 원문 복제 (글자 8-gram 잔존율이 0.75 초과) | 같은 실행 안에서 사유를 붙여 최대 두 번 다시 생성하고, 세 시도 중 위반이 가장 적은 것을 채택합니다. 위반이 남으면 다음 실행에서 한 번 더 생성합니다. |
게이트는 재생성 트리거이지 폐기 조건이 아니라서 본문을 버리지 않습니다. 재작성 잔존율이 임계를 넘은 채로 남은 기사는 수집 현황 대시보드의 「확인 대상」 표에 올라 사람이 검토합니다.
| 영역 | 선택 | 이유 |
|---|---|---|
| 서빙 mart | MariaDB | 일 수십 건 규모의 서빙 (포인트 조회 · 필터 · UNIQUE dedup) 에는 OLTP 가 적합 |
| 원본 랜딩 | MongoDB | 구조가 제각각인 원문을 손실 없이 schema-on-read 로 보존해 언제든 재처리 가능 |
| 품질 · 분석 | dbt + DuckDB | dbt test 가 데이터 계약 검증과 그대로 대응 · DuckDB 가 MariaDB 를 attach 해 별도 인프라 없이 집계와 테스트 수행 |
| 레이크하우스 | Apache Iceberg on GCS + Google Lakehouse runtime catalog | 변경 이력 · 스냅샷 · 행동 로그처럼 append 위주 데이터를 서빙 DB 외부에 적재 · PyIceberg 로 직접 쓰고 카탈로그만 매니지드 서비스를 사용해 운영할 서버가 없음 (§8) |
| 스크래핑 | Playwright / httpx | 소스 난이도 (정적 · 쿠키 인증 · 안티봇) 에 맞춰 도구를 선택 · X 는 쿠키 주입 Playwright |
| 스케줄 · 배포 | Airflow 3 (LocalExecutor) + wrangler | 실행을 태스크 8개로 분리해 3시간마다 수행 · 실패한 태스크만 식별되고 판정 태스크가 배포를 롤백 (§8) · 실행 종료 시 Pages 직접 업로드 |
| LLM 번역 · 요약 | Gemini 3.1 Flash-Lite | 새 기사 하루 15 ~ 17건 규모 (이적 기간) · 단순 번역에 맞는 단가 · response_mime_type 으로 JSON 출력 강제. 유료 (Tier 1 선불) 이며 월 요금은 GCP 결제 보고서에서 조회 (문서에 금액을 고정하면 값이 낡음) |
선택한 것보다 선택하지 않은 것과 그에 따라 감수한 것을 기록합니다.
CDC 를 사용하지 않았습니다
content_hash 비교 + revision 증가) 를 사용했습니다.articles_changes 에 적재합니다.SLO-1 목표를 70% 에서 55% 로 하향했습니다
매니지드 Iceberg 테이블을 사용하지 않았습니다
BigLake Table Management 는 관리 작업이 쓴 연산량에 시간당 165.99 KRW 를 과금하고 무료 구간이 없습니다. 한 달 내내 돈다고 가정하면 12만원이 넘습니다 (레이크하우스 설계 §2).품질 게이트와 웨어하우스 적재를 분리했습니다
게이트 실패 시 배포를 차단하는 쪽으로 설계했습니다 (fail-closed)
메달리온을 2개 운영합니다. 기사와 행동 로그는 원천 시스템도 저장소도 달라 층을 따로 구성했으며, 둘 다 같은 DAG 가 채웁니다.
메달리온 1 · 기사
Bronze · MongoDB
- raw_items · 소스 원문을 변형 없이 보존
- 신선도 판정의 워터마크가 이 층에서 나옵니다
|
v 정규화 · content_hash 계산 · 공신력 tier 산출 · UNIQUE 중복 제거
Silver · MariaDB
- 테이블 6개 · 서비스 화면과 수집 현황 대시보드가 직접 조회합니다
- articles · sources · players · article_players
- pipeline_runs · source_freshness
|
v dbt build (실행의 마지막 단계)
Gold · dbt + DuckDB
- 집계 모델 3개 · 테스트 22종이 배포를 막는 품질 게이트
Silver 는 mart_history (Iceberg) 로도 흘러갑니다 · 아래 레이크하우스 절
메달리온 2 · 행동 로그 Iceberg on GCS 의 behavior 네임스페이스
Bronze · ga4_events
- BigQuery 일별 내보내기 · 중첩 · 반복 필드를 그대로 적재
|
v Flatten (ARRAY · STRUCT 해제) · 중복 이벤트 제거
Silver · ga4_events_flat
- 1행 1이벤트 · 1열 1값
|
v 전량 재구축 (신규 · 재방문은 전체 이력이 필요한 비가산 지표)
Gold
- 팩트 3개 · fact_card_click · fact_session · fact_user_daily
- 디멘션 2개 · dim_date · dim_user
층 이름은 실제 데이터가 존재하는 자리에만 붙였습니다.
raw_items): 원문을 변형 없이 보존합니다.
신선도 판정의 워터마크가 이 층에서 산출됩니다.articles: 정규화 메타 + tier + confidence + 번역 · 요약 · content_hash 와 url 에 UNIQUE 로 dedupsources · players: 소스와 선수 명단article_players: 기사 × 선수 매핑 (주체 · 언급)pipeline_runs: 실행별 SLO 근거source_freshness: 실행 × 소스 신선도 이력models/gold/ 모델 3개): gold_daily_source_quality · gold_slo_rollup · gold_tier_distribution.
실행 종료 시 dbt build 가 갱신하며 같은 실행의 테스트 22종이 품질 게이트로 동작합니다.models/staging/ 의 모델 5개는 MariaDB 테이블을 그대로 읽어 오는 경유 뷰라 층 이름을 붙이지 않았습니다.
레이크하우스 (Iceberg on GCS): 서빙 DB 외부에 적재하는 네임스페이스 2개입니다.
mart_history: 서빙 DB 가 덮어쓰면 사라질 값을 남깁니다.
articles_changes: 실행별 변경분articles_snapshot · players_snapshot · article_players_snapshot: 90일까지 매일 · 이후 주 1회ops_daily: 일별 운영 지표behavior: GA4 이벤트 (BigQuery 일별 내보내기 · §5) 를 층으로 나눠 적재합니다.
ga4_events · Silver ga4_events_flatfact_card_click · fact_session · fact_user_dailydim_date · dim_useruser_pseudo_id 만 씁니다 (사용자 키를 혼용하면 방문자가 두 배로 집계된다).| 경로 | 내용 |
|---|---|
src/bullet_in/run.py · pipeline.py | 실행 1회의 진입점과 항목 판정 (여성 축구 제외 · 본문 등급 · 기자 선택) |
src/bullet_in/adapters/ | 소스별 수집기 · 설정이 쓰는 5종 (arsenal_api · rss · html · x_playwright · fmkorea) · 코드만 있고 쓰는 소스가 없는 2종 (playwright_news · guardian_api) |
src/bullet_in/ingest.py · canonical.py · dedup.py | 병렬 수집 · URL 정본화와 content_hash · 신규 · 변경 · 중복 분류 |
src/bullet_in/credibility.py · score.py | 기자 · 매체 레지스트리 조회와 tier · confidence 산출 |
src/bullet_in/enrich.py · fidelity.py | LLM 번역 · 요약과 번역 품질 게이트 (§6) |
src/bullet_in/roster.py · transfer_stage.py | 선수 명단과 이적 단계 |
src/bullet_in/storage/ | MongoDB (Bronze) · MariaDB (Silver) 접근과 schema.sql |
src/bullet_in/serve/ | 정적 HTML 렌더 · 대시보드 2종의 뷰 · 차트 |
src/bullet_in/dbt_gate.py · deploy.py · warehouse.py | 품질 게이트 · 배포 판정과 롤백 · Iceberg 적재 |
src/bullet_in/quality.py · metrics.py · notify.py | 수집량 이상 감지 · SLO 측정 · Discord 알림 |
airflow/dags/ | DAG 정의와 태스크 스크립트 |
dbt/models/ | staging 5개 · gold 3개와 테스트 22종 |
config/ | 수집 소스 · 공신력 레지스트리 · 표기 사전 등 YAML 5개 |
tests/ | 단위 테스트와 integration/ (MariaDB 컨테이너가 필요합니다) |
infra/ | systemd 유닛 · 백업 · 배포 스크립트 |
docs/ | 설계 · 계획 · 런북 · 트러블슈팅 (§12) |
# 0. 환경
cp .env.example .env # 값 채우기 (Mongo · MariaDB · Gemini · X)
uv sync --extra dev
uv run playwright install chromium
# 1. 데이터 스토어
docker compose up -d # mongo, mariadb
# 2. 파이프라인 1회 실행 (dotenv 미사용 → 셸 export 필요)
set -a; source .env; set +a
uv run python -m bullet_in.run --concurrency 8 # collect → enrich → publish → gate 를 한 프로세스로
# 3. 결과 확인
open site/index.html # 기사 · 선수 · 대시보드 2종 (site/behavior.html · site/ops.html)
테스트는 uv run pytest -q 로 실행합니다 (통합 테스트는 MariaDB 컨테이너가 없으면 skip 됩니다).
Airflow DAG 임포트는 별도 venv 에서 검증합니다 (docs/MIGRATION.md).
운영 VM 의 실행 · 수동 트리거 · 롤백 절차는 Airflow 런북 에 있습니다.
설계 (docs/superpowers/specs/ 75편) · 계획 (docs/superpowers/plans/ 64편) · 런북 (docs/runbook/ 89편) · 트러블슈팅 (docs/troubleshooting/ 189편 · 폴더 안내문 README.md 제외) 이 있습니다 (2026-10-07 기준).
아래 5편을 먼저 읽는 것을 권장합니다.
트러블슈팅 문서는 「무엇이 틀렸는가」 보다 「해당 결함을 어떤 검증 수단이 놓쳤는가」 에 무게를 둡니다. 같은 검증 공백이 다른 곳에서 반복되기 때문입니다.
이 저장소는 한 사람이 Claude Code 와 함께 개발하고 운영합니다. 어디까지 AI 에 맡기고 어디부터 사람이 결정하는지, AI 가 만든 오류를 어떻게 걸러냈는지를 적었습니다.
| 순서 | 단계 | 주체 |
|---|---|---|
| 1 | brainstorming | 사람이 승인 |
| 2 | 스펙 | AI 초안 · 사람이 승인 |
| 3 | 계획서 (dry run) | AI 초안 · 사람이 승인 |
| 4 | Task 단위 구현 (subagent · TDD) | AI |
| 5 | spec 리뷰 · code 리뷰 | 리뷰 모델 |
| 6 | 실측 검증 | 사람 · 리뷰 모델 |
| 7 | PR 작성 | AI |
| 8 | Merge | 사람 |
| 9 | 배포 | 다음 파이프라인 실행이 수행 |
superpowers 스킬 묶음으로 고정합니다.
brainstorming: 아직 정해지지 않은 사항을 먼저 확인합니다.writing-plans: 스펙과 계획서를 문서로 만듭니다.subagent-driven-development: 계획서를 Task 단위로 구현합니다.verification-before-completion: 실행 결과를 근거로만 완료를 선언하도록 강제합니다.CLAUDE.md 에 병합했고 모든 코드 태스크에 동일하게 적용합니다.| 주체 | 맡은 일 |
|---|---|
| 사람 | 요구사항과 지표를 정의하고, 스펙 · 계획서 · 화면 목업을 승인하거나 반려합니다. 실측값을 화면 · DB · 로그로 대조하고, LLM 호출과 운영 데이터 정정을 승인합니다. 리뷰 피드백의 채택과 기각, 최종 전체 리뷰를 담당합니다. PR Merge 527건 (2026-10-07 기준) 은 전부 사람이 수행했습니다. |
| AI | 스펙과 계획서 초안, 코드와 테스트 (버그는 재현 테스트부터 작성), 실측 스크립트, 실데이터 기반 대시보드 목업, 문서 초안, 리뷰를 담당합니다. |
| AI 에 부여하지 않은 권한 | Merge 권한, 운영 서버에서의 git pull, 승인 없는 운영 데이터 정정입니다. 배포는 파이프라인의 첫 태스크가 origin/main 을 내려받는 방식이므로 사람도 서버에서 직접 pull 하지 않습니다. |
반려 예시: 잘린 트윗의 뒷부분을 프롬프트로 보완하자는 제안은 기각하고 수집기를 수정해 전문을 받도록 했습니다.
사람이 매번 확인하지 않아도 자동으로 걸러지도록 만든 장치입니다.
.claude/hooks/check-doc-format.py 가 커밋 · PR 컨벤션 §2.2 를 검사하고, CI 의 docs-format 잡이 변경된 docs/*.md 에 같은 검사를 한 번 더 돌립니다..claude/tools/check-pr-format.py 를 통과시킵니다.
동일한 위반이 5회 재발한 뒤에 만든 도구라 파일 상단에 그 이력을 기록해 두었습니다.동일한 실패가 반복되면 트러블슈팅으로 기록하고, 재발 방지 규칙을 CLAUDE.md 나 런북으로 옮기고, 검사 장치가 그 규칙을 받아 강화됩니다.
이 순환이 실제로 동작한 사례 4건입니다.
나머지는 docs/troubleshooting/ 에서 subagent · session · plan · prompt 로 검색하면 됩니다.
fact_user_daily · 08-29 ~ 09-04).
구독이나 알림 기능은 아직 없습니다..env 로 분리해 커밋하지 않습니다.
개인 학습 용도입니다.config/sources.yaml 의 serving: full · 지금 수집하는 소스는 8종).
번역 전문은 출처 표기 · 링크 · 비영리만으로 면책되지 않는다는 위험을 알고 받아들인 결정이며, 소스 단위로 요약과 앞부분 발췌만 싣도록 되돌리는 설정 (serving: excerpt) 이 준비돼 있습니다 (결정 기록).LLM 429 응답에 행 단위 백오프를 두지 않았습니다
systemd 타이머에서 Airflow 로 마이그레이션했습니다 (2026-09-04)