Spaces:
Running on Zero
Running on Zero
File size: 12,866 Bytes
0f2ecac 1163af8 0539596 0f2ecac 0539596 0f2ecac 413d5a1 0539596 1163af8 0539596 1163af8 0539596 1163af8 0539596 1163af8 0539596 1163af8 0539596 e726170 0539596 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | """Shared typed models for the GCMD classifier MVP."""
from enum import Enum
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
HierarchyLevel = Literal[
"Topic",
"Term",
"Variable_Level_1",
"Variable_Level_2",
"Variable_Level_3",
]
SourceHierarchyLevel = Literal[
"Category",
"Topic",
"Term",
"Variable_Level_1",
"Variable_Level_2",
"Variable_Level_3",
]
class CanonicalConceptRecord(BaseModel):
"""Canonical UUID-bearing GCMD concept record derived from the hierarchy."""
model_config = ConfigDict(frozen=True)
UUID: str
name: str
level: HierarchyLevel
topic: str
term: str | None = None
path_components: tuple[str, ...]
canonical_path: str
parent_uuid: str | None = None
parent_name: str | None = None
child_uuids: tuple[str, ...] = Field(default_factory=tuple)
has_children: bool
assignable: bool
definition: str | None = None
vocabulary_version: str
class ArticleRecord(BaseModel):
"""Validated source article record with exact serialized field names."""
model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
DOI: str
Title: str
Year: int
Abstract: str
class ArticleValidationIssue(BaseModel):
"""Structured validation issue for one article source record."""
model_config = ConfigDict(frozen=True)
index: int | None
code: str
message: str
field: str | None = None
DOI: str | None = None
class ArticleLoadResult(BaseModel):
"""Aggregate article loading result preserving valid records and all issues."""
model_config = ConfigDict(frozen=True)
articles: tuple[ArticleRecord, ...]
errors: tuple[ArticleValidationIssue, ...] = Field(default_factory=tuple)
source_count: int
@property
def valid_count(self) -> int:
"""Number of records that passed validation and duplicate checks."""
return len(self.articles)
@property
def has_errors(self) -> bool:
"""Whether any source records failed validation."""
return bool(self.errors)
class SupportType(str, Enum): # noqa: UP042
"""Uncalibrated model support category for a candidate selection."""
EXPLICIT = "explicit"
INFERRED = "inferred"
MIXED = "mixed"
class ArticleProcessingStatus(str, Enum): # noqa: UP042
"""Workflow execution state for one article."""
COMPLETED = "completed"
PARTIAL = "partial"
FAILED = "failed"
SKIPPED = "skipped"
class ArticleClassificationOutcome(str, Enum): # noqa: UP042
"""Semantic article-level outcome when processing completes."""
CLASSIFIED = "classified"
PENDING_REVIEW = "pending_review"
NOT_CLASSIFIED = "not_classified"
class ClassificationFinalStatus(str, Enum): # noqa: UP042
"""Final automated status for one classification candidate."""
ACCEPTED = "accepted"
REDUCED_TO_ANCESTOR = "reduced_to_ancestor"
REVIEW_REQUIRED = "review_required"
REJECTED = "rejected"
class ReviewStatus(str, Enum): # noqa: UP042
"""Human-review workflow state."""
NOT_REQUIRED = "not_required"
PENDING = "pending"
COMPLETED = "completed"
class StructuredMessage(BaseModel):
"""Structured warning or error message for output records."""
model_config = ConfigDict(extra="forbid", frozen=True)
code: str = Field(min_length=1)
message: str = Field(min_length=1)
stage: str | None = None
details: dict[str, Any] | None = None
index: int | None = None
DOI: str | None = None
class OutputWarning(StructuredMessage):
"""Non-fatal warning associated with an article or classification."""
class OutputError(StructuredMessage):
"""Structured failure associated with an article or classification."""
retry_count: int | None = Field(default=None, ge=0)
class DeterministicValidationResult(BaseModel):
"""Vocabulary validation result for a proposed final classification."""
model_config = ConfigDict(extra="forbid", frozen=True)
valid: bool
errors: tuple[OutputError, ...] = Field(default_factory=tuple)
warnings: tuple[OutputWarning, ...] = Field(default_factory=tuple)
class ConfidenceMetadata(BaseModel):
"""Optional, uncalibrated confidence signals from routing stages."""
model_config = ConfigDict(extra="forbid", frozen=True)
topic: float | None = Field(default=None, ge=0.0, le=1.0)
term: float | None = Field(default=None, ge=0.0, le=1.0)
variable_level_1: float | None = Field(default=None, ge=0.0, le=1.0)
variable_level_2: float | None = Field(default=None, ge=0.0, le=1.0)
variable_level_3: float | None = Field(default=None, ge=0.0, le=1.0)
final: float | None = Field(default=None, ge=0.0, le=1.0)
class OriginalCandidateReference(BaseModel):
"""Reference to an earlier candidate retained for reductions or diagnostics."""
model_config = ConfigDict(extra="forbid", frozen=True)
UUID: str = Field(min_length=1)
name: str | None = None
level: HierarchyLevel | None = None
canonical_path: str = Field(min_length=1)
path_components: tuple[str, ...] = Field(default_factory=tuple)
class SemanticValidationResult(BaseModel):
"""Draft semantic-validation result shape for future pipeline stages."""
model_config = ConfigDict(extra="forbid", frozen=True)
decision: str | None = None
support_level: str | None = None
deepest_supported_uuid: str | None = None
deepest_supported_path: str | None = None
unsupported_components: tuple[str, ...] = Field(default_factory=tuple)
evidence: str | None = None
errors: tuple[OutputError, ...] = Field(default_factory=tuple)
class ClassificationRecord(BaseModel):
"""Article output record for one GCMD classification or retained diagnostic candidate."""
model_config = ConfigDict(extra="forbid", frozen=True)
UUID: str = Field(min_length=1)
name: str = Field(min_length=1)
level: HierarchyLevel
canonical_path: str = Field(min_length=1)
path_components: tuple[str, ...] = Field(min_length=1)
topic: str = Field(min_length=1)
term: str | None = None
parent_uuid: str | None = None
branch_id: str | None = None
confidence: ConfidenceMetadata | None = None
classifier_evidence: str | None = None
support_type: SupportType | None = None
reason_for_stopping: str | None = None
deterministic_validation: DeterministicValidationResult
semantic_validation: SemanticValidationResult | None = None
final_status: ClassificationFinalStatus
review_required: bool = False
review_status: ReviewStatus | None = None
original_candidate: OriginalCandidateReference | None = None
warnings: tuple[OutputWarning, ...] = Field(default_factory=tuple)
errors: tuple[OutputError, ...] = Field(default_factory=tuple)
@model_validator(mode="after")
def accepted_results_require_valid_deterministic_validation(self) -> "ClassificationRecord":
"""Prevent accepted final statuses from carrying failed vocabulary validation."""
final_statuses_requiring_validity = {
ClassificationFinalStatus.ACCEPTED,
ClassificationFinalStatus.REDUCED_TO_ANCESTOR,
}
if (
self.final_status in final_statuses_requiring_validity
and not self.deterministic_validation.valid
):
raise ValueError(
"accepted or reduced classifications require deterministic_validation.valid"
)
return self
class ProcessingMetadata(BaseModel):
"""Reproducibility and runtime metadata for an article result."""
model_config = ConfigDict(extra="forbid", frozen=True)
run_id: str | None = None
processed_at: str | None = None
started_at: str | None = None
completed_at: str | None = None
model_provider: str | None = None
model_name: str | None = None
model_parameters: dict[str, Any] = Field(default_factory=dict)
prompt_versions: dict[str, str] = Field(default_factory=dict)
vocabulary_version: str | None = None
vocabulary_hash: str | None = None
application_version: str | None = None
configuration_hash: str | None = None
article_fingerprint: str | None = None
cache_used: bool = False
processing_time_seconds: float | None = Field(default=None, ge=0.0)
model_calls: int | None = Field(default=None, ge=0)
input_tokens: int | None = Field(default=None, ge=0)
output_tokens: int | None = Field(default=None, ge=0)
estimated_cost: float | None = Field(default=None, ge=0.0)
title_available: bool | None = None
abstract_available: bool | None = None
class ArticleResult(BaseModel):
"""Structured output for one article, preserving source fields exactly."""
model_config = ConfigDict(extra="forbid", frozen=True)
DOI: str = Field(strict=True)
Title: str = Field(strict=True)
Year: int = Field(strict=True)
Abstract: str = Field(strict=True)
processing_status: ArticleProcessingStatus
classification_outcome: ArticleClassificationOutcome | None = None
classifications: tuple[ClassificationRecord, ...] = Field(default_factory=tuple)
no_classification_reason: str | None = None
review_status: ReviewStatus = ReviewStatus.NOT_REQUIRED
warnings: tuple[OutputWarning, ...] = Field(default_factory=tuple)
errors: tuple[OutputError, ...] = Field(default_factory=tuple)
processing_metadata: ProcessingMetadata = Field(default_factory=ProcessingMetadata)
@field_validator("Year")
@classmethod
def reject_boolean_year(cls, value: int) -> int:
"""Keep article-output validation consistent with ArticleRecord."""
if isinstance(value, bool):
raise ValueError("Year must be a strict integer and not a Boolean.")
return value
@model_validator(mode="after")
def validate_article_result_consistency(self) -> "ArticleResult":
"""Apply article-level status-scope consistency rules."""
if self.processing_status is ArticleProcessingStatus.COMPLETED:
if self.classification_outcome is None:
raise ValueError("completed article results require classification_outcome")
if (
self.classification_outcome is ArticleClassificationOutcome.NOT_CLASSIFIED
and not self.no_classification_reason
):
raise ValueError("not_classified article results require no_classification_reason")
if (
self.classification_outcome is ArticleClassificationOutcome.NOT_CLASSIFIED
and self.classifications
):
raise ValueError("not_classified article results cannot include classifications")
if (
self.classification_outcome is ArticleClassificationOutcome.CLASSIFIED
and not self.classifications
):
raise ValueError("classified article results require at least one classification")
return self
class RunSummary(BaseModel):
"""Draft aggregate run-summary output schema."""
model_config = ConfigDict(extra="forbid", frozen=True)
run_id: str = Field(min_length=1)
started_at: str | None = None
completed_at: str | None = None
articles_received: int = Field(ge=0)
valid_article_records: int = Field(default=0, ge=0)
invalid_source_records: int = Field(default=0, ge=0)
processed_articles: int = Field(default=0, ge=0)
cache_hits: int = Field(default=0, ge=0)
cache_misses: int = Field(default=0, ge=0)
total_warnings: int = Field(default=0, ge=0)
total_errors: int = Field(default=0, ge=0)
duration_seconds: float | None = Field(default=None, ge=0.0)
articles_completed: int = Field(default=0, ge=0)
articles_partial: int = Field(default=0, ge=0)
articles_failed: int = Field(default=0, ge=0)
articles_skipped: int = Field(default=0, ge=0)
articles_not_classified: int = Field(default=0, ge=0)
articles_requiring_review: int = Field(default=0, ge=0)
accepted_classifications: int = Field(default=0, ge=0)
reduced_classifications: int = Field(default=0, ge=0)
rejected_candidates: int = Field(default=0, ge=0)
average_classifications_per_article: float | None = Field(default=None, ge=0.0)
average_processing_time_seconds: float | None = Field(default=None, ge=0.0)
total_model_calls: int | None = Field(default=None, ge=0)
input_tokens: int | None = Field(default=None, ge=0)
output_tokens: int | None = Field(default=None, ge=0)
estimated_total_cost: float | None = Field(default=None, ge=0.0)
warnings: tuple[OutputWarning, ...] = Field(default_factory=tuple)
errors: tuple[OutputError, ...] = Field(default_factory=tuple)
|