feat: improve generation analytics and maintenance
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import HTTPException
|
||||
@@ -9,6 +10,7 @@ from sqlalchemy import desc, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import joinedload
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.logging import get_logger
|
||||
from app.db.models import ChildProfile, Story, StoryUniverse
|
||||
from app.schemas.story_schemas import (
|
||||
@@ -32,6 +34,7 @@ from app.services.audio_storage import (
|
||||
)
|
||||
from app.services.generation_jobs import (
|
||||
create_generation_job,
|
||||
ensure_no_active_story_generation_job,
|
||||
finish_generation_job,
|
||||
record_generation_event,
|
||||
)
|
||||
@@ -1369,6 +1372,7 @@ async def retry_story_assets(
|
||||
db: AsyncSession,
|
||||
) -> Story:
|
||||
"""Retry selected assets through one workflow-level endpoint."""
|
||||
await ensure_no_active_story_generation_job(db, story_id=story_id, user_id=user_id)
|
||||
requested_assets = list(dict.fromkeys(assets))
|
||||
job = await create_generation_job(
|
||||
db,
|
||||
@@ -1443,6 +1447,7 @@ async def generate_story_cover(
|
||||
db: AsyncSession,
|
||||
) -> str:
|
||||
"""Generate cover image for an existing story."""
|
||||
await ensure_no_active_story_generation_job(db, story_id=story_id, user_id=user_id)
|
||||
job = await create_generation_job(
|
||||
db,
|
||||
user_id=user_id,
|
||||
@@ -1495,6 +1500,7 @@ async def generate_story_audio(
|
||||
db: AsyncSession,
|
||||
) -> bytes:
|
||||
"""Generate audio for a story."""
|
||||
await ensure_no_active_story_generation_job(db, story_id=story_id, user_id=user_id)
|
||||
job = await create_generation_job(
|
||||
db,
|
||||
user_id=user_id,
|
||||
@@ -1597,6 +1603,50 @@ async def clear_story_audio_cache(
|
||||
return await get_story_audio_status(story_id, user_id, db)
|
||||
|
||||
|
||||
async def prune_story_audio_cache(db: AsyncSession) -> dict[str, int]:
|
||||
"""Prune expired audio cache files and repair story metadata."""
|
||||
|
||||
ttl_days = max(1, settings.story_audio_cache_ttl_days)
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(days=ttl_days)
|
||||
result = await db.execute(select(Story).where(Story.audio_path.is_not(None)))
|
||||
stories = result.scalars().all()
|
||||
|
||||
scanned = 0
|
||||
pruned = 0
|
||||
repaired = 0
|
||||
|
||||
for story in stories:
|
||||
scanned += 1
|
||||
metadata = get_audio_cache_metadata(story.audio_path)
|
||||
|
||||
if not metadata.exists:
|
||||
story.audio_path = None
|
||||
if story.audio_status == StoryAssetStatus.READY.value:
|
||||
sync_story_status(story, audio_status=StoryAssetStatus.NOT_REQUESTED)
|
||||
repaired += 1
|
||||
continue
|
||||
|
||||
if metadata.updated_at and metadata.updated_at < cutoff:
|
||||
delete_audio_cache(story.audio_path)
|
||||
story.audio_path = None
|
||||
sync_story_status(
|
||||
story,
|
||||
audio_status=StoryAssetStatus.NOT_REQUESTED,
|
||||
last_error=None,
|
||||
)
|
||||
pruned += 1
|
||||
|
||||
await db.commit()
|
||||
logger.info(
|
||||
"story_audio_cache_pruned",
|
||||
scanned=scanned,
|
||||
pruned=pruned,
|
||||
repaired=repaired,
|
||||
ttl_days=ttl_days,
|
||||
)
|
||||
return {"scanned": scanned, "pruned": pruned, "repaired": repaired}
|
||||
|
||||
|
||||
async def get_story_achievements(
|
||||
story_id: int,
|
||||
user_id: str,
|
||||
|
||||
Reference in New Issue
Block a user