Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Target Safety and Therapeutic-Window Pre-screen Handoff

## Status

- Branch: `task_20260804_target-safety-prescreen`
- Base: latest `origin/main` at task start
- Review: implementation complete; PR and ChatGPT review are required before merge
- Data boundary: no source data, cache, result, model weight, or runtime output in the repository

## Scope

This GenModule is a target-level public-evidence pre-screen for ADC development.
It asks whether public evidence contains a target-intrinsic hazard strong enough
to kill, hold, or downgrade investment before antibody discovery and ADC assembly.
It does not claim product-specific therapeutic-window prediction.

## Implemented

- Six evidence axes: normal tissue expression, surface accessibility, antigen density, soluble antigen/shedding/sink, existing modality toxicity, and tissue consequence/recoverability.
- Evidence levels `A/B/C/D/U` and explicit risk directions.
- Fatal-first rules for critical surface hazard, confirmed severe on-target toxicity, non-lower normal density, clinically demonstrated sink/exposure failure, and no exploitable differential.
- Decision semantics: `KILL`, `HOLD`, `CONDITIONAL_GO`, `GO`.
- Unknown, unresolved, and conflicting claims remain visible and produce next-experiment references.
- All cross-boundary identities and evidence references require `external:` references.
- Runtime location is declared as `${BIOWORKSPACE_ROOT}/DATA/target_safety_therapeutic_window_prescreen/{raw,processed,result}`; no runtime writer is enabled in the repository.

## Validation

- Module tests pass.
- Full suite: 212 tests pass.
- `scripts/verify_repository_boundary.sh` passes.
- `git diff --check` passes.
- No `__pycache__` directory remains.

## Known limitations

- Evidence retrieval, source normalization, citation resolution, scoring calibration, and persistence remain external runtime responsibilities.
- The first ruleset is deterministic and conservative; it is not a clinical safety model and must not be used as a product-level therapeutic-window claim.
- The next implementation phase should add an external runtime adapter and benchmark fixtures under `DATA`, only after this contract PR is reviewed.
4 changes: 4 additions & 0 deletions genmodules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ are not lifecycle stages or Gate implementations.
- `gen_indication_endpoint_target@0.1.0`: defines data-free contracts for
constrained ADC indication, endpoint, and target opportunity generation;
generation, evaluation, ranking, and evidence remain external.
- `target_safety_therapeutic_window_prescreen@0.1.0`: applies conservative,
fatal-first rules to externally supplied public-evidence claims for target-
intrinsic ADC safety pre-screening; it does not predict a product-specific
therapeutic window.

## Repository boundary

Expand Down
46 changes: 46 additions & 0 deletions genmodules/target_safety_therapeutic_window_prescreen/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Public-Evidence Target Safety and Therapeutic-Window Pre-screen Engine

This GenModule performs a conservative, target-level ADC safety pre-screen from
already-normalized public evidence. It does **not** predict a product-specific
therapeutic window and does not replace Gate evaluation, toxicology, or human
decision-making.

## Six evidence axes

1. Normal-tissue and cell-type expression.
2. Surface localization and vascular accessibility.
3. Normal-cell antigen density.
4. Soluble antigen, shedding, and target sink.
5. Existing modality exposure and toxicity attribution.
6. Tissue consequence and recoverability.

Evidence levels are `A` (human causal), `B` (human protein/cell-resolved), `C`
(multi-omic concordance), `D` (single or indirect), and `U` (unknown).
Unknown remains unresolved; it is never converted into safety.

## Decision semantics

The evaluator applies fatal flags first:

- `KILL`: a defined target-level fatal condition is supported.
- `HOLD`: critical evidence is unknown, conflicting, or unresolved.
- `CONDITIONAL_GO`: no fatal condition and a plausible exploitable differential
exists, with explicit mitigation work.
- `GO`: no public target-intrinsic fatal flaw was found; this is not proof of a
therapeutic window.

## Runtime boundary

The package is pure and in-memory. Evidence claims carry only `external:`
references. A runtime may resolve those references under:

