- Migration 020: Add type, stages, output_format columns to ai_prompts
- Migrate existing prompts to 1-stage pipeline format
- Migrate pipeline_configs into ai_prompts as multi-stage pipelines
- Add UnifiedPrompt Pydantic models for new API
- Backup pipeline_configs table (keep during transition)
Schema structure:
- type: 'base' (reusable) or 'pipeline' (multi-stage)
- stages: JSONB array [{stage:1, prompts:[{source, slug, template, output_key, output_format}]}]
- output_format: 'text' or 'json'
- output_schema: JSON validation schema (optional)
Next: Backend executor + Frontend UI consolidation
243 lines
7.8 KiB
Python
243 lines
7.8 KiB
Python
"""
|
|
Pydantic Models for Mitai Jinkendo API
|
|
|
|
Data validation schemas for request/response bodies.
|
|
"""
|
|
from typing import Optional
|
|
from pydantic import BaseModel
|
|
|
|
|
|
# ── Profile Models ────────────────────────────────────────────────────────────
|
|
|
|
class ProfileCreate(BaseModel):
|
|
name: str
|
|
avatar_color: Optional[str] = '#1D9E75'
|
|
sex: Optional[str] = 'm'
|
|
dob: Optional[str] = None
|
|
height: Optional[float] = 178
|
|
goal_weight: Optional[float] = None
|
|
goal_bf_pct: Optional[float] = None
|
|
|
|
|
|
class ProfileUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
avatar_color: Optional[str] = None
|
|
sex: Optional[str] = None
|
|
dob: Optional[str] = None
|
|
height: Optional[float] = None
|
|
goal_weight: Optional[float] = None
|
|
goal_bf_pct: Optional[float] = None
|
|
quality_filter_level: Optional[str] = None # Issue #31: Global quality filter
|
|
|
|
|
|
# ── Tracking Models ───────────────────────────────────────────────────────────
|
|
|
|
class WeightEntry(BaseModel):
|
|
date: str
|
|
weight: float
|
|
note: Optional[str] = None
|
|
|
|
|
|
class CircumferenceEntry(BaseModel):
|
|
date: str
|
|
c_neck: Optional[float] = None
|
|
c_chest: Optional[float] = None
|
|
c_waist: Optional[float] = None
|
|
c_belly: Optional[float] = None
|
|
c_hip: Optional[float] = None
|
|
c_thigh: Optional[float] = None
|
|
c_calf: Optional[float] = None
|
|
c_arm: Optional[float] = None
|
|
notes: Optional[str] = None
|
|
photo_id: Optional[str] = None
|
|
|
|
|
|
class CaliperEntry(BaseModel):
|
|
date: str
|
|
sf_method: Optional[str] = 'jackson3'
|
|
sf_chest: Optional[float] = None
|
|
sf_axilla: Optional[float] = None
|
|
sf_triceps: Optional[float] = None
|
|
sf_subscap: Optional[float] = None
|
|
sf_suprailiac: Optional[float] = None
|
|
sf_abdomen: Optional[float] = None
|
|
sf_thigh: Optional[float] = None
|
|
sf_calf_med: Optional[float] = None
|
|
sf_lowerback: Optional[float] = None
|
|
sf_biceps: Optional[float] = None
|
|
body_fat_pct: Optional[float] = None
|
|
lean_mass: Optional[float] = None
|
|
fat_mass: Optional[float] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class ActivityEntry(BaseModel):
|
|
date: str
|
|
start_time: Optional[str] = None
|
|
end_time: Optional[str] = None
|
|
activity_type: str
|
|
duration_min: Optional[float] = None
|
|
kcal_active: Optional[float] = None
|
|
kcal_resting: Optional[float] = None
|
|
hr_avg: Optional[float] = None
|
|
hr_max: Optional[float] = None
|
|
distance_km: Optional[float] = None
|
|
rpe: Optional[int] = None
|
|
source: Optional[str] = 'manual'
|
|
notes: Optional[str] = None
|
|
training_type_id: Optional[int] = None # v9d: Training type categorization
|
|
training_category: Optional[str] = None # v9d: Denormalized category
|
|
training_subcategory: Optional[str] = None # v9d: Denormalized subcategory
|
|
|
|
|
|
class NutritionDay(BaseModel):
|
|
date: str
|
|
kcal: Optional[float] = None
|
|
protein_g: Optional[float] = None
|
|
fat_g: Optional[float] = None
|
|
carbs_g: Optional[float] = None
|
|
|
|
|
|
# ── Auth Models ───────────────────────────────────────────────────────────────
|
|
|
|
class LoginRequest(BaseModel):
|
|
email: str
|
|
password: str
|
|
|
|
|
|
class PasswordResetRequest(BaseModel):
|
|
email: str
|
|
|
|
|
|
class PasswordResetConfirm(BaseModel):
|
|
token: str
|
|
new_password: str
|
|
|
|
|
|
class RegisterRequest(BaseModel):
|
|
name: str
|
|
email: str
|
|
password: str
|
|
|
|
|
|
# ── Admin Models ──────────────────────────────────────────────────────────────
|
|
|
|
class AdminProfileUpdate(BaseModel):
|
|
role: Optional[str] = None
|
|
ai_enabled: Optional[int] = None
|
|
ai_limit_day: Optional[int] = None
|
|
export_enabled: Optional[int] = None
|
|
|
|
|
|
# ── Prompt Models (Issue #28) ────────────────────────────────────────────────
|
|
|
|
class PromptCreate(BaseModel):
|
|
name: str
|
|
slug: str
|
|
display_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
template: str
|
|
category: str = 'ganzheitlich'
|
|
active: bool = True
|
|
sort_order: int = 0
|
|
|
|
|
|
class PromptUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
display_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
template: Optional[str] = None
|
|
category: Optional[str] = None
|
|
active: Optional[bool] = None
|
|
sort_order: Optional[int] = None
|
|
|
|
|
|
class PromptGenerateRequest(BaseModel):
|
|
goal: str
|
|
data_categories: list[str]
|
|
example_output: Optional[str] = None
|
|
|
|
|
|
# ── Unified Prompt System Models (Issue #28 Phase 2) ───────────────────────
|
|
|
|
class StagePromptCreate(BaseModel):
|
|
"""Single prompt within a stage"""
|
|
source: str # 'inline' or 'reference'
|
|
slug: Optional[str] = None # Required if source='reference'
|
|
template: Optional[str] = None # Required if source='inline'
|
|
output_key: str # Key for storing result (e.g., 'nutrition', 'stage1_body')
|
|
output_format: str = 'text' # 'text' or 'json'
|
|
output_schema: Optional[dict] = None # JSON schema if output_format='json'
|
|
|
|
|
|
class StageCreate(BaseModel):
|
|
"""Single stage with multiple prompts"""
|
|
stage: int # Stage number (1, 2, 3, ...)
|
|
prompts: list[StagePromptCreate]
|
|
|
|
|
|
class UnifiedPromptCreate(BaseModel):
|
|
"""Create a new unified prompt (base or pipeline type)"""
|
|
name: str
|
|
slug: str
|
|
display_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
type: str # 'base' or 'pipeline'
|
|
category: str = 'ganzheitlich'
|
|
active: bool = True
|
|
sort_order: int = 0
|
|
|
|
# For base prompts (single reusable template)
|
|
template: Optional[str] = None # Required if type='base'
|
|
output_format: str = 'text'
|
|
output_schema: Optional[dict] = None
|
|
|
|
# For pipeline prompts (multi-stage workflow)
|
|
stages: Optional[list[StageCreate]] = None # Required if type='pipeline'
|
|
|
|
|
|
class UnifiedPromptUpdate(BaseModel):
|
|
"""Update an existing unified prompt"""
|
|
name: Optional[str] = None
|
|
display_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
type: Optional[str] = None
|
|
category: Optional[str] = None
|
|
active: Optional[bool] = None
|
|
sort_order: Optional[int] = None
|
|
template: Optional[str] = None
|
|
output_format: Optional[str] = None
|
|
output_schema: Optional[dict] = None
|
|
stages: Optional[list[StageCreate]] = None
|
|
|
|
|
|
# ── Pipeline Config Models (Issue #28) ─────────────────────────────────────
|
|
# NOTE: These will be deprecated in favor of UnifiedPrompt models above
|
|
|
|
class PipelineConfigCreate(BaseModel):
|
|
name: str
|
|
description: Optional[str] = None
|
|
is_default: bool = False
|
|
active: bool = True
|
|
modules: dict # {"körper": true, "ernährung": true, ...}
|
|
timeframes: dict # {"körper": 30, "ernährung": 30, ...}
|
|
stage1_prompts: list[str] # Array of slugs
|
|
stage2_prompt: str # slug
|
|
stage3_prompt: Optional[str] = None # slug
|
|
|
|
|
|
class PipelineConfigUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
is_default: Optional[bool] = None
|
|
active: Optional[bool] = None
|
|
modules: Optional[dict] = None
|
|
timeframes: Optional[dict] = None
|
|
stage1_prompts: Optional[list[str]] = None
|
|
stage2_prompt: Optional[str] = None
|
|
stage3_prompt: Optional[str] = None
|
|
|
|
|
|
class PipelineExecuteRequest(BaseModel):
|
|
config_id: Optional[str] = None # None = use default config
|