Skip to content

Commit dcda795

Browse files
Pigbibicodex
andcommitted
fix: tighten entity evidence remediation
Co-Authored-By: Codex <noreply@openai.com>
1 parent 7e984c0 commit dcda795

6 files changed

Lines changed: 65 additions & 8 deletions

File tree

src/political_event_tracking_research/official_event_import.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ def validate_record(record: OfficialRecord) -> None:
9797
raise ValueError(f"{record.record_id}: unsupported entity_match_type {record.entity_match_type!r}")
9898
if record.relationship_type not in {"issuer", "direct_beneficiary", "industry_context", "unverified"}:
9999
raise ValueError(f"{record.record_id}: unsupported relationship_type {record.relationship_type!r}")
100+
if record.entity_match_type != record.relationship_type:
101+
raise ValueError(f"{record.record_id}: entity_match_type and relationship_type must agree")
100102
if record.event_type not in ALLOWED_EVENT_TYPES:
101103
raise ValueError(f"{record.record_id}: unsupported event_type {record.event_type!r}")
102104
if record.source_type in GOVERNMENT_SOURCE_TYPES:

src/political_event_tracking_research/source_mention_extract.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,23 @@
1010
from .official_event_import import OfficialRecord, normalize_records
1111

1212

13-
GENERIC_ENTITY_NAMES = frozenset({"strategy"})
13+
GENERIC_ALIAS_DENYLIST = frozenset(
14+
{
15+
"advanced nuclear",
16+
"bitcoin treasury",
17+
"chips act",
18+
"crypto assets",
19+
"cybersecurity",
20+
"digital assets",
21+
"energy-related infrastructure",
22+
"foundry",
23+
"nuclear energy",
24+
"nuclear reactor",
25+
"semiconductor manufacturing",
26+
"strategy",
27+
"tokenization",
28+
}
29+
)
1430

1531

1632
@dataclass(frozen=True)
@@ -99,12 +115,7 @@ def _is_generic_alias(alias_record: MentionAlias, alias: str) -> bool:
99115
normalized = normalize_match_text(alias).strip()
100116
if normalized.upper() == alias_record.symbol.upper():
101117
return False
102-
if normalized.casefold() == alias_record.name.casefold():
103-
return normalized.casefold() in GENERIC_ENTITY_NAMES
104-
if alias_record.name:
105-
return True
106-
# Lower-case phrases are intentionally treated as industry vocabulary, not entities.
107-
return normalized == normalized.lower() or len(normalized.split()) >= 3 and not normalized.istitle()
118+
return normalized.casefold() in GENERIC_ALIAS_DENYLIST
108119

109120

110121
def match_evidence(text: str, alias_record: MentionAlias) -> tuple[str, str] | None:

src/political_event_tracking_research/tracker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def latest_event(events: list[Event]) -> Event | None:
6767
def event_score(events: list[Event]) -> int:
6868
score = 0
6969
for event in events:
70-
if event.entity_match_type not in {"issuer", "direct_beneficiary"}:
70+
if event.relationship_type not in {"issuer", "direct_beneficiary"}:
7171
continue
7272
score += EVENT_WEIGHTS.get(event.event_type, 0)
7373
score += CONFIDENCE_WEIGHTS.get(event.confidence, 0)

tests/test_official_event_import.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,21 @@ def test_community_lead_records_are_not_in_stable_source_set() -> None:
7676

7777
with pytest.raises(ValueError, match="unsupported source_type"):
7878
normalize_records([record])
79+
80+
81+
def test_import_rejects_mismatched_entity_and_relationship_types() -> None:
82+
record = OfficialRecord(
83+
record_id="mismatched-relation",
84+
record_date="2026-01-10",
85+
symbol="BAD",
86+
source_type="issuer_release",
87+
event_type="public_mention",
88+
direction="neutral",
89+
source_url="https://example.com/release",
90+
summary="Mismatched evidence fields.",
91+
entity_match_type="issuer",
92+
relationship_type="industry_context",
93+
)
94+
95+
with pytest.raises(ValueError, match="must agree"):
96+
normalize_records([record])

tests/test_source_mention_extract.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,23 @@ def test_explicit_issuer_match_is_company_level(tmp_path: Path) -> None:
100100
assert rows[0]["match_evidence"] == "Coinbase"
101101

102102

103+
def test_proper_noun_alternate_alias_remains_company_level(tmp_path: Path) -> None:
104+
raw_items = tmp_path / "source_items.csv"
105+
raw_items.write_text(
106+
"item_id,published_at,source_type,source_url,author,text\n"
107+
"issuer-2,2026-04-01T00:00:00Z,issuer_release,https://example.com/release,Issuer,"
108+
'Former Brand announced a new product.\n',
109+
encoding="utf-8",
110+
)
111+
aliases = tmp_path / "aliases.csv"
112+
aliases.write_text("symbol,name,aliases\nTEST,Current Company,Current Company|Former Brand\n", encoding="utf-8")
113+
114+
rows = extract_source_records(raw_items, aliases, tmp_path / "events.csv")
115+
116+
assert rows[0]["entity_match_type"] == "issuer"
117+
assert rows[0]["match_evidence"] == "Former Brand"
118+
119+
103120
def test_explicit_beneficiary_relation_is_preserved(tmp_path: Path) -> None:
104121
raw_items = tmp_path / "source_items.csv"
105122
raw_items.write_text(

tests/test_tracker.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,12 @@ def test_tracker_does_not_score_industry_context_as_company_evidence() -> None:
4747
rows = build_tracker_rows([item], events)
4848

4949
assert rows[0]["priority_score"] == 0
50+
51+
52+
def test_tracker_uses_relationship_type_as_scoring_fact() -> None:
53+
item = WatchlistItem("AAA", "Alpha", "watchlist", "watchlist", "seed", "https://example.com")
54+
events = [Event("e1", date(2026, 1, 1), "AAA", "public_mention", "bullish", "high", "https://example.com", "", "industry_context", "cybersecurity", "issuer")]
55+
56+
rows = build_tracker_rows([item], events)
57+
58+
assert rows[0]["priority_score"] > 0

0 commit comments

Comments
 (0)