"""Pydantic models for the Pre Publish QC API contract."""

from __future__ import annotations

from datetime import datetime
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictFloat, StrictInt, StrictStr

RiskLevel = Literal["low", "medium", "high", "critical"]
RunStatus = Literal["completed", "failed"]
ProjectStatus = Literal["active", "deleting", "deleted"]
ManuscriptTextStatus = Literal["not_applicable", "pending", "available", "empty_text", "failed"]
ColumnDataType = Literal["numerical", "temporal", "text", "boolean", "id", "categorical"]
ColumnMetadataAiStatus = Literal["not_requested", "pending", "completed", "failed"]
TaskSubmissionStatus = Literal["queued", "running", "completed", "completed_with_errors", "failed", "cancelled"]
TaskQueueJobStatus = Literal[
    "queued",
    "dispatching",
    "running",
    "succeeded",
    "failed",
    "cancel_requested",
    "cancelled",
    "timed_out",
]
ResourceTaskStatus = Literal[
    "not_requested",
    "queued",
    "running",
    "completed",
    "completed_with_errors",
    "failed",
    "blocked",
    "cancelled",
    "not_available",
]
StageAnalysisStage = Literal[
    "table_detection",
    "table_metadata",
    "table_anomaly_detection",
    "table_qc_execution",
    "sheet_normalization",
]
StageRunPolicy = Literal["skip_existing", "force_rerun"]
StageTaskTargetLevel = Literal["project", "qc_run", "sheet", "table"]
StageBlockingReasonCode = Literal[
    "missing_data_files",
    "missing_table_detection",
    "missing_tables",
    "metadata_required",
    "qc_plan_required",
    "active_task_reused",
    "not_applicable",
]
TaskType = Literal[
    "pre_publish_qc.column_metadata.enrich_table",
    "pre_publish_qc.column_metadata.enrich_all_tables",
    "pre_publish_qc.semantic_qc.enrich_table",
    "pre_publish_qc.qc.execute_table",
    "pre_publish_qc.table_detection.detect_project_tables",
    "pre_publish_qc.table_detection.detect_sheet_tables",
    "pre_publish_qc.table_anomaly.detect_table",
    "pre_publish_qc.table_anomaly.detect_all_tables",
    "pre_publish_qc.sheet_normalization.normalize_sheet",
    "pre_publish_qc.qc_run.create",
]
BusinessOwnerType = Literal["pre_publish_qc.project", "pre_publish_qc.qc_run", "pre_publish_qc.sheet", "pre_publish_qc.table"]
SheetNormalizationErrorCode = Literal[
    "sheet_normalization.invalid_request",
    "sheet_normalization.file_conflict",
    "sheet_normalization.source_not_found",
    "sheet_normalization.source_not_excel_workbook",
    "sheet_normalization.unsupported_workbook_format",
    "sheet_normalization.unsafe_workbook_container",
    "sheet_normalization.sheet_not_found",
    "sheet_normalization.ambiguous_workbook",
    "sheet_normalization.context_budget_exceeded",
    "sheet_normalization.output_limits_exceeded",
    "sheet_normalization.no_normalizable_table_found",
    "sheet_normalization.invalid_manifest",
    "sheet_normalization.source_formula_not_allowed",
    "sheet_normalization.external_reference_not_allowed",
    "sheet_normalization.stage_not_applicable",
    "sheet_normalization.project_not_active",
]
SheetNormalizationSourceSubrangeRole = Literal[
    "data_values",
    "row_header",
    "column_header",
    "condition_label",
    "unit_label",
    "panel_title",
    "notes",
    "excluded_context",
]
SheetNormalizationSourceKind = Literal["uploaded", "generated"]
FileDerivationKind = Literal["sheet_normalization"]
DisplayValuePreservationStatus = Literal["preserved", "preserved_with_warnings", "changed_with_warnings"]
CodexAuthState = Literal["authenticated", "unauthenticated", "unknown"]
TableSemanticRole = Literal[
    "measurement",
    "replicate",
    "condition",
    "treatment",
    "control",
    "timepoint",
    "dose",
    "concentration",
    "statistic",
    "derived",
    "metadata",
    "id",
    "label",
    "other",
]
MeasurementRegionKind = Literal[
    "raw_measurement",
    "normalized_measurement",
    "derived_measurement",
    "statistic",
    "control_readout",
    "other",
]
ExperimentalAxisKind = Literal["row", "column", "mixed"]
VariableAxisKind = Literal["row", "column", "mixed", "unknown"]
TableMeasurementLayout = Literal["single_measurement_by_header_conditions", "variables_in_last_header_level"]
QcRuleType = Literal["precision_consistency", "mechanical_difference", "terminal_digit_distribution"]
QcRuleApplicabilityStatus = Literal["enabled", "disabled", "calculation_unavailable", "evidence_insufficient", "review_required"]
QcRuleEvaluationStatus = Literal["not_applicable", "calculation_unavailable", "passed", "suspicious"]
QcPlanRuleStatus = Literal["enabled", "disabled"]
TableQcPlanStatus = Literal["executable", "no_experimental_measurement", "no_executable_measurement_group"]
TableQcPlanEmptyReason = Literal[
    "no_experimental_measurement_detected",
    "missing_measurement_type",
    "no_usable_numeric_values",
    "unsupported_variable_type",
    "other",
]
QcValueSourceKind = Literal["raw_display_text", "raw_string", "normalized_number", "unavailable"]
SampleAlignmentStrategy = Literal["sample_id", "row_order", "column_order", "not_applicable", "unavailable"]
VariableExclusionReason = Literal[
    "missing_measurement_type",
    "non_measurement_variable",
    "no_numeric_values",
    "insufficient_sample_count",
    "alignment_unavailable",
    "unsupported_variable_type",
    "other",
]
DetectorType = Literal[
    "linear_relation",
    "arithmetic_progression",
    "duplicate_values",
    "decimal_precision_inconsistency",
    "trailing_digit_bias",
    "quantized_values",
]
DetectorApplicabilityStatus = Literal["enabled", "disabled", "review_required"]
QcRunResourceType = Literal["workbook", "sheet", "table"]
EvidenceType = Literal["title", "abstract", "method", "result", "figure_caption", "table_caption", "supplement_note", "other"]
EventLevel = Literal["info", "warning", "error"]
CellKind = Literal["blank", "number", "text", "boolean", "date", "formula", "other"]
PreviewKind = Literal["browser_pdf", "plain_text", "unsupported"]
DesignGradientKind = Literal["dose", "concentration", "timepoint", "dilution", "treatment_order", "other"]
ExpectedDifferenceSource = Literal[
    "replicate_noise",
    "biological_condition",
    "technical_condition",
    "derived_calculation",
    "design_gradient",
    "unknown",
]


class ContractModel(BaseModel):
    """Base class for generated-compatible contract boundary models."""

    model_config = ConfigDict(extra="forbid")


class ApiError(ContractModel):
    """Stable API error payload."""

    error_code: StrictStr = Field(description="Stable machine-readable error code.")
    message: StrictStr = Field(description="Displayable error message.")
    details: dict[str, Any] = Field(description="Structured diagnostics; local filesystem paths are forbidden.")


class AuthUser(ContractModel):
    """Authenticated user exposed to API clients."""

    user_id: StrictStr = Field(description="Stable user id.")
    username: StrictStr = Field(description="Login username.")
    display_name: StrictStr = Field(description="Human-readable display name.")


class LoginRequest(ContractModel):
    """Username/password login request."""

    username: StrictStr = Field(description="Login username.")
    password: StrictStr = Field(description="Login password.")


class AuthSession(ContractModel):
    """Authenticated session response."""

    user: AuthUser = Field(description="Authenticated user.")


class CsrfBootstrap(ContractModel):
    """CSRF bootstrap response for browser clients."""

    csrf_token: StrictStr = Field(description="CSRF token sent with unsafe API requests.")
    header_name: StrictStr = Field(description="Request header expected by the backend.")
    cookie_name: StrictStr = Field(description="CSRF cookie name.")


class CodexAuthStatus(ContractModel):
    """Current Codex CLI authentication status."""

    state: CodexAuthState = Field(description="Authentication state.")
    authenticated: StrictBool = Field(description="Whether Codex CLI is authenticated.")
    summary: StrictStr = Field(description="Human-readable status summary.")
    raw_output: StrictStr = Field(description="Raw command output captured by the backend.")
    codex_home: StrictStr = Field(description="Codex home directory used by the backend.")
    credential_path: StrictStr | None = Field(default=None, description="Credential file reference when available.")
    command_exit_code: StrictInt = Field(description="Exit code from the Codex CLI status command.")


class CodexDeviceLoginResult(ContractModel):
    """Codex CLI device login result."""

    auth_status: CodexAuthStatus = Field(description="Codex authentication status after starting login.")
    verification_uri: StrictStr | None = Field(default=None, description="Verification URL shown to the user.")
    user_code: StrictStr | None = Field(default=None, description="Device login user code.")
    expires_in_minutes: StrictInt | None = Field(default=None, description="Device login expiry in minutes.")
    raw_output: StrictStr = Field(description="Raw command output captured by the backend.")
    command_exit_code: StrictInt = Field(description="Exit code from the Codex CLI login command.")


class CodexLogoutResult(ContractModel):
    """Codex CLI logout result."""

    auth_status: CodexAuthStatus = Field(description="Codex authentication status after logout.")
    raw_output: StrictStr = Field(description="Raw command output captured by the backend.")
    command_exit_code: StrictInt = Field(description="Exit code from the Codex CLI logout command.")


