Files
Celes Renata a72f336ad1 feat: Intelligence Pipeline v3 — full implementation
Multi-stage evidence-grounded inference architecture replacing the
monolithic 9B model extraction pipeline. CPU-first specialist services
handle routine extraction while the 9B vLLM model is preserved for
semantic adjudication of ambiguous cases.

Key components:
- Capability-aware inference gateway (OpenAI-compatible + Ollama)
- Endpoint registry with DB migrations and REST API
- Sentence-aware document segmenter (property tests)
- Deterministic financial parsing with offset integrity
- Symbol resolution with ambiguity detection
- Specialist service (GLiNER2, dynamic batching, K8s deployment)
- Company-specific sentiment (FinBERT, calibration)
- Retrieval-based novelty and duplicate detection
- Confidence calibration pipeline
- Deterministic routing engine (property tests)
- 9B adjudication layer with VRAM gating
- Stock-specific impact model (features, labels, baseline, trained)
- Pipeline orchestrator (state machine, queues, leases, feature flags)
- Bounded parallelism (async workers, semaphore, load shedding)
- Observability (tracing, metrics, alerts)
- Compatibility adapter (v3→v2 golden mapping tests)
- Shadow/canary promotion framework
- Active learning and fine-tuning pipeline

Test results: 1,161 tests pass, ruff lint clean.
All 282 spec tasks completed.
2026-07-13 02:14:59 +00:00

