feat(ingestion): расширение охвата корпуса — 64 новые дисциплины

Продолжение 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 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-08-24 13:53:01 +05:00
parent ea5c3a962c
commit 92e23b5209

View File

@@ -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()