from __future__ import annotations from enum import Enum from typing import Any, Literal from pydantic import BaseModel, Field class RubricDimensionScore(BaseModel): dimension_id: str label: str score: int weight: float = 1.0 rationale: str | None = None class StageGradeResult(BaseModel): stage_id: str artifact_version_id: str | None = None score: int passed_threshold: bool rubric_scores: list[RubricDimensionScore] = Field(default_factory=list) issue_ids: list[str] = Field(default_factory=list) recommended_actions: list[str] = Field(default_factory=list) graded_at: str grader: Literal["deterministic", "mock_ai", "human", "system"] = "deterministic" class IssueType(str, Enum): MISSING_REQUIRED_INPUT = "missing_required_input" SOURCE_EXTRACTION_FAILED = "source_extraction_failed" OBJECTIVE_UNCOVERED = "objective_uncovered" OBJECTIVE_WEAKLY_COVERED = "objective_weakly_covered" UNSUPPORTED_CLAIM = "unsupported_claim" TECHNICAL_INACCURACY = "technical_inaccuracy" TEXT_DENSITY_EXCEEDED = "text_density_exceeded" COGNITIVE_LOAD_HIGH = "cognitive_load_high" SPEAKER_NOTES_MISSING = "speaker_notes_missing" LAYOUT_SCHEMA_INVALID = "layout_schema_invalid" LAYOUT_SLOT_VIOLATION = "layout_slot_violation" VISUAL_ASSET_MISSING = "visual_asset_missing" VISUAL_ASSET_PURPOSE_MISSING = "visual_asset_purpose_missing" ALT_TEXT_MISSING = "alt_text_missing" ACCESSIBILITY_CONTRAST_RISK = "accessibility_contrast_risk" COPYRIGHT_OR_LICENSE_RISK = "copyright_or_license_risk" RENDER_SAFETY_VIOLATION = "render_safety_violation" GOOGLE_EXPORT_FAILED = "google_export_failed" HUMAN_APPROVAL_MISSING = "human_approval_missing" SCORE_BELOW_THRESHOLD = "score_below_threshold" STALE_DOWNSTREAM_STAGE = "stale_downstream_stage" APPROVAL_INVALIDATED = "approval_invalidated" EXPORT_PREFLIGHT_FAILED = "export_preflight_failed" class IssueSeverity(str, Enum): INFO = "info" MINOR = "minor" MAJOR = "major" BLOCKER = "blocker" class ReviewRole(str, Enum): SME = "sme" INSTRUCTIONAL_DESIGNER = "instructional_designer" VISUAL_DESIGNER = "visual_designer" PRODUCER = "producer" INSTRUCTOR = "instructor" GENERAL_REVIEWER = "general_reviewer" class ReviewModeConfig(BaseModel): role: ReviewRole label: str description: str | None = None visible_stage_ids: list[str] = Field(default_factory=list) focused_issue_types: list[IssueType] = Field(default_factory=list) focused_rubric_dimensions: list[str] = Field(default_factory=list) default_sort: Literal["severity", "stage", "slide", "newest", "oldest"] = "severity" class IssueStatus(str, Enum): OPEN = "open" ACKNOWLEDGED = "acknowledged" IN_PROGRESS = "in_progress" RESOLVED = "resolved" WAIVED = "waived" WONT_FIX = "wont_fix" class QualityIssue(BaseModel): issue_id: str issue_type: IssueType severity: IssueSeverity message: str stage_id: str | None = None slide_id: str | None = None objective_id: str | None = None artifact_version_id: str | None = None claim_id: str | None = None suggested_fix: str | None = None resolved: bool = False status: IssueStatus = IssueStatus.OPEN priority: Literal["low", "medium", "high", "critical"] = "medium" assigned_role: ReviewRole | None = None assigned_to: str | None = None resolution_note: str | None = None waiver_reason: str | None = None resolved_by: str | None = None waived_by: str | None = None waived_at: str | None = None last_seen_artifact_version_id: str | None = None created_at: str resolved_at: str | None = None class ReviewQueueItem(BaseModel): queue_item_id: str issue_id: str issue_type: IssueType severity: IssueSeverity status: IssueStatus priority: Literal["low", "medium", "high", "critical"] stage_id: str | None = None slide_id: str | None = None objective_id: str | None = None claim_id: str | None = None artifact_version_id: str | None = None assigned_role: ReviewRole | None = None assigned_to: str | None = None message: str suggested_fix_summary: str | None = None created_at: str class SuggestedFix(BaseModel): fix_id: str issue_ids: list[str] = Field(default_factory=list) stage_id: str | None = None slide_ids: list[str] = Field(default_factory=list) objective_ids: list[str] = Field(default_factory=list) claim_ids: list[str] = Field(default_factory=list) fix_type: Literal[ "rewrite_text", "reduce_text", "add_example", "add_speaker_notes", "support_claim", "remove_unsupported_claim", "change_layout", "add_alt_text", "add_visual_asset", "improve_objective_mapping", "mark_for_human_review", "other", ] = "other" description: str expected_score_delta: dict[str, int] = Field(default_factory=dict) risk_level: Literal["low", "medium", "high"] = "medium" requires_human_confirmation: bool = True class RevisionConstraints(BaseModel): preserve_slide_order: bool = True preserve_slide_titles: bool = False preserve_technical_claims: bool = True preserve_objective_mappings: bool = True preserve_visual_assets: bool = False preserve_layout_ids: bool = False selected_slide_ids: list[str] = Field(default_factory=list) allowed_fields: list[str] = Field(default_factory=list) blocked_fields: list[str] = Field(default_factory=list) reviewer_instruction: str | None = None class ProposedChange(BaseModel): change_id: str target_type: Literal[ "stage_artifact", "slide", "slide_title", "slide_text", "speaker_notes", "layout", "visual_asset", "objective_mapping", "claim", "metadata", ] target_id: str field_path: str | None = None before: Any | None = None after: Any | None = None rationale: str | None = None issue_ids: list[str] = Field(default_factory=list) risk_level: Literal["low", "medium", "high"] = "medium" class ProposedChangeSet(BaseModel): change_set_id: str stage_id: str artifact_version_id: str | None = None created_at: str created_by: Literal["mock_ai", "system", "human"] = "mock_ai" status: Literal["draft", "reviewing", "partially_applied", "applied", "rejected"] = "draft" constraints: RevisionConstraints = Field(default_factory=RevisionConstraints) changes: list[ProposedChange] = Field(default_factory=list) summary: str | None = None class AIReviewCritique(BaseModel): critique_id: str stage_id: str artifact_version_id: str | None = None created_at: str provider: Literal["mock", "system"] = "mock" issue_ids: list[str] = Field(default_factory=list) diagnosis: str proposed_change_set_id: str | None = None risks: list[str] = Field(default_factory=list) reviewer_questions: list[str] = Field(default_factory=list) class ReviewPacket(BaseModel): packet_id: str job_id: str role: ReviewRole | None = None stage_ids: list[str] = Field(default_factory=list) slide_ids: list[str] = Field(default_factory=list) issue_ids: list[str] = Field(default_factory=list) objective_ids: list[str] = Field(default_factory=list) artifact_version_ids: list[str] = Field(default_factory=list) created_at: str format: Literal["markdown", "json"] = "markdown" path: str | None = None summary: str | None = None class VersionComparisonSummary(BaseModel): comparison_id: str previous_artifact_version_id: str current_artifact_version_id: str created_at: str changed_slide_ids: list[str] = Field(default_factory=list) added_slide_ids: list[str] = Field(default_factory=list) removed_slide_ids: list[str] = Field(default_factory=list) changed_titles: list[str] = Field(default_factory=list) changed_objective_mappings: list[str] = Field(default_factory=list) changed_claims: list[str] = Field(default_factory=list) changed_layouts: list[str] = Field(default_factory=list) changed_visual_assets: list[str] = Field(default_factory=list) changed_speaker_notes: list[str] = Field(default_factory=list) summary: str | None = None diff_text: str | None = None class ReviewerProductivityMetrics(BaseModel): job_id: str total_issues: int open_issues: int resolved_issues: int waived_issues: int blocker_count: int major_count: int issues_by_stage: dict[str, int] = Field(default_factory=dict) issues_by_role: dict[str, int] = Field(default_factory=dict) average_score_by_stage: dict[str, int] = Field(default_factory=dict) candidate_versions_created: int approvals_invalidated: int stale_events: int generated_at: str class ObjectiveTrace(BaseModel): objective_id: str objective_text: str mapped_slide_ids: list[str] = Field(default_factory=list) coverage_score: int = 0 coverage_status: Literal["uncovered", "weak", "partial", "strong"] = "uncovered" evidence: list[str] = Field(default_factory=list) issue_ids: list[str] = Field(default_factory=list) class PedagogicalRole(str, Enum): INTRO = "intro" MOTIVATION = "motivation" CONCEPT = "concept" WORKED_EXAMPLE = "worked_example" PROCEDURE = "procedure" COMPARISON = "comparison" COMMON_MISCONCEPTION = "common_misconception" KNOWLEDGE_CHECK = "knowledge_check" DEMO_SETUP = "demo_setup" DEMO_WALKTHROUGH = "demo_walkthrough" SUMMARY = "summary" TRANSITION = "transition" UNKNOWN = "unknown" class SpeakerNotes(BaseModel): slide_id: str notes_text: str | None = None instructor_intent: str | None = None estimated_teaching_time_seconds: int | None = None possible_student_confusions: list[str] = Field(default_factory=list) teaching_tips: list[str] = Field(default_factory=list) class SlideClaim(BaseModel): claim_id: str slide_id: str claim_text: str source_ids: list[str] = Field(default_factory=list) source_chunk_ids: list[str] = Field(default_factory=list) confidence: float = Field(default=1.0, ge=0.0, le=1.0) review_status: Literal[ "unreviewed", "supported", "unsupported", "needs_human_review", ] = "unreviewed" issue_ids: list[str] = Field(default_factory=list) class VisualAsset(BaseModel): asset_id: str slide_id: str | None = None asset_type: Literal[ "generated_image", "diagram", "chart", "icon", "screenshot", "placeholder", ] path_or_url: str | None = None prompt: str | None = None purpose: Literal["instructional", "decorative", "background", "unknown"] = "unknown" alt_text: str | None = None source: Literal["mock", "generated", "uploaded", "google_drive", "unknown"] = "unknown" license_status: Literal[ "generated", "user_provided", "unknown", "needs_review", ] = "unknown" approved_for_export: bool = False issue_ids: list[str] = Field(default_factory=list) class LayoutSpec(BaseModel): slide_id: str layout_id: str approved_template_id: str | None = None slot_assignments: dict[str, Any] = Field(default_factory=dict) schema_version: str = "1.0" class ArtifactStatus(str, Enum): DRAFT = "draft" CANDIDATE = "candidate" APPROVED = "approved" EXPORTED = "exported" STALE = "stale" INVALIDATED = "invalidated" class ArtifactVersion(BaseModel): artifact_version_id: str artifact_id: str stage_id: str version_number: int created_at: str created_by: Literal["system", "human", "ai", "mock"] content_hash: str status: ArtifactStatus = ArtifactStatus.DRAFT parent_artifact_version_ids: list[str] = Field(default_factory=list) upstream_stage_versions: dict[str, str] = Field(default_factory=dict) prompt_run_id: str | None = None is_current: bool = True metadata: dict[str, Any] = Field(default_factory=dict) class RequestedChange(BaseModel): slide_id: str | None = None stage_id: str | None = None change_type: Literal[ "add_example", "reduce_text", "fix_technical_accuracy", "improve_layout", "add_visual", "remove_visual", "add_speaker_notes", "improve_objective_alignment", "other", ] = "other" priority: Literal["low", "medium", "high", "blocker"] = "medium" note: str class ReviewerNotes(BaseModel): stage_id: str artifact_version_id: str | None = None reviewer_name: str = "human_reviewer" summary: str | None = None requested_changes: list[RequestedChange] = Field(default_factory=list) created_at: str class PromptRun(BaseModel): prompt_run_id: str stage_id: str artifact_version_id: str | None = None provider: Literal["mock", "openai", "anthropic", "gemini", "system"] = "mock" model_name: str | None = None prompt_template_id: str | None = None prompt_template_version: str | None = None rendered_prompt: str | None = None input_artifact_version_ids: list[str] = Field(default_factory=list) output_artifact_version_id: str | None = None settings: dict[str, Any] = Field(default_factory=dict) created_at: str class DeckHealthSummary(BaseModel): job_id: str deck_title: str | None = None approved_stage_count: int total_stage_count: int stale_stage_count: int invalidated_approval_count: int unresolved_blocker_count: int unresolved_major_issue_count: int slide_count: int average_slide_score: int | None = None objectives_total: int objectives_strong: int objectives_partial_or_weak: int objectives_uncovered: int unsupported_claim_count: int can_export: bool top_blockers: list[str] = Field(default_factory=list) class SlideQAStatus(BaseModel): slide_id: str slide_number: int title: str | None = None pedagogical_role: PedagogicalRole = PedagogicalRole.UNKNOWN objective_ids: list[str] = Field(default_factory=list) claim_ids: list[str] = Field(default_factory=list) visual_asset_ids: list[str] = Field(default_factory=list) layout_id: str | None = None aggregate_score: int | None = None technical_score: int | None = None pedagogical_score: int | None = None aesthetic_score: int | None = None issue_ids: list[str] = Field(default_factory=list) stale: bool = False status: Literal["draft", "needs_review", "needs_revision", "approved", "stale", "blocked"] = ( "draft" ) class ExportPreflightReport(BaseModel): can_export: bool checked_at: str blocking_issue_ids: list[str] = Field(default_factory=list) warning_issue_ids: list[str] = Field(default_factory=list) summary: str class StageApproval(BaseModel): stage_id: str artifact_version_id: str reviewer_name: str = "human_reviewer" reviewer_role: ReviewRole | None = None approved_at: str approval_status: Literal["approved", "invalidated", "revoked"] = "approved" invalidated_at: str | None = None invalidation_reason: str | None = None class AuditEvent(BaseModel): event_id: str event_type: str timestamp: str stage_id: str | None = None slide_id: str | None = None objective_id: str | None = None artifact_version_id: str | None = None issue_id: str | None = None claim_id: str | None = None reason: str | None = None metadata: dict[str, Any] = Field(default_factory=dict) class StageState(BaseModel): stage_id: str label: str score: int | None = None is_stale: bool = False current_artifact_version_id: str | None = None grade_result: StageGradeResult | None = None class Slide(BaseModel): slide_id: str slide_number: int title: str | None = None visible_text: str = "" bullet_points: list[str] = Field(default_factory=list) objective_ids: list[str] = Field(default_factory=list) pedagogical_role: PedagogicalRole = PedagogicalRole.UNKNOWN requires_visual: bool = False speaker_notes: SpeakerNotes | None = None objective_coverage_scores: dict[str, int] = Field(default_factory=dict) distinct_concept_count: int | None = None metadata: dict[str, Any] = Field(default_factory=dict) class PipelineState(BaseModel): job_id: str deck_title: str | None = None dry_run: bool = True production_export_requested: bool = False enable_role_required_approvals: bool = False source_url: str | None = None template_url: str | None = None output_folder_id: str | None = None mutation_target_url: str | None = None uploaded_materials: list[dict[str, Any]] = Field(default_factory=list) unsupported_materials: list[dict[str, Any]] = Field(default_factory=list) start_mode: Literal["instructions_file", "input_outline", "import_existing_draft", "start_from_zero"] = "instructions_file" draft_upload_metadata: dict[str, Any] = Field(default_factory=dict) source_chunks: dict[str, str] = Field(default_factory=dict) objectives: dict[str, str] = Field(default_factory=dict) stages: dict[str, StageState] = Field(default_factory=dict) artifacts: dict[str, ArtifactVersion] = Field(default_factory=dict) stage_artifact_versions: dict[str, list[str]] = Field(default_factory=dict) issues: dict[str, QualityIssue] = Field(default_factory=dict) objective_traces: dict[str, ObjectiveTrace] = Field(default_factory=dict) slides: dict[str, Slide] = Field(default_factory=dict) slide_statuses: dict[str, SlideQAStatus] = Field(default_factory=dict) claims: dict[str, SlideClaim] = Field(default_factory=dict) visual_assets: dict[str, VisualAsset] = Field(default_factory=dict) layout_specs: dict[str, LayoutSpec] = Field(default_factory=dict) approvals: list[StageApproval] = Field(default_factory=list) reviewer_notes: list[ReviewerNotes] = Field(default_factory=list) prompt_runs: dict[str, PromptRun] = Field(default_factory=dict) suggested_fixes: dict[str, SuggestedFix] = Field(default_factory=dict) critiques: dict[str, AIReviewCritique] = Field(default_factory=dict) proposed_change_sets: dict[str, ProposedChangeSet] = Field(default_factory=dict) review_packets: dict[str, ReviewPacket] = Field(default_factory=dict) version_comparisons: dict[str, VersionComparisonSummary] = Field(default_factory=dict) audit_events: list[AuditEvent] = Field(default_factory=list) export_preflight_report: ExportPreflightReport | None = None