Merges ac-helper (PHP Activation Calendar) and brief-extractor (Python AI) into a single Docker app with React/TypeScript frontend. Features: - Brief upload → AI extraction → review → Activation Calendar import - Handsontable v17 spreadsheet with dependent dropdowns (148 categories) - AI natural language commands via Gemini (YOLO mode, voice input) - Azure AD MSAL SPA PKCE authentication, user roles (user/admin) - CSV Activation Calendar export - Real-time WebSocket job progress - Admin: user management, dropdown Excel upload - Multi-stage Dockerfile, docker-compose, nginx proxy instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2 KiB
Python
73 lines
2 KiB
Python
"""
|
|
Pydantic models for sheets and deliverables.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
from datetime import datetime, timezone
|
|
from typing import List, Optional
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class Deliverable(BaseModel):
|
|
Number: str = ""
|
|
Title: str = ""
|
|
Status: str = "Booked"
|
|
Category: str = ""
|
|
Media: str = ""
|
|
SubMedia: str = Field(default="", alias="Sub-media")
|
|
Format: str = ""
|
|
SupplyDate: str = Field(default="", alias="Supply date")
|
|
LiveDate: str = Field(default="", alias="Live date")
|
|
Language: str = ""
|
|
Country: str = ""
|
|
Quantity: int = 1
|
|
|
|
class Config:
|
|
populate_by_name = True
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"Number": self.Number,
|
|
"Title": self.Title,
|
|
"Status": self.Status,
|
|
"Category": self.Category,
|
|
"Media": self.Media,
|
|
"Sub-media": self.SubMedia,
|
|
"Format": self.Format,
|
|
"Supply date": self.SupplyDate,
|
|
"Live date": self.LiveDate,
|
|
"Language": self.Language,
|
|
"Country": self.Country,
|
|
"Quantity": self.Quantity,
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, d: dict) -> "Deliverable":
|
|
return cls(
|
|
Number=d.get("Number", ""),
|
|
Title=d.get("Title", ""),
|
|
Status=d.get("Status", "Booked"),
|
|
Category=d.get("Category", ""),
|
|
Media=d.get("Media", ""),
|
|
**{"Sub-media": d.get("Sub-media", "")},
|
|
Format=d.get("Format", ""),
|
|
**{"Supply date": d.get("Supply date", "")},
|
|
**{"Live date": d.get("Live date", "")},
|
|
Language=d.get("Language", ""),
|
|
Country=d.get("Country", ""),
|
|
Quantity=int(d.get("Quantity", 1)),
|
|
)
|
|
|
|
|
|
class SheetMeta(BaseModel):
|
|
id: str
|
|
name: str
|
|
created: str
|
|
modified: str
|
|
itemCount: int
|
|
user: str
|
|
|
|
|
|
class Sheet(BaseModel):
|
|
meta: SheetMeta
|
|
data: List[dict] # raw dicts for speed; validated on write
|