```text
${BIOWORKSPACE_ROOT}/DATA/target_safety_therapeutic_window_prescreen/
├── raw/ immutable source downloads and manifests
├── processed/ normalized evidence tables and provenance
└── result/ run-specific assessment packages and reports
```

The repository contains no source data, database, cache, result, model weight,
or runtime artifact. The external runtime must record source versions,
checksums, policy version, code commit, and unresolved evidence.
29 changes: 29 additions & 0 deletions genmodules/target_safety_therapeutic_window_prescreen/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Public-evidence target safety pre-screen contracts and conservative rules."""

from .contracts import (
AssessmentRequest,
AssessmentResult,
Criticality,
Decision,
EvidenceAxis,
EvidenceClaim,
EvidenceLevel,
FatalFlag,
RiskDirection,
TargetProfile,
)
from .engine import assess_target

__all__ = [
"AssessmentRequest",
"AssessmentResult",
"Criticality",
"Decision",
"EvidenceAxis",
"EvidenceClaim",
"EvidenceLevel",
"FatalFlag",
"RiskDirection",
"TargetProfile",
"assess_target",
]
208 changes: 208 additions & 0 deletions genmodules/target_safety_therapeutic_window_prescreen/contracts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
"""Data-free contracts for target-level ADC safety pre-screening.

The module never reads evidence and never persists a record. Runtime evidence is
represented by external references so an execution service can resolve it from
``DATA`` or another approved workspace.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import StrEnum
import re
from typing import Final


MODULE_VERSION: Final = "0.1.0"
CONTRACT_VERSION: Final = "0.1.0"
_EXTERNAL_REF = re.compile(r"^external:[^\s]+$")
_GENE_SYMBOL = re.compile(r"^[A-Za-z0-9][A-Za-z0-9-]*$")


def _external(value: str, label: str) -> None:
if not isinstance(value, str) or _EXTERNAL_REF.fullmatch(value) is None:
raise ValueError(f"{label} must use the external:<id> form")


class EvidenceAxis(StrEnum):
NORMAL_TISSUE_EXPRESSION = "normal_tissue_expression"
SURFACE_ACCESSIBILITY = "surface_accessibility"
ANTIGEN_DENSITY = "antigen_density"
SOLUBLE_SINK = "soluble_antigen_shedding_sink"
EXISTING_MODALITY_TOXICITY = "existing_modality_toxicity"
TISSUE_CONSEQUENCE = "tissue_consequence_recoverability"


class EvidenceLevel(StrEnum):
A = "A" # Human causal evidence.
B = "B" # Human tissue, protein-level, cell-resolved evidence.
C = "C" # Multi-omic concordance.
D = "D" # Single-source or indirect evidence.
U = "U" # Unknown.


class RiskDirection(StrEnum):
SUPPORTS_SAFETY = "supports_safety"
SUPPORTS_RISK = "supports_risk"
CONFLICTING = "conflicting"
UNKNOWN = "unknown"


class Criticality(StrEnum):
NON_CRITICAL = "non_critical"
REGENERATIVE = "regenerative"
CRITICAL_REVERSIBLE = "critical_reversible"
CRITICAL_NON_REGENERATIVE = "critical_non_regenerative"
UNKNOWN = "unknown"


class Decision(StrEnum):
GO = "GO"
CONDITIONAL_GO = "CONDITIONAL_GO"
HOLD = "HOLD"
KILL = "KILL"


class FatalFlag(StrEnum):
CRITICAL_SURFACE_HAZARD = "critical_surface_hazard"
CONFIRMED_ON_TARGET_TOXICITY = "confirmed_severe_on_target_toxicity"
NORMAL_DENSITY_NOT_LOWER = "normal_density_not_lower_than_tumor"
CLINICAL_SINK_EXPOSURE_FAILURE = "clinical_sink_exposure_failure"
NO_EXPLOITABLE_DIFFERENTIAL = "no_exploitable_target_differential"


@dataclass(frozen=True)
class TargetProfile:
"""Target and proposed modality context; no sequence or evidence payload."""

