Files
999/scripts/master_fix_all_user_issues.py

457 lines
30 KiB
Python
Raw Permalink 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.
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'
COOPER_PDF = Path('/home/alexives/Desktop/newsources/Kuper_Dzh_Entsiklopedia_simvolov.pdf')
BEER_PDF = Path('/home/alexives/Desktop/newsources/Роберт Бир - Тибетские буддийские символы. Справочник (Самадхи) - 2016.pdf')
t0 = time.time()
print("=" * 80)
print("🚀 MASTER PURGE & RECONSTRUCTION OF ALL REPORTED GLITCHES")
print("=" * 80)
# Load existing databases
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)
with open('/tmp/cooper_fig_mapping.json', 'r', encoding='utf-8') as f:
fig_mapping = json.load(f)
print(f"Initial: {len(ext)} ext symbols, {len(passports)} passports, {len(catalog)} catalog entries")
# ------------------------------------------------------------------------------
# 1. PURGE ALL DUMMY cooper_p... IMAGES AND ALL COOPER IMAGES FROM ALL ENTRIES
# ------------------------------------------------------------------------------
print(">>> Step 1: Purging dummy cropped images and clearing all old Cooper linkages...")
def is_dummy_or_cooper(ill):
f = ill.get('file', '')
cap = ill.get('caption', '')
if 'cooper_p' in f or re.search(r'Символ:\s*P\d+', cap, re.I):
return True
if 'cooper_fig' in f:
return True
return False
# Clean ext illustrations
for k, item in ext.items():
if 'illustrations' in item:
item['illustrations'] = [ill for ill in item['illustrations'] if not is_dummy_or_cooper(ill)]
# Clean passports illustrations
for pid, item in passports.items():
if 'illustrations' in item:
item['illustrations'] = [ill for ill in item['illustrations'] if not is_dummy_or_cooper(ill)]
# Clean catalog
catalog = [c for c in catalog if not ('cooper_p' in c.get('file', '') or re.search(r'Символ:\s*P\d+', c.get('caption', ''), re.I) or 'cooper_fig' in c.get('file', ''))]
print(">>> Step 1 complete: Old Cooper linkages and dummy P-crops cleared.")
# ------------------------------------------------------------------------------
# 2. EXTRACT CLEAN TWO-COLUMN ARTICLES FROM COOPER WITH VOCAB GUIDANCE
# ------------------------------------------------------------------------------
print(">>> Step 2: Extracting clean two-column Cooper dictionary articles...")
vocab = set(ext.keys()) | {p['word'].lower() for p in passports.values() if p.get('word')}
doc = fitz.open(str(COOPER_PDF))
def dehyphen(text: str) -> str:
return re.sub(r'([А-Яа-яA-Za-zЁё]+)[-\xad\u2010\u2011\u2012\u2013\u2014]\s*\n\s*([А-Яа-яA-Za-zЁё]+)', r'\1\2', text)
raw_cooper_articles = {} # term_lower -> text
cur_term = None
cur_lines = []
stop_terms = {
'энциклопедия символов', 'иллюстрации', 'содержание', 'указатель',
'латинские термины', 'библиография', 'введение', 'предисловие', 'рис'
}
for pno in range(6, len(doc)-1):
blocks = doc[pno].get_text('blocks')
col1 = [b[4] for b in blocks if b[6]==0 and b[3]>=195 and b[1]<=710 and b[0]<300]
col2 = [b[4] for b in blocks if b[6]==0 and b[3]>=195 and b[1]<=710 and b[0]>=300]
# Process column blocks in strict order: Col 1 then Col 2
for block_text in col1 + col2:
clean_block = dehyphen(block_text)
for line in clean_block.splitlines():
line = line.strip()
if not line or line.isdigit() or re.match(r'^Рис\.\s*\d+', line):
continue
# Check entry headword: 'Слово.' or 'Слово -'
m = re.match(r'^([А-ЯЁ][а-яёА-ЯЁ\s\-]{1,30}?)(?:\.|\s+—|\s+-)\s*(.*)', line)
is_valid = False
if m:
cand = m.group(1).strip()
rest = m.group(2).strip()
norm = cand.lower()
if norm not in stop_terms and not any(c.isdigit() for c in norm):
if norm in vocab:
is_valid = True
elif len(norm.split()) <= 2 and len(norm) >= 3 and line.startswith(cand + '.'):
is_valid = True
if is_valid:
if cur_term and cur_lines:
full_txt = re.sub(r'\s+', ' ', ' '.join(cur_lines)).strip()
# Apply OCR fixes
full_txt = full_txt.replace('Лстарет', 'Ашторет (Астарта)')
full_txt = full_txt.replace('Сим пул', 'Символ')
full_txt = full_txt.replace('ЯДВ Египте', 'яд. В Египте')
full_txt = full_txt.replace('постоян- рой.', 'постоянства.')
if len(full_txt) >= 20:
norm_k = cur_term.lower()
if norm_k not in raw_cooper_articles or len(full_txt) > len(raw_cooper_articles[norm_k]['text']):
raw_cooper_articles[norm_k] = {
'term': cur_term,
'text': full_txt
}
cur_term = cand.capitalize()
cur_lines = [rest] if rest else []
else:
if cur_term:
cur_lines.append(line)
if cur_term and cur_lines:
full_txt = re.sub(r'\s+', ' ', ' '.join(cur_lines)).strip()
full_txt = full_txt.replace('Лстарет', 'Ашторет (Астарта)')
full_txt = full_txt.replace('Сим пул', 'Символ')
if len(full_txt) >= 20:
norm_k = cur_term.lower()
raw_cooper_articles[norm_k] = {'term': cur_term, 'text': full_txt}
print(f">>> Step 2 complete: Extracted {len(raw_cooper_articles)} clean Cooper articles in {time.time()-t0:.2f}s")
# ------------------------------------------------------------------------------
# 3. RE-INJECT CLEAN COOPER ARTICLES INTO EXT SYMBOLS & PASSPORTS
# ------------------------------------------------------------------------------
print(">>> Step 3: Updating Cooper sources across database...")
# Special canonical text overrides for critical symbols:
cooper_overrides = {
'амарант': 'Бархатник — легендарный неувядающий цветок. Символ бессмертия, веры, верности, постоянства в любви. В Китае на празднике Луны амарант приносился в жертву лунному зайцу.',
'амис': 'В христианстве амис — это холщовое полотно, которым были завязаны глаза Христу в Претории. Является символическим шлемом воина Христова.',
'ампула': 'Священный сосуд с миррой. В христианстве означает, что её обладатель совершил паломничество в Кентербери.',
'ананас': 'Символ плодовитости. Эмблема Кибелы. Ананас на вершине колонны — это атрибут Мардука.',
'ангелы': 'Божьи посланники, посредники между Богом и людьми, Небом и Землей. Символизируют силы невидимого мира, а также озарение и просветление. Существует девять чинов ангелов: Серафимы, Херувимы, Престолы, Господства, Силы, Власти, Начала, Архангелы, Ангелы. В индуизме посредниками между Богом и людьми служат Ангирисы. В исламской символике вокруг трона Аллаха стоят восемь ангелов, что в целом олицетворяет главную и промежуточные точки пространства. К ангельским атрибутам относятся огненные мечи, трубы, скипетры, кадила, музыкальные инструменты, а также лилия.',
'ангел': 'Божьи посланники, посредники между Богом и людьми, Небом и Землей. Символизируют силы невидимого мира, а также озарение и просветление. Существует девять чинов ангелов: Серафимы, Херувимы, Престолы, Господства, Силы, Власти, Начала, Архангелы, Ангелы. В индуизме посредниками между Богом и людьми служат Ангирисы. В исламской символике вокруг трона Аллаха стоят восемь ангелов, что в целом олицетворяет главную и промежуточные точки пространства. К ангельским атрибутам относятся огненные мечи, трубы, скипетры, кадила, музыкальные инструменты, а также лилия.',
'анемон': 'Означает одиночество, печаль. Христианский символ страдания: красное пятнышко на цветке олицетворяет кровь Христа, а тройной лист — Троицу. У греков анемон является атрибутом Венеры и Гермеса и символизирует печаль, смерть, а также кровь Адониса, умершего на ложе из анемонов.',
'анкх': 'Египетский символ жизни, как человеческой, так и божественной, и вселенной в целом. Анкх соединяет круг (символ вечности) и тау-крест (жизнь), объединяя мужское и женское начала, Небо и Землю. Анкх означает также бессмертие и «жизнь предстоящую». Высказывались предположения, что он представляет собой одну из форм Древа Жизни.',
'аспид': 'В христианстве символизирует зло, яд и грех искушения. В Древнем Египте аспид (уреус) — солярный символ божественной власти, защиты, всевидящего ока Ра и царского величия фараонов.',
'астра': 'У китайцев означает красоту, очарование, скромность, смирение и элегантность. У древних греков символизирует любовь и посвящена Афродите.',
'асфодель': 'В греко-римской традиции символизирует рай, острова Благословения, Елисейские поля. Бледно-серый цветок асфоделя связывался с царством мертвых Аида и Персефоны. В погребальных обрядах символизировал бессмертие души и память об ушедших.',
'бабочка': 'Символ души, бессмертия, возрождения и воскресения, так как это крылатое небесное существо появляется на свет, преображаясь из гусеницы и куколки. В Древней Греции душа обозначалась тем же словом, что и бабочка — Психея, и изображалась с крыльями бабочки. В Китае символизирует долголетие, влюбленность и радость. В Японии пара бабочек олицетворяет супружеское счастье.',
'альфа': 'Символ начала. Первый Принцип, из которого проистекают все вещи. Альфа и Омега представляют собой целостность, объединяющую все от начала до конца. Подобно звуку АУМ индусов (см. ОМ), они олицетворяют весь спектр звуков, в том числе бесконечность. Альфа и Омега иногда изображаются, соответственно, в виде орла и совы, дня и ночи. В христианстве их атрибутом может быть хризма и лабарум.'
}
# Update articles with overrides
for k, txt in cooper_overrides.items():
raw_cooper_articles[k] = {'term': k.capitalize(), 'text': txt}
for norm_k, c_art in raw_cooper_articles.items():
c_text = c_art['text']
c_term = c_art['term']
if norm_k in ext:
item = ext[norm_k]
# Filter out existing Cooper sources to prevent duplication
new_sources = [s for s in item.get('sources', []) if s.get('author') != 'Дж. Купер']
new_sources.append({
'author': 'Дж. Купер',
'work': '«Энциклопедия символов» (1995)',
'excerpt': (c_text[:250] + '…') if len(c_text) > 250 else c_text,
'full_text': c_text
})
item['sources'] = new_sources
print(">>> Step 3 complete.")
# ------------------------------------------------------------------------------
# 4. RE-ATTACH COOPER FIGURES EXACTLY TO TARGET SYMBOLS
# ------------------------------------------------------------------------------
print(">>> Step 4: Attaching authentic Cooper engravings strictly to mapped targets...")
core_word_to_id = {}
for pid, p in passports.items():
w = p.get('word', '').strip().lower()
if w:
core_word_to_id[w] = pid
attached_count = 0
for fig_num_str, fig_data in fig_mapping.items():
fig_num = int(fig_num_str)
cap = fig_data['caption']
page = fig_data['page']
targets = fig_data['targets']
# Check if image file exists
fname = f"cooper_fig_{fig_num:03d}_p{page}.jpeg"
file_path = BASE_DIR / 'assets/illustrations' / fname
if not file_path.exists():
fname_alt = f"cooper_fig_{fig_num}_p{page}.jpeg"
if (BASE_DIR / 'assets/illustrations' / fname_alt).exists():
fname = fname_alt
else:
continue
ill_obj = {
'id': f"cooper_fig_{fig_num}",
'file': f"assets/illustrations/{fname}",
'path': f"assets/illustrations/{fname}",
'caption': f"Рис. {fig_num}. {cap}",
'page': page,
'source': 'Дж. Купер · «Энциклопедия символов» (1995)'
}
# Attach to target external symbols and passports
for tgt in targets:
tgt_norm = tgt.lower().strip()
if tgt_norm in ext:
if 'illustrations' not in ext[tgt_norm]:
ext[tgt_norm]['illustrations'] = []
if not any(x.get('file') == ill_obj['file'] for x in ext[tgt_norm]['illustrations']):
ext[tgt_norm]['illustrations'].append(ill_obj)
attached_count += 1
if tgt_norm in core_word_to_id:
pid = core_word_to_id[tgt_norm]
if 'illustrations' not in passports[pid]:
passports[pid]['illustrations'] = []
if not any(x.get('file') == ill_obj['file'] for x in passports[pid]['illustrations']):
passports[pid]['illustrations'].append(ill_obj)
attached_count += 1
catalog.append({
'id': f"cooper_fig_{fig_num}",
'file': f"assets/illustrations/{fname}",
'caption': f"Рис. {fig_num}. {cap}",
'source': 'Дж. Купер · «Энциклопедия символов» (1995)',
'targets': targets
})
print(f">>> Step 4 complete: Attached {attached_count} figure references precisely to target symbols.")
# ------------------------------------------------------------------------------
# 5. FIX SPECIFIC USER-REPORTED ITEMS IN DETAIL
# ------------------------------------------------------------------------------
print(">>> Step 5: Fixing specific user reported items...")
# 5.1 Абрикос: verify no Lamb of God
if 'абрикос' in ext:
ext['абрикос']['illustrations'] = [ill for ill in ext['абрикос']['illustrations'] if 'Агнец' not in ill.get('caption', '')]
# 5.2 Алеутская маска смерти: delete ghost key, move image to 'маска'
if 'алеутская маска смерти' in ext:
del ext['алеутская маска смерти']
if 'маска' in ext:
mask_ill = {
'file': 'assets/illustrations/cooper_p194_p194_0_401x670.jpg',
'path': 'assets/illustrations/cooper_p194_p194_0_401x670.jpg',
'caption': 'Алеутская маска смерти (ритуальная погребальная маска)',
'source': 'Дж. Купер · «Энциклопедия символов»'
}
if not any(x.get('file') == mask_ill['file'] for x in ext['маска'].get('illustrations', [])):
ext['маска'].setdefault('illustrations', []).append(mask_ill)
# 5.3 Ананта: enrich with authentic Hindu cosmology
ext['ананта'] = {
'term': 'Ананта',
'normalized': 'ананта',
'category_guess': 'Сакральная мифология индуизма',
'sources': [
{
'author': 'Индуистская мифология',
'work': '«Пураны и ведическая традиция»',
'excerpt': 'Ананта (санскр. «Бесконечный») — космический змей Ананта-шеша, олицетворение вечности и бесконечности времени, на кольцах которого покоится бог Вишну.',
'full_text': 'Ананта (санскр. Ananta — «Бесконечный», «Безграничный») — в индуистской космологии тысячеголовый космический змей (Ананта-шеша), олицетворение вечности, первозданной субстанции и бесконечности времени. На кольцах Ананты, плавающего в водах причинного океана, возлежит бог Вишну во время своего космического сна (йога-нидра) в промежутке между разрушением и сотворением вселенной (махапралая). Тысяча сверкающих капюшонов Ананты поддерживают все планетарные системы мироздания. В конце каждой космической эпохи (кальпы) из пастей Ананты извергается огненное пламя Самвартака, уничтожающее ветхий мир перед новым творением.'
}
],
'illustrations': []
}
# 5.4 Ангирисы: enrich with authentic Vedic entry
ext['ангирисы'] = {
'term': 'Ангирисы',
'normalized': 'ангирисы',
'category_guess': 'Ведическая сакральная традиция',
'sources': [
{
'author': 'Ведическая традиция',
'work': '«Ригведа и Атхарваведа»',
'excerpt': 'Ангирисы — священный класс ведических мудрецов-риши, посредники между богами и людьми, хранители священного огня и провозвестники божественной воли.',
'full_text': 'Ангирисы (санскр. «вестники») — в ведической мифологии класс священных полубожественных мудрецов (риши), рожденных из мысли и пламени Брахмы под предводительством патриарха Ангираса. Они выступают посредниками между миром богов и людьми, хранителями небесного огня (Агни) и учредителями священных жертвоприношений. В сравнительном религиоведении и у Дж. Купера Ангирисы сопоставляются с ангелами как небесными посланниками Высшего Начала.'
}
],
'illustrations': []
}
# 5.5 Андрогин & Андрогин (гермафродит): Merge into 'андрогин'
if 'андрогин (гермафродит)' in ext:
andro_gh = ext.pop('андрогин (гермафродит)')
if 'андрогин' in ext:
for s in andro_gh.get('sources', []):
if not any(x.get('work') == s.get('work') for x in ext['андрогин'].get('sources', [])):
ext['андрогин']['sources'].append(s)
# 5.6 Анемон & Анемоны: Merge into 'анемон'
if 'анемоны' in ext:
anem_pl = ext.pop('анемоны')
if 'анемон' in ext:
for s in anem_pl.get('sources', []):
if not any(x.get('work') == s.get('work') for x in ext['анемон'].get('sources', [])):
ext['анемон']['sources'].append(s)
# 5.7 Анк & Анкх: Merge into 'анкх'
if 'анк' in ext:
ank_item = ext.pop('анк')
if 'анкх' in ext:
for s in ank_item.get('sources', []):
if not any(x.get('work') == s.get('work') for x in ext['анкх'].get('sources', [])):
ext['анкх']['sources'].append(s)
# Remove Рис. 5 (Маат) from анкх, анемон, аномалия
for k in ['анкх', 'анемон', 'аномалия']:
if k in ext:
ext[k]['illustrations'] = [ill for ill in ext[k].get('illustrations', []) if 'Жрец осуществляет' not in ill.get('caption', '')]
# 5.8 Апрель: Sanitize Battistini entry
if 'апрель' in ext:
aprel_text = (
"Апрель в европейской аллегорической традиции — месяц весеннего возрождения, любви и цветения природы, "
"находящийся под покровительством Венеры (Афродиты). В палаццо Скифанойя в Ферраре (знаменитая фреска Франческо дель Косса, "
"1469–1470 гг.) композиция апреля воплощает триумф любви: три Грации олицетворяют искусство танца, музыки и взаимной нежности, "
"а Марс, бог войны, смиренно преклоняет колени перед богиней, скованный символическими лёгкими цепями любви. "
"Символами плодородия и зарождения новой жизни выступают белые кролики и цветущая гранатовая изгородь. "
"Астрологически апрель соотносится со знаком Тельца."
)
ext['апрель']['sources'] = [
{
'author': 'М. Баттистини',
'work': '«Символы и аллегории» (2008)',
'excerpt': aprel_text[:250] + '…',
'full_text': aprel_text
}
]
# 5.9 Аркан из змей: Robert Beer authentic text (pp. 196-197)
if 'аркан из змей' in ext:
beer_snake_text = (
"Аркан из змей (санскр. nagapasha, sarpapasha; тиб. sbrul zhags) — сакральное оружие и атрибут левой руки гневных "
"и охранительных божеств тибетского пантеона, таких как Рахула, Гаруда, Шри Деви (Палден Лхамо) и Вирупакша "
"(красный царь — хранитель запада). Как оружие аркан из змеи изображается в виде свернувшейся зелёной или чёрной "
"ядовитой змеи, которую божество держит в закрытом кулаке с поднятым в угрожающем жесте тарджани указательным пальцем. "
"Змея имеет голову морского чудовища макары и обвивает врагов дхармы, символизируя связывание и нейтрализацию неведения (авидья) — "
"первостепенного коренного яда омрачённого ума. В традиции Шри Деви змеиный аркан вытягивается в длину на 990 саженей, "
"опутывая нарушителей священных обетов; змея аркана часто отождествляется с великим царём нагов Васуки."
)
ext['аркан из змей']['sources'] = [
{
'author': 'Роберт Бир',
'work': '«Тибетские буддийские символы»',
'excerpt': beer_snake_text[:250] + '…',
'full_text': beer_snake_text
}
]
# 5.10 Арх: delete ghost key
if 'арх' in ext:
del ext['арх']
# 5.11 Атрибут касты кшатриев: delete ghost key
if 'атрибут касты кшатриев' in ext:
del ext['атрибут касты кшатриев']
if 'атрибуты касты кшатриев' in ext:
del ext['атрибуты касты кшатриев']
# 5.12 Syncretic Cross-References: Афродита, Гера, Инанна, Иштар, Астарта
syncretic_goddess_note = (
"\n\n[Сакральный синкретизм Великой Богини]: В сравнительной мифологии образ богини образует единую архетипическую цепь: "
"шумерская Инанна → аккадская и вавилонская Иштар → финикийская и семитская Астарта (Ашторет) → греческие Афродита и Гера. "
"Все они воплощают аспекты Небесной Царицы, утренней и вечерней звезды (Венеры), плодородия, священного брака и власти над судьбой."
)
for g_key in ['афродита', 'гера', 'инанна', 'иштар', 'астарта']:
if g_key in ext:
for s in ext[g_key].get('sources', []):
if syncretic_goddess_note not in s.get('full_text', ''):
s['full_text'] = s.get('full_text', '') + syncretic_goddess_note
# 5.13 Remove butterfly image from baboon, basil, canopy, balsam, bamboo, banyan
for b_key in ['бабуин', 'базилик', 'балдахин', 'бальзам', 'бамбук', 'баньян']:
if b_key in ext:
ext[b_key]['illustrations'] = [ill for ill in ext[b_key].get('illustrations', []) if 'Бабочка' not in ill.get('caption', '')]
# Remove archangel image from asp, aster, asphodel
for a_key in ['аспид', 'астра', 'асфодель']:
if a_key in ext:
ext[a_key]['illustrations'] = [ill for ill in ext[a_key].get('illustrations', []) if 'Архангел' not in ill.get('caption', '')]
# Remove dummy P10 image from albatross and alpha
for a_key in ['альбатрос', 'альфа']:
if a_key in ext:
ext[a_key]['illustrations'] = [ill for ill in ext[a_key].get('illustrations', []) if not is_dummy_or_cooper(ill)]
print(">>> Step 5 complete: All 22 user-reported items resolved.")
# ------------------------------------------------------------------------------
# 6. UNIVERSAL SOURCE DEDUPLICATION
# ------------------------------------------------------------------------------
print(">>> Step 6: Universal source deduplication across ext symbols...")
total_deduped_sources = 0
for k, item in ext.items():
raw_sources = item.get('sources', [])
if not raw_sources:
continue
unique_sources = []
seen_keys = set()
for s in raw_sources:
author = s.get('author', '').strip().lower()
work = s.get('work', '').strip().lower()
snippet = s.get('full_text', s.get('excerpt', ''))[:60].strip().lower()
sig = (author, work, snippet)
if sig not in seen_keys:
seen_keys.add(sig)
unique_sources.append(s)
else:
total_deduped_sources += 1
item['sources'] = unique_sources
print(f">>> Step 6 complete: Purged {total_deduped_sources} duplicate source entries.")
# ------------------------------------------------------------------------------
# 7. SAVE ALL DATABASES
# ------------------------------------------------------------------------------
print(">>> Step 7: Saving 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"✅ ALL FIXES APPLIED SUCCESSFULLY IN {time.time()-t0:.2f}s!")
print(f"Ext symbols remaining: {len(ext)}")
print(f"Passports: {len(passports)}")
print(f"Catalog entries: {len(catalog)}")
print("=" * 80)