Files
anti-plagiarism/scripts/seed_broad_corpus.py
jze9 92e23b5209 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>
2026-08-24 13:53:01 +05:00

135 lines
6.7 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
"""Расширение корпуса вширь — новые дисциплины, которых ещё нет в 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()