364 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# V3 Annotation Guidelines
**Schema version:** 1.0.0
**Last updated:** 2025-01-15
## Purpose
These guidelines define how human annotators and automated systems label documents in the Intelligence Pipeline v3 Gold Corpus. Every annotation must be evidence-grounded — no label is valid without a supporting evidence span traceable to the source text.
## Core Principles
1. **Evidence first.** If you cannot point to exact text that supports a label, do not apply the label.
2. **Explicit over inferred.** Mark only what the document explicitly states in primary annotations. Inferred exposure uses a separate, lower-confidence channel.
3. **Precision over recall.** A missed entity is preferable to a fabricated one. The pipeline uses multiple stages — later stages catch omissions.
4. **Reproducibility.** Two annotators given the same document should produce substantially the same labels. Ambiguous cases are marked, not resolved by guess.
---
## Evidence Spans
### Definition
An evidence span is the exact substring of the source document that supports an annotation. It uses zero-based character offsets into the original (pre-chunking) document text.
### Rules
- Every entity, event, relation, numeric fact, and sentiment annotation MUST reference at least one evidence span.
- Spans should be minimal but complete — include enough context for the label to be verifiable without the full document.
- Overlapping spans are permitted (e.g., the same sentence supports both an entity and an event).
- The `text` field MUST exactly match `source_text[start_char:end_char]`.
### Positive example
```
Source: "Apple Inc. reported quarterly earnings of $1.52 per share"
Span: start_char=0, end_char=10, text="Apple Inc."
```
### Negative example
```
Source: "Apple Inc. reported quarterly earnings of $1.52 per share"
Span: start_char=0, end_char=5, text="Apple"
```
❌ Truncating "Apple Inc." to "Apple" loses the corporate suffix needed to distinguish from Apple Records or the fruit.
---
## Entity Annotation
### Entity Types
| Type | When to use | Example |
|------|-------------|---------|
| `company` | Legal entity, publicly traded firm, government agency | "Apple Inc.", "The Federal Reserve" |
| `person` | Named individual | "Tim Cook", "Jerome Powell" |
| `product` | Named product or service | "iPhone 16", "Azure OpenAI Service" |
| `event` | Named event instance | "Q1 2025 earnings call" |
| `financial_metric` | Named metric class | "EPS", "revenue", "free cash flow" |
| `date` | Temporal expression | "Q1 2025", "January 15, 2025" |
| `percentage` | Percentage value | "4%", "25 basis points" |
| `currency` | Monetary value | "$1.52", "$10 billion" |
| `relationship` | Explicit relationship mention | "subsidiary", "joint venture partner" |
### Canonical Resolution
- If an entity maps to a company in the symbol registry, set `canonical_id` and `canonical_name` (ticker).
- If an entity is ambiguous (e.g., "Apple" could be AAPL or a fruit company), mark an ambiguity marker and set confidence below 1.0.
- Do NOT invent canonical IDs. If not in the registry, leave `canonical_id` as null.
### Positive example
```json
{
"entity_type": "company",
"literal_text": "Alphabet",
"canonical_id": "googl-uuid",
"canonical_name": "GOOGL",
"confidence": 0.97
}
```
### Negative example
```json
{
"entity_type": "company",
"literal_text": "the company",
"canonical_id": "aapl-uuid",
"canonical_name": "AAPL",
"confidence": 0.90
}
```
❌ "the company" is a pronoun reference, not an entity mention. Resolve coreference but annotate the actual named mention, not the pronoun.
---
## Event Classification
### Event Classes
| Class | Definition | Distinguishing criteria |
|-------|-----------|------------------------|
| `earnings_beat` | Reported EPS or revenue exceeds consensus | Explicit comparison to estimates |
| `earnings_miss` | Reported EPS or revenue below consensus | Explicit comparison to estimates |
| `guidance_raise` | Forward guidance raised vs prior or consensus | Future-looking, not historical result |
| `guidance_cut` | Forward guidance lowered | Future-looking, not historical result |
| `ma_announcement` | Merger, acquisition, investment, or divestiture | Transaction between entities |
| `legal_regulatory` | Lawsuit, fine, regulatory action, or settlement | Legal or regulatory body involved |
| `product_launch` | New product, service, or major feature announced | Not routine updates |
| `supply_chain` | Disruption, partnership, or change in supply relationships | Affects production/delivery |
| `rating_change` | Analyst upgrade, downgrade, or target change | From research analyst/firm |
| `management_change` | CEO/CFO/board appointment, resignation, or removal | C-suite or board level |
| `macro_event` | Interest rates, policy, trade, geopolitical | Not specific to one company |
| `dividend_change` | Dividend increase, decrease, or special dividend | Shareholder distribution |
| `buyback` | Share repurchase program announcement or completion | Capital return via buyback |
### Adjudication triggers for events
Route to the 9B adjudicator when:
- The same facts could be classified as multiple event types (e.g., guidance_raise during an earnings call could be either earnings_beat or guidance_raise — label the most specific applicable class).
- The event is implied but not explicitly stated.
- The primary company is unclear.
### Positive example
```
Source: "Apple beat earnings expectations with EPS of $1.52 vs $1.43 expected"
Event class: earnings_beat
Confidence: 0.98
```
### Negative example
```
Source: "Apple reported EPS of $1.52"
Event class: earnings_beat
```
❌ Without a comparison to consensus/estimates, this is a numeric fact report, not an earnings beat. The document must provide evidence of beating expectations.
---
## Relations
### Relation Types
| Type | Subject | Object | When to use |
|------|---------|--------|-------------|
| `directly_affects` | Event | Company | Event explicitly names or discusses the company |
| `inferred_exposure` | Event | Company | Exposure inferred from sector, supply chain, or competition |
| `competes_with` | Company | Company | Competitive relationship stated or clearly implied |
| `supplies` | Company | Company | Supply chain relationship stated |
### Critical distinction: directly_affects vs inferred_exposure
- `directly_affects`: The document **explicitly states** the company is impacted. Evidence span exists.
- `inferred_exposure`: The impact is **reasoned** from relationships, not stated. May have weak or no direct evidence span.
Only `directly_affects` enters primary company extraction. `inferred_exposure` flows through the separate interpolation/propagation architecture with distinct confidence and provenance.
### Positive example (directly_affects)
```
Source: "Microsoft announced a $10 billion investment in OpenAI"
Relation: directly_affects(event=ma_announcement, company=Microsoft)
Evidence: "Microsoft announced"
```
### Negative example (incorrectly using directly_affects)
```
Source: "Microsoft announced a $10 billion investment in OpenAI"
Relation: directly_affects(event=ma_announcement, company=Google)
```
❌ Google is not mentioned in the event sentence. This should be `inferred_exposure` based on competitive relationship, with appropriate lower confidence.
---
## Numeric Facts
### Annotation rules
1. Always store both `literal_value` (exact text) and `normalized_value` (parsed number).
2. Include `unit` (USD, %, bps, shares, etc.).
3. Link to the subject entity when determinable.
4. Use `predicate` to capture the semantic role: reported, expected, raised_to, cut_to, beat_by, missed_by.
5. Include `period` when the fact references a specific time frame.
### Normalization conventions
| Literal | Normalized | Unit |
|---------|-----------|------|
| "$1.52" | 1.52 | USD |
| "$94.9 billion" | 94900000000 | USD |
| "25 basis points" | 0.25 | percentage_points |
| "4%" | 4.0 | % |
| "$0.26 per share" | 0.26 | USD |
### Positive example
```json
{
"fact_type": "eps",
"predicate": "reported",
"literal_value": "$1.52 per share",
"normalized_value": 1.52,
"unit": "USD",
"period": {"period_type": "fiscal_quarter", "fiscal_year": 2025, "fiscal_quarter": 1}
}
```
### Negative example
```json
{
"fact_type": "eps",
"predicate": "reported",
"literal_value": "$1.52 per share",
"normalized_value": 152,
"unit": "cents"
}
```
❌ While $1.52 = 152 cents, always normalize to the unit stated in the source. Conversion to a different unit introduces potential confusion.
---
## Sentiment
### Rules
1. Sentiment is **company-specific**, not document-level. A single article can have positive sentiment for one company and negative for another.
2. Annotate probability distributions (positive, negative, neutral) that sum to 1.0.
3. `mixed` label is used when evidence groups disagree — it is computed from evidence-group-level disagreement, NOT an unconstrained fourth class.
4. The label should reflect the dominant probability.
### When to label "mixed"
Label `mixed` when:
- Different paragraphs contain opposing sentiment for the same company
- The same fact has both positive and negative implications (e.g., restructuring = cost cuts but also layoffs)
- Analyst opinions explicitly disagree within the document
Do NOT label `mixed` when:
- Sentiment is merely uncertain or mild — that's `neutral` with lower confidence
- The document discusses multiple companies with different sentiments — annotate separately per company
### Positive example
```json
{
"label": "mixed",
"positive_probability": 0.40,
"negative_probability": 0.45,
"neutral_probability": 0.15,
"evidence_ids": ["ev-pressure", "ev-validation"]
}
```
(Article says AI investment pressures cloud revenue but validates the broader thesis)
### Negative example
```json
{
"label": "mixed",
"positive_probability": 0.85,
"negative_probability": 0.05,
"neutral_probability": 0.10
}
```
❌ When positive_probability dominates at 0.85, the label should be `positive`, not `mixed`. Mixed requires genuine disagreement in evidence.
---
## Direct Effects vs Inferred Exposure
### Direct Effects
A direct effect means the document **explicitly states or clearly demonstrates** that an event impacts a specific company.
**Criteria:**
- The company is named in the same sentence or paragraph as the event
- The causal link is stated, not inferred
- Evidence span directly connects event to company
### Inferred Exposure
Inferred exposure captures **reasoned but unstated** impacts on companies.
**Criteria:**
- The company is NOT explicitly linked to the event in the source text
- The connection comes from known relationships (competitor, supplier, sector peer)
- Confidence should be lower than direct effects (typically 0.50.8)
- Requires `reasoning` field explaining the inference chain
### Adjudication routing
When it's unclear whether an effect is direct or inferred, mark an ambiguity marker with type `implied_causal_impact` and route to the 9B adjudicator.
---
## Ambiguity Markers
### When to flag
Flag ambiguity when:
- An alias resolves to multiple candidate companies (`unresolved_alias`)
- Multiple companies could be the primary subject (`multiple_primary_companies`)
- Numeric facts within the same document contradict each other (`contradictory_numeric_facts`)
- Sentiment evidence points in opposing directions for the same company (`conflicting_sentiment`)
- Impact is implied through causal chain, not stated (`implied_causal_impact`)
- Guidance must be compared to consensus to determine direction (`guidance_vs_consensus_requires_reasoning`)
- A required field cannot be determined from available evidence (`material_field_missing`)
- Evidence covers less than the minimum threshold for confident extraction (`evidence_coverage_below_threshold`)
- Calibrated confidence falls below the routing threshold (`calibrated_confidence_below_threshold`)
- A relation spans multiple document chunks (`long_document_cross_chunk_relation`)
### Severity levels
- **low**: The annotation is likely correct but has reduced certainty. Fast path may proceed with a confidence penalty.
- **medium**: The annotation requires review. Routes to adjudication by default.
- **high**: The annotation cannot be reliably made without semantic reasoning. Always routes to adjudication.
---
## Safety-Critical Fields
The following fields are **safety-critical** for promotion gates. Errors in these fields can directly cause incorrect trading decisions:
| Field | Why it's critical | Minimum promotion gate |
|-------|-------------------|----------------------|
| Company identity (ticker) | Wrong ticker = trade on wrong security | Precision ≥ 0.95, Recall ≥ 0.90 |
| Event class | Misclassifying beat/miss inverts signal direction | Macro-F1 ≥ 0.85 |
| Sentiment direction | Wrong sentiment → wrong position direction | Direction accuracy ≥ 0.90 |
| Numeric fact values | Wrong magnitude affects impact estimation | Tolerance match ≥ 0.92 |
| Direct effect attribution | Wrong company attribution creates false signals | Precision ≥ 0.93 |
| Evidence support | Unsupported claims are unverifiable | Support rate ≥ 0.95 |
| Confidence calibration | Overconfidence bypasses review | ECE ≤ 0.05 |
Annotators must pay special attention to these fields. During review, any error in a safety-critical field requires correction before the annotation can receive "gold" status.
---
## Annotation Workflow
1. **First pass:** Identify all entities and evidence spans
2. **Second pass:** Classify events and link to companies
3. **Third pass:** Extract numeric facts with periods
4. **Fourth pass:** Assess per-company sentiment
5. **Fifth pass:** Identify relations, direct effects, and inferred exposures
6. **Sixth pass:** Flag ambiguities and set confidence levels
7. **Review:** Senior annotator validates safety-critical fields
### Inter-annotator agreement
Hard cases (flagged with ambiguity markers) receive double annotation. Inter-annotator agreement is measured per field type using Cohen's kappa. Target: κ ≥ 0.80 for entity and event labels, κ ≥ 0.70 for relations and sentiment.
---
## Version History
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2025-01-15 | Initial schema and guidelines |