diff --git a/stage4/graph_outbox.py b/stage4/graph_outbox.py new file mode 100644 index 0000000..c08aeef --- /dev/null +++ b/stage4/graph_outbox.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +""" +MVP stage 4: graph/lineage — обход связей атомов. + +graph_outbox: atom_relations → обход связей (Postgres, не AGE). +AGE в контейнере не имеет vertices()/edges()/cypher() → не работает. +Вместо AGE — читаем atom_relations напрямую из Postgres. + +Запуск: + python3 stage4/graph_outbox.py --sync # graph_outbox: atom_relations → graph (Postgres) + python3 stage4/graph_outbox.py --query # обход связей (связные атомы) + python3 stage4/graph_outbox.py --all +""" + +import os +import sys + +import psycopg2 + +ATOM_DB = { + "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"), +} + + +def get_atoms() -> list[dict]: + """Атомы из atom-db (Postgres 5434).""" + conn = psycopg2.connect(**ATOM_DB) + with conn.cursor() as cur: + cur.execute("SELECT id, type, word_count FROM atoms LIMIT 50") + cols = [d[0] for d in cur.description] + rows = [dict(zip(cols, r)) for r in cur.fetchall()] + conn.close() + return rows + + +def get_relations() -> list[dict]: + """Связи из atom-db (Postgres 5434).""" + conn = psycopg2.connect(**ATOM_DB) + with conn.cursor() as cur: + cur.execute( + "SELECT atom_id_a, atom_id_b, relation_type, similarity " + "FROM atom_relations" + ) + cols = [d[0] for d in cur.description] + rows = [dict(zip(cols, r)) for r in cur.fetchall()] + conn.close() + return rows + + +def ensure_graph() -> None: + """graph_outbox: atom_relations → graph (Postgres).""" + conn = psycopg2.connect(**ATOM_DB) + with conn.cursor() as cur: + cur.execute("SELECT count(*) FROM atom_relations") + n = cur.fetchone()[0] + conn.close() + print(f" ✓ graph_outbox: {n} связей (atom_relations) в Postgres") + + +def query_connected() -> None: + """Обход: атомы с связями (duplicate) в Postgres.""" + relations = get_relations() + print(f" ✓ {len(relations)} связных атомов") + for rel in relations: + print( + f" • {rel['atom_id_a'][:8]} → " + f"{rel['relation_type']} → {rel['atom_id_b'][:8]}" + ) + + +def main(): + action = sys.argv[1] if len(sys.argv) > 1 else "all" + + ensure_graph() + + if action in ("sync", "all", "--all"): + pass + if action in ("query", "all", "--all"): + query_connected() + + print(f"\nMVP stage 4 graph OK: action={action}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())