- Add image intake service with format validation (JPEG, PNG, WebP) and EXIF/GPS stripping - Enforce strict single-image rule across Web and Signal attachment channels - Implement token-optimized vision downscaling and JPEG compression - Add IMAGE_CONTEXT pipeline stage with OmniRoute vision routing and resilient failover - Seed and manage versioned idea-image-interpreter prompt in catalog - Update Web UI with responsive image picker, preview chip, and Visual Context tab - Add comprehensive automated test suite in test_image_intake.py - Update README and Labyricorn devlog
248 lines
6.6 KiB
Python
248 lines
6.6 KiB
Python
"""
|
|
ThinkStorm Data Models & Enums
|
|
Defines lifecycle states, operational entities, and schema types.
|
|
"""
|
|
|
|
from enum import Enum
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional, List, Dict, Any
|
|
from datetime import datetime
|
|
|
|
class LifecycleState(str, Enum):
|
|
SUBMITTED = "SUBMITTED"
|
|
AVAILABLE = "AVAILABLE"
|
|
CLAIMED = "CLAIMED"
|
|
ACTIVE = "ACTIVE"
|
|
COMPLETED = "COMPLETED"
|
|
ABANDONED = "ABANDONED"
|
|
RETIRED = "RETIRED"
|
|
QUARANTINED = "QUARANTINED"
|
|
REJECTED = "REJECTED"
|
|
DUPLICATE = "DUPLICATE"
|
|
TRASHED = "TRASHED"
|
|
|
|
class ProcessingState(str, Enum):
|
|
IDLE = "IDLE"
|
|
QUEUED = "QUEUED"
|
|
PROCESSING = "PROCESSING"
|
|
PARTIAL = "PARTIAL"
|
|
ERROR = "ERROR"
|
|
|
|
class URLSafetyState(str, Enum):
|
|
PENDING = "PENDING"
|
|
CHECKING = "CHECKING"
|
|
SAFE = "SAFE"
|
|
SUSPICIOUS = "SUSPICIOUS"
|
|
MALICIOUS = "MALICIOUS"
|
|
UNKNOWN = "UNKNOWN"
|
|
ERROR = "ERROR"
|
|
|
|
class AutomationPolicy(str, Enum):
|
|
APPROVED = "APPROVED"
|
|
BLOCKED = "BLOCKED"
|
|
REQUIRES_REVIEW = "REQUIRES_REVIEW"
|
|
|
|
class WorkTrackState(str, Enum):
|
|
PLANNED = "PLANNED"
|
|
ACTIVE = "ACTIVE"
|
|
PAUSED = "PAUSED"
|
|
COMPLETED = "COMPLETED"
|
|
ABANDONED = "ABANDONED"
|
|
RETIRED = "RETIRED"
|
|
|
|
class UserRole(str, Enum):
|
|
ANONYMOUS = "ANONYMOUS"
|
|
USER = "USER"
|
|
ADMIN = "ADMIN"
|
|
|
|
class ProcessorStatus(str, Enum):
|
|
PENDING = "PENDING"
|
|
RUNNING = "RUNNING"
|
|
COMPLETED = "COMPLETED"
|
|
FAILED = "FAILED"
|
|
CANCELLED = "CANCELLED"
|
|
|
|
@dataclass
|
|
class User:
|
|
id: int
|
|
username: str
|
|
password_hash: str
|
|
role: UserRole = UserRole.USER
|
|
created_at: str = ""
|
|
|
|
@dataclass
|
|
class IdeaURL:
|
|
id: Optional[int]
|
|
idea_id: str
|
|
url: str
|
|
safety_state: URLSafetyState = URLSafetyState.PENDING
|
|
automation_policy: AutomationPolicy = AutomationPolicy.REQUIRES_REVIEW
|
|
virustotal_data: Dict[str, Any] = field(default_factory=dict)
|
|
admin_review_required: bool = False
|
|
reviewed_by: Optional[str] = None
|
|
reviewed_at: Optional[str] = None
|
|
decision: Optional[str] = None
|
|
|
|
@dataclass
|
|
class Idea:
|
|
id: str # e.g. TS-0001
|
|
original_text: str
|
|
submitted_at: str
|
|
title: str = ""
|
|
summary: str = ""
|
|
categories: List[str] = field(default_factory=list)
|
|
tags: List[str] = field(default_factory=list)
|
|
lifecycle_state: LifecycleState = LifecycleState.SUBMITTED
|
|
processing_state: ProcessingState = ProcessingState.IDLE
|
|
enrichment_level: int = 0
|
|
claimed_by: Optional[str] = None
|
|
claimed_at: Optional[str] = None
|
|
released_at: Optional[str] = None
|
|
previous_lifecycle_state: Optional[str] = None
|
|
trashed_at: Optional[str] = None
|
|
profile_id: Optional[str] = None
|
|
profile_version: int = 1
|
|
opengist_id: Optional[str] = None
|
|
opengist_url: Optional[str] = None
|
|
gitea_repo_name: Optional[str] = None
|
|
gitea_repo_url: Optional[str] = None
|
|
created_at: str = ""
|
|
updated_at: str = ""
|
|
urls: List[IdeaURL] = field(default_factory=list)
|
|
relationships: List[Dict[str, Any]] = field(default_factory=list)
|
|
submission_image: Optional[Dict[str, Any]] = None
|
|
source_channel: str = "web"
|
|
source_sender_uuid: Optional[str] = None
|
|
source_group_id: Optional[str] = None
|
|
source_event_id: Optional[str] = None
|
|
|
|
@dataclass
|
|
class SubmittedImage:
|
|
raw_bytes: bytes
|
|
filename: str
|
|
content_type: str
|
|
source: str = "web" # "web" | "signal"
|
|
|
|
@dataclass
|
|
class IdeaSubmission:
|
|
text: str
|
|
source: str = "web" # "web" | "signal"
|
|
image: Optional[SubmittedImage] = None
|
|
sender_uuid: Optional[str] = None
|
|
sender_number: Optional[str] = None
|
|
sender_name: Optional[str] = None
|
|
group_id: Optional[str] = None
|
|
message_id: Optional[str] = None
|
|
event_id: Optional[str] = None
|
|
reply_to: Optional[str] = None
|
|
received_at: Optional[str] = None
|
|
|
|
@dataclass
|
|
class WorkTrackOutput:
|
|
id: Optional[int]
|
|
work_track_id: str
|
|
name: str
|
|
artifact_path: str
|
|
content: str
|
|
version: int = 1
|
|
is_current: bool = True
|
|
model_used: Optional[str] = None
|
|
opengist_file: Optional[str] = None
|
|
created_at: str = ""
|
|
updated_at: str = ""
|
|
|
|
@dataclass
|
|
class WorkTrack:
|
|
id: str # e.g. WT-0001
|
|
idea_id: str
|
|
work_type_id: str # ARTICLE, BLOG_ENTRY, CODING_PROJECT, YOUTUBE_VIDEO
|
|
name: str
|
|
state: WorkTrackState = WorkTrackState.PLANNED
|
|
workflow_id: str = ""
|
|
model_override: Optional[str] = None
|
|
created_at: str = ""
|
|
started_at: Optional[str] = None
|
|
completed_at: Optional[str] = None
|
|
outputs: List[WorkTrackOutput] = field(default_factory=list)
|
|
external_resources: List[Dict[str, Any]] = field(default_factory=list)
|
|
|
|
@dataclass
|
|
class PromptVersion:
|
|
id: Optional[int]
|
|
prompt_definition_id: str
|
|
version: int
|
|
system_prompt: str
|
|
user_prompt_template: str
|
|
applies_to: Dict[str, Any] = field(default_factory=dict)
|
|
model_policy: str = "reasoning"
|
|
expected_outputs: List[str] = field(default_factory=list)
|
|
prompt_hash: str = ""
|
|
created_by: str = "system"
|
|
created_at: str = ""
|
|
|
|
@dataclass
|
|
class PromptDefinition:
|
|
id: str # e.g. prior-art-software
|
|
name: str
|
|
stage: str
|
|
description: str
|
|
current_version: int = 1
|
|
enabled: bool = True
|
|
created_at: str = ""
|
|
versions: List[PromptVersion] = field(default_factory=list)
|
|
|
|
@dataclass
|
|
class PromptProfile:
|
|
id: str # e.g. software-idea-v1
|
|
name: str
|
|
description: str
|
|
prompt_assignments: Dict[str, str] = field(default_factory=dict) # stage -> prompt_id@version
|
|
is_default: bool = False
|
|
created_at: str = ""
|
|
|
|
@dataclass
|
|
class ProcessorRun:
|
|
id: str # e.g. RUN-0001
|
|
idea_id: str
|
|
work_track_id: Optional[str]
|
|
processor_name: str
|
|
stage: str
|
|
prompt_id: Optional[str]
|
|
prompt_version: Optional[int]
|
|
prompt_hash: Optional[str]
|
|
model_policy: str
|
|
resolved_provider: str
|
|
resolved_model: str
|
|
input_tokens: int = 0
|
|
output_tokens: int = 0
|
|
total_tokens: int = 0
|
|
started_at: str = ""
|
|
completed_at: Optional[str] = None
|
|
duration_ms: int = 0
|
|
output_artifact: Optional[str] = None
|
|
output_data: Dict[str, Any] = field(default_factory=dict)
|
|
error_message: Optional[str] = None
|
|
status: ProcessorStatus = ProcessorStatus.PENDING
|
|
|
|
@dataclass
|
|
class ServiceConfig:
|
|
id: str
|
|
name: str
|
|
endpoint: str
|
|
api_key_masked: str
|
|
enabled: bool
|
|
config_json: Dict[str, Any] = field(default_factory=dict)
|
|
last_tested_at: Optional[str] = None
|
|
health_status: str = "UNKNOWN"
|
|
last_error: Optional[str] = None
|
|
|
|
@dataclass
|
|
class AuditEvent:
|
|
id: Optional[int]
|
|
user_id: str
|
|
action: str
|
|
entity_type: str
|
|
entity_id: str
|
|
details: Dict[str, Any]
|
|
created_at: str = ""
|