Spaces:
Sleeping
Sleeping
Download guided_workbench.py from automl-team/AutoML: direct link, hf CLI and curl.
- Browser
- Download file 87.8 kB
-
https://huggingface.co/spaces/automl-team/AutoML/resolve/main/guided_workbench.py
- Command line
-
hf download hf://spaces/automl-team/AutoML/guided_workbench.py
-
curl -L -o guided_workbench.py https://huggingface.co/spaces/automl-team/AutoML/resolve/main/guided_workbench.py
87.8 kB
| """Pure UI state and presentation helpers for the Guided Research Workbench. | |
| The module is intentionally independent from Gradio, gateway routing, and | |
| training code. It consumes only the stable UI contracts so state transitions | |
| and rendering can be tested without waking a backend. | |
| """ | |
| from __future__ import annotations | |
| import html | |
| import json | |
| import uuid | |
| from dataclasses import dataclass, field, replace | |
| from typing import Any, Mapping, Sequence | |
| from execution_metadata import normalize_execution_metadata | |
| from ui_contracts import ( | |
| CandidateVerification, | |
| CandidatePipeline, | |
| DecisionSource, | |
| DecisionTrace, | |
| ExperimentReflection, | |
| FeatureExtractionReport, | |
| MockResearchPlanProvider, | |
| ResearchPlan, | |
| ) | |
| WORKBENCH_STEPS = ( | |
| "data", | |
| "objective", | |
| "research", | |
| "pipeline", | |
| "compute", | |
| "review", | |
| "run", | |
| ) | |
| PIPELINE_NODE_IDS = ( | |
| "input", | |
| "preprocessing", | |
| "feature_extractor", | |
| "postprocessing", | |
| "model", | |
| "evaluation", | |
| ) | |
| PIPELINE_NODE_PRESENTATION = { | |
| "input": ("IN", "Input"), | |
| "preprocessing": ("PR", "Preprocessing"), | |
| "feature_extractor": ("FX", "Feature extractor"), | |
| "postprocessing": ("PO", "Post-processing"), | |
| "model": ("ML", "Model"), | |
| "evaluation": ("EV", "Evaluation"), | |
| } | |
| # Six stages share one row of the pipeline track, so the visible stage chip | |
| # needs labels that survive a ~130px card. The full names above still carry the | |
| # accessible name for each card. | |
| PIPELINE_NODE_SHORT_LABELS = { | |
| "input": "Input", | |
| "preprocessing": "Prepare", | |
| "feature_extractor": "Features", | |
| "postprocessing": "Transform", | |
| "model": "Model", | |
| "evaluation": "Evaluate", | |
| } | |
| SOURCE_LABELS = { | |
| DecisionSource.USER: "User", | |
| DecisionSource.PRESET: "Preset", | |
| DecisionSource.DETERMINISTIC_RULE: "Deterministic Rule", | |
| DecisionSource.BENCHMARK: "Benchmark", | |
| DecisionSource.RESEARCH_AGENT: "Research Agent", | |
| DecisionSource.LLM: "LLM", | |
| DecisionSource.FALLBACK: "Fallback", | |
| } | |
| _DATA_SOURCES = {"managed", "upload"} | |
| _DATA_MODALITIES = {"tabular", "image"} | |
| _AUTOMATIC_VALUES = {"", "auto", "automatic selection"} | |
| def _json_copy(value: Any) -> Any: | |
| return json.loads(json.dumps(value)) | |
| def _clean_text(value: Any, *, limit: int = 2_000) -> str: | |
| text = " ".join(str(value or "").split()) | |
| return text[:limit] | |
| def _positive_int(value: Any, field_name: str) -> int: | |
| if isinstance(value, bool): | |
| raise ValueError(f"{field_name} must be an integer") | |
| try: | |
| result = int(value) | |
| except (TypeError, ValueError) as exc: | |
| raise ValueError(f"{field_name} must be an integer") from exc | |
| if result < 1: | |
| raise ValueError(f"{field_name} must be positive") | |
| return result | |
| def _constraints(value: Any) -> tuple[str, ...]: | |
| if value is None: | |
| return () | |
| if isinstance(value, str): | |
| value = [value] | |
| if not isinstance(value, (list, tuple)): | |
| raise ValueError("constraints must be an array of strings") | |
| cleaned = tuple(_clean_text(item, limit=500) for item in value) | |
| return tuple(item for item in cleaned if item) | |
| def candidate_verification_map( | |
| plan: ResearchPlan, | |
| ) -> dict[str, CandidateVerification]: | |
| """Return the canonical verifier result for each candidate, if present.""" | |
| if plan.verification is None: | |
| return {} | |
| return { | |
| item.candidate_id: item for item in plan.verification.candidate_results | |
| } | |
| def candidate_is_executable(plan: ResearchPlan, candidate_id: str) -> bool: | |
| """Treat verifier rejection as a public UI block, never as execution authority.""" | |
| verification = candidate_verification_map(plan).get(_clean_text(candidate_id)) | |
| return verification is None or ( | |
| verification.compatible | |
| and verification.status in {"approved", "revised"} | |
| ) | |
| class RunVariant: | |
| """Serializable snapshot for one member of a two-run plan.""" | |
| variant_id: str | |
| data_source: str | |
| data_modality: str | |
| candidate_id: str | |
| dataset: str | |
| target: str | |
| trials: int | |
| preset: str | |
| time_limit: int | |
| image_backend: str | |
| research_plan: Mapping[str, Any] | |
| feature_report: Mapping[str, Any] | |
| approved_research: Mapping[str, Any] | |
| def to_dict(self) -> dict[str, Any]: | |
| return { | |
| "variant_id": self.variant_id, | |
| "data_source": self.data_source, | |
| "data_modality": self.data_modality, | |
| "candidate_id": self.candidate_id, | |
| "dataset": self.dataset, | |
| "target": self.target, | |
| "trials": self.trials, | |
| "preset": self.preset, | |
| "time_limit": self.time_limit, | |
| "image_backend": self.image_backend, | |
| "research_plan": _json_copy(dict(self.research_plan)), | |
| "feature_report": _json_copy(dict(self.feature_report)), | |
| "approved_research": _json_copy(dict(self.approved_research)), | |
| } | |
| class PipelineNodeView: | |
| """Presentation-ready projection of one pipeline stage.""" | |
| node_id: str | |
| label: str | |
| component: str | |
| source: DecisionSource | |
| parameters: tuple[tuple[str, str], ...] | |
| input_shape: tuple[int, ...] | |
| output_shape: tuple[int, ...] | |
| compatibility: str | |
| rationale: str | |
| alternatives: tuple[str, ...] | |
| trace: DecisionTrace | |
| feature_report: FeatureExtractionReport | None = None | |
| class WorkbenchState: | |
| """Immutable, JSON-serializable wizard state.""" | |
| schema_version: int = 1 | |
| step: str = "data" | |
| data_source: str = "managed" | |
| data_modality: str = "tabular" | |
| dataset_name: str = "" | |
| upload_type: str = "Tabular CSV" | |
| target: str = "" | |
| task_type: str = "classification" | |
| review_metric: str = "accuracy" | |
| user_objective: str = "" | |
| constraints: tuple[str, ...] = () | |
| research_plan: ResearchPlan | None = None | |
| selected_candidate_id: str = "" | |
| approved_candidate_id: str = "" | |
| approved_pipeline_config: Mapping[str, Any] = field(default_factory=dict) | |
| approved_evidence_ids: tuple[str, ...] = () | |
| approval_trace: DecisionTrace | None = None | |
| approval_error: str = "" | |
| selected_node_id: str = "input" | |
| preset: str = "medium_quality" | |
| time_limit: int = 300 | |
| trials: int = 1 | |
| image_backend: str = "Auto" | |
| feature_tool: str = "Automatic selection" | |
| transform_tool: str = "Automatic selection" | |
| ml_tool: str = "Automatic selection" | |
| variants: tuple[RunVariant, ...] = () | |
| provider_error: str = "" | |
| def __post_init__(self) -> None: | |
| if self.schema_version != 1: | |
| raise ValueError( | |
| f"unsupported workbench schema_version {self.schema_version!r}" | |
| ) | |
| if self.step not in WORKBENCH_STEPS: | |
| raise ValueError(f"unknown workbench step {self.step!r}") | |
| if self.data_source not in _DATA_SOURCES: | |
| raise ValueError(f"unknown data source {self.data_source!r}") | |
| if self.data_modality not in _DATA_MODALITIES: | |
| raise ValueError(f"unknown data modality {self.data_modality!r}") | |
| if self.selected_node_id not in PIPELINE_NODE_IDS: | |
| raise ValueError(f"unknown pipeline node {self.selected_node_id!r}") | |
| object.__setattr__(self, "constraints", _constraints(self.constraints)) | |
| if not isinstance(self.approved_pipeline_config, Mapping): | |
| raise ValueError("approved_pipeline_config must be a mapping") | |
| object.__setattr__( | |
| self, | |
| "approved_pipeline_config", | |
| _json_copy(dict(self.approved_pipeline_config)), | |
| ) | |
| object.__setattr__( | |
| self, | |
| "approved_evidence_ids", | |
| tuple(_clean_text(item, limit=200) for item in self.approved_evidence_ids), | |
| ) | |
| if self.approval_trace is not None and not isinstance( | |
| self.approval_trace, DecisionTrace | |
| ): | |
| raise ValueError("approval_trace must be a DecisionTrace or null") | |
| object.__setattr__(self, "time_limit", _positive_int(self.time_limit, "time_limit")) | |
| object.__setattr__(self, "trials", _positive_int(self.trials, "trials")) | |
| object.__setattr__(self, "variants", tuple(self.variants)) | |
| def current_candidate(self) -> CandidatePipeline: | |
| if self.research_plan is None: | |
| raise ValueError("Generate a research plan before selecting a candidate.") | |
| for candidate in self.research_plan.candidates: | |
| if candidate.candidate_id == self.selected_candidate_id: | |
| return candidate | |
| raise ValueError("The selected candidate is not present in the research plan.") | |
| def to_dict(self) -> dict[str, Any]: | |
| return { | |
| "schema_version": self.schema_version, | |
| "step": self.step, | |
| "data_source": self.data_source, | |
| "data_modality": self.data_modality, | |
| "dataset_name": self.dataset_name, | |
| "upload_type": self.upload_type, | |
| "target": self.target, | |
| "task_type": self.task_type, | |
| "review_metric": self.review_metric, | |
| "user_objective": self.user_objective, | |
| "constraints": list(self.constraints), | |
| "research_plan": ( | |
| self.research_plan.to_dict() if self.research_plan else None | |
| ), | |
| "selected_candidate_id": self.selected_candidate_id, | |
| "approved_candidate_id": self.approved_candidate_id, | |
| "approved_pipeline_config": _json_copy( | |
| dict(self.approved_pipeline_config) | |
| ), | |
| "approved_evidence_ids": list(self.approved_evidence_ids), | |
| "approval_trace": ( | |
| self.approval_trace.to_dict() if self.approval_trace else None | |
| ), | |
| "approval_error": self.approval_error, | |
| "selected_node_id": self.selected_node_id, | |
| "preset": self.preset, | |
| "time_limit": self.time_limit, | |
| "trials": self.trials, | |
| "image_backend": self.image_backend, | |
| "feature_tool": self.feature_tool, | |
| "transform_tool": self.transform_tool, | |
| "ml_tool": self.ml_tool, | |
| "variants": [variant.to_dict() for variant in self.variants], | |
| "provider_error": self.provider_error, | |
| } | |
| def _variant_from_mapping(value: Mapping[str, Any]) -> RunVariant: | |
| if not isinstance(value, Mapping): | |
| raise ValueError("run variant must be a mapping") | |
| data_source = _clean_text(value.get("data_source") or "managed").lower() | |
| if data_source not in _DATA_SOURCES: | |
| raise ValueError("run variant data_source is invalid") | |
| research_plan = value.get("research_plan") or {} | |
| feature_report = value.get("feature_report") or {} | |
| approved_research = value.get("approved_research") or {} | |
| if not isinstance(research_plan, Mapping) or not isinstance( | |
| feature_report, Mapping | |
| ) or not isinstance(approved_research, Mapping): | |
| raise ValueError("run variant snapshots must be mappings") | |
| return RunVariant( | |
| variant_id=_clean_text(value.get("variant_id")), | |
| data_source=data_source, | |
| data_modality=_clean_text( | |
| value.get("data_modality") or "tabular" | |
| ).lower(), | |
| candidate_id=_clean_text(value.get("candidate_id")), | |
| dataset=_clean_text(value.get("dataset")), | |
| target=_clean_text(value.get("target")), | |
| trials=_positive_int(value.get("trials", 1), "trials"), | |
| preset=_clean_text(value.get("preset") or "medium_quality"), | |
| time_limit=_positive_int(value.get("time_limit", 300), "time_limit"), | |
| image_backend=_clean_text(value.get("image_backend") or "Auto"), | |
| research_plan=_json_copy(dict(research_plan)), | |
| feature_report=_json_copy(dict(feature_report)), | |
| approved_research=_json_copy(dict(approved_research)), | |
| ) | |
| def state_from_mapping(value: Mapping[str, Any] | WorkbenchState) -> WorkbenchState: | |
| """Rebuild state from the plain mapping kept by ``gr.State``.""" | |
| if isinstance(value, WorkbenchState): | |
| return value | |
| if not isinstance(value, Mapping): | |
| raise ValueError("workbench state must be a mapping") | |
| version = value.get("schema_version", 1) | |
| if version != 1: | |
| raise ValueError(f"unsupported workbench schema_version {version!r}") | |
| raw_plan = value.get("research_plan") | |
| plan = ( | |
| None | |
| if raw_plan in (None, {}) | |
| else ResearchPlan.from_mapping(raw_plan) | |
| ) | |
| raw_variants = value.get("variants") or () | |
| if not isinstance(raw_variants, (list, tuple)): | |
| raise ValueError("variants must be an array") | |
| raw_approval_trace = value.get("approval_trace") | |
| approval_trace = ( | |
| None | |
| if raw_approval_trace in (None, {}) | |
| else DecisionTrace.from_mapping(raw_approval_trace) | |
| ) | |
| approved_config = value.get("approved_pipeline_config") or {} | |
| if not isinstance(approved_config, Mapping): | |
| raise ValueError("approved_pipeline_config must be a mapping") | |
| raw_evidence_ids = value.get("approved_evidence_ids") or () | |
| if not isinstance(raw_evidence_ids, (list, tuple)) or not all( | |
| isinstance(item, str) for item in raw_evidence_ids | |
| ): | |
| raise ValueError("approved_evidence_ids must be an array of strings") | |
| return WorkbenchState( | |
| step=_clean_text(value.get("step") or "data").lower(), | |
| data_source=_clean_text(value.get("data_source") or "managed").lower(), | |
| data_modality=_clean_text( | |
| value.get("data_modality") or "tabular" | |
| ).lower(), | |
| dataset_name=_clean_text(value.get("dataset_name")), | |
| upload_type=_clean_text(value.get("upload_type") or "Tabular CSV"), | |
| target=_clean_text(value.get("target")), | |
| task_type=_clean_text(value.get("task_type") or "classification"), | |
| review_metric=_clean_text(value.get("review_metric") or "accuracy"), | |
| user_objective=_clean_text(value.get("user_objective"), limit=1_000), | |
| constraints=_constraints(value.get("constraints")), | |
| research_plan=plan, | |
| selected_candidate_id=_clean_text(value.get("selected_candidate_id")), | |
| approved_candidate_id=_clean_text(value.get("approved_candidate_id")), | |
| approved_pipeline_config=_json_copy(dict(approved_config)), | |
| approved_evidence_ids=tuple(raw_evidence_ids), | |
| approval_trace=approval_trace, | |
| approval_error=_clean_text(value.get("approval_error")), | |
| selected_node_id=_clean_text( | |
| value.get("selected_node_id") or "input" | |
| ).lower(), | |
| preset=_clean_text(value.get("preset") or "medium_quality"), | |
| time_limit=_positive_int(value.get("time_limit", 300), "time_limit"), | |
| trials=_positive_int(value.get("trials", 1), "trials"), | |
| image_backend=_clean_text(value.get("image_backend") or "Auto"), | |
| feature_tool=_clean_text( | |
| value.get("feature_tool") or "Automatic selection" | |
| ), | |
| transform_tool=_clean_text( | |
| value.get("transform_tool") or "Automatic selection" | |
| ), | |
| ml_tool=_clean_text(value.get("ml_tool") or "Automatic selection"), | |
| variants=tuple(_variant_from_mapping(item) for item in raw_variants), | |
| provider_error=_clean_text(value.get("provider_error")), | |
| ) | |
| def new_workbench_state( | |
| *, | |
| data_source: str = "managed", | |
| data_modality: str = "tabular", | |
| dataset_name: str = "", | |
| upload_type: str = "Tabular CSV", | |
| target: str = "", | |
| task_type: str = "classification", | |
| user_objective: str = "", | |
| ) -> WorkbenchState: | |
| return WorkbenchState( | |
| data_source=_clean_text(data_source).lower() or "managed", | |
| data_modality=_clean_text(data_modality).lower() or "tabular", | |
| dataset_name=_clean_text(dataset_name), | |
| upload_type=_clean_text(upload_type) or "Tabular CSV", | |
| target=_clean_text(target), | |
| task_type=_clean_text(task_type) or "classification", | |
| user_objective=_clean_text(user_objective, limit=1_000), | |
| ) | |
| def update_context( | |
| state: Mapping[str, Any] | WorkbenchState, **changes: Any | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| allowed = { | |
| "data_source", | |
| "data_modality", | |
| "dataset_name", | |
| "upload_type", | |
| "target", | |
| "task_type", | |
| "review_metric", | |
| "user_objective", | |
| "constraints", | |
| "preset", | |
| "time_limit", | |
| "trials", | |
| "image_backend", | |
| "feature_tool", | |
| "transform_tool", | |
| "ml_tool", | |
| } | |
| unknown = set(changes) - allowed | |
| if unknown: | |
| raise ValueError(f"unknown workbench field {sorted(unknown)[0]!r}") | |
| normalized = dict(changes) | |
| for name in ( | |
| "data_source", | |
| "data_modality", | |
| "dataset_name", | |
| "upload_type", | |
| "target", | |
| "task_type", | |
| "review_metric", | |
| "user_objective", | |
| "preset", | |
| "image_backend", | |
| "feature_tool", | |
| "transform_tool", | |
| "ml_tool", | |
| ): | |
| if name in normalized: | |
| normalized[name] = _clean_text(normalized[name]) | |
| if "data_source" in normalized: | |
| normalized["data_source"] = normalized["data_source"].lower() | |
| if "data_modality" in normalized: | |
| normalized["data_modality"] = normalized["data_modality"].lower() | |
| if "constraints" in normalized: | |
| normalized["constraints"] = _constraints(normalized["constraints"]) | |
| if "time_limit" in normalized: | |
| normalized["time_limit"] = _positive_int( | |
| normalized["time_limit"], "time_limit" | |
| ) | |
| if "trials" in normalized: | |
| normalized["trials"] = _positive_int(normalized["trials"], "trials") | |
| research_fields = { | |
| "data_source", | |
| "data_modality", | |
| "dataset_name", | |
| "upload_type", | |
| "target", | |
| "task_type", | |
| "review_metric", | |
| "user_objective", | |
| "constraints", | |
| "time_limit", | |
| } | |
| if any( | |
| name in normalized and getattr(current, name) != normalized[name] | |
| for name in research_fields | |
| ): | |
| normalized.update( | |
| research_plan=None, | |
| selected_candidate_id="", | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| variants=(), | |
| provider_error="", | |
| ) | |
| approval_fields = { | |
| "image_backend", | |
| "feature_tool", | |
| "transform_tool", | |
| "ml_tool", | |
| } | |
| if any( | |
| name in normalized and getattr(current, name) != normalized[name] | |
| for name in approval_fields | |
| ): | |
| normalized.update( | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| ) | |
| return replace(current, **normalized) | |
| def set_step( | |
| state: Mapping[str, Any] | WorkbenchState, step: str | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| normalized = _clean_text(step).lower() | |
| if normalized not in WORKBENCH_STEPS: | |
| raise ValueError(f"unknown workbench step {normalized!r}") | |
| return replace(current, step=normalized) | |
| def _fallback_plan(context: Mapping[str, Any]) -> ResearchPlan: | |
| target = _clean_text(context.get("target_column") or "target") | |
| task = _clean_text(context.get("task_type") or "classification") | |
| return ResearchPlan.from_mapping( | |
| { | |
| "schema_version": 1, | |
| "plan_id": "fallback-research-plan-v1", | |
| "task_summary": f"Model {target} as {task} with automatic components.", | |
| "candidates": [ | |
| { | |
| "candidate_id": "fallback-automatic-pipeline", | |
| "name": "Conservative automatic pipeline", | |
| "task_type": task, | |
| "stages": [ | |
| {"stage": "preprocess", "component": "automatic"}, | |
| {"stage": "model", "component": "automatic"}, | |
| ], | |
| "rationale": "Preserves safe existing automatic defaults.", | |
| "expected_tradeoffs": [ | |
| "No agent comparison is available for this fallback." | |
| ], | |
| "expected_metrics": { | |
| "relative_quality": { | |
| "kind": "expected", | |
| "estimate": "unavailable", | |
| "basis": "fallback", | |
| } | |
| }, | |
| "measured_metrics": {}, | |
| "warnings": ["Research planning was unavailable."], | |
| } | |
| ], | |
| "recommendation": { | |
| "candidate_id": "fallback-automatic-pipeline", | |
| "rationale": "Use existing automatic defaults.", | |
| }, | |
| "warnings": [ | |
| "This fallback contains no measured results or live agent evidence." | |
| ], | |
| "decision_provenance": [ | |
| { | |
| "schema_version": 1, | |
| "trace_id": "fallback-plan-selection", | |
| "decision_type": "recommend_candidate", | |
| "decision_source": "fallback", | |
| "selected": "fallback-automatic-pipeline", | |
| "options": ["fallback-automatic-pipeline"], | |
| "rationale": "The research provider was unavailable.", | |
| } | |
| ], | |
| "provider_provenance": { | |
| "provider": "fallback", | |
| "network_used": False, | |
| "llm_used": False, | |
| }, | |
| } | |
| ) | |
| def _plan_with_constraint_recommendation( | |
| plan: ResearchPlan, constraints: Sequence[str] | |
| ) -> ResearchPlan: | |
| combined = " ".join(constraints).lower() | |
| if not any(word in combined for word in ("interpret", "runtime", "fast")): | |
| return plan | |
| preferred = next( | |
| ( | |
| candidate | |
| for candidate in plan.candidates | |
| if "interpret" in ( | |
| f"{candidate.candidate_id} {candidate.name}" | |
| ).lower() | |
| ), | |
| None, | |
| ) | |
| if preferred is None: | |
| preferred = next( | |
| ( | |
| candidate | |
| for candidate in plan.candidates | |
| if any( | |
| word in f"{candidate.candidate_id} {candidate.name}".lower() | |
| for word in ("efficient", "inspectable") | |
| ) | |
| ), | |
| None, | |
| ) | |
| if preferred is None: | |
| return plan | |
| data = plan.to_dict() | |
| data["recommendation"] = { | |
| "candidate_id": preferred.candidate_id, | |
| "rationale": ( | |
| "Selected by a deterministic refinement rule for the stated constraint." | |
| ), | |
| } | |
| data["decision_provenance"] = [ | |
| *data.get("decision_provenance", []), | |
| { | |
| "schema_version": 1, | |
| "trace_id": "mock-refinement-rule", | |
| "decision_type": "refine_recommendation", | |
| "decision_source": "deterministic_rule", | |
| "selected": preferred.candidate_id, | |
| "options": [item.candidate_id for item in plan.candidates], | |
| "rationale": "The refinement prioritizes interpretability or runtime.", | |
| }, | |
| ] | |
| return ResearchPlan.from_mapping(data) | |
| def generate_research_plan( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| provider: Any | None = None, | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| selected_provider = provider or MockResearchPlanProvider() | |
| context = { | |
| "dataset_name": current.dataset_name or current.upload_type, | |
| "modality": current.data_modality, | |
| "task_type": current.task_type, | |
| "target_column": current.target, | |
| "constraints": list(current.constraints), | |
| } | |
| try: | |
| plan = selected_provider.create_plan(context) | |
| if not isinstance(plan, ResearchPlan): | |
| plan = ResearchPlan.from_mapping(plan) | |
| plan = _plan_with_constraint_recommendation(plan, current.constraints) | |
| provider_error = "" | |
| except Exception: | |
| plan = _fallback_plan(context) | |
| provider_error = ( | |
| "Research planning was unavailable. " | |
| "A conservative fallback plan is shown." | |
| ) | |
| recommended = _clean_text(plan.recommendation.get("candidate_id")) | |
| executable_ids = [ | |
| item.candidate_id | |
| for item in plan.candidates | |
| if candidate_is_executable(plan, item.candidate_id) | |
| ] | |
| if recommended not in executable_ids: | |
| recommended = executable_ids[0] if executable_ids else "" | |
| return replace( | |
| current, | |
| research_plan=plan, | |
| selected_candidate_id=recommended, | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| provider_error=provider_error, | |
| ) | |
| def apply_research_plan( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| plan_value: Mapping[str, Any] | ResearchPlan, | |
| *, | |
| provider_error: str = "", | |
| ) -> WorkbenchState: | |
| """Apply one validated real plan while invalidating every prior approval.""" | |
| current = state_from_mapping(state) | |
| plan = ( | |
| plan_value | |
| if isinstance(plan_value, ResearchPlan) | |
| else ResearchPlan.from_mapping(plan_value) | |
| ) | |
| if not 1 <= len(plan.candidates) <= 3: | |
| raise ValueError("research plan must contain one to three candidates") | |
| recommended = _clean_text(plan.recommendation.get("candidate_id")) | |
| candidate_ids = {item.candidate_id for item in plan.candidates} | |
| executable_ids = [ | |
| item.candidate_id | |
| for item in plan.candidates | |
| if candidate_is_executable(plan, item.candidate_id) | |
| ] | |
| if recommended not in candidate_ids or recommended not in executable_ids: | |
| recommended = executable_ids[0] if executable_ids else "" | |
| return replace( | |
| current, | |
| research_plan=plan, | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| variants=(), | |
| provider_error=_clean_text(provider_error, limit=500), | |
| selected_candidate_id="", | |
| ) | |
| def apply_research_error( | |
| state: Mapping[str, Any] | WorkbenchState, message: str | |
| ) -> WorkbenchState: | |
| """Fail closed when the real research provider is unavailable or invalid.""" | |
| current = state_from_mapping(state) | |
| return replace( | |
| current, | |
| research_plan=None, | |
| selected_candidate_id="", | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| variants=(), | |
| provider_error=_clean_text(message, limit=500), | |
| ) | |
| def apply_candidate_approval( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| approval_value: Mapping[str, Any], | |
| ) -> WorkbenchState: | |
| """Apply a private-backend registry approval to the selected candidate.""" | |
| current = state_from_mapping(state) | |
| if current.research_plan is None or not current.selected_candidate_id: | |
| raise ValueError("select a research candidate before approval") | |
| if not isinstance(approval_value, Mapping): | |
| raise ValueError("candidate approval must be a mapping") | |
| approved_id = _clean_text(approval_value.get("approved_candidate_id")) | |
| plan_id = _clean_text(approval_value.get("plan_id")) | |
| if approved_id != current.selected_candidate_id: | |
| raise ValueError("approval does not match the selected candidate") | |
| if plan_id != current.research_plan.plan_id: | |
| raise ValueError("approval does not match the active research plan") | |
| if not candidate_is_executable(current.research_plan, approved_id): | |
| raise ValueError("candidate verification does not permit execution") | |
| config = approval_value.get("pipeline_config") | |
| evidence_ids = approval_value.get("evidence_ids") | |
| trace_value = approval_value.get("decision_trace") | |
| if not isinstance(config, Mapping): | |
| raise ValueError("approved pipeline config must be a mapping") | |
| if not isinstance(evidence_ids, (list, tuple)) or not all( | |
| isinstance(item, str) for item in evidence_ids | |
| ): | |
| raise ValueError("approved evidence IDs must be an array of strings") | |
| if not isinstance(trace_value, Mapping): | |
| raise ValueError("approval decision trace is required") | |
| trace = DecisionTrace.from_mapping(trace_value) | |
| if trace.decision_source is not DecisionSource.RESEARCH_AGENT: | |
| raise ValueError("approval trace must come from the research agent") | |
| if trace.selected != approved_id: | |
| raise ValueError("approval trace selected value does not match") | |
| config_copy = _json_copy(dict(config)) | |
| if config_copy.get("candidate_id") not in (None, approved_id): | |
| raise ValueError("approved pipeline config references another candidate") | |
| if config_copy.get("data_modality") not in (None, current.data_modality): | |
| raise ValueError("approved pipeline config has an incompatible modality") | |
| candidate = current.current_candidate | |
| known_evidence = { | |
| str(item.get("evidence_id")) | |
| for item in current.research_plan.evidence | |
| if isinstance(item, Mapping) and item.get("evidence_id") | |
| } | |
| if not set(evidence_ids) <= known_evidence: | |
| raise ValueError("approval references unknown research evidence") | |
| changes: dict[str, Any] = { | |
| "approved_candidate_id": approved_id, | |
| "approved_pipeline_config": config_copy, | |
| "approved_evidence_ids": tuple(evidence_ids), | |
| "approval_trace": trace, | |
| "approval_error": "", | |
| } | |
| control_fields = { | |
| "feature_selection_tool": "feature_tool", | |
| "transform_tool": "transform_tool", | |
| "ml_tool": "ml_tool", | |
| "image_backend": "image_backend", | |
| } | |
| for config_name, state_name in control_fields.items(): | |
| value = config_copy.get(config_name) | |
| if isinstance(value, str) and value.strip(): | |
| changes[state_name] = _clean_text(value) | |
| if not set(candidate.evidence_ids) >= set(evidence_ids): | |
| raise ValueError("approval contains evidence not used by the candidate") | |
| return replace(current, **changes) | |
| def apply_approval_error( | |
| state: Mapping[str, Any] | WorkbenchState, message: str | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| return replace( | |
| current, | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error=_clean_text(message, limit=500), | |
| ) | |
| def approved_research_metadata( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> dict[str, Any]: | |
| """Return the optional canonical metadata carried into an approved run.""" | |
| current = state_from_mapping(state) | |
| if ( | |
| current.research_plan is None | |
| or current.approved_candidate_id != current.selected_candidate_id | |
| or not current.approved_pipeline_config | |
| or current.approval_trace is None | |
| ): | |
| return {} | |
| return { | |
| "schema_version": 1, | |
| "research_plan": current.research_plan.to_dict(), | |
| "approved_candidate_id": current.approved_candidate_id, | |
| "pipeline_config": _json_copy(dict(current.approved_pipeline_config)), | |
| "evidence_ids": list(current.approved_evidence_ids), | |
| "approval_trace": current.approval_trace.to_dict(), | |
| } | |
| def select_candidate( | |
| state: Mapping[str, Any] | WorkbenchState, candidate_id: str | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| selected = _clean_text(candidate_id) | |
| if current.research_plan is None or selected not in { | |
| candidate.candidate_id for candidate in current.research_plan.candidates | |
| }: | |
| raise ValueError(f"unknown candidate_id {selected!r}") | |
| if not candidate_is_executable(current.research_plan, selected): | |
| raise ValueError("candidate verification does not permit execution") | |
| preset = current.preset | |
| if "interpretable" in selected.lower(): | |
| preset = "interpretable" | |
| elif "balanced" in selected.lower(): | |
| preset = "medium_quality" | |
| return replace( | |
| current, | |
| selected_candidate_id=selected, | |
| preset=preset, | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| ) | |
| def apply_next_experiment_draft( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| reflection: Mapping[str, Any] | ExperimentReflection, | |
| suggestion_id: str, | |
| ) -> WorkbenchState: | |
| """Create a fresh unapproved objective draft without execution authority.""" | |
| current = state_from_mapping(state) | |
| canonical = ( | |
| reflection | |
| if isinstance(reflection, ExperimentReflection) | |
| else ExperimentReflection.from_mapping(reflection) | |
| ) | |
| selected_id = _clean_text(suggestion_id) | |
| suggestion = next( | |
| (item for item in canonical.suggestions if item.suggestion_id == selected_id), | |
| None, | |
| ) | |
| if suggestion is None: | |
| raise ValueError(f"unknown next experiment suggestion {selected_id!r}") | |
| constraints: tuple[str, ...] = () | |
| if suggestion.executable and not suggestion.suggestion_only: | |
| components = tuple( | |
| _clean_text(stage.get("component"), limit=200) | |
| for stage in suggestion.stages | |
| if isinstance(stage, Mapping) and stage.get("component") | |
| ) | |
| constraints = tuple( | |
| dict.fromkeys( | |
| ( | |
| *(f"Trusted component: {component}" for component in components), | |
| *suggestion.expected_tradeoffs, | |
| ) | |
| ) | |
| ) | |
| drafted = update_context( | |
| current, | |
| user_objective=suggestion.title, | |
| constraints=constraints, | |
| ) | |
| # Clear explicitly even if a package repeats the current objective text. | |
| drafted = replace( | |
| drafted, | |
| research_plan=None, | |
| selected_candidate_id="", | |
| approved_candidate_id="", | |
| approved_pipeline_config={}, | |
| approved_evidence_ids=(), | |
| approval_trace=None, | |
| approval_error="", | |
| variants=(), | |
| provider_error="", | |
| ) | |
| return set_step(drafted, "objective") | |
| def apply_refinement( | |
| state: Mapping[str, Any] | WorkbenchState, refinement: str | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| text = _clean_text(refinement, limit=500) | |
| if not text: | |
| return current | |
| refined = replace(current, constraints=(*current.constraints, text)) | |
| return generate_research_plan(refined) | |
| def select_node( | |
| state: Mapping[str, Any] | WorkbenchState, node_id: str | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| selected = _clean_text(node_id).lower() | |
| if selected not in PIPELINE_NODE_IDS: | |
| raise ValueError(f"unknown pipeline node {selected!r}") | |
| return replace(current, selected_node_id=selected) | |
| def validate_state( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> tuple[str, ...]: | |
| current = state_from_mapping(state) | |
| issues: list[str] = [] | |
| if current.data_source == "managed" and not current.dataset_name: | |
| issues.append("Data: choose a managed dataset.") | |
| if current.data_source == "upload" and not current.upload_type: | |
| issues.append("Data: choose an upload type.") | |
| if not current.target: | |
| issues.append("Objective: choose an outcome to predict.") | |
| if current.research_plan is None: | |
| issues.append("Research: generate a research plan.") | |
| elif not current.selected_candidate_id: | |
| issues.append("Research: select a candidate pipeline.") | |
| else: | |
| candidate_ids = { | |
| candidate.candidate_id for candidate in current.research_plan.candidates | |
| } | |
| if current.selected_candidate_id not in candidate_ids: | |
| issues.append("Research: the selected candidate is no longer available.") | |
| elif not candidate_is_executable( | |
| current.research_plan, current.selected_candidate_id | |
| ): | |
| issues.append("Research: verifier rejected the selected candidate.") | |
| elif ( | |
| current.approved_candidate_id != current.selected_candidate_id | |
| or not current.approved_pipeline_config | |
| or current.approval_trace is None | |
| ): | |
| issues.append("Research: approve the selected candidate before Run.") | |
| if not 60 <= current.time_limit <= 3_600: | |
| issues.append("Compute: time budget must be between 60 and 3600 seconds.") | |
| if not 1 <= current.trials <= 4: | |
| issues.append("Compute: candidate runs must be between 1 and 4.") | |
| return tuple(issues) | |
| def _planned_feature_report(state: WorkbenchState) -> FeatureExtractionReport: | |
| method = state.feature_tool | |
| if method.lower() in _AUTOMATIC_VALUES: | |
| method = "Automatic feature extraction" | |
| return FeatureExtractionReport.from_mapping( | |
| { | |
| "schema_version": 1, | |
| "report_id": "planned-feature-report", | |
| "status": "planned", | |
| "modality": state.data_modality, | |
| "method": method, | |
| "input_shape": [], | |
| "output_shape": [], | |
| "features_per_sample": None, | |
| "parameters": {"selection": state.feature_tool}, | |
| "warnings": [ | |
| "Execution shapes are unavailable until the backend reports them." | |
| ], | |
| "provenance": { | |
| "phase": "planned", | |
| "source": ( | |
| "user" | |
| if state.feature_tool.lower() not in _AUTOMATIC_VALUES | |
| else "deterministic_rule" | |
| ), | |
| }, | |
| } | |
| ) | |
| def _candidate_stage_component( | |
| candidate: CandidatePipeline | None, stage_names: set[str], fallback: str | |
| ) -> str: | |
| if candidate is None: | |
| return fallback | |
| for stage in candidate.stages: | |
| stage_name = _clean_text(stage.get("stage")).lower() | |
| if stage_name in stage_names: | |
| component = _clean_text(stage.get("component")) | |
| if component: | |
| return component.replace("_", " ").title() | |
| return fallback | |
| def _is_user_selection(value: str) -> bool: | |
| return _clean_text(value).lower() not in _AUTOMATIC_VALUES | |
| def _node_trace( | |
| node_id: str, | |
| component: str, | |
| source: DecisionSource, | |
| rationale: str, | |
| alternatives: Sequence[str], | |
| parameters: Sequence[tuple[str, str]], | |
| ) -> DecisionTrace: | |
| return DecisionTrace.from_mapping( | |
| { | |
| "schema_version": 1, | |
| "trace_id": f"planned-{node_id}", | |
| "decision_type": f"select_{node_id}", | |
| "stage": node_id, | |
| "decision_source": source.value, | |
| "selected": component, | |
| "options": list(alternatives), | |
| "rationale": rationale, | |
| "parameters": dict(parameters), | |
| "provenance": {"phase": "planned"}, | |
| } | |
| ) | |
| def pipeline_nodes( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> tuple[PipelineNodeView, ...]: | |
| current = state_from_mapping(state) | |
| try: | |
| candidate = current.current_candidate | |
| except ValueError: | |
| candidate = None | |
| planned_report = _planned_feature_report(current) | |
| has_plan = candidate is not None | |
| input_component = ( | |
| current.dataset_name | |
| if current.data_source == "managed" | |
| else current.upload_type | |
| ) or "Data selection pending" | |
| preprocessing = _candidate_stage_component( | |
| candidate, | |
| {"preprocess", "preprocessing"}, | |
| "Automatic preprocessing", | |
| ) | |
| feature_component = planned_report.method | |
| postprocessing = ( | |
| current.transform_tool | |
| if _is_user_selection(current.transform_tool) | |
| else "Automatic post-processing" | |
| ) | |
| model = _candidate_stage_component( | |
| candidate, | |
| {"model", "training"}, | |
| current.ml_tool | |
| if _is_user_selection(current.ml_tool) | |
| else "Model selection pending", | |
| ) | |
| evaluation = f"{current.review_metric.title()} review" | |
| definitions = ( | |
| ( | |
| "input", | |
| "Input", | |
| input_component, | |
| DecisionSource.USER, | |
| (("source", current.data_source),), | |
| "The user selected the data source for this run.", | |
| ("Managed dataset", "Upload"), | |
| "compatible" if input_component != "Data selection pending" else "blocked", | |
| ), | |
| ( | |
| "preprocessing", | |
| "Preprocessing", | |
| preprocessing, | |
| DecisionSource.DETERMINISTIC_RULE, | |
| (("task", current.task_type),), | |
| "A deterministic compatibility rule supplies preprocessing defaults.", | |
| ("Automatic preprocessing", "Backend-compatible preprocessing"), | |
| "compatible" if has_plan else "review", | |
| ), | |
| ( | |
| "feature_extractor", | |
| "Feature Extractor", | |
| feature_component, | |
| ( | |
| DecisionSource.USER | |
| if _is_user_selection(current.feature_tool) | |
| else DecisionSource.DETERMINISTIC_RULE | |
| ), | |
| (("selection", current.feature_tool),), | |
| ( | |
| "The user selected this feature method." | |
| if _is_user_selection(current.feature_tool) | |
| else "A deterministic rule defers feature selection to the backend." | |
| ), | |
| ( | |
| "Automatic feature extraction", | |
| "No feature selection", | |
| "T-test feature selection", | |
| ), | |
| "compatible" if has_plan else "review", | |
| ), | |
| ( | |
| "postprocessing", | |
| "Post-processing", | |
| postprocessing, | |
| ( | |
| DecisionSource.USER | |
| if _is_user_selection(current.transform_tool) | |
| else DecisionSource.PRESET | |
| ), | |
| (("selection", current.transform_tool),), | |
| ( | |
| "The user selected this post-processing method." | |
| if _is_user_selection(current.transform_tool) | |
| else "The active preset supplies post-processing defaults." | |
| ), | |
| ( | |
| "Automatic post-processing", | |
| "No transformation", | |
| "PCA transformation", | |
| ), | |
| "compatible" if has_plan else "review", | |
| ), | |
| ( | |
| "model", | |
| "Model", | |
| model, | |
| ( | |
| DecisionSource.USER | |
| if _is_user_selection(current.ml_tool) | |
| else DecisionSource.PRESET | |
| ), | |
| (("quality_profile", current.preset),), | |
| ( | |
| "The user selected the training engine." | |
| if _is_user_selection(current.ml_tool) | |
| else "The selected candidate and quality preset define the model route." | |
| ), | |
| tuple( | |
| item.name for item in current.research_plan.candidates | |
| ) | |
| if current.research_plan | |
| else ("Generate a research plan",), | |
| "compatible" if has_plan else "blocked", | |
| ), | |
| ( | |
| "evaluation", | |
| "Evaluation", | |
| evaluation, | |
| DecisionSource.USER, | |
| (("review_priority", current.review_metric),), | |
| "The user selected which reported metric to emphasize during review.", | |
| ("Accuracy", "F1", "ROC AUC", "Backend-reported primary metric"), | |
| "compatible" if current.target else "blocked", | |
| ), | |
| ) | |
| result: list[PipelineNodeView] = [] | |
| for ( | |
| node_id, | |
| label, | |
| component, | |
| source, | |
| parameters, | |
| rationale, | |
| alternatives, | |
| compatibility, | |
| ) in definitions: | |
| trace = _node_trace( | |
| node_id, | |
| component, | |
| source, | |
| rationale, | |
| alternatives, | |
| parameters, | |
| ) | |
| result.append( | |
| PipelineNodeView( | |
| node_id=node_id, | |
| label=label, | |
| component=component, | |
| source=source, | |
| parameters=tuple(parameters), | |
| input_shape=(), | |
| output_shape=(), | |
| compatibility=compatibility, | |
| rationale=rationale, | |
| alternatives=tuple(alternatives), | |
| trace=trace, | |
| feature_report=( | |
| planned_report if node_id == "feature_extractor" else None | |
| ), | |
| ) | |
| ) | |
| return tuple(result) | |
| def _variant_signature(variant: RunVariant) -> tuple[Any, ...]: | |
| return ( | |
| variant.data_source, | |
| variant.candidate_id, | |
| variant.dataset, | |
| variant.target, | |
| variant.trials, | |
| variant.preset, | |
| variant.time_limit, | |
| variant.image_backend, | |
| ) | |
| def add_variant( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> WorkbenchState: | |
| current = state_from_mapping(state) | |
| if current.data_source != "managed": | |
| raise ValueError( | |
| "Parallel run plans currently support managed datasets only." | |
| ) | |
| issues = validate_state(current) | |
| if issues: | |
| raise ValueError(" ".join(issues)) | |
| if len(current.variants) >= 2: | |
| raise ValueError("A run plan supports at most two variants.") | |
| feature_report = _planned_feature_report(current) | |
| variant = RunVariant( | |
| variant_id=uuid.uuid4().hex, | |
| data_source=current.data_source, | |
| data_modality=current.data_modality, | |
| candidate_id=current.selected_candidate_id, | |
| dataset=current.dataset_name, | |
| target=current.target, | |
| trials=current.trials, | |
| preset=current.preset, | |
| time_limit=current.time_limit, | |
| image_backend=current.image_backend, | |
| research_plan=current.research_plan.to_dict(), | |
| feature_report=feature_report.to_dict(), | |
| approved_research=approved_research_metadata(current), | |
| ) | |
| if _variant_signature(variant) in { | |
| _variant_signature(item) for item in current.variants | |
| }: | |
| raise ValueError("This configuration is already in the run plan.") | |
| return replace(current, variants=(*current.variants, variant)) | |
| def source_badge_label(source: DecisionSource | str) -> str: | |
| try: | |
| normalized = DecisionSource(source) | |
| except (TypeError, ValueError) as exc: | |
| raise ValueError(f"unknown decision source {source!r}") from exc | |
| return SOURCE_LABELS[normalized] | |
| def _escape(value: Any) -> str: | |
| return html.escape(_clean_text(value), quote=True) | |
| def _list_html(values: Sequence[Any], *, empty: str = "None recorded") -> str: | |
| cleaned = [_clean_text(value) for value in values if _clean_text(value)] | |
| if not cleaned: | |
| return f'<p class="guided-empty-copy">{_escape(empty)}</p>' | |
| return "<ul>" + "".join(f"<li>{_escape(value)}</li>" for value in cleaned) + "</ul>" | |
| def _mapping_rows(value: Mapping[str, Any]) -> str: | |
| if not value: | |
| return '<p class="guided-empty-copy">Unavailable</p>' | |
| rows = [] | |
| for key, item in value.items(): | |
| normalized_key = str(key).lower() | |
| if item is None: | |
| rendered = "Not reported" | |
| elif isinstance(item, (list, tuple)) and not item: | |
| rendered = ( | |
| ("Not reported" if value.get("status") == "completed" else "Available after training") | |
| if "shape" in normalized_key | |
| else "None recorded" | |
| ) | |
| elif isinstance(item, dict) and not item: | |
| rendered = "Not reported" | |
| elif item == "": | |
| rendered = "Not reported" | |
| elif isinstance(item, Mapping): | |
| rendered = "; ".join( | |
| f"{str(nested_key).replace('_', ' ').title()}: " | |
| f"{_clean_text(str(nested_value)) if nested_value is not None else 'Not reported'}" | |
| for nested_key, nested_value in item.items() | |
| ) | |
| elif isinstance(item, (list, tuple)): | |
| separator = " × " if "shape" in normalized_key else ", " | |
| rendered = separator.join(_clean_text(entry) for entry in item) | |
| else: | |
| rendered = str(item) | |
| rows.append( | |
| '<div class="guided-detail-row">' | |
| f'<span>{_escape(str(key).replace("_", " ").title())}</span>' | |
| f'<strong>{_escape(rendered)}</strong>' | |
| "</div>" | |
| ) | |
| return "".join(rows) | |
| def _runtime_contracts( | |
| value: Mapping[str, Any] | None, | |
| ) -> tuple[FeatureExtractionReport | None, tuple[DecisionTrace, ...]]: | |
| """Validate optional terminal execution metadata for presentation.""" | |
| if not isinstance(value, Mapping): | |
| return None, () | |
| try: | |
| normalized = normalize_execution_metadata(value) | |
| except (TypeError, ValueError): | |
| return None, () | |
| report = None | |
| raw_report = normalized.get("feature_extraction_report") | |
| if isinstance(raw_report, Mapping): | |
| try: | |
| report = FeatureExtractionReport.from_mapping(raw_report) | |
| except (TypeError, ValueError): | |
| report = None | |
| traces: list[DecisionTrace] = [] | |
| raw_traces = normalized.get("decision_traces") or () | |
| for raw_trace in raw_traces: | |
| if not isinstance(raw_trace, Mapping): | |
| continue | |
| try: | |
| traces.append(DecisionTrace.from_mapping(raw_trace)) | |
| except (TypeError, ValueError): | |
| continue | |
| return report, tuple(traces) | |
| def _expected_metrics_html(candidate: CandidatePipeline) -> str: | |
| if not candidate.expected_metrics: | |
| return '<p class="guided-empty-copy">No estimates available</p>' | |
| cards = [] | |
| for name, value in candidate.expected_metrics.items(): | |
| estimate = value.get("estimate", "Unavailable") if isinstance(value, Mapping) else value | |
| basis = value.get("basis", "") if isinstance(value, Mapping) else "" | |
| cards.append( | |
| '<div class="guided-estimate">' | |
| '<small class="guided-metric-kind">Estimate · unmeasured</small>' | |
| f'<span>{_escape(str(name).replace("_", " ").title())}</span>' | |
| f'<strong>{_escape(str(estimate) if estimate is not None else "Unavailable")}</strong>' | |
| f'<small>{_escape(basis or "Planning estimate; not measured.")}</small>' | |
| "</div>" | |
| ) | |
| return "".join(cards) | |
| def candidate_display_name(candidate: CandidatePipeline) -> str: | |
| """Readable labels for known deterministic defaults; identity stays canonical.""" | |
| names = { | |
| "no_feature_selection_tool + no_transform_tool + autogluon_tool": "AutoGluon baseline", | |
| "t_test_feature_selection_tool + no_transform_tool + autogluon_tool": "AutoGluon with feature selection", | |
| "no_feature_selection_tool + pca_transform_tool + autogluon_tool": "AutoGluon with PCA", | |
| } | |
| return names.get(candidate.name, candidate.name or candidate.candidate_id) | |
| def candidate_cards_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> str: | |
| current = state_from_mapping(state) | |
| if current.research_plan is None: | |
| return ( | |
| '<section class="guided-empty-state">' | |
| "<h3>Research plan not generated</h3>" | |
| "<p>Complete Data and Objective, then generate a plan.</p>" | |
| "</section>" | |
| ) | |
| constraints = ( | |
| '<div class="guided-plan-context"><h4>Research constraints</h4>' | |
| f'{_list_html(current.constraints, empty="No additional constraints")}</div>' | |
| ) | |
| cards = [] | |
| verifications = candidate_verification_map(current.research_plan) | |
| recommended_id = _clean_text( | |
| current.research_plan.recommendation.get("candidate_id") | |
| ) | |
| for index, candidate in enumerate(current.research_plan.candidates, start=1): | |
| selected = candidate.candidate_id == current.selected_candidate_id | |
| recommended = candidate.candidate_id == recommended_id | |
| verification = verifications.get(candidate.candidate_id) | |
| executable = candidate_is_executable( | |
| current.research_plan, candidate.candidate_id | |
| ) | |
| recommended_badge = ( | |
| '<span class="guided-recommended-badge">Recommended</span>' | |
| if recommended | |
| else "" | |
| ) | |
| verification_badge = "" | |
| verification_details = "" | |
| if verification is not None: | |
| verification_badge = ( | |
| '<span class="guided-verification-badge ' | |
| f'{_escape(verification.status)}">Verifier · ' | |
| f'{_escape(verification.status.title())}</span>' | |
| ) | |
| verification_details = ( | |
| '<div class="guided-candidate-section guided-verification-detail">' | |
| '<h4>Verifier assessment</h4>' | |
| f'<p>{_escape(verification.revised_rationale or verification.rationale)}</p>' | |
| '<div class="guided-candidate-columns">' | |
| '<div><h4>Weaknesses</h4>' | |
| f'{_list_html(verification.weaknesses)}</div>' | |
| '<div><h4>Verifier warnings</h4>' | |
| f'{_list_html(verification.warnings)}</div></div></div>' | |
| ) | |
| execution_status = ( | |
| "Available after explicit approval" | |
| if executable | |
| else "Not executable · verifier rejected or marked incompatible" | |
| ) | |
| cards.append( | |
| f'<article class="guided-candidate-card' | |
| f'{" selected" if selected else ""}' | |
| f'{" recommended" if recommended else ""}' | |
| f'{" non-executable" if not executable else ""}' | |
| f'" data-candidate-id="{_escape(candidate.candidate_id)}">' | |
| f'<div class="guided-candidate-index">Candidate {index}</div>' | |
| f"{recommended_badge}{verification_badge}" | |
| f'<h3>{_escape(candidate_display_name(candidate))}</h3>' | |
| '<p class="guided-candidate-rationale">' | |
| f'{_escape(candidate.rationale or "No rationale provided")}</p>' | |
| '<div class="guided-candidate-section"><h4>Estimated metric values · unmeasured</h4>' | |
| f'<div class="guided-estimate-grid">{_expected_metrics_html(candidate)}</div></div>' | |
| '<details class="guided-candidate-details"><summary>Evidence and assessment</summary>' | |
| '<h4>Registered pipeline</h4>' | |
| f'<p>{_escape(candidate.name or candidate.candidate_id)}</p>' | |
| '<div class="guided-candidate-columns">' | |
| '<div><h4>Assumptions</h4>' | |
| f'{_list_html(candidate.assumptions)}</div>' | |
| '<div><h4>Expected tradeoffs</h4>' | |
| f'{_list_html(candidate.expected_tradeoffs)}</div>' | |
| "</div>" | |
| '<div class="guided-candidate-section"><h4>Evidence references</h4>' | |
| f'{_list_html(candidate.evidence_ids)}</div>' | |
| '<div class="guided-candidate-section"><h4>Warnings</h4>' | |
| f'{_list_html(candidate.warnings)}</div>' | |
| f"{verification_details}</details>" | |
| '<p class="guided-selection-state">' | |
| f'{_escape("Selected · " + execution_status if selected else execution_status)}</p>' | |
| "</article>" | |
| ) | |
| return ( | |
| '<section class="guided-candidate-list" aria-label="Research candidates">' | |
| f"{constraints}{''.join(cards)}</section>" | |
| ) | |
| def approval_status_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> str: | |
| current = state_from_mapping(state) | |
| if current.approval_error: | |
| return ( | |
| '<div class="guided-inline-error">' | |
| f'{_escape(current.approval_error)}</div>' | |
| ) | |
| if ( | |
| current.approved_candidate_id | |
| and current.approved_candidate_id == current.selected_candidate_id | |
| and current.approval_trace is not None | |
| ): | |
| return ( | |
| '<div class="guided-approval-status approved">' | |
| '<strong>Approved</strong><span>Registry validated · ' | |
| f'{_escape(source_badge_label(current.approval_trace.decision_source))}' | |
| "</span></div>" | |
| ) | |
| if current.selected_candidate_id: | |
| return ( | |
| '<div class="guided-approval-status pending">' | |
| '<strong>Approval required</strong>' | |
| '<span>Review and explicitly approve this candidate before Run.</span>' | |
| "</div>" | |
| ) | |
| return ( | |
| '<div class="guided-approval-status pending">' | |
| '<strong>Select a candidate</strong>' | |
| '<span>Run remains disabled until registry validation and approval.</span>' | |
| "</div>" | |
| ) | |
| def _evidence_card_html(item: Mapping[str, Any]) -> str: | |
| evidence_type = _clean_text(item.get("evidence_type") or "catalog") | |
| source = _clean_text(item.get("source") or "unknown") | |
| title = _clean_text( | |
| item.get("title") | |
| or item.get("name") | |
| or item.get("external_id") | |
| or item.get("evidence_id") | |
| ) | |
| detail_fields = ( | |
| "authors", | |
| "date", | |
| "external_id", | |
| "source_url", | |
| "source_reference", | |
| "query", | |
| "pipeline_stages", | |
| "modality", | |
| "task", | |
| "size", | |
| "target", | |
| "classes", | |
| "license", | |
| "authentication_required", | |
| "download_required", | |
| "architecture", | |
| "framework", | |
| "parameter_count", | |
| "memory_bytes", | |
| "compatibility", | |
| "executable", | |
| "suggestion_only", | |
| "status", | |
| ) | |
| details = { | |
| field: item[field] | |
| for field in detail_fields | |
| if field in item and item[field] not in (None, "", [], {}) | |
| } | |
| return ( | |
| '<article class="guided-evidence-card" ' | |
| f'data-evidence-type="{_escape(evidence_type)}">' | |
| '<div class="guided-evidence-heading">' | |
| f'<span>{_escape(evidence_type.replace("_", " ").title())}</span>' | |
| f'<small>{_escape(source.replace("_", " ").title())}</small></div>' | |
| f'<h4>{_escape(title)}</h4>' | |
| f'<p>{_escape(item.get("summary") or "No summary provided")}</p>' | |
| '<div class="guided-evidence-provenance"><strong>Evidence ID</strong>' | |
| f'<code>{_escape(item.get("evidence_id"))}</code></div>' | |
| f'{_mapping_rows(details)}</article>' | |
| ) | |
| def research_dossier_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> str: | |
| current = state_from_mapping(state) | |
| if current.research_plan is None: | |
| provider_error = ( | |
| f'<div class="guided-inline-error">{_escape(current.provider_error)}</div>' | |
| if current.provider_error | |
| else "" | |
| ) | |
| return ( | |
| '<section class="guided-dossier-empty">' | |
| '<div class="guided-empty-agent-heading">' | |
| '<span class="guided-research-icon" aria-hidden="true">RA</span>' | |
| '<div><h3>Research assistant</h3>' | |
| '<small>Private research backend</small></div></div>' | |
| '<p class="guided-agent-intro">Compare viable pipelines for your ' | |
| 'dataset, objective, and compute budget.</p>' | |
| '<article class="guided-empty-insight">' | |
| '<span>Research plan</span><h4>Ready when your objective is set</h4>' | |
| '<p>Generate a plan to review recommendations, tradeoffs, assumptions, ' | |
| 'and supporting evidence.</p></article>' | |
| f"{provider_error}" | |
| '<p class="guided-provider-note">Research is generated only when ' | |
| "the private backend feature is enabled.</p></section>" | |
| ) | |
| plan = current.research_plan | |
| recommendation = plan.recommendation | |
| decision_provenance = tuple( | |
| " · ".join( | |
| part | |
| for part in ( | |
| source_badge_label(trace.decision_source), | |
| _clean_text(trace.stage or trace.decision_type or "Plan decision"), | |
| _clean_text(trace.rationale), | |
| ( | |
| "Alternatives: " | |
| + ", ".join(_clean_text(item) for item in trace.options) | |
| if trace.options | |
| else "" | |
| ), | |
| ( | |
| "Evidence: " + ", ".join(trace.evidence_ids) | |
| if trace.evidence_ids | |
| else "" | |
| ), | |
| ( | |
| "Warnings: " + ", ".join(trace.warnings) | |
| if trace.warnings | |
| else "" | |
| ), | |
| ) | |
| if part | |
| ) | |
| for trace in plan.decision_provenance | |
| ) | |
| provider = _clean_text( | |
| plan.provider_provenance.get("provider") or "research_agent" | |
| ) | |
| planner_source = ( | |
| DecisionSource.LLM | |
| if plan.provider_provenance.get("llm_used") | |
| else DecisionSource.FALLBACK | |
| if plan.provider_provenance.get("fallback_used") | |
| else DecisionSource.RESEARCH_AGENT | |
| ) | |
| planner_model = _clean_text( | |
| plan.provider_provenance.get("llm_model") or "Not configured" | |
| ) | |
| verification = plan.verification | |
| verifier_source = ( | |
| verification.source if verification is not None else DecisionSource.FALLBACK | |
| ) | |
| verifier_model = _clean_text( | |
| ( | |
| verification.provider_provenance.get("model") | |
| if verification is not None | |
| else None | |
| ) | |
| or plan.provider_provenance.get("verification_model") | |
| or "Not configured" | |
| ) | |
| planner_status_html = ( | |
| '<div class="guided-agent-status-grid">' | |
| '<article><span class="guided-source-badge" data-source="' | |
| f'{_escape(planner_source.value)}">{_escape(source_badge_label(planner_source))}</span>' | |
| f'<h4>{_escape(source_badge_label(planner_source))} planner</h4>' | |
| f'<p>Model: {_escape(planner_model)}</p>' | |
| '<small>Planning only · no execution authority</small></article>' | |
| '<article><span class="guided-source-badge" data-source="' | |
| f'{_escape(verifier_source.value)}">{_escape(source_badge_label(verifier_source))}</span>' | |
| f'<h4>{_escape(source_badge_label(verifier_source))} verifier</h4>' | |
| f'<p>Status: {_escape(verification.status.title() if verification else "Unavailable")}</p>' | |
| f'<p>Model: {_escape(verifier_model)}</p>' | |
| '<small>Deterministic validation remains final</small></article></div>' | |
| ) | |
| evidence_cards = "".join( | |
| _evidence_card_html(item) for item in plan.evidence | |
| ) | |
| provider_error = ( | |
| f'<div class="guided-inline-error">{_escape(current.provider_error)}</div>' | |
| if current.provider_error | |
| else "" | |
| ) | |
| try: | |
| selected_candidate = current.current_candidate | |
| except ValueError: | |
| selected_candidate = None | |
| tradeoffs = ( | |
| selected_candidate.expected_tradeoffs | |
| if selected_candidate and selected_candidate.expected_tradeoffs | |
| else plan.expected_tradeoffs | |
| ) | |
| assumptions = ( | |
| selected_candidate.assumptions | |
| if selected_candidate and selected_candidate.assumptions | |
| else plan.assumptions | |
| ) | |
| warnings = tuple(dict.fromkeys( | |
| (*plan.warnings, *(selected_candidate.warnings if selected_candidate else ())) | |
| )) | |
| insight_blocks = [ | |
| _research_insight_html( | |
| "recommendation", | |
| "Recommendation", | |
| recommendation.get("candidate_id") or "Unavailable", | |
| recommendation.get("rationale") or "No rationale provided", | |
| ) | |
| ] | |
| if tradeoffs: | |
| insight_blocks.append( | |
| _research_insight_html( | |
| "tradeoff", "Tradeoff", "Expected tradeoff", tradeoffs[0] | |
| ) | |
| ) | |
| if assumptions: | |
| insight_blocks.append( | |
| _research_insight_html( | |
| "assumption", "Assumption", "Confirm before running", assumptions[0] | |
| ) | |
| ) | |
| for warning in warnings: | |
| insight_blocks.append( | |
| _research_insight_html( | |
| "warning", "Warning", "Review before running", warning | |
| ) | |
| ) | |
| return ( | |
| '<section class="guided-dossier">' | |
| '<div class="guided-dossier-heading"><div><span>Research assistant</span>' | |
| '<h3>Recommendation and evidence</h3></div>' | |
| '<span class="guided-research-provider">' | |
| f'<b class="guided-provider-badge">{_escape(provider.title())}</b>' | |
| '<small>Planning only · no automatic execution</small></span></div>' | |
| f"{provider_error}{planner_status_html}" | |
| '<div class="guided-dossier-block"><h4>Dataset profile</h4>' | |
| f'<p>{_escape(plan.extensions.get("dataset_profile_source") or "Profile source not recorded")}</p>' | |
| f'{_mapping_rows(plan.extensions.get("dataset_profile") or {})}</div>' | |
| f'<div class="guided-research-insights">{"".join(insight_blocks)}</div>' | |
| '<div class="guided-dossier-block"><h4>Verifier summary</h4>' | |
| f'<p>{_escape(verification.summary if verification else "No verifier result is available.")}</p>' | |
| f'{_list_html(verification.warnings if verification else ())}</div>' | |
| '<div class="guided-dossier-block"><h4>Evidence</h4>' | |
| f'<div class="guided-evidence-grid">{evidence_cards}</div></div>' | |
| '<div class="guided-dossier-block"><h4>Decision provenance</h4>' | |
| f'{_list_html(decision_provenance)}</div>' | |
| '<p class="guided-provider-note">External search: ' | |
| + ( | |
| "enabled; partial source failures are shown above." | |
| if plan.provider_provenance.get("external_search_enabled") | |
| else "disabled; deterministic planning and internal evidence remain available." | |
| ) | |
| + "</p>" | |
| "</section>" | |
| ) | |
| def _research_insight_html( | |
| kind: str, | |
| label: str, | |
| heading: str, | |
| body: str, | |
| ) -> str: | |
| return ( | |
| '<article class="guided-research-insight" ' | |
| f'data-insight-type="{_escape(kind)}">' | |
| f'<span class="guided-insight-label">{_escape(label)}</span>' | |
| f'<h4>{_escape(heading)}</h4>' | |
| f'<p>{_escape(body)}</p>' | |
| "</article>" | |
| ) | |
| def _shape_summary(input_shape: Sequence[int], output_shape: Sequence[int]) -> str: | |
| if not input_shape and not output_shape: | |
| return "Unavailable before execution" | |
| before = " x ".join(str(item) for item in input_shape) or "Unavailable" | |
| after = " x ".join(str(item) for item in output_shape) or "Unavailable" | |
| return f"{before} to {after}" | |
| def _parameter_summary(parameters: Sequence[tuple[str, str]]) -> str: | |
| if not parameters: | |
| return "Backend defaults" | |
| return ", ".join( | |
| f"{key.replace('_', ' ').title()}: {value}" for key, value in parameters | |
| ) | |
| def pipeline_node_labels( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> tuple[str, ...]: | |
| return tuple( | |
| "\n".join( | |
| ( | |
| node.label, | |
| node.component or "Selection pending", | |
| f"Source: {source_badge_label(node.source)}", | |
| f"Parameters: {_parameter_summary(node.parameters)}", | |
| f"Shape: {_shape_summary(node.input_shape, node.output_shape)}", | |
| f"Compatibility: {node.compatibility.title()}", | |
| ) | |
| ) | |
| for node in pipeline_nodes(state) | |
| ) | |
| _COMPATIBILITY_ICONS = { | |
| "compatible": "✓", | |
| "review": "!", | |
| "blocked": "×", | |
| } | |
| def pipeline_node_cards_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> tuple[str, ...]: | |
| """Render the compact, escaped visual contract for every pipeline node.""" | |
| current = state_from_mapping(state) | |
| cards = [] | |
| for index, node in enumerate(pipeline_nodes(current), start=1): | |
| selected = node.node_id == current.selected_node_id | |
| selected_class = " is-selected" if selected else "" | |
| compatibility = node.compatibility | |
| source = source_badge_label(node.source) | |
| component = node.component or "Selection pending" | |
| parameters = _parameter_summary(node.parameters) | |
| shape = _shape_summary(node.input_shape, node.output_shape) | |
| compatibility_icon = _COMPATIBILITY_ICONS[compatibility] | |
| node_icon, stage_name = PIPELINE_NODE_PRESENTATION[node.node_id] | |
| stage_chip = PIPELINE_NODE_SHORT_LABELS[node.node_id] | |
| current_attribute = ' aria-current="true"' if selected else "" | |
| selected_label = ( | |
| '<span class="guided-node-selected">Inspecting</span>' | |
| if selected | |
| else "" | |
| ) | |
| cards.append( | |
| f'<article class="guided-pipeline-card-content {compatibility}' | |
| f'{selected_class}" data-node-id="{_escape(node.node_id)}" ' | |
| f'data-stage-order="{index:02d}"{current_attribute} ' | |
| f'aria-label="{_escape(stage_name)}: {_escape(component)}; ' | |
| f'{_escape(compatibility)}">' | |
| '<div class="guided-node-topline">' | |
| f'<span class="guided-node-icon" aria-hidden="true">{node_icon}</span>' | |
| f'<span class="guided-node-stage-name" title="{_escape(stage_name)}">' | |
| f"{_escape(stage_chip)}</span>" | |
| "</div>" | |
| f"{selected_label}" | |
| f'<h4 title="{_escape(component)}">{_escape(component)}</h4>' | |
| f'<p class="guided-node-parameter" title="{_escape(parameters)}">' | |
| f"{_escape(parameters)}</p>" | |
| f'<p class="guided-node-shape" title="{_escape(shape)}">' | |
| f"{_escape(shape)}</p>" | |
| '<footer class="guided-node-footer">' | |
| f'<span class="guided-node-source">{_escape(source)}</span>' | |
| f'<span class="guided-node-compatibility {compatibility}">' | |
| f'<b aria-hidden="true">{compatibility_icon}</b>' | |
| f"{_escape(compatibility.title())}</span>" | |
| "</footer></article>" | |
| ) | |
| return tuple(cards) | |
| def node_inspector_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> str: | |
| current = state_from_mapping(state) | |
| node = next( | |
| item for item in pipeline_nodes(current) if item.node_id == current.selected_node_id | |
| ) | |
| trace = node.trace.to_dict() | |
| report = node.feature_report.to_dict() if node.feature_report else None | |
| trace_rows = { | |
| key: value | |
| for key, value in trace.items() | |
| if key not in {"extensions", "schema_version"} | |
| } | |
| report_html = ( | |
| '<div class="guided-inspector-contract"><h4>FeatureExtractionReport</h4>' | |
| f'{_mapping_rows(report)}</div>' | |
| if report is not None | |
| else ( | |
| '<div class="guided-inspector-contract"><h4>FeatureExtractionReport</h4>' | |
| '<p class="guided-empty-copy">Not applicable to this node</p></div>' | |
| ) | |
| ) | |
| return ( | |
| '<section class="guided-node-inspector">' | |
| '<div class="guided-inspector-heading"><div>' | |
| f'<span>{_escape(node.label)}</span><h3>{_escape(node.component)}</h3></div>' | |
| f'<span class="guided-compatibility {node.compatibility}">' | |
| f'{_escape(node.compatibility.title())}</span></div>' | |
| '<div class="guided-inspector-grid">' | |
| '<div><h4>Selection reason</h4>' | |
| f'<p>{_escape(node.rationale)}</p></div>' | |
| '<div><h4>Alternatives considered</h4>' | |
| f'{_list_html(node.alternatives)}</div>' | |
| "</div>" | |
| '<div class="guided-inspector-contract"><h4>DecisionTrace</h4>' | |
| '<div class="guided-detail-row"><span>Decision source</span><strong>' | |
| f'{_escape(source_badge_label(node.source))}</strong></div>' | |
| f"{_mapping_rows(trace_rows)}" | |
| "</div>" | |
| f"{report_html}" | |
| "</section>" | |
| ) | |
| def review_html(state: Mapping[str, Any] | WorkbenchState) -> str: | |
| current = state_from_mapping(state) | |
| issues = validate_state(current) | |
| if issues: | |
| return ( | |
| '<section class="guided-review blocked">' | |
| "<span>Needs attention</span><h3>The run plan is not ready</h3>" | |
| f'{_list_html(issues)}</section>' | |
| ) | |
| warnings = current.research_plan.warnings if current.research_plan else () | |
| return ( | |
| '<section class="guided-review ready">' | |
| "<span>Ready to run</span><h3>Review complete</h3>" | |
| '<div class="guided-review-grid">' | |
| '<div><small>Data</small><strong>' | |
| f'{_escape(current.dataset_name or current.upload_type)}</strong></div>' | |
| f'<div><small>Objective</small><strong>{_escape(current.target)}</strong></div>' | |
| '<div><small>Candidate</small><strong>' | |
| f'{_escape(candidate_display_name(current.current_candidate))}</strong></div>' | |
| f'<div><small>Quality profile</small><strong>{_escape(current.preset)}</strong></div>' | |
| f'<div><small>Time budget</small><strong>{current.time_limit} seconds</strong></div>' | |
| f'<div><small>Candidate trials</small><strong>{current.trials}</strong></div>' | |
| f'<div><small>Run variants</small><strong>{max(1, len(current.variants))}</strong></div>' | |
| "</div>" | |
| '<div class="guided-review-warnings"><h4>Warnings to review</h4>' | |
| f'{_list_html(warnings)}</div></section>' | |
| ) | |
| def run_plan_summary_html( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| ) -> str: | |
| current = state_from_mapping(state) | |
| issues = validate_state(current) | |
| run_count = max(1, len(current.variants)) | |
| variant_label = "variant" if run_count == 1 else "variants" | |
| candidate = "Selection pending" | |
| try: | |
| candidate = candidate_display_name(current.current_candidate) | |
| except ValueError: | |
| pass | |
| status = "Ready" if not issues else f"{len(issues)} item(s) need attention" | |
| return ( | |
| '<section class="guided-run-summary">' | |
| '<div class="guided-summary-status"><span>Run plan</span>' | |
| f'<strong>{_escape(status)}</strong></div>' | |
| '<div class="guided-summary-facts">' | |
| f'<span><small>Candidate</small>{_escape(candidate)}</span>' | |
| f'<span><small>Quality</small>{_escape(current.preset)}</span>' | |
| f'<span><small>Budget</small>{current.time_limit} seconds</span>' | |
| f'<span><small>Trials</small>{current.trials}</span>' | |
| f'<span><small>Variants</small>{run_count} {variant_label}</span>' | |
| "</div></section>" | |
| ) | |
| def _research_plan_snapshot(state: WorkbenchState) -> str: | |
| if state.research_plan is None: | |
| return '<p class="guided-empty-copy">Research plan unavailable</p>' | |
| plan = state.research_plan | |
| evidence = tuple( | |
| _clean_text(item.get("summary") or item.get("evidence_id")) | |
| for item in plan.evidence | |
| ) | |
| return ( | |
| '<section class="guided-snapshot guided-research-snapshot">' | |
| '<div class="guided-snapshot-heading"><span>Pre-run record</span>' | |
| '<h3>Research plan</h3></div>' | |
| '<div class="guided-dossier-block"><h4>Recommendation</h4>' | |
| f'<strong>{_escape(plan.recommendation.get("candidate_id") or "Unavailable")}</strong>' | |
| f'<p>{_escape(plan.recommendation.get("rationale") or "No rationale provided")}</p></div>' | |
| '<div class="guided-dossier-block"><h4>Evidence</h4>' | |
| f'{_list_html(evidence)}</div>' | |
| '<div class="guided-dossier-block"><h4>Assumptions</h4>' | |
| f'{_list_html(plan.assumptions)}</div>' | |
| '<div class="guided-dossier-block"><h4>Warnings</h4>' | |
| f'{_list_html(plan.warnings)}</div>' | |
| '<div class="guided-candidate-section"><h4>Candidate estimates</h4>' | |
| f'{candidate_cards_html(state)}</div></section>' | |
| ) | |
| def _pipeline_trace_snapshot( | |
| state: WorkbenchState, | |
| traces: Sequence[DecisionTrace] = (), | |
| ) -> str: | |
| if traces: | |
| entries = [] | |
| for trace in traces: | |
| selected = ( | |
| trace.selected if trace.selected is not None else "Not reported" | |
| ) | |
| trace_rows = { | |
| "Source": source_badge_label(trace.decision_source), | |
| "Stage": trace.stage or trace.decision_type or "Not reported", | |
| "Selected": selected, | |
| "Alternatives": list(trace.options), | |
| "Rationale": trace.rationale or "Not reported", | |
| "Parameters": dict(trace.parameters), | |
| "Evidence references": list(trace.evidence_ids), | |
| "Warnings": list(trace.warnings), | |
| } | |
| entries.append( | |
| '<article class="guided-trace-entry guided-runtime-trace-entry">' | |
| f'<span>{_escape(trace.stage or trace.decision_type or "Decision")}</span>' | |
| f'<h4>{_escape(selected)}</h4>' | |
| f"{_mapping_rows(trace_rows)}" | |
| "</article>" | |
| ) | |
| return ( | |
| '<section class="guided-snapshot guided-trace-snapshot">' | |
| '<div class="guided-snapshot-heading"><span>Execution provenance</span>' | |
| '<h3>Pipeline trace</h3></div>' | |
| f'<div class="guided-trace-list">{"".join(entries)}</div>' | |
| '<div class="guided-dossier-block"><h4>Research assumptions</h4>' | |
| f'{_list_html(state.research_plan.assumptions if state.research_plan else ())}' | |
| '</div></section>' | |
| ) | |
| entries = [] | |
| for node in pipeline_nodes(state): | |
| entries.append( | |
| '<article class="guided-trace-entry">' | |
| f'<span>{_escape(node.label)}</span><h4>{_escape(node.component)}</h4>' | |
| f'<p><strong>Decision source:</strong> {_escape(source_badge_label(node.source))}</p>' | |
| f'<p>{_escape(node.rationale)}</p></article>' | |
| ) | |
| return ( | |
| '<section class="guided-snapshot guided-trace-snapshot">' | |
| '<div class="guided-snapshot-heading"><span>Planned provenance</span>' | |
| '<h3>Pipeline trace</h3></div>' | |
| f'<div class="guided-trace-list">{"".join(entries)}</div></section>' | |
| ) | |
| def _feature_report_snapshot( | |
| state: WorkbenchState, | |
| report: FeatureExtractionReport | None = None, | |
| ) -> str: | |
| if report is not None: | |
| warning_html = ( | |
| '<div class="guided-dossier-block"><h4>Warnings</h4>' | |
| f"{_list_html(report.warnings)}</div>" | |
| if report.warnings | |
| else "" | |
| ) | |
| return ( | |
| '<section class="guided-snapshot guided-feature-snapshot">' | |
| '<div class="guided-snapshot-heading"><span>Execution report</span>' | |
| '<h3>Feature extraction</h3></div>' | |
| f'{_mapping_rows(report.to_dict())}{warning_html}</section>' | |
| ) | |
| report = _planned_feature_report(state) | |
| return ( | |
| '<section class="guided-snapshot guided-feature-snapshot">' | |
| '<div class="guided-snapshot-heading"><span>Planned report</span>' | |
| '<h3>Feature extraction</h3></div>' | |
| '<p class="guided-contract-note">Execution shapes are unavailable ' | |
| "unless the backend reports them.</p>" | |
| f'{_mapping_rows(report.to_dict())}</section>' | |
| ) | |
| def _reflection_metric_cards( | |
| metrics: Mapping[str, Any], *, measured: bool | |
| ) -> str: | |
| if not metrics: | |
| return '<p class="guided-empty-copy">No metrics recorded</p>' | |
| cards = [] | |
| for name, raw in metrics.items(): | |
| value = raw.get("value") if isinstance(raw, Mapping) else raw | |
| estimate = ( | |
| raw.get("estimate") if not measured and isinstance(raw, Mapping) else None | |
| ) | |
| basis = raw.get("basis") if isinstance(raw, Mapping) else "" | |
| if measured and value is None: | |
| continue | |
| displayed = value if measured else ( | |
| estimate if estimate is not None else "Requires validation" | |
| ) | |
| kind = "Measured" if measured else "Estimate · unmeasured" | |
| cards.append( | |
| '<div class="guided-estimate guided-reflection-metric">' | |
| f'<small class="guided-metric-kind">{_escape(kind)}</small>' | |
| f'<span>{_escape(str(name).replace("_", " ").title())}</span>' | |
| f'<strong>{_escape(str(displayed))}</strong>' | |
| f'<small>{_escape(basis)}</small></div>' | |
| ) | |
| return "".join(cards) or '<p class="guided-empty-copy">No metrics recorded</p>' | |
| def experiment_reflection_outputs( | |
| execution_metadata: Mapping[str, Any] | None, | |
| ) -> tuple[str, dict[str, Any], tuple[tuple[str, str], ...]]: | |
| """Project canonical reflection metadata without granting execution authority.""" | |
| empty = ( | |
| '<section class="guided-reflection-empty">' | |
| '<h3>No experiment reflection</h3>' | |
| '<p>A completed governed run may provide a bounded reflection here.</p>' | |
| "</section>" | |
| ) | |
| if not isinstance(execution_metadata, Mapping) or not execution_metadata: | |
| return empty, {}, () | |
| try: | |
| normalized = normalize_execution_metadata(execution_metadata) | |
| raw_reflection = normalized.get("experiment_reflection") | |
| if not isinstance(raw_reflection, Mapping): | |
| return empty, {}, () | |
| reflection = ExperimentReflection.from_mapping(raw_reflection) | |
| except (TypeError, ValueError): | |
| return empty, {}, () | |
| approved = normalized.get("approved_research") | |
| recommendation_id = "Unavailable" | |
| recommendation_rationale = "No original recommendation was recorded." | |
| approved_id = "Unavailable" | |
| pipeline_config: Mapping[str, Any] = {} | |
| if isinstance(approved, Mapping): | |
| raw_plan = approved.get("research_plan") | |
| if isinstance(raw_plan, Mapping): | |
| try: | |
| plan = ResearchPlan.from_mapping(raw_plan) | |
| recommendation_id = _clean_text( | |
| plan.recommendation.get("candidate_id") or "Unavailable" | |
| ) | |
| recommendation_rationale = _clean_text( | |
| plan.recommendation.get("rationale") | |
| or "No original recommendation rationale was recorded." | |
| ) | |
| except (TypeError, ValueError): | |
| pass | |
| approved_id = _clean_text( | |
| approved.get("approved_candidate_id") or "Unavailable" | |
| ) | |
| if isinstance(approved.get("pipeline_config"), Mapping): | |
| pipeline_config = approved["pipeline_config"] | |
| actual_decisions = [] | |
| for raw_trace in normalized.get("decision_traces") or (): | |
| if not isinstance(raw_trace, Mapping): | |
| continue | |
| try: | |
| trace = DecisionTrace.from_mapping(raw_trace) | |
| except (TypeError, ValueError): | |
| continue | |
| if trace.stage in {"research_verification", "candidate_approval", "experiment_reflection"}: | |
| continue | |
| selected = trace.selected if trace.selected is not None else "Not reported" | |
| actual_decisions.append( | |
| f"{trace.stage or trace.decision_type}: {selected} " | |
| f"({source_badge_label(trace.decision_source)})" | |
| ) | |
| suggestion_cards = [] | |
| choices: list[tuple[str, str]] = [] | |
| for suggestion in reflection.suggestions: | |
| choices.append((suggestion.title, suggestion.suggestion_id)) | |
| status = ( | |
| "Executable · fresh approval required" | |
| if suggestion.executable and not suggestion.suggestion_only | |
| else "Suggestion only · cannot execute" | |
| ) | |
| suggestion_cards.append( | |
| '<article class="guided-next-experiment-card ' | |
| f'{"executable" if suggestion.executable else "suggestion-only"}">' | |
| f'<span>{_escape(status)}</span><h4>{_escape(suggestion.title)}</h4>' | |
| f'<p>{_escape(suggestion.rationale)}</p>' | |
| '<div><h5>Expected tradeoffs</h5>' | |
| f'{_list_html(suggestion.expected_tradeoffs)}</div>' | |
| '<div><h5>Expected metric estimates</h5>' | |
| '<div class="guided-estimate-grid">' | |
| f'{_reflection_metric_cards(suggestion.expected_metrics, measured=False)}' | |
| "</div></div>" | |
| '<div><h5>Evidence references</h5>' | |
| f'{_list_html(suggestion.evidence_ids)}</div>' | |
| '<div><h5>Warnings</h5>' | |
| f'{_list_html(suggestion.warnings)}</div></article>' | |
| ) | |
| provider_model = _clean_text( | |
| reflection.provider_provenance.get("model") or "Not configured" | |
| ) | |
| reflection_source = source_badge_label(reflection.source) | |
| html_output = ( | |
| '<section class="guided-experiment-reflection">' | |
| '<div class="guided-snapshot-heading"><span>Post-run interpretation</span>' | |
| '<h3>Experiment reflection</h3></div>' | |
| '<div class="guided-reflection-sections">' | |
| '<article><h4>Original research recommendation</h4>' | |
| f'<strong>{_escape(recommendation_id)}</strong>' | |
| f'<p>{_escape(recommendation_rationale)}</p></article>' | |
| '<article><h4>Actual executed pipeline</h4>' | |
| f'<strong>Approved candidate: {_escape(approved_id)}</strong>' | |
| f'{_mapping_rows(pipeline_config)}' | |
| f'{_list_html(actual_decisions, empty="No actual execution decisions recorded")}</article>' | |
| '<article><h4>Measured results</h4>' | |
| '<div class="guided-estimate-grid">' | |
| f'{_reflection_metric_cards(reflection.measured_metrics, measured=True)}' | |
| "</div></article>" | |
| '<article><h4>Experiment reflection</h4>' | |
| '<span class="guided-source-badge" data-source="' | |
| f'{_escape(reflection.source.value)}">{_escape(reflection_source)}</span>' | |
| f'<p><strong>Expectations: {_escape(reflection.expectations_met.replace("_", " ").title())}</strong></p>' | |
| f'<p>{_escape(reflection.summary)}</p><p>{_escape(reflection.interpretation)}</p>' | |
| '<h5>Observed limitations</h5>' | |
| f'{_list_html(reflection.limitations)}' | |
| '<h5>Evidence references</h5>' | |
| f'{_list_html(reflection.evidence_ids)}' | |
| '<h5>Warnings</h5>' | |
| f'{_list_html(reflection.warnings)}' | |
| f'<small>Model: {_escape(provider_model)} · no automatic execution</small>' | |
| "</article></div>" | |
| '<div class="guided-dossier-block"><h4>Suggested next experiments</h4>' | |
| f'<div class="guided-next-experiment-grid">{"".join(suggestion_cards)}</div>' | |
| '<p class="guided-provider-note">Using a suggestion creates an unapproved ' | |
| "draft only. Fresh research, verification, validation, and explicit approval " | |
| "remain required.</p></div></section>" | |
| ) | |
| return html_output, reflection.to_dict(), tuple(choices) | |
| def snapshot_outputs( | |
| state: Mapping[str, Any] | WorkbenchState, | |
| execution_metadata: Mapping[str, Any] | None = None, | |
| ) -> tuple[str, str, str]: | |
| current = state_from_mapping(state) | |
| report, traces = _runtime_contracts(execution_metadata) | |
| return ( | |
| _research_plan_snapshot(current), | |
| _pipeline_trace_snapshot(current, traces), | |
| _feature_report_snapshot(current, report), | |
| ) | |
| __all__ = [ | |
| "PIPELINE_NODE_IDS", | |
| "SOURCE_LABELS", | |
| "WORKBENCH_STEPS", | |
| "PipelineNodeView", | |
| "RunVariant", | |
| "WorkbenchState", | |
| "add_variant", | |
| "apply_next_experiment_draft", | |
| "apply_refinement", | |
| "candidate_is_executable", | |
| "candidate_verification_map", | |
| "candidate_cards_html", | |
| "experiment_reflection_outputs", | |
| "generate_research_plan", | |
| "new_workbench_state", | |
| "node_inspector_html", | |
| "pipeline_node_cards_html", | |
| "pipeline_node_labels", | |
| "pipeline_nodes", | |
| "research_dossier_html", | |
| "review_html", | |
| "run_plan_summary_html", | |
| "select_candidate", | |
| "select_node", | |
| "set_step", | |
| "snapshot_outputs", | |
| "source_badge_label", | |
| "state_from_mapping", | |
| "update_context", | |
| "validate_state", | |
| ] | |