319 lines
16 KiB
Python
319 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
import json
|
|
import re
|
|
import os
|
|
|
|
DART_PATH = "lib/data/words_data.dart"
|
|
KB_PATH = "assets/data/oracle_kb.json"
|
|
KB_OUT_PATH = "oracle_enricher/out/oracle_kb.json"
|
|
TRADITIONS_PATH = "assets/data/traditions_canon.json"
|
|
TRADITIONS_ENRICHER_PATH = "oracle_enricher/data/traditions_canon.json"
|
|
HTML_PATH = "lexicon_viewer.html"
|
|
|
|
def load_dart_words():
|
|
with open(DART_PATH, "r", encoding="utf-8") as f:
|
|
content = f.read()
|
|
|
|
pattern = re.compile(
|
|
r'WordItem\s*\(\s*id:\s*(\d+),\s*word:\s*"([^"]+)",\s*partOfSpeech:\s*"([^"]+)",\s*frequency:\s*([\d\.]+),\s*category:\s*"([^"]+)",\s*description:\s*"(.*?)",\s*\)',
|
|
re.DOTALL
|
|
)
|
|
items = {}
|
|
for m in pattern.findall(content):
|
|
wid = int(m[0])
|
|
items[wid] = {
|
|
"id": wid,
|
|
"word": m[1].strip(),
|
|
"pos": m[2].strip(),
|
|
"frequency": float(m[3]),
|
|
"category": m[4].strip(),
|
|
"description": m[5].strip()
|
|
}
|
|
return items
|
|
|
|
def clean_desc(desc):
|
|
clean = re.sub(r'\[.*?\]\s*', '', desc).strip()
|
|
return clean
|
|
|
|
def parse_layers(desc):
|
|
matches = re.findall(r'\[([^\]:]+):\s*([^\]]+)\]', desc)
|
|
clean = clean_desc(desc)
|
|
layers = []
|
|
for sys_name, title in matches:
|
|
layers.append({
|
|
"system": sys_name.strip(),
|
|
"title": title.strip(),
|
|
"law": clean
|
|
})
|
|
return layers
|
|
|
|
def generate_core(clean, word, pos, cat):
|
|
# Take first significant segment
|
|
parts = re.split(r'[,;.]\s*', clean)
|
|
if len(parts) >= 2 and len(parts[0]) < 25:
|
|
core_candidate = f"{parts[0].strip().capitalize()}, {parts[1].strip()}."
|
|
elif len(parts) >= 1:
|
|
core_candidate = f"{parts[0].strip().capitalize()}."
|
|
else:
|
|
core_candidate = f"{clean.capitalize()}."
|
|
return core_candidate
|
|
|
|
def generate_oracle(clean, word, pos, cat):
|
|
c_lower = clean[0].lower() + clean[1:] if len(clean) > 1 else clean.lower()
|
|
# Strip trailing punctuation
|
|
c_clean = c_lower.rstrip('.,;')
|
|
|
|
if pos == 'глагол':
|
|
return (
|
|
f"Импульс знака «{word}» воплощает {c_clean}. "
|
|
f"В раскладе это прямой вектор трансформации: ситуация требует не выжидания, а точного и осознанного действия. "
|
|
f"Знак проверяет, готов ли вопрошающий взять ответственность за последствия своего шага."
|
|
)
|
|
elif pos == 'прилагательное':
|
|
return (
|
|
f"Качество знака «{word}» проявляется как {c_clean}. "
|
|
f"В пространстве оракула оно задает тон и вибрацию всего поля: это не застывшая характеристика, а живой закон восприятия. "
|
|
f"Символ спрашивает, видишь ли ты истинную природу этого свойства или подменяешь её внешней формой."
|
|
)
|
|
elif cat == 'Архетип Человека':
|
|
return (
|
|
f"Архетип «{word}» несет в себе {c_clean}. "
|
|
f"В раскладе он указывает на ключевую роль сознания или явление проводника силы. "
|
|
f"Это зеркало духа, в котором отражается зрелость вопрошающего и его готовность следовать своему призванию."
|
|
)
|
|
elif cat in ['Священный Предмет', 'Сакральный Символ']:
|
|
return (
|
|
f"Сакральный знак «{word}» удерживает {c_clean}. "
|
|
f"В поле гадания он выступает как фокус и орудие преображения: материальная опора для невидимой силы духа. "
|
|
f"Знак напоминает, что форма священна лишь до тех пор, пока сквозь нее сияет изначальный смысл."
|
|
)
|
|
elif cat == 'Космический Знак':
|
|
return (
|
|
f"Космический ритм знака «{word}» открывает {c_clean}. "
|
|
f"В раскладе это высший закон времени и пространства, неподвластный сиюминутным желаниям. "
|
|
f"Символ призывает согласовать личные намерения с великим круговоротом мироздания."
|
|
)
|
|
else:
|
|
return (
|
|
f"Архетип «{word}» утверждает: {c_clean}. "
|
|
f"В пространстве оракула этот образ обнажает глубинную суть происходящего, снимая наслоения иллюзий. "
|
|
f"Он служит ориентиром для поиска равновесия между внутренним миром и внешними вызовами."
|
|
)
|
|
|
|
def generate_shadow(word, pos, cat, clean):
|
|
if pos == 'глагол':
|
|
return "Суета, поспешность, насилие над естественным ритмом или, напротив, паралич воли и бегство от действия."
|
|
elif pos == 'прилагательное':
|
|
return "Крайность проявления, ригидность, слепота к противоположному качеству, искажение меры."
|
|
elif cat == 'Архетип Человека':
|
|
return "Гордыня роли, высокомерие, догматизм, манипуляция или страх перед собственной уязвимостью."
|
|
elif cat in ['Священный Предмет', 'Сакральный Символ']:
|
|
return "Фетишизация внешней оболочки, суеверие, утрата живой духовной связи за слепым следованием обряду."
|
|
elif cat == 'Космический Знак':
|
|
return "Фатализм, ощущение беспомощности перед роком, слепое подчинение внешним циклам."
|
|
else:
|
|
return "Искажение первообраза, пленение иллюзией, страх встречи с подлинной реальностью или бегство в фантазии."
|
|
|
|
def generate_asks(word, pos, cat):
|
|
if pos == 'глагол':
|
|
return f"Какое назревшее действие в теме «{word}» ты откладываешь, и что мешает сделать первый честный шаг?"
|
|
elif pos == 'прилагательное':
|
|
return f"Где в твоей жизни сейчас требуется проявить качество «{word}», а где оно стало маской?"
|
|
elif cat == 'Архетип Человека':
|
|
return f"Какую грань архетипа «{word}» ты отрицаешь в себе или слепо проецируешь на окружающих?"
|
|
else:
|
|
return f"Где в твоей ситуации сейчас говорит суть знака «{word}» — и готов ли ты принять этот урок без прикрас?"
|
|
|
|
def generate_step(word, pos, cat):
|
|
if pos == 'глагол':
|
|
return f"Соверши прямо сегодня одно конкретное действие, прямо воплощающее импульс «{word}»."
|
|
elif pos == 'прилагательное':
|
|
return f"Прояви качество «{word}» в ближайшем бытовом или внутреннем решении."
|
|
elif cat == 'Архетип Человека':
|
|
return f"Взгляни на текущую трудность глазами зрелого архетипа «{word}» и поступи соответственно его достоинству."
|
|
else:
|
|
return f"Выдели несколько минут тишины, чтобы созерцать символ «{word}» и согласовать с ним свои приоритеты."
|
|
|
|
def main():
|
|
print("1. Loading words from Dart...")
|
|
words = load_dart_words()
|
|
print(f"Loaded {len(words)} words.")
|
|
|
|
print("\n2. Updating assets/data/oracle_kb.json...")
|
|
with open(KB_PATH, "r", encoding="utf-8") as f:
|
|
kb = json.load(f)
|
|
|
|
stub_fixed_count = 0
|
|
for wid, it in words.items():
|
|
str_id = str(wid)
|
|
w_name = it["word"]
|
|
clean = clean_desc(it["description"])
|
|
extracted_layers = parse_layers(it["description"])
|
|
|
|
# Check existing record by ID or create
|
|
rec = kb.get(str_id, {})
|
|
if not rec:
|
|
rec = kb.get(w_name, {})
|
|
|
|
is_stub = False
|
|
core = rec.get("core", "")
|
|
oracle = rec.get("oracle", "")
|
|
if (
|
|
not core
|
|
or "функция знака" in core.lower()
|
|
or "образ без прямой карты" in core.lower()
|
|
or not oracle
|
|
or "знак держит функция знака" in oracle.lower()
|
|
):
|
|
is_stub = True
|
|
|
|
if is_stub:
|
|
rec["id"] = wid
|
|
rec["word"] = w_name
|
|
rec["pos"] = it["pos"]
|
|
rec["category"] = it["category"]
|
|
rec["lookup"] = "A"
|
|
rec["has_canon"] = True
|
|
rec["core"] = generate_core(clean, w_name, it["pos"], it["category"])
|
|
rec["oracle"] = generate_oracle(clean, w_name, it["pos"], it["category"])
|
|
rec["shadow"] = generate_shadow(w_name, it["pos"], it["category"], clean)
|
|
rec["asks"] = generate_asks(w_name, it["pos"], it["category"])
|
|
rec["step"] = generate_step(w_name, it["pos"], it["category"])
|
|
if extracted_layers:
|
|
rec["layers"] = extracted_layers
|
|
stub_fixed_count += 1
|
|
else:
|
|
# Even if not stub, ensure layers from dart are merged if missing
|
|
if extracted_layers and not rec.get("layers"):
|
|
rec["layers"] = extracted_layers
|
|
|
|
# Ensure both keys exist in kb and match
|
|
kb[str_id] = rec
|
|
kb[w_name] = rec
|
|
|
|
with open(KB_PATH, "w", encoding="utf-8") as f:
|
|
json.dump(kb, f, ensure_ascii=False, indent=2)
|
|
print(f"Fixed {stub_fixed_count} stub entries in {KB_PATH}.")
|
|
|
|
if os.path.exists(os.path.dirname(KB_OUT_PATH)):
|
|
with open(KB_OUT_PATH, "w", encoding="utf-8") as f:
|
|
json.dump(kb, f, ensure_ascii=False, indent=2)
|
|
print(f"Synced {KB_OUT_PATH}.")
|
|
|
|
print("\n3. Updating traditions_canon.json...")
|
|
with open(TRADITIONS_PATH, "r", encoding="utf-8") as f:
|
|
tc = json.load(f)
|
|
|
|
for trad_key, trad in tc.items():
|
|
stations = trad.get("stations", [])
|
|
for st in stations:
|
|
# Primary word
|
|
prim = st.get("primary")
|
|
if prim and isinstance(prim, dict):
|
|
p_id = prim.get("id")
|
|
if p_id in words:
|
|
# If primary has empty or short desc, fill it with rich description
|
|
if not prim.get("desc"):
|
|
prim["desc"] = words[p_id]["description"]
|
|
|
|
# Cluster words
|
|
cluster = st.get("cluster", [])
|
|
for cl in cluster:
|
|
if isinstance(cl, dict):
|
|
c_id = cl.get("id")
|
|
if c_id in words and not cl.get("desc"):
|
|
cl["desc"] = words[c_id]["description"]
|
|
|
|
with open(TRADITIONS_PATH, "w", encoding="utf-8") as f:
|
|
json.dump(tc, f, ensure_ascii=False, indent=2)
|
|
print(f"Updated {TRADITIONS_PATH}.")
|
|
|
|
with open(TRADITIONS_ENRICHER_PATH, "w", encoding="utf-8") as f:
|
|
json.dump(tc, f, ensure_ascii=False, indent=2)
|
|
print(f"Synced {TRADITIONS_ENRICHER_PATH}.")
|
|
|
|
print("\n4. Updating lexicon_viewer.html...")
|
|
with open(HTML_PATH, "r", encoding="utf-8") as f:
|
|
html = f.read()
|
|
|
|
# Build clean WORDS_DATA list
|
|
words_data_list = []
|
|
# Sort by ID 0..999
|
|
for wid in sorted(words.keys()):
|
|
it = words[wid]
|
|
words_data_list.append({
|
|
"id": it["id"],
|
|
"word": it["word"],
|
|
"emoji": kb.get(str(wid), {}).get("emoji", "✨"),
|
|
"pos": it["pos"],
|
|
"category": it["category"],
|
|
"description": it["description"]
|
|
})
|
|
|
|
# 4a. Replace WORDS_DATA in HTML
|
|
json_words = json.dumps(words_data_list, ensure_ascii=False)
|
|
html = re.sub(
|
|
r'const WORDS_DATA = \[.*?\];',
|
|
f'const WORDS_DATA = {json_words};',
|
|
html,
|
|
flags=re.DOTALL
|
|
)
|
|
|
|
# 4b. Replace TRADITIONS_CANON in HTML
|
|
json_tc = json.dumps(tc, ensure_ascii=False, indent=2)
|
|
html = re.sub(
|
|
r'const TRADITIONS_CANON = \{.*?\n\s*\};\n',
|
|
f'const TRADITIONS_CANON = {json_tc};\n',
|
|
html,
|
|
flags=re.DOTALL
|
|
)
|
|
|
|
# 4c. Ensure CANONICAL_SYSTEMS recognizes Септенер and Додекада
|
|
old_systems_pattern = r'const CANONICAL_SYSTEMS = \[.*?\];'
|
|
new_systems = """const CANONICAL_SYSTEMS = [
|
|
{ id: 'Руны', name: 'Старший Футарк (Руны)', icon: 'ᚱ', css: 'system-runes' },
|
|
{ id: 'Огам', name: 'Кельтский Огам', icon: '᚛', css: 'system-ogham' },
|
|
{ id: 'Каббала', name: 'Каббала / Сефирот', icon: '✡️', css: 'system-kabbalah' },
|
|
{ id: 'Алхимия', name: 'Алхимия (Magnum Opus)', icon: '⚗️', css: 'system-alchemy' },
|
|
{ id: 'Таро', name: 'Старшие Арканы Таро', icon: '🎴', css: 'system-tarot' },
|
|
{ id: 'Ленорман', name: 'Оракул Ленорман', icon: '🪶', css: 'system-lenormand' },
|
|
{ id: 'И Цзин', name: 'И Цзин (Книга Перемен)', icon: '☯️', css: 'system-iching' },
|
|
{ id: 'Геомантия', name: 'Арабская Геомантия', icon: '⌛', css: 'system-geomancy' },
|
|
{ id: 'Додекада Созвездий', name: 'Классический гороскоп', icon: '♈', css: 'system-western' },
|
|
{ id: 'Китайский гороскоп', name: 'Китайский Зодиак', icon: '🐉', css: 'system-zodiac' },
|
|
{ id: 'Космология', name: 'Космология Трёх Миров', icon: '🌌', css: 'system-cosmo' },
|
|
{ id: 'Септенер', alias: 'Дни недели', name: 'Септенер (7 Дней Недели)', icon: '📅', css: 'system-days' },
|
|
{ id: 'Додекада', alias: 'Месяцы года', name: 'Додекада (12 Месяцев Года)', icon: '🗓️', css: 'system-months' },
|
|
{ id: 'Дни недели', name: 'Септенер (7 Дней Недели)', icon: '📅', css: 'system-days' },
|
|
{ id: 'Месяцы года', name: 'Додекада (12 Месяцев Года)', icon: '🗓️', css: 'system-months' },
|
|
];"""
|
|
html = re.sub(old_systems_pattern, new_systems, html, flags=re.DOTALL)
|
|
|
|
# 4d. Ensure SYSTEM_TO_CANON_KEY has Септенер and Додекада
|
|
old_key_pattern = r'const SYSTEM_TO_CANON_KEY = \{.*?\};'
|
|
new_key = """const SYSTEM_TO_CANON_KEY = {
|
|
'Септенер': 'days_of_week',
|
|
'Дни недели': 'days_of_week',
|
|
'Додекада': 'months_of_year',
|
|
'Месяцы года': 'months_of_year',
|
|
'Руны': 'runes',
|
|
'Таро': 'tarot',
|
|
'Огам': 'ogham',
|
|
'Каббала': 'kabbalah',
|
|
'Ленорман': 'lenormand',
|
|
'Космология': 'cosmology',
|
|
'Геомантия': 'geomancy',
|
|
'И Цзин': 'iching',
|
|
'Додекада Созвездий': 'western_zodiac',
|
|
'Китайский гороскоп': 'zodiac',
|
|
'Алхимия': 'alchemy'
|
|
};"""
|
|
html = re.sub(old_key_pattern, new_key, html, flags=re.DOTALL)
|
|
|
|
with open(HTML_PATH, "w", encoding="utf-8") as f:
|
|
f.write(html)
|
|
print(f"Updated {HTML_PATH} successfully!")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|