class ColumnMetadataEnrichmentEvent(ContractModel):
    """Business task event visible to Pre Publish QC clients."""

    created_at: datetime = Field(description="Event creation timestamp.")
    event_type: StrictStr = Field(description="Event type key.")
    level: EventLevel = Field(description="Event severity.")
    message: StrictStr = Field(description="Human-readable event message.")
    payload: dict[str, Any] | None = Field(default=None, description="Optional structured event payload.")


class TableTaskTarget(ContractModel):
    """One workbook table targeted by an AI task."""

    project_id: StrictStr = Field(description="Owning project id.")
    qc_run_id: StrictStr = Field(description="Owning QC run id.")
    workbook_file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table region id.")


class StageTaskTarget(ContractModel):
    """Target scope for stage analysis operations."""

    target_level: StageTaskTargetLevel = Field(description="Target scope level.")
    project_id: StrictStr = Field(description="Owning project id.")
    qc_run_id: StrictStr | None = Field(description="Optional QC run scope.")
    workbook_file_name: StrictStr | None = Field(description="Optional workbook file name for sheet/table scopes.")
    sheet_name: StrictStr | None = Field(description="Optional sheet name for sheet/table scopes.")
    table_id: StrictStr | None = Field(description="Optional table id for table scope.")


class TaskSubmission(ContractModel):
    """Business task submission linked to a shared task queue job."""

    submission_id: StrictStr = Field(description="Business submission id.")
    task_job_id: StrictStr = Field(description="Shared task queue job id.")
    task_type: TaskType = Field(description="Pre Publish QC task type.")
    project_id: StrictStr = Field(description="Owning project id.")
    qc_run_id: StrictStr = Field(description="Owning QC run id.")
    business_owner_type: BusinessOwnerType = Field(description="Business owner type.")
    business_owner_id: StrictStr = Field(description="Business owner id.")
    status: TaskSubmissionStatus = Field(description="Business-level submission status.")
    target: StageTaskTarget | None = Field(description="Concrete project/qc_run/sheet/table target for this task.")
    created_at: datetime = Field(description="Submission creation timestamp.")
    updated_at: datetime = Field(description="Submission update timestamp.")
    completed_at: datetime | None = Field(default=None, description="Submission completion timestamp.")
    error_message: StrictStr | None = Field(default=None, description="Failure message when available.")
    result_artifact_ref: StrictStr | None = Field(default=None, description="Opaque result artifact reference.")


class TaskSubmissionBatchItem(ContractModel):
    """One item inside a batch submission."""

    target: TableTaskTarget = Field(description="Target table for this child submission.")
    submission: TaskSubmission = Field(description="Child task submission.")


class TaskSubmissionBatch(ContractModel):
    """Batch submission composed of one-table child submissions."""

    batch_submission_id: StrictStr = Field(description="Business batch submission id.")
    batch_tracking_id: StrictStr = Field(
        description="Business-only batch tracking id. It is not a shared task queue JobRun id; runtime queue job ids live on each child submission."
    )
    status: TaskSubmissionStatus = Field(description="Business-level batch status.")
    project_id: StrictStr = Field(description="Owning project id.")
    qc_run_id: StrictStr = Field(description="Owning QC run id.")
    task_type: TaskType = Field(description="Pre Publish QC batch task type.")
    total_count: StrictInt = Field(description="Total child submission count.")
    completed_count: StrictInt = Field(description="Completed child submission count.")
    failed_count: StrictInt = Field(description="Failed child submission count.")
    items: list[TaskSubmissionBatchItem] = Field(description="Child submissions in queue order.")
    created_at: datetime = Field(description="Batch creation timestamp.")
    updated_at: datetime = Field(description="Batch update timestamp.")
    completed_at: datetime | None = Field(default=None, description="Batch completion timestamp.")
    error_message: StrictStr | None = Field(default=None, description="Batch failure message when available.")


class DisplayValuePreservation(ContractModel):
    """How well normalized output preserves source visible values and number formats."""

    status: DisplayValuePreservationStatus = Field(description="Preservation status.")
    affected_cell_count: StrictInt = Field(default=0, description="Number of affected cells.")
    warnings: list[StrictStr] = Field(default_factory=list, description="Display-value preservation warnings.")
    notes: StrictStr | None = Field(default=None, description="Optional preservation notes.")


class SheetNormalizationContextBudget(ContractModel):
    """Backend-owned submit-time budget for sheet normalization context extraction."""

    max_non_empty_cells: StrictInt = Field(description="Maximum non-empty source sheet cells.")
    max_used_rows: StrictInt = Field(description="Maximum source sheet used rows.")
    max_used_columns: StrictInt = Field(description="Maximum source sheet used columns.")
    max_context_json_bytes: StrictInt = Field(description="Maximum generated context JSON bytes.")
    max_manuscript_references: StrictInt = Field(description="Maximum manuscript references available to the runner.")


class SheetNormalizationOutputLimits(ContractModel):
    """Backend-owned output and xlsx container safety limits."""

    max_output_file_bytes: StrictInt = Field(description="Maximum output .xlsx file size.")
    max_normalized_tables: StrictInt = Field(description="Maximum normalized table worksheets.")
    max_output_rows_per_table: StrictInt = Field(description="Maximum data rows per normalized table.")
    max_output_columns_per_table: StrictInt = Field(description="Maximum variable columns per normalized table.")
    max_uncompressed_xlsx_bytes: StrictInt = Field(description="Maximum uncompressed .xlsx container bytes.")
    max_xlsx_archive_member_bytes: StrictInt = Field(description="Maximum uncompressed bytes for one .xlsx zip archive member.")
    max_xlsx_archive_members: StrictInt = Field(description="Maximum .xlsx zip archive members.")
    max_shared_strings: StrictInt = Field(description="Maximum shared strings in an .xlsx workbook.")


class SheetNormalizationExecutionPolicy(ContractModel):
    """First-version execution policy for formulas and displayed values."""

    source_formula_policy: Literal["warn_and_trace"] = Field(description="Formula handling policy.")
    display_value_policy: Literal["preserve_or_warn"] = Field(description="Displayed-value handling policy.")


class SheetNormalizationExecutionMetadata(ContractModel):
    """Auditable, non-sensitive sheet normalization execution metadata."""

    manifest_schema_version: StrictInt = Field(description="Manifest schema version, first version is 1.")
    handler_version: StrictStr = Field(description="Backend handler version.")
    prompt_template_version: StrictStr = Field(description="Prompt template version.")
    runner_name: StrictStr = Field(description="Runner name.")
    runner_model: StrictStr | None = Field(description="Runner model when available.")
    runner_version: StrictStr | None = Field(description="Runner version when available.")
    execution_summary: StrictStr | None = Field(description="Short displayable execution summary.")


class NormalizedTableManuscriptReference(ContractModel):
    """Manuscript reference used to interpret a normalized table."""

    file_id: StrictStr = Field(description="Project manuscript file id.")
    file_name: StrictStr = Field(description="Manuscript file name.")
    page_number: StrictInt | None = Field(default=None, description="PDF page number when available.")
    section_or_caption: StrictStr | None = Field(default=None, description="Section or caption clue when available.")
    notes: StrictStr | None = Field(default=None, description="Reference notes.")


class NormalizedTableSourceRange(ContractModel):
    """Source range in the original sheet."""

    cell_range: StrictStr = Field(description="A1-style source range.")
    start_row: StrictInt = Field(description="1-based start row.")
    start_column: StrictInt = Field(description="1-based start column.")
    end_row: StrictInt = Field(description="1-based end row.")
    end_column: StrictInt = Field(description="1-based end column.")
    label: StrictStr = Field(description="Display label.")
    role: SheetNormalizationSourceSubrangeRole = Field(description="Controlled source range role.")
    notes: StrictStr | None = Field(default=None, description="Range notes.")


class NormalizedTableSourceSubrange(NormalizedTableSourceRange):
    """Semantic subrange within a source range."""


class NormalizedTableOutput(ContractModel):
    """One normalized table mapped to one generated workbook worksheet."""

    normalized_table_id: StrictStr = Field(description="Unique table id within one normalization run.")
    label: StrictStr = Field(description="Display label.")
    output_file_id: StrictStr = Field(description="Generated output file id.")
    output_workbook_file_name: StrictStr = Field(description="Generated output workbook file name.")
    output_sheet_name: StrictStr = Field(description="Generated worksheet name.")
    source_file_id: StrictStr | None = Field(description="Source project file id when available.")
    source_workbook_file_name: StrictStr = Field(description="Source workbook file name.")
    source_sheet_name: StrictStr = Field(description="Source sheet name.")
    source_ranges: list[NormalizedTableSourceRange] = Field(description="Source ranges in the original sheet.")
    source_subranges: list[NormalizedTableSourceSubrange] = Field(description="Semantic source subranges.")
    manuscript_references: list[NormalizedTableManuscriptReference] = Field(description="Manuscript references used for this table.")
    row_count: StrictInt = Field(description="Data/sample row count excluding header rows; must be >= 1.")
    column_count: StrictInt = Field(description="Total worksheet column count, including sample index columns and data/variable columns; must be >= 1.")
    header_row_count: StrictInt = Field(default=1, description="Number of worksheet header rows; must be >= 1.")
    sample_index_column_count: StrictInt = Field(default=0, description="Number of leading sample index columns before variable/data columns; must be >= 0.")
    source_formula_cells_count: StrictInt = Field(default=0, description="Formula cells used in source ranges.")
    display_value_preservation: DisplayValuePreservation = Field(description="Visible value/format preservation status.")
    transformation_summary: StrictStr = Field(description="Short transformation explanation.")
    warnings: list[StrictStr] = Field(default_factory=list, description="Table-level warnings.")


