Files
content-atom/stage2/compositions.py
T

128 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
MVP stage 2: compositions — собрать atom_versions под второй канал (Telegram).
Для каждого атома генерирует версию под Telegram (plain text, до 1024 символов),
если atom_versions для этого канала ещё нет.
bge-m3 (dim=1024) — единственная embed-модель (для semantic-версий).
Postgres (atom-db) — atom_versions как источник.
Запуск:
python3 stage2/compositions.py --channel telegram --top 20
python3 stage2/compositions.py --channel telegram --limit 50
"""
import argparse
import json
import os
import sys
import time
import psycopg2
POSTGRES = {
"host": os.environ.get("POSTGRES_HOST", "127.0.0.1"),
"port": int(os.environ.get("POSTGRES_PORT", "5434")),
"user": os.environ.get("POSTGRES_USER", "atom"),
"dbname": os.environ.get("POSTGRES_DB", "content_atom"),
"password": os.environ.get("POSTGRES_PASSWORD", "atompass123"),
}
MAX_LEN = 1024 # Telegram limit
def get_conn():
return psycopg2.connect(**POSTGRES)
def get_atoms(conn, limit: int) -> list[dict]:
"""Атомы для генерации (draft, не занятые)."""
with conn.cursor() as cur:
cur.execute("""
SELECT a.id, a.type, a.plain_text, a.word_count, a.project_id
FROM atoms a
WHERE a.plain_text IS NOT NULL
ORDER BY a.created_at DESC
LIMIT %s
""", (limit,))
cols = [d[0] for d in cur.description]
return [dict(zip(cols, row)) for row in cur.fetchall()]
def has_version(conn, atom_id: str, channel: str) -> bool:
with conn.cursor() as cur:
cur.execute(
"SELECT 1 FROM atom_versions WHERE atom_id=%s AND channel=%s LIMIT 1",
(atom_id, channel),
)
return cur.fetchone() is not None
def build_telegram_version(atom: dict) -> str:
"""Генерация версии под Telegram (plain text)."""
text = atom["plain_text"]
if len(text) > MAX_LEN:
text = text[:MAX_LEN].rstrip() + "…"
# Типизированное оформление
t = atom["type"]
if t in ("stat",):
return f"📊 {text}"
if t in ("qa",):
return f"❓ {text}"
if t in ("cta",):
return f"👉 {text}"
if t in ("hook",):
return f"🔥 {text}"
if t in ("comparison",):
return f"⚖️ {text}"
if t in ("instruction",):
return f"✅ {text}"
if t in ("quote",):
return f'» {text}'
return text
def insert_versions(conn, atom_id: str, content: str) -> bool:
"""Вставить atom_versions под telegram (plain)."""
with conn.cursor() as cur:
cur.execute("""
INSERT INTO atom_versions
(atom_id, channel, format, content, quality_score)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (atom_id, channel, format) DO NOTHING
""", (atom_id, "telegram", "plain", content, 7.0))
committed = cur.rowcount > 0
conn.commit()
return committed
def main():
ap = argparse.ArgumentParser(description="MVP stage 2: compositions (telegram)")
ap.add_argument("--channel", default="telegram", help="канал")
ap.add_argument("--format", default="plain", help="формат")
ap.add_argument("--limit", type=int, default=20, help="сколько атомов")
args = ap.parse_args()
conn = get_conn()
atoms = get_atoms(conn, args.limit)
print(f" • {len(atoms)} атомов для {args.channel}")
n = 0
start = time.time()
for atom in atoms:
if has_version(conn, str(atom["id"]), args.channel):
continue
content = build_telegram_version(atom)
if insert_versions(conn, str(atom["id"]), content):
n += 1
conn.close()
print(f" ✓ atom_versions: {n} за {time.time()-start:.1f}с")
print(f"\nMVP stage 2 compositions OK: {n} версий под {args.channel}")
return 0
if __name__ == "__main__":
sys.exit(main())