Skip to content

Research Gap Detection

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

Research Gap Detection

Author: Ciprian Ștefan Pleșca

This page documents src/openlongevity/gaps.py, which implements ResearchGapDetector. The detector applies a fixed sequence of explicit, inspectable heuristics to a topic-filtered subset of EvidenceRecord objects, producing a list of ResearchGap instances. Every heuristic here is deterministic and rule-based — there is no statistical inference or machine learning involved, which is itself a design choice: a reviewer can read the source and know exactly why a gap was or was not flagged.

Selection step

Before any gap logic runs, detect(topic, records) filters the input to records whose title, endpoint, source, or tags contain the topic string (case-insensitive):

flowchart LR
    R[All EvidenceRecord inputs] --> F{"topic.casefold() in\ntitle+endpoint+source+tags?"}
    F -- no match for any record --> NoEvidence["ResearchGap('no_indexed_evidence', priority=high)"]
    F -- at least one match --> Selected[selected records]
    Selected --> G1[translational_gap check]
    Selected --> G2[validation_gap check]
    Selected --> G3[replication_gap check]
    Selected --> G4[concentration check]
    Selected --> G5[small_sample check]
    Selected --> G6[outdated_evidence check]
Loading

If no record matches the topic at all, the detector short-circuits and returns a single high-priority no_indexed_evidence gap — this is the only case where the function returns early rather than running all six checks.

The six gap categories

flowchart TD
    subgraph Inputs["selected: EvidenceRecord[] matching topic"]
        T[study_type set]
        RS[replication_status values]
        SRC[distinct sources]
        SS[sample_size values]
        MD[metadata.outdated flags]
    end

    T --> C1{"ANIMAL present\nAND\nCLINICAL or RCT absent?"}
    C1 -- yes --> GAP1["translational_gap\npriority: high, confidence: 0.81"]

    T --> C2{"IN_VITRO present\nAND ANIMAL absent?"}
    C2 -- yes --> GAP2["validation_gap\npriority: high, confidence: 0.78"]

    RS --> C3{"ALL records\nunknown/unreplicated?"}
    C3 -- yes --> GAP3["replication_gap\npriority: medium, confidence: 0.72"]

    SRC --> C4{"exactly 1 distinct source\nAND more than 1 record?"}
    C4 -- yes --> GAP4["concentration\npriority: medium, confidence: 0.68"]

    SS --> C5{"any sample_size < 50?"}
    C5 -- yes --> GAP5["small_sample\npriority: medium, confidence: 0.70\n(only the small-sample record IDs)"]

    MD --> C6{"any metadata.outdated == True?"}
    C6 -- yes --> GAP6["outdated_evidence\npriority: low, confidence: 0.65\n(only the outdated record IDs)"]
Loading

Reading each heuristic precisely

  1. translational_gap — fires when animal evidence exists for the topic but no clinical or RCT evidence does. This encodes a standard translational-research concern: findings in animal models that have not yet been tested in humans.
  2. validation_gap — fires when in-vitro evidence exists without animal-level validation, one step earlier in the same translational chain.
  3. replication_gap — fires only when every selected record has replication_status in {"unknown", "unreplicated"}. A single replicated record among many is enough to suppress this gap, which means the heuristic answers "is there any replicated evidence at all for this topic," not "how much of the evidence is replicated."
  4. concentration — fires when more than one record exists but they all originate from a single source string, flagging a lack of independent corroboration. Note this is single-source concentration, not single-provider concentration — source here is the free-form provenance/source field on EvidenceRecord, not the adapter name.
  5. small_sample — unlike the previous checks, this one attaches only the specific record IDs with sample_size < 50, not the full selected set, so a reviewer can go directly to the underrepresented records.
  6. outdated_evidence — reads a boolean flag from the free-form metadata dictionary (metadata.get("outdated") is True), again attaching only the flagged record IDs. This is the lowest-priority gap and depends entirely on an external refresh process populating that metadata key correctly.

Why these heuristics are non-exclusive

A single topic can trigger multiple gaps simultaneously — a topic represented only by three in-vitro studies from the same source, none replicated, would trigger validation_gap, replication_gap, and concentration all at once. The detector does not attempt to rank or deduplicate across categories; it surfaces every applicable signal and leaves prioritization to the reader, consistent with the project's broader stance that automated heuristics should surface, not resolve, research questions.

sequenceDiagram
    autonumber
    participant Client
    participant Det as ResearchGapDetector
    participant Eng as EvidenceEngine

    Client->>Det: detect("cellular senescence", records)
    Det->>Det: filter by topic match (_search_text)
    alt no matches
        Det-->>Client: [ResearchGap(no_indexed_evidence)]
    else matches found
        Det->>Det: compute types, replication set, sources, sample sizes, outdated flags
        Det->>Det: evaluate all 6 heuristics independently
        Det-->>Client: list[ResearchGap] (0 to 6 entries)
    end
    Note over Det,Eng: EvidenceEngine is accepted in the constructor\nbut is not currently used by detect() — reserved\nfor future score-aware gap heuristics.
Loading

An implementation detail worth flagging

ResearchGapDetector.__init__ accepts an EvidenceEngine instance and stores it as self.engine, but the current detect() method does not call any EvidenceEngine method — the gap heuristics operate directly on raw EvidenceRecord fields (study_type, replication_status, sample_size, metadata), not on grades or navigation scores. This is consistent with the module's design as a set of independent, literal heuristics, but it also means score-weighted gap detection (for example, "flag topics where all evidence has a navigation score below 0.3") is not yet implemented, despite the constructor's dependency suggesting a natural extension point.

Next

See Evidence-Grading-Engine for the scoring logic referenced above, or API-Reference for how gap detection is exposed over HTTP.

Clone this wiki locally