pen-stack 0.1.0__py3-none-any.whl
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.
- pen_stack/__init__.py +2 -0
- pen_stack/_resources.py +34 -0
- pen_stack/active/__init__.py +20 -0
- pen_stack/active/acquire.py +165 -0
- pen_stack/active/brains.py +74 -0
- pen_stack/active/campaign.py +109 -0
- pen_stack/active/design.py +66 -0
- pen_stack/active/validate.py +104 -0
- pen_stack/adapt/__init__.py +14 -0
- pen_stack/adapt/finetune.py +33 -0
- pen_stack/adapt/ingest.py +86 -0
- pen_stack/adapt/pipeline.py +101 -0
- pen_stack/adapt/recalibrate.py +58 -0
- pen_stack/adapt/report.py +130 -0
- pen_stack/agent/__init__.py +1 -0
- pen_stack/agent/cite.py +175 -0
- pen_stack/agent/co_scientist.py +262 -0
- pen_stack/agent/epistemic.py +102 -0
- pen_stack/agent/guardrails.py +67 -0
- pen_stack/agent/mcp_server.py +215 -0
- pen_stack/agent/orchestrator.py +112 -0
- pen_stack/agent/orchestrator_live.py +56 -0
- pen_stack/agent/pen_agent.py +242 -0
- pen_stack/agent/scope.py +60 -0
- pen_stack/agent/tools.py +130 -0
- pen_stack/api/__init__.py +12 -0
- pen_stack/api/manifest.py +160 -0
- pen_stack/atlas/__init__.py +1 -0
- pen_stack/atlas/atlas.parquet +0 -0
- pen_stack/atlas/build_wtkb.py +80 -0
- pen_stack/atlas/crosslink.py +179 -0
- pen_stack/atlas/expand.py +190 -0
- pen_stack/atlas/guide_design.py +178 -0
- pen_stack/atlas/schema.py +59 -0
- pen_stack/atlas/scorecard.py +134 -0
- pen_stack/atlas/scorecard_v3.parquet +0 -0
- pen_stack/atlas/universe.py +75 -0
- pen_stack/atlas/universe_v3.parquet +0 -0
- pen_stack/atlas/variant_propose.py +155 -0
- pen_stack/atlas/writer_efficiency.py +184 -0
- pen_stack/atlas/writer_predict.py +229 -0
- pen_stack/atlas/writer_recommend.py +170 -0
- pen_stack/atlas/writer_verify.py +167 -0
- pen_stack/atlas/wtkb.parquet +0 -0
- pen_stack/bridge/__init__.py +1 -0
- pen_stack/bridge/activity.py +52 -0
- pen_stack/bridge/cli.py +65 -0
- pen_stack/bridge/fold_qc.py +53 -0
- pen_stack/bridge/guide_qc.py +87 -0
- pen_stack/bridge/ingest.py +139 -0
- pen_stack/bridge/offtarget.py +191 -0
- pen_stack/bridge/offtarget_energetics.py +105 -0
- pen_stack/bridge/ortholog_screen.py +73 -0
- pen_stack/bridge/pipeline.py +83 -0
- pen_stack/build/__init__.py +16 -0
- pen_stack/build/cloudlab.py +74 -0
- pen_stack/build/ingest.py +47 -0
- pen_stack/build/protocol.py +82 -0
- pen_stack/build/simlab.py +30 -0
- pen_stack/cli.py +126 -0
- pen_stack/data/__init__.py +1 -0
- pen_stack/data/encode.py +84 -0
- pen_stack/data/genome.py +71 -0
- pen_stack/data/ingest_chromatin.py +119 -0
- pen_stack/data/ingest_integration.py +112 -0
- pen_stack/data/ingest_safety_annot.py +201 -0
- pen_stack/data/ingest_trip.py +76 -0
- pen_stack/design/__init__.py +14 -0
- pen_stack/design/capsid_generate.py +62 -0
- pen_stack/design/generate.py +70 -0
- pen_stack/design/pareto.py +70 -0
- pen_stack/design/space.py +137 -0
- pen_stack/design/writer_variants.py +121 -0
- pen_stack/env/__init__.py +1 -0
- pen_stack/env/genome_writing_env.py +248 -0
- pen_stack/env/policies.py +94 -0
- pen_stack/graph/__init__.py +21 -0
- pen_stack/graph/build.py +133 -0
- pen_stack/graph/cell_types.py +58 -0
- pen_stack/graph/ingest.py +132 -0
- pen_stack/graph/query.py +148 -0
- pen_stack/graph/schema.py +100 -0
- pen_stack/loop/__init__.py +15 -0
- pen_stack/loop/continual.py +61 -0
- pen_stack/loop/cycle.py +84 -0
- pen_stack/loop/drift.py +41 -0
- pen_stack/mech/__init__.py +1 -0
- pen_stack/mech/classify_atlas.py +71 -0
- pen_stack/mech/pfam_whitelist.yaml +247 -0
- pen_stack/mech/whitelist.py +66 -0
- pen_stack/monitor/__init__.py +1 -0
- pen_stack/monitor/europepmc.py +32 -0
- pen_stack/monitor/run.py +57 -0
- pen_stack/monitor/triage.py +63 -0
- pen_stack/oracles/__init__.py +65 -0
- pen_stack/oracles/affinity.py +116 -0
- pen_stack/oracles/cache.py +53 -0
- pen_stack/oracles/energetics.py +33 -0
- pen_stack/oracles/genome.py +167 -0
- pen_stack/oracles/protein_design.py +136 -0
- pen_stack/oracles/reliability.py +64 -0
- pen_stack/oracles/rna.py +28 -0
- pen_stack/oracles/schema.py +77 -0
- pen_stack/oracles/status.py +123 -0
- pen_stack/oracles/structure.py +42 -0
- pen_stack/oracles/structure_run.py +76 -0
- pen_stack/oracles/vcell.py +74 -0
- pen_stack/planner/__init__.py +1 -0
- pen_stack/planner/ada_risk.py +64 -0
- pen_stack/planner/antipeg_oracle.py +75 -0
- pen_stack/planner/capsid_epitope_oracle.py +135 -0
- pen_stack/planner/cargo.py +56 -0
- pen_stack/planner/cargo_polish.py +146 -0
- pen_stack/planner/chromosome.py +106 -0
- pen_stack/planner/delivery.py +55 -0
- pen_stack/planner/delivery_constraints.py +110 -0
- pen_stack/planner/delivery_immune.py +61 -0
- pen_stack/planner/delivery_immunology.py +222 -0
- pen_stack/planner/delivery_predict.py +196 -0
- pen_stack/planner/delivery_vehicles.py +37 -0
- pen_stack/planner/genotoxicity_oracle.py +112 -0
- pen_stack/planner/immune_mhc2.py +154 -0
- pen_stack/planner/immune_profile.py +292 -0
- pen_stack/planner/innate_sensing.py +135 -0
- pen_stack/planner/multiplex.py +110 -0
- pen_stack/planner/optimize.py +278 -0
- pen_stack/planner/pipeline.py +87 -0
- pen_stack/planner/report.py +26 -0
- pen_stack/planner/router.py +57 -0
- pen_stack/planner/seroprevalence_oracle.py +92 -0
- pen_stack/planner/target_site.py +118 -0
- pen_stack/rag/__init__.py +1 -0
- pen_stack/rag/corpus.py +133 -0
- pen_stack/rag/embed.py +98 -0
- pen_stack/rag/ground.py +131 -0
- pen_stack/rag/index.py +53 -0
- pen_stack/rag/llm.py +215 -0
- pen_stack/rag/qa.py +105 -0
- pen_stack/rag/retrieve.py +48 -0
- pen_stack/rules/__init__.py +9 -0
- pen_stack/rules/evaluators.py +318 -0
- pen_stack/rules/loader.py +31 -0
- pen_stack/rules/schema.py +99 -0
- pen_stack/rules/solver.py +43 -0
- pen_stack/rules/spec.py +78 -0
- pen_stack/safety/__init__.py +21 -0
- pen_stack/safety/audit.py +90 -0
- pen_stack/safety/gate.py +58 -0
- pen_stack/safety/pfam_scan.py +157 -0
- pen_stack/safety/policy.py +69 -0
- pen_stack/safety/redteam.py +71 -0
- pen_stack/safety/registry.py +255 -0
- pen_stack/safety/screen.py +59 -0
- pen_stack/safety/standards.py +141 -0
- pen_stack/score/__init__.py +1 -0
- pen_stack/score/recalibrate.py +77 -0
- pen_stack/score/therapeutic.py +85 -0
- pen_stack/server/__init__.py +1 -0
- pen_stack/server/api.py +647 -0
- pen_stack/spec/__init__.py +18 -0
- pen_stack/spec/clarify.py +42 -0
- pen_stack/spec/extract.py +406 -0
- pen_stack/spec/resolvers/__init__.py +17 -0
- pen_stack/spec/resolvers/cell.py +51 -0
- pen_stack/spec/resolvers/chem.py +32 -0
- pen_stack/spec/resolvers/feature.py +36 -0
- pen_stack/spec/resolvers/gene.py +43 -0
- pen_stack/spec/resolvers/locus.py +29 -0
- pen_stack/spec/resolvers/phenotype.py +37 -0
- pen_stack/spec/satisfy.py +114 -0
- pen_stack/spec/service.py +33 -0
- pen_stack/spec/writespec.py +252 -0
- pen_stack/twin/__init__.py +14 -0
- pen_stack/twin/calibrate.py +61 -0
- pen_stack/twin/data/__init__.py +12 -0
- pen_stack/twin/data/position_effect.py +245 -0
- pen_stack/twin/mechanistic.py +147 -0
- pen_stack/twin/outcome.py +132 -0
- pen_stack/twin/position_effect.py +454 -0
- pen_stack/ui/__init__.py +1 -0
- pen_stack/ui/app.py +713 -0
- pen_stack/validate/__init__.py +1 -0
- pen_stack/validate/adapt_demo.py +69 -0
- pen_stack/validate/agent_eval.py +117 -0
- pen_stack/validate/bench_adversarial_tasks.py +118 -0
- pen_stack/validate/bench_coscientist_tasks.py +60 -0
- pen_stack/validate/bench_graph_tasks.py +64 -0
- pen_stack/validate/bench_rule_tasks.py +84 -0
- pen_stack/validate/bench_trust_tasks.py +92 -0
- pen_stack/validate/bench_writetype_tasks.py +101 -0
- pen_stack/validate/blind_gsh_discovery.py +261 -0
- pen_stack/validate/cargo_directionality.py +57 -0
- pen_stack/validate/closed_loop.py +63 -0
- pen_stack/validate/durability_baselines.py +185 -0
- pen_stack/validate/experiment_design.py +65 -0
- pen_stack/validate/expr_controls.py +39 -0
- pen_stack/validate/forward_hypotheses.py +104 -0
- pen_stack/validate/generative_design.py +62 -0
- pen_stack/validate/guide_qc_demo.py +69 -0
- pen_stack/validate/heldout_celltype_expr.py +32 -0
- pen_stack/validate/immune_calibration.py +133 -0
- pen_stack/validate/intent_specification.py +82 -0
- pen_stack/validate/known_biology_expr.py +38 -0
- pen_stack/validate/offtarget_energetics_eval.py +144 -0
- pen_stack/validate/out_of_scope_refusal.py +82 -0
- pen_stack/validate/outcome_calibration.py +194 -0
- pen_stack/validate/outcome_prediction.py +76 -0
- pen_stack/validate/paper3_benchmark.py +165 -0
- pen_stack/validate/paper4_real_validation.py +144 -0
- pen_stack/validate/paper4_validation.py +82 -0
- pen_stack/validate/protocol_safety.py +62 -0
- pen_stack/validate/safety_screening.py +72 -0
- pen_stack/validate/selective_prediction.py +104 -0
- pen_stack/validate/seq_vs_measured.py +134 -0
- pen_stack/validate/target_site_controls.py +65 -0
- pen_stack/validate/uncertainty_eval.py +244 -0
- pen_stack/validate/ungrounded_baseline.py +234 -0
- pen_stack/validate/within_locus_ranking.py +84 -0
- pen_stack/validate/writer_recovery.py +91 -0
- pen_stack/verify/__init__.py +5 -0
- pen_stack/verify/proof.py +206 -0
- pen_stack/verify/schema.py +53 -0
- pen_stack/verify/service.py +191 -0
- pen_stack/web/__init__.py +18 -0
- pen_stack/web/guide.py +110 -0
- pen_stack/web/llm.py +393 -0
- pen_stack/web/llm_provider.py +119 -0
- pen_stack/web/router.py +119 -0
- pen_stack/web/server.py +96 -0
- pen_stack/web/tools.py +197 -0
- pen_stack/wgenome/__init__.py +1 -0
- pen_stack/wgenome/chromatin_seq.py +83 -0
- pen_stack/wgenome/durability.py +108 -0
- pen_stack/wgenome/export_tracks.py +52 -0
- pen_stack/wgenome/features.py +82 -0
- pen_stack/wgenome/genotoxic_blocklist.py +88 -0
- pen_stack/wgenome/gsh_baseline.py +154 -0
- pen_stack/wgenome/mesh_features.py +61 -0
- pen_stack/wgenome/offtarget_assay.py +80 -0
- pen_stack/wgenome/offtarget_bridge.py +47 -0
- pen_stack/wgenome/offtarget_cast.py +97 -0
- pen_stack/wgenome/offtarget_data.py +148 -0
- pen_stack/wgenome/offtarget_enumerate.py +274 -0
- pen_stack/wgenome/offtarget_integrase.py +155 -0
- pen_stack/wgenome/offtarget_nuclease.py +123 -0
- pen_stack/wgenome/offtarget_paste.py +41 -0
- pen_stack/wgenome/offtarget_predict.py +282 -0
- pen_stack/wgenome/ood.py +135 -0
- pen_stack/wgenome/providers.py +278 -0
- pen_stack/wgenome/safety.py +69 -0
- pen_stack/wgenome/structure3d.py +212 -0
- pen_stack/wgenome/uncertainty.py +250 -0
- pen_stack/wgenome/writability.py +72 -0
- pen_stack-0.1.0.dist-info/METADATA +401 -0
- pen_stack-0.1.0.dist-info/RECORD +259 -0
- pen_stack-0.1.0.dist-info/WHEEL +5 -0
- pen_stack-0.1.0.dist-info/entry_points.txt +3 -0
- pen_stack-0.1.0.dist-info/licenses/LICENSE +21 -0
- pen_stack-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Repair-oriented proof object for verify().
|
|
2
|
+
|
|
3
|
+
``verify(design)`` already returns legality, a calibrated confidence and a biosecurity verdict as separate
|
|
4
|
+
fields. ``verify_proof(design)`` repackages that into a machine-readable proof an agent can repair a design
|
|
5
|
+
from: three SEPARATELY-REPORTED axes (legality, confidence, biosecurity), each carrying a status, the rule or
|
|
6
|
+
signature that fired, the supporting evidence, and a repair hint. The single collapsed verdict is always
|
|
7
|
+
``None`` (the axes are never fused), per the verification-service invariant.
|
|
8
|
+
|
|
9
|
+
Repair hints are actionable for legality (a structured field edit that an agent, or ``repair_from_proof``, can
|
|
10
|
+
apply and re-verify) and for the confidence abstention (which scores to supply). Biosecurity repair hints are
|
|
11
|
+
deliberately NON-ACTIONABLE for a hazard: a refused or escalated design is acknowledged and routed to human
|
|
12
|
+
biosafety review, never auto-repaired and never given instructions toward the hazard. Generation may be
|
|
13
|
+
intractable; verification, and repairing a failed-on-legality design, is tractable, which is the point.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from pydantic import BaseModel, Field
|
|
20
|
+
|
|
21
|
+
from pen_stack.planner.delivery_vehicles import names as _vehicle_names
|
|
22
|
+
from pen_stack.planner.delivery_vehicles import vehicle as _vehicle
|
|
23
|
+
from pen_stack.rules import Design
|
|
24
|
+
from pen_stack.verify.schema import Verdict
|
|
25
|
+
from pen_stack.verify.service import verify
|
|
26
|
+
|
|
27
|
+
AXES = ("legality", "confidence", "biosecurity")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AxisProof(BaseModel):
|
|
31
|
+
axis: str # one of AXES
|
|
32
|
+
status: str # pass | fail | abstain | refuse | escalate | flag | deferred
|
|
33
|
+
ok: bool # True when this axis does not block (pass/flag/clear; abstain is ok-but-uncertain for confidence)
|
|
34
|
+
violated: list[dict[str, Any]] = Field(default_factory=list) # the rule(s)/signature(s) that fired
|
|
35
|
+
evidence: dict[str, Any] = Field(default_factory=dict)
|
|
36
|
+
repair_hint: dict[str, Any] | None = None # {text, repair?: {field, set_to}} | None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Proof(BaseModel):
|
|
40
|
+
design: dict[str, Any]
|
|
41
|
+
write_type: str
|
|
42
|
+
axes: list[AxisProof]
|
|
43
|
+
collapsed: None = None # the three axes are NEVER fused into one verdict
|
|
44
|
+
passable: bool # legality passes AND biosecurity does not refuse/escalate (confidence may abstain)
|
|
45
|
+
no_fabrication: bool = True
|
|
46
|
+
provenance: dict[str, Any] = Field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
def axis(self, name: str) -> AxisProof:
|
|
49
|
+
return next(a for a in self.axes if a.axis == name)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _pick_vehicle(cargo_bp: int | None, form: str | None, non_integrating: bool) -> str | None:
|
|
53
|
+
"""Smallest-capacity vehicle that carries ``form``, fits ``cargo_bp``, and respects an integration goal.
|
|
54
|
+
Deterministic; used to turn a legality violation into a concrete repair."""
|
|
55
|
+
best: tuple[str, float] | None = None
|
|
56
|
+
for name in _vehicle_names():
|
|
57
|
+
rec = _vehicle(name) or {}
|
|
58
|
+
forms = rec.get("compatible_cargo_form") or []
|
|
59
|
+
if form and form not in forms:
|
|
60
|
+
continue
|
|
61
|
+
if non_integrating and rec.get("integrating"):
|
|
62
|
+
continue
|
|
63
|
+
cap = rec.get("cargo_capacity_bp")
|
|
64
|
+
if cargo_bp is not None and cap is not None and cap < cargo_bp:
|
|
65
|
+
continue
|
|
66
|
+
score = float(cap) if cap is not None else float("inf")
|
|
67
|
+
if best is None or score < best[1]:
|
|
68
|
+
best = (name, score)
|
|
69
|
+
return best[0] if best else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _repair_for_violation(d: Design, rule_id: str) -> dict[str, Any] | None:
|
|
73
|
+
"""A structured, applyable repair for a repairable legality violation, or None if not auto-repairable."""
|
|
74
|
+
form = d.writer_output_form or ("DNA" if d.write_type in ("insertion", "replacement", "landing_pad") else None)
|
|
75
|
+
if rule_id in ("payload.cargo_within_capacity", "delivery.cargo_form_compatible"):
|
|
76
|
+
veh = _pick_vehicle(d.cargo_bp, form, non_integrating=bool(d.no_integration))
|
|
77
|
+
if veh and veh != d.delivery_vehicle:
|
|
78
|
+
why = ("cargo exceeds the vehicle packaging capacity" if "capacity" in rule_id
|
|
79
|
+
else "the cargo form is not compatible with the vehicle")
|
|
80
|
+
return {"text": f"{why}; switch to a vehicle that fits ({veh}).",
|
|
81
|
+
"repair": {"field": "delivery_vehicle", "set_to": veh}}
|
|
82
|
+
if rule_id == "delivery.no_integration_constraint":
|
|
83
|
+
veh = _pick_vehicle(d.cargo_bp, form, non_integrating=True)
|
|
84
|
+
if veh and veh != d.delivery_vehicle:
|
|
85
|
+
return {"text": f"the goal forbids integration but the vehicle integrates; switch to {veh}.",
|
|
86
|
+
"repair": {"field": "delivery_vehicle", "set_to": veh}}
|
|
87
|
+
if rule_id == "reachability.target_element_available":
|
|
88
|
+
return {"text": "the writer cannot reach the target as specified; declare a pre-installed landing pad "
|
|
89
|
+
"(installed_att) or choose a writer family whose target element is present.",
|
|
90
|
+
"repair": {"field": "installed_att", "set_to": True}}
|
|
91
|
+
if rule_id == "compliance.germline_prohibition":
|
|
92
|
+
return {"text": "this is a heritable / germline edit, which is out of scope for this somatic-editing "
|
|
93
|
+
"tool and is broadly prohibited (international moratorium); restrict to SOMATIC editing - "
|
|
94
|
+
"move to ex vivo (in_vivo = false) and/or a somatic cell type (e.g. hepatocytes / HepG2, "
|
|
95
|
+
"HSPCs), and drop any germline/heritable intent. (Not auto-repairable: a germline edit is "
|
|
96
|
+
"a change of intent, not a field fix.)",
|
|
97
|
+
"repair": None}
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
_SAFETY_REFUSED_SOURCE = "safety_gate(refused)" # the exact provenance.source verify.service sets on early-return
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _legality_axis(d: Design, v: Verdict) -> AxisProof:
|
|
105
|
+
if v.provenance.get("source") == _SAFETY_REFUSED_SOURCE:
|
|
106
|
+
# biosecurity refused BEFORE the rules engine ran (verify.service short-circuits on refuse), legality
|
|
107
|
+
# was never evaluated, so no rule fired and there is nothing to cite. Reporting "fail" here would
|
|
108
|
+
# fabricate a legality verdict for an axis that never ran. State: unknown, not itself blocking
|
|
109
|
+
# (biosecurity already blocks `passable`), mirrors the "abstaining is a deferral, not a block" convention
|
|
110
|
+
# already used by the confidence axis.
|
|
111
|
+
return AxisProof(axis="legality", status="not_evaluated", ok=True,
|
|
112
|
+
evidence={"reason": "biosecurity refused this design before legality was evaluated"},
|
|
113
|
+
repair_hint={"text": "legality was not evaluated, biosecurity refused this design "
|
|
114
|
+
"first. Legality can only be checked once the biosecurity concern "
|
|
115
|
+
"is resolved (route to institutional biosafety / IBC review).",
|
|
116
|
+
"repair": None})
|
|
117
|
+
if v.deferred:
|
|
118
|
+
return AxisProof(axis="legality", status="deferred", ok=False,
|
|
119
|
+
evidence={"reason": v.routing.get("reason", "unsupported write type")},
|
|
120
|
+
repair_hint={"text": "the write type is not yet supported; route to a supported type."})
|
|
121
|
+
if v.legal:
|
|
122
|
+
return AxisProof(axis="legality", status="pass", ok=True,
|
|
123
|
+
evidence={"rules_version": v.provenance.get("rules_version")})
|
|
124
|
+
first = v.violations[0] if v.violations else None
|
|
125
|
+
hint = _repair_for_violation(d, first["rule_id"]) if first else None
|
|
126
|
+
return AxisProof(axis="legality", status="fail", ok=False,
|
|
127
|
+
violated=[{"rule_id": x["rule_id"], "reason": x["reason"], "citation": x.get("citation", [])}
|
|
128
|
+
for x in v.violations],
|
|
129
|
+
evidence={"n_violations": len(v.violations)}, repair_hint=hint)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _confidence_axis(v: Verdict) -> AxisProof:
|
|
133
|
+
if v.provenance.get("source") == _SAFETY_REFUSED_SOURCE:
|
|
134
|
+
# same short-circuit: confidence was never computed. The "uncalibrated" repair hint below would be
|
|
135
|
+
# ACTIVELY MISLEADING here, supplying the soft scores would not help, since the block happens before
|
|
136
|
+
# they would ever be read.
|
|
137
|
+
return AxisProof(axis="confidence", status="not_evaluated", ok=True,
|
|
138
|
+
evidence={"confidence": None, "interval": None,
|
|
139
|
+
"reason": "biosecurity refused this design before confidence was evaluated"},
|
|
140
|
+
repair_hint={"text": "confidence was not evaluated, biosecurity refused this design "
|
|
141
|
+
"before reaching the confidence check.", "repair": None})
|
|
142
|
+
if v.confidence is None:
|
|
143
|
+
return AxisProof(axis="confidence", status="abstain", ok=True, # abstaining is a deferral, not a block
|
|
144
|
+
evidence={"confidence": None, "interval": None},
|
|
145
|
+
repair_hint={"text": "confidence is uncalibrated for this design; supply the soft "
|
|
146
|
+
"per-axis scores (safety, p_durable, writer_activity) to calibrate."})
|
|
147
|
+
return AxisProof(axis="confidence", status="pass", ok=True,
|
|
148
|
+
evidence={"confidence": v.confidence, "interval": v.interval,
|
|
149
|
+
"epistemic_status": v.epistemic_status})
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _biosecurity_axis(v: Verdict) -> AxisProof:
|
|
153
|
+
s = v.safety
|
|
154
|
+
if s is None:
|
|
155
|
+
return AxisProof(axis="biosecurity", status="pass", ok=True, evidence={"decision": "clear"})
|
|
156
|
+
blocked = s.decision in ("refuse", "escalate")
|
|
157
|
+
# Carry the same detail Guardian's own /api/safety view shows (which signature, and for a
|
|
158
|
+
# sequence-domain hit, which Pfam accession + bit score) -- Design Studio's Proof was previously
|
|
159
|
+
# stripping this down to a bare signature id with no reason/citation/evidence.
|
|
160
|
+
sigs = [{"signature": h.provenance.get("signature_id"), "kind": h.kind, "severity": h.severity,
|
|
161
|
+
"reason": h.detail, "citation": [c for c in [h.provenance.get("control_ref")] if c],
|
|
162
|
+
**({"pfam_accession": h.evidence.get("pfam_accession"), "bit_score": h.evidence.get("bit_score")}
|
|
163
|
+
if h.kind == "sequence_homology" else {})}
|
|
164
|
+
for h in (s.hits or [])]
|
|
165
|
+
# repair hints for a hazard are deliberately NON-actionable: acknowledge and route to human review.
|
|
166
|
+
hint = None
|
|
167
|
+
if blocked:
|
|
168
|
+
hint = {"text": "this design matches a controlled-hazard signature; it is not auto-repairable. "
|
|
169
|
+
"Route to institutional biosafety / IBC review. (No actionable repair is provided.)",
|
|
170
|
+
"repair": None}
|
|
171
|
+
return AxisProof(axis="biosecurity", status=s.decision, ok=not blocked,
|
|
172
|
+
violated=sigs, evidence={"decision": s.decision, "reason": s.reason}, repair_hint=hint)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def verify_proof(design: Design | dict, question: str | None = None, *, actor: str = "anonymous") -> Proof:
|
|
176
|
+
"""Verify a design and return the per-axis proof object. The three axes are reported separately and the
|
|
177
|
+
collapsed verdict is always None. ``passable`` is True when legality passes and biosecurity does not
|
|
178
|
+
refuse/escalate (an abstaining confidence axis does not block)."""
|
|
179
|
+
d = Design(**dict(design)) if isinstance(design, dict) else design
|
|
180
|
+
v = verify(design, question=question, actor=actor)
|
|
181
|
+
axes = [_legality_axis(d, v), _confidence_axis(v), _biosecurity_axis(v)]
|
|
182
|
+
legality_ok = axes[0].ok
|
|
183
|
+
biosec_ok = axes[2].ok
|
|
184
|
+
return Proof(design=d.model_dump(exclude_none=True), write_type=d.write_type, axes=axes,
|
|
185
|
+
passable=bool(legality_ok and biosec_ok),
|
|
186
|
+
provenance={"source": "verify_proof", "rules_version": v.provenance.get("rules_version")})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def repair_from_proof(design: Design | dict, proof: Proof) -> Design:
|
|
190
|
+
"""Apply the structured repairs carried by the proof's axes (legality first) and return the repaired
|
|
191
|
+
Design. Uses ONLY the proof object, never re-inspecting the engine, so it demonstrates the proof is
|
|
192
|
+
agent-repairable. Biosecurity hazards are never auto-repaired (their repair is None)."""
|
|
193
|
+
d = Design(**dict(design)) if isinstance(design, dict) else Design(**design.model_dump())
|
|
194
|
+
patch: dict[str, Any] = {}
|
|
195
|
+
for ax in proof.axes:
|
|
196
|
+
hint = ax.repair_hint
|
|
197
|
+
if not hint:
|
|
198
|
+
continue
|
|
199
|
+
rep = hint.get("repair")
|
|
200
|
+
if rep and rep.get("field") is not None:
|
|
201
|
+
patch[rep["field"]] = rep["set_to"]
|
|
202
|
+
if not patch:
|
|
203
|
+
return d
|
|
204
|
+
data = d.model_dump()
|
|
205
|
+
data.update(patch)
|
|
206
|
+
return Design(**data)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""The Verdict object returned by the verification service.
|
|
2
|
+
|
|
3
|
+
Carries legality + the named rejections + a calibrated confidence + an epistemic status + scope flags, with
|
|
4
|
+
legality and confidence kept as separate fields (never collapsed). Serializable for REST/MCP.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
11
|
+
|
|
12
|
+
from pen_stack.safety.policy import SafetyVerdict
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Verdict(BaseModel):
|
|
16
|
+
"""Frozen: the verification result is immutable once returned, so legality, the named violations, the
|
|
17
|
+
calibrated confidence, and the nested (also-frozen) safety verdict cannot be rewritten after the fact.
|
|
18
|
+
Build a changed verdict with `model_copy(update=...)` if a transform genuinely needs one."""
|
|
19
|
+
model_config = ConfigDict(frozen=True)
|
|
20
|
+
|
|
21
|
+
legal: bool | None # True/False; None when the write type is deferred
|
|
22
|
+
deferred: bool = False
|
|
23
|
+
write_type: str
|
|
24
|
+
routing: dict[str, Any] = Field(default_factory=dict)
|
|
25
|
+
rule_results: list[dict] = Field(default_factory=list)
|
|
26
|
+
violations: list[dict] = Field(default_factory=list) # named hard-rule rejections + citation
|
|
27
|
+
soft_flags: list[dict] = Field(default_factory=list) # soft penalties raised
|
|
28
|
+
scope_flags: list[dict] = Field(default_factory=list) # known-unknowns + rule scope flags
|
|
29
|
+
confidence: float | None = None # calibrated confidence on the SOFT components (distinct axis)
|
|
30
|
+
interval: list[float] | None = None
|
|
31
|
+
epistemic_status: str = "not-computable"
|
|
32
|
+
provenance: dict[str, Any] = Field(default_factory=dict)
|
|
33
|
+
no_fabrication: bool = True
|
|
34
|
+
writer_critique: dict[str, Any] | None = None # critique of a generated candidate writer
|
|
35
|
+
# (pass/flag + reasons); NEVER a claim that it works
|
|
36
|
+
delivery_profile: dict[str, Any] | None = None # documented ordinal immune/safety/efficacy
|
|
37
|
+
# priors for the chosen vehicle (NEVER a predicted magnitude)
|
|
38
|
+
immune_profile: dict[str, Any] | None = None # per-axis immune-risk vector (genotox/CD8/
|
|
39
|
+
# innate/NAb/anti-PEG), each w/ own uncertainty + validation
|
|
40
|
+
# label; collapsed_score is None (never fused); magnitude KU
|
|
41
|
+
safety: SafetyVerdict | None = None # the Guardian: biosecurity/dual-use screen
|
|
42
|
+
# (clear/flag/refuse/escalate); a refused design is NOT
|
|
43
|
+
# evaluated further; orthogonal to immune_profile
|
|
44
|
+
|
|
45
|
+
def summary(self) -> str:
|
|
46
|
+
if self.safety is not None and self.safety.decision == "refuse":
|
|
47
|
+
return f"REFUSED (safety): {self.safety.reason}"
|
|
48
|
+
if self.deferred:
|
|
49
|
+
return f"DEFERRED ({self.write_type}): {self.routing.get('reason', 'unsupported write type')}"
|
|
50
|
+
verdict = "LEGAL" if self.legal else "ILLEGAL"
|
|
51
|
+
conf = f"conf={self.confidence}" if self.confidence is not None else "conf=n/a (abstained)"
|
|
52
|
+
why = "" if self.legal else " | " + "; ".join(v["rule_id"] for v in self.violations)
|
|
53
|
+
return f"{verdict} [{self.epistemic_status}, {conf}]{why}"
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""The verification service, `verify(design) -> Verdict`.
|
|
2
|
+
|
|
3
|
+
The first-class "for the AI" surface: submit a proposed genomic write, get back a structured verdict,
|
|
4
|
+
legality (from the rule solver), the named reason + citation for any rejection, a calibrated confidence
|
|
5
|
+
on the *soft* components (from the L4 trust layer), an epistemic status, and any out-of-scope flags.
|
|
6
|
+
Thin orchestration over existing layers; it generates **no new numbers** (no-fabrication holds: legality is
|
|
7
|
+
rule-deterministic, confidence comes from the calibrated tool, scope from the registry).
|
|
8
|
+
|
|
9
|
+
Legality and confidence are DISTINCT axes (Principle 2) and are never collapsed: a design can be
|
|
10
|
+
legal-but-low-confidence or illegal-with-certainty. The Verdict carries both, separately.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from pen_stack.rules import Design
|
|
17
|
+
from pen_stack.rules.loader import RULES_VERSION
|
|
18
|
+
from pen_stack.verify.schema import Verdict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _plan_confidence(design: Design) -> dict:
|
|
22
|
+
"""Calibrated plan confidence on the SOFT components, only when the design carries the per-axis scores
|
|
23
|
+
(safety / p_durable / writer_activity). Otherwise None (abstain), never fabricated."""
|
|
24
|
+
extra = design.model_extra or {}
|
|
25
|
+
keys = ("safety", "p_durable", "writer_activity")
|
|
26
|
+
if not all(k in extra and extra[k] is not None for k in keys):
|
|
27
|
+
return {"confidence": None, "interval": None}
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
from pen_stack.planner.optimize import attach_uncertainty
|
|
31
|
+
row = pd.DataFrame([{"chrom": design.chrom or "chr?", "bin": 0,
|
|
32
|
+
"safety": float(extra["safety"]), "p_durable": float(extra["p_durable"]),
|
|
33
|
+
"writer_activity": float(extra["writer_activity"]), "score": 0.0}])
|
|
34
|
+
intent = design.edit_intent or "safe_harbour_insertion"
|
|
35
|
+
out = attach_uncertainty(row, intent, ood_factor=float(extra.get("ood_factor", 1.0)))
|
|
36
|
+
r = out.iloc[0]
|
|
37
|
+
return {"confidence": float(r["confidence"]), "interval": [float(r["score_lo"]), float(r["score_hi"])]}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def verify(design: Design | dict, question: str | None = None, *, actor: str = "anonymous") -> Verdict:
|
|
41
|
+
"""Verify a proposed write. ``design`` may be a Design or a plain dict (e.g. from REST/MCP JSON).
|
|
42
|
+
|
|
43
|
+
The Guardian: a biosecurity / dual-use safety gate runs FIRST. A design that matches a high-severity
|
|
44
|
+
hazard signature is REFUSED and returned un-evaluated (not scored further); every call is logged to the
|
|
45
|
+
tamper-evident audit trail. The safety verdict is orthogonal to the immune profile and is attached to
|
|
46
|
+
every Verdict. ``actor`` is recorded in the audit log."""
|
|
47
|
+
if isinstance(design, dict):
|
|
48
|
+
d = dict(design)
|
|
49
|
+
question = question or d.pop("question", None)
|
|
50
|
+
screen_payload = dict(d)
|
|
51
|
+
design = Design(**d)
|
|
52
|
+
else:
|
|
53
|
+
screen_payload = {**design.model_dump(exclude_none=False), **(design.model_extra or {})}
|
|
54
|
+
|
|
55
|
+
# The safety gate is the first thing every caller inherits.
|
|
56
|
+
from pen_stack.safety.gate import safety_gate
|
|
57
|
+
safety = safety_gate(screen_payload, actor=actor)
|
|
58
|
+
if safety.decision == "refuse":
|
|
59
|
+
return Verdict(
|
|
60
|
+
legal=None, deferred=False, write_type=design.write_type, routing={},
|
|
61
|
+
rule_results=[], violations=[], soft_flags=[],
|
|
62
|
+
scope_flags=[{"kind": "safety_refused", "reason": safety.reason,
|
|
63
|
+
"signatures": [h.provenance.get("signature_id") for h in safety.hits]}],
|
|
64
|
+
confidence=None, interval=None, epistemic_status="refused",
|
|
65
|
+
provenance={"rules_version": RULES_VERSION, "source": "safety_gate(refused)"},
|
|
66
|
+
no_fabrication=True, safety=safety)
|
|
67
|
+
|
|
68
|
+
from pen_stack.agent.epistemic import classify
|
|
69
|
+
from pen_stack.planner.router import route_and_evaluate
|
|
70
|
+
|
|
71
|
+
routed = route_and_evaluate(design)
|
|
72
|
+
routing = routed["routing"]
|
|
73
|
+
|
|
74
|
+
# scope: out-of-scope question (known-unknowns) + rule scope_flags
|
|
75
|
+
scope_flags: list[dict[str, Any]] = []
|
|
76
|
+
oos_hit = False
|
|
77
|
+
if question:
|
|
78
|
+
from pen_stack.agent.scope import match_scope
|
|
79
|
+
m = match_scope(question)
|
|
80
|
+
if m:
|
|
81
|
+
oos_hit = True
|
|
82
|
+
scope_flags.append({"kind": "known_unknown", "id": m["id"], "reason": m["deferral"]})
|
|
83
|
+
|
|
84
|
+
if routed["deferred"]:
|
|
85
|
+
verdict = classify(grounded=False, out_of_scope=True)
|
|
86
|
+
return Verdict(
|
|
87
|
+
legal=None, deferred=True, write_type=design.write_type, routing=routing,
|
|
88
|
+
rule_results=[], violations=[], soft_flags=[],
|
|
89
|
+
scope_flags=scope_flags + [{"kind": "unsupported_write_type", "reason": routing["reason"]}],
|
|
90
|
+
confidence=None, interval=None, epistemic_status=verdict.status,
|
|
91
|
+
provenance={"rules_version": RULES_VERSION, "source": "router(deferred)"}, no_fabrication=True,
|
|
92
|
+
safety=safety)
|
|
93
|
+
|
|
94
|
+
results = routed["rule_results"]
|
|
95
|
+
violations = [r for r in results if r["kind"] == "hard_reject" and r["status"] == "violate"]
|
|
96
|
+
soft = [r for r in results if r["kind"] == "soft_penalty" and r["status"] == "flag"]
|
|
97
|
+
rule_scope = [{"kind": "rule_scope", "rule_id": r["rule_id"], "reason": r["reason"]}
|
|
98
|
+
for r in results if r["kind"] == "scope_flag" and r["status"] == "scope"]
|
|
99
|
+
scope_flags += rule_scope
|
|
100
|
+
|
|
101
|
+
# Chromosome validation + gene/chromosome concordance + chromosome-context advisory. The free-text
|
|
102
|
+
# chrom field does not move the scored locus (scoring is indexed by the gene's resolved coordinates), so a
|
|
103
|
+
# mismatch / invalid value / chromosome-context note is surfaced as a scope flag, never silently ignored.
|
|
104
|
+
from pen_stack.planner.chromosome import chromosome_concordance, chromosome_context
|
|
105
|
+
conc = chromosome_concordance(design.gene, getattr(design, "chrom", None))
|
|
106
|
+
if conc["status"] in ("invalid", "mismatch", "unverifiable"):
|
|
107
|
+
scope_flags.append({"kind": f"chromosome_{conc['status']}", "reason": conc["message"],
|
|
108
|
+
"entered": conc["entered"], "gene_chrom": conc["gene_chrom"]})
|
|
109
|
+
ctx = chromosome_context(getattr(design, "chrom", None))
|
|
110
|
+
if ctx:
|
|
111
|
+
scope_flags.append({"kind": "chromosome_context", "chrom": ctx["chrom"], "reason": ctx["note"]})
|
|
112
|
+
|
|
113
|
+
pc = _plan_confidence(design)
|
|
114
|
+
# A design with no calibrated confidence (the soft-axis scores were absent, so _plan_confidence abstained)
|
|
115
|
+
# must NOT read as grounded-confident: a missing confidence is an abstention, not a high-trust result.
|
|
116
|
+
verdict = classify(grounded=True, confidence=pc["confidence"],
|
|
117
|
+
abstained=pc["confidence"] is None, out_of_scope=oos_hit)
|
|
118
|
+
|
|
119
|
+
# If the design carries a GENERATED candidate writer sequence, critique it (fold / active
|
|
120
|
+
# site / deliverability / reachability), never a claim that it works. Adds a scope flag, never confidence.
|
|
121
|
+
writer_critique = None
|
|
122
|
+
cand_seq = (design.model_extra or {}).get("writer_candidate_seq")
|
|
123
|
+
if cand_seq:
|
|
124
|
+
from pen_stack.atlas.writer_verify import critique_candidate
|
|
125
|
+
writer_critique = critique_candidate(
|
|
126
|
+
cand_seq, writer_family=design.writer_family or "bridge_IS110",
|
|
127
|
+
delivery_vehicle=design.delivery_vehicle, no_integration=design.no_integration,
|
|
128
|
+
site_seq=design.site_seq)
|
|
129
|
+
scope_flags.append({"kind": "writer_candidate_critique", "pass": writer_critique["pass"],
|
|
130
|
+
"flags": writer_critique["flags"],
|
|
131
|
+
"reason": "generated writer is critiqued, never claimed to work"})
|
|
132
|
+
|
|
133
|
+
# If the design names a delivery vehicle, surface its DOCUMENTED ordinal immune/safety/
|
|
134
|
+
# efficacy priors (with the standing "magnitude is a known-unknown" scope flag). This never adds confidence
|
|
135
|
+
# and never predicts a magnitude, it exposes the curated qualitative tradeoff so safety can be weighed.
|
|
136
|
+
delivery_profile = None
|
|
137
|
+
if design.delivery_vehicle:
|
|
138
|
+
from pen_stack.planner.delivery_immunology import safety_efficacy_profile
|
|
139
|
+
delivery_profile = safety_efficacy_profile(design.delivery_vehicle)
|
|
140
|
+
if delivery_profile and delivery_profile.get("tradeoff"):
|
|
141
|
+
scope_flags.append({"kind": "delivery_immune_profile",
|
|
142
|
+
"vehicle": delivery_profile["vehicle"],
|
|
143
|
+
"tradeoff": delivery_profile["tradeoff"],
|
|
144
|
+
"magnitude_id": delivery_profile["magnitude_scope_flag"]["id"],
|
|
145
|
+
"reason": "documented ordinal immune/safety priors surfaced; the in-vivo immune "
|
|
146
|
+
"MAGNITUDE remains a known-unknown (not predicted)"})
|
|
147
|
+
|
|
148
|
+
# If a cargo SEQUENCE is supplied, compute its innate-sensing load from sequence
|
|
149
|
+
# (CpG/TLR9 for DNA, U/dsRNA for mRNA). Surfaced as a scope flag; the realized innate RESPONSE magnitude
|
|
150
|
+
# is a known-unknown. Cargo form = the writer output form, else the vehicle's first compatible form.
|
|
151
|
+
if design.cargo_seq:
|
|
152
|
+
form = design.writer_output_form
|
|
153
|
+
if not form and design.delivery_vehicle:
|
|
154
|
+
from pen_stack.planner.delivery_vehicles import vehicle as _veh
|
|
155
|
+
forms = (_veh(design.delivery_vehicle) or {}).get("compatible_cargo_form") or []
|
|
156
|
+
form = forms[0] if forms else None
|
|
157
|
+
if form:
|
|
158
|
+
from pen_stack.planner.innate_sensing import innate_sensing
|
|
159
|
+
inr = innate_sensing(design.cargo_seq, form)
|
|
160
|
+
if inr.available:
|
|
161
|
+
scope_flags.append({"kind": "cargo_innate_sensing", "cargo_form": form,
|
|
162
|
+
"innate_score": inr.value["innate_score"], "pathway": inr.value["pathway"],
|
|
163
|
+
"reason": "computed sequence-intrinsic innate-sensing load; the realized "
|
|
164
|
+
"in-vivo innate RESPONSE magnitude is a known-unknown"})
|
|
165
|
+
if delivery_profile is not None:
|
|
166
|
+
delivery_profile = dict(delivery_profile)
|
|
167
|
+
delivery_profile["cargo_innate"] = inr.value
|
|
168
|
+
|
|
169
|
+
# The unified per-axis immune-risk profile (genotox / CD8 epitope / innate / pre-existing
|
|
170
|
+
# NAb / anti-PEG), each with its own uncertainty + validation label; collapsed_score is None
|
|
171
|
+
# (never fused into one number); the in-vivo magnitude + patient titer stay known-unknowns.
|
|
172
|
+
immune_profile = None
|
|
173
|
+
if design.delivery_vehicle:
|
|
174
|
+
from pen_stack.planner.immune_profile import immune_profile as _immune_profile
|
|
175
|
+
extra = design.model_extra or {}
|
|
176
|
+
immune_profile = _immune_profile({
|
|
177
|
+
"delivery_vehicle": design.delivery_vehicle, "serotype": extra.get("serotype"),
|
|
178
|
+
"cargo_seq": design.cargo_seq, "writer_output_form": design.writer_output_form,
|
|
179
|
+
"pegylated": extra.get("pegylated")})
|
|
180
|
+
|
|
181
|
+
return Verdict(
|
|
182
|
+
legal=routed["legal"], deferred=False, write_type=design.write_type, routing=routing,
|
|
183
|
+
rule_results=results,
|
|
184
|
+
violations=[{"rule_id": v["rule_id"], "reason": v["reason"], "citation": v.get("citation", [])}
|
|
185
|
+
for v in violations],
|
|
186
|
+
soft_flags=[{"rule_id": s["rule_id"], "reason": s["reason"], "value": s.get("value")} for s in soft],
|
|
187
|
+
scope_flags=scope_flags,
|
|
188
|
+
confidence=pc["confidence"], interval=pc["interval"], epistemic_status=verdict.status,
|
|
189
|
+
provenance={"rules_version": RULES_VERSION, "source": "rules.solver + L4(uncertainty/scope/epistemic)"},
|
|
190
|
+
no_fabrication=True, writer_critique=writer_critique, delivery_profile=delivery_profile,
|
|
191
|
+
immune_profile=immune_profile, safety=safety)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""PEN-STACK Web Platform, the human surface.
|
|
2
|
+
|
|
3
|
+
A grounded co-scientist chat + every engine feature behind one typed gateway, with provenance-first UX. The LLM narrates
|
|
4
|
+
and routes; the engine sources every number. Submodules:
|
|
5
|
+
|
|
6
|
+
* ``tools``, the deterministic engine tool-runner (parse a goal, run verify/safety/immune/scope, return a
|
|
7
|
+
grounded dossier) and the grounding allow-list.
|
|
8
|
+
* ``llm``, the grounded co-scientist (Ollama → Nemotron → deterministic) with the grounding-guard hard gate.
|
|
9
|
+
* ``server``, the FastAPI gateway (mounts the engine surface + ``/chat``); needs the ``server`` extra.
|
|
10
|
+
|
|
11
|
+
``server`` is imported lazily (it requires FastAPI) so ``import pen_stack.web`` works on a bare install.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from pen_stack.web.llm import grounded_reply
|
|
16
|
+
from pen_stack.web.tools import extract_grounded_numbers, parse_goal, run_tools
|
|
17
|
+
|
|
18
|
+
__all__ = ["grounded_reply", "run_tools", "parse_goal", "extract_grounded_numbers"]
|
pen_stack/web/guide.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Metric interpretation + PEN-STACK capability facts (the explain/meta lanes).
|
|
2
|
+
|
|
3
|
+
Two grounded sources the hybrid co-scientist narrates over WITHOUT fabricating:
|
|
4
|
+
* `metric_guide()`, curated interpretation cards (configs/metric_guide.yaml): what each engine number means,
|
|
5
|
+
its scale/direction, reference bands, how it is computed, validation status. Used to EXPLAIN a value.
|
|
6
|
+
* `pen_stack_facts()`, capability facts assembled from LIVE engine data (writer atlas coverage, the delivery
|
|
7
|
+
palette, the immune axes, the known-unknowns), so "how many enzymes / vectors / axes, how accurate" is
|
|
8
|
+
answered from the system itself, not invented.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
|
|
14
|
+
import yaml
|
|
15
|
+
|
|
16
|
+
from pen_stack._resources import resource
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@lru_cache(maxsize=1)
|
|
20
|
+
def metric_guide() -> dict:
|
|
21
|
+
return yaml.safe_load(resource("configs/metric_guide.yaml").read_text(encoding="utf-8"))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def guide_for(metric: str) -> dict | None:
|
|
25
|
+
"""The interpretation card for a metric name (immune axis / verdict field)."""
|
|
26
|
+
return (metric_guide().get("metrics") or {}).get(metric)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def enrich_axes(axes: dict) -> dict:
|
|
30
|
+
"""Attach the interpretation card (meaning/scale/bands/computed/validation/reference) to each immune axis."""
|
|
31
|
+
out = {}
|
|
32
|
+
for name, a in (axes or {}).items():
|
|
33
|
+
g = guide_for(name)
|
|
34
|
+
out[name] = {**a, "guide": g} if g else a
|
|
35
|
+
return out
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@lru_cache(maxsize=1)
|
|
39
|
+
def pen_stack_facts() -> dict:
|
|
40
|
+
"""GROUNDED facts about what PEN-STACK covers + how it computes, assembled from live engine data, never
|
|
41
|
+
hand-typed counts. Answers the 'meta' lane ('how many enzymes/vectors/axes, how is X computed, how accurate')."""
|
|
42
|
+
facts: dict = {"disclaimer": (metric_guide() or {}).get("disclaimer")}
|
|
43
|
+
|
|
44
|
+
# writer atlas coverage (enzymes / families), from the committed atlas
|
|
45
|
+
try:
|
|
46
|
+
import pandas as pd
|
|
47
|
+
|
|
48
|
+
from pen_stack import __file__ as _pkg
|
|
49
|
+
from pathlib import Path
|
|
50
|
+
atlas = pd.read_parquet(Path(_pkg).resolve().parents[0] / "atlas" / "atlas.parquet")
|
|
51
|
+
facts["writers"] = {
|
|
52
|
+
"systems": int(len(atlas)),
|
|
53
|
+
"families": sorted(atlas["family"].astype(str).unique().tolist()),
|
|
54
|
+
"n_families": int(atlas["family"].nunique()),
|
|
55
|
+
"measured": int((atlas["confidence"] == "measured").sum()) if "confidence" in atlas.columns else None,
|
|
56
|
+
"note": "writer = genome-writing enzyme system (integrases, recombinases, nucleases, transposases) on "
|
|
57
|
+
"common measured axes (cargo capacity, programmability, DSB-freeness, human-cell activity, "
|
|
58
|
+
"deliverability).",
|
|
59
|
+
}
|
|
60
|
+
except Exception:
|
|
61
|
+
facts["writers"] = {"note": "writer atlas requires the committed atlas.parquet"}
|
|
62
|
+
|
|
63
|
+
# delivery palette (vectors)
|
|
64
|
+
try:
|
|
65
|
+
from pen_stack.planner.delivery_vehicles import names as _vnames
|
|
66
|
+
from pen_stack.planner.delivery_vehicles import vehicle as _veh
|
|
67
|
+
vs = _vnames()
|
|
68
|
+
viral = [v for v in vs if (_veh(v) or {}).get("class", "").lower().startswith("vir")
|
|
69
|
+
or any(k in v.lower() for k in ("aav", "lenti", "adeno", "hsv", "virus"))]
|
|
70
|
+
facts["delivery"] = {
|
|
71
|
+
"n_vehicles": len(vs), "vehicles": list(vs),
|
|
72
|
+
"viral_like": viral, "nonviral_like": [v for v in vs if v not in viral],
|
|
73
|
+
"note": "each vehicle carries documented cargo capacity, integration status, cargo-form compatibility "
|
|
74
|
+
"(DNA/mRNA/RNP) and an ordinal immune/safety prior; the planner picks compatible vehicles.",
|
|
75
|
+
}
|
|
76
|
+
except Exception:
|
|
77
|
+
facts["delivery"] = {"note": "delivery palette in configs/delivery_vehicles.yaml"}
|
|
78
|
+
|
|
79
|
+
# immune-risk axes (how immunogenicity is computed) + their guides
|
|
80
|
+
axes = ["genotoxicity", "cd8_epitope", "innate", "preexisting_nab", "anti_peg"]
|
|
81
|
+
facts["immunogenicity"] = {
|
|
82
|
+
"n_axes": len(axes), "axes": axes,
|
|
83
|
+
"never_collapsed": "the per-axis profile is never fused into one score (collapsed_score is None), the "
|
|
84
|
+
"axes measure different mechanisms on different evidence.",
|
|
85
|
+
"how": {a: (guide_for(a) or {}).get("computed") for a in axes},
|
|
86
|
+
"validation": {a: (guide_for(a) or {}).get("validation") for a in axes},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# accuracy and grounding posture + the known-unknowns
|
|
90
|
+
try:
|
|
91
|
+
from pen_stack.agent.scope import load_registry
|
|
92
|
+
kus = [{"id": e["id"], "title": e["title"]} for e in load_registry()]
|
|
93
|
+
except Exception:
|
|
94
|
+
kus = []
|
|
95
|
+
facts["accuracy"] = {
|
|
96
|
+
"posture": "every number is a tool-computed proxy with a stated scope; proxies are labelled "
|
|
97
|
+
"'mechanistic/population proxy, NOT outcome-validated'; the engine ABSTAINS or flags "
|
|
98
|
+
"out-of-scope rather than guess; nothing is fabricated.",
|
|
99
|
+
"known_unknowns": kus,
|
|
100
|
+
"what_is_NOT_predicted": "functional titer / % of normal, in-vivo response magnitude, long-term clinical "
|
|
101
|
+
"durability, phenotype, these are measured clinical endpoints, never predicted.",
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# live foundation-model oracles (which execute live + their latency class)
|
|
105
|
+
try:
|
|
106
|
+
from pen_stack.oracles.status import summary as _orsum
|
|
107
|
+
facts["foundation_models"] = _orsum()
|
|
108
|
+
except Exception:
|
|
109
|
+
facts["foundation_models"] = {"note": "oracle execution map in configs/oracles/execution.yaml"}
|
|
110
|
+
return facts
|