target_ref: str
gene_symbol: str
protein_name: str | None = None
modality: str = "ADC"
cancer_context_ref: str | None = None
payload_class: str | None = None
epitope_ref: str | None = None

def __post_init__(self) -> None:
_external(self.target_ref, "target_ref")
if not _GENE_SYMBOL.fullmatch(self.gene_symbol):
raise ValueError("gene_symbol must be a compact gene/protein symbol")
if self.cancer_context_ref is not None:
_external(self.cancer_context_ref, "cancer_context_ref")
if self.epitope_ref is not None:
_external(self.epitope_ref, "epitope_ref")
if self.modality != "ADC":
raise ValueError("this pre-screen currently supports modality=ADC only")


@dataclass(frozen=True)
class EvidenceClaim:
"""One externally stored observation or synthesis claim."""

claim_ref: str
axis: EvidenceAxis
level: EvidenceLevel
direction: RiskDirection
source_ref: str
rationale_ref: str
tissue: str | None = None
cell_type: str | None = None
criticality: Criticality = Criticality.UNKNOWN
surface_exposed: bool | None = None
normal_density_relation: str | None = None
toxicity_attribution: str | None = None
severe: bool = False
clinically_demonstrated: bool = False
unresolved: bool = False
tags: tuple[str, ...] = ()

def __post_init__(self) -> None:
for value, label in (
(self.claim_ref, "claim_ref"),
(self.source_ref, "source_ref"),
(self.rationale_ref, "rationale_ref"),
):
_external(value, label)
if self.normal_density_relation not in {None, "lower", "similar", "higher", "unknown"}:
raise ValueError("normal_density_relation is invalid")
if self.toxicity_attribution not in {
None,
"confirmed_on_target_on_tissue",
"probable_on_target",
"possible_on_target",
"payload_class_effect",
"linker_or_conjugation_effect",
"immune_mechanism",
"disease_related",
"off_target",
"unresolved",
}:
raise ValueError("toxicity_attribution is invalid")


@dataclass(frozen=True)
class AssessmentRequest:
"""External runtime input for one target assessment."""

request_ref: str
target: TargetProfile
evidence_refs: tuple[str, ...]
claims: tuple[EvidenceClaim, ...]
policy_ref: str
run_context_ref: str

def __post_init__(self) -> None:
_external(self.request_ref, "request_ref")
_external(self.policy_ref, "policy_ref")
_external(self.run_context_ref, "run_context_ref")
for evidence_ref in self.evidence_refs:
_external(evidence_ref, "evidence_ref")
claim_refs = {claim.claim_ref for claim in self.claims}
if claim_refs - set(self.evidence_refs):
raise ValueError("every claim_ref must be declared in evidence_refs")


@dataclass(frozen=True)
class AxisSummary:
axis: EvidenceAxis
claim_count: int
highest_level: EvidenceLevel
unresolved: bool
risk_claim_count: int
safety_claim_count: int
conflict_claim_count: int


@dataclass(frozen=True)
class AssessmentResult:
"""Conservative, target-level output; not a product therapeutic-window claim."""

contract_version: str
request_ref: str
target_ref: str
axis_summaries: tuple[AxisSummary, ...]
fatal_flags: tuple[FatalFlag, ...]
unresolved_refs: tuple[str, ...]
conflict_refs: tuple[str, ...]
mitigation_refs: tuple[str, ...]
next_experiment_refs: tuple[str, ...]
decision: Decision
confidence: str
limitation_ref: str

def __post_init__(self) -> None:
if self.contract_version != CONTRACT_VERSION:
raise ValueError("unsupported assessment result contract version")
_external(self.request_ref, "request_ref")
_external(self.target_ref, "target_ref")
_external(self.limitation_ref, "limitation_ref")
for ref in (
*self.unresolved_refs,
*self.conflict_refs,
*self.mitigation_refs,
*self.next_experiment_refs,
):
_external(ref, "result reference")
if self.confidence not in {"high", "medium", "low"}:
raise ValueError("confidence must be high, medium, or low")

Loading
Loading