Skip to content

Commit 9bc175c

Browse files
committed
Improve stable source quality summaries
1 parent f9fa729 commit 9bc175c

9 files changed

Lines changed: 119 additions & 17 deletions

File tree

.github/workflows/rss_source_pipeline.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ jobs:
8888
python scripts/write_live_manifest.py \
8989
--base-dir . \
9090
--output data/live/source_manifest.json \
91+
data/live/source_fetch_status.json \
9192
data/live/source_items.csv \
9293
data/live/source_events.csv \
9394
data/live/political_events.csv \

.github/workflows/source_event_pipeline.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,19 @@ jobs:
7171
cp data/output/source_event_pipeline/source_events.csv data/live/source_events.csv
7272
cp data/output/source_event_pipeline/source_events.csv data/live/political_events.csv
7373
cp data/output/source_event_pipeline/source_tracker.csv data/live/source_tracker.csv
74+
MANIFEST_PATHS=(
75+
data/live/source_items.csv
76+
data/live/source_events.csv
77+
data/live/political_events.csv
78+
data/live/source_tracker.csv
79+
)
80+
if [ -f data/live/source_fetch_status.json ]; then
81+
MANIFEST_PATHS+=(data/live/source_fetch_status.json)
82+
fi
7483
python scripts/write_live_manifest.py \
7584
--base-dir . \
7685
--output data/live/source_manifest.json \
77-
data/live/source_items.csv \
78-
data/live/source_events.csv \
79-
data/live/political_events.csv \
80-
data/live/source_tracker.csv
86+
"${MANIFEST_PATHS[@]}"
8187
git config user.name "github-actions[bot]"
8288
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
8389
git add data/live/source_events.csv data/live/political_events.csv data/live/source_tracker.csv data/live/source_manifest.json

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,14 @@ The price loader also accepts `as_of` instead of `date`.
144144

145145
## Live Pipeline Notes
146146

147-
The RSS fetcher records per-feed health in `data/live/source_fetch_status.json` and keeps a small hash/row-count manifest in `data/live/source_manifest.json`. A single blocked or unavailable feed should not stop the rest of the official-source refresh.
147+
The RSS fetcher records per-feed health in `data/live/source_fetch_status.json` and keeps a small hash/row-count plus data-quality manifest in `data/live/source_manifest.json`. The manifest summarizes feed health, covered symbols, confidence counts, and event-type counts. A single blocked or unavailable feed should not stop the rest of the official-source refresh.
148148

149149
If the RSS workflow downloads articles but `source_events.csv` is empty, the
150150
usual cause is deterministic alias coverage rather than a fetch failure: broad
151-
policy items often mention sectors such as grid infrastructure or crypto assets
152-
instead of company names. Alias updates should remain targeted, documented, and
153-
reviewable to avoid turning every broad policy article into a false positive.
151+
policy items often mention sectors such as grid infrastructure, HBM, foundry,
152+
AI servers, or crypto assets instead of company names. Alias updates should
153+
remain targeted, documented, and reviewable to avoid turning every broad policy
154+
article into a false positive.
154155

155156
Local macOS Python installations can also fail HTTPS RSS fetches with a local CA
156157
certificate error. GitHub-hosted runners use a normal CA bundle; local operators

README.zh-CN.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,9 @@ python -m pytest -q
112112

113113
## Live pipeline 说明
114114

115-
RSS fetcher 会把每个源的成功/失败写入 `data/live/source_fetch_status.json`,并在 `data/live/source_manifest.json` 保留 live CSV 的 hash 和行数。单个源被屏蔽或临时不可用时,不应阻断其他官方源刷新。
115+
RSS fetcher 会把每个源的成功/失败写入 `data/live/source_fetch_status.json`,并在 `data/live/source_manifest.json` 保留 live CSV 的 hash、行数和数据质量摘要。manifest 会记录 feed 健康度、覆盖 symbol、置信度分布和事件类型分布。单个源被屏蔽或临时不可用时,不应阻断其他官方源刷新。
116116

