graphite-engine 0.3.1__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {graphite_engine-0.3.1/src/graphite_engine.egg-info → graphite_engine-0.3.2}/PKG-INFO +7 -7
  2. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/README.md +2 -2
  3. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/pyproject.toml +6 -5
  4. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/__init__.py +24 -4
  5. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/alphaearth.py +4 -1
  6. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/weathernext.py +1 -0
  7. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/cache.py +1 -0
  8. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/claim.py +89 -33
  9. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/claim_store.py +49 -38
  10. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/confidence.py +101 -67
  11. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/domain.py +23 -13
  12. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/enums.py +13 -6
  13. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/evidence.py +19 -11
  14. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/llm.py +1 -0
  15. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/__init__.py +2 -1
  16. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/analyzer.py +49 -27
  17. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/extractor.py +31 -15
  18. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/report.py +22 -18
  19. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/retriever.py +23 -14
  20. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/verifier.py +54 -27
  21. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/rules.py +3 -0
  22. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/schemas.py +62 -15
  23. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/text.py +128 -33
  24. {graphite_engine-0.3.1 → graphite_engine-0.3.2/src/graphite_engine.egg-info}/PKG-INFO +7 -7
  25. graphite_engine-0.3.2/src/graphite_engine.egg-info/SOURCES.txt +32 -0
  26. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/tests/test_claim.py +30 -11
  27. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/tests/test_confidence.py +21 -14
  28. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/assembler.py +0 -299
  29. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/__init__.py +0 -1
  30. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/alphaearth_enricher.py +0 -125
  31. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/embedding_similarity.py +0 -193
  32. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/geo_evidence/__init__.py +0 -1
  33. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/geo_evidence/geo_foundation.py +0 -86
  34. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/graph.py +0 -182
  35. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/io.py +0 -194
  36. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenario.py +0 -157
  37. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenarios/__init__.py +0 -1
  38. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenarios/weathernext_forecast.py +0 -140
  39. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/simulate.py +0 -245
  40. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_graph_store.py +0 -57
  41. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_pipeline_core.py +0 -460
  42. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_propagation.py +0 -248
  43. graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_scenario.py +0 -196
  44. graphite_engine-0.3.1/src/graphite_engine.egg-info/SOURCES.txt +0 -48
  45. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/LICENSE +0 -0
  46. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/setup.cfg +0 -0
  47. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/__init__.py +0 -0
  48. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/py.typed +0 -0
  49. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite_engine.egg-info/dependency_links.txt +0 -0
  50. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite_engine.egg-info/requires.txt +0 -0
  51. {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite_engine.egg-info/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphite-engine
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: Open-source claim verification engine for agent-generated assertions in high-stakes domains
5
5
  Author: Min Jun Kim
6
6
  License: Apache-2.0
7
- Project-URL: Homepage, https://github.com/graf-research/graphite
8
- Project-URL: Documentation, https://github.com/graf-research/graphite#readme
9
- Project-URL: Repository, https://github.com/graf-research/graphite
10
- Project-URL: Issues, https://github.com/graf-research/graphite/issues
7
+ Project-URL: Homepage, https://github.com/minjun1/graphite-core
8
+ Project-URL: Documentation, https://github.com/minjun1/graphite-core#readme
9
+ Project-URL: Repository, https://github.com/minjun1/graphite-core
10
+ Project-URL: Issues, https://github.com/minjun1/graphite-core/issues
11
11
  Keywords: verification,claims,evidence,provenance,trust,graph
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
@@ -111,8 +111,8 @@ Dynamic: license-file
111
111
  Or from source:
112
112
 
113
113
  ```bash
114
- git clone https://github.com/graf-research/graphite.git
115
- cd graphite
114
+ git clone https://github.com/minjun1/graphite-core.git
115
+ cd graphite-core
116
116
  pip install -e ".[llm]"
117
117
  export GEMINI_API_KEY="your-api-key-here"
118
118
  python examples/quickstart_verification/run.py
@@ -73,8 +73,8 @@
73
73
  Or from source:
74
74
 
75
75
  ```bash
76
- git clone https://github.com/graf-research/graphite.git
77
- cd graphite
76
+ git clone https://github.com/minjun1/graphite-core.git
77
+ cd graphite-core
78
78
  pip install -e ".[llm]"
79
79
  export GEMINI_API_KEY="your-api-key-here"
80
80
  python examples/quickstart_verification/run.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graphite-engine"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Open-source claim verification engine for agent-generated assertions in high-stakes domains"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -37,13 +37,14 @@ dev = [
37
37
  ]
38
38
 
39
39
  [project.urls]
40
- Homepage = "https://github.com/graf-research/graphite"
41
- Documentation = "https://github.com/graf-research/graphite#readme"
42
- Repository = "https://github.com/graf-research/graphite"
43
- Issues = "https://github.com/graf-research/graphite/issues"
40
+ Homepage = "https://github.com/minjun1/graphite-core"
41
+ Documentation = "https://github.com/minjun1/graphite-core#readme"
42
+ Repository = "https://github.com/minjun1/graphite-core"
43
+ Issues = "https://github.com/minjun1/graphite-core/issues"
44
44
 
45
45
  [tool.setuptools.packages.find]
46
46
  where = ["src"]
47
+ exclude = ["graphite._archive", "graphite._archive.*"]
47
48
 
48
49
  [tool.setuptools.package-data]
49
50
  graphite = ["py.typed"]
@@ -14,20 +14,40 @@ Pipeline:
14
14
 
15
15
  # ── Core schemas ──
16
16
  from .schemas import ExtractedEdge, NodeRef, Provenance, InferenceBasis, ExtractionError
17
- from .enums import EdgeType, NodeType, SourceType, ConfidenceLevel, AssertionMode, EvidenceType
17
+ from .enums import (
18
+ EdgeType,
19
+ NodeType,
20
+ SourceType,
21
+ ConfidenceLevel,
22
+ AssertionMode,
23
+ EvidenceType,
24
+ )
18
25
  from .evidence import EvidencePacket, EvidenceData
19
26
 
20
27
  # ── Trust engine primitives ──
21
- from .claim import Claim, ClaimType, ClaimStatus, ClaimGranularity, ReviewState, ClaimOrigin
28
+ from .claim import (
29
+ Claim,
30
+ ClaimType,
31
+ ClaimStatus,
32
+ ClaimGranularity,
33
+ ReviewState,
34
+ ClaimOrigin,
35
+ )
22
36
  from .claim import ConfidenceFactor, ConfidenceResult
23
37
  from .claim_store import ClaimStore
24
38
  from .confidence import ConfidenceScorer
25
39
 
26
40
  # ── Domain plugin contracts ──
27
- from .domain import BaseFetcher, BaseExtractor, BasePipeline, DocumentContext, DomainSpec
41
+ from .domain import (
42
+ BaseFetcher,
43
+ BaseExtractor,
44
+ BasePipeline,
45
+ DocumentContext,
46
+ DomainSpec,
47
+ )
28
48
  from .domain import register_domain, get_domain, list_domains
29
49
 
30
50
  # ── Rules ──
31
51
  from .rules import BaseRuleEngine, RuleResult, ScoreBreakdown
32
52
 
33
- __version__ = "0.3.1"
53
+ __version__ = "0.3.2"
@@ -12,6 +12,7 @@ AlphaEarth Foundations:
12
12
  - Earth Engine dataset: GOOGLE/SATELLITE_EMBEDDING/V1/ANNUAL
13
13
  - GCS bucket: gs://alphaearth_foundations (Requester Pays)
14
14
  """
15
+
15
16
  import json
16
17
  import os
17
18
  from pathlib import Path
@@ -122,7 +123,9 @@ class AlphaEarthAdapter:
122
123
  Returns:
123
124
  numpy array of shape (64,)
124
125
  """
125
- cache_key = node_id or f"bbox_{bbox[0]:.4f}_{bbox[1]:.4f}_{bbox[2]:.4f}_{bbox[3]:.4f}"
126
+ cache_key = (
127
+ node_id or f"bbox_{bbox[0]:.4f}_{bbox[1]:.4f}_{bbox[2]:.4f}_{bbox[3]:.4f}"
128
+ )
126
129
 
127
130
  cached = self._read_cache(cache_key, year)
128
131
  if cached is not None:
@@ -12,6 +12,7 @@ WeatherNext 2:
12
12
  - Access: EE/BigQuery (requires data request form)
13
13
  - Note: Experimental dataset, not validated for real-world use
14
14
  """
15
+
15
16
  import json
16
17
  from pathlib import Path
17
18
  from typing import Any, Dict, List, Optional
@@ -4,6 +4,7 @@ graphite/cache.py — Extraction result caching.
4
4
  5-part cache key prevents pollution when anything changes:
5
5
  (source_id, content_hash, extractor_version, prompt_version, model_name)
6
6
  """
7
+
7
8
  import hashlib
8
9
  import json
9
10
  import os
@@ -10,6 +10,7 @@ dependency links.
10
10
  Pipeline:
11
11
  Extractor → Claim[] → (optional) Edge materialization → Graph
12
12
  """
13
+
13
14
  import hashlib
14
15
  from datetime import datetime, timezone
15
16
  from enum import Enum
@@ -25,25 +26,29 @@ from .schemas import Provenance
25
26
  # Claim Enums
26
27
  # ═══════════════════════════════════════
27
28
 
29
+
28
30
  class ClaimType(str, Enum):
29
31
  """What kind of assertion this claim makes."""
30
- RELATIONSHIP = "RELATIONSHIP" # "A supplies to B"
31
- ATTRIBUTE = "ATTRIBUTE" # "A's revenue is $10B"
32
- RISK_ASSERTION = "RISK_ASSERTION" # "A is exposed to China refining risk"
33
- DEPENDENCY = "DEPENDENCY" # "A depends on B for sole-source component"
32
+
33
+ RELATIONSHIP = "RELATIONSHIP" # "A supplies to B"
34
+ ATTRIBUTE = "ATTRIBUTE" # "A's revenue is $10B"
35
+ RISK_ASSERTION = "RISK_ASSERTION" # "A is exposed to China refining risk"
36
+ DEPENDENCY = "DEPENDENCY" # "A depends on B for sole-source component"
34
37
 
35
38
 
36
39
  class ClaimStatus(str, Enum):
37
40
  """Trust verdict for a claim — typically computed, not manually set."""
38
- SUPPORTED = "SUPPORTED" # Evidence meets threshold
39
- WEAK = "WEAK" # Some evidence but below threshold
40
- MIXED = "MIXED" # Supporting AND weakening evidence present
41
- UNSUPPORTED = "UNSUPPORTED" # No or rejected evidence
42
- PENDING_REVIEW = "PENDING_REVIEW" # Awaiting analyst decision
41
+
42
+ SUPPORTED = "SUPPORTED" # Evidence meets threshold
43
+ WEAK = "WEAK" # Some evidence but below threshold
44
+ MIXED = "MIXED" # Supporting AND weakening evidence present
45
+ UNSUPPORTED = "UNSUPPORTED" # No or rejected evidence
46
+ PENDING_REVIEW = "PENDING_REVIEW" # Awaiting analyst decision
43
47
 
44
48
 
45
49
  class ReviewState(str, Enum):
46
50
  """Analyst review workflow state."""
51
+
47
52
  UNREVIEWED = "UNREVIEWED"
48
53
  APPROVED = "APPROVED"
49
54
  REJECTED = "REJECTED"
@@ -52,11 +57,12 @@ class ReviewState(str, Enum):
52
57
 
53
58
  class ClaimOrigin(str, Enum):
54
59
  """How this claim was created."""
55
- EXTRACTOR = "EXTRACTOR" # Produced by an extraction pipeline
56
- AGENT = "AGENT" # Generated by an AI agent
57
- RULE_ENGINE = "RULE_ENGINE" # Inferred by scoring rules
58
- ANALYST = "ANALYST" # Manually entered by a human
59
- IMPORTED = "IMPORTED" # Bulk-imported from external system
60
+
61
+ EXTRACTOR = "EXTRACTOR" # Produced by an extraction pipeline
62
+ AGENT = "AGENT" # Generated by an AI agent
63
+ RULE_ENGINE = "RULE_ENGINE" # Inferred by scoring rules
64
+ ANALYST = "ANALYST" # Manually entered by a human
65
+ IMPORTED = "IMPORTED" # Bulk-imported from external system
60
66
 
61
67
 
62
68
  class ClaimGranularity(str, Enum):
@@ -66,6 +72,7 @@ class ClaimGranularity(str, Enum):
66
72
  SYNTHESIZED: combined from multiple atomic claims.
67
73
  THESIS: high-level investment/risk thesis for UI display.
68
74
  """
75
+
69
76
  ATOMIC = "ATOMIC"
70
77
  SYNTHESIZED = "SYNTHESIZED"
71
78
  THESIS = "THESIS"
@@ -75,6 +82,7 @@ class ClaimGranularity(str, Enum):
75
82
  # Confidence (explainable)
76
83
  # ═══════════════════════════════════════
77
84
 
85
+
78
86
  class ConfidenceFactor(BaseModel):
79
87
  """One factor contributing to the overall confidence score.
80
88
 
@@ -87,9 +95,16 @@ class ConfidenceFactor(BaseModel):
87
95
  explanation="3 independent sources confirm this claim",
88
96
  )
89
97
  """
90
- name: str = Field(description="Factor name: source_count, source_diversity, directness, recency, doc_quality, counter_penalty")
91
- raw_value: str = Field(description="Human-readable raw value, e.g. '3 sources', '2024-01-15'")
92
- contribution: float = Field(description="Normalized contribution to final score, e.g. +0.15 or -0.08")
98
+
99
+ name: str = Field(
100
+ description="Factor name: source_count, source_diversity, directness, recency, doc_quality, counter_penalty"
101
+ )
102
+ raw_value: str = Field(
103
+ description="Human-readable raw value, e.g. '3 sources', '2024-01-15'"
104
+ )
105
+ contribution: float = Field(
106
+ description="Normalized contribution to final score, e.g. +0.15 or -0.08"
107
+ )
93
108
  direction: str = Field(default="POSITIVE", description="POSITIVE or NEGATIVE")
94
109
  explanation: str = Field(description="Why this factor matters for this claim")
95
110
 
@@ -101,12 +116,19 @@ class ConfidenceResult(BaseModel):
101
116
  The level (HIGH/MEDIUM/LOW) is derived from the score for
102
117
  backwards compatibility with existing ConfidenceLevel enum.
103
118
  """
119
+
104
120
  score: float = Field(ge=0.0, le=1.0, description="Computed confidence 0.0 - 1.0")
105
- level: ConfidenceLevel = Field(description="Derived from score: HIGH >= 0.7, MEDIUM >= 0.4, LOW < 0.4")
106
- factors: List[ConfidenceFactor] = Field(default_factory=list, description="Explainable breakdown")
121
+ level: ConfidenceLevel = Field(
122
+ description="Derived from score: HIGH >= 0.7, MEDIUM >= 0.4, LOW < 0.4"
123
+ )
124
+ factors: List[ConfidenceFactor] = Field(
125
+ default_factory=list, description="Explainable breakdown"
126
+ )
107
127
 
108
128
  @classmethod
109
- def from_score(cls, score: float, factors: List[ConfidenceFactor] = None) -> "ConfidenceResult":
129
+ def from_score(
130
+ cls, score: float, factors: List[ConfidenceFactor] = None
131
+ ) -> "ConfidenceResult":
110
132
  """Create a ConfidenceResult from a raw score, deriving the level."""
111
133
  score = max(0.0, min(1.0, score))
112
134
  if score >= 0.7:
@@ -122,6 +144,7 @@ class ConfidenceResult(BaseModel):
122
144
  # Claim (the core primitive)
123
145
  # ═══════════════════════════════════════
124
146
 
147
+
125
148
  def _make_claim_id(
126
149
  subjects: List[str],
127
150
  predicate: str,
@@ -166,9 +189,15 @@ class Claim(BaseModel):
166
189
  supporting_evidence=[...],
167
190
  )
168
191
  """
192
+
169
193
  # Identity — deterministic from (subjects, predicate, objects)
170
- claim_id: str = Field(default="", description="Auto-computed if empty. Hash of (subjects, predicate, objects)")
171
- claim_text: str = Field(description="Human-readable claim: 'NVDA supplies GPUs to TSLA'")
194
+ claim_id: str = Field(
195
+ default="",
196
+ description="Auto-computed if empty. Hash of (subjects, predicate, objects)",
197
+ )
198
+ claim_text: str = Field(
199
+ description="Human-readable claim: 'NVDA supplies GPUs to TSLA'"
200
+ )
172
201
  claim_type: ClaimType
173
202
  granularity: ClaimGranularity = Field(
174
203
  default=ClaimGranularity.ATOMIC,
@@ -177,7 +206,9 @@ class Claim(BaseModel):
177
206
 
178
207
  # Structured triple
179
208
  subject_entities: List[str] = Field(description="NodeRef IDs: ['company:NVDA']")
180
- predicate: str = Field(description="Canonical relation: SUPPLIES_TO, DEPENDS_ON, EXPOSED_TO, etc.")
209
+ predicate: str = Field(
210
+ description="Canonical relation: SUPPLIES_TO, DEPENDS_ON, EXPOSED_TO, etc."
211
+ )
181
212
  object_entities: List[str] = Field(description="NodeRef IDs: ['company:TSLA']")
182
213
 
183
214
  # How this claim was asserted
@@ -321,8 +352,10 @@ class Claim(BaseModel):
321
352
  self.final_status = status
322
353
  self.override_reason = reason
323
354
  self.review_state = (
324
- ReviewState.APPROVED if status == ClaimStatus.SUPPORTED
325
- else ReviewState.REJECTED if status == ClaimStatus.UNSUPPORTED
355
+ ReviewState.APPROVED
356
+ if status == ClaimStatus.SUPPORTED
357
+ else ReviewState.REJECTED
358
+ if status == ClaimStatus.UNSUPPORTED
326
359
  else ReviewState.NEEDS_FOLLOWUP
327
360
  )
328
361
  self.reviewed_by = reviewer
@@ -336,7 +369,9 @@ class Claim(BaseModel):
336
369
  def is_overridden(self) -> bool:
337
370
  """True if an analyst explicitly set the status (Approved/Rejected/NeedsFollowup)."""
338
371
  return self.review_state in (
339
- ReviewState.APPROVED, ReviewState.REJECTED, ReviewState.NEEDS_FOLLOWUP
372
+ ReviewState.APPROVED,
373
+ ReviewState.REJECTED,
374
+ ReviewState.NEEDS_FOLLOWUP,
340
375
  )
341
376
 
342
377
  @property
@@ -348,6 +383,7 @@ class Claim(BaseModel):
348
383
  # LLM-Native Verification Report Models
349
384
  # ═══════════════════════════════════════
350
385
 
386
+
351
387
  class VerdictEnum(str, Enum):
352
388
  SUPPORTED = "SUPPORTED"
353
389
  CONFLICTED = "CONFLICTED"
@@ -362,28 +398,45 @@ class ArgumentVerdictEnum(str, Enum):
362
398
 
363
399
  class VerdictRationale(BaseModel):
364
400
  """Structured reasoning slots for meta-evaluations and transparent auditing."""
365
- contradiction_type: Optional[str] = Field(default=None, description="e.g., numeric mismatch, entity mismatch")
366
- missing_evidence_reason: Optional[str] = Field(default=None, description="Why the evidence was insufficient or lacking")
367
- temporal_alignment: Optional[str] = Field(default=None, description="e.g., stale evidence vs current claim timeline")
401
+
402
+ contradiction_type: Optional[str] = Field(
403
+ default=None, description="e.g., numeric mismatch, entity mismatch"
404
+ )
405
+ missing_evidence_reason: Optional[str] = Field(
406
+ default=None, description="Why the evidence was insufficient or lacking"
407
+ )
408
+ temporal_alignment: Optional[str] = Field(
409
+ default=None, description="e.g., stale evidence vs current claim timeline"
410
+ )
368
411
  text: str = Field(description="Free-form rationale from the LLM judge")
369
412
 
370
413
 
371
414
  class Verdict(BaseModel):
372
415
  """A claim-level judgment returned by the verifier pipeline."""
416
+
373
417
  claim_id: str
374
418
  claim_text: str
375
419
  verdict: VerdictEnum
376
420
  supporting_evidence_ids: List[str] = Field(default_factory=list)
377
421
  conflicting_evidence_ids: List[str] = Field(default_factory=list)
378
422
  rationale: VerdictRationale
379
- needs_human_review: bool = Field(default=False, description="Flag for high-risk or low-confidence verdicts to route to human queues")
380
- cited_span: Optional[str] = Field(default=None, description="The exact span from the evidence corpus cited to make the verdict")
423
+ needs_human_review: bool = Field(
424
+ default=False,
425
+ description="Flag for high-risk or low-confidence verdicts to route to human queues",
426
+ )
427
+ cited_span: Optional[str] = Field(
428
+ default=None,
429
+ description="The exact span from the evidence corpus cited to make the verdict",
430
+ )
381
431
  model_version: str = Field(default="")
382
- timestamp: str = Field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
432
+ timestamp: str = Field(
433
+ default_factory=lambda: datetime.now(timezone.utc).isoformat()
434
+ )
383
435
 
384
436
 
385
437
  class ArgumentVerdict(BaseModel):
386
438
  """An argument-level verification representing semantic logic jumps."""
439
+
387
440
  text: str = Field(description="The argument chain or conclusion being evaluated")
388
441
  verdict: ArgumentVerdictEnum
389
442
  rationale: VerdictRationale
@@ -392,6 +445,7 @@ class ArgumentVerdict(BaseModel):
392
445
 
393
446
  class VerificationReport(BaseModel):
394
447
  """Top-level review object aggregating the entire verification workflow."""
448
+
395
449
  document_id: str
396
450
  total_claims: int = 0
397
451
  supported_count: int = 0
@@ -399,7 +453,9 @@ class VerificationReport(BaseModel):
399
453
  insufficient_count: int = 0
400
454
  grounded_argument_count: int = 0
401
455
  conclusion_jump_count: int = 0
402
- risky_claim_ids: List[str] = Field(default_factory=list, description="Claim IDs flagged for human review")
456
+ risky_claim_ids: List[str] = Field(
457
+ default_factory=list, description="Claim IDs flagged for human review"
458
+ )
403
459
  evidence_coverage_score: float = 0.0
404
460
  verdicts: List[Verdict] = Field(default_factory=list)
405
461
  argument_verdicts: List[ArgumentVerdict] = Field(default_factory=list)
@@ -13,6 +13,7 @@ Key semantics:
13
13
  * Same source_id, different quote → append
14
14
  * Different source_id → always append
15
15
  """
16
+
16
17
  import json
17
18
  import sqlite3
18
19
  from typing import List, Optional, Set, Tuple
@@ -38,7 +39,7 @@ class ClaimStore:
38
39
  cursor = conn.cursor()
39
40
 
40
41
  # Main claims table
41
- cursor.execute('''
42
+ cursor.execute("""
42
43
  CREATE TABLE IF NOT EXISTS claims (
43
44
  claim_id TEXT PRIMARY KEY,
44
45
  claim_text TEXT,
@@ -52,11 +53,15 @@ class ClaimStore:
52
53
  confidence_score REAL,
53
54
  full_json TEXT
54
55
  )
55
- ''')
56
+ """)
56
57
 
57
58
  # Index for fast querying
58
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_claims_predicate ON claims(predicate)')
59
- cursor.execute('CREATE INDEX IF NOT EXISTS idx_claims_as_of_date ON claims(as_of_date)')
59
+ cursor.execute(
60
+ "CREATE INDEX IF NOT EXISTS idx_claims_predicate ON claims(predicate)"
61
+ )
62
+ cursor.execute(
63
+ "CREATE INDEX IF NOT EXISTS idx_claims_as_of_date ON claims(as_of_date)"
64
+ )
60
65
 
61
66
  conn.commit()
62
67
 
@@ -79,9 +84,7 @@ class ClaimStore:
79
84
  - Same source_id, different quote → append
80
85
  - Different source_id → always append
81
86
  """
82
- seen: Set[Tuple[str, str]] = {
83
- ClaimStore._evidence_key(ev) for ev in existing
84
- }
87
+ seen: Set[Tuple[str, str]] = {ClaimStore._evidence_key(ev) for ev in existing}
85
88
  merged = list(existing)
86
89
  for ev in incoming:
87
90
  key = ClaimStore._evidence_key(ev)
@@ -104,10 +107,12 @@ class ClaimStore:
104
107
  if existing is not None:
105
108
  # Merge evidence from the new claim into the existing one
106
109
  existing.supporting_evidence = self._merge_evidence(
107
- existing.supporting_evidence, claim.supporting_evidence,
110
+ existing.supporting_evidence,
111
+ claim.supporting_evidence,
108
112
  )
109
113
  existing.weakening_evidence = self._merge_evidence(
110
- existing.weakening_evidence, claim.weakening_evidence,
114
+ existing.weakening_evidence,
115
+ claim.weakening_evidence,
111
116
  )
112
117
  # Update mutable fields from the newer claim
113
118
  existing.claim_text = claim.claim_text or existing.claim_text
@@ -145,7 +150,8 @@ class ClaimStore:
145
150
  score = claim.confidence.score if claim.confidence else 0.0
146
151
  full_json = claim.model_dump_json()
147
152
 
148
- cursor.execute('''
153
+ cursor.execute(
154
+ """
149
155
  INSERT INTO claims (
150
156
  claim_id, claim_text, claim_type, granularity,
151
157
  subject_entities, predicate, object_entities,
@@ -157,19 +163,21 @@ class ClaimStore:
157
163
  computed_status=excluded.computed_status,
158
164
  confidence_score=excluded.confidence_score,
159
165
  full_json=excluded.full_json
160
- ''', (
161
- claim.claim_id,
162
- claim.claim_text,
163
- claim.claim_type.value,
164
- claim.granularity.value,
165
- subjects_str,
166
- claim.predicate,
167
- objects_str,
168
- claim.as_of_date,
169
- claim.computed_status.value,
170
- score,
171
- full_json
172
- ))
166
+ """,
167
+ (
168
+ claim.claim_id,
169
+ claim.claim_text,
170
+ claim.claim_type.value,
171
+ claim.granularity.value,
172
+ subjects_str,
173
+ claim.predicate,
174
+ objects_str,
175
+ claim.as_of_date,
176
+ claim.computed_status.value,
177
+ score,
178
+ full_json,
179
+ ),
180
+ )
173
181
 
174
182
  conn.commit()
175
183
 
@@ -182,36 +190,40 @@ class ClaimStore:
182
190
  """Retrieve a claim by its exact ID."""
183
191
  with sqlite3.connect(self.db_path) as conn:
184
192
  cursor = conn.cursor()
185
- cursor.execute('SELECT full_json FROM claims WHERE claim_id = ?', (claim_id,))
193
+ cursor.execute(
194
+ "SELECT full_json FROM claims WHERE claim_id = ?", (claim_id,)
195
+ )
186
196
  row = cursor.fetchone()
187
197
 
188
198
  if row:
189
199
  return Claim.model_validate_json(row[0])
190
200
  return None
191
201
 
192
- def search_claims(self,
193
- subject_contains: Optional[str] = None,
194
- object_contains: Optional[str] = None,
195
- predicate: Optional[str] = None,
196
- as_of_date: Optional[str] = None) -> List[Claim]:
202
+ def search_claims(
203
+ self,
204
+ subject_contains: Optional[str] = None,
205
+ object_contains: Optional[str] = None,
206
+ predicate: Optional[str] = None,
207
+ as_of_date: Optional[str] = None,
208
+ ) -> List[Claim]:
197
209
  """Search claims by components."""
198
- query = 'SELECT full_json FROM claims WHERE 1=1'
210
+ query = "SELECT full_json FROM claims WHERE 1=1"
199
211
  params = []
200
212
 
201
213
  if subject_contains:
202
- query += ' AND subject_entities LIKE ?'
203
- params.append(f'%{subject_contains}%')
214
+ query += " AND subject_entities LIKE ?"
215
+ params.append(f"%{subject_contains}%")
204
216
 
205
217
  if object_contains:
206
- query += ' AND object_entities LIKE ?'
207
- params.append(f'%{object_contains}%')
218
+ query += " AND object_entities LIKE ?"
219
+ params.append(f"%{object_contains}%")
208
220
 
209
221
  if predicate:
210
- query += ' AND predicate = ?'
222
+ query += " AND predicate = ?"
211
223
  params.append(predicate)
212
224
 
213
225
  if as_of_date:
214
- query += ' AND as_of_date = ?'
226
+ query += " AND as_of_date = ?"
215
227
  params.append(as_of_date)
216
228
 
217
229
  with sqlite3.connect(self.db_path) as conn:
@@ -301,8 +313,7 @@ class ClaimStore:
301
313
 
302
314
  # Must share at least one entity
303
315
  c_entities = set(
304
- e.upper().strip()
305
- for e in c.subject_entities + c.object_entities
316
+ e.upper().strip() for e in c.subject_entities + c.object_entities
306
317
  )
307
318
  if claim_entities & c_entities:
308
319
  conflicts.append(c)