"""Tool input/output schemas and compact result types.""" from enum import StrEnum from typing import Literal from pydantic import BaseModel, Field class AllocationPolicy(StrEnum): FIFO = "fifo" NEAREST_EXPIRY = "nearest_expiry" SPECIFIC = "specific" class Consumable(BaseModel): resource_key: str quantity: float | None = None unit: str allocation: AllocationPolicy = AllocationPolicy.FIFO container_id: int | None = None optional: bool = False class ProtocolAnnotation(BaseModel): consumables: list[Consumable] = Field(default_factory=list) prompt_quantity: bool = False class StepInfo(BaseModel): id: int position: int | None = None body: str finished: bool = False class ContainerStock(BaseModel): container_id: int amount: str available: float unit: str class NextStep(BaseModel): experiment_id: int step: StepInfo consumables: list[Consumable] = Field(default_factory=list) stock: list[ContainerStock] = Field(default_factory=list) class ItemSummary(BaseModel): id: int title: str resource_key: str | None = None class CommentInfo(BaseModel): id: int body: str created_at: str | None = None class ExperimentSummary(BaseModel): id: int title: str status: str | None = None class ExperimentContext(BaseModel): experiment_id: int title: str status: str | None = None unfinished_steps: list[StepInfo] = Field(default_factory=list) linked_resources: list[ItemSummary] = Field(default_factory=list) recent_comments: list[CommentInfo] = Field(default_factory=list) class AppState(BaseModel): experiment_id: int title: str next_step: NextStep | None = None unfinished_steps: list[int] = Field(default_factory=list) class HandoffContext(BaseModel): """Minimal id-only context the app forwards to the edge LLM.""" experiment_id: int user_id: int step_id: int | None = None device_id: str | None = None location_id: str | None = None class TemplateSummary(BaseModel): id: int title: str short_description: str tags: list[str] = Field(default_factory=list) class CreateExperimentResult(BaseModel): ok: bool = True experiment_id: int title: str first_step: StepInfo | None = None class ConsumedRecord(BaseModel): resource_key: str container_id: int amount: str remaining: str class CompleteStepResult(BaseModel): ok: bool = True status: Literal["completed", "protocol_complete", "reverted", "partial_failure"] = "completed" experiment_id: int step: StepInfo consumed: list[ConsumedRecord] = Field(default_factory=list) comment_id: int | None = None next_step: StepInfo | None = None class ObservationResult(BaseModel): ok: bool = True experiment_id: int comment_id: int | None = None step: StepInfo | None = None class InventoryResult(BaseModel): ok: bool = True container_id: int resource_key: str | None = None amount: str remaining: str class ContainerAllocation(BaseModel): container_id: int take: float unit: str remaining: str class MappingEntry(BaseModel): resource_key: str item_id: int title: str | None = None unit: str | None = None class MappingProposal(BaseModel): resource_key: str item_id: int title: str | None = None reason: str | None = None score: float | None = None class ScanResult(BaseModel): containers_seen: int = 0 markers_found: int = 0 proposals: list[MappingProposal] = Field(default_factory=list) class ValidationIssue(BaseModel): severity: Literal["error", "warning"] message: str step_id: int | None = None resource_key: str | None = None class ValidationReport(BaseModel): entity_type: str entity_id: int ok: bool issues: list[ValidationIssue] = Field(default_factory=list)