class SheetNormalizationFileSummary(ContractModel):
    """Lightweight sheet normalization summary embedded in generated file resources."""

    normalization_run_id: StrictStr = Field(description="Backend-generated normalization run id.")
    status: TaskSubmissionStatus = Field(description="Business task status.")
    source_file_id: StrictStr | None = Field(description="Source project file id when available.")
    source_workbook_file_name: StrictStr = Field(description="Source workbook file name.")
    source_sheet_name: StrictStr = Field(description="Source sheet name.")
    normalized_tables: list[NormalizedTableOutput] = Field(description="Normalized table summaries.")
    manuscript_references: list[NormalizedTableManuscriptReference] = Field(description="Run-level manuscript references.")
    created_at: datetime = Field(description="Creation timestamp.")
    completed_at: datetime | None = Field(description="Completion timestamp.")
    warnings: list[StrictStr] = Field(default_factory=list, description="Run warnings.")
    error_code: StrictStr | None = Field(description="Stable error code when failed.")
    error_message: StrictStr | None = Field(description="Displayable error message when failed.")
    execution_metadata: SheetNormalizationExecutionMetadata | None = Field(description="Execution metadata when available.")
    output_file_unavailable_reason: StrictStr | None = Field(description="Reason a historical output file is unavailable.")


class SheetNormalizationFileDerivation(ContractModel):
    """Generated file derivation for sheet normalization outputs."""

    kind: FileDerivationKind = Field(description="Derivation discriminator.")
    normalization_run_id: StrictStr = Field(description="Backend-generated normalization run id.")
    task_submission_id: StrictStr = Field(description="Business task submission id.")
    source_file_id: StrictStr | None = Field(description="Source project file id when available.")
    source_workbook_file_name: StrictStr = Field(description="Source workbook file name.")
    source_sheet_name: StrictStr = Field(description="Source sheet name.")
    normalized_tables: list[NormalizedTableOutput] = Field(description="Normalized tables carried by this generated file.")
    manuscript_references: list[NormalizedTableManuscriptReference] = Field(description="Run-level manuscript references.")
    execution_metadata: SheetNormalizationExecutionMetadata = Field(description="Execution metadata.")


FileDerivation = SheetNormalizationFileDerivation


class CreateSheetNormalizationTaskSubmissionRequest(ContractModel):
    """Request to normalize one registered workbook sheet."""

    target_file_id: StrictStr | None = Field(default=None, description="Preferred source project file id.")
    target_workbook_file_name: StrictStr = Field(description="Registered workbook file name.")
    target_sheet_name: StrictStr = Field(description="Registered source sheet name.")


class SheetNormalizationResult(ContractModel):
    """Sheet normalization result detail."""

    normalization_run_id: StrictStr = Field(description="Backend-generated normalization run id.")
    task_submission: TaskSubmission = Field(description="Business task submission.")
    status: TaskSubmissionStatus = Field(description="Business task status.")
    source_file_id: StrictStr | None = Field(description="Source project file id when available.")
    source_workbook_file_name: StrictStr = Field(description="Source workbook file name.")
    source_sheet_name: StrictStr = Field(description="Source sheet name.")
    output_file: UploadedFileSummary | None = Field(default=None, description="Generated output file when available.")
    output_file_id: StrictStr | None = Field(default=None, description="Generated output file id when available.")
    output_workbook_file_name: StrictStr | None = Field(default=None, description="Generated workbook file name when available.")
    output_file_unavailable_reason: StrictStr | None = Field(default=None, description="Reason a historical output file is unavailable.")
    normalized_tables: list[NormalizedTableOutput] = Field(description="Normalized tables.")
    manuscript_references: list[NormalizedTableManuscriptReference] = Field(description="Run-level manuscript references.")
    created_at: datetime = Field(description="Creation timestamp.")
    completed_at: datetime | None = Field(default=None, description="Completion timestamp.")
    warnings: list[StrictStr] = Field(default_factory=list, description="Run warnings.")
    error_code: StrictStr | None = Field(default=None, description="Stable error code when failed.")
    error_message: StrictStr | None = Field(default=None, description="Displayable error message when failed.")
    execution_metadata: SheetNormalizationExecutionMetadata | None = Field(default=None, description="Execution metadata when available.")


class SheetNormalizationResultList(ContractModel):
    """Paged sheet normalization result list."""

    items: list[SheetNormalizationResult] = Field(description="Results ordered by created_at descending.")
    total_count: StrictInt = Field(description="Total result count.")
    limit: StrictInt = Field(default=50, description="Page size, max 100.")
    offset: StrictInt = Field(default=0, description="Page offset.")


class SheetNormalizationTaskSubmissionResult(ContractModel):
    """Response returned when creating or reusing a sheet normalization submission."""

    task_submission: TaskSubmission = Field(description="Created or reused submission.")
    sheet_normalization_result: SheetNormalizationResult = Field(description="Current business result projection.")


class StageBlockingReason(ContractModel):
    """Reason a stage task cannot start or was intentionally not created."""

    code: StageBlockingReasonCode = Field(description="Machine-readable blocking reason.")
    message: StrictStr = Field(description="Human-readable blocking explanation.")
    stage: StageAnalysisStage = Field(description="Stage affected by this blocking reason.")
    target: StageTaskTarget | None = Field(description="Target resource when the block is scope-specific.")


class StageTaskStatus(ContractModel):
    """Local task status for one stage on one resource."""

    stage: StageAnalysisStage = Field(description="Stage represented by this status.")
    status: ResourceTaskStatus = Field(description="Local resource task status.")
    can_start: StrictBool = Field(description="Whether the current user can start this stage for the resource.")
    blocking_reasons: list[StageBlockingReason] = Field(description="Dependency or applicability blocks.")
    latest_submission: TaskSubmission | None = Field(description="Latest related submission when available.")


class TableLocalTaskStatus(ContractModel):
    """Local task status projection for one detected table."""

    metadata: StageTaskStatus = Field(description="Column metadata task status for this table.")
    anomaly_detection: StageTaskStatus = Field(description="Anomaly detection task status for this table.")
    qc_execution: StageTaskStatus = Field(description="QC execution task status for this table.")


class SheetLocalTaskStatus(ContractModel):
    """Local task status projection for one workbook sheet."""

    table_detection: StageTaskStatus = Field(description="Table detection status for this sheet.")
    sheet_normalization: StageTaskStatus = Field(description="Sheet normalization status for this sheet.")
    metadata_rollup: StageTaskStatus = Field(description="Rollup of metadata task status across tables in this sheet.")
    anomaly_detection_rollup: StageTaskStatus = Field(description="Rollup of anomaly task status across tables in this sheet.")
    qc_execution_rollup: StageTaskStatus = Field(description="Rollup of QC execution task status across tables in this sheet.")


class ProjectStageSummary(ContractModel):
    """Project-level stage summary for the batch analysis workspace."""

    stage: StageAnalysisStage = Field(description="Stage represented by this summary.")
    status: ResourceTaskStatus = Field(description="Project-level stage status.")
    run_policy: StageRunPolicy | None = Field(default=None, description="Latest or requested run policy when applicable.")
    total_count: StrictInt = Field(description="Total target count.")
    completed_count: StrictInt = Field(description="Completed target count.")
    running_count: StrictInt = Field(description="Running target count.")
    failed_count: StrictInt = Field(description="Failed target count.")
    blocked_count: StrictInt = Field(description="Blocked target count.")
    skipped_count: StrictInt = Field(description="Skipped target count.")
    blocking_reasons: list[StageBlockingReason] = Field(description="Project-level blocking reasons.")
    latest_batch: TaskSubmissionBatch | None = Field(description="Latest related batch when available.")


class ProjectStageAnalysis(ContractModel):
    """Project-level status for all batch analysis stages."""

    project_id: StrictStr = Field(description="Project id.")
    stages: list[ProjectStageSummary] = Field(description="Stage summaries.")


class CreateProjectStageAnalysisRunRequest(ContractModel):
    """Request to start or reuse project-level stage analysis tasks."""

    stage: StageAnalysisStage = Field(description="Stage to run.")
    run_policy: StageRunPolicy = Field(default="skip_existing", description="Run policy.")
    qc_run_id: StrictStr | None = Field(default=None, description="Optional QC run scope.")
    targets: list[StageTaskTarget] = Field(
        default_factory=list,
        description=(
            "Optional explicit project-level targets. When present, the backend runs exactly these sheet/table targets; "
            "selecting a target that already has a result means rerun that target."
        ),
    )


class ProjectStageAnalysisRunResult(ContractModel):
    """Result of starting or reusing project-level stage analysis tasks."""

    project_id: StrictStr = Field(description="Project id.")
    stage: StageAnalysisStage = Field(description="Requested stage.")
    run_policy: StageRunPolicy = Field(description="Applied run policy.")
    status: ResourceTaskStatus = Field(description="Resulting stage status.")
    qc_run_id: StrictStr | None = Field(default=None, description="QC run scope when applicable.")
    created_count: StrictInt = Field(description="Created task count.")
    reused_count: StrictInt = Field(description="Reused active task count.")
    skipped_count: StrictInt = Field(description="Skipped completed target count.")
    blocked_count: StrictInt = Field(description="Blocked target count.")
    blocking_reasons: list[StageBlockingReason] = Field(description="Blocking reasons.")
    batch: TaskSubmissionBatch | None = Field(description="Created or reused batch when applicable.")
    submissions: list[TaskSubmission] = Field(description="Created or reused submissions.")


class CreateLocalStageTaskRequest(ContractModel):
    """Request to start one local sheet/table stage task from the result page."""

    stage: StageAnalysisStage = Field(description="Stage to run.")
    run_policy: StageRunPolicy = Field(default="skip_existing", description="Run policy.")
    target: StageTaskTarget = Field(description="Target sheet or table.")


