From 92e23b52098bb1694097ad051f4e07d7ed5c73b6 Mon Sep 17 00:00:00 2001 From: jze9 Date: Mon, 24 Aug 2026 13:53:01 +0500 Subject: [PATCH] =?UTF-8?q?feat(ingestion):=20=D1=80=D0=B0=D1=81=D1=88?= =?UTF-8?q?=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=85=D0=B2=D0=B0?= =?UTF-8?q?=D1=82=D0=B0=20=D0=BA=D0=BE=D1=80=D0=BF=D1=83=D1=81=D0=B0=20?= =?UTF-8?q?=E2=80=94=2064=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B4=D0=B8?= =?UTF-8?q?=D1=81=D1=86=D0=B8=D0=BF=D0=BB=D0=B8=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Продолжение seed_ru_sources.py: все 30 базовых RU-тем + 26 EN-тем (OpenAlex) + 4 категории arXiv уже отработаны (done). Этот скрипт добавляет НОВОЕ поверх — углубление RU (специализированные подотрасли права/экономики/IT, 29 тем) и расширение EN на области, которых не было (право, лингвистика, история, искусство, науки о Земле и т.д., 20 тем) + 15 новых тем arXiv. Идемпотентно (префиксы ru2:/oa3:/arxiv2: не пересекаются с уже существующими). Co-Authored-By: Claude Opus 4.8 --- scripts/seed_broad_corpus.py | 134 +++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 scripts/seed_broad_corpus.py diff --git a/scripts/seed_broad_corpus.py b/scripts/seed_broad_corpus.py new file mode 100644 index 0000000..778df2f --- /dev/null +++ b/scripts/seed_broad_corpus.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Расширение корпуса вширь — новые дисциплины, которых ещё нет в parse_sources. + +Продолжение scripts/seed_ru_sources.py: та заливка покрыла 30 базовых русских +дисциплин + был уже собран seed-корпус из ~26 английских тем (OpenAlex) и 4 +категорий arXiv. Этот скрипт добавляет НОВЫЕ темы поверх уже сделанного — +углубление RU (специализированные подотрасли права/экономики/IT) и расширение EN +(область права/лингвистики/искусства/наук о Земле и т.д., которых ещё не было), +плюс новые категории arXiv. + +Идемпотентно (по name, с префиксами ru2:/oa3:/arxiv2: — не пересекаются с уже +существующими ru:/corpus:/oa2:/arxiv:). Dry-run по умолчанию. + +Запуск: + python scripts/seed_broad_corpus.py # план + python scripts/seed_broad_corpus.py --apply # записать в БД +""" + +import argparse +import os +from pathlib import Path + +# (name_suffix, source_type, query, lang, limit) +RU_DEEP = [ # углубление: специализированные RU-темы, которых не было в первой волне + "семейное право", "административное право", "уголовный процесс", + "гражданский процесс", "международное право", "предпринимательское право", + "налоговое право", "земельное право", "экологическое право", + "информационная безопасность", "искусственный интеллект", "нейронные сети", + "веб-разработка", "архитектура зданий", "дизайн", "музыкальное искусство", + "физическая культура", "спортивная медицина", "туризм", "логистика", + "инновационный менеджмент", "антикризисное управление", "аудит", + "страхование", "банковское дело", "инвестиции", + "внешнеэкономическая деятельность", "региональная экономика", "демография", +] + +EN_OPENALEX_NEW = [ # области, которых не было в первой EN-волне + "law", "political science", "linguistics", "literature studies", "history", + "philosophy", "anthropology", "geography", "agriculture", + "business management", "finance", "architecture", "astronomy", "geology", + "pharmacology", "nursing", "veterinary science", "art history", + "music theory", "religious studies", +] + +EN_ARXIV_NEW = [ # темы поиска arXiv, не пересекаются с уже сделанными 4 + "natural language processing", "reinforcement learning", "quantum physics", + "particle physics", "condensed matter physics", "number theory", + "statistics methodology", "signal processing", "distributed systems", + "software engineering", "bioinformatics", "econometrics", "optimization", + "graph theory", "information theory", +] + + +def _load_env() -> None: + env = Path(__file__).resolve().parent.parent / ".env" + if not env.exists(): + return + for line in env.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + os.environ.setdefault(key.strip(), val.strip()) + + +def _pg_dsn() -> dict: + return { + "host": os.environ.get("POSTGRES_HOST", "localhost"), + "port": int(os.environ.get("POSTGRES_PORT", "5432")), + "dbname": os.environ.get("POSTGRES_DB", "antiplagiator"), + "user": os.environ.get("POSTGRES_USER", "antiplagiator"), + "password": os.environ.get("POSTGRES_PASSWORD", ""), + } + + +def _build_rows(ru_limit: int, en_limit: int, arxiv_limit: int) -> list[tuple]: + rows = [] + rows += [(f"ru2:{t}", "cyberleninka", t, "ru", ru_limit) for t in RU_DEEP] + rows += [(f"oa3:{t}", "openalex", t, None, en_limit) for t in EN_OPENALEX_NEW] + rows += [(f"arxiv2:{t}", "arxiv", t, None, arxiv_limit) for t in EN_ARXIV_NEW] + return rows + + +def main() -> None: + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--apply", action="store_true", help="реально записать в БД (иначе dry-run)") + ap.add_argument("--ru-limit", type=int, default=500) + ap.add_argument("--en-limit", type=int, default=1000) + ap.add_argument("--arxiv-limit", type=int, default=800) + args = ap.parse_args() + + _load_env() + rows = _build_rows(args.ru_limit, args.en_limit, args.arxiv_limit) + total_potential = sum(r[4] for r in rows) + + print(f"Новых источников: {len(rows)} " + f"(RU углубление {len(RU_DEEP)}, EN OpenAlex {len(EN_OPENALEX_NEW)}, " + f"arXiv {len(EN_ARXIV_NEW)})") + print(f"Потенциальный объём (верхняя граница): ~{total_potential} документов") + + if not args.apply: + print("\n[dry-run] запустите с --apply, чтобы записать в parse_sources.") + for name, stype, q, _lang, lim in rows[:10]: + print(f" {name:32} [{stype:12}] limit={lim} → {q!r}") + print(f" ... и ещё {len(rows) - 10}") + return + + import psycopg2 + + conn = psycopg2.connect(**_pg_dsn()) + conn.autocommit = True + created, skipped, ids = 0, 0, [] + with conn.cursor() as cur: + for name, stype, query, lang, limit in rows: + cur.execute("SELECT id FROM parse_sources WHERE name = %s", (name,)) + if cur.fetchone(): + skipped += 1 + continue + cur.execute( + 'INSERT INTO parse_sources ' + '(source_type, name, query, lang, "limit", enabled, last_status, docs_added) ' + "VALUES (%s, %s, %s, %s, %s, TRUE, 'idle', 0) RETURNING id", + (stype, name, query, lang, limit), + ) + ids.append(cur.fetchone()[0]) + created += 1 + conn.close() + + print(f"\nСоздано: {created}, пропущено (уже были): {skipped}") + print(f"ID новых источников: {ids}") + + +if __name__ == "__main__": + main()