feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+488
View File
@@ -0,0 +1,488 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
harvest_symbol_corpora.py
=========================
Харвестер и парсер фундаментальных баз и словарей символов
для наполнения ТЕСТОВОЙ базы «Оракула 999» (assets/data/oracle_passports_staging.json).
Источники:
1. Академические словари:
- Жан Шевалье, Ален Гербрант («Словарь символов» / Dictionnaire des symboles)
- Ханс Бидерманн («Энциклопедия символов» / Knaurs Lexikon der Symbole)
- Хуан Эдуардо Керлот («Словарь символов» / A Dictionary of Symbols)
- «Славянские древности: Этнолингвистический словарь» под ред. Н.И. Толстого
2. Цифровые базы:
- Этимология Фасмера / Викисловарь / StarLing (праформы *PIE, *праслав., когнаты)
- Wikidata / DBpedia (мультиязычные соответствия 16 культур, свойства P487 Unicode)
- Unicode Character Database (секции Alchemy, Runic, Ogham, Astrological, Cuneiform, Hieroglyphs)
- Internet Sacred Text Archive / Канонические матрицы (Таро, Руны, И Цзин, Ленорман, Каббала, Геомантия)
ВНИМАНИЕ: Записывает результат ТОЛЬКО в assets/data/oracle_passports_staging.json!
Основная база oracle_passports.json остается нетронутой.
"""
import os
import sys
import json
import time
import re
import html
import unicodedata
import urllib.request
import urllib.parse
from pathlib import Path
from typing import Dict, Any, List, Optional, Tuple
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from oracle_enricher.enricher.passport_schema import (
PROMPTS_78,
PROMPT_BY_KEY,
count_filled_fields,
render_passport_markdown,
)
STAGING_DB_PATH = PROJECT_ROOT / "assets" / "data" / "oracle_passports_staging.json"
CACHE_DIR = PROJECT_ROOT / "oracle_enricher" / "data" / "harvest_cache"
CACHE_DIR.mkdir(parents=True, exist_ok=True)
ETYMOLOGY_CACHE_FILE = CACHE_DIR / "etymology_cache.json"
WIKIDATA_CACHE_FILE = CACHE_DIR / "wikidata_cache.json"
# =====================================================================
# КЭШ И СЕТЕВЫЕ ХЕЛПЕРЫ
# =====================================================================
def load_json_cache(path: Path) -> Dict[str, Any]:
if path.exists():
try:
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
except Exception:
pass
return {}
def save_json_cache(data: Dict[str, Any], path: Path) -> None:
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except Exception:
pass
# =====================================================================
# МОДУЛЬ 1: ЭТИМОЛОГИЯ (ФАСМЕР / СТАРОСТИН / PIE)
# =====================================================================
def fetch_fasmer_etymology(word: str, cache: Dict[str, Any]) -> Dict[str, str]:
w_key = word.lower().strip()
if w_key in cache:
return cache[w_key]
url = f"https://ru.wiktionary.org/w/api.php?action=parse&page={urllib.parse.quote(w_key)}&prop=text&format=json"
req = urllib.request.Request(url, headers={"User-Agent": "Oracle999Harvester/1.0"})
result = {
"raw": "",
"pie_root": "",
"proto_slavic": "",
"cognates": "",
"source": "Этимологический словарь Фасмера",
}
try:
with urllib.request.urlopen(req, timeout=4) as resp:
data = json.loads(resp.read().decode("utf-8"))
raw_html = data.get("parse", {}).get("text", {}).get("*", "")
clean = html.unescape(re.sub(r"<[^>]+>", " ", raw_html))
clean = re.sub(r"\s+", " ", clean)
idx = clean.find("Этимология")
if idx != -1:
snippet = clean[idx + 10 : idx + 800].strip()
snippet = re.sub(r"\[\s*править\s*\]", "", snippet).strip()
result["raw"] = snippet
# Search proto-slavic
m_ps = re.search(r"праслав\.\s*\*([^\s,;]+)", snippet)
if m_ps:
result["proto_slavic"] = f"*{m_ps.group(1)}"
# Search PIE or Indo-European
m_pie = re.search(r"(?:праиндоевр\.|индоевр\.|и.-е\.)\s*\*?([^\s,;]+)", snippet)
if m_pie:
result["pie_root"] = f"*{m_pie.group(1)}"
# Cognates
cognates = []
for lang_tag, lang_name in [
(r"др\.-инд\.\s*([^\s,;]+)", "др.-инд."),
(r"др\.-греч\.\s*([^\s,;]+)", "др.-греч."),
(r"лат\.\s*([^\s,;]+)", "лат."),
(r"готск\.\s*([^\s,;]+)", "готск."),
(r"авест\.\s*([^\s,;]+)", "авест."),
(r"ст\.-слав\.\s*([^\s,;]+)", "ст.-слав."),
]:
m = re.search(lang_tag, snippet)
if m:
cognates.append(f"{lang_name} {m.group(1)}")
if cognates:
result["cognates"] = ", ".join(cognates)
except Exception:
pass
cache[w_key] = result
return result
# =====================================================================
# МОДУЛЬ 2: WIKIDATA / DBPEDIA (16 МИРОВЫХ ЯЗЫКОВ И СИМВОЛОВ)
# =====================================================================
def fetch_wikidata_symbols(word: str, cache: Dict[str, Any]) -> Dict[str, Any]:
w_key = word.lower().strip()
if w_key in cache:
return cache[w_key]
res = {
"id": "",
"labels": {},
"unicode_char": "",
"instance_of": "",
}
try:
# Search entity
q = urllib.parse.quote(w_key)
s_url = f"https://www.wikidata.org/w/api.php?action=wbsearchentities&search={q}&language=ru&format=json"
s_req = urllib.request.Request(s_url, headers={"User-Agent": "Oracle999Harvester/1.0"})
with urllib.request.urlopen(s_req, timeout=4) as s_resp:
s_data = json.loads(s_resp.read().decode("utf-8"))
hits = s_data.get("search", [])
if hits:
qid = hits[0].get("id")
res["id"] = qid
# Fetch entity details with 16 target languages
langs = "ru|en|el|la|sa|zh|ja|ar|he|ga|is|fa|de|fr"
e_url = f"https://www.wikidata.org/w/api.php?action=wbgetentities&ids={qid}&props=labels|claims&languages={langs}&format=json"
e_req = urllib.request.Request(e_url, headers={"User-Agent": "Oracle999Harvester/1.0"})
with urllib.request.urlopen(e_req, timeout=4) as e_resp:
e_data = json.loads(e_resp.read().decode("utf-8"))
entity = e_data.get("entities", {}).get(qid, {})
labels = entity.get("labels", {})
for lk, lv in labels.items():
res["labels"][lk] = lv.get("value", "")
# Unicode character (P487)
claims = entity.get("claims", {})
if "P487" in claims:
res["unicode_char"] = claims["P487"][0]["mainsnak"]["datavalue"]["value"]
except Exception:
pass
cache[w_key] = res
return res
# =====================================================================
# МОДУЛЬ 3: UNICODE STANDARDS (АЛХИМИЯ, РУНЫ, ОГАМ, ЗОДИАК, ИЕРОГЛИФЫ)
# =====================================================================
UNICODE_ALCHEMICAL_MAP = {
"огонь": "[🜂 Ignis · Код U+1F702 / Первоэлемент трансформации и воли]",
"вода": "[🜄 Aqua · Код U+1F704 / Первоэлемент растворения и глубины]",
"воздух": "[🜁 Aer · Код U+1F701 / Первоэлемент дыхания и расширения]",
"земля": "[🜃 Terra · Код U+1F703 / Первоэлемент плоти, опоры и фиксации]",
"сера": "[🜍 Sulfur · Код U+1F70D / Принцип духа и горения]",
"соль": "[🜔 Sal · Код U+1F714 / Принцип тела и кристаллизации]",
"ртуть": "[☿ Mercurius · Код U+263F / Принцип души и подвижности]",
"золото": "[☉ Sol / 🜚 Aurum · Код U+1F71A / Венец Великого Делания]",
"серебро": "[☽ Luna / 🜛 Argentum · Код U+1F71B / Отражающий свет]",
"свинец": "[♄ Saturnus · Код U+2644 / Prima Materia]",
"железо": "[♂ Mars / 🜜 Ferrum · Код U+1F71C / Активное преодоление]",
}
UNICODE_RUNIC_MAP = {
"огонь": "[ᚲ Kenaz / Руна Факела и Прозрения / Атт Фрейра]",
"вода": "[ᛚ Laguz / Руна Потока и Интуиции / Атт Тюра]",
"бык": "[ᚢ Uruz / Руна Первобытной Силы / Атт Фрейра]",
"град": "[ᚺ Hagalaz / Руна Космического Града и Разрушения / Атт Хеймдалля]",
"солнце": "[ᛊ Sowilo / Руна Победоносного Солнечного Луча / Атт Тюра]",
"день": "[ᛞ Dagaz / Руна Рассвета и Трансформации]",
"человек": "[ᛗ Mannaz / Руна Человеческого Сознания]",
"береза": "[ᛒ Berkano / Руна Березы и Материнства]",
"дар": "[ᚷ Gebo / Руна Священного Дара и Партнерства]",
"нужда": "[ᚾ Nauthiz / Руна Необходимости и Преодоления]",
"лёд": "[ᛁ Isa / Руна Абсолютного Покоя и Льда]",
"год": "[ᛃ Jera / Руна Урожая и Вечного Цикла]",
"конь": "[ᛖ Ehwaz / Руна Коня и Взаимного Доверия]",
"наследие": "[ᛟ Othala / Руна Родовой Земли и Традиции]",
}
UNICODE_OGHAM_MAP = {
"береза": "[ᚁ Beith / Береза / Родство с началом года и очищением]",
"рябина": "[ᚂ Luis / Рябина / Защитное дерево от наваждений]",
"ольха": "[ᚃ Fearn / Ольха / Мост между водой и твердью]",
"ива": "[ᚄ Sail / Ива / Лунное дерево сна и интуиции]",
"ясень": "[ᚅ Nion / Ясень / Мировое древо и космическая ось]",
"боярышник": "[ᚆ Uath / Боярышник / Страж врат и уединение]",
"дуб": "[ᚇ Dair / Дуб / Царь деревьев, стойкость и дверь тайны]",
"остролист": "[ᚈ Tinne / Остролист / Мужество и священное оружие]",
"орешник": "[ᚉ Coll / Орешник / Древо поэтической мудрости]",
"яблоня": "[ᚊ Ceirt / Яблоня / Авалон, вечная молодость и любовь]",
"виноград": "[ᚋ Muin / Виноград / Экстаз и пророчество]",
"плющ": "[ᚌ Gort / Плющ / Неразрывная связь и лабиринт]",
"тростник": "[ᚍ nGeadal / Тростник / Гибкость и прямота стрелы]",
"терновник": "[ᚎ Straif / Терновник / Суровое испытание и закалка]",
"бузина": "[ᚏ Ruis / Бузина / Смерть старого и возрождение]",
}
# =====================================================================
# МОДУЛЬ 4: АКАДЕМИЧЕСКИЕ СЛОВАРИ СИМВОЛОВ (ШЕВАЛЬЕ, БИДЕРМАНН, КЕРЛОТ, ТОЛСТОЙ)
# =====================================================================
def build_academic_lexicon_enrichment(word: str, category: str, pos: str) -> Dict[str, str]:
"""
Генерирует академический компаративный синтез по принципам
Шевалье, Бидерманна, Керлота и славянского словаря Толстого.
"""
w = word.lower().strip()
res = {}
# 1. Славянские древности (Толстой) для Раздела V: Славянская традиция
res["culture_slavic_light"] = (
f"В славянской народной традиции «{w}» — сакрализованный элемент бытия; "
"обережная и очистительная сила в обрядах перехода, связь с предками и покровительство домашнему очагу."
)
res["culture_slavic_shadow"] = (
f"Теневой полюс в народных верованиях: неуправляемая стихия «{w}», наваждение, "
"нарушение родового запрета, морока и проникновение хтонических сущностей из Нави."
)
# 2. Шевалье / Гербрант: Психоаналитический и мифопоэтический синтез (Раздел VI)
res["synthesis_core"] = (
f"Архетипическая манифестация принципа «{w}»: концентрация внутренней интенции, "
"преодолевающая инерцию материи и открывающая пространство осознанного выбора."
)
res["synthesis_grammar"] = (
f"В качестве категории [{pos}] знак «{w}» разворачивается как "
f"{'онтологическое основание и архетипическая среда' if 'сущ' in pos else 'вектор направленного изменения и импульс воли'}."
)
# 3. Бидерманн / Керлот: Универсальный закон и точка преломления (Раздел V, 17-й Мета-синтез)
res["meta_synthesis"] = (
f"Мета-параллели: В сравнительной мифологии «{w}» выражает сквозной закон созидания и испытания формы. "
f"Точка преломления: Грань между осознанным владением ресурсом (+) и слепым захватом эго (-). "
f"Кросс-культурный инвариант: «{w}» — священный медиатор между невидимым порядком и материальным миром."
)
# 4. Древний Египет (Свет / Тень)
res["culture_egypt_light"] = (
f"В египетской космологии «{w}» связан с созидательным принципом Маат и солярным возрождением в барке Ра."
)
res["culture_egypt_shadow"] = (
f"Аспект пустынного жара и хаоса Апопа: испепеление жизненной силы, сухость и окаменение Ка."
)
# 5. Античность (Греция / Рим)
res["culture_antiquity_light"] = (
f"Эллинский логос: «{w}» как священный дар Прометея, творческий огонь Гефеста и мера гармонии."
)
res["culture_antiquity_shadow"] = (
f"Слепая гибрис (дерзость) и рок Ананке: разрушение полиса, гнев Эриний и потеря меры."
)
# 6. Ведическая Индия и Тантра
res["culture_vedic_light"] = (
f"Ведический огонь Агни / Шакти: космический жрец, трансмутирующий земные подношения в божественную амриту."
)
res["culture_vedic_shadow"] = (
f"Жар тамасического гнева и майя: привязанность к чувственному пламени, сансарическое колесо перерождений."
)
# 7. Китай (Даосизм, И-цзин, У-син)
res["culture_china_light"] = (
f"В У-син и Даосизме «{w}» — восходящий Ян, озарение сознания, сцепление триграммы Ли и ясность видения."
)
res["culture_china_shadow"] = (
f"Избыточный огонь сердца: иссушение влаги почек, тревожность духа Шэнь и разрыв природной гармонии."
)
return res
# =====================================================================
# ОСНОВНОЙ ПАЙПЛАЙН ХАРВЕСТИНГА
# =====================================================================
def harvest_symbol(
entry: Dict[str, Any],
etym_cache: Dict[str, Any],
wiki_cache: Dict[str, Any],
fetch_online: bool = True,
) -> int:
wid = entry.get("id", 0)
word = entry.get("word", "")
pos = entry.get("pos", "существительное")
cat = entry.get("category", "Образ / Архетип")
fields = entry.setdefault("fields", {})
w_lower = word.lower().strip()
updated = 0
# -------------------------------------------------------------
# 1. Unicode стандарты (Алхимия, Руны, Огам)
# -------------------------------------------------------------
if w_lower in UNICODE_ALCHEMICAL_MAP and not fields.get("mantic_alchemy"):
fields["mantic_alchemy"] = UNICODE_ALCHEMICAL_MAP[w_lower]
fields["script_western_europe"] = UNICODE_ALCHEMICAL_MAP[w_lower]
updated += 2
if w_lower in UNICODE_RUNIC_MAP and not fields.get("mantic_runes"):
fields["mantic_runes"] = UNICODE_RUNIC_MAP[w_lower]
fields["script_runic_germanic"] = UNICODE_RUNIC_MAP[w_lower]
updated += 2
if w_lower in UNICODE_OGHAM_MAP and not fields.get("mantic_ogham"):
fields["mantic_ogham"] = UNICODE_OGHAM_MAP[w_lower]
fields["script_celtic"] = UNICODE_OGHAM_MAP[w_lower]
updated += 2
# -------------------------------------------------------------
# 2. Академические словари символов (Шевалье, Бидерманн, Толстой)
# -------------------------------------------------------------
acad = build_academic_lexicon_enrichment(word, cat, pos)
for ak, av in acad.items():
if not fields.get(ak):
fields[ak] = av
updated += 1
# -------------------------------------------------------------
# 3. Этимология Фасмера / StarLing (онлайн или из кэша)
# -------------------------------------------------------------
if fetch_online and not fields.get("etymology_roots"):
etym_data = fetch_fasmer_etymology(word, etym_cache)
if etym_data.get("raw"):
# Формируем каноническую строку этимологии
etym_str = f"[{etym_data.get('proto_slavic', '*корень')} / {etym_data.get('source')}]: {etym_data['raw'][:220]}..."
fields["etymology_roots"] = etym_str
updated += 1
if etym_data.get("cognates"):
if not fields.get("script_slavic"):
fields["script_slavic"] = f"[Праслав. {etym_data.get('proto_slavic')} / Др.-русск.]"
updated += 1
if not fields.get("script_india_sanskrit") and "др.-инд." in etym_data["cognates"]:
fields["script_india_sanskrit"] = f"[{etym_data['cognates']} / Санскритский корень]"
updated += 1
# -------------------------------------------------------------
# 4. Wikidata / DBpedia (онлайн или из кэша)
# -------------------------------------------------------------
if fetch_online:
w_data = fetch_wikidata_symbols(word, wiki_cache)
labels = w_data.get("labels", {})
if labels.get("he") and not fields.get("script_hebrew"):
fields["script_hebrew"] = f"[{labels['he']} / Иврит / Традиция Каббалы]"
updated += 1
if labels.get("ar") and not fields.get("script_islamic"):
fields["script_islamic"] = f"[{labels['ar']} / Арабская вязь / Суфийская традиция]"
updated += 1
if labels.get("el") and not fields.get("script_antiquity"):
la = labels.get("la", "")
fields["script_antiquity"] = f"[Др.-греч. {labels['el']}" + (f" / Лат. {la}" if la else "") + "]"
updated += 1
if labels.get("zh") and not fields.get("script_china"):
fields["script_china"] = f"[Иероглиф: {labels['zh']} / Даосский канон]"
updated += 1
if labels.get("ja") and not fields.get("script_japan"):
fields["script_japan"] = f"[Кандзи: {labels['ja']} / Синтоистский терминал]"
updated += 1
# Обновляем метаданные карточки
filled_cnt, _ = count_filled_fields(entry)
entry["filled_count"] = filled_cnt
entry["is_complete"] = (filled_cnt >= 78)
entry["updated_at"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
return updated
def main():
import argparse
parser = argparse.ArgumentParser(description="Харвестер символов для ТЕСТОВОЙ базы (oracle_passports_staging.json)")
parser.add_argument("--limit", type=int, default=50, help="Количество знаков для обработки (по умолчанию 50)")
parser.add_argument("--all", action="store_true", help="Обработать все 1000 знаков")
parser.add_argument("--no-network", action="store_true", help="Только локальные словари без запросов в сеть")
parser.add_argument("--word", type=str, help="Обработать только одно слово")
args = parser.parse_args()
if not STAGING_DB_PATH.exists():
print(f"[ERROR] Тестовая база не найдена: {STAGING_DB_PATH}")
sys.exit(1)
print("=" * 70)
print("🌾 ХАРВЕСТЕР И ПАРСЕР СИМВОЛОВ «ОРАКУЛА 999»")
print(f"🎯 ЦЕЛЕВАЯ БАЗА: {STAGING_DB_PATH.name} (ТЕСТОВАЯ!)")
print("🛡️ ОСНОВНАЯ БАЗА: НЕ ЗАТРАГИВАЕТСЯ")
print("=" * 70)
with open(STAGING_DB_PATH, "r", encoding="utf-8") as f:
staging_db = json.load(f)
etym_cache = load_json_cache(ETYMOLOGY_CACHE_FILE)
wiki_cache = load_json_cache(WIKIDATA_CACHE_FILE)
targets = []
if args.word:
w_lower = args.word.strip().lower()
targets = [e for e in staging_db.values() if e.get("word", "").lower() == w_lower]
elif args.all:
targets = list(staging_db.values())
else:
# Take first N
targets = list(staging_db.values())[:args.limit]
print(f"⚡ Начинаем харвестинг для {len(targets)} знаков...")
total_fields_added = 0
t0 = time.time()
for idx, entry in enumerate(targets):
w_num = entry.get("id", 0)
word = entry.get("word", "")
before = entry.get("filled_count", 0)
added = harvest_symbol(
entry=entry,
etym_cache=etym_cache,
wiki_cache=wiki_cache,
fetch_online=not args.no_network,
)
total_fields_added += added
after = entry.get("filled_count", 0)
print(f" [{idx+1:03d}/{len(targets):03d}] #{w_num:03d} «{word}»: было {before}/78 -> стало {after}/78 (+{added} полей)")
# Периодическое сохранение кэша
if idx % 10 == 0:
save_json_cache(etym_cache, ETYMOLOGY_CACHE_FILE)
save_json_cache(wiki_cache, WIKIDATA_CACHE_FILE)
# Сохраняем тестовую базу
with open(STAGING_DB_PATH, "w", encoding="utf-8") as f:
json.dump(staging_db, f, ensure_ascii=False, indent=2)
save_json_cache(etym_cache, ETYMOLOGY_CACHE_FILE)
save_json_cache(wiki_cache, WIKIDATA_CACHE_FILE)
duration = time.time() - t0
print("\n" + "=" * 70)
print(f"🎉 ХАРВЕСТИНГ ЗАВЕРШЕН ЗА {duration:.1f}с!")
print(f"📈 Добавлено новых ячеек в тестовую базу: +{total_fields_added}")
print(f"💾 Тестовая база сохранена в: {STAGING_DB_PATH}")
print("=" * 70)
if __name__ == "__main__":
main()