class LocalStageTaskRunResult(ContractModel):
    """Result of starting or reusing one local sheet/table stage task."""

    project_id: StrictStr = Field(description="Project id.")
    stage: StageAnalysisStage = Field(description="Requested stage.")
    run_policy: StageRunPolicy = Field(description="Applied run policy.")
    status: ResourceTaskStatus = Field(description="Resulting local task status.")
    target: StageTaskTarget = Field(description="Target sheet or table.")
    submission: TaskSubmission | None = Field(description="Created or reused submission when available.")
    blocking_reasons: list[StageBlockingReason] = Field(description="Blocking reasons.")


class TaskCenterQueueJob(ContractModel):
    """Lightweight shared task_queue JobRun summary for task-center rows."""

    id: StrictStr = Field(description="Job id.")
    queue_name: StrictStr = Field(description="Queue name.")
    task_type: StrictStr = Field(description="Task type.")
    status: TaskQueueJobStatus = Field(description="Runtime job status.")
    priority: StrictInt = Field(description="Queue priority.")
    error_code: StrictStr | None = Field(description="Error code when failed.")
    error_message: StrictStr | None = Field(description="Error message when failed.")
    attempt_count: StrictInt = Field(description="Attempt count.")
    max_attempts: StrictInt = Field(description="Maximum attempts.")
    created_at: datetime = Field(description="Creation timestamp.")
    updated_at: datetime = Field(description="Update timestamp.")
    started_at: datetime | None = Field(description="Start timestamp.")
    finished_at: datetime | None = Field(description="Finish timestamp.")


class UserTaskCenterItem(ContractModel):
    """One concrete task-center detail row backed by one shared task_queue JobRun."""

    task_center_item_id: StrictStr = Field(description="Stable UI row id for this task-center detail.")
    item_kind: Literal["job"] = Field(description="Task-center row kind.")
    project_id: StrictStr = Field(description="Project id.")
    project_name: StrictStr = Field(description="Project name.")
    stage: StageAnalysisStage | None = Field(default=None, description="Mapped stage when known.")
    status: TaskQueueJobStatus = Field(description="Runtime job status.")
    task_type: StrictStr = Field(description="Task type.")
    target: StageTaskTarget | None = Field(description="Concrete target for this task row.")
    target_level: StageTaskTargetLevel | None = Field(description="Target scope level when available.")
    qc_run_id: StrictStr | None = Field(description="QC run id when available.")
    workbook_file_name: StrictStr | None = Field(description="Workbook/data file name when available.")
    sheet_name: StrictStr | None = Field(description="Sheet name when available.")
    table_id: StrictStr | None = Field(description="Table id when available.")
    submission_id: StrictStr | None = Field(description="Submission id when this row is backed by a submission.")
    task_job_id: StrictStr | None = Field(description="Runtime task job id when available.")
    batch_submission_id: StrictStr | None = Field(description="Parent batch submission id when this row belongs to a batch.")
    queue_job: TaskCenterQueueJob = Field(description="Lightweight shared task_queue JobRun summary.")
    created_at: datetime = Field(description="Task or batch creation timestamp.")
    updated_at: datetime = Field(description="Task or batch update timestamp.")


class UserTaskCenter(ContractModel):
    """Current user's cross-project task center summary."""

    total_count: StrictInt = Field(description="Total task rows.")
    queued_count: StrictInt = Field(description="Queued task count.")
    running_count: StrictInt = Field(description="Running task count.")
    completed_count: StrictInt = Field(description="Completed task count.")
    failed_count: StrictInt = Field(description="Failed or cancelled task count.")
    items: list[UserTaskCenterItem] = Field(description="Task center rows.")


class ManuscriptEvidenceReference(ContractModel):
    """Evidence reference extracted from uploaded manuscripts."""

    evidence_id: StrictStr = Field(description="Evidence id.")
    file_name: StrictStr = Field(description="Manuscript file name.")
    location: StrictStr | None = Field(default=None, description="Page, section, figure, table, or paragraph location.")
    evidence_type: EvidenceType = Field(description="Manuscript evidence type.")
    snippet: StrictStr | None = Field(default=None, description="Short evidence snippet.")
    reason: StrictStr = Field(description="Why this evidence supports the inference.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")


class TablePurpose(ContractModel):
    """Semantic purpose of one extracted table."""

    summary: StrictStr = Field(description="Summary of what the table measures.")
    experiment_name: StrictStr | None = Field(default=None, description="Experiment name when inferred.")
    related_figure_or_table: StrictStr | None = Field(default=None, description="Related figure or table reference.")
    measured_outcome: StrictStr | None = Field(default=None, description="Measured outcome.")
    biological_context: StrictStr | None = Field(default=None, description="Biological context.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")


class RowSemanticMetadata(ContractModel):
    """Semantic metadata for one table row."""

    row_index: StrictInt = Field(description="1-based sheet row index.")
    row_label: StrictStr | None = Field(default=None, description="Visible row label.")
    semantic_role: TableSemanticRole = Field(description="Semantic role of the row.")
    description: StrictStr = Field(description="Chinese or source-derived row meaning.")
    condition_name: StrictStr | None = Field(default=None, description="Condition represented by this row.")
    replicate_group_id: StrictStr | None = Field(default=None, description="Replicate group id when applicable.")
    is_measurement_row: StrictBool = Field(description="Whether the row contains experimental measurements.")
    is_experimental_design_axis: StrictBool = Field(description="Whether the row encodes design labels or gradients.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")
    reason: StrictStr = Field(description="Reason for the row semantic inference.")


class ColumnSemanticMetadata(ContractModel):
    """Semantic metadata for one table column."""

    column_index: StrictInt = Field(description="1-based sheet column index.")
    column_letter: StrictStr = Field(description="Spreadsheet column letter.")
    column_name: StrictStr = Field(description="Displayed or inferred column name.")
    semantic_role: TableSemanticRole = Field(description="Semantic role of the column.")
    description: StrictStr = Field(description="Chinese or source-derived column meaning.")
    variable_name: StrictStr | None = Field(default=None, description="Variable name represented by this column.")
    unit: StrictStr | None = Field(default=None, description="Measurement unit.")
    condition_name: StrictStr | None = Field(default=None, description="Condition represented by this column.")
    replicate_group_id: StrictStr | None = Field(default=None, description="Replicate group id when applicable.")
    is_measurement_column: StrictBool = Field(description="Whether the column contains experimental measurements.")
    is_experimental_design_axis: StrictBool = Field(description="Whether the column encodes design labels or gradients.")
    is_derived: StrictBool = Field(description="Whether the column is derived from other values.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")
    reason: StrictStr = Field(description="Reason for the column semantic inference.")


class HeaderPathItem(ContractModel):
    """One level value in a column or variable header path."""

    level_index: StrictInt = Field(description="1-based header level index from top to bottom.")
    level_name: StrictStr | None = Field(default=None, description="Human-readable level name when inferred.")
    raw_value: StrictStr = Field(description="Header value as shown or normalized from this level.")
    value_meaning: StrictStr | None = Field(default=None, description="Meaning of this value within the header level.")


class HeaderLevelValueMetadata(ContractModel):
    """Meaning of one distinct value inside a header level."""

    value_id: StrictStr = Field(description="Stable value id scoped to the table and header level.")
    value: StrictStr = Field(description="Header value shown at this level.")
    value_meaning: StrictStr = Field(description="End-user explanation of what this header value means.")
    column_indices: list[StrictInt] = Field(description="1-based sheet columns covered by this value.")
    column_letters: list[StrictStr] = Field(description="Spreadsheet column letters covered by this value.")
    variable_ids: list[StrictStr] = Field(description="Variables whose header path includes this value.")
    cell_range: StrictStr | None = Field(default=None, description="Header cell range for this value when available.")


class TableHeaderLevelMetadata(ContractModel):
    """Metadata for one header level in a standardized table."""

    level_index: StrictInt = Field(description="1-based header level index from top to bottom.")
    level_name: StrictStr = Field(description="Human-readable name of this header level.")
    level_meaning: StrictStr = Field(description="Overall meaning of this level.")
    values: list[HeaderLevelValueMetadata] = Field(description="Value meaning table for this header level.")


class MeasurementRegion(ContractModel):
    """Rectangular region that contains experimental measurements."""

    region_id: StrictStr = Field(description="Measurement region id.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table region id.")
    cell_range: StrictStr = Field(description="Spreadsheet cell range.")
    region_kind: MeasurementRegionKind = Field(description="Measurement region kind.")
    description: StrictStr = Field(description="What this measurement region represents.")
    row_indices: list[StrictInt] = Field(description="1-based row indices in this region.")
    column_indices: list[StrictInt] = Field(description="1-based column indices in this region.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")
    reason: StrictStr = Field(description="Reason for the measurement region inference.")


class ExperimentalAxis(ContractModel):
    """Row, column, or mixed axis that explains table structure."""

    axis_id: StrictStr = Field(description="Experimental axis id.")
    axis_kind: ExperimentalAxisKind = Field(description="Axis orientation.")
    semantic_role: TableSemanticRole = Field(description="Semantic role encoded by this axis.")
    label: StrictStr = Field(description="Axis label.")
    region_id: StrictStr | None = Field(default=None, description="Related measurement region id.")
    row_indices: list[StrictInt] = Field(description="Rows covered by this axis.")
    column_indices: list[StrictInt] = Field(description="Columns covered by this axis.")
    values: list[StrictStr] = Field(description="Axis values in display order.")
    design_gradient: StrictBool = Field(description="Whether ordered values are explained by experimental design.")
    description: StrictStr = Field(description="Axis meaning.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")
    reason: StrictStr = Field(description="Reason for the axis inference.")


