Rates each source on three axes the user asked for: - freshness: how recently/often new content arrives (newest age + 7d volume) - richness: metadata coverage (thumbnail/tags/performers/description/studio/duration) - plays: does it actually play — from real playback telemetry when available, else a proxy from the resolve mechanism. 0★ = offline (gates the overall stars, so a fresh+rich source that doesn't play still ranks bottom — the hqfap/4k69 case) Backend: - playback_events: fire-and-forget telemetry POST from the app per playback attempt (origin + success/error + time-to-first-frame), append-only, 30d retention - source_stats: per-origin computed scores, refreshed by a scheduler job (6h); /sources joins it and sorts by stars - models + local migration 0025; new GOON_SCHED_SOURCE_STATS_HOURS setting Mobile: - Sites rows show ★ rating; tap the stars for a breakdown (axes + metadata %, plus whether "plays" is measured or estimated) - PlayerScreen reports playback success/failure per source (native path only — symmetric, conservative); origin threaded through Scene/Movie play callsites Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
66 lines
2.8 KiB
Python
66 lines
2.8 KiB
Python
"""source ranking: playback telemetry + per-origin source_stats
|
|
|
|
Revision ID: 0025_source_ranking
|
|
Revises: 0024_saved_searches
|
|
Create Date: 2026-06-22
|
|
|
|
Ranking stron-źródeł na Sites screen (user request): ocena 0-5★ per origin wg
|
|
częstotliwości odświeżania, bogactwa metadanych i tego czy źródło realnie gra.
|
|
- playback_events: fire-and-forget telemetria odtwarzania z apki (sygnał health),
|
|
- source_stats: policzona offline ocena per origin (run_source_stats).
|
|
"""
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
from sqlalchemy.dialects import postgresql
|
|
|
|
revision: str = "0025_source_ranking"
|
|
down_revision: str | None = "0024_saved_searches"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"playback_events",
|
|
sa.Column("id", postgresql.UUID(as_uuid=True), nullable=False),
|
|
sa.Column("origin", sa.String(length=64), nullable=False),
|
|
sa.Column("scene_id", postgresql.UUID(as_uuid=True), nullable=True),
|
|
sa.Column("status", sa.String(length=16), nullable=False),
|
|
sa.Column("error_kind", sa.String(length=64), nullable=True),
|
|
sa.Column("ttff_ms", sa.Integer(), nullable=True),
|
|
sa.Column("device_id", sa.String(length=64), nullable=True),
|
|
sa.Column(
|
|
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
sa.PrimaryKeyConstraint("id", name="pk_playback_events"),
|
|
)
|
|
op.create_index(
|
|
"ix_playback_events_origin_created", "playback_events", ["origin", "created_at"]
|
|
)
|
|
|
|
op.create_table(
|
|
"source_stats",
|
|
sa.Column("origin", sa.String(length=64), nullable=False),
|
|
sa.Column("stars", sa.SmallInteger(), nullable=False, server_default="0"),
|
|
sa.Column("freshness", sa.SmallInteger(), nullable=False, server_default="0"),
|
|
sa.Column("richness", sa.SmallInteger(), nullable=False, server_default="0"),
|
|
sa.Column("health", sa.SmallInteger(), nullable=True),
|
|
sa.Column("scenes", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("new_7d", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("newest_at", sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column(
|
|
"components", postgresql.JSONB(), nullable=False, server_default="{}"
|
|
),
|
|
sa.Column(
|
|
"computed_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
|
),
|
|
sa.PrimaryKeyConstraint("origin", name="pk_source_stats"),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_table("source_stats")
|
|
op.drop_index("ix_playback_events_origin_created", table_name="playback_events")
|
|
op.drop_table("playback_events")
|