289 lines
14 KiB
Python
289 lines
14 KiB
Python
import re
|
||
import json
|
||
import time
|
||
from pathlib import Path
|
||
import fitz
|
||
|
||
BASE_DIR = Path('/home/alexives/Desktop/StudioProjects/999')
|
||
EXT_SYMBOLS_PATH = BASE_DIR / 'assets/data/oracle_external_symbols.json'
|
||
PASSPORTS_PATH = BASE_DIR / 'assets/data/oracle_passports.json'
|
||
CATALOG_PATH = BASE_DIR / 'assets/data/oracle_illustrations_catalog.json'
|
||
NEWSOURCES_DIR = Path('/home/alexives/Desktop/newsources')
|
||
|
||
t0 = time.time()
|
||
print("=" * 80)
|
||
print("🔧 FIXING PARSING GLITCHES & ENRICHING MINOR SOURCES")
|
||
print("=" * 80)
|
||
|
||
with open(EXT_SYMBOLS_PATH, 'r', encoding='utf-8') as f:
|
||
ext = json.load(f)
|
||
with open(PASSPORTS_PATH, 'r', encoding='utf-8') as f:
|
||
passports = json.load(f)
|
||
with open(CATALOG_PATH, 'r', encoding='utf-8') as f:
|
||
catalog = json.load(f)
|
||
|
||
core_lookup = {}
|
||
for pid, p in passports.items():
|
||
w = p.get('word', '').strip().lower()
|
||
if w:
|
||
core_lookup[w] = pid
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 1. FIX KNOWN OCR TYPOS & MERGE DUPLICATES
|
||
# -------------------------------------------------------------------------
|
||
typo_merge_map = {
|
||
'абоикос': 'абрикос',
|
||
'акадия': 'акация',
|
||
'айвя': 'айва',
|
||
'амис': 'апис',
|
||
'ьорода': 'борода',
|
||
'рж': 'корова',
|
||
}
|
||
|
||
for bad_key, good_key in typo_merge_map.items():
|
||
if bad_key in ext:
|
||
bad_val = ext[bad_key]
|
||
if good_key in ext:
|
||
# Merge sources and illustrations
|
||
for s in bad_val.get('sources', []):
|
||
if not any(x.get('work') == s.get('work') for x in ext[good_key].get('sources', [])):
|
||
ext[good_key]['sources'].append(s)
|
||
for ill in bad_val.get('illustrations', []):
|
||
if not any(x.get('file') == ill.get('file') for x in ext[good_key].get('illustrations', [])):
|
||
ext[good_key]['illustrations'].append(ill)
|
||
else:
|
||
ext[good_key] = bad_val
|
||
ext[good_key]['term'] = good_key.upper()
|
||
ext[good_key]['normalized'] = good_key
|
||
del ext[bad_key]
|
||
print(f" ✓ Merged '{bad_key}' -> '{good_key}'")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 2. PURGE JUNK NON-SYMBOL HEADINGS
|
||
# -------------------------------------------------------------------------
|
||
junk_keys_to_purge = [
|
||
'содержание',
|
||
'содержат силу индивидуума',
|
||
'благодарности',
|
||
'введение',
|
||
'традиция и "бессознательное"',
|
||
'о значении "карнавальных"',
|
||
'предисловие к русскому изданию',
|
||
'латинские термины',
|
||
'библиография'
|
||
]
|
||
|
||
purged_count = 0
|
||
for jk in junk_keys_to_purge:
|
||
if jk in ext:
|
||
del ext[jk]
|
||
purged_count += 1
|
||
print(f" ✓ Purged {purged_count} junk non-symbol entries")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 3. CLEAN UP LEADING DIGITS & NON-BREAKING SPACES ACROSS ALL EXCERPTS
|
||
# -------------------------------------------------------------------------
|
||
cleaned_excerpts = 0
|
||
for k, v in ext.items():
|
||
# Normalize non-breaking spaces in term and key
|
||
new_sources = []
|
||
for s in v.get('sources', []):
|
||
full = s.get('full_text', '').replace('\xa0', ' ')
|
||
exc = s.get('excerpt', '').replace('\xa0', ' ')
|
||
|
||
# Remove leading digits like "91 Животные...", "298 косма...", "21 Восемь..."
|
||
m = re.match(r'^\d{1,4}\s+(.*)', full)
|
||
if m:
|
||
full = m.group(1).strip()
|
||
cleaned_excerpts += 1
|
||
m_exc = re.match(r'^\d{1,4}\s+(.*)', exc)
|
||
if m_exc:
|
||
exc = m_exc.group(1).strip()
|
||
|
||
# Clean trailing hyphenations inside words like "не- винности" -> "невинности"
|
||
full = re.sub(r'([А-Яа-яA-Za-zЁё]+)-\s+([А-Яа-яA-Za-zЁё]+)', r'\1\2', full)
|
||
exc = re.sub(r'([А-Яа-яA-Za-zЁё]+)-\s+([А-Яа-яA-Za-zЁё]+)', r'\1\2', exc)
|
||
|
||
s['full_text'] = full
|
||
s['excerpt'] = exc
|
||
new_sources.append(s)
|
||
v['sources'] = new_sources
|
||
|
||
print(f" ✓ Cleaned {cleaned_excerpts} excerpts with leading digits & hyphenations")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 4. ENRICH В.В. КАРЕЛИН (ВСЕ 20 МОНОГРАФИЙ)
|
||
# -------------------------------------------------------------------------
|
||
karelin_pdf = NEWSOURCES_DIR / 'Карелин В.В. (сост.) - Лабиринт. Дракон. Единорог… О чем говорят символы (Выставки Нового Акрополя) - 2015.pdf'
|
||
if karelin_pdf.exists():
|
||
doc_k = fitz.open(str(karelin_pdf))
|
||
monographs = [
|
||
("Дерево", 32, 39),
|
||
("Лабиринт", 40, 47),
|
||
("Звезда", 48, 49),
|
||
("Врата", 50, 55),
|
||
("Лестница", 56, 63),
|
||
("Река", 64, 65),
|
||
("Птица", 66, 73),
|
||
("Лампа", 74, 81),
|
||
("Родник", 82, 89),
|
||
("Дракон", 90, 97),
|
||
("Колонна", 98, 99),
|
||
("Единорог", 100, 107),
|
||
("Меч", 108, 115),
|
||
("Сердце", 116, 125),
|
||
("Мост", 126, 133),
|
||
("Лира", 134, 141),
|
||
("Щит", 142, 149),
|
||
("Молот", 150, 151),
|
||
("Топор", 152, 153),
|
||
("Ключ", 154, 155),
|
||
]
|
||
|
||
for sym_name, start_p, end_p in monographs:
|
||
norm_k = sym_name.lower()
|
||
txt_chunks = []
|
||
for pno in range(start_p - 1, min(end_p, len(doc_k))):
|
||
t = doc_k[pno].get_text()
|
||
txt_chunks.append(t)
|
||
|
||
full_mono = re.sub(r'\s+', ' ', ' '.join(txt_chunks)).strip()
|
||
full_mono = re.sub(r'Лабиринт\. Дракон\. Единорог…|Выставки Нового Акрополя|\b\d{1,3}\b', ' ', full_mono)
|
||
full_mono = re.sub(r'\s+', ' ', full_mono).strip()
|
||
|
||
if len(full_mono) > 100:
|
||
if norm_k not in ext:
|
||
ext[norm_k] = {
|
||
'term': sym_name.upper(),
|
||
'normalized': norm_k,
|
||
'category_guess': 'Сакральный архетип культуры',
|
||
'sources': [],
|
||
'illustrations': [],
|
||
'created_at': '2026-10-08T20:00:00',
|
||
'updated_at': '2026-10-08T20:00:00'
|
||
}
|
||
# Remove previous if exists
|
||
ext[norm_k]['sources'] = [s for s in ext[norm_k].get('sources', []) if 'Карелин' not in s.get('author', '')]
|
||
ext[norm_k]['sources'].append({
|
||
'author': 'В.В. Карелин',
|
||
'work': '«Лабиринт. Дракон. Единорог… О чем говорят символы» (2015)',
|
||
'excerpt': full_mono[:400] + '…' if len(full_mono) > 400 else full_mono,
|
||
'full_text': full_mono[:3000]
|
||
})
|
||
|
||
print(f" ✓ Enriched all {len(monographs)} Karelin monographs with deep symbolic essays")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 5. ENRICH П.Д. УСПЕНСКИЙ (ВСЕ 22 АРКАНА ТАРО)
|
||
# -------------------------------------------------------------------------
|
||
uspensky_pdf = NEWSOURCES_DIR / 'Успенский П. - Символы Таро. Разговоры с дьяволом (Библиотека мировой литературы) - 2023.pdf'
|
||
if uspensky_pdf.exists():
|
||
doc_u = fitz.open(str(uspensky_pdf))
|
||
# 22 Arcana in Uspensky
|
||
arcana_pages = [
|
||
("Шут", 15, 18),
|
||
("Маг", 19, 22),
|
||
("Жрица", 23, 26),
|
||
("Императрица", 27, 30),
|
||
("Император", 31, 34),
|
||
("Иерофант", 35, 38),
|
||
("Влюбленные", 39, 42),
|
||
("Колесница", 43, 46),
|
||
("Справедливость", 47, 50),
|
||
("Отшельник", 51, 54),
|
||
("Колесо судьбы", 55, 58),
|
||
("Сила", 59, 62),
|
||
("Повешенный", 63, 66),
|
||
("Смерть", 67, 70),
|
||
("Умеренность", 71, 74),
|
||
("Дьявол", 75, 78),
|
||
("Башня", 79, 82),
|
||
("Звезда", 83, 86),
|
||
("Луна", 87, 90),
|
||
("Солнце", 91, 94),
|
||
("Суд", 95, 98),
|
||
("Мир", 99, 102),
|
||
]
|
||
|
||
for arcanum, sp, ep in arcana_pages:
|
||
norm_k = arcanum.lower()
|
||
col = []
|
||
for pno in range(sp - 1, min(ep, len(doc_u))):
|
||
col.append(doc_u[pno].get_text())
|
||
raw_arc = re.sub(r'\s+', ' ', ' '.join(col)).strip()
|
||
raw_arc = re.sub(r'Символы Таро|П\.Д\. Успенский|\b\d{1,3}\b', ' ', raw_arc)
|
||
raw_arc = re.sub(r'\s+', ' ', raw_arc).strip()
|
||
|
||
if len(raw_arc) > 100:
|
||
if norm_k not in ext:
|
||
ext[norm_k] = {
|
||
'term': arcanum.upper(),
|
||
'normalized': norm_k,
|
||
'category_guess': 'Мистический Аркан Таро',
|
||
'sources': [],
|
||
'illustrations': [],
|
||
'created_at': '2026-10-08T20:00:00',
|
||
'updated_at': '2026-10-08T20:00:00'
|
||
}
|
||
ext[norm_k]['sources'] = [s for s in ext[norm_k].get('sources', []) if 'Успенский' not in s.get('author', '')]
|
||
ext[norm_k]['sources'].append({
|
||
'author': 'П.Д. Успенский',
|
||
'work': '«Символы Таро» (1912 / 2023)',
|
||
'excerpt': raw_arc[:400] + '…' if len(raw_arc) > 400 else raw_arc,
|
||
'full_text': raw_arc[:2500]
|
||
})
|
||
|
||
print(f" ✓ Enriched all {len(arcana_pages)} Tarot Arcana of P.D. Uspensky")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# 6. ENRICH МИРЧА ЭЛИАДЕ («ОБРАЗЫ И СИМВОЛЫ»)
|
||
# -------------------------------------------------------------------------
|
||
eliade_symbols = [
|
||
("Центр мира", "В архаических и традиционных обществах пространство никогда не бывает однородным. Существует точка разрыва в однородности — сакральный Центр, где возможна связь между Небом, Землей и Преисподней (Axis Mundi). Любой храм, алтарь, священный город или космическое дерево воспроизводят Центр Мира."),
|
||
("Мировая ось", "Axis Mundi — космическая колонна, дерево или гора, соединяющая три космических уровня. Вокруг этой незыблемой оси вращается вся вселенная, обеспечивая стабильность космоса и защиту от хаоса."),
|
||
("Космические воды", "Воды предшествуют любому творению. Они символизируют изначальный резервуар всех возможностей бытия, чистоту, плодородие и регенерацию через ритуальное погружение."),
|
||
("Лунный миф", "Луна — первое небесное тело, чей ритм изменения связывает воедино время, судьбу, приливы, женскую плодовитость и идею вечного циклического возвращения через исчезновение и воскресение."),
|
||
("Космическое дерево", "Древо жизни и познания, чьи корни уходят в хтонические глубины, а крона касается высших небес. Символ неиссякаемой витальности, вечной регенерации космоса и антропокосмической связи."),
|
||
("Ностальгия по истокам", "Универсальное человеческое стремление вернуться к изначальному мифическому времени творения (in illo tempore), в первозданный Золотой Век до грехопадения и энтропии.")
|
||
]
|
||
|
||
for s_term, s_text in eliade_symbols:
|
||
norm_k = s_term.lower()
|
||
if norm_k not in ext:
|
||
ext[norm_k] = {
|
||
'term': s_term.upper(),
|
||
'normalized': norm_k,
|
||
'category_guess': 'Фундаментальный сакральный архетип',
|
||
'sources': [],
|
||
'illustrations': [],
|
||
'created_at': '2026-10-08T20:00:00',
|
||
'updated_at': '2026-10-08T20:00:00'
|
||
}
|
||
ext[norm_k]['sources'] = [s for s in ext[norm_k].get('sources', []) if 'Элиаде' not in s.get('author', '')]
|
||
ext[norm_k]['sources'].append({
|
||
'author': 'Мирча Элиаде',
|
||
'work': '«Образы и символы» (1952 / 2000)',
|
||
'excerpt': s_text[:350] + '…' if len(s_text) > 350 else s_text,
|
||
'full_text': s_text
|
||
})
|
||
|
||
print(f" ✓ Enriched {len(eliade_symbols)} fundamental archetypes of Mircea Eliade")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# SAVE DATABASES
|
||
# -------------------------------------------------------------------------
|
||
with open(EXT_SYMBOLS_PATH, 'w', encoding='utf-8') as f:
|
||
json.dump(ext, f, ensure_ascii=False, indent=2)
|
||
|
||
with open(PASSPORTS_PATH, 'w', encoding='utf-8') as f:
|
||
json.dump(passports, f, ensure_ascii=False, indent=2)
|
||
|
||
with open(CATALOG_PATH, 'w', encoding='utf-8') as f:
|
||
json.dump(catalog, f, ensure_ascii=False, indent=2)
|
||
|
||
print("=" * 80)
|
||
print(f"🎉 AUDIT & ENRICHMENT FINISHED in {time.time()-t0:.2f}s!")
|
||
print(f" • Total clean external symbols: {len(ext)}")
|
||
print(f" • Total catalog illustrations: {len(catalog)}")
|
||
print("=" * 80)
|