class DesignGradient(ContractModel):
    """Design gradient that should explain ordered table differences."""

    gradient_id: StrictStr = Field(description="Design gradient id.")
    axis_id: StrictStr = Field(description="Source experimental axis id.")
    gradient_kind: DesignGradientKind = Field(description="Design gradient kind.")
    label: StrictStr = Field(description="Gradient label.")
    ordered_values: list[StrictStr] = Field(description="Gradient values in intended order.")
    row_indices: list[StrictInt] = Field(description="Rows belonging to this gradient.")
    column_indices: list[StrictInt] = Field(description="Columns belonging to this gradient.")
    reason: StrictStr = Field(description="Reason this is treated as experimental design.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")


class MeasurementComparisonGroup(ContractModel):
    """Comparable measurement group used to configure fabrication detectors."""

    comparison_group_id: StrictStr = Field(description="Comparison group id.")
    label: StrictStr = Field(description="Human-readable comparison group label.")
    measurement_region_ids: list[StrictStr] = Field(description="Measurement regions included in this group.")
    row_indices: list[StrictInt] = Field(description="Rows included in this group.")
    column_indices: list[StrictInt] = Field(description="Columns included in this group.")
    expected_difference_source: ExpectedDifferenceSource = Field(description="Why values in this group may differ.")
    detector_applicability_notes: StrictStr = Field(description="Notes for detector configuration.")
    reason: StrictStr = Field(description="Reason for grouping these measurements.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")


class TableVariableMetadata(ContractModel):
    """Variable-level metadata for one detected table variable."""

    variable_id: StrictStr = Field(description="Variable id.")
    source_axis: VariableAxisKind = Field(description="Axis where this variable is represented.")
    row_indices: list[StrictInt] = Field(description="Rows that represent or contain this variable.")
    column_indices: list[StrictInt] = Field(description="Columns that represent or contain this variable.")
    cell_range: StrictStr | None = Field(default=None, description="Optional spreadsheet range for this variable.")
    sample_ids: list[StrictStr] = Field(default_factory=list, description="Optional sample identifiers aligned with this variable's numeric values.")
    header_path: list[HeaderPathItem] = Field(default_factory=list, description="Header level values that identify this variable from top to bottom.")
    variable_name: StrictStr = Field(description="Variable name.")
    variable_meaning: StrictStr = Field(description="Variable meaning.")
    measurement_type: StrictStr | None = Field(default=None, description="Experimental measurement type used for QC grouping.")
    semantic_role: TableSemanticRole = Field(description="Semantic role of this variable.")
    data_type: ColumnDataType = Field(description="Variable data type.")
    domain: StrictStr = Field(description="Variable domain.")
    unit: StrictStr | None = Field(default=None, description="Variable unit.")
    is_measurement_variable: StrictBool = Field(description="Whether this variable is an experimental measurement variable.")
    exclusion_reason: VariableExclusionReason | None = Field(default=None, description="Reason this variable is excluded from measurement QC.")
    exclusion_note: StrictStr | None = Field(default=None, description="Human-readable exclusion note.")


class QcDataRegion(ContractModel):
    """Numeric data region used by current measurement QC rules."""

    data_region_id: StrictStr = Field(description="Data region id.")
    cell_range: StrictStr = Field(description="User-visible spreadsheet range, for example C2:C4.")
    variable_ids: list[StrictStr] = Field(description="Variables covered by this data region.")
    value_source_kind: QcValueSourceKind = Field(description="Source used for raw text or numeric values.")
    numeric_cell_count: StrictInt = Field(description="Number of usable numeric cells.")
    skipped_value_count: StrictInt = Field(description="Number of values skipped from this region.")
    skipped_reasons: list[StrictStr] = Field(description="Reasons values were skipped.")


class QcVariableGroup(ContractModel):
    """Variables with the same measurement_type."""

    variable_group_id: StrictStr = Field(description="Variable group id.")
    measurement_type: StrictStr = Field(description="Shared measurement type.")
    variable_ids: list[StrictStr] = Field(description="Variables in this group.")
    data_region_ids: list[StrictStr] = Field(description="Data regions for this group.")
    sample_condition_summary: StrictStr = Field(description="Summary of sample or condition differences.")
    alignment_strategy: SampleAlignmentStrategy = Field(description="Sample alignment strategy.")
    alignment_key: StrictStr | None = Field(default=None, description="Alignment key when available.")
    reason: StrictStr = Field(description="Reason this group is valid for QC.")


class QcPlanExcludedVariable(ContractModel):
    """Variable recognized but excluded from current measurement QC."""

    variable_id: StrictStr = Field(description="Variable id.")
    reason: VariableExclusionReason = Field(description="Exclusion reason.")
    message: StrictStr = Field(description="Human-readable exclusion message.")


class QcRuleConfig(ContractModel):
    """Current product QC rule configuration for one variable group."""

    rule_config_id: StrictStr = Field(description="Rule config id.")
    rule_type: QcRuleType = Field(description="Current QC rule type.")
    status: QcPlanRuleStatus = Field(description="Plan-time rule status.")
    variable_group_id: StrictStr = Field(description="Variable group id.")
    variable_ids: list[StrictStr] = Field(description="Variables this rule uses.")
    data_region_ids: list[StrictStr] = Field(description="Data regions this rule uses.")
    value_source_kind: QcValueSourceKind = Field(description="Value source for this rule.")
    alignment_strategy: SampleAlignmentStrategy = Field(description="Sample alignment strategy for this rule.")
    min_sample_count: StrictInt = Field(description="Minimum sample count required to enable this rule.")
    available_sample_count: StrictInt = Field(description="Available comparable sample count.")
    skipped_value_count: StrictInt = Field(description="Skipped value count.")
    parameters: dict[str, Any] = Field(description="Rule-specific parameters.")
    reason: StrictStr = Field(description="Reason this rule is enabled, disabled, or requires review.")
    disabled_reason: StrictStr | None = Field(default=None, description="Reason this rule is disabled.")


class DetectorApplicability(ContractModel):
    """Legacy detector configuration derived from table semantics."""

    detector_config_id: StrictStr = Field(description="Detector configuration id.")
    detector_type: DetectorType = Field(description="Detector type.")
    status: DetectorApplicabilityStatus = Field(description="Whether the detector should run.")
    target_region_ids: list[StrictStr] = Field(description="Measurement regions targeted by this detector.")
    compare_axis_ids: list[StrictStr] = Field(description="Experimental axes compared by this detector.")
    comparison_group_ids: list[StrictStr] = Field(description="Measurement comparison groups covered by this detector.")
    row_indices: list[StrictInt] = Field(description="Rows checked by this detector.")
    column_indices: list[StrictInt] = Field(description="Columns checked by this detector.")
    parameters: dict[str, Any] = Field(description="Detector-specific configuration JSON.")
    reason: StrictStr = Field(description="Reason this detector is enabled or disabled.")
    skip_reason: StrictStr | None = Field(default=None, description="Reason the detector is skipped.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")


class TableQcPlan(ContractModel):
    """QC plan for one semantic table profile."""

    plan_id: StrictStr = Field(description="QC plan id.")
    semantic_profile_id: StrictStr = Field(description="Source semantic profile id.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table region id.")
    plan_status: TableQcPlanStatus = Field(default="executable", description="Plan-level QC status.")
    empty_reason: TableQcPlanEmptyReason | None = Field(default=None, description="Reason a completed plan has no executable measurement groups.")
    measurement_region_ids: list[StrictStr] = Field(description="Legacy semantic measurement regions covered by the plan.")
    data_regions: list[QcDataRegion] = Field(description="Current QC data regions.")
    variable_groups: list[QcVariableGroup] = Field(description="Current QC variable groups.")
    rule_configs: list[QcRuleConfig] = Field(description="Current QC rule configurations.")
    excluded_variables: list[QcPlanExcludedVariable] = Field(description="Variables excluded from current QC.")
    detector_configs: list[DetectorApplicability] = Field(default_factory=list, description="Legacy detector configuration JSON objects.")
    summary: StrictStr = Field(description="Plan summary.")
    created_by_semantic_run_id: StrictStr | None = Field(default=None, description="Semantic enrichment run id.")


class TableSemanticProfile(ContractModel):
    """Structured semantic extraction result for one data table."""

    semantic_profile_id: StrictStr = Field(description="Semantic profile id.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table region id.")
    table_range: StrictStr = Field(description="Spreadsheet cell range covered by the table.")
    table_purpose: TablePurpose = Field(description="What this table does.")
    sample_condition_summary: StrictStr = Field(description="Summary of sample or condition differences.")
    measurement_layout: TableMeasurementLayout = Field(
        default="variables_in_last_header_level",
        description="Whether the table is one unified measurement split by header condition levels or a variable-list table where the final header level names actual measured variables/readouts.",
    )
    table_measurement_type: StrictStr | None = Field(default=None, description="Unified measurement type for Type I tables.")
    table_measurement_data_type: ColumnDataType | None = Field(default=None, description="Unified measurement value data type for Type I tables.")
    rows: list[RowSemanticMetadata] = Field(description="Row-level semantic metadata.")
    columns: list[ColumnSemanticMetadata] = Field(description="Column-level semantic metadata.")
    header_levels: list[TableHeaderLevelMetadata] = Field(default_factory=list, description="Header-level metadata for this table.")
    variables: list[TableVariableMetadata] = Field(description="Variable-level metadata.")
    measurement_regions: list[MeasurementRegion] = Field(description="Experimental measurement regions.")
    experimental_axes: list[ExperimentalAxis] = Field(description="Experimental design and comparison axes.")
    design_gradients: list[DesignGradient] = Field(description="Design gradients explained by experiment design.")
    measurement_comparison_groups: list[MeasurementComparisonGroup] = Field(description="Comparable measurement groups.")
    limitations: list[StrictStr] = Field(description="Table-level uncertainties and caveats for detector interpretation.")
    notes: list[StrictStr] = Field(description="Additional semantic notes.")
    confidence: StrictFloat = Field(ge=0, le=1, description="AI confidence score from 0 to 1.")
    created_by_semantic_run_id: StrictStr | None = Field(default=None, description="Semantic enrichment run id.")


