"""Odtworzenie uczenia feedu Edge zdarzenie po zdarzeniu (bez zagladania w przyszlosc). Dla kazdego wyswietlenia (complete/skip/impression/autoswipe) zapisuje cechy znane W TEJ CHWILI: skladniki obecnej punktacji z wag urzadzenia sprzed zdarzenia oraz liczniki klipu z wczesniejszych zdarzen wszystkich urzadzen. Wynik CSV na stdout. Uzyte do przestrojenia punktacji 2026-10-02 (patrz app/clips_feed.py, score_candidate). Wybor tagow na starcie (onboarding) nie jest zdarzeniem, wiec tu go brakuje. docker compose exec -T worker sh -c "cd /srv && python3 scripts/edge_replay_features.py | gzip -c" > edge.csv.gz Analiza lokalnie: python scripts/edge_tuning_analysis.py edge.csv (pandas + scikit-learn). """ import csv import math import sys from collections import defaultdict from datetime import timedelta from sqlalchemy import text from app import clips_feed as cf from app.db import session_scope with session_scope() as s: ev = s.execute(text(""" select device_id, clip_id, kind, dwell_ms, reason, slot, created_at from clip_feed_events where clip_id is not null order by created_at""")).all() ids = list({e.clip_id for e in ev}) clips = {} for i in range(0, len(ids), 5000): for r in s.execute(text("""select id, creator, likes, published_at, niches, duration_sec, width, height, has_audio, creator_verified from clips where id = any(:ids)"""), {"ids": ids[i:i + 5000]}).all(): clips[r.id] = r tags = defaultdict(list) for i in range(0, len(ids), 5000): for cid, t in s.execute(text("select clip_id, tag from clip_tags where clip_id = any(:ids)"), {"ids": ids[i:i + 5000]}).all(): tags[cid].append(t) n_all = s.execute(text("select count(*) from clips")).scalar() tag_df = dict(s.execute(text("select tag, count(*) from clip_tags group by tag")).all()) idf = {t: math.log((n_all + 1) / (n + 1)) for t, n in tag_df.items()} print(f"zdarzen {len(ev)}, klipow {len(clips)}, tagow {len(idf)}", file=sys.stderr) aff = defaultdict(cf.Affinity) last_day = {} dev_n = defaultdict(int) # liczniki klipu z wczesniejszych zdarzen (wszystkie urzadzenia) c_seen = defaultdict(int) c_comp = defaultdict(int) c_skip = defaultdict(int) c_like = defaultdict(int) liked = set((e.device_id, e.clip_id) for e in ev if e.kind == "like") w = csv.writer(sys.stdout) w.writerow(["dev", "ts", "slot", "kind", "dwell", "liked", "tag_sum", "tag_pos", "tag_n", "niche_sum", "creator_w", "tag_idf_sum", "tag_idf_pos", "pop", "fresh", "dur", "vertical", "audio", "verified", "c_seen", "c_comp", "c_skip", "c_like", "dev_n"]) OUTCOME = {"complete", "skip", "impression", "autoswipe"} for e in ev: c = clips.get(e.clip_id) if c is None: continue a = aff[e.device_id] day = e.created_at.date() prev = last_day.get(e.device_id) if prev is not None and day > prev: for _ in range((day - prev).days): a.tags = cf.decay_weights(a.tags) a.niches = cf.decay_weights(a.niches) a.creators = cf.decay_weights(a.creators) last_day[e.device_id] = day t = tags.get(e.clip_id, []) niches = list(c.niches or []) if e.kind in OUTCOME: ts_vals = [a.tags.get(x, 0.0) for x in t] fresh = int(bool(c.published_at and c.published_at >= e.created_at - timedelta(days=cf.FRESH_DAYS))) vert = int(bool(c.width and c.height and c.height > c.width)) w.writerow([e.device_id[:12], e.created_at.isoformat(), e.slot or "", e.kind, e.dwell_ms or 0, int((e.device_id, e.clip_id) in liked), round(sum(ts_vals), 3), sum(1 for v in ts_vals if v > 0), len(t), round(sum(a.niches.get(n, 0.0) for n in niches), 3), round(a.creators.get(c.creator or "", 0.0), 3), round(sum(a.tags.get(x, 0.0) * idf.get(x, 0.0) for x in t), 3), round(sum(idf.get(x, 0.0) for x in t if a.tags.get(x, 0.0) > 0), 3), round(math.log1p(max(c.likes or 0, 0)), 3), fresh, round(c.duration_sec or 0, 1), vert, int(bool(c.has_audio)), int(bool(c.creator_verified)), c_seen[e.clip_id], c_comp[e.clip_id], c_skip[e.clip_id], c_like[e.clip_id], dev_n[e.device_id]]) c_seen[e.clip_id] += 1 if e.kind == "complete": c_comp[e.clip_id] += 1 elif e.kind == "skip": c_skip[e.clip_id] += 1 dev_n[e.device_id] += 1 elif e.kind == "like": c_like[e.clip_id] += 1 cf.apply_event(a, kind=e.kind, dwell_ms=e.dwell_ms, reason=e.reason, tags=t, niches=niches, creator=c.creator)