Skip to content

Domain Model

CIPRIAN STEFAN PLESCA edited this page Sep 16, 2026 · 1 revision

Domain Model

Author: Ciprian Ștefan Pleșca

This page documents the typed core defined in src/openlongevity/models.py, plus the closely related Publication/Provenance shapes from providers/base.py. These are frozen (immutable) dataclasses: once constructed, a record cannot be mutated in place, which makes accidental silent corruption of scientific data structurally harder.

Class diagram

classDiagram
    class StudyType {
        <<enumeration>>
        SYSTEMATIC_REVIEW
        RCT
        CLINICAL
        OBSERVATIONAL
        ANIMAL
        IN_VITRO
        COMPUTATIONAL
    }

    class RetractionStatus {
        <<enumeration>>
        ACTIVE
        CORRECTED
        EXPRESSION_OF_CONCERN
        RETRACTED
        UNKNOWN
    }

    class FindingDirection {
        <<enumeration>>
        POSITIVE
        NULL
        NEGATIVE
        MIXED
    }

    class ReviewStatus {
        <<enumeration>>
        UNREVIEWED
        MACHINE_EXTRACTED
        HUMAN_REVIEWED
        VERIFIED
        DISPUTED
    }

    class EvidenceRecord {
        +str identifier
        +str title
        +StudyType study_type
        +str species
        +str endpoint
        +str source
        +str~ publication_date
        +int~ sample_size
        +float confidence
        +tuple~str~ limitations
        +str replication_status
        +RetractionStatus retraction_status
        +tuple~str~ tags
        +dict metadata
        +FindingDirection direction
        +ReviewStatus review_status
        +str~ reviewed_by
        +tuple provenance_history
        +__post_init__() validates identifier, title, source, confidence range, sample_size
    }

    class ResearchGap {
        +str topic
        +str kind
        +str rationale
        +str priority
        +tuple~str~ supporting_record_ids
        +float confidence
        +dict details
    }

    class Contradiction {
        +str topic
        +tuple~str~ supporting_record_ids
        +tuple~str~ contradicting_record_ids
        +str explanation
    }

    class Provenance {
        +str source_provider
        +str source_identifier
        +str source_url
        +str retrieved_at
        +str~ source_updated_at
        +str~ license
        +str~ checksum
        +str normalization_version
        +str parser_version
    }

    class Publication {
        +str identifier
        +str title
        +str abstract
        +tuple~str~ authors
        +str~ journal
        +str~ publication_date
        +str~ doi
        +tuple~str~ publication_types
        +tuple~str~ mesh_terms
        +int~ citation_count
        +Provenance~ provenance
        +str retraction_status
        +tuple corrections
    }

    EvidenceRecord --> StudyType
    EvidenceRecord --> RetractionStatus
    EvidenceRecord --> FindingDirection
    EvidenceRecord --> ReviewStatus
    Publication --> Provenance
Loading

Design rationale

Why frozen dataclasses instead of mutable models

EvidenceRecord, Publication, Provenance, ResearchGap, and Contradiction are all declared with @dataclass(frozen=True). In a research-evidence system, in-place mutation of a graded record is a correctness hazard: a caller could hold a reference, grade it, and then have the underlying values change without re-grading. Immutability forces any transformation (for example, incrementing a revision) to happen through an explicit, auditable code path — in this codebase, that path is PublicationRepository, which constructs new payload dictionaries rather than mutating a live Publication.

Invariant enforcement at construction time

EvidenceRecord.__post_init__ enforces three invariants unconditionally:

flowchart LR
    A[EvidenceRecord constructed] --> B{identifier, title, source all non-empty?}
    B -- no --> E1[raise ValueError]
    B -- yes --> C{0.0 <= confidence <= 1.0 ?}
    C -- no --> E2[raise ValueError]
    C -- yes --> D{sample_size is None or >= 0 ?}
    D -- no --> E3[raise ValueError]
    D -- yes --> OK[Valid, immutable record]
Loading

This means an EvidenceRecord cannot exist in an invalid state anywhere in the system — there is no code path that produces a record with a negative sample size or an out-of-range confidence value, because the object itself refuses to be built.

Separation of EvidenceRecord from Publication

A deliberate modeling choice is that EvidenceRecord (an evidentiary claim: a study design, a species, an endpoint, a confidence value) is a distinct type from Publication (a bibliographic record with a title, authors, and a DOI). The README states this directly: "A publication's bibliographic envelope is insufficient to establish that a specific outcome was reported accurately." In the current implementation, only Publication records flow through the persistence layer; EvidenceRecord instances exist solely as demonstration fixtures inside api.py. Any future feature that extracts an EvidenceRecord from a Publication would need its own provenance and review workflow — the type boundary already anticipates that requirement.

ReviewStatus as a documented but not-yet-wired lifecycle

ReviewStatus (UNREVIEWEDMACHINE_EXTRACTEDHUMAN_REVIEWED / VERIFIED / DISPUTED) models a review pipeline that the README explicitly calls a "proposed" future integration, illustrated in the research-pipeline diagram as Human review pipeline: proposed -. future integration .-> D. Encoding the enum now, ahead of the pipeline that will populate it, lets every consumer of EvidenceRecord already branch on review state defensively, even before human review is implemented.

Provenance as a first-class type

Provenance is attached to every Publication and is the mechanism by which a normalized record remains traceable to its origin:

Field Purpose
source_provider Which adapter produced this record (pubmed, europe_pmc, …)
source_identifier The upstream identifier (e.g., a PMID)
source_url A resolvable link back to the original record
retrieved_at Retrieval timestamp — explicitly excluded from the content hash so re-fetching identical content does not create a false revision
checksum SHA-256 of the canonicalized source payload, used to detect real content changes
normalization_version / parser_version Lets a consumer know which transformation logic produced this shape, so a later parser fix can be distinguished from a change in the underlying data

PublicationRepository.save refuses to persist any Publication whose provenance is None or whose checksum is empty — provenance is not optional metadata, it is a precondition for storage.

Next

See Evidence-Grading-Engine for how EvidenceRecord values are turned into grades and scores, or Persistence-and-Data-Governance for how Publication/Provenance are persisted with revision history.

Clone this wiki locally