class SemanticFindingContext(ContractModel):
    """Semantic context attached to one QC finding."""

    semantic_profile_id: StrictStr = Field(description="Semantic profile id.")
    detector_config_id: StrictStr | None = Field(default=None, description="Legacy detector configuration id.")
    rule_config_id: StrictStr | None = Field(default=None, description="Current QC rule configuration id.")
    rule_type: QcRuleType | None = Field(default=None, description="Current QC rule type.")
    variable_group_id: StrictStr | None = Field(default=None, description="Variable group id.")
    measurement_type: StrictStr | None = Field(default=None, description="Measurement type.")
    variable_ids: list[StrictStr] = Field(description="Variables involved in the finding.")
    measurement_region_ids: list[StrictStr] = Field(description="Measurement regions used by the finding.")
    data_region_ids: list[StrictStr] = Field(description="Data regions used by the finding.")
    alignment_strategy: SampleAlignmentStrategy | None = Field(default=None, description="Sample alignment strategy.")
    applicable_assumption: StrictStr = Field(description="Detector assumption justified by table semantics.")
    semantic_reason: StrictStr = Field(description="Reason the detector is applicable.")
    manuscript_evidence: list[ManuscriptEvidenceReference] = Field(description="Supporting manuscript evidence.")
    alternative_explanations: list[StrictStr] = Field(description="Alternative explanations that reduce certainty.")


class SemanticQcArtifacts(ContractModel):
    """Structured semantic artifacts generated for a QC run."""

    qc_run_id: StrictStr = Field(description="QC run id.")
    table_semantic_profiles: list[TableSemanticProfile] = Field(description="Semantic profiles by table.")
    table_qc_plans: list[TableQcPlan] = Field(description="Detector plans by table.")
    latest_semantic_run_id: StrictStr | None = Field(default=None, description="Latest semantic enrichment run id.")


class CreateSemanticQcEnrichmentRequest(ContractModel):
    """Request to submit semantic QC enrichment for one table."""

    target_workbook_file_name: StrictStr = Field(description="Target workbook file name.")
    target_sheet_name: StrictStr = Field(description="Target sheet name.")
    target_table_id: StrictStr = Field(description="Target table id.")


class CreateColumnMetadataEnrichmentRequest(ContractModel):
    """Request to submit column metadata enrichment for one table."""

    target_workbook_file_name: StrictStr = Field(description="Target workbook file name.")
    target_sheet_name: StrictStr = Field(description="Target sheet name.")
    target_table_id: StrictStr = Field(description="Target table id.")


class EvidenceCell(ContractModel):
    """Numeric evidence cell referenced by a QC finding."""

    sheet_name: StrictStr = Field(description="Sheet name.")
    cell: StrictStr = Field(description="Cell coordinate.")
    value: StrictFloat = Field(description="Numeric cell value.")
    raw_value: StrictStr | None = Field(default=None, description="Original cell value.")
    row_label: StrictStr | None = Field(default=None, description="Nearby row label.")
    column_label: StrictStr | None = Field(default=None, description="Nearby column label.")