117-
如果 RSS workflow 能拉到 `source_items.csv`,但 `source_events.csv` 为空,通常不是新闻没跑,而是确定性 alias 覆盖不足:很多政策文件只写“grid infrastructure”“crypto assets”这类主题词,不直接写公司名。后续补 alias 要保持克制、可审计,避免把所有宽泛政策新闻都误映射成公司事件。
117+
如果 RSS workflow 能拉到 `source_items.csv`,但 `source_events.csv` 为空,通常不是新闻没跑,而是确定性 alias 覆盖不足:很多政策文件只写“grid infrastructure”“HBM”“foundry”“AI server”“crypto assets”这类主题词,不直接写公司名。后续补 alias 要保持克制、可审计,避免把所有宽泛政策新闻都误映射成公司事件。
118118

119119
本机 macOS Python 也可能因为本地 CA 证书问题导致 HTTPS RSS 拉取失败;GitHub-hosted runner 使用正常 CA bundle。本地排查时可以先修 Python 证书,也可以直接从 GitHub Actions 下载 `source_items.csv` artifact 后验证抽取链路。
120120

config/core_us_equity_aliases.csv

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
symbol,name,aliases
22
AAPL,Apple,Apple|AAPL|iPhone|iPad|Mac|Tim Cook
3-
AMD,Advanced Micro Devices,AMD|Advanced Micro Devices|Radeon|EPYC|Instinct
3+
AMD,Advanced Micro Devices,AMD|Advanced Micro Devices|Radeon|EPYC|Instinct|MI300|MI350
44
AMZN,Amazon,Amazon|AMZN|AWS|Amazon Web Services|Project Kuiper
55
AVGO,Broadcom,Broadcom|AVGO|VMware
66
BA,Boeing,Boeing|BA|737 MAX|Starliner
77
COIN,Coinbase,Coinbase|COIN|crypto exchange|crypto assets|digital assets|crypto asset transactions
88
CRWD,CrowdStrike,CrowdStrike|CRWD|Falcon cybersecurity
99
CVX,Chevron,Chevron|CVX|energy-related infrastructure|energy related infrastructure|large-scale energy|oil and gas
10-
DELL,Dell Technologies,Dell|Dell Technologies|DELL|AI server|PowerEdge
10+
DELL,Dell Technologies,Dell|Dell Technologies|DELL|AI server|PowerEdge|PowerScale|PowerStore|enterprise server|data center infrastructure
1111
ENPH,Enphase Energy,Enphase|ENPH|solar inverter
1212
F,Ford,Ford|F|F-150|Mustang Mach-E
1313
GE,GE Aerospace,GE Aerospace|General Electric|GE|jet engine
1414
GME,GameStop,GameStop|GME
1515
GOOGL,Alphabet,Alphabet|Google|GOOGL|GOOG|Gemini|Waymo
16-
INTC,Intel,Intel|INTC|foundry|CHIPS Act
16+
INTC,Intel,Intel|INTC|foundry|Intel Foundry|CHIPS Act|semiconductor manufacturing|advanced packaging|Gaudi
1717
IONQ,IonQ,IonQ|IONQ|quantum computing
1818
JPM,JPMorgan Chase,JPMorgan|JPMorgan Chase|JPM|Jamie Dimon
1919
LMT,Lockheed Martin,Lockheed Martin|LMT|F-35|missile defense
2020
META,Meta Platforms,Meta|META|Facebook|Instagram|WhatsApp|Llama
2121
MSFT,Microsoft,Microsoft|MSFT|Azure|OpenAI|Copilot
2222
MSTR,Strategy,Strategy|MicroStrategy|MSTR|Michael Saylor|Bitcoin treasury|crypto assets|Bitcoin
23-
MU,Micron Technology,Micron|MU|HBM|DRAM|NAND
23+
MU,Micron Technology,Micron|Micron Technology|MU|HBM|High Bandwidth Memory|DRAM|NAND|memory chip
2424
NEE,NextEra Energy,NextEra|NEE|renewable energy|grid infrastructure|energy-related infrastructure|energy related infrastructure|large-scale energy|renewable power
2525
NOC,Northrop Grumman,Northrop Grumman|NOC|B-21
2626
NVDA,NVIDIA,NVIDIA|Nvidia|NVDA|Blackwell|CUDA|GPU
@@ -37,5 +37,5 @@ SOFI,SoFi Technologies,SoFi|SOFI
3737
TSLA,Tesla,Tesla|TSLA|Elon Musk|Cybertruck|FSD|Optimus|xAI
3838
TSM,Taiwan Semiconductor,TSMC|Taiwan Semiconductor|TSM|foundry
3939
UNH,UnitedHealth Group,UnitedHealth|UNH|UnitedHealthcare
40-
VRT,Vertiv,Vertiv|VRT|data center power|liquid cooling|grid infrastructure|energy-related infrastructure|energy related infrastructure|large-scale energy|data center energy
40+
VRT,Vertiv,Vertiv|Vertiv Holdings|VRT|data center power|liquid cooling|grid infrastructure|energy-related infrastructure|energy related infrastructure|large-scale energy|data center energy|critical digital infrastructure|thermal management
4141
XOM,Exxon Mobil,Exxon|ExxonMobil|XOM|energy-related infrastructure|energy related infrastructure|large-scale energy|oil and gas

