Claims¶
Every name on this page is importable from sci_etl_core.claims. The whole
package is provisional: a name may change in a minor release, with a
CHANGELOG entry, until a known consumer depends on it. The
Claims and provenance guide shows how the pieces fit
together.
sci_etl_core.claims.models
¶
UncertaintyKind
module-attribute
¶
UncertaintyKind = Literal[
"sd", "se", "ci95", "iqr", "range", "unknown"
]
ExtractionStamp
dataclass
¶
What produced an extracted value: enough to find every value a fix must redo.
model is the LLM's name, empty when the client does not report one.
prompt_hash and schema_hash are :func:content_hash digests of the
system prompt and of the JSON Schema the answer had to match, empty when
there was no schema. extractor_version names the library release,
such as "sci-etl-core/0.6.0".
from_dict
classmethod
¶
from_dict(data: Mapping[str, Any]) -> ExtractionStamp
Rebuild a stamp from :meth:to_dict output.
EvidenceSpan
dataclass
¶
The sentence or sentences a claim was read from.
start and end are half-open character offsets into the text the
model was sent, computed by
:func:~sci_etl_core.claims.locate.locate_quote and snapped to sentence
boundaries. quote is the model's exact quote, kept for grounding
checks.
CanonicalValue
dataclass
¶
CanonicalValue(
*,
value: float,
uncertainty: float | None,
unit: str,
dimension: tuple[tuple[str, int], ...],
kind: str,
conversion_path: tuple[str, ...],
)
A normalized value that a downstream engine computed; sci-etl-core stores it and never converts.
Quantity
dataclass
¶
Quantity(
*,
verbatim: str,
value: float | None,
unit_text: str,
qualifier: Qualifier = "=",
uncertainty: float | None = None,
uncertainty_kind: UncertaintyKind | None = None,
scale_note: str = "",
canonical: CanonicalValue | None = None,
)
A number as the paper reports it.
verbatim is the text the value was read from, such as "2.9 ± 0.3
kpc". value is None when the text holds no single number.
uncertainty_kind
class-attribute
instance-attribute
¶
uncertainty_kind: UncertaintyKind | None = None
Statistics
dataclass
¶
Statistics(
*,
effect_metric: str | None = None,
effect_value: float | None = None,
ci_low: float | None = None,
ci_high: float | None = None,
p_value: float | None = None,
p_qualifier: Qualifier | None = None,
test_statistic: float | None = None,
test_kind: str | None = None,
degrees_of_freedom: float | None = None,
group_sizes: tuple[int, ...] = (),
)
The statistics a paper reports with a claim, each optional.
ClaimContext
dataclass
¶
Domain-specific conditions as sorted (key, value) pairs; sci-etl-core gives keys no meaning.
from_mapping
classmethod
¶
from_mapping(attributes: Mapping[str, str]) -> ClaimContext
Build a context from a mapping, sorting its pairs.
Claim
dataclass
¶
Claim(
*,
claim_id: str,
kind: ClaimKind,
subject: str,
predicate: str,
span: EvidenceSpan,
stamp: ExtractionStamp,
object: str = "",
polarity: Literal[-1, 0, 1] = 0,
modality: Modality = "asserted",
quantity: Quantity | None = None,
statistics: Statistics | None = None,
context: ClaimContext = ClaimContext(),
confidence: float | None = None,
)
One value or statement extracted from a paper, with its evidence and provenance.
A "measurement" needs a quantity and an "assertion" an
object. polarity is 1 for a positive statement, -1 for a
negated one, and 0 when it has no direction. claim_id is
:func:make_claim_id of the identifying fields: the same claim extracted by
another model has the same id and a different stamp.
context
class-attribute
instance-attribute
¶
context: ClaimContext = field(default_factory=ClaimContext)
from_draft
classmethod
¶
from_draft(
draft: ClaimDraft,
*,
span: EvidenceSpan,
stamp: ExtractionStamp,
) -> Claim
Build a trusted claim from a validated draft, the evidence span code located, and a stamp.
Raises:
| Type | Description |
|---|---|
ValueError
|
The draft is a measurement without a quantity or an assertion without an object. |
to_row
¶
Return the claim as one flat, JSON-friendly mapping, the shape every store and table uses.
content_hash
¶
Return the SHA-256 hex digest of value serialized as compact JSON with sorted keys.
A string is hashed as its JSON string. Equal mappings give equal digests whatever their key order, so one schema always has one hash.
make_claim_id
¶
make_claim_id(
*,
record_id: str,
kind: str,
subject: str,
predicate: str,
object: str,
polarity: int,
context: ClaimContext,
quantity: Quantity | None,
span: EvidenceSpan,
schema_hash: str,
) -> str
Return a claim's id: a digest of the fields that make it one claim.
The id covers the record, kind, subject, predicate, object, polarity,
context, the quantity's verbatim text with whitespace removed, the span
offsets, and the schema hash. It leaves out the model, prompt, and library
version, so re-extracting a paper with another model keeps every id whose
fields match. Mentions are compared exactly: "DF44" and "DF 44" are
different subjects.
sci_etl_core.claims.drafts
¶
QuantityDraft
¶
Bases: BaseModel
A number as the model reads it from the paper.
model_config
class-attribute
instance-attribute
¶
verbatim
class-attribute
instance-attribute
¶
verbatim: str = Field(
description="The value exactly as the paper writes it, with its unit"
)
value
class-attribute
instance-attribute
¶
value: float | None = Field(
default=None,
description="The number, or null when the text holds no single number",
)
unit_text
class-attribute
instance-attribute
¶
unit_text: str = Field(
default="",
description="The unit exactly as the paper writes it",
)
qualifier
class-attribute
instance-attribute
¶
qualifier: Literal['=', '<', '<=', '>', '>=', '~'] = '='
StatisticsDraft
¶
ClaimDraft
¶
Bases: BaseModel
What the model returns for one claim; validated once, then turned into a claim.
:meth:~sci_etl_core.claims.models.Claim.from_draft builds the claim.
quote is the sentence the claim was read from, copied from the paper.
The model never supplies ids, offsets, or provenance; code computes them.
Subclass it to narrow predicate or the context keys for a field of
science, and pass the subclass as the claim extractor's schema.
model_config
class-attribute
instance-attribute
¶
subject
class-attribute
instance-attribute
¶
subject: str = Field(
description="What the claim is about, as the paper names it"
)
predicate
class-attribute
instance-attribute
¶
predicate: str = Field(
description="The property measured or the relation asserted"
)
object
class-attribute
instance-attribute
¶
object: str = Field(
default="",
description="For an assertion, what the subject is related to",
)
modality
class-attribute
instance-attribute
¶
modality: Literal["asserted", "hedged", "speculative"] = (
"asserted"
)
context
class-attribute
instance-attribute
¶
context: dict[str, str] = Field(
default_factory=dict,
description="Conditions such as band or sample",
)
sci_etl_core.claims.locate
¶
sentence_bounds
¶
Split text into sentences tuned for scientific prose, as half-open (start, end) offsets.
A sentence ends at ., !, or ? followed by whitespace and a
character that is not a lowercase letter, and at every blank line. A
period does not end a sentence after a common abbreviation such as
et al., Fig., Eq., Ref., or cf., after an initialism
such as R.A., e.g., or i.e., or inside a number such as
0.5. Offsets exclude the whitespace between sentences.
locate_quote
¶
Return the sentence-snapped offsets of quote in text and how closely it matched.
Matching is exact after collapsing whitespace first, which gives a ratio
of 1.0. Otherwise each sentence, and each pair of adjacent sentences,
is aligned with the quote through :mod:difflib, counting runs of three
or more matching characters. The ratio compares the matched characters
with the length of the quote and of the stretch of text they span, so a
quote scattered over a long candidate scores low. The best candidate at
min_ratio or above wins, and of equal candidates the shortest. The
offsets cover whole sentences, so two models that quote different parts
of one sentence get the same span. None means the quote is not in the
text, so the claim is ungrounded.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
sci_etl_core.claims.extractor_async
¶
AsyncLLMClaimExtractor
¶
AsyncLLMClaimExtractor(
llm_client: AsyncLLMClient,
system_prompt: str,
*,
schema: type[ClaimDraft] = ClaimDraft,
result_key: str = "claims",
validator: RecordValidator | None = None,
rejections: AsyncRejectionStore | None = None,
min_ratio: float = 0.9,
html_parser: Parser | None = None,
max_chars: int = 120000,
max_tokens: int | None = None,
timeout: int = 120,
now: Callable[[], datetime] | None = None,
)
Bases: AsyncEntityExtractor[Claim]
Extract claims with their evidence and provenance from a record's full text.
One LLM call per record returns :class:~sci_etl_core.claims.drafts.ClaimDraft
objects, validated against schema through an inner
:class:~sci_etl_core.llm.extraction_async.AsyncLLMEntityExtractor, so
structured output, validation, and the schema's place in the LLM cache key
all apply. Each draft's quote is then located in the text the model was
sent; a draft whose quote cannot be found is ungrounded, logged, and put in
rejections with the code "ungrounded". Every other draft becomes a
:class:~sci_etl_core.claims.models.Claim stamped with the model, prompt,
schema, and library version.
A claim cannot be built without its record, so requires_record is
True: call :meth:extract_record. An extractor that wraps this one
must delegate to its extract_record.
Configure the extractor.
validator sees each draft as a dict after the schema check.
min_ratio is the share of a quote's characters that must be found
in one or two adjacent sentences of the text for a fuzzy match. The
other arguments are those of
:class:~sci_etl_core.llm.extraction_async.AsyncLLMEntityExtractor.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
answer_schema
property
¶
The JSON Schema requested for the whole answer.
extract
async
¶
Refuse: a claim needs its record.
Raises:
| Type | Description |
|---|---|
TypeError
|
Always; call :meth: |
extract_record
async
¶
Extract the record's grounded claims.
Raises:
| Type | Description |
|---|---|
LLMError
|
As for
:meth: |
ClaimStoreError
|
The rejection store could not be written. |
sci_etl_core.claims.store_base
¶
ClaimChange
dataclass
¶
ClaimChangePage
dataclass
¶
ClaimChangePage(
*, changes: tuple[ClaimChange, ...], next_revision: int
)
Changes after a revision, oldest first; next_revision is where the next page starts.
AsyncClaimStore
¶
Bases: ABC
An accumulating store of claims, replaced one record at a time.
Every write records a change with a new revision in the same transaction, so a downstream consumer reads what changed by revision, which only grows and never depends on clocks.
replace_record
abstractmethod
async
¶
Replace every claim of record_id in one transaction and return the new revision.
An empty claims deletes the record's claims and still records a
change. Claims with an id already seen in claims are dropped, and
the number dropped is logged.
Raises:
| Type | Description |
|---|---|
ValueError
|
A claim's |
ClaimStoreError
|
The store cannot be written. |
delete_record
abstractmethod
async
¶
Delete every claim of record_id and return the new revision.
claims_for_records
abstractmethod
async
¶
Return the stored claims of each record in record_ids that has any, in stored order.
changes_since
abstractmethod
async
¶
changes_since(
revision: int, limit: int = 1000
) -> ClaimChangePage
Return up to limit changes with a revision above revision, oldest first.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
unique_claims
¶
Check that every claim belongs to record_id and keep the first claim of each id.
Raises:
| Type | Description |
|---|---|
ValueError
|
A claim's |
sci_etl_core.claims.store_memory
¶
InMemoryClaimStore
¶
sci_etl_core.claims.store_sqlite_async
¶
AsyncSqliteClaimStore
¶
Bases: AsyncClaimStore
A claim store kept in a SQLite file.
Claims are indexed by record, by (subject, predicate, object), by
canonical kind and value, and by context pair. The file and its parent
folder are created on first use, foreign keys are enforced on every
connection, and the file records its schema version in
PRAGMA user_version. Every SQLite failure, including a file written by
a newer sci-etl-core, raises
:class:~sci_etl_core.exceptions.ClaimStoreError. Close it with
:meth:aclose; a later call reopens it. Use each instance from one event
loop.
claims_for_records
async
¶
sci_etl_core.claims.exporter_async
¶
AsyncClaimStoreExporter
¶
AsyncClaimStoreExporter(store: AsyncClaimStore)
Bases: AsyncExporter[Claim]
Store each record's claims in an :class:~sci_etl_core.claims.store_base.AsyncClaimStore.
write replaces the record's claims in one transaction and returns once
they are committed, so durable_writes is True. The last write for
a record wins, and a write with no claims clears the record's claims.
When write raises :class:~sci_etl_core.exceptions.ClaimStoreError,
the pipeline fails the record: it is not marked processed, counts one
failed attempt, and is retried on the next run. After max_attempts
counted attempts it is quarantined at listing time like any failing
record. This is not a memory fault: the pipeline absorbs only
:data:~sci_etl_core.ingest_protocol.MEMORY_FAULTS, and this exporter
never raises one.
write
async
¶
Replace record's claims with entities.
Raises:
| Type | Description |
|---|---|
ClaimStoreError
|
The store could not be written. |
ValueError
|
A claim belongs to another record. |
sci_etl_core.claims.rejections
¶
RejectedEntity
dataclass
¶
RejectedEntity(
*,
entry_id: str,
record_id: str,
entity: dict[str, Any],
violations: tuple[Violation, ...],
stamp: ExtractionStamp | None,
quote: str = "",
created_at: str,
)
An extracted entity that was not kept, with every reason, for review.
entry_id is :func:rejection_id of the other identifying fields, so
rejecting the same entity again in a later run gives the same entry.
quote is the evidence the model quoted, empty when it quoted none.
created_at is an ISO 8601 time with a UTC offset.
AsyncRejectionStore
¶
Bases: ABC
Storage for rejected entities that a reviewer resolves later.
:class:~sci_etl_core.llm.extraction_async.AsyncLLMEntityExtractor puts
every entity its schema or validator rejects here when given
rejections=. A rejected entity is kept for review; it is unrelated
to a quarantined record, one the pipeline skips after it failed
max_attempts times.
put
abstractmethod
async
¶
put(entries: Sequence[RejectedEntity]) -> None
Store entries; an entry whose entry_id is already stored is left as it is, resolution included.
Raises:
| Type | Description |
|---|---|
ClaimStoreError
|
The store cannot be written. |
unresolved
abstractmethod
async
¶
unresolved(
limit: int = 100, after: str | None = None
) -> list[RejectedEntity]
Return up to limit unresolved entries in entry_id order, after the entry_id after.
Raises:
| Type | Description |
|---|---|
ValueError
|
|
resolve
abstractmethod
async
¶
Record a reviewer's resolution of an entry, and the corrected entity if there is one.
Raises:
| Type | Description |
|---|---|
KeyError
|
No entry has |
count
abstractmethod
async
¶
Return how many entries are stored, or only how many are unresolved.
InMemoryRejectionStore
¶
AsyncSqliteRejectionStore
¶
Bases: AsyncRejectionStore
A rejection store kept in a SQLite file, so a review can span runs.
The file and its parent folder are created on first use, and the file
records its schema version in PRAGMA user_version. Every SQLite
failure, including a file written by a newer sci-etl-core, raises
:class:~sci_etl_core.exceptions.ClaimStoreError. Close it with
:meth:aclose. Use each instance from one event loop.