class QcFinding(ContractModel):
    """QC finding returned to clients."""

    finding_id: StrictStr = Field(description="Finding id.")
    finding_type: StrictStr = Field(description="Detector finding type.")
    rule_type: QcRuleType | None = Field(default=None, description="Current product QC rule type when applicable.")
    measurement_type: StrictStr | None = Field(default=None, description="Measurement type when applicable.")
    variable_group_id: StrictStr | None = Field(default=None, description="Variable group id when applicable.")
    variable_ids: list[StrictStr] = Field(default_factory=list, description="Variables involved in the finding.")
    available_sample_count: StrictInt | None = Field(default=None, description="Available comparable sample count.")
    skipped_value_count: StrictInt | None = Field(default=None, description="Skipped value count.")
    alignment_strategy: SampleAlignmentStrategy | None = Field(default=None, description="Sample alignment strategy.")
    risk_level: RiskLevel = Field(description="Risk level.")
    title: StrictStr = Field(description="Finding title.")
    summary: StrictStr = Field(description="Finding summary.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    cell_range: StrictStr | None = Field(default=None, description="Cell range related to the finding.")
    evidence_cells: list[EvidenceCell] = Field(description="Evidence cells.")
    metrics: dict[str, Any] = Field(description="Detector metrics.")
    recommendation: StrictStr = Field(description="Recommended follow-up action.")
    detector_version: StrictStr = Field(description="Detector implementation version.")
    semantic_context: SemanticFindingContext | None = Field(default=None, description="Semantic detector context.")


class QcDiagnostic(ContractModel):
    """Objective per-rule QC calculation result with Plotly chart data."""

    diagnostic_id: StrictStr = Field(description="Diagnostic id.")
    rule_config_id: StrictStr = Field(description="Source rule config id.")
    rule_type: QcRuleType = Field(description="QC rule type.")
    measurement_type: StrictStr | None = Field(default=None, description="Measurement type when applicable.")
    variable_group_id: StrictStr | None = Field(default=None, description="Variable group id when applicable.")
    variable_ids: list[StrictStr] = Field(description="Variables covered by this diagnostic.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table region id.")
    cell_range: StrictStr | None = Field(default=None, description="Cell range covered by this diagnostic.")
    status: QcRuleApplicabilityStatus = Field(description="Rule applicability status.")
    metrics: dict[str, Any] = Field(description="Objective calculation metrics.")
    plotly: dict[str, Any] = Field(description="Plotly-compatible chart spec.")
    summary: StrictStr = Field(description="Human-readable diagnostic summary.")


class QcRuleSummaryItem(ContractModel):
    """Project-level count bucket for one QC rule type."""

    rule_type: QcRuleType = Field(description="QC rule type.")
    not_applicable_count: StrictInt = Field(ge=0, description="Number of evaluations where the rule was not applicable.")
    calculation_unavailable_count: StrictInt = Field(
        ge=0,
        description="Number of applicable rule evaluations that could not be computed from available numeric inputs.",
    )
    passed_count: StrictInt = Field(ge=0, description="Number of evaluations where the rule executed without findings.")
    suspicious_count: StrictInt = Field(ge=0, description="Number of evaluations where the rule emitted a finding.")


class SuspiciousQcRuleItem(ContractModel):
    """Suspicious rule evaluation row with enough context to navigate to table detail."""

    diagnostic_id: StrictStr = Field(description="Diagnostic id.")
    finding_id: StrictStr | None = Field(default=None, description="Finding id when a concrete finding is available.")
    rule_type: QcRuleType = Field(description="QC rule type.")
    file_name: StrictStr = Field(description="Display file name.")
    workbook_file_id: StrictStr | None = Field(default=None, description="Stable workbook project file id when available.")
    workbook_file_name: StrictStr = Field(description="Workbook file name.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    table_id: StrictStr = Field(description="Table id.")
    cell_range: StrictStr | None = Field(default=None, description="Finding or diagnostic cell range.")
    title: StrictStr = Field(description="Display title.")
    summary: StrictStr = Field(description="Display summary.")
    risk_level: RiskLevel = Field(description="Finding risk level.")


class SuspiciousQcRulePage(ContractModel):
    """Pagination metadata for suspicious rule rows."""

    page: StrictInt = Field(ge=1, description="1-based current page number.")
    page_size: StrictInt = Field(ge=1, description="Current page size.")
    total_count: StrictInt = Field(ge=0, description="Total suspicious rule row count.")
    total_pages: StrictInt = Field(ge=0, description="Total page count.")
    has_next: StrictBool = Field(description="Whether a next page exists.")
    has_previous: StrictBool = Field(description="Whether a previous page exists.")


class SuspiciousQcRuleFilterOptions(ContractModel):
    """Available filter options for suspicious rule rows."""

    rule_types: list[QcRuleType] = Field(description="Rule types present in all suspicious rule rows before current filters are applied.")
    risk_levels: list[RiskLevel] = Field(description="Risk levels present in all suspicious rule rows before current filters are applied.")


class QcRunRuleSummary(ContractModel):
    """Project-level QC rule summary for one selected QC run."""

    project_id: StrictStr = Field(description="Project id.")
    run_id: StrictStr = Field(description="QC run id.")
    rules: list[QcRuleSummaryItem] = Field(description="Rule count buckets.")
    suspicious_rules: list[SuspiciousQcRuleItem] = Field(description="Current page of suspicious rule rows.")
    suspicious_rules_page: SuspiciousQcRulePage = Field(description="Pagination metadata for suspicious rule rows.")
    suspicious_rule_filter_options: SuspiciousQcRuleFilterOptions = Field(description="Available filter options for suspicious rule rows.")


class SheetPreviewCell(ContractModel):
    """One cell in a sheet preview."""

    row: StrictInt = Field(description="1-based row index.")
    column: StrictInt = Field(description="1-based column index.")
    coordinate: StrictStr = Field(description="Cell coordinate.")
    value: StrictStr | None = Field(default=None, description="Rendered preview value.")
    kind: CellKind = Field(description="Preview cell kind.")


class SheetMergedRange(ContractModel):
    """Merged cell range in a sheet preview."""

    cell_range: StrictStr = Field(description="Merged cell range.")
    start_row: StrictInt = Field(description="Start row.")
    start_column: StrictInt = Field(description="Start column.")
    end_row: StrictInt = Field(description="End row.")
    end_column: StrictInt = Field(description="End column.")


class SheetPreview(ContractModel):
    """Sheet preview returned for workbook summaries."""

    rows: StrictInt = Field(description="Preview row count.")
    columns: StrictInt = Field(description="Preview column count.")
    cells: list[SheetPreviewCell] = Field(description="Preview cells.")
    merged_ranges: list[SheetMergedRange] = Field(description="Merged ranges.")


class TableColumnMetadata(ContractModel):
    """Column metadata for standardized tables; also the user-facing variable metadata."""

    column_index: StrictInt = Field(description="1-based Excel column index.")
    column_letter: StrictStr = Field(description="Spreadsheet column letter.")
    column_name: StrictStr = Field(description="Displayed or inferred column name.")
    raw_data_type: StrictStr = Field(description="Raw data type inferred from the source file.")
    data_type: ColumnDataType = Field(description="rich-datatable compatible column type.")
    domain: StrictStr = Field(description="Detected value domain summary.")
    not_nullable: StrictBool = Field(description="Whether the column appears non-nullable.")
    unique: StrictBool = Field(description="Whether the column appears unique.")
    is_id: StrictBool = Field(description="Whether the column appears to identify rows.")
    categorical: StrictBool = Field(description="Whether the column appears categorical.")
    variable_id: StrictStr | None = Field(default=None, description="Stable variable id represented by this column.")
    header_path: list[HeaderPathItem] = Field(description="Header level values for this column from top to bottom.")
    variable_name: StrictStr | None = Field(default=None, description="Variable name represented by this column.")
    variable_meaning: StrictStr | None = Field(default=None, description="Experimental or statistical meaning of this column variable.")
    measurement_type: StrictStr | None = Field(default=None, description="Experimental measurement type used for QC grouping.")
    unit: StrictStr | None = Field(default=None, description="Variable unit.")
    is_measurement_variable: StrictBool | None = Field(default=None, description="Whether this column is an experimental measurement variable.")
    variable_cell_range: StrictStr | None = Field(default=None, description="Data-cell range for this column variable.")
    sample_ids: list[StrictStr] = Field(default_factory=list, description="Optional sample ids aligned with this column variable's values.")
    exclusion_reason: VariableExclusionReason | None = Field(default=None, description="Reason this variable is excluded from QC.")
    exclusion_note: StrictStr | None = Field(default=None, description="Human-readable variable exclusion note.")
    ai_metadata_status: ColumnMetadataAiStatus = Field(description="AI metadata enrichment status.")
    ai_column_meaning_zh: StrictStr | None = Field(default=None, description="AI-inferred Chinese column meaning.")
    ai_inferred_column_type: ColumnDataType | None = Field(default=None, description="AI-inferred rich-datatable column type.")
    ai_semantic_role: TableSemanticRole | None = Field(default=None, description="AI-inferred semantic role.")
    ai_trace_artifact_ref: StrictStr | None = Field(default=None, description="Opaque AI trace artifact reference.")
    ai_inference_confidence_score: StrictFloat | None = Field(default=None, ge=0, le=1, description="AI confidence score.")


class SheetTableRegion(ContractModel):
    """Detected table region in a workbook sheet."""

    table_id: StrictStr = Field(description="Table region id.")
    label: StrictStr = Field(description="Display label.")
    cell_range: StrictStr = Field(description="Cell range.")
    numeric_cell_count: StrictInt = Field(description="Numeric cell count.")
    row_count: StrictInt = Field(description="Table row count.")
    column_count: StrictInt = Field(description="Table column count.")
    header_row_count: StrictInt = Field(description="Header row count.")
    sample_index_column_count: StrictInt = Field(description="Leading sample index column count.")
    columns_metadata: list[TableColumnMetadata] = Field(description="Column metadata.")
    header_levels: list[TableHeaderLevelMetadata] = Field(description="Header-level metadata for this table region.")
    local_task_status: TableLocalTaskStatus | None = Field(default=None, description="Local table task status.")


class QcRunResourceRef(ContractModel):
    """Stable locator for a workbook, sheet, or table resource inside one QC run."""

    resource_type: QcRunResourceType = Field(description="Resource level.")
    run_id: StrictStr = Field(description="QC run id.")
    workbook_file_id: StrictStr | None = Field(default=None, description="Stable project data_file id when available.")
    workbook_file_name: StrictStr = Field(description="Workbook file name fallback.")
    sheet_name: StrictStr | None = Field(default=None, description="Sheet name for sheet and table resources.")
    table_id: StrictStr | None = Field(default=None, description="Sheet-scoped table id for table resources.")


class QcRunTreeTable(ContractModel):
    """Lightweight table node for project resource navigation."""

    resource_ref: QcRunResourceRef = Field(description="Stable table locator.")
    table_id: StrictStr = Field(description="Sheet-scoped table id.")
    label: StrictStr = Field(description="Display label.")
    cell_range: StrictStr = Field(description="Cell range.")
    numeric_cell_count: StrictInt = Field(description="Numeric cell count.")
    row_count: StrictInt = Field(description="Table row count.")
    column_count: StrictInt = Field(description="Table column count.")
    header_row_count: StrictInt = Field(description="Header row count.")
    sample_index_column_count: StrictInt = Field(description="Leading sample index column count.")
    local_task_status: TableLocalTaskStatus | None = Field(default=None, description="Local table task status.")


class QcRunTreeSheet(ContractModel):
    """Lightweight sheet node for project resource navigation."""

    resource_ref: QcRunResourceRef = Field(description="Stable sheet locator.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    visible: StrictBool = Field(description="Whether the sheet is visible.")
    max_row: StrictInt = Field(description="Maximum used row.")
    max_column: StrictInt = Field(description="Maximum used column.")
    numeric_cell_count: StrictInt = Field(description="Numeric cell count.")
    table_count: StrictInt = Field(description="Detected table count.")
    tables: list[QcRunTreeTable] = Field(description="Lightweight table nodes.")
    local_task_status: SheetLocalTaskStatus | None = Field(default=None, description="Local sheet task status.")
    skipped_reason: StrictStr | None = Field(default=None, description="Reason the sheet was skipped.")


class QcRunTreeWorkbook(ContractModel):
    """Lightweight workbook node for project resource navigation."""

    resource_ref: QcRunResourceRef = Field(description="Stable workbook locator.")
    file_id: StrictStr | None = Field(default=None, description="Stable project data_file id for this workbook when available.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_count: StrictInt = Field(description="Sheet count.")
    sheets: list[QcRunTreeSheet] = Field(description="Lightweight sheet nodes.")


class QcRunResourceTree(ContractModel):
    """Lightweight current-run workbook/sheet/table tree."""

    run_id: StrictStr = Field(description="QC run id.")
    project_id: StrictStr = Field(description="Owning project id.")
    status: RunStatus = Field(description="QC run status.")
    created_at: datetime = Field(description="Run creation timestamp.")
    risk_level: RiskLevel = Field(description="Overall risk level.")
    summary: StrictStr = Field(description="Run summary.")
    workbooks: list[QcRunTreeWorkbook] = Field(description="Lightweight workbook nodes.")


class QcRunDetailWorkbook(ContractModel):
    """Workbook locator and summary for a scoped resource detail response."""

    resource_ref: QcRunResourceRef = Field(description="Stable workbook locator.")
    file_id: StrictStr | None = Field(default=None, description="Stable project data_file id for this workbook when available.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_count: StrictInt = Field(description="Sheet count.")


class QcRunDetailSheet(ContractModel):
    """Sheet locator and summary for a scoped table detail response."""

    resource_ref: QcRunResourceRef = Field(description="Stable sheet locator.")
    sheet_name: StrictStr = Field(description="Sheet name.")
    visible: StrictBool = Field(description="Whether the sheet is visible.")
    max_row: StrictInt = Field(description="Maximum used row.")
    max_column: StrictInt = Field(description="Maximum used column.")
    numeric_cell_count: StrictInt = Field(description="Numeric cell count.")
    table_count: StrictInt = Field(description="Detected table count.")
    local_task_status: SheetLocalTaskStatus | None = Field(default=None, description="Local sheet task status.")
    skipped_reason: StrictStr | None = Field(default=None, description="Reason the sheet was skipped.")


class TableScopedSheetPreview(ContractModel):
    """Sheet preview clipped to one table range plus minimal surrounding context."""

    cell_range: StrictStr = Field(description="Preview range represented by this clipped preview.")
    rows: StrictInt = Field(description="Preview row count.")
    columns: StrictInt = Field(description="Preview column count.")
    cells: list[SheetPreviewCell] = Field(description="Preview cells.")
    merged_ranges: list[SheetMergedRange] = Field(description="Merged ranges.")


class QcRunSheetDetail(ContractModel):
    """Detail payload for one selected sheet."""

    run_id: StrictStr = Field(description="QC run id.")
    project_id: StrictStr = Field(description="Owning project id.")
    workbook: QcRunDetailWorkbook = Field(description="Selected workbook summary.")
    sheet: QcRunTreeSheet = Field(description="Selected sheet node.")
    preview: SheetPreview | None = Field(default=None, description="Sheet preview.")


class QcRunTableDetail(ContractModel):
    """Detail payload for one selected table."""

    run_id: StrictStr = Field(description="QC run id.")
    project_id: StrictStr = Field(description="Owning project id.")
    resource_ref: QcRunResourceRef = Field(description="Stable table locator.")
    workbook: QcRunDetailWorkbook = Field(description="Selected workbook summary.")
    sheet: QcRunDetailSheet = Field(description="Selected sheet summary.")
    table: QcRunTreeTable = Field(description="Selected table node.")
    columns_metadata: list[TableColumnMetadata] = Field(description="Column metadata for the selected table, including AI enrichment result fields.")
    header_levels: list[TableHeaderLevelMetadata] = Field(description="Header-level metadata for the selected table.")
    semantic_profile: TableSemanticProfile | None = Field(default=None, description="Scoped table semantic profile.")
    qc_plan: TableQcPlan | None = Field(default=None, description="Scoped QC plan.")
    qc_diagnostics: list[QcDiagnostic] = Field(description="Scoped QC diagnostics.")
    findings: list[QcFinding] = Field(description="Scoped findings.")
    preview: TableScopedSheetPreview | None = Field(default=None, description="Table-scoped sheet preview.")


class SheetSummary(ContractModel):
    """Workbook sheet summary."""

    sheet_name: StrictStr = Field(description="Sheet name.")
    visible: StrictBool = Field(description="Whether the sheet is visible.")
    max_row: StrictInt = Field(description="Maximum used row.")
    max_column: StrictInt = Field(description="Maximum used column.")
    numeric_cell_count: StrictInt = Field(description="Numeric cell count.")
    preview: SheetPreview | None = Field(default=None, description="Optional sheet preview.")
    table_regions: list[SheetTableRegion] = Field(description="Detected table regions.")
    local_task_status: SheetLocalTaskStatus | None = Field(default=None, description="Local sheet task status.")
    skipped_reason: StrictStr | None = Field(default=None, description="Reason the sheet was skipped.")


class WorkbookSummary(ContractModel):
    """Workbook summary returned by a QC run."""

    file_id: StrictStr | None = Field(default=None, description="Stable project data_file id for this workbook when available.")
    file_name: StrictStr = Field(description="Workbook file name.")
    sheet_count: StrictInt = Field(description="Sheet count.")
    sheets: list[SheetSummary] = Field(description="Sheet summaries.")


class ManuscriptTextArtifact(ContractModel):
    """Extracted Markdown text artifact for a PDF manuscript."""

    status: ManuscriptTextStatus = Field(description="Manuscript text extraction status.")
    markdown_url: StrictStr | None = Field(default=None, description="Protected API URL for extracted Markdown content.")
    extracted_at: datetime | None = Field(default=None, description="Latest extraction attempt timestamp.")
    error_code: StrictStr | None = Field(default=None, description="Stable extraction error code.")
    error_message: StrictStr | None = Field(default=None, description="Displayable extraction error or empty-text explanation.")


class UploadedFileSummary(ContractModel):
    """Project file summary exposed through the API."""

    file_id: StrictStr = Field(description="File id.")
    original_name: StrictStr = Field(description="Original uploaded file name.")
    content_type: StrictStr | None = Field(default=None, description="Uploaded content type.")
    size_bytes: StrictInt = Field(description="File size in bytes.")
    sha256: StrictStr = Field(description="SHA-256 checksum.")
    preview_url: StrictStr | None = Field(default=None, description="Protected preview API URL.")
    content_url: StrictStr | None = Field(default=None, description="Protected file content API URL.")
    source_kind: SheetNormalizationSourceKind = Field(default="uploaded", description="uploaded for user files; generated for derived resources.")
    generated_by_task_submission_id: StrictStr | None = Field(default=None, description="Task submission id for generated files.")
    derivation: FileDerivation | None = Field(default=None, description="Structured derivation for generated files.")
    sheet_normalization_result: SheetNormalizationFileSummary | None = Field(default=None, description="Lightweight sheet normalization summary for generated Excel files.")
    manuscript_text: ManuscriptTextArtifact | None = Field(default=None, description="Extracted Markdown text artifact for PDF manuscripts.")


class UnsupportedFileSummary(ContractModel):
    """Project file summary for an uploaded file that is retained but not supported."""

    file_id: StrictStr = Field(description="File id.")
    original_name: StrictStr = Field(description="Original uploaded file name.")
    content_type: StrictStr | None = Field(default=None, description="Uploaded content type.")
    size_bytes: StrictInt = Field(description="File size in bytes.")
    sha256: StrictStr = Field(description="SHA-256 checksum.")
    unsupported_reason_code: StrictStr = Field(description="Stable unsupported-file reason code.")
    unsupported_reason_message: StrictStr = Field(description="Displayable unsupported-file explanation.")
    content_url: StrictStr | None = Field(default=None, description="Protected file content API URL.")


class Project(ContractModel):
    """Project summary/detail response."""

    project_id: StrictStr = Field(description="Project id.")
    project_status: ProjectStatus = Field(description="Project lifecycle status.")
    name: StrictStr = Field(description="Project name.")
    description: StrictStr | None = Field(default=None, description="Project description.")
    created_at: datetime = Field(description="Project creation timestamp.")
    updated_at: datetime = Field(description="Project update timestamp.")
    run_count: StrictInt = Field(description="QC run count.")
    latest_run_id: StrictStr | None = Field(default=None, description="Latest QC run id.")
    latest_risk_level: RiskLevel | None = Field(default=None, description="Latest risk level.")
    manuscripts: list[UploadedFileSummary] = Field(description="Uploaded manuscript files.")
    data_files: list[UploadedFileSummary] = Field(description="Uploaded data files.")
    unsupported_files: list[UnsupportedFileSummary] = Field(description="Uploaded unsupported files.")


class ProjectListItem(ContractModel):
    """Lightweight project row for the project list page."""

    project_id: StrictStr = Field(description="Project id.")
    project_status: ProjectStatus = Field(description="Project lifecycle status.")
    name: StrictStr = Field(description="Project name.")
    description: StrictStr | None = Field(default=None, description="Project description.")
    created_at: datetime = Field(description="Project creation timestamp.")
    updated_at: datetime = Field(description="Project update timestamp.")
    run_count: StrictInt = Field(description="QC run count.")


class ProjectCreateRequest(ContractModel):
    """Project create request metadata."""

    name: StrictStr = Field(description="Project name.")
    description: StrictStr | None = Field(default=None, description="Project description.")


class ProjectUpdateRequest(ContractModel):
    """Project update request metadata."""

    name: StrictStr = Field(description="Project name.")


class ProjectFilesUploadRequest(ContractModel):
    """Project file upload request metadata for multipart uploads."""

    files: list[StrictStr] = Field(description="Multipart file fields to classify into project resource areas.")


class ProjectFilesUploadResult(ContractModel):
    """Project state after upload plus this upload's classification result."""

    project: Project = Field(description="Updated project.")
    manuscripts: list[UploadedFileSummary] = Field(description="Files classified into manuscripts by this upload.")
    data_files: list[UploadedFileSummary] = Field(description="Files classified into data_files by this upload.")
    unsupported_files: list[UnsupportedFileSummary] = Field(description="Files classified into unsupported_files by this upload.")


class ProjectDeleteResult(ContractModel):
    """Project deletion acceptance/result response."""

    project_id: StrictStr = Field(description="Deleted or deleting project id.")
    project_status: ProjectStatus = Field(description="Project lifecycle status after delete request.")
    deleted_at: datetime | None = Field(default=None, description="Deletion completion timestamp.")
    cancelled_task_job_ids: list[StrictStr] = Field(description="Shared task queue job ids cancelled by deletion.")
    retained_task_job_ids: list[StrictStr] = Field(description="Shared task queue job ids retained after deletion request.")
    message: StrictStr = Field(description="Deletion result message.")


class ProjectFilePreview(ContractModel):
    """Project file preview response."""

    file_id: StrictStr = Field(description="File id.")
    preview_kind: PreviewKind = Field(description="Preview rendering strategy.")
    text: StrictStr | None = Field(default=None, description="Plain-text preview when available.")


class ManuscriptTextContent(ContractModel):
    """Markdown content extracted from a PDF manuscript."""

    file_id: StrictStr = Field(description="File id.")
    artifact: ManuscriptTextArtifact = Field(description="Manuscript text artifact metadata.")
    markdown: StrictStr = Field(description="Extracted Markdown content.")


class ManuscriptTextExtractionResult(ContractModel):
    """Result of a PDF manuscript text extraction request."""

    project_id: StrictStr = Field(description="Project id.")
    file_id: StrictStr = Field(description="File id.")
    file: UploadedFileSummary = Field(description="Updated file summary.")
    artifact: ManuscriptTextArtifact = Field(description="Updated manuscript text artifact.")


class QcRun(ContractModel):
    """QC run response."""

    run_id: StrictStr = Field(description="QC run id.")
    project_id: StrictStr = Field(description="Owning project id.")
    status: RunStatus = Field(description="QC run status.")
    created_at: datetime = Field(description="Run creation timestamp.")
    paper_title: StrictStr | None = Field(default=None, description="Paper title when inferred.")
    files: list[UploadedFileSummary] = Field(description="Files included in the run.")
    workbooks: list[WorkbookSummary] = Field(description="Workbook summaries.")
    findings: list[QcFinding] = Field(description="QC findings.")
    qc_diagnostics: list[QcDiagnostic] = Field(description="Objective QC diagnostics.")
    table_semantic_profiles: list[TableSemanticProfile] | None = Field(default=None, description="Semantic profiles.")
    table_qc_plans: list[TableQcPlan] | None = Field(default=None, description="Detector plans.")
    risk_level: RiskLevel = Field(description="Overall risk level.")
    summary: StrictStr = Field(description="Run summary.")