docs/source_ingestion_options.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ not part of this release. Reasons:
3636
- `scripts/fetch_rss_sources.py` fetches RSS/Atom feeds into `source_items.csv`.
3737
- `scripts/extract_source_mentions.py` converts `source_items.csv` into normalized event rows by deterministic alias matching.
3838
- `scripts/import_source_events.py` remains available when upstream records are already normalized.
39+
- `scripts/write_live_manifest.py` records hash, row-count, feed-health, covered-symbol, confidence, and event-type summaries for live artifacts.
3940

4041
## Operational Caveats
4142

docs/source_registry.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@ The stable release intentionally excludes X / Twitter, Truth Social, and
4444
Longbridge community/profile/following-list ingestion. They can be revisited
4545
later only if a stable official interface and clear operating policy exist.
4646

47+
## Current Configured RSS Feeds
48+
49+
`config/free_rss_feeds.csv` currently keeps the stable default set small:
50+
51+
- White House presidential actions
52+
- SEC press releases
53+
- SEC speeches and statements
54+
- Federal Reserve press releases
55+
- Federal Reserve speeches and testimony
56+
57+
`data/live/source_manifest.json` records source row counts, feed health, covered
58+
symbols, confidence counts, and event-type counts for every published live run.
59+
60+
## Alias Coverage Notes
61+
62+
`config/core_us_equity_aliases.csv` includes targeted aliases for the current AI
63+
and data-center names that often appear through products or infrastructure terms,
64+
including MU, INTC, AMD, DELL, and VRT. Alias updates should prefer company names,
65+
product names, or clear infrastructure phrases over very broad hype terms.
66+
4767
## Confidence Guide
4868

4969
- `high`: official government source or direct filing with stable URL.

src/political_event_tracking_research/artifacts.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,57 @@ def csv_row_count(path: str | Path) -> int:
2121
return sum(1 for _ in csv.DictReader(handle))
2222

2323

