Changes for page POC1 API & Schemas Specification
Last modified by Robert Schaub on 2025/12/24 21:27
To version 2.1
edited by Robert Schaub
on 2025/12/24 21:08
on 2025/12/24 21:08
Change comment:
There is no comment for this version
Summary
-
Page properties (1 modified, 0 added, 0 removed)
Details
- Page properties
-
- Content
-
... ... @@ -1,4 +1,6 @@ 1 1 = POC1 API & Schemas Specification = 2 +Version: 0.4.1 (POC1) 3 +Scope: POC1 (Test) — URL/Text → Stage 1 Claim Extraction → Stage 2 Claim Analysis (cached) → Stage 3 Article Assessment → result.json + report.md 2 2 3 3 ---- 4 4 ... ... @@ -5,1414 +5,751 @@ 5 5 == Version History == 6 6 7 7 |=Version|=Date|=Changes 8 -|0.4.1|2025-12-24| Applied9criticalfixes: file formatnotice,verdicttaxonomy, canonicalizationalgorithm,Stage1 costpolicy,BullMQfix,languageincachekey,historicalclaimsTTL, idempotency,copyright policy9 -|0.4|2025-12-24| **BREAKING:**3-stage pipeline with claim-level caching,user tier system,cache-only mode for free users,Redis cache architecture10 -|0.3.1|2025-12-24| Fixed single-promptstrategy,SSEclarification,schemacanonicalization,cost constraints11 -|0.3|2025-12-24| Added completeAPIendpoints,LLMconfig,risktiers,scraping details10 +|0.4.1|2025-12-24|Codegen-ready canonical contract: locked enums + mapping, deterministic normalization (v1norm1), idempotency, cache_preference semantics, minimal OpenAPI 3.1, URL extraction + SSRF rules, evidence excerpt policy 11 +|0.4|2025-12-24|3-stage pipeline with claim-level caching; cache-only mode for free users; Redis cache architecture 12 +|0.3.1|2025-12-24|Reduced schema naming drift; clarified SSE (progress only); added cost knobs and constraints 13 +|0.3|2025-12-24|Initial endpoint set + draft schemas 12 12 13 13 ---- 14 14 15 -== 1 .CoreObjective(POC1) ==17 +== POC1 Codegen Contract (Canonical) == 16 16 17 -The primary technical goal of POC1 is to validate **Approach 1 (Single-Pass Holistic Analysis)** while implementing **claim-level caching** to achieve cost sustainability. 19 +{{info}} 20 +This section is the **authoritative, code-generation-ready contract** for POC1. 21 +If any other page conflicts with this section, **this section wins**. 18 18 19 -The system must prove that AI can identify an article's **Main Thesis** and determine if supporting claims logically support that thesis without committing fallacies. 23 +**Format note:** This page is authored in **xWiki 2.1** syntax. If exchanged as a transport `.md` file, import it as **xWiki content**, not Markdown. 24 +{{/info}} 20 20 21 -=== Success Criteria: === 26 +=== 1) Goals === 27 +POC1 provides: 28 +* A REST API to submit **either a URL or raw text** 29 +* Asynchronous execution (job-based) with progress/status and optional SSE events 30 +* Deterministic outputs: 31 +** ``result.json`` (machine-readable; schema-validated) 32 +** ``report.md`` (human-readable; rendered from JSON via template) 33 +* Mandatory counter-evidence attempt per scenario (or explicit “not found despite search” uncertainty note) 34 +* Claim-level caching to validate cost sustainability 22 22 23 -* Test with 30 diverse articles 24 -* Target: ≥70% accuracy detecting misleading articles 25 -* Cost: <$0.25 per NEW analysis (uncached) 26 -* Cost: $0.00 for cached claim reuse 27 -* Cache hit rate: ≥50% after 1,000 articles 28 -* Processing time: <2 minutes (standard depth) 36 +=== 2) Non-goals (POC1) === 37 +* Full editorial workflow / human review UI 38 +* Full provenance ledger beyond minimal job metadata 39 +* Complex billing/rate-limit systems (simple caps are fine) 40 +* Multi-language i18n beyond best-effort language detection 29 29 30 -=== Economic Model: === 42 +=== 3) Pipeline model (locked) === 43 +* **Stage 1 — Claim Extraction** 44 +** Input: URL-text or pasted text 45 +** Output: claim candidates including canonical claim + ``claim_hash`` 46 +* **Stage 2 — Claim Analysis (cached by claim_hash)** 47 +** Input: canonical claim(s) 48 +** Output: scenarios, evidence/counter-evidence, scenario verdicts, plus a claim-level verdict summary 49 +* **Stage 3 — Article Assessment** 50 +** Input: article text + Stage 2 analyses (from cache and/or freshly computed) 51 +** Output: article-level assessment (main thesis, reasoning quality, key risks, context) 31 31 32 -* **Free tier:** $10 credit per month (~~40-140 articles depending on cache hits) 33 -* **After limit:** Cache-only mode (instant, free access to cached claims) 34 -* **Paid tier:** Unlimited new analyses 53 +=== 4) Locked verdict taxonomies (two enums + mapping) === 35 35 36 ----- 55 +**Scenario verdict enum** (``ScenarioVerdict.verdict_label``): 56 +* ``Highly likely`` | ``Likely`` | ``Unclear`` | ``Unlikely`` | ``Highly unlikely`` | ``Unsubstantiated`` 37 37 38 -== 2. Architecture Overview == 58 +**Claim verdict enum** (``ClaimVerdict.verdict_label``): 59 +* ``Supported`` | ``Refuted`` | ``Inconclusive`` 39 39 40 -=== 2.1 3-Stage Pipeline with Caching === 61 +**Mapping rule (for summaries):** 62 +* If the claim’s primary-interpretation scenario verdict is: 63 +** ``Highly likely`` / ``Likely`` ⇒ ``Supported`` 64 +** ``Highly unlikely`` / ``Unlikely`` ⇒ ``Refuted`` 65 +** ``Unclear`` / ``Unsubstantiated`` ⇒ ``Inconclusive`` 66 +* If scenarios materially disagree (different assumptions lead to different outcomes) ⇒ ``Inconclusive``, and explain why. 41 41 42 -FactHarbor POC1 uses a **3-stage architecture** designed for claim-level caching and cost efficiency: 68 +=== 5) Deterministic canonical claim normalization (locked) === 69 +Normalization version: ``v1norm1`` 70 +Cache key namespace: ``claim:v1norm1:{language}:{sha256(canonical_claim_text)}`` 43 43 44 -{{mermaid}} 45 -graph TD 46 - A[Article Input] --> B[Stage 1: Extract Claims] 47 - B --> C{For Each Claim} 48 - C --> D[Check Cache] 49 - D -->|Cache HIT| E[Return Cached Verdict] 50 - D -->|Cache MISS| F[Stage 2: Analyze Claim] 51 - F --> G[Store in Cache] 52 - G --> E 53 - E --> H[Stage 3: Holistic Assessment] 54 - H --> I[Final Report] 55 -{{/mermaid}} 72 +Rules (MUST be implemented exactly; any change requires bumping normalization version): 73 +1. Unicode normalize: NFD 74 +2. Lowercase 75 +3. Strip diacritics 76 +4. Normalize apostrophes: ``’`` and ``‘`` → ``'`` 77 +5. Normalize percent: ``%`` → `` percent`` 78 +6. Normalize whitespace (collapse) 79 +7. Remove punctuation except apostrophes 80 +8. Expand contractions (fixed list for v1norm1) 81 +9. Normalize whitespace again 56 56 57 -==== Stage 1: Claim Extraction (Haiku, no cache) ==== 83 +Contractions list (v1norm1): 84 +* don't→do not, doesn't→does not, didn't→did not, can't→cannot, won't→will not, 85 +* shouldn't→should not, wouldn't→would not, isn't→is not, aren't→are not, 86 +* wasn't→was not, weren't→were not 58 58 59 -* **Input:** Article text 60 -* **Output:** 5 canonical claims (normalized, deduplicated) 61 -* **Model:** Claude Haiku 4.5.5 (default, configurable via LLM abstraction layer) 62 -* **Cost:** $0.003 per article 63 -* **Cache strategy:** No caching (article-specific) 64 - 65 -==== Stage 2: Claim Analysis (Sonnet, CACHED) ==== 66 - 67 -* **Input:** Single canonical claim 68 -* **Output:** Scenarios + Evidence + Verdicts 69 -* **Model:** Claude Sonnet 4.5 (default, configurable via LLM abstraction layer) 70 -* **Cost:** $0.081 per NEW claim 71 -* **Cache strategy:** Redis, 90-day TTL 72 -* **Cache key:** claim:v1norm1:{language}:{sha256(canonical_claim)} 73 - 74 -==== Stage 3: Holistic Assessment (Sonnet, no cache) ==== 75 - 76 -* **Input:** Article + Claim verdicts (from cache or Stage 2) 77 -* **Output:** Article verdict + Fallacies + Logic quality 78 -* **Model:** Claude Sonnet 4.5 (default, configurable via LLM abstraction layer) 79 -* **Cost:** $0.030 per article 80 -* **Cache strategy:** No caching (article-specific) 81 - 82 - 83 - 84 -**Note:** Stage 3 implements **Approach 1 (Single-Pass Holistic Analysis)** from the [[Article Verdict Problem>>Test.FactHarbor.Specification.POC.Article-Verdict-Problem]]. While claim analysis (Stage 2) is cached for efficiency, the holistic assessment maintains the integrated evaluation philosophy of Approach 1. 85 - 86 -=== Total Cost Formula: === 87 - 88 -{{{Cost = $0.003 (extraction) + (N_new_claims × $0.081) + $0.030 (holistic) 89 - 90 -Examples: 91 -- 0 new claims (100% cache hit): $0.033 92 -- 1 new claim (80% cache hit): $0.114 93 -- 3 new claims (40% cache hit): $0.276 94 -- 5 new claims (0% cache hit): $0.438 95 -}}} 96 - 97 ----- 98 - 99 -=== 2.2 User Tier System === 100 - 101 -|=Tier|=Monthly Credit|=After Limit|=Cache Access|=Analytics 102 -|**Free**|$10|Cache-only mode|✅ Full|Basic 103 -|**Pro** (future)|$50|Continues|✅ Full|Advanced 104 -|**Enterprise** (future)|Custom|Continues|✅ Full + Priority|Full 105 - 106 -**Free Tier Economics:** 107 - 108 -* $10 credit = 40-140 articles analyzed (depending on cache hit rate) 109 -* Average 70 articles/month at 70% cache hit rate 110 -* After limit: Cache-only mode 111 - 112 ----- 113 - 114 -=== 2.3 Cache-Only Mode (Free Tier Feature) === 115 - 116 -When free users reach their $10 monthly limit, they enter **Cache-Only Mode**: 117 - 118 - 119 - 120 -==== Stage 3: Holistic Assessment - Complete Specification ==== 121 - 122 -===== 3.3.1 Overview ===== 123 - 124 -**Purpose:** Synthesize individual claim analyses into an overall article assessment, identifying logical fallacies, reasoning quality, and publication readiness. 125 - 126 -**Approach:** **Single-Pass Holistic Analysis** (Approach 1 from Comparison Matrix) 127 - 128 -**Why This Approach for POC1:** 129 -* ✅ **1 API call** (vs 2 for Two-Pass or Judge) 130 -* ✅ **Low cost** ($0.030 per article) 131 -* ✅ **Fast** (4-6 seconds) 132 -* ✅ **Low complexity** (simple implementation) 133 -* ⚠️ **Medium reliability** (acceptable for POC1, will improve in POC2/Production) 134 - 135 -**Alternative Approaches Considered:** 136 - 137 -|= Approach |= API Calls |= Cost |= Speed |= Complexity |= Reliability |= Best For 138 -| **1. Single-Pass** ⭐ | 1 | 💰 Low | ⚡ Fast | 🟢 Low | ⚠️ Medium | **POC1** 139 -| 2. Two-Pass | 2 | 💰💰 Med | 🐢 Slow | 🟡 Med | ✅ High | POC2/Prod 140 -| 3. Structured | 1 | 💰 Low | ⚡ Fast | 🟡 Med | ✅ High | POC1 (alternative) 141 -| 4. Weighted | 1 | 💰 Low | ⚡ Fast | 🟢 Low | ⚠️ Medium | POC1 (alternative) 142 -| 5. Heuristics | 1 | 💰 Lowest | ⚡⚡ Fastest | 🟡 Med | ⚠️ Medium | Any 143 -| 6. Hybrid | 1 | 💰 Low | ⚡ Fast | 🔴 Med-High | ✅ High | POC2 144 -| 7. Judge | 2 | 💰💰 Med | 🐢 Slow | 🟡 Med | ✅ High | Production 145 - 146 -**POC1 Choice:** Approach 1 (Single-Pass) for speed and simplicity. Will upgrade to Approach 2 (Two-Pass) or 6 (Hybrid) in POC2 for higher reliability. 147 - 148 -===== 3.3.2 What Stage 3 Evaluates ===== 149 - 150 -Stage 3 performs **integrated holistic analysis** considering: 151 - 152 -**1. Claim-Level Aggregation:** 153 -* Verdict distribution (how many TRUE vs FALSE vs DISPUTED) 154 -* Average confidence across all claims 155 -* Claim interdependencies (do claims support/contradict each other?) 156 -* Critical claim identification (which claims are most important?) 157 - 158 -**2. Contextual Factors:** 159 -* **Source credibility**: Is the article from a reputable publisher? 160 -* **Author expertise**: Does the author have relevant credentials? 161 -* **Publication date**: Is information current or outdated? 162 -* **Claim coherence**: Do claims form a logical narrative? 163 -* **Missing context**: Are important caveats or qualifications missing? 164 - 165 -**3. Logical Fallacies:** 166 -* **Cherry-picking**: Selective evidence presentation 167 -* **False equivalence**: Treating unequal things as equal 168 -* **Straw man**: Misrepresenting opposing arguments 169 -* **Ad hominem**: Attacking person instead of argument 170 -* **Slippery slope**: Assuming extreme consequences without justification 171 -* **Circular reasoning**: Conclusion assumes premise 172 -* **False dichotomy**: Presenting only two options when more exist 173 - 174 -**4. Reasoning Quality:** 175 -* **Evidence strength**: Quality and quantity of supporting evidence 176 -* **Logical coherence**: Arguments follow logically 177 -* **Transparency**: Assumptions and limitations acknowledged 178 -* **Nuance**: Complexity and uncertainty appropriately addressed 179 - 180 -**5. Publication Readiness:** 181 -* **Risk tier assignment**: A (high risk), B (medium), or C (low risk) 182 -* **Publication mode**: DRAFT_ONLY, AI_GENERATED, or HUMAN_REVIEWED 183 -* **Required disclaimers**: What warnings should accompany this content? 184 - 185 -===== 3.3.3 Implementation: Single-Pass Approach ===== 186 - 187 -**Input:** 188 -* Original article text (full content) 189 -* Stage 2 claim analyses (array of ClaimAnalysis objects) 190 -* Article metadata (URL, title, author, date, source) 191 - 192 -**Processing:** 193 - 88 +**Reference implementation (normative):** 194 194 {{code language="python"}} 195 -# Pseudo-code for Stage 3 (Single-Pass) 90 +import re 91 +import unicodedata 196 196 197 -def stage3_holistic_assessment(article, claim_analyses, metadata): 198 - """ 199 - Single-pass holistic assessment using Claude Sonnet 4.5. 200 - 201 - Approach 1: One comprehensive prompt that asks the LLM to: 202 - 1. Review all claim verdicts 203 - 2. Identify patterns and dependencies 204 - 3. Detect logical fallacies 205 - 4. Assess reasoning quality 206 - 5. Determine credibility score and risk tier 207 - 6. Generate publication recommendations 208 - """ 209 - 210 - # Construct comprehensive prompt 211 - prompt = f""" 212 -You are analyzing an article for factual accuracy and logical reasoning. 93 +# Canonical claim normalization for deduplication. 94 +# Version: v1norm1 95 +# 96 +# IMPORTANT: 97 +# - Any change to these rules REQUIRES a new normalization version. 98 +# - Cache keys MUST include the normalization version to avoid collisions. 213 213 214 -ARTICLE METADATA: 215 -- Title: {metadata['title']} 216 -- Source: {metadata['source']} 217 -- Date: {metadata['date']} 218 -- Author: {metadata['author']} 100 +CONTRACTIONS_V1NORM1 = { 101 + "don't": "do not", 102 + "doesn't": "does not", 103 + "didn't": "did not", 104 + "can't": "cannot", 105 + "won't": "will not", 106 + "shouldn't": "should not", 107 + "wouldn't": "would not", 108 + "isn't": "is not", 109 + "aren't": "are not", 110 + "wasn't": "was not", 111 + "weren't": "were not", 112 +} 219 219 220 -ARTICLE TEXT: 221 -{article} 114 +def normalize_claim(text: str) -> str: 115 + """Canonical claim normalization (v1norm1).""" 116 + if text is None: 117 + return "" 222 222 223 - INDIVIDUALCLAIMANALYSES:224 - {format_claim_analyses(claim_analyses)}119 + # 1) Unicode normalization 120 + text = unicodedata.normalize("NFD", text) 225 225 226 - YOURTASK:227 - Performaholisticassessmentconsidering:122 + # 2) Lowercase 123 + text = text.lower() 228 228 229 -1. CLAIM AGGREGATION: 230 - - Review the verdict for each claim 231 - - Identify any interdependencies between claims 232 - - Determine which claims are most critical to the article's thesis 125 + # 3) Remove diacritics 126 + text = "".join(c for c in text if unicodedata.category(c) != "Mn") 233 233 234 -2. CONTEXTUAL EVALUATION: 235 - - Assess source credibility 236 - - Evaluate author expertise 237 - - Consider publication timeliness 238 - - Identify missing context or important caveats 128 + # 4) Normalize apostrophes (common unicode variants) 129 + text = text.replace("’", "'").replace("‘", "'") 239 239 240 -3. LOGICAL FALLACIES: 241 - - Identify any logical fallacies present 242 - - For each fallacy, provide: 243 - * Type of fallacy 244 - * Where it occurs in the article 245 - * Why it's problematic 246 - * Severity (minor/moderate/severe) 131 + # 5) Normalize percent sign 132 + text = text.replace("%", " percent") 247 247 248 -4. REASONING QUALITY: 249 - - Evaluate evidence strength 250 - - Assess logical coherence 251 - - Check for transparency in assumptions 252 - - Evaluate handling of nuance and uncertainty 134 + # 6) Normalize whitespace 135 + text = re.sub(r"\s+", " ", text).strip() 253 253 254 -5. CREDIBILITY SCORING: 255 - - Calculate overall credibility score (0.0-1.0) 256 - - Assign risk tier: 257 - * A (high risk): ≤0.5 credibility OR severe fallacies 258 - * B (medium risk): 0.5-0.8 credibility OR moderate issues 259 - * C (low risk): >0.8 credibility AND no significant issues 260 - 261 -6. PUBLICATION RECOMMENDATIONS: 262 - - Determine publication mode: 263 - * DRAFT_ONLY: Tier A, multiple severe issues 264 - * AI_GENERATED: Tier B/C, acceptable quality with disclaimers 265 - * HUMAN_REVIEWED: Complex or borderline cases 266 - - List required disclaimers 267 - - Explain decision rationale 137 + # 7) Remove punctuation except apostrophes 138 + text = re.sub(r"[^\w\s']", "", text) 268 268 269 -OUTPUT FORMAT: 270 -Return a JSON object matching the ArticleAssessment schema. 271 -""" 272 - 273 - # Call LLM 274 - response = llm_client.complete( 275 - model="claude-sonnet-4-5-20250929", 276 - prompt=prompt, 277 - max_tokens=4000, 278 - response_format="json" 279 - ) 280 - 281 - # Parse and validate response 282 - assessment = parse_json(response.content) 283 - validate_article_assessment_schema(assessment) 284 - 285 - return assessment 286 -{{/code}} 140 + # 8) Expand contractions (fixed list for v1norm1) 141 + for k, v in CONTRACTIONS_V1NORM1.items(): 142 + text = re.sub(rf"\b{re.escape(k)}\b", v, text) 287 287 288 -**Prompt Engineering Notes:** 144 + # 9) Final whitespace normalization 145 + text = re.sub(r"\s+", " ", text).strip() 289 289 290 -1. **Structured Instructions**: Break down task into 6 clear sections 291 -2. **Context-Rich**: Provide article + all claim analyses + metadata 292 -3. **Explicit Criteria**: Define credibility scoring and risk tiers precisely 293 -4. **JSON Schema**: Request structured output matching ArticleAssessment schema 294 -5. **Examples** (in production): Include 2-3 example assessments for consistency 295 - 296 -===== 3.3.4 Credibility Scoring Algorithm ===== 297 - 298 -**Base Score Calculation:** 299 - 300 -{{code language="python"}} 301 -def calculate_credibility_score(claim_analyses, fallacies, contextual_factors): 302 - """ 303 - Calculate overall credibility score (0.0-1.0). 304 - 305 - This is a GUIDELINE for the LLM, not strict code. 306 - The LLM has flexibility to adjust based on context. 307 - """ 308 - 309 - # 1. Claim Verdict Score (60% weight) 310 - verdict_weights = { 311 - "TRUE": 1.0, 312 - "PARTIALLY_TRUE": 0.7, 313 - "DISPUTED": 0.5, 314 - "UNSUPPORTED": 0.3, 315 - "FALSE": 0.0, 316 - "UNVERIFIABLE": 0.4 317 - } 318 - 319 - claim_scores = [ 320 - verdict_weights[c.verdict.label] * c.verdict.confidence 321 - for c in claim_analyses 322 - ] 323 - avg_claim_score = sum(claim_scores) / len(claim_scores) 324 - claim_component = avg_claim_score * 0.6 325 - 326 - # 2. Fallacy Penalty (20% weight) 327 - fallacy_penalties = { 328 - "minor": -0.05, 329 - "moderate": -0.15, 330 - "severe": -0.30 331 - } 332 - 333 - fallacy_score = 1.0 334 - for fallacy in fallacies: 335 - fallacy_score += fallacy_penalties[fallacy.severity] 336 - 337 - fallacy_score = max(0.0, min(1.0, fallacy_score)) 338 - fallacy_component = fallacy_score * 0.2 339 - 340 - # 3. Contextual Factors (20% weight) 341 - context_adjustments = { 342 - "source_credibility": {"positive": +0.1, "neutral": 0, "negative": -0.1}, 343 - "author_expertise": {"positive": +0.1, "neutral": 0, "negative": -0.1}, 344 - "timeliness": {"positive": +0.05, "neutral": 0, "negative": -0.05}, 345 - "transparency": {"positive": +0.05, "neutral": 0, "negative": -0.05} 346 - } 347 - 348 - context_score = 1.0 349 - for factor in contextual_factors: 350 - adjustment = context_adjustments.get(factor.factor, {}).get(factor.impact, 0) 351 - context_score += adjustment 352 - 353 - context_score = max(0.0, min(1.0, context_score)) 354 - context_component = context_score * 0.2 355 - 356 - # 4. Combine components 357 - final_score = claim_component + fallacy_component + context_component 358 - 359 - # 5. Apply confidence modifier 360 - avg_confidence = sum(c.verdict.confidence for c in claim_analyses) / len(claim_analyses) 361 - final_score = final_score * (0.8 + 0.2 * avg_confidence) 362 - 363 - return max(0.0, min(1.0, final_score)) 147 + return text 364 364 {{/code}} 365 365 366 -**Note:** This algorithm is a **guideline** provided to the LLM in the system prompt. The LLM has flexibility to adjust based on specific article context, but should generally follow this structure for consistency. 150 +=== 6) Report generation (locked) === 151 +* ``report.md`` MUST be generated from ``result.json`` using a deterministic template (server-side). 152 +* The LLM MUST NOT be the sole renderer of report markdown. 367 367 368 -===== 3.3.5 Risk Tier Assignment ===== 154 +=== 7) No chain-of-thought storage/exposure (locked) === 155 +* Store/expose only short, user-visible rationale bullets. 156 +* Never store or expose internal reasoning traces. 369 369 370 -**Automatic Risk Tier Rules:** 371 - 372 -{{code}} 373 -Risk Tier A (High Risk - Requires Review): 374 -- Credibility score ≤ 0.5, OR 375 -- Any severe fallacies detected, OR 376 -- Multiple (3+) moderate fallacies, OR 377 -- 50%+ of claims are FALSE or UNSUPPORTED 378 - 379 -Risk Tier B (Medium Risk - May Publish with Disclaimers): 380 -- Credibility score 0.5-0.8, OR 381 -- 1-2 moderate fallacies, OR 382 -- 20-49% of claims are DISPUTED or PARTIALLY_TRUE 383 - 384 -Risk Tier C (Low Risk - Safe to Publish): 385 -- Credibility score > 0.8, AND 386 -- No severe or moderate fallacies, AND 387 -- <20% disputed/problematic claims, AND 388 -- No critical missing context 389 -{{/code}} 390 - 391 -===== 3.3.6 Output: ArticleAssessment Schema ===== 392 - 393 -(See Stage 3 Output Schema section above for complete JSON schema) 394 - 395 -===== 3.3.7 Performance Metrics ===== 396 - 397 -**POC1 Targets:** 398 -* **Processing time**: 4-6 seconds per article 399 -* **Cost**: $0.030 per article (Sonnet 4.5 tokens) 400 -* **Quality**: 70-80% agreement with human reviewers (acceptable for POC) 401 -* **API calls**: 1 per article 402 - 403 -**Future Improvements (POC2/Production):** 404 -* Upgrade to Two-Pass (Approach 2): +15% accuracy, +$0.020 cost 405 -* Add human review sampling: 10% of Tier B articles 406 -* Implement Judge approach (Approach 7) for Tier A: Highest quality 407 - 408 -===== 3.3.8 Example Stage 3 Execution ===== 409 - 410 -**Input:** 411 -* Article: "Biden won the 2020 election" 412 -* Claim analyses: [{claim: "Biden won", verdict: "TRUE", confidence: 0.95}] 413 - 414 -**Stage 3 Processing:** 415 -1. Analyzes single claim with high confidence 416 -2. Checks for contextual factors (source credibility) 417 -3. Searches for logical fallacies (none found) 418 -4. Calculates credibility: 0.6 * 0.95 + 0.2 * 1.0 + 0.2 * 1.0 = 0.97 419 -5. Assigns risk tier: C (low risk) 420 -6. Recommends: AI_GENERATED publication mode 421 - 422 -**Output:** 423 -```json 424 -{ 425 - "article_id": "a1", 426 - "overall_assessment": { 427 - "credibility_score": 0.97, 428 - "risk_tier": "C", 429 - "summary": "Article makes single verifiable claim with strong evidence support", 430 - "confidence": 0.95 431 - }, 432 - "claim_aggregation": { 433 - "total_claims": 1, 434 - "verdict_distribution": {"TRUE": 1}, 435 - "avg_confidence": 0.95 436 - }, 437 - "contextual_factors": [ 438 - {"factor": "source_credibility", "impact": "positive", "description": "Reputable news source"} 439 - ], 440 - "recommendations": { 441 - "publication_mode": "AI_GENERATED", 442 - "requires_review": false, 443 - "suggested_disclaimers": [] 444 - } 445 -} 446 -``` 447 - 448 -==== What Cache-Only Mode Provides: ==== 449 - 450 -✅ **Claim Extraction (Platform-Funded):** 451 - 452 -* Stage 1 extraction runs at $0.003 per article 453 -* **Cost: Absorbed by platform** (not charged to user credit) 454 -* Rationale: Extraction is necessary to check cache, and cost is negligible 455 -* Rate limit: Max 50 extractions/day in cache-only mode (prevents abuse) 456 - 457 -✅ **Instant Access to Cached Claims:** 458 - 459 -* Any claim that exists in cache → Full verdict returned 460 -* Cost: $0 (no LLM calls) 461 -* Response time: <100ms 462 - 463 -✅ **Partial Article Analysis:** 464 - 465 -* Check each claim against cache 466 -* Return verdicts for ALL cached claims 467 -* For uncached claims: Return "status": "cache_miss" 468 - 469 -✅ **Cache Coverage Report:** 470 - 471 -* "3 of 5 claims available in cache (60% coverage)" 472 -* Links to cached analyses 473 -* Estimated cost to complete: $0.162 (2 new claims) 474 - 475 -❌ **Not Available in Cache-Only Mode:** 476 - 477 -* New claim analysis (Stage 2 LLM calls blocked) 478 -* Full holistic assessment (Stage 3 blocked if any claims missing) 479 - 480 -==== User Experience Example: ==== 481 - 482 -{{{{ 483 - "status": "cache_only_mode", 484 - "message": "Monthly credit limit reached. Showing cached results only.", 485 - "cache_coverage": { 486 - "claims_total": 5, 487 - "claims_cached": 3, 488 - "claims_missing": 2, 489 - "coverage_percent": 60 490 - }, 491 - "cached_claims": [ 492 - {"claim_id": "C1", "verdict": "Likely", "confidence": 0.82}, 493 - {"claim_id": "C2", "verdict": "Highly Likely", "confidence": 0.91}, 494 - {"claim_id": "C4", "verdict": "Unclear", "confidence": 0.55} 495 - ], 496 - "missing_claims": [ 497 - {"claim_id": "C3", "claim_text": "...", "estimated_cost": "$0.081"}, 498 - {"claim_id": "C5", "claim_text": "...", "estimated_cost": "$0.081"} 499 - ], 500 - "upgrade_options": { 501 - "top_up": "$5 for 20-70 more articles", 502 - "pro_tier": "$50/month unlimited" 503 - } 504 -} 505 -}}} 506 - 507 -**Design Rationale:** 508 - 509 -* Free users still get value (cached claims often answer their question) 510 -* Demonstrates FactHarbor's value (partial results encourage upgrade) 511 -* Sustainable for platform (no additional cost) 512 -* Fair to all users (everyone contributes to cache) 513 - 514 514 ---- 515 515 160 +== REST API (POC1) == 516 516 162 +=== 1) API Versioning === 163 +Base path: ``/v1`` 517 517 518 -== 6. LLM Abstraction Layer == 165 +=== 2) Content types === 166 +* Requests: ``application/json`` 167 +* JSON responses: ``application/json`` 168 +* Report download: ``text/markdown; charset=utf-8`` 169 +* SSE events: ``text/event-stream`` 519 519 520 -=== 6.1 Design Principle === 171 +=== 3) Time & IDs === 172 +* All timestamps: ISO 8601 UTC (e.g., ``2025-12-24T19:31:30Z``) 173 +* ``job_id``: ULID (string) 174 +* ``claim_hash``: sha256 hex string (lowercase), computed over canonical claim + version + language as specified 521 521 522 -**FactHarbor uses provider-agnostic LLM abstraction** to avoid vendor lock-in and enable: 176 +=== 4) Authentication === 177 +All ``/v1`` endpoints require: 178 +* Header: ``Authorization: Bearer <API_KEY>`` 523 523 524 -* **Provider switching:** Change LLM providers without code changes 525 -* **Cost optimization:** Use different providers for different stages 526 -* **Resilience:** Automatic fallback if primary provider fails 527 -* **Cross-checking:** Compare outputs from multiple providers 528 -* **A/B testing:** Test new models without deployment changes 529 - 530 -**Implementation:** All LLM calls go through an abstraction layer that routes to configured providers. 531 - 532 ----- 533 - 534 -=== 6.2 LLM Provider Interface === 535 - 536 -**Abstract Interface:** 537 - 538 -{{{ 539 -interface LLMProvider { 540 - // Core methods 541 - complete(prompt: string, options: CompletionOptions): Promise<CompletionResponse> 542 - stream(prompt: string, options: CompletionOptions): AsyncIterator<StreamChunk> 543 - 544 - // Provider metadata 545 - getName(): string 546 - getMaxTokens(): number 547 - getCostPer1kTokens(): { input: number, output: number } 548 - 549 - // Health check 550 - isAvailable(): Promise<boolean> 551 -} 552 - 553 -interface CompletionOptions { 554 - model?: string 555 - maxTokens?: number 556 - temperature?: number 557 - stopSequences?: string[] 558 - systemPrompt?: string 559 -} 560 -}}} 561 - 562 ----- 563 - 564 -=== 6.3 Supported Providers (POC1) === 565 - 566 -**Primary Provider (Default):** 567 - 568 -* **Anthropic Claude API** 569 - * Models: Claude Haiku 4.5, Claude Sonnet 4.5, Claude Opus 4 570 - * Used by default in POC1 571 - * Best quality for holistic analysis 572 - 573 -**Secondary Providers (Future):** 574 - 575 -* **OpenAI API** 576 - * Models: GPT-4o, GPT-4o-mini 577 - * For cost comparison 578 - 579 -* **Google Vertex AI** 580 - * Models: Gemini 1.5 Pro, Gemini 1.5 Flash 581 - * For diversity in evidence gathering 582 - 583 -* **Local Models** (Post-POC) 584 - * Models: Llama 3.1, Mistral 585 - * For privacy-sensitive deployments 586 - 587 ----- 588 - 589 -=== 6.4 Provider Configuration === 590 - 591 -**Environment Variables:** 592 - 593 -{{{ 594 -# Primary provider 595 -LLM_PRIMARY_PROVIDER=anthropic 596 -ANTHROPIC_API_KEY=sk-ant-... 597 - 598 -# Fallback provider 599 -LLM_FALLBACK_PROVIDER=openai 600 -OPENAI_API_KEY=sk-... 601 - 602 -# Provider selection per stage 603 -LLM_STAGE1_PROVIDER=anthropic 604 -LLM_STAGE1_MODEL=claude-haiku-4 605 -LLM_STAGE2_PROVIDER=anthropic 606 -LLM_STAGE2_MODEL=claude-sonnet-4-5-20250929 607 -LLM_STAGE3_PROVIDER=anthropic 608 -LLM_STAGE3_MODEL=claude-sonnet-4-5-20250929 609 - 610 -# Cost limits 611 -LLM_MAX_COST_PER_REQUEST=1.00 612 -}}} 613 - 614 -**Database Configuration (Alternative):** 615 - 616 -{{{{ 180 +=== 5) Error Envelope (all non-2xx) === 181 +{{code language="json"}} 617 617 { 618 - "providers": [ 619 - { 620 - "name": "anthropic", 621 - "api_key_ref": "vault://anthropic-api-key", 622 - "enabled": true, 623 - "priority": 1 624 - }, 625 - { 626 - "name": "openai", 627 - "api_key_ref": "vault://openai-api-key", 628 - "enabled": true, 629 - "priority": 2 183 + "error": { 184 + "code": "CACHE_MISS | VALIDATION_ERROR | UNAUTHORIZED | FORBIDDEN | NOT_FOUND | RATE_LIMITED | UPSTREAM_FETCH_ERROR | INTERNAL_ERROR", 185 + "message": "Human readable message", 186 + "details": { 187 + "field_errors": [ 188 + {"field": "input_url", "issue": "Must be a valid https URL"} 189 + ] 630 630 } 631 - ], 632 - "stage_config": { 633 - "stage1": { 634 - "provider": "anthropic", 635 - "model": "claude-haiku-4-5-20251001", 636 - "max_tokens": 4096, 637 - "temperature": 0.0 638 - }, 639 - "stage2": { 640 - "provider": "anthropic", 641 - "model": "claude-sonnet-4-5-20250929", 642 - "max_tokens": 16384, 643 - "temperature": 0.3 644 - }, 645 - "stage3": { 646 - "provider": "anthropic", 647 - "model": "claude-sonnet-4-5-20250929", 648 - "max_tokens": 8192, 649 - "temperature": 0.2 650 - } 651 651 } 652 652 } 653 -}} }193 +{{/code}} 654 654 655 655 ---- 656 656 657 -== =6.5 Stage-Specific Models (POC1 Defaults)===197 +== Endpoints == 658 658 659 -**Stage 1: Claim Extraction** 199 +=== 1) POST /v1/analyze === 200 +Creates an asynchronous job. Exactly one of ``input_url`` or ``input_text`` MUST be provided. 660 660 661 -* **Default:** Anthropic Claude Haiku 4.5 662 -* **Alternative:** OpenAI GPT-4o-mini, Google Gemini 1.5 Flash 663 -* **Rationale:** Fast, cheap, simple task 664 -* **Cost:** ~$0.003 per article 665 - 666 -**Stage 2: Claim Analysis** (CACHEABLE) 667 - 668 -* **Default:** Anthropic Claude Sonnet 4.5 669 -* **Alternative:** OpenAI GPT-4o, Google Gemini 1.5 Pro 670 -* **Rationale:** High-quality analysis, cached 90 days 671 -* **Cost:** ~$0.081 per NEW claim 672 - 673 -**Stage 3: Holistic Assessment** 674 - 675 -* **Default:** Anthropic Claude Sonnet 4.5 676 -* **Alternative:** OpenAI GPT-4o, Claude Opus 4 (for high-stakes) 677 -* **Rationale:** Complex reasoning, logical fallacy detection 678 -* **Cost:** ~$0.030 per article 679 - 680 -**Cost Comparison (Example):** 681 - 682 -|=Stage|=Anthropic (Default)|=OpenAI Alternative|=Google Alternative 683 -|Stage 1|Claude Haiku 4.5.5 ($0.003)|GPT-4o-mini ($0.002)|Gemini Flash ($0.002) 684 -|Stage 2|Claude Sonnet 4.5 ($0.081)|GPT-4o ($0.045)|Gemini Pro ($0.050) 685 -|Stage 3|Claude Sonnet 4.5 ($0.030)|GPT-4o ($0.018)|Gemini Pro ($0.020) 686 -|**Total (0% cache)**|**$0.114**|**$0.065**|**$0.072** 687 - 688 -**Note:** POC1 uses Anthropic exclusively for consistency. Multi-provider support planned for POC2. 689 - 690 ----- 691 - 692 -=== 6.6 Failover Strategy === 693 - 694 -**Automatic Failover:** 695 - 696 -{{{ 697 -async function completeLLM(stage: string, prompt: string): Promise<string> { 698 - const primaryProvider = getProviderForStage(stage) 699 - const fallbackProvider = getFallbackProvider() 700 - 701 - try { 702 - return await primaryProvider.complete(prompt) 703 - } catch (error) { 704 - if (error.type === 'rate_limit' || error.type === 'service_unavailable') { 705 - logger.warn(`Primary provider failed, using fallback`) 706 - return await fallbackProvider.complete(prompt) 707 - } 708 - throw error 202 +**Request (AnalyzeRequest)** 203 +{{code language="json"}} 204 +{ 205 + "input_url": "https://example.org/article", 206 + "input_text": null, 207 + "options": { 208 + "max_claims": 5, 209 + "cache_preference": "prefer_cache", 210 + "browsing": "on", 211 + "output_report": true 212 + }, 213 + "client": { 214 + "request_id": "optional-idempotency-key" 709 709 } 710 710 } 711 -}} }217 +{{/code}} 712 712 713 -**Fallback Priority:** 219 +**Options** 220 +* ``max_claims``: integer, 1..50 (default 5) 221 +* ``cache_preference``: ``prefer_cache`` | ``allow_partial`` | ``cache_only`` | ``skip_cache`` 222 +** ``prefer_cache``: use cache when available; otherwise run full pipeline 223 +** ``allow_partial``: if Stage 2 cached analyses exist, server MAY skip Stage 1+2 and rerun only Stage 3 using cached analyses 224 +** ``cache_only``: do not run Stage 2 for uncached claims; fail with CACHE_MISS (402) if required claim analyses are missing 225 +** ``skip_cache``: ignore cache and recompute 226 +* ``browsing``: ``on`` | ``off`` 227 +** ``off``: do not retrieve evidence; mark evidence items as ``NEEDS_RETRIEVAL`` and output retrieval queries 228 +* ``output_report``: boolean (default true) 714 714 715 -1. **Primary:** Configured provider for stage 716 -2. **Secondary:** Fallback provider (if configured) 717 -3. **Cache:** Return cached result (if available for Stage 2) 718 -4. **Error:** Return 503 Service Unavailable 719 - 720 ----- 721 - 722 -=== 6.7 Provider Selection API === 723 - 724 -**Admin Endpoint:** POST /admin/v1/llm/configure 725 - 726 -**Update provider for specific stage:** 727 - 728 -{{{{ 230 +**Response: 202 Accepted (JobCreated)** 231 +{{code language="json"}} 729 729 { 730 - "stage": "stage2", 731 - "provider": "openai", 732 - "model": "gpt-4o", 733 - "max_tokens": 16384, 734 - "temperature": 0.3 735 -} 736 -}}} 737 - 738 -**Response:** 200 OK 739 - 740 -{{{{ 741 -{ 742 - "message": "LLM configuration updated", 743 - "stage": "stage2", 744 - "previous": { 745 - "provider": "anthropic", 746 - "model": "claude-sonnet-4-5-20250929" 233 + "job_id": "01J8Y9K6M2Q1J0JZ7E5P8H7Y9C", 234 + "status": "QUEUED", 235 + "created_at": "2025-12-24T19:30:00Z", 236 + "estimated_cost": { 237 + "credits": 420, 238 + "explain": "Estimate may change after Stage 1 (claim count)." 747 747 }, 748 - "current": { 749 - "provider": "openai", 750 - "model": "gpt-4o" 751 - }, 752 - "cost_impact": { 753 - "previous_cost_per_claim": 0.081, 754 - "new_cost_per_claim": 0.045, 755 - "savings_percent": 44 240 + "links": { 241 + "self": "/v1/jobs/01J8Y9K6M2Q1J0JZ7E5P8H7Y9C", 242 + "events": "/v1/jobs/01J8Y9K6M2Q1J0JZ7E5P8H7Y9C/events", 243 + "result": "/v1/jobs/01J8Y9K6M2Q1J0JZ7E5P8H7Y9C/result", 244 + "report": "/v1/jobs/01J8Y9K6M2Q1J0JZ7E5P8H7Y9C/report" 756 756 } 757 757 } 758 -}} }247 +{{/code}} 759 759 760 -**Get current configuration:** 761 - 762 -GET /admin/v1/llm/config 763 - 764 -{{{{ 249 +**Cache-only failure: 402 (CACHE_MISS)** 250 +{{code language="json"}} 765 765 { 766 - "providers": ["anthropic", "openai"], 767 - "primary": "anthropic", 768 - "fallback": "openai", 769 - "stages": { 770 - "stage1": { 771 - "provider": "anthropic", 772 - "model": "claude-haiku-4-5-20251001", 773 - "cost_per_request": 0.003 774 - }, 775 - "stage2": { 776 - "provider": "anthropic", 777 - "model": "claude-sonnet-4-5-20250929", 778 - "cost_per_new_claim": 0.081 779 - }, 780 - "stage3": { 781 - "provider": "anthropic", 782 - "model": "claude-sonnet-4-5-20250929", 783 - "cost_per_request": 0.030 252 + "error": { 253 + "code": "CACHE_MISS", 254 + "message": "cache_only requested but a required claim analysis is not cached.", 255 + "details": { 256 + "missing_claim_hash": "9f2a...c01", 257 + "normalization_version": "v1norm1" 784 784 } 785 785 } 786 786 } 787 -}} }261 +{{/code}} 788 788 789 ----- 263 +=== 2) GET /v1/jobs/{job_id} === 264 +Returns job status and progress. 790 790 791 -=== 6.8 Implementation Notes === 792 - 793 -**Provider Adapter Pattern:** 794 - 795 -{{{ 796 -class AnthropicProvider implements LLMProvider { 797 - async complete(prompt: string, options: CompletionOptions) { 798 - const response = await anthropic.messages.create({ 799 - model: options.model || 'claude-sonnet-4-5-20250929', 800 - max_tokens: options.maxTokens || 4096, 801 - messages: [{ role: 'user', content: prompt }], 802 - system: options.systemPrompt 803 - }) 804 - return response.content[0].text 266 +**Response: 200 OK (Job)** 267 +{{code language="json"}} 268 +{ 269 + "job_id": "01J...", 270 + "status": "RUNNING", 271 + "created_at": "2025-12-24T19:30:00Z", 272 + "updated_at": "2025-12-24T19:31:12Z", 273 + "progress": { 274 + "stage": "STAGE2_CLAIM_ANALYSIS", 275 + "stage_progress": 0.4, 276 + "message": "Analyzing claim 3/8" 277 + }, 278 + "links": { 279 + "events": "/v1/jobs/01J.../events", 280 + "result": "/v1/jobs/01J.../result", 281 + "report": "/v1/jobs/01J.../report" 805 805 } 806 806 } 284 +{{/code}} 807 807 808 -class OpenAIProvider implements LLMProvider { 809 - async complete(prompt: string, options: CompletionOptions) { 810 - const response = await openai.chat.completions.create({ 811 - model: options.model || 'gpt-4o', 812 - max_tokens: options.maxTokens || 4096, 813 - messages: [ 814 - { role: 'system', content: options.systemPrompt }, 815 - { role: 'user', content: prompt } 816 - ] 817 - }) 818 - return response.choices[0].message.content 819 - } 820 -} 821 -}}} 286 +Statuses: 287 +* ``QUEUED`` → ``RUNNING`` → ``SUCCEEDED`` | ``FAILED`` | ``CANCELED`` 822 822 823 -**Provider Registry:** 289 +Stages: 290 +* ``STAGE1_CLAIM_EXTRACT`` 291 +* ``STAGE2_CLAIM_ANALYSIS`` 292 +* ``STAGE3_ARTICLE_ASSESSMENT`` 824 824 825 -{{{ 826 -const providers = new Map<string, LLMProvider>() 827 -providers.set('anthropic', new AnthropicProvider()) 828 -providers.set('openai', new OpenAIProvider()) 829 -providers.set('google', new GoogleProvider()) 294 +=== 3) GET /v1/jobs/{job_id}/events === 295 +Server-Sent Events (SSE) for job progress reporting (no token streaming). 830 830 831 -function getProvider(name: string): LLMProvider { 832 - return providers.get(name) || providers.get(config.primaryProvider) 833 -} 834 -}}} 297 +Event types (minimum): 298 +* ``job.created`` 299 +* ``stage.started`` 300 +* ``stage.progress`` 301 +* ``stage.completed`` 302 +* ``job.succeeded`` 303 +* ``job.failed`` 835 835 836 ----- 305 +=== 4) GET /v1/jobs/{job_id}/result === 306 +Returns JSON result. 307 +* ``200`` if job ``SUCCEEDED`` 308 +* ``409`` if not finished 837 837 838 -== 3. REST API Contract == 310 +=== 5) GET /v1/jobs/{job_id}/report === 311 +Returns ``text/markdown`` report. 312 +* ``200`` if job ``SUCCEEDED`` 313 +* ``409`` if not finished 839 839 840 -=== 3.1 User Credit Tracking === 315 +=== 6) DELETE /v1/jobs/{job_id} === 316 +Cancels a job (best-effort) and deletes stored artifacts if supported. 317 +* ``204`` success 318 +* ``404`` if not found 841 841 842 -**Endpoint:** GET /v1/user/credit 843 - 844 -**Response:** 200 OK 845 - 846 -{{{{ 847 - "user_id": "user_abc123", 848 - "tier": "free", 849 - "credit_limit": 10.00, 850 - "credit_used": 7.42, 851 - "credit_remaining": 2.58, 852 - "reset_date": "2025-02-01T00:00:00Z", 853 - "cache_only_mode": false, 854 - "usage_stats": { 855 - "articles_analyzed": 67, 856 - "claims_from_cache": 189, 857 - "claims_newly_analyzed": 113, 858 - "cache_hit_rate": 0.626 859 - } 860 -} 861 -}}} 862 - 863 ----- 864 - 865 - 866 - 867 -==== Stage 2 Output Schema: ClaimAnalysis ==== 868 - 869 -**Complete schema for each claim's analysis result:** 870 - 320 +=== 7) GET /v1/health === 321 +**Response: 200 OK** 871 871 {{code language="json"}} 872 872 { 873 - "claim_id": "claim_abc123", 874 - "claim_text": "Biden won the 2020 election", 875 - "scenarios": [ 876 - { 877 - "scenario_id": "scenario_1", 878 - "description": "Interpreting 'won' as Electoral College victory", 879 - "verdict": { 880 - "label": "TRUE", 881 - "confidence": 0.95, 882 - "explanation": "Joe Biden won 306 electoral votes vs Trump's 232" 883 - }, 884 - "evidence": { 885 - "supporting": [ 886 - { 887 - "text": "Biden certified with 306 electoral votes", 888 - "source_url": "https://www.archives.gov/electoral-college/2020", 889 - "source_title": "2020 Electoral College Results", 890 - "credibility_score": 0.98 891 - } 892 - ], 893 - "opposing": [] 894 - } 895 - } 896 - ], 897 - "recommended_scenario": "scenario_1", 898 - "metadata": { 899 - "analysis_timestamp": "2024-12-24T18:00:00Z", 900 - "model_used": "claude-sonnet-4-5-20250929", 901 - "processing_time_seconds": 8.5 902 - } 324 + "status": "ok", 325 + "service": "factharbor-poc1", 326 + "version": "v0.9.105", 327 + "time": "2025-12-24T19:31:30Z" 903 903 } 904 904 {{/code}} 905 905 906 -**Required Fields:** 907 -* **claim_id**: Unique identifier matching Stage 1 output 908 -* **claim_text**: The exact claim being analyzed 909 -* **scenarios**: Array of interpretation scenarios (minimum 1) 910 - * **scenario_id**: Unique ID for this scenario 911 - * **description**: Clear interpretation of the claim 912 - * **verdict**: Verdict object with label, confidence, explanation 913 - * **evidence**: Supporting and opposing evidence arrays 914 -* **recommended_scenario**: ID of the primary/recommended scenario 915 -* **metadata**: Processing metadata (timestamp, model, timing) 331 +---- 916 916 917 -**Optional Fields:** 918 -* Additional context, warnings, or quality scores 333 +== Idempotency (Required) == 919 919 920 -**Minimum Viable Example:** 335 +Clients SHOULD send: 336 +* Header: ``Idempotency-Key: <string>`` (preferred) 337 +or 338 +* Body: ``client.request_id`` 921 921 922 -{{code language="json"}} 923 -{ 924 - "claim_id": "c1", 925 - "claim_text": "The sky is blue", 926 - "scenarios": [{ 927 - "scenario_id": "s1", 928 - "description": "Under clear daytime conditions", 929 - "verdict": {"label": "TRUE", "confidence": 0.99, "explanation": "Rayleigh scattering"}, 930 - "evidence": {"supporting": [], "opposing": []} 931 - }], 932 - "recommended_scenario": "s1", 933 - "metadata": {"analysis_timestamp": "2024-12-24T18:00:00Z"} 934 -} 935 -{{/code}} 340 +Server behavior: 341 +* Same key + same request body → return existing job (``200``) with: 342 +** ``idempotent=true`` 343 +** ``original_request_at`` 344 +* Same key + different request body → ``409`` with ``VALIDATION_ERROR`` and mismatch details 936 936 346 +Idempotency TTL: 24 hours (matches minimum job retention) 937 937 348 +---- 938 938 939 -== ==Stage 3 Output Schema:ArticleAssessment====350 +== Schemas (result.json) == 940 940 941 -**Complete schema for holistic article-level assessment:** 942 - 352 +=== Top-level AnalysisResult === 943 943 {{code language="json"}} 944 944 { 945 - "article_id": "article_xyz789", 946 - "overall_assessment": { 947 - "credibility_score": 0.72, 948 - "risk_tier": "B", 949 - "summary": "Article contains mostly accurate claims with one disputed claim requiring expert review", 950 - "confidence": 0.85 355 + "job_id": "01J...", 356 + "input": { 357 + "source_type": "url|text", 358 + "source": "https://example.org/article", 359 + "language": "en", 360 + "retrieved_at_utc": "2025-12-24T19:30:12Z", 361 + "extraction": { 362 + "method": "jina|trafilatura|manual", 363 + "word_count": 1234 364 + } 951 951 }, 952 - "claim_aggregation": { 953 - "total_claims": 5, 954 - "verdict_distribution": { 955 - "TRUE": 3, 956 - "PARTIALLY_TRUE": 1, 957 - "DISPUTED": 1, 958 - "FALSE": 0, 959 - "UNSUPPORTED": 0, 960 - "UNVERIFIABLE": 0 961 - }, 962 - "avg_confidence": 0.82 366 + "claim_extraction": { 367 + "normalization_version": "v1norm1", 368 + "claims": [ 369 + { 370 + "claim_hash": "sha256hex...", 371 + "claim_text": "as found in source", 372 + "canonical_claim_text": "normalized claim", 373 + "confidence": 0.72 374 + } 375 + ] 963 963 }, 964 - "c ontextual_factors": [377 + "claim_analyses": [ 965 965 { 966 - "factor": "Source credibility", 967 - "impact": "positive", 968 - "description": "Published by reputable news organization" 969 - }, 970 - { 971 - "factor": "Claim interdependence", 972 - "impact": "neutral", 973 - "description": "Claims are independent; no logical chains" 379 + "claim_hash": "sha256hex...", 380 + "claim_verdict": { 381 + "verdict_label": "Supported|Refuted|Inconclusive", 382 + "confidence": 0.63, 383 + "rationale_bullets": [ 384 + "Short bullet 1", 385 + "Short bullet 2" 386 + ] 387 + }, 388 + "scenarios": [ 389 + { 390 + "scenario_id": "01J...ULID", 391 + "scenario_title": "Interpretation / boundary", 392 + "definitions": { "term": "definition" }, 393 + "assumptions": ["..."], 394 + "boundaries": { "time": "...", "geography": "...", "population": "...", "conditions": "..." }, 395 + "retrieval_plan": { 396 + "queries": [ 397 + {"q": "support query", "purpose": "support"}, 398 + {"q": "counter query", "purpose": "counter"} 399 + ] 400 + }, 401 + "evidence": [ 402 + { 403 + "evidence_id": "01J...ULID", 404 + "stance": "supports|undermines|mixed|context_dependent", 405 + "relevance": 0.0, 406 + "summary_bullets": ["..."], 407 + "citation": { 408 + "title": "Source title", 409 + "publisher": "Publisher", 410 + "author_or_org": "Org/Author", 411 + "publication_date": "YYYY-MM-DD", 412 + "url": "https://...", 413 + "retrieved_at_utc": "2025-12-24T19:31:01Z" 414 + }, 415 + "excerpt": "optional short quote (max 25 words)", 416 + "reliability_rating": "high|medium|low", 417 + "limitations": ["..."], 418 + "retrieval_status": "OK|NEEDS_RETRIEVAL|FAILED" 419 + } 420 + ], 421 + "verdict": { 422 + "verdict_label": "Highly likely|Likely|Unclear|Unlikely|Highly unlikely|Unsubstantiated", 423 + "probability_range": [0.0, 1.0], 424 + "confidence": 0.0, 425 + "rationale_bullets": ["..."], 426 + "key_supporting_evidence_ids": ["E..."], 427 + "key_counter_evidence_ids": ["E..."], 428 + "uncertainty_factors": ["..."], 429 + "what_would_change_my_mind": ["..."] 430 + } 431 + } 432 + ], 433 + "quality_gates": { 434 + "gate1_claim_validation": "pass|partial|fail", 435 + "gate2_contradiction_search": "pass|partial|fail", 436 + "gate3_uncertainty_disclosure": "pass|partial|fail", 437 + "gate4_verdict_confidence": "pass|partial|fail", 438 + "fail_reasons": [] 439 + } 974 974 } 975 975 ], 976 - "recommendations": { 977 - "publication_mode": "AI_GENERATED", 978 - "requires_review": false, 979 - "review_reason": null, 980 - "suggested_disclaimers": [ 981 - "One claim (Claim 4) has conflicting expert opinions" 442 + "article_assessment": { 443 + "main_thesis": "string", 444 + "thesis_support": "supported|challenged|mixed|unclear", 445 + "overall_reasoning_quality": "high|medium|low", 446 + "summary": "string", 447 + "key_risks": [ 448 + "missing evidence", 449 + "cherry-picking", 450 + "correlation/causation", 451 + "time window mismatch" 452 + ], 453 + "how_claims_connect_to_thesis": [ 454 + "Short bullets connecting claim analyses to the thesis assessment" 982 982 ] 983 983 }, 984 - "metadata": { 985 - "holistic_timestamp": "2024-12-24T18:00:10Z", 986 - "model_used": "claude-sonnet-4-5-20250929", 987 - "processing_time_seconds": 4.2, 988 - "cache_used": false 457 + "global_notes": { 458 + "limitations": ["..."], 459 + "policy_notes": [] 989 989 } 990 990 } 991 991 {{/code}} 992 992 993 -**Required Fields:** 994 -* **article_id**: Unique identifier for this article 995 -* **overall_assessment**: Top-level assessment 996 - * **credibility_score**: 0.0-1.0 composite score 997 - * **risk_tier**: A, B, or C (per AKEL quality gates) 998 - * **summary**: Human-readable assessment 999 - * **confidence**: How confident the holistic assessment is 1000 -* **claim_aggregation**: Statistics across all claims 1001 - * **total_claims**: Count of claims analyzed 1002 - * **verdict_distribution**: Count per verdict label 1003 - * **avg_confidence**: Average confidence across verdicts 1004 -* **contextual_factors**: Array of contextual considerations 1005 -* **recommendations**: Publication decision support 1006 - * **publication_mode**: DRAFT_ONLY, AI_GENERATED, or HUMAN_REVIEWED 1007 - * **requires_review**: Boolean flag 1008 - * **suggested_disclaimers**: Array of disclaimer texts 1009 -* **metadata**: Processing metadata 464 +=== Mandatory counter-evidence rule (POC1) === 465 +For each scenario, attempt to include at least one evidence item with: 466 +* ``stance`` ∈ {``undermines``, ``mixed``, ``context_dependent``} 1010 1010 1011 -**Minimum Viable Example:** 468 +If not found: 469 +* include explicit “not found despite targeted search” note in ``uncertainty_factors`` 470 +* and/or include evidence items with ``retrieval_status=FAILED`` indicating the attempted search 1012 1012 1013 -{{code language="json"}} 1014 -{ 1015 - "article_id": "a1", 1016 - "overall_assessment": { 1017 - "credibility_score": 0.95, 1018 - "risk_tier": "C", 1019 - "summary": "All claims verified as true", 1020 - "confidence": 0.98 1021 - }, 1022 - "claim_aggregation": { 1023 - "total_claims": 1, 1024 - "verdict_distribution": {"TRUE": 1}, 1025 - "avg_confidence": 0.99 1026 - }, 1027 - "contextual_factors": [], 1028 - "recommendations": { 1029 - "publication_mode": "AI_GENERATED", 1030 - "requires_review": false, 1031 - "suggested_disclaimers": [] 1032 - }, 1033 - "metadata": {"holistic_timestamp": "2024-12-24T18:00:00Z"} 1034 -} 1035 -{{/code}} 1036 - 1037 -=== 3.2 Create Analysis Job (3-Stage) === 1038 - 1039 -**Endpoint:** POST /v1/analyze 1040 - 1041 -==== Idempotency Support: ==== 1042 - 1043 -To prevent duplicate job creation on network retries, clients SHOULD include: 1044 - 1045 -{{{POST /v1/analyze 1046 -Idempotency-Key: {client-generated-uuid} 1047 -}}} 1048 - 1049 -OR use the client.request_id field: 1050 - 1051 -{{{{ 1052 - "input_url": "...", 1053 - "client": { 1054 - "request_id": "client-uuid-12345", 1055 - "source_label": "optional" 1056 - } 1057 -} 1058 -}}} 1059 - 1060 -**Server Behavior:** 1061 - 1062 -* If Idempotency-Key or request_id seen before (within 24 hours): 1063 -** Return existing job (200 OK, not 202 Accepted) 1064 -** Do NOT create duplicate job or charge twice 1065 -* Idempotency keys expire after 24 hours (matches job retention) 1066 - 1067 -**Example Response (Idempotent):** 1068 - 1069 -{{{{ 1070 - "job_id": "01J...ULID", 1071 - "status": "RUNNING", 1072 - "idempotent": true, 1073 - "original_request_at": "2025-12-24T10:31:00Z", 1074 - "message": "Returning existing job (idempotency key matched)" 1075 -} 1076 -}}} 1077 - 1078 -==== Request Body: ==== 1079 - 1080 -{{{{ 1081 - "input_type": "url", 1082 - "input_url": "https://example.com/medical-report-01", 1083 - "input_text": null, 1084 - "options": { 1085 - "browsing": "on", 1086 - "depth": "standard", 1087 - "max_claims": 5, 1088 - 1089 -* **cache_preference** (optional): Cache usage preference 1090 - * **Type:** string 1091 - * **Enum:** {{code}}["prefer_cache", "allow_partial", "skip_cache"]{{/code}} 1092 - * **Default:** {{code}}"prefer_cache"{{/code}} 1093 - * **Semantics:** 1094 - * {{code}}"prefer_cache"{{/code}}: Use full cache if available, otherwise run all stages 1095 - * {{code}}"allow_partial"{{/code}}: Use cached Stage 2 results if available, rerun only Stage 3 1096 - * {{code}}"skip_cache"{{/code}}: Always rerun all stages (ignore cache) 1097 - * **Behavior:** When set to {{code}}"allow_partial"{{/code}} and Stage 2 cached results exist: 1098 - * Stage 1 & 2 are skipped 1099 - * Stage 3 (holistic assessment) runs fresh with cached claim analyses 1100 - * Response includes {{code}}"cache_used": true{{/code}} and {{code}}"stages_cached": ["stage1", "stage2"]{{/code}} 1101 - 1102 - "scenarios_per_claim": 2, 1103 - "max_evidence_per_scenario": 6, 1104 - "context_aware_analysis": true 1105 - }, 1106 - "client": { 1107 - "request_id": "optional-client-tracking-id", 1108 - "source_label": "optional" 1109 - } 1110 -} 1111 -}}} 1112 - 1113 -**Options:** 1114 - 1115 -* browsing: on | off (retrieve web sources or just output queries) 1116 -* depth: standard | deep (evidence thoroughness) 1117 -* max_claims: 1-10 (default: **5** for cost control) 1118 -* scenarios_per_claim: 1-5 (default: **2** for cost control) 1119 -* max_evidence_per_scenario: 3-10 (default: **6**) 1120 -* context_aware_analysis: true | false (experimental) 1121 - 1122 -**Response:** 202 Accepted 1123 - 1124 -{{{{ 1125 - "job_id": "01J...ULID", 1126 - "status": "QUEUED", 1127 - "created_at": "2025-12-24T10:31:00Z", 1128 - "estimated_cost": 0.114, 1129 - "cost_breakdown": { 1130 - "stage1_extraction": 0.003, 1131 - "stage2_new_claims": 0.081, 1132 - "stage2_cached_claims": 0.000, 1133 - "stage3_holistic": 0.030 1134 - }, 1135 - "cache_info": { 1136 - "claims_to_extract": 5, 1137 - "estimated_cache_hits": 4, 1138 - "estimated_new_claims": 1 1139 - }, 1140 - "links": { 1141 - "self": "/v1/jobs/01J...ULID", 1142 - "result": "/v1/jobs/01J...ULID/result", 1143 - "report": "/v1/jobs/01J...ULID/report", 1144 - "events": "/v1/jobs/01J...ULID/events" 1145 - } 1146 -} 1147 -}}} 1148 - 1149 -**Error Responses:** 1150 - 1151 -402 Payment Required - Free tier limit reached, cache-only mode 1152 - 1153 -{{{{ 1154 - "error": "credit_limit_reached", 1155 - "message": "Monthly credit limit reached. Entering cache-only mode.", 1156 - "cache_only_mode": true, 1157 - "credit_remaining": 0.00, 1158 - "reset_date": "2025-02-01T00:00:00Z", 1159 - "action": "Resubmit with cache_preference=allow_partial for cached results" 1160 -} 1161 -}}} 1162 - 1163 1163 ---- 1164 1164 1165 -== 4.DataSchemas ==474 +== URL Extraction Rules (POC1) == 1166 1166 1167 -=== 4.1 Stage 1 Output: ClaimExtraction === 476 +* Primary extraction: Jina Reader (if enabled) 477 +* Fallback: Trafilatura (or equivalent) 1168 1168 1169 -{{{{ 1170 - "job_id": "01J...ULID", 1171 - "stage": "stage1_extraction", 1172 - "article_metadata": { 1173 - "title": "Article title", 1174 - "source_url": "https://example.com/article", 1175 - "extracted_text_length": 5234, 1176 - "language": "en" 1177 - }, 1178 - "claims": [ 1179 - { 1180 - "claim_id": "C1", 1181 - "claim_text": "Original claim text from article", 1182 - "canonical_claim": "Normalized, deduplicated phrasing", 1183 - "claim_hash": "sha256:abc123...", 1184 - "is_central_to_thesis": true, 1185 - "claim_type": "causal", 1186 - "evaluability": "evaluable", 1187 - "risk_tier": "B", 1188 - "domain": "public_health" 1189 - } 1190 - ], 1191 - "article_thesis": "Main argument detected", 1192 - "cost": 0.003 1193 -} 1194 -}}} 479 +**SSRF protections (required):** 480 +* Block local IPs, metadata IPs, file:// URLs, and internal hostnames 481 +* If blocked: return ``UPSTREAM_FETCH_ERROR`` with reason 1195 1195 1196 ----- 483 +**Copyright/ToS safe storage policy (POC1):** 484 +* Store only metadata + short excerpts 485 +* Excerpts: max 25 words per quote, and cap total quotes per source 1197 1197 1198 -=== 4.5 Verdict Label Taxonomy === 1199 - 1200 -FactHarbor uses **three distinct verdict taxonomies** depending on analysis level: 1201 - 1202 -==== 4.5.1 Scenario Verdict Labels (Stage 2) ==== 1203 - 1204 -Used for individual scenario verdicts within a claim. 1205 - 1206 -**Enum Values:** 1207 - 1208 -* Highly Likely - Probability 0.85-1.0, high confidence 1209 -* Likely - Probability 0.65-0.84, moderate-high confidence 1210 -* Unclear - Probability 0.35-0.64, or low confidence 1211 -* Unlikely - Probability 0.16-0.34, moderate-high confidence 1212 -* Highly Unlikely - Probability 0.0-0.15, high confidence 1213 -* Unsubstantiated - Insufficient evidence to determine probability 1214 - 1215 -==== 4.5.2 Claim Verdict Labels (Rollup) ==== 1216 - 1217 -Used when summarizing a claim across all scenarios. 1218 - 1219 -**Enum Values:** 1220 - 1221 -* Supported - Majority of scenarios are Likely or Highly Likely 1222 -* Refuted - Majority of scenarios are Unlikely or Highly Unlikely 1223 -* Inconclusive - Mixed scenarios or majority Unclear/Unsubstantiated 1224 - 1225 -**Mapping Logic:** 1226 - 1227 -* If ≥60% scenarios are (Highly Likely | Likely) → Supported 1228 -* If ≥60% scenarios are (Highly Unlikely | Unlikely) → Refuted 1229 -* Otherwise → Inconclusive 1230 - 1231 -==== 4.5.3 Article Verdict Labels (Stage 3) ==== 1232 - 1233 -Used for holistic article-level assessment. 1234 - 1235 -**Enum Values:** 1236 - 1237 -* WELL-SUPPORTED - Article thesis logically follows from supported claims 1238 -* MISLEADING - Claims may be true but article commits logical fallacies 1239 -* REFUTED - Central claims are refuted, invalidating thesis 1240 -* UNCERTAIN - Insufficient evidence or highly mixed claim verdicts 1241 - 1242 -**Note:** Article verdict considers **claim centrality** (central claims override supporting claims). 1243 - 1244 -==== 4.5.4 API Field Mapping ==== 1245 - 1246 -|=Level|=API Field|=Enum Name 1247 -|Scenario|scenarios[].verdict.label|scenario_verdict_label 1248 -|Claim|claims[].rollup_verdict (optional)|claim_verdict_label 1249 -|Article|article_holistic_assessment.overall_verdict|article_verdict_label 1250 - 1251 1251 ---- 1252 1252 1253 -== 5.CacheArchitecture ==489 +== Cost Control Knobs (POC1 defaults) == 1254 1254 1255 -=== 5.1 Redis Cache Design === 491 +Defaults: 492 +* ``max_claims = 5`` 493 +* ``scenarios_per_claim = 2..3`` (internal Stage 2 policy) 494 +* Cap evidence items per scenario (recommended: 6 total; at least 1 counter) 495 +* Keep rationales concise (bullets) 1256 1256 1257 -**Technology:** Redis 7.0+ (in-memory key-value store) 1258 - 1259 -**Cache Key Schema:** 1260 - 1261 -{{{claim:v1norm1:{language}:{sha256(canonical_claim)} 1262 -}}} 1263 - 1264 -**Example:** 1265 - 1266 -{{{Claim (English): "COVID vaccines are 95% effective" 1267 -Canonical: "covid vaccines are 95 percent effective" 1268 -Language: "en" 1269 -SHA256: abc123...def456 1270 -Key: claim:v1norm1:en:abc123...def456 1271 -}}} 1272 - 1273 -**Rationale:** Prevents cross-language collisions and enables per-language cache analytics. 1274 - 1275 -**Data Structure:** 1276 - 1277 -{{{SET claim:v1norm1:en:abc123...def456 '{...ClaimAnalysis JSON...}' 1278 -EXPIRE claim:v1norm1:en:abc123...def456 7776000 # 90 days 1279 -}}} 1280 - 1281 1281 ---- 1282 1282 1283 -== =5.1.1 Canonical ClaimNormalization (v1) ===499 +== Minimal OpenAPI 3.1 (POC1) == 1284 1284 1285 -The cache key depends on deterministic claim normalization. All implementations MUST follow this algorithm exactly. 1286 - 1287 -**Algorithm: Canonical Claim Normalization v1** 1288 - 1289 - 1290 -**Normative Algorithm:** 1291 - 1292 -{{code language="python"}} 1293 -def normalize_claim(text: str) -> str: 1294 - """ 1295 - Canonical claim normalization for deduplication. 1296 - MUST follow this algorithm exactly. 1297 - 1298 - Version: v1norm1 1299 - """ 1300 - import re 1301 - import unicodedata 1302 - 1303 - # 1. Unicode normalization (NFD) 1304 - text = unicodedata.normalize('NFD', text) 1305 - 1306 - # 2. Lowercase 1307 - text = text.lower() 1308 - 1309 - # 3. Remove diacritics 1310 - text = ''.join(c for c in text if unicodedata.category(c) != 'Mn') 1311 - 1312 - # 4. Normalize whitespace 1313 - text = re.sub(r'\s+', ' ', text) 1314 - text = text.strip() 1315 - 1316 - # 5. Remove punctuation except apostrophes in contractions 1317 - text = re.sub(r"[^\w\s']", '', text) 1318 - 1319 - # 6. Normalize common contractions 1320 - contractions = { 1321 - "don't": "do not", 1322 - "doesn't": "does not", 1323 - "didn't": "did not", 1324 - "can't": "cannot", 1325 - "won't": "will not", 1326 - "shouldn't": "should not", 1327 - "wouldn't": "would not", 1328 - "isn't": "is not", 1329 - "aren't": "are not", 1330 - "wasn't": "was not", 1331 - "weren't": "were not", 1332 - "haven't": "have not", 1333 - "hasn't": "has not", 1334 - "hadn't": "had not" 1335 - } 1336 - 1337 - for contraction, expansion in contractions.items(): 1338 - text = re.sub(r'\b' + contraction + r'\b', expansion, text) 1339 - 1340 - # 7. Remove remaining apostrophes 1341 - text = text.replace("'", "") 1342 - 1343 - # 8. Final whitespace normalization 1344 - text = re.sub(r'\s+', ' ', text) 1345 - text = text.strip() 1346 - 1347 - return text 501 +{{code language="yaml"}} 502 +openapi: 3.1.0 503 +info: 504 + title: FactHarbor POC1 API 505 + version: 0.9.105 506 +paths: 507 + /v1/analyze: 508 + post: 509 + summary: Create analysis job 510 + parameters: 511 + - in: header 512 + name: Idempotency-Key 513 + required: false 514 + schema: { type: string } 515 + requestBody: 516 + required: true 517 + content: 518 + application/json: 519 + schema: 520 + $ref: '#/components/schemas/AnalyzeRequest' 521 + responses: 522 + '202': 523 + description: Accepted 524 + content: 525 + application/json: 526 + schema: 527 + $ref: '#/components/schemas/JobCreated' 528 + '4XX': 529 + description: Error 530 + content: 531 + application/json: 532 + schema: 533 + $ref: '#/components/schemas/ErrorEnvelope' 534 + /v1/jobs/{job_id}: 535 + get: 536 + summary: Get job status 537 + parameters: 538 + - in: path 539 + name: job_id 540 + required: true 541 + schema: { type: string } 542 + responses: 543 + '200': 544 + description: OK 545 + content: 546 + application/json: 547 + schema: 548 + $ref: '#/components/schemas/Job' 549 + '404': 550 + description: Not Found 551 + content: 552 + application/json: 553 + schema: 554 + $ref: '#/components/schemas/ErrorEnvelope' 555 + delete: 556 + summary: Cancel job (best-effort) and delete artifacts 557 + parameters: 558 + - in: path 559 + name: job_id 560 + required: true 561 + schema: { type: string } 562 + responses: 563 + '204': { description: No Content } 564 + '404': 565 + description: Not Found 566 + content: 567 + application/json: 568 + schema: 569 + $ref: '#/components/schemas/ErrorEnvelope' 570 + /v1/jobs/{job_id}/events: 571 + get: 572 + summary: Job progress via SSE 573 + parameters: 574 + - in: path 575 + name: job_id 576 + required: true 577 + schema: { type: string } 578 + responses: 579 + '200': 580 + description: text/event-stream 581 + /v1/jobs/{job_id}/result: 582 + get: 583 + summary: Get final JSON result 584 + parameters: 585 + - in: path 586 + name: job_id 587 + required: true 588 + schema: { type: string } 589 + responses: 590 + '200': 591 + description: OK 592 + content: 593 + application/json: 594 + schema: 595 + $ref: '#/components/schemas/AnalysisResult' 596 + '409': 597 + description: Not ready 598 + content: 599 + application/json: 600 + schema: 601 + $ref: '#/components/schemas/ErrorEnvelope' 602 + /v1/jobs/{job_id}/report: 603 + get: 604 + summary: Download report (markdown) 605 + parameters: 606 + - in: path 607 + name: job_id 608 + required: true 609 + schema: { type: string } 610 + responses: 611 + '200': 612 + description: text/markdown 613 + '409': 614 + description: Not ready 615 + content: 616 + application/json: 617 + schema: 618 + $ref: '#/components/schemas/ErrorEnvelope' 619 + /v1/health: 620 + get: 621 + summary: Health check 622 + responses: 623 + '200': 624 + description: OK 625 + content: 626 + application/json: 627 + schema: 628 + type: object 629 + properties: 630 + status: { type: string } 631 +components: 632 + schemas: 633 + AnalyzeRequest: 634 + type: object 635 + required: [options] 636 + properties: 637 + input_url: { type: ['string', 'null'] } 638 + input_text: { type: ['string', 'null'] } 639 + options: 640 + $ref: '#/components/schemas/AnalyzeOptions' 641 + client: 642 + type: object 643 + properties: 644 + request_id: { type: string } 645 + AnalyzeOptions: 646 + type: object 647 + properties: 648 + max_claims: { type: integer, minimum: 1, maximum: 50, default: 5 } 649 + cache_preference: 650 + type: string 651 + enum: [prefer_cache, allow_partial, cache_only, skip_cache] 652 + default: prefer_cache 653 + browsing: 654 + type: string 655 + enum: [on, off] 656 + default: on 657 + output_report: { type: boolean, default: true } 658 + JobCreated: 659 + type: object 660 + required: [job_id, status, created_at, links] 661 + properties: 662 + job_id: { type: string } 663 + status: { type: string } 664 + created_at: { type: string } 665 + estimated_cost: 666 + type: object 667 + properties: 668 + credits: { type: integer } 669 + explain: { type: string } 670 + links: 671 + type: object 672 + properties: 673 + self: { type: string } 674 + events: { type: string } 675 + result: { type: string } 676 + report: { type: string } 677 + Job: 678 + type: object 679 + required: [job_id, status, created_at, updated_at, links] 680 + properties: 681 + job_id: { type: string } 682 + status: { type: string, enum: [QUEUED, RUNNING, SUCCEEDED, FAILED, CANCELED] } 683 + progress: 684 + type: object 685 + properties: 686 + stage: { type: string } 687 + stage_progress: { type: number, minimum: 0, maximum: 1 } 688 + message: { type: string } 689 + created_at: { type: string } 690 + updated_at: { type: string } 691 + links: 692 + type: object 693 + properties: 694 + events: { type: string } 695 + result: { type: string } 696 + report: { type: string } 697 + AnalysisResult: 698 + type: object 699 + required: [job_id, claim_extraction, claim_analyses, article_assessment] 700 + properties: 701 + job_id: { type: string } 702 + claim_extraction: 703 + type: object 704 + properties: 705 + normalization_version: { type: string } 706 + claims: 707 + type: array 708 + items: 709 + type: object 710 + properties: 711 + claim_hash: { type: string } 712 + claim_text: { type: string } 713 + canonical_claim_text: { type: string } 714 + confidence: { type: number } 715 + claim_analyses: 716 + type: array 717 + items: 718 + type: object 719 + properties: 720 + claim_hash: { type: string } 721 + scenarios: 722 + type: array 723 + items: 724 + type: object 725 + properties: 726 + scenario_id: { type: string } 727 + scenario_title: { type: string } 728 + verdict: 729 + type: object 730 + properties: 731 + verdict_label: { type: string } 732 + confidence: { type: number } 733 + rationale_bullets: 734 + type: array 735 + items: { type: string } 736 + article_assessment: 737 + type: object 738 + properties: 739 + overall_reasoning_quality: { type: string, enum: [high, medium, low] } 740 + summary: { type: string } 741 + key_risks: 742 + type: array 743 + items: { type: string } 744 + ErrorEnvelope: 745 + type: object 746 + properties: 747 + error: 748 + type: object 749 + properties: 750 + code: { type: string } 751 + message: { type: string } 752 + details: { type: object } 1348 1348 {{/code}} 1349 1349 1350 -**Normalization Examples:** 1351 - 1352 -|= Input |= Normalized Output 1353 -| "Biden won the 2020 election" | {{code}}biden won the 2020 election{{/code}} 1354 -| "Biden won the 2020 election!" | {{code}}biden won the 2020 election{{/code}} 1355 -| "Biden won the 2020 election" | {{code}}biden won the 2020 election{{/code}} 1356 -| "Biden didn't win the 2020 election" | {{code}}biden did not win the 2020 election{{/code}} 1357 -| "BIDEN WON THE 2020 ELECTION" | {{code}}biden won the 2020 election{{/code}} 1358 - 1359 -**Versioning:** Algorithm version is {{code}}v1norm1{{/code}}. Changes to the algorithm require a new version identifier. 1360 - 1361 -=== 5.1.2 Copyright & Data Retention Policy === 1362 - 1363 -**Evidence Excerpt Storage:** 1364 - 1365 -To comply with copyright law and fair use principles: 1366 - 1367 -**What We Store:** 1368 - 1369 -* **Metadata only:** Title, author, publisher, URL, publication date 1370 -* **Short excerpts:** Max 25 words per quote, max 3 quotes per evidence item 1371 -* **Summaries:** AI-generated bullet points (not verbatim text) 1372 -* **No full articles:** Never store complete article text beyond job processing 1373 - 1374 -**Total per Cached Claim:** 1375 - 1376 -* Scenarios: 2 per claim 1377 -* Evidence items: 6 per scenario (12 total) 1378 -* Quotes: 3 per evidence × 25 words = 75 words per item 1379 -* **Maximum stored verbatim text:** ~~900 words per claim (12 × 75) 1380 - 1381 -**Retention:** 1382 - 1383 -* Cache TTL: 90 days 1384 -* Job outputs: 24 hours (then archived or deleted) 1385 -* No persistent full-text article storage 1386 - 1387 -**Rationale:** 1388 - 1389 -* Short excerpts for citation = fair use 1390 -* Summaries are transformative (not copyrightable) 1391 -* Limited retention (90 days max) 1392 -* No commercial republication of excerpts 1393 - 1394 -**DMCA Compliance:** 1395 - 1396 -* Cache invalidation endpoint available for rights holders 1397 -* Contact: dmca@factharbor.org 1398 - 1399 1399 ---- 756 +End of page. 1400 1400 1401 -== Summary == 1402 - 1403 -This WYSIWYG preview shows the **structure and key sections** of the 1,515-line API specification. 1404 - 1405 -**Full specification includes:** 1406 - 1407 -* Complete API endpoints (7 total) 1408 -* All data schemas (ClaimExtraction, ClaimAnalysis, HolisticAssessment, Complete) 1409 -* Quality gates & validation rules 1410 -* LLM configuration for all 3 stages 1411 -* Implementation notes with code samples 1412 -* Testing strategy 1413 -* Cross-references to other pages 1414 - 1415 -**The complete specification is available in:** 1416 - 1417 -* FactHarbor_POC1_API_and_Schemas_Spec_v0_4_1_PATCHED.md (45 KB standalone) 1418 -* Export files (TEST/PRODUCTION) for xWiki import