# Phase 2 저장 구조 및 데이터 계약 ## 1. 목적 이 문서는 Phase 2 수집 파이프라인이 쓰는 **파일 경로 규칙**, **raw sidecar 형식**, **staging canonical schema**, **structured write 규칙**을 정의합니다. 핵심 목표는 아래와 같습니다. 1. 모든 raw 파일의 출처와 실행(run)을 추적할 수 있어야 한다. 2. 모든 staging/structured 레코드가 원문(raw)로 역추적 가능해야 한다. 3. 파일명/경로만 보고 source, date, object type을 알 수 있어야 한다. 4. 재실행 시 같은 경로/키 체계를 사용해야 한다. ## 2. Raw 저장 규칙 ### 2.1 공통 경로 형식 ```text data/raw/{source}/{ingestion_date}/{object_scope}/... ``` 예시: ```text data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/submissions.json data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/filing.txt data/raw/sec/2026-03-12/cik_0000789019/0001193125-26-027198/exhibit_99_1.html data/raw/alpaca/2026-03-12/daily/AAPL.json data/raw/fred/2026-03-12/series/DGS10.json data/raw/finra/2026-03-12/short_volume/2026-03-11.txt ``` ### 2.2 Sidecar 메타데이터 각 raw object마다 `.meta.json` sidecar를 생성합니다. 예: ```text submissions.json submissions.json.meta.json ``` ### 2.3 Sidecar 필수 필드 ```json { "run_id": "2026-03-12T21:05:14Z_sec_submissions_poll_001", "job_name": "sec_submissions_poll", "source": "sec", "source_url": "https://...", "http_status": 200, "fetched_at": "2026-03-12T21:05:14Z", "content_type": "application/json", "payload_bytes": 18293, "sha256": "...", "mode": "poll", "source_identifier": { "cik": "0000789019", "accession": "0001193125-26-027198" }, "parser_hint": { "object_type": "sec_submissions" } } ``` ### 2.4 Raw 쓰기 원칙 - 임시 파일에 먼저 씁니다. - checksum 계산 후 원자적 rename을 합니다. - sidecar까지 성공해야 raw 저장 성공으로 간주합니다. - sidecar 저장 전에는 `job_runs.status = raw_saved`로 올리지 않습니다. ## 3. Staging 저장 규칙 staging은 파싱/정규화의 중간 결과입니다. 경로 형식: ```text data/staging/{source}/{logical_date}/{object_type}/{entity_key}.jsonl ``` 예시: ```text data/staging/sec/2026-03-12/filing_headers/cik_0000789019.jsonl data/staging/sec/2026-03-12/exhibit_inventory/0001193125-26-027198.jsonl data/staging/alpaca/2026-03-12/daily_bars/AAPL.jsonl data/staging/fred/2026-03-12/series/DGS10.jsonl data/staging/finra/2026-03-12/short_volume/2026-03-11.jsonl ``` 원칙: - staging은 재생성 가능 데이터입니다. - raw가 진실의 원천이고, staging은 파생 데이터입니다. - staging은 overwrite 가능하지만 lineage가 남아야 합니다. ## 4. Canonical Record 계약 ### 4.1 SEC Document Header Record ```json { "document_id": "sec:0001193125-26-027198", "source": "sec", "cik": "0000789019", "accession": "0001193125-26-027198", "form_type": "8-K", "filing_date": "2026-01-28", "acceptance_datetime": "2026-01-28T16:13:02Z", "primary_document": "msft-8k.htm", "has_xbrl": true, "raw_path": "data/raw/.../filing.txt", "raw_sha256": "...", "run_id": "..." } ``` ### 4.2 SEC Exhibit Record ```json { "artifact_id": "sec_artifact:0001193125-26-027198:ex99_1", "document_id": "sec:0001193125-26-027198", "artifact_name": "ex99_1.htm", "artifact_type": "exhibit_99_1", "mime_type": "text/html", "raw_path": "data/raw/.../exhibit_99_1.html", "raw_sha256": "...", "run_id": "..." } ``` ### 4.3 Alpaca Canonical Bar Record ```json { "source": "alpaca", "symbol": "AAPL", "timeframe": "1D", "ts": "2026-03-11T21:00:00Z", "trading_date": "2026-03-11", "open": 212.31, "high": 214.05, "low": 211.62, "close": 213.98, "volume": 48761234, "trade_count": 302119, "vwap": 213.12, "raw_path": "data/raw/.../AAPL.json", "raw_sha256": "...", "run_id": "..." } ``` ### 4.4 FRED Observation Record ```json { "source": "fred", "series_id": "DGS10", "observation_date": "2026-03-11", "value": 4.13, "frequency": "Daily", "units": "Percent", "raw_path": "data/raw/.../DGS10.json", "raw_sha256": "...", "run_id": "..." } ``` ### 4.5 FINRA Short Volume Record ```json { "source": "finra", "trade_date": "2026-03-11", "symbol": "AAPL", "short_volume": 14122112, "total_volume": 42211884, "short_volume_ratio": 0.3345, "raw_path": "data/raw/.../2026-03-11.txt", "raw_sha256": "...", "run_id": "..." } ``` ## 5. Structured Write 규칙 ### 5.1 PostgreSQL 운영 상태/메타데이터는 PostgreSQL에 저장합니다. 대표 테이블: - `job_runs` - `source_checkpoints` - `raw_objects` - `documents` - `document_artifacts` - `xbrl_facts` - `macro_series_observations` - `short_sale_volume_daily` 규칙: - 운영 테이블은 upsert 기반 - natural key 또는 idempotency key를 반드시 둠 - `created_at`, `updated_at`, `run_id` 필수 ### 5.2 Parquet 대량 시계열/분석용 데이터는 Parquet로 적재합니다. 경로 예시: ```text data/parquet/market_bars_daily/trading_date=2026-03-11/part-000.parquet data/parquet/market_bars_intraday/trading_date=2026-03-11/symbol=AAPL/part-000.parquet data/parquet/fred/series_id=DGS10/part-000.parquet data/parquet/finra_short_volume/trade_date=2026-03-11/part-000.parquet ``` 규칙: - partition overwrite는 날짜/심볼 범위 단위로 제한 - late-arriving data는 해당 partition 재작성 - write manifest로 어떤 partition이 언제 갱신됐는지 기록 ## 6. Lineage 계약 모든 staging/structured 레코드는 최소 아래 필드를 가져야 합니다. - `source` - `run_id` - `raw_path` - `raw_sha256` - `ingested_at` 이 5개가 없으면 downstream에서 사용 금지입니다. ## 7. 상태 필드 규칙 `job_runs.status`는 아래 enum만 허용합니다. - `created` - `running` - `raw_saved` - `staged` - `structured_written` - `validated` - `completed` - `failed_retriable` - `failed_terminal` - `quarantined` ## 8. 중복 방지 중복 방지는 아래 3단계로 합니다. 1. raw checksum 중복 검사 2. staging natural key 중복 검사 3. structured upsert key 검사 어느 단계에서도 중복이 발생하면 경고를 남기고 동일성 비교 후 no-op 처리합니다. ## 9. 운영자가 빠르게 확인해야 할 경로 최소 아래 경로는 사람이 쉽게 찾을 수 있어야 합니다. - 최근 실패 잡 로그 - quarantine raw - 특정 accession 관련 raw/staging/structured lineage - 특정 심볼/거래일 bars 파일 - 특정 FRED series 최근 observation ## 10. 완료 기준 - 임의의 structured row에서 raw_path를 따라가면 원문을 확인할 수 있어야 합니다. - raw object checksum이 바뀌면 새 버전 또는 경고가 생성되어야 합니다. - 동일 run을 다시 실행해도 동일 natural key에 대해 중복 row가 생기지 않아야 합니다.