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.
- {graphite_engine-0.3.1/src/graphite_engine.egg-info → graphite_engine-0.3.2}/PKG-INFO +7 -7
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/README.md +2 -2
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/pyproject.toml +6 -5
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/__init__.py +24 -4
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/alphaearth.py +4 -1
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/weathernext.py +1 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/cache.py +1 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/claim.py +89 -33
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/claim_store.py +49 -38
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/confidence.py +101 -67
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/domain.py +23 -13
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/enums.py +13 -6
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/evidence.py +19 -11
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/llm.py +1 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/__init__.py +2 -1
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/analyzer.py +49 -27
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/extractor.py +31 -15
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/report.py +22 -18
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/retriever.py +23 -14
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/pipeline/verifier.py +54 -27
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/rules.py +3 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/schemas.py +62 -15
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/text.py +128 -33
- {graphite_engine-0.3.1 → graphite_engine-0.3.2/src/graphite_engine.egg-info}/PKG-INFO +7 -7
- graphite_engine-0.3.2/src/graphite_engine.egg-info/SOURCES.txt +32 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/tests/test_claim.py +30 -11
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/tests/test_confidence.py +21 -14
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/assembler.py +0 -299
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/__init__.py +0 -1
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/alphaearth_enricher.py +0 -125
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/features/embedding_similarity.py +0 -193
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/geo_evidence/__init__.py +0 -1
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/geo_evidence/geo_foundation.py +0 -86
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/graph.py +0 -182
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/io.py +0 -194
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenario.py +0 -157
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenarios/__init__.py +0 -1
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/scenarios/weathernext_forecast.py +0 -140
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/simulate.py +0 -245
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_graph_store.py +0 -57
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_pipeline_core.py +0 -460
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_propagation.py +0 -248
- graphite_engine-0.3.1/src/graphite/_archive/entity_graph/tests/test_scenario.py +0 -196
- graphite_engine-0.3.1/src/graphite_engine.egg-info/SOURCES.txt +0 -48
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/LICENSE +0 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/setup.cfg +0 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/adapters/__init__.py +0 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite/py.typed +0 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite_engine.egg-info/dependency_links.txt +0 -0
- {graphite_engine-0.3.1 → graphite_engine-0.3.2}/src/graphite_engine.egg-info/requires.txt +0 -0
- {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.
|
|
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/
|
|
8
|
-
Project-URL: Documentation, https://github.com/
|
|
9
|
-
Project-URL: Repository, https://github.com/
|
|
10
|
-
Project-URL: Issues, https://github.com/
|
|
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/
|
|
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/
|
|
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.
|
|
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/
|
|
41
|
-
Documentation = "https://github.com/
|
|
42
|
-
Repository = "https://github.com/
|
|
43
|
-
Issues = "https://github.com/
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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 =
|
|
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:
|
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
106
|
-
|
|
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(
|
|
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(
|
|
171
|
-
|
|
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(
|
|
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
|
|
325
|
-
|
|
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,
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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(
|
|
380
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
59
|
-
|
|
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,
|
|
110
|
+
existing.supporting_evidence,
|
|
111
|
+
claim.supporting_evidence,
|
|
108
112
|
)
|
|
109
113
|
existing.weakening_evidence = self._merge_evidence(
|
|
110
|
-
existing.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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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(
|
|
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(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 =
|
|
210
|
+
query = "SELECT full_json FROM claims WHERE 1=1"
|
|
199
211
|
params = []
|
|
200
212
|
|
|
201
213
|
if subject_contains:
|
|
202
|
-
query +=
|
|
203
|
-
params.append(f
|
|
214
|
+
query += " AND subject_entities LIKE ?"
|
|
215
|
+
params.append(f"%{subject_contains}%")
|
|
204
216
|
|
|
205
217
|
if object_contains:
|
|
206
|
-
query +=
|
|
207
|
-
params.append(f
|
|
218
|
+
query += " AND object_entities LIKE ?"
|
|
219
|
+
params.append(f"%{object_contains}%")
|
|
208
220
|
|
|
209
221
|
if predicate:
|
|
210
|
-
query +=
|
|
222
|
+
query += " AND predicate = ?"
|
|
211
223
|
params.append(predicate)
|
|
212
224
|
|
|
213
225
|
if as_of_date:
|
|
214
|
-
query +=
|
|
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)
|