Files
autoayuno-api/models.py
Carlos Narro d9f3e91aa8 telemetría: tiempo por pantalla (columna seconds) + Aprende/subsecciones
telemetry_daily gana columna `seconds` (migración idempotente ALTER ADD COLUMN
IF NOT EXISTS para el Postgres ya desplegado; create_all para SQLite fresco). El
POST acepta seconds_by_module y lo agrega por clave (tope 86400/clave/envío); el
summary suma y devuelve seconds por módulo/vista. Las claves ya eran genéricas,
así que Aprende ('aprende', 'aprende:<tema>') y las vistas ('today', '__other__')
entran sin cambios de esquema. Tests actualizados.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 00:01:46 +02:00

88 lines
3.5 KiB
Python

"""Modelos de la base de datos: alimentos y recetas del catálogo compartido."""
from sqlalchemy import Column, Float, Integer, String, JSON, DateTime, func
from db import Base
class Food(Base):
__tablename__ = "foods"
id = Column(String, primary_key=True)
name = Column(String, nullable=False, index=True)
kcal_100g = Column(Float, nullable=False)
zone = Column(String, default="otros")
portions = Column(JSON, default=list) # [{"name","grams"}]
barcode = Column(String, nullable=True)
source = Column(String, default="user") # off | user
off_id = Column(String, nullable=True, index=True)
author_token = Column(String, nullable=True)
created_at = Column(DateTime, server_default=func.now())
def to_app(self) -> dict:
"""Serializa a la forma que espera la app (Food.fromJson)."""
return {
"id": self.id,
"name": self.name,
"kcalPer100g": self.kcal_100g,
"zone": self.zone or "otros",
"portions": self.portions or [],
"origin": "remote",
}
class Recipe(Base):
__tablename__ = "recipes"
id = Column(String, primary_key=True)
name = Column(String, nullable=False, index=True)
servings = Column(Integer, default=1)
items = Column(JSON, default=list) # [{"food": {...}, "grams", "portionLabel"}]
category = Column(String, default="otros")
prep_minutes = Column(Integer, nullable=True)
steps = Column(JSON, default=list) # ["paso 1", "paso 2"]
notes = Column(String, nullable=True)
author_id = Column(String, nullable=True, index=True)
author_name = Column(String, nullable=True)
parent_id = Column(String, nullable=True)
parent_author_name = Column(String, nullable=True)
root_id = Column(String, nullable=True)
root_author_name = Column(String, nullable=True)
visibility = Column(String, default="public", index=True)
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
author_token = Column(String, nullable=True)
created_at = Column(DateTime, server_default=func.now())
def to_app(self) -> dict:
return {
"id": self.id,
"name": self.name,
"servings": self.servings,
"items": self.items or [],
"category": self.category or "otros",
"prepMinutes": self.prep_minutes,
"steps": self.steps or [],
"notes": self.notes,
"authorId": self.author_id,
"authorName": self.author_name,
"parentId": self.parent_id,
"parentAuthorName": self.parent_author_name,
"rootId": self.root_id,
"rootAuthorName": self.root_author_name,
"visibility": self.visibility or "public",
}
class TelemetryDaily(Base):
"""Contadores de uso ANÓNIMOS y agregados, por día y módulo. NO hay
identificador de usuario ni contenido de salud: solo cuántas instalaciones
tienen cada módulo activo (`active`) y cuántas veces lo abren (`opens`). La
fila especial module='__submissions__' cuenta los envíos del día (≈ nº de
instalaciones activas que ceden estadística)."""
__tablename__ = "telemetry_daily"
day = Column(String, primary_key=True) # 'YYYY-MM-DD'
module = Column(String, primary_key=True) # nombre del módulo/vista o '__submissions__'
active = Column(Integer, default=0)
opens = Column(Integer, default=0)
seconds = Column(Integer, default=0) # tiempo en pantalla acumulado