FrameLanguageLM / scripts /build_catalog.py
josortmel's picture
Upload folder using huggingface_hub
e6ccde2 verified
Raw
History Blame Contribute Delete
3.64 kB
"""Fase 1.4: ensambla el catalogo final y las secuencias de entrenamiento.
Uso: uv run python scripts/build_catalog.py
Entrada: data/interim/vocab.parquet + data/interim/tmdb.jsonl + data/raw/ml-32m/
Salida: data/catalog.sqlite + data/sequences.parquet
"""
import json
import sqlite3
import sys
from pathlib import Path
import duckdb
ROOT = Path(__file__).resolve().parent.parent
INTERIM = ROOT / "data" / "interim"
RAW = ROOT / "data" / "raw"
MIN_TMDB_COVERAGE = 0.80
vocab = duckdb.sql(
f"SELECT * FROM read_parquet('{(INTERIM / 'vocab.parquet').as_posix()}') ORDER BY numVotes DESC"
).fetchall()
cols = [
"tconst", "titleType", "primaryTitle", "originalTitle", "startYear",
"runtimeMinutes", "genres", "averageRating", "numVotes",
"directors", "cast", "tmdb_id_links",
]
tmdb: dict[str, dict] = {}
tmdb_path = INTERIM / "tmdb.jsonl"
if tmdb_path.exists():
with tmdb_path.open(encoding="utf-8") as f:
for line in f:
if line.strip():
rec = json.loads(line)
if "error" not in rec:
tmdb[rec["tconst"]] = rec
coverage = len([1 for row in vocab if row[0] in tmdb]) / len(vocab)
if coverage < MIN_TMDB_COVERAGE:
sys.exit(
f"cobertura TMDB insuficiente: {coverage:.1%} del vocabulario "
f"(minimo {MIN_TMDB_COVERAGE:.0%}). Lanza scripts/fetch_tmdb.py hasta completar."
)
db_path = ROOT / "data" / "catalog.sqlite"
db_path.unlink(missing_ok=True)
db = sqlite3.connect(db_path)
db.execute("""
CREATE TABLE items (
tconst TEXT PRIMARY KEY,
title_type TEXT NOT NULL,
primary_title TEXT NOT NULL,
original_title TEXT,
start_year INTEGER,
runtime_minutes INTEGER,
genres TEXT,
imdb_rating REAL,
num_votes INTEGER NOT NULL,
directors TEXT,
"cast" TEXT,
tmdb_id INTEGER,
media_type TEXT,
original_language TEXT,
countries TEXT,
budget INTEGER,
keywords TEXT,
poster_path TEXT,
popularity REAL
)
""")
rows = []
for row in vocab:
r = dict(zip(cols, row))
t = tmdb.get(r["tconst"], {})
rows.append((
r["tconst"], r["titleType"], r["primaryTitle"], r["originalTitle"],
r["startYear"], r["runtimeMinutes"], r["genres"], r["averageRating"],
r["numVotes"], r["directors"], r["cast"],
t.get("tmdb_id"), t.get("media_type"), t.get("original_language"),
"|".join(t.get("production_countries") or []) or None,
t.get("budget") or None,
"|".join(t.get("keywords") or []) or None,
t.get("poster_path"), t.get("popularity"),
))
db.executemany(f"INSERT INTO items VALUES ({','.join('?' * 19)})", rows)
db.execute("CREATE INDEX idx_items_title ON items (primary_title)")
db.commit()
db.close()
print(f"catalog.sqlite: {len(rows):,} items (cobertura TMDB {coverage:.1%})")
seq_out = ROOT / "data" / "sequences.parquet"
duckdb.sql(f"""
COPY (
SELECT m.userId, v.tconst, m.rating, m.timestamp
FROM read_csv_auto('{(RAW / "ml-32m" / "ratings.csv").as_posix()}') m
JOIN read_csv_auto('{(RAW / "ml-32m" / "links.csv").as_posix()}') l USING (movieId)
JOIN read_parquet('{(INTERIM / "vocab.parquet").as_posix()}') v
ON v.tconst = printf('tt%07d', CAST(l.imdbId AS BIGINT))
ORDER BY m.userId, m.timestamp
) TO '{seq_out.as_posix()}' (FORMAT PARQUET)
""")
n, users = duckdb.sql(
f"SELECT count(*), count(DISTINCT userId) FROM read_parquet('{seq_out.as_posix()}')"
).fetchone()
print(f"sequences.parquet: {n:,} interacciones de {users:,} usuarios")