feat(backend): champs identified_* sur Media pour stocker l'identification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
71
backend/app/migrate.py
Normal file
71
backend/app/migrate.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Migration automatique SQLite — détecte et ajoute les colonnes manquantes."""
|
||||
import logging
|
||||
from sqlalchemy import text
|
||||
from app.database import engine
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Définition des colonnes attendues par table : {table: [(col, type_sql, default)]}
|
||||
EXPECTED_COLUMNS: dict[str, list[tuple[str, str, str | None]]] = {
|
||||
"plant": [
|
||||
("categorie", "TEXT", None),
|
||||
("hauteur_cm", "INTEGER", None),
|
||||
("maladies_courantes", "TEXT", None),
|
||||
("astuces_culture", "TEXT", None),
|
||||
("url_reference", "TEXT", None),
|
||||
],
|
||||
"garden": [
|
||||
("surface_m2", "REAL", None),
|
||||
("ensoleillement", "TEXT", None),
|
||||
],
|
||||
"task": [
|
||||
("frequence_jours", "INTEGER", None),
|
||||
("date_prochaine", "TEXT", None),
|
||||
("outil_id", "INTEGER", None),
|
||||
],
|
||||
"plantvariety": [
|
||||
# ancien nom de table → migration vers "plant" si présente
|
||||
],
|
||||
"media": [
|
||||
("identified_species", "TEXT", None),
|
||||
("identified_common", "TEXT", None),
|
||||
("identified_confidence", "REAL", None),
|
||||
("identified_source", "TEXT", None),
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _existing_columns(conn, table: str) -> set[str]:
|
||||
rows = conn.execute(text(f"PRAGMA table_info({table})")).fetchall()
|
||||
return {row[1] for row in rows}
|
||||
|
||||
|
||||
def _existing_tables(conn) -> set[str]:
|
||||
rows = conn.execute(text("SELECT name FROM sqlite_master WHERE type='table'")).fetchall()
|
||||
return {row[0] for row in rows}
|
||||
|
||||
|
||||
def run_migrations() -> None:
|
||||
with engine.connect() as conn:
|
||||
tables = _existing_tables(conn)
|
||||
|
||||
# Renommage plantvariety → plant si l'ancienne table existe et la nouvelle non
|
||||
if "plantvariety" in tables and "plant" not in tables:
|
||||
conn.execute(text("ALTER TABLE plantvariety RENAME TO plant"))
|
||||
conn.commit()
|
||||
logger.info("Migration: plantvariety renommée en plant")
|
||||
tables = _existing_tables(conn)
|
||||
|
||||
for table, columns in EXPECTED_COLUMNS.items():
|
||||
if table not in tables or not columns:
|
||||
continue
|
||||
existing = _existing_columns(conn, table)
|
||||
for col_name, col_type, default in columns:
|
||||
if col_name not in existing:
|
||||
if default is not None:
|
||||
sql = f"ALTER TABLE {table} ADD COLUMN {col_name} {col_type} DEFAULT {default}"
|
||||
else:
|
||||
sql = f"ALTER TABLE {table} ADD COLUMN {col_name} {col_type}"
|
||||
conn.execute(text(sql))
|
||||
conn.commit()
|
||||
logger.info(f"Migration: colonne {table}.{col_name} ajoutée")
|
||||
28
backend/app/models/media.py
Normal file
28
backend/app/models/media.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class Media(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
entity_type: str # jardin|plante|outil|plantation
|
||||
entity_id: int
|
||||
url: str
|
||||
thumbnail_url: Optional[str] = None
|
||||
titre: Optional[str] = None
|
||||
# Identification automatique
|
||||
identified_species: Optional[str] = None
|
||||
identified_common: Optional[str] = None
|
||||
identified_confidence: Optional[float] = None
|
||||
identified_source: Optional[str] = None # "plantnet" | "yolo" | "cache"
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
|
||||
class Attachment(SQLModel, table=True):
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
entity_type: str
|
||||
entity_id: int
|
||||
type: str # pdf|url|note
|
||||
titre: Optional[str] = None
|
||||
contenu: Optional[str] = None
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
Reference in New Issue
Block a user