24+
def summarize_source_events(path: str | Path) -> dict[str, Any]:
25+
symbols: set[str] = set()
26+
confidence_counts: dict[str, int] = {}
27+
event_type_counts: dict[str, int] = {}
28+
with Path(path).open(newline="", encoding="utf-8") as handle:
29+
for row in csv.DictReader(handle):
30+
symbol = row.get("symbol", "").strip().upper()
31+
if symbol:
32+
symbols.add(symbol)
33+
confidence = row.get("confidence", "").strip() or "unknown"
34+
confidence_counts[confidence] = confidence_counts.get(confidence, 0) + 1
35+
event_type = row.get("event_type", "").strip() or "unknown"
36+
event_type_counts[event_type] = event_type_counts.get(event_type, 0) + 1
37+
return {
38+
"symbol_count": len(symbols),
39+
"symbols": sorted(symbols),
40+
"confidence_counts": dict(sorted(confidence_counts.items())),
41+
"event_type_counts": dict(sorted(event_type_counts.items())),
42+
}
43+
44+
45+
def summarize_feed_status(path: str | Path) -> dict[str, Any]:
46+
payload = json.loads(Path(path).read_text(encoding="utf-8"))
47+
feed_count = int(payload.get("feed_count") or 0)
48+
successful_feed_count = int(payload.get("successful_feed_count") or 0)
49+
failed_feed_count = int(payload.get("failed_feed_count") or 0)
50+
return {
51+
"feed_count": feed_count,
52+
"successful_feed_count": successful_feed_count,
53+
"failed_feed_count": failed_feed_count,
54+
"feed_success_ratio": round(successful_feed_count / feed_count, 4) if feed_count else None,
55+
}
56+
57+
58+
def build_data_quality_summary(paths: list[Path]) -> dict[str, Any]:
59+
by_name = {path.name: path for path in paths}
60+
summary: dict[str, Any] = {}
61+
source_events_path = by_name.get("source_events.csv") or by_name.get("political_events.csv")
62+
if source_events_path and source_events_path.exists():
63+
summary["source_events"] = summarize_source_events(source_events_path)
64+
feed_status_path = by_name.get("source_fetch_status.json")
65+
if feed_status_path and feed_status_path.exists():
66+
summary["feed_status"] = summarize_feed_status(feed_status_path)
67+
return summary
68+
69+
2470
def build_live_manifest(paths: list[str | Path], *, base_dir: str | Path | None = None) -> dict[str, Any]:
2571
base = Path(base_dir).resolve() if base_dir else None
2672
artifacts: dict[str, Any] = {}
27-
for raw_path in paths:
28-
path = Path(raw_path)
73+
resolved_paths = [Path(raw_path) for raw_path in paths]
74+
for path in resolved_paths:
2975
key = path.name
3076
display_path = str(path)
3177
if base:
@@ -43,6 +89,7 @@ def build_live_manifest(paths: list[str | Path], *, base_dir: str | Path | None
4389
"generated_at": datetime.now(timezone.utc).isoformat(),
4490
"artifact_count": len(artifacts),
4591
"artifacts": artifacts,
92+
"data_quality": build_data_quality_summary(resolved_paths),
4693
}
4794

4895

tests/test_artifacts.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,29 @@ def test_build_live_manifest_records_hash_and_row_count(tmp_path: Path) -> None:
1616
assert item["path"] == "source_events.csv"
1717
assert item["row_count"] == 2
1818
assert len(item["sha256"]) == 64
19+
20+
21+
def test_build_live_manifest_includes_source_quality_summary(tmp_path: Path) -> None:
22+
csv_path = tmp_path / "source_events.csv"
23+
csv_path.write_text(
24+
"\n".join(
25+
[
26+
"event_id,event_date,symbol,event_type,direction,confidence,source_url,notes",
27+
"e1,2026-05-30,MU,public_mention,neutral,high,https://example.invalid/mu,MU mention",
28+
"e2,2026-05-30,VRT,policy_capital,bullish,medium,https://example.invalid/vrt,VRT policy",
29+
]
30+
)
31+
+ "\n",
32+
encoding="utf-8",
33+
)
34+
status_path = tmp_path / "source_fetch_status.json"
35+
status_path.write_text(
36+
'{"feed_count": 2, "successful_feed_count": 1, "failed_feed_count": 1, "feeds": []}\n',
37+
encoding="utf-8",
38+
)
39+
40+
manifest = build_live_manifest([csv_path, status_path], base_dir=tmp_path)
41+
42+
assert manifest["data_quality"]["source_events"]["symbols"] == ["MU", "VRT"]
43+
assert manifest["data_quality"]["source_events"]["confidence_counts"] == {"high": 1, "medium": 1}
44+
assert manifest["data_quality"]["feed_status"]["feed_success_ratio"] == 0.5

0 commit comments

Comments
 (0)