Files
999/scripts/build_iching_canon.py
T

663 lines
62 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
"""Build I Ching canon: 8 Ba Gua trigrams + 64 King Wen hexagrams.
Updates:
oracle_enricher/data/traditions_canon.json
assets/data/traditions_canon.json
lexicon_viewer.html (embedded TRADITIONS_CANON)
oracle_enricher/data/sources/iching.yaml
"""
from __future__ import annotations
import json
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CANON_PATH = ROOT / "oracle_enricher" / "data" / "traditions_canon.json"
ASSET_PATH = ROOT / "assets" / "data" / "traditions_canon.json"
HTML_PATH = ROOT / "lexicon_viewer.html"
LEXICON_PATH = ROOT / "oracle_enricher" / "data" / "lexicon.json"
DART_PATH = ROOT / "lib" / "data" / "words_data.dart"
YAML_PATH = ROOT / "oracle_enricher" / "data" / "sources" / "iching.yaml"
# ---------------------------------------------------------------------------
# 8 trigrams (Ba Gua)
# ---------------------------------------------------------------------------
TRIGRAMS = [
{
"id": "qian",
"name": "Триграмма ☰ Цянь (Творчество / Небо)",
"symbol": "☰",
"group": "Ба Гуа · Отец",
"lore": "Первичная созидающая сила неба: неутомимая крепость духа, высшая воля, свет, лидерство и власть закона.",
"names": ["небо", "небосвод", "творец", "воля", "свет"],
"aspects": ["чистый ян", "несокрушимая сила", "непрерывное движение", "семя замысла", "свет духа"],
"shadow": ["гордыня", "выжигание без опоры", "тирания жесткости"],
"yaml_name": "Творчество (Небо)",
"hanzi": "乾",
},
{
"id": "kun",
"name": "Триграмма ☷ Кунь (Исполнение / Земля)",
"symbol": "☷",
"group": "Ба Гуа · Мать",
"lore": "Самоотверженное принятие, великое земное плодородие, мягкость, питание всех существ, верность и покой.",
"names": ["земля", "мать", "покой", "лоно"],
"aspects": ["чистый инь", "восприимчивость", "питающее лоно", "преданное следование", "уступчивость"],
"shadow": ["косность", "инертность", "потеря формы"],
"yaml_name": "Исполнение (Земля)",
"hanzi": "坤",
},
{
"id": "zhen",
"name": "Триграмма ☳ Чжэнь (Возбуждение / Гром)",
"symbol": "☳",
"group": "Ба Гуа · Старший сын",
"lore": "Пробуждающий удар грома, импульсивный прорыв весны, сокрушение оцепенения, страх, ведущий к осмотрительности.",
"names": ["гром", "молния", "удар"],
"aspects": ["возбуждение", "внезапный толчок", "раскат небес", "шок пробуждения", "первое движение"],
"shadow": ["паника", "сотрясение без плода", "слепая ярость"],
"yaml_name": "Пробуждение (Гром)",
"hanzi": "震",
},
{
"id": "kan",
"name": "Триграмма ☵ Кань (Погружение / Вода)",
"symbol": "☵",
"group": "Ба Гуа · Средний сын",
"lore": "Опасность стремнины, глубина бездны, неостановимый бег воды, верность своей природе среди теснин.",
"names": ["вода", "бездна", "опасность"],
"aspects": ["глубина", "текучесть в расселинах", "опасный поток", "искренность сердца в испытании", "неиссякаемый исток"],
"shadow": ["утопление в страхе", "холод отчаяния", "затягивающий водоворот"],
"yaml_name": "Погружение (Вода)",
"hanzi": "坎",
},
{
"id": "gen",
"name": "Триграмма ☶ Гэнь (Пребывание / Гора)",
"symbol": "☶",
"group": "Ба Гуа · Младший сын",
"lore": "Неподвижность священной горы, способность вовремя остановиться, завершение движения, крепость духа.",
"names": ["гора", "скала", "покой", "спина"],
"aspects": ["незыблемая остановка", "покой спины", "граница движения", "молчание скалы", "завершение фазы"],
"shadow": ["каменный застой", "глухота к переменам", "черствость"],
"yaml_name": "Пребывание (Гора)",
"hanzi": "艮",
},
{
"id": "xun",
"name": "Триграмма ☴ Сюнь (Утончение / Ветер)",
"symbol": "☴",
"group": "Ба Гуа · Старшая дочь",
"lore": "Мягкое всепроникающее дыхание ветра, гибкость древесных ветвей, постепенное незаметное влияние.",
"names": ["ветер", "дыхание", "воздух", "дерево"],
"aspects": ["проникновение", "мягкая гибкость", "незаметное влияние", "постоянство дыхания", "корни дерева"],
"shadow": ["размывание границ", "уклончивость", "бессилие удара"],
"yaml_name": "Утончение (Ветер)",
"hanzi": "巽",
},
{
"id": "li",
"name": "Триграмма ☲ Ли (Сцепление / Огонь)",
"symbol": "☲",
"group": "Ба Гуа · Средняя дочь",
"lore": "Очищающее пламя, свет познания, ясность видения, тепло очага, зависимость пламени от питающей древесины.",
"names": ["огонь", "пламя", "сияние", "свет"],
"aspects": ["ясность видения", "солярный свет", "преображающее тепло", "приверженность истине", "зависимость от топлива"],
"shadow": ["выгорание", "ослепляющая иллюзия", "пожирающая страсть"],
"yaml_name": "Сцепление (Огонь)",
"hanzi": "離",
},
{
"id": "dui",
"name": "Триграмма ☱ Дуй (Разрешение / Озеро)",
"symbol": "☱",
"group": "Ба Гуа · Младшая дочь",
"lore": "Зеркальная гладь спокойного озера, открытая радость общения, благожелательность, поэзия и вдохновение.",
"names": ["озеро", "радость", "смех", "рот", "зеркало"],
"aspects": ["зеркало вод", "открытая радость", "плодотворный диалог", "улыбка сердца", "чистое отражение"],
"shadow": ["пустая болтовня", "разлив соблазна", "легкомыслие"],
"yaml_name": "Разрешение (Озеро)",
"hanzi": "兌",
},
]
# ---------------------------------------------------------------------------
# 64 hexagrams — King Wen order, Shchutsky Russian names
# group key before ' · ' becomes the atlas section header
# ---------------------------------------------------------------------------
def _hex(n: int) -> str:
return chr(0x4DC0 + (n - 1))
def _grp(n: int) -> str:
start = ((n - 1) // 8) * 8 + 1
end = start + 7
return f"Книга Перемен · {start:02d}–{end:02d}"
HEX_HANZI = {
1: "乾", 2: "坤", 3: "屯", 4: "蒙", 5: "需", 6: "訟", 7: "師", 8: "比",
9: "小畜", 10: "履", 11: "泰", 12: "否", 13: "同人", 14: "大有", 15: "謙", 16: "豫",
17: "隨", 18: "蠱", 19: "臨", 20: "觀", 21: "噬嗑", 22: "賁", 23: "剝", 24: "復",
25: "無妄", 26: "大畜", 27: "頤", 28: "大過", 29: "坎", 30: "離", 31: "咸", 32: "恆",
33: "遯", 34: "大壯", 35: "晉", 36: "明夷", 37: "家人", 38: "睽", 39: "蹇", 40: "解",
41: "損", 42: "益", 43: "夬", 44: "姤", 45: "萃", 46: "升", 47: "困", 48: "井",
49: "革", 50: "鼎", 51: "震", 52: "艮", 53: "漸", 54: "歸妹", 55: "豐", 56: "旅",
57: "巽", 58: "兌", 59: "渙", 60: "節", 61: "中孚", 62: "小過", 63: "既濟", 64: "未濟",
}
HEXAGRAMS = [
dict(n=1, pid="qian", ru="Творчество", py="Цянь",
lore="Небо над небом: чистый ян без перерыва. Неутомимое творчество, крепость духа и ход, который сам себе закон. Знак велит держаться замысла до конца и не искать опоры внизу.",
names=["творец", "небо", "воля", "свет", "небосвод"],
aspects=["сухое небо", "непрерывное творчество", "настойчивость до конца", "свет духа", "закон силы"],
shadow=["жёсткость без земли", "гордыня творца", "выжигание без опоры"]),
dict(n=2, pid="kun", ru="Исполнение", py="Кунь",
lore="Земля над землёй: чистое принятие. Плодородие, преданное следование и сила кобылы, которая несёт, не выбирая всадника. Знак велит служить и вынашивать, а не захватывать ход.",
names=["земля", "мать", "покой"],
aspects=["чистый приём", "вместилище", "плодородие", "следование", "кобыла пути"],
shadow=["инертность", "косность", "потеря формы"]),
dict(n=3, pid="zhun", ru="Начальная трудность", py="Чжунь",
lore="Вода над громом: росток ещё под мерзлой землёй. Первые всходы требуют корней, а не рывка. Знак держит хаос перед порядком и верность зародышу.",
names=["росток", "семя", "начать", "бездна", "гром"],
aspects=["пробивание мерзлой земли", "накопление корней", "верность зародышу", "хаос перед порядком"],
shadow=["преждевременный рывок", "увядание на корню", "страх перед миром"]),
dict(n=4, pid="meng", ru="Недоразвитость", py="Мэн",
lore="Гора над водой: родник у подножия, закрытый туманом. Ученичество, искренний вопрос и поиск учителя. Знак рассеивает неведение только тому, кто спрашивает трижды и слушает.",
names=["ученик", "туман", "учиться", "гора", "источник"],
aspects=["родник у горы", "искреннее вопрошание", "смирение ученичества", "поиск учителя"],
shadow=["упрямое невежество", "дерзость дилетанта", "пустые споры"]),
dict(n=5, pid="xu", ru="Необходимость ждать", py="Сюй",
lore="Вода над небом: облака ещё не стали дождём. Священное ожидание, накопление и пир веры, пока час не созрел. Знак велит не торопить тучу.",
names=["ждать", "терпение", "туча", "дождь", "пир"],
aspects=["облака в ожидании дождя", "накопление потенциала", "священный пир веры", "созревание часа"],
shadow=["парализующая лень", "отчаяние ожидания", "упущенный момент"]),
dict(n=6, pid="song", ru="Суд", py="Сун",
lore="Небо над водой: верх и глубина разошлись. Тяжба, проверка совести и поиск беспристрастного суда. Знак велит остановиться на полпути и признать предел правоты.",
names=["суд", "закон", "справедливость", "спор"],
aspects=["расхождение Неба и Воды", "проверка совести", "беспристрастный суд", "признание предела"],
shadow=["бесконечная вражда", "жажда мести", "гордыня правоты"]),
dict(n=7, pid="shi", ru="Войско", py="Ши",
lore="Земля над водой: скрытая сила в недрах. Дисциплина, единая цель и зрелый полководец. Знак даёт справедливость силы и требует, чтобы войско служило порядку, а не жажде крови.",
names=["войско", "воин", "солдат", "земля", "вода"],
aspects=["скрытая сила воды в земле", "суровая дисциплина", "единство цели", "зрелый полководец"],
shadow=["жестокость приказа", "напрасные жертвы", "войско без вождя"]),
dict(n=8, pid="bi", ru="Приближение", py="Би",
lore="Вода над землёй: добровольное единение. Союз, взаимная опора и выбор соратников. Знак зовёт встать рядом вовремя — опоздание к союзу уже тень.",
names=["союз", "близкий", "связь", "вода", "земля"],
aspects=["вода орошает землю", "добровольное единение", "верность союзу", "выбор соратников"],
shadow=["опоздание к союзу", "фальшивая лесть", "зависимость"]),
dict(n=9, pid="xiao_xu", ru="Воспитание малым", py="Сяо-чу",
lore="Ветер над небом: малая туча ещё не льёт. Сдерживание великого малым, накопление капли за каплей. Знак учит мягкой узде, а не удару.",
names=["ветер", "небо", "терпение", "дыхание"],
aspects=["малая туча на небе", "сдерживание великого", "накопление по капле", "мягкая узда"],
shadow=["бессилие узды", "мелочный контроль", "туча без дождя"]),
dict(n=10, pid="lu", ru="Поступь", py="Люй",
lore="Небо над озером: ступать по хвосту тигра и не быть укушенным. Осторожный шаг в присутствии силы. Знак требует меры в каждом ходе.",
names=["шаг", "ход", "путь", "тигр", "мера"],
aspects=["ступня на хвосте тигра", "осторожный этикет", "мера каждого хода", "опасная близость силы"],
shadow=["дерзость шага", "укус тигра", "потеря лица"]),
dict(n=11, pid="tai", ru="Расцвет", py="Тай",
lore="Земля над небом: священный брак низа и верха. Малое уходит, великое приходит. Золотой век согласия, плодородие и расцвет — пока не забыли об упадке.",
names=["мир", "изобилие", "весна", "благодать", "земля"],
aspects=["земля над небом", "золотой век согласия", "малое уходит великое приходит", "плодородие сил"],
shadow=["беспечность благополучия", "забывчивость об упадке", "сытая слепота"]),
dict(n=12, pid="pi", ru="Упадок", py="Пи",
lore="Небо над землёй: миры удалились друг от друга. Застой, сокрытие мудреца и сбережение зерна. Знак велит не служить недостойным и ждать поворота.",
names=["уныние", "слабость", "отчаяние", "небо", "земля"],
aspects=["разрыв связи миров", "сокрытие мудреца", "сбережение зерна", "терпеливая аскеза"],
shadow=["отчаяние как дом", "подчинение недостойным", "утрата веры"]),
dict(n=13, pid="tong_ren", ru="Родня", py="Тун-жэнь",
lore="Небо над огнём: люди одного света собираются в открытом поле. Содружество по духу, а не по крови. Знак требует широкого круга и прямого сердца.",
names=["род", "брат", "союз", "огонь", "небо"],
aspects=["содружество в открытом поле", "единство по духу", "широкий круг", "прямое сердце"],
shadow=["клан против чужих", "ложное братство", "огонь кружка"]),
dict(n=14, pid="da_you", ru="Великое обладание", py="Да-ю",
lore="Огонь над небом: солнце в зените владеет всем, что видит. Богатство, ясность и великая мера. Знак велит держать изобилие светлым, иначе оно слепит.",
names=["изобилие", "золото", "успех", "огонь", "небо"],
aspects=["солнце в зените", "владение ясностью", "великая мера", "светлое богатство"],
shadow=["слепящая роскошь", "жадность к свету", "обладание без меры"]),
dict(n=15, pid="qian_mod", ru="Смирение", py="Цянь",
lore="Земля над горой: высокая внутри, низкая снаружи. Смирение как закон силы, которая не выпирает. Знак уравнивает избыток и восполняет недостаток.",
names=["смирение", "гора", "земля"],
aspects=["высокая внутри низкая снаружи", "уравнивание избытка", "сила без выпирания", "скромный ход"],
shadow=["ложное смирение", "самоуничижение", "гора под спудом"]),
dict(n=16, pid="yu", ru="Вольность", py="Юй",
lore="Гром над землёй: музыка предвидения, радость готовности. Знак зовёт собирать войско в настроении праздника — вольность, которая знает срок удара.",
names=["радость", "гром", "свобода", "земля"],
aspects=["гром над землёй", "музыка предвидения", "радость готовности", "сбор войска в празднике"],
shadow=["разгул без срока", "праздность вместо удара", "ложная вольность"]),
dict(n=17, pid="sui", ru="Последование", py="Суй",
lore="Озеро над громом: радость следует за возбуждением. Знак велит идти за тем, что истинно движется, и отпускать то, что уже не ведёт.",
names=["идти", "вести", "радость", "гром"],
aspects=["радость за возбуждением", "следование истинному движению", "отпускание мёртвого вождя"],
shadow=["слепое следование", "стадный ход", "потеря своего удара"]),
dict(n=18, pid="gu", ru="Исправление порчи", py="Гу",
lore="Гора над ветром: ветер заперт и гниёт. Расчистка наследственных завалов, три дня до и три дня после. Знак требует чистить порчу, а не жить в ней.",
names=["скверна", "яд", "целитель", "гора", "ветер"],
aspects=["ветер под горой", "расчистка наследства", "три дня до и после", "очищающая реформа"],
shadow=["отравление прошлым", "страх растревожить гниль", "бессилие перед традицией"]),
dict(n=19, pid="lin", ru="Посещение", py="Линь",
lore="Земля над озером: приближение великого к малому. Надзор, забота и срок восьмого месяца. Знак даёт приход силы — и предупреждает, что приход не вечен.",
names=["гость", "озеро", "земля", "приближение"],
aspects=["приближение великого", "надзор и забота", "срок восьмого месяца", "приход силы"],
shadow=["навязчивый надзор", "гость, который не уходит", "забота как захват"]),
dict(n=20, pid="guan", ru="Созерцание", py="Гуань",
lore="Ветер над землёй: башня взора, чистота помыслов перед жертвой. Знак ставит пример без слов — созерцать закон космоса и быть видимым как маяк.",
names=["созерцание", "взгляд", "маяк", "ветер", "земля"],
aspects=["башня визионерского взора", "чистота перед жертвой", "безмолвный пример", "постижение космоса"],
shadow=["праздное любопытство", "оторванность от действия", "холод наблюдателя"]),
dict(n=21, pid="shi_he", ru="Стиснутые зубы", py="Ши-хэ",
lore="Огонь над громом: стиснуть и перекусить преграду. Судебная ясность, удар по помехе, глоток правды. Знак велит разгрызть узел, а не обходить его.",
names=["удар", "закон", "огонь", "гром", "суд"],
aspects=["перекусить преграду", "судебная ясность", "удар по помехе", "глоток правды"],
shadow=["ломание зубов о камень", "жестокость приговора", "гнев вместо суда"]),
dict(n=22, pid="bi_grace", ru="Убранство", py="Би",
lore="Гора над огнём: огонь освещает форму горы. Красота как оболочка сути, не как суть. Знак даёт убранство малому и велит не принимать позолоту за кость.",
names=["сияние", "маска", "гора", "огонь", "форма"],
aspects=["огонь освещает гору", "красота как оболочка", "убранство малого", "форма без подмены сути"],
shadow=["позолота вместо кости", "суета украшения", "маска, съевшая лицо"]),
dict(n=23, pid="bo", ru="Разорение", py="Бо",
lore="Гора над землёй: верх осыпается, остаётся ложе. Сдирание ветхой формы, конец цикла, обнажение основы. Знак велит не удерживать то, что уже осыпается.",
names=["угасание", "гора", "земля", "конец"],
aspects=["осыпание верха", "сдирание ветхой формы", "обнажение основы", "конец цикла"],
shadow=["цепляться за осыпь", "разрушение без остатка", "паника конца"]),
dict(n=24, pid="fu", ru="Возврат", py="Фу",
lore="Земля над громом: первый ян в глубине тьмы. Поворот колеса, семь дней обновления, закрытые врата покоя. Знак велит дать ростку срок и не рвать его на свет.",
names=["вернуться", "искра", "цикл", "гром", "земля"],
aspects=["первый ян во тьме", "поворот колеса года", "семь дней обновления", "покой закрытых врат"],
shadow=["поспешность незрелого света", "страх темноты", "рецидив старой ошибки"]),
dict(n=25, pid="wu_wang", ru="Беспорочность", py="У-ван",
lore="Небо над громом: движение без кривды. Невинный удар, который не рассчитывает выгоду. Знак бережёт чистоту намерения и гасит ход, затеянный ради добычи.",
names=["чистота", "небо", "гром", "истина"],
aspects=["движение без кривды", "невинный удар", "чистота намерения", "ход не ради добычи"],
shadow=["наивность как слепота", "удар без расчёта", "чистота как предлог"]),
dict(n=26, pid="da_xu", ru="Воспитание великим", py="Да-чу",
lore="Гора над небом: небо удержано в горе. Великое накопление, кормление мудрости, сила в упряжи. Знак велит копить и воспитывать, а не выпускать всё сразу.",
names=["гора", "небо", "сила", "мудрость"],
aspects=["небо удержано в горе", "великое накопление", "кормление мудрости", "сила в упряжи"],
shadow=["скупость накопления", "сила без выпуска", "гора как тюрьма неба"]),
dict(n=27, pid="yi_jaws", ru="Питание", py="И",
lore="Гора над громом: челюсти, которые кормят или пожирают. Знак спрашивает, чем ты питаешь себя и других — речью, хлебом, вниманием — и велит смотреть на рот.",
names=["хлеб", "голод", "рот", "гора", "гром"],
aspects=["челюсти питания", "чем кормишь себя", "речь как пища", "взгляд на рот"],
shadow=["пожирание", "пустой рот", "питание чужим"]),
dict(n=28, pid="da_guo", ru="Переразвитие великого", py="Да-го",
lore="Озеро над ветром: балка прогнулась под тяжестью крыши. Избыток великого, крайний срок подпорок. Знак велит действовать сейчас: стоять или рухнуть.",
names=["озеро", "ветер", "дерево", "верёвка"],
aspects=["прогнувшаяся балка", "избыток великого", "крайний срок подпорок", "стоять или рухнуть"],
shadow=["обвал крыши", "упрямство в избытке", "поздняя подпорка"]),
dict(n=29, pid="kan_hex", ru="Повторная опасность", py="Кань",
lore="Вода над водой: бездна в бездне. Испытание верности через риск, стремление не замирать в омуте. Знак закаливает сердце, если оно остаётся искренним в порогах.",
names=["бездна", "опасность", "вода"],
aspects=["вода над водой", "испытание через риск", "не останавливаться в стремнине", "глубинная закалка"],
shadow=["ужас глубины", "паника в порогах", "предательство себя"]),
dict(n=30, pid="li_hex", ru="Сияние", py="Ли",
lore="Огонь над огнём: двойная ясность, зависимость от топлива. Сцепление света с тем, что его кормит. Знак даёт видение и велит не выгореть посреди дня.",
names=["огонь", "сияние", "свет", "пламя"],
aspects=["двойная ясность", "сцепление с топливом", "видение дня", "свет, который держится за опору"],
shadow=["выгорание середины", "ослепление", "пожар без очага"]),
dict(n=31, pid="xian", ru="Взаимодействие", py="Сянь",
lore="Озеро над горой: влияние, которое сходит сверху на неподвижное. Притяжение, отклик сердца, священный брак чувств. Знак велит принять воздействие и ответить без захвата.",
names=["любовь", "встреча", "союз", "озеро", "гора"],
aspects=["влияние сверху на гору", "притяжение сердец", "священный отклик", "принять и ответить"],
shadow=["захват чувством", "слепая влюблённость", "влияние как власть"]),
dict(n=32, pid="heng", ru="Постоянство", py="Хэн",
lore="Гром над ветром: долгий брак движения и проникновения. Выдержка, клятва на срок, ритм, который не срывается. Знак велит держать курс, а не искать новый ветер каждый час.",
names=["цикл", "клятва", "вечность", "гром", "ветер"],
aspects=["долгий брак грома и ветра", "выдержка", "клятва на срок", "ритм без срыва"],
shadow=["рутина вместо верности", "упрямство курса", "клятва мёртвому"]),
dict(n=33, pid="dun", ru="Бегство", py="Дунь",
lore="Небо над горой: мудрец отступает, пока сила тьмы ещё мала. Стратегический уход, сохранение ядра. Знак велит уйти вовремя — не геройствовать на гиблом поле.",
names=["бежать", "гора", "небо", "изгнанник"],
aspects=["отступление мудреца", "уход пока тьма мала", "сохранение ядра", "стратегический тыл"],
shadow=["паническое бегство", "уход от срока", "оставление поля слишком рано"]),
dict(n=34, pid="da_zhuang", ru="Мощь великого", py="Да-чжуан",
lore="Гром над небом: сила в зените, рога баранов. Великая мощь требует прямоты — иначе рога застрянут в изгороди. Знак даёт удар и велит не бодать без пути.",
names=["сила", "гром", "небо", "баран"],
aspects=["сила в зените", "рога баранов", "прямота удара", "мощь, которая знает путь"],
shadow=["бодание изгороди", "слепая мощь", "сила без прямоты"]),
dict(n=35, pid="jin", ru="Восход", py="Цзинь",
lore="Огонь над землёй: солнце всходит над почвой. Продвижение, признание, свет, который уже виден всем. Знак даёт восход заслуги — и предупреждает не принимать рассвет за полдень.",
names=["рассвет", "свет", "огонь", "земля", "успех"],
aspects=["солнце над почвой", "продвижение заслуги", "свет, видимый всем", "восход, не полдень"],
shadow=["жажда признания", "рассвет как корона", "свет без корня"]),
dict(n=36, pid="ming_yi", ru="Поражение света", py="Мин-и",
lore="Земля над огнём: свет ранен и спрятан в земле. Затмение, мудрость в изгнании, крыло, которое прячут. Знак велит хранить ясность внутри, когда снаружи правят тьма и рана.",
names=["затмение", "тьма", "рана", "земля", "огонь"],
aspects=["свет спрятан в земле", "мудрость в изгнании", "спрятанное крыло", "ясность внутри раны"],
shadow=["угасание духа", "сотрудничество с тьмой", "рана как характер"]),
dict(n=37, pid="jia_ren", ru="Домашние", py="Цзя-жэнь",
lore="Ветер над огнём: огонь очага, ветер речей семьи. Порядок дома, роли, слово, которое держит кров. Знак велит начать правление с собственного очага.",
names=["дом", "семья", "очаг", "ветер", "огонь"],
aspects=["огонь очага", "ветер семейных речей", "порядок ролей", "правление с дома"],
shadow=["тирания дома", "сплетня очага", "семья как клетка"]),
dict(n=38, pid="kui", ru="Разлад", py="Куй",
lore="Огонь над озером: два света смотрят врозь. Отчуждение, несходство, огонь и озеро не смешиваются. Знак учит малому согласию внутри большого разлада — и не требует слияния силой.",
names=["враг", "огонь", "озеро", "двойник"],
aspects=["два света врозь", "отчуждение", "малое согласие в разладе", "несходство без слияния"],
shadow=["вражда ради вражды", "принудительное слияние", "огонь, высушивший озеро"]),
dict(n=39, pid="jian", ru="Препятствие", py="Цзянь",
lore="Вода над горой: стремнина на хребте. Преграда, которая велит идти не вперёд, а за советом. Знак останавливает прямой ход и открывает юг — путь к мудрому.",
names=["гора", "вода", "пропасть", "скала"],
aspects=["стремнина на хребте", "преграда прямого хода", "идти за советом", "юг к мудрому"],
shadow=["упрямство в тупике", "штурм пропасти", "отказ от совета"]),
dict(n=40, pid="xie", ru="Разрешение", py="Се",
lore="Гром над водой: гроза разряжает бездну. Снятие пут, прощение, стрела, вынутая из раны. Знак велит освободить вовремя — держать узел после грозы уже порча.",
names=["гром", "вода", "свобода", "прощение"],
aspects=["гроза над бездной", "снятие пут", "стрела из раны", "освобождение в срок"],
shadow=["разрешение слишком рано", "месть вместо прощения", "узел, который не развязали"]),
dict(n=41, pid="sun", ru="Убыль", py="Сунь",
lore="Гора над озером: озеро отдаёт воде горе. Уменьшение низа ради верха, искренняя жертва двух чаш. Знак велит убавить простое, чтобы укрепить главное — и знать меру убыли.",
names=["жертва", "отлив", "гора", "озеро", "мера"],
aspects=["озеро отдаёт горе", "убыль низа ради верха", "две чаши жертвы", "мера уменьшения"],
shadow=["самоистязание", "убыль без укрепления", "жертва впустую"]),
dict(n=42, pid="yi_increase", ru="Прибыль", py="И",
lore="Ветер над громом: верхи убавляются, низы прибывают. Дар свыше, прилив сил, пора сеять. Знак велит брать прибыль в работу, пока ветер несёт семя.",
names=["дар", "изобилие", "прилив", "ветер", "гром"],
aspects=["верхи убавляются низы прибывают", "дар свыше", "пора сеять", "прибыль в работу"],
shadow=["жадность к дару", "прибыль без сева", "ветер, разнёсший семя"]),
dict(n=43, pid="guai", ru="Выход", py="Гуай",
lore="Озеро над небом: прорыв плотины, решительное объявление. Пять ян вытесняют последнее инь. Знак велит говорить правду в лицо и не применять тайного оружия.",
names=["выход", "озеро", "небо", "правда"],
aspects=["прорыв плотины", "решительное объявление", "пять ян против одного инь", "правда в лицо"],
shadow=["слом плотины без берега", "насилие выхода", "тайное оружие"]),
dict(n=44, pid="gou", ru="Перечение", py="Гоу",
lore="Небо над ветром: встреча с тем, чего не звали. Один инь входит снизу в пять ян. Знак предупреждает: не выдавать за жениха то, что пришло как искушение.",
names=["встреча", "искушение", "ветер", "небо"],
aspects=["встреча незваного", "один инь в пяти ян", "искушение снизу", "не выдавать за союз"],
shadow=["брак с порчей", "слепое влечение", "ветер, подточивший небо"]),
dict(n=45, pid="cui", ru="Воссоединение", py="Цуй",
lore="Озеро над землёй: воды собираются в низине. Сбор людей, жертва вождю, большое собрание. Знак велит соединиться вокруг достойного центра — иначе толпа станет болотом.",
names=["союз", "встреча", "озеро", "земля", "пир"],
aspects=["воды в низине", "сбор людей", "жертва вождю", "центр собрания"],
shadow=["толпа без центра", "болото собрания", "вождь как идол"]),
dict(n=46, pid="sheng", ru="Подъём", py="Шэн",
lore="Земля над ветром: дерево растёт из почвы, не торопя день. Постепенный подъём, покорность земле, шаг за шагом вверх. Знак велит расти из корня, а не прыгать на вершину.",
names=["рост", "дерево", "земля", "ветер"],
aspects=["дерево из почвы", "постепенный подъём", "шаг за шагом", "рост из корня"],
shadow=["прыжок без корня", "подъём чужими плечами", "спешка роста"]),
dict(n=47, pid="kun_exhaust", ru="Истощение", py="Кунь",
lore="Озеро над водой: озеро утекло, осталась яма. Сжатие, нужда, слово, которое не слышат. Знак велит хранить внутреннюю правду, когда внешняя сила иссякла.",
names=["голод", "нужда", "озеро", "вода"],
aspects=["утекающее озеро", "сжатие сил", "слово, которое не слышат", "правда внутри нужды"],
shadow=["отчаяние ямы", "молчание как смерть", "нужда как закон"]),
dict(n=48, pid="jing", ru="Колодец", py="Цзин",
lore="Вода над ветром: вода поднимается по дереву. Неиссякаемый исток общины, кувшин у устья, глубина, которую не переносят с места на место. Знак велит беречь колодец, а не менять город.",
names=["колодец", "источник", "родник", "вода", "дерево"],
aspects=["вода по дереву", "исток общины", "непереносимая глубина", "кувшин у устья"],
shadow=["заиленный колодец", "разбитый кувшин", "жадность к воде"]),
dict(n=49, pid="ge", ru="Смена", py="Гэ",
lore="Озеро над огнём: огонь вскипает в озере, змея меняет кожу. Переворот календаря, линька порядка. Знак велит менять шкуру, когда срок созрел, и иметь проект на место старого.",
names=["змей", "лава", "феникс", "озеро", "огонь"],
aspects=["огонь в озере", "смена шкуры", "обновление календаря", "линька порядка"],
shadow=["бунт ради бунта", "разрушение без проекта", "запоздалое обновление"]),
dict(n=50, pid="ding", ru="Котел", py="Дин",
lore="Огонь над ветром: дерево кормит огонь под треножником. Священный котёл, превращение грубого в пищу духа, три ножки устойчивости. Знак велит варить культуру, а не опрокидывать сосуд.",
names=["котёл", "сосуд", "алтарь", "огонь", "ветер"],
aspects=["огонь под деревом", "алхимический сосуд", "жертвенная трапеза", "три ножки треножника"],
shadow=["опрокинутый котёл", "осквернение пищи", "пустая ритуальность"]),
dict(n=51, pid="zhen_hex", ru="Возбуждение", py="Чжэнь",
lore="Гром над громом: двойной удар, священный страх. Пробуждение, осмотрительность после раската, жертвенное вино, которое не проливают. Знак трясёт, чтобы вернуть в себя.",
names=["гром", "молния", "удар"],
aspects=["двойной гром", "священный страх", "осмотрительность после раската", "пробуждение в себя"],
shadow=["паника раската", "пролитое вино", "страх как характер"]),
dict(n=52, pid="gen_hex", ru="Сосредоточенность", py="Гэнь",
lore="Гора над горой: спина к спине, остановка каждой черты. Молчание, предел движения, храм покоя. Знак велит остановиться в своём месте и не следить за чужим телом.",
names=["гора", "покой", "спина"],
aspects=["гора над горой", "остановка каждой черты", "молчание спины", "предел движения"],
shadow=["застой как добродетель", "глухота покоя", "остановка чужой жизни"]),
dict(n=53, pid="jian_gradual", ru="Течение", py="Цзянь",
lore="Ветер над горой: дерево на склоне растёт годами. Постепенность брака, дикого гуся, ступеней. Знак велит идти как течение — не прыжком, а укоренением на каждой полке склона.",
names=["течение", "путь", "гора", "ветер", "дерево"],
aspects=["дерево на склоне", "постепенность", "дикий гусь ступенями", "укоренение на полке"],
shadow=["прыжок через ступень", "застой под видом постепенности", "течение в никуда"]),
dict(n=54, pid="gui_mei", ru="Невеста", py="Гуй-мэй",
lore="Гром над озером: младшая идёт в дом. Неравный союз, срок, который нельзя назначать самой. Знак предупреждает о браке без места и о радости, которая не стала правом.",
names=["жена", "дочь", "озеро", "гром"],
aspects=["младшая идёт в дом", "неравный союз", "срок не по своей воле", "радость без права"],
shadow=["брак без места", "самовольный союз", "радость, ставшая унижением"]),
dict(n=55, pid="feng", ru="Изобилие", py="Фэн",
lore="Гром над огнём: полнота дня, которая уже держит тень. Изобилие в зените, ясность среди затмения, встреча царя. Знак велит действовать в полный час — после зенита свет сядет.",
names=["изобилие", "гром", "огонь", "полдень"],
aspects=["полнота дня", "ясность среди затмения", "встреча в зените", "час, который не длится"],
shadow=["слепота изобилия", "зенит как вечность", "пир после света"]),
dict(n=56, pid="lu_wander", ru="Странствие", py="Люй",
lore="Огонь над горой: костёр на хребте, ночлег не свой. Странник, мера гостя, гнездо, которое не строят. Знак велит идти легко, не ссориться на постоялом дворе и помнить, что огонь чужой.",
names=["странник", "путь", "огонь", "гора", "гость"],
aspects=["костёр на хребте", "мера гостя", "гнездо, которое не строят", "лёгкий ход"],
shadow=["ссора на постоялом дворе", "захват чужого очага", "странствие как бегство"]),
dict(n=57, pid="xun_hex", ru="Проникновение", py="Сюнь",
lore="Ветер над ветром: двойное проникновение без удара. Приказ, который разносится как дыхание, корень в щели камня. Знак побеждает гибкостью и повтором, не единоборством.",
names=["ветер", "дыхание", "дерево"],
aspects=["двойной ветер", "приказ как дыхание", "корень в щели", "гибкость и повтор"],
shadow=["размывание воли", "уклончивость", "бессилие удара"]),
dict(n=58, pid="dui_hex", ru="Радость", py="Дуй",
lore="Озеро над озером: двойная радость открытых уст. Обмен речью, обучение через удовольствие, зеркало вод. Знак даёт радость как закон — и предупреждает о разливе болтовни.",
names=["радость", "озеро", "смех", "рот"],
aspects=["двойное озеро", "открытые уста", "обучение через радость", "зеркало вод"],
shadow=["пустая болтовня", "разлив соблазна", "радость как бегство"]),
dict(n=59, pid="huan", ru="Раздробление", py="Хуань",
lore="Ветер над водой: ветер разгоняет лёд реки. Рассеяние эго, храм как общее тело, растворение затора. Знак велит разобрать сплочённую тьму — не копить её в себе.",
names=["ветер", "вода", "туман", "храм"],
aspects=["ветер разгоняет лёд", "рассеяние эго", "храм как общее тело", "растворение затора"],
shadow=["распад без сбора", "растворение себя", "ветер, разнёсший храм"]),
dict(n=60, pid="jie", ru="Ограничение", py="Цзе",
lore="Вода над озером: озеро знает свой берег. Мера, устав, сладкое ограничение, которое не превращает жизнь в тюрьму. Знак велит поставить предел — и не затягивать узел до удушья.",
names=["мера", "закон", "озеро", "вода", "берег"],
aspects=["озеро знает берег", "сладкое ограничение", "устав меры", "предел без тюрьмы"],
shadow=["удушье устава", "мера как клетка", "берег, съевший озеро"]),
dict(n=61, pid="zhong_fu", ru="Внутренняя правда", py="Чжун-фу",
lore="Ветер над озером: пустота в середине держит целое. Искренность, которая двигает даже свинью и рыбу. Знак велит совпасть внутри — внешний обряд без ядра пуст.",
names=["правда", "истина", "сердце", "ветер", "озеро"],
aspects=["пустота в середине", "искренность, движущая тварь", "ядро вместо обряда", "совпадение внутри"],
shadow=["правда как поза", "пустота без ядра", "обряд без сердца"]),
dict(n=62, pid="xiao_guo", ru="Переразвитие малого", py="Сяо-го",
lore="Гром над горой: птица не должна взлетать слишком высоко. Малое превышение, внимание к низу, птица в гнезде. Знак велит скромный ход: лучше недолет, чем срыв в вышине.",
names=["гром", "гора", "мера", "птицы"],
aspects=["птица не слишком высоко", "малое превышение", "внимание к низу", "гнездо важнее взлёта"],
shadow=["срыв в вышине", "мелочность как добродетель", "полёт без гнезда"]),
dict(n=63, pid="ji_ji", ru="Уже конец", py="Цзи-цзи",
lore="Вода над огнём: каждая черта на месте, переправа свершилась. Совершенный порядок на пике цикла. Знак велит бдительность лисы на льду: конец уже содержит начало распада.",
names=["итог", "мост", "берег", "конец", "вода"],
aspects=["вода над огнём", "успешная переправа", "строгий порядок черт", "бдительность лисы на льду"],
shadow=["самоуспокоение", "начало распада", "подмоченный хвост"]),
dict(n=64, pid="wei_ji", ru="Ещё не конец", py="Вэй-цзи",
lore="Огонь над водой: огонь стремится вверх, вода вниз — дело не закрыто. Лисенок ещё в реке. Знак держит вечную незавершённость как потенциал: не объявлять финал, пока хвост мокр.",
names=["цикл", "путь", "порог", "начало", "огонь"],
aspects=["огонь над водой", "вечная незавершённость", "лисёнок в реке", "потенциал нового шага"],
shadow=["бессилие довести", "вечные колебания", "страх финальной точки"]),
]
def load_lexicon():
lex = json.loads(LEXICON_PATH.read_text(encoding="utf-8"))
by_word = {e["word"]: e for e in lex}
return lex, by_word
def load_dart_desc():
text = DART_PATH.read_text(encoding="utf-8")
desc = {}
for m in re.finditer(
r'word:\s*"([^"]+)"[\s\S]*?description:\s*"([^"]*)"',
text,
):
desc[m.group(1)] = m.group(2)
return desc
def payload(entry: dict, dart_desc: dict) -> dict:
word = entry["word"]
return {
"id": entry["id"],
"word": word,
"emoji": entry.get("emoji", ""),
"pos": entry.get("pos", ""),
"category": entry.get("category", ""),
"desc": dart_desc.get(word, ""),
}
def resolve(names: list[str], by_word: dict, dart_desc: dict):
found = []
seen = set()
for n in names:
if not n or n in seen:
continue
entry = by_word.get(n)
if entry:
seen.add(n)
found.append(payload(entry, dart_desc))
primary = found[0] if found else None
cluster = found[1:]
return primary, cluster
def yaml_quote(s: str) -> str:
if any(c in s for c in ":#{}[]&*!|>'\"%@`"):
return json.dumps(s, ensure_ascii=False)
return s
def yaml_list(items: list[str], indent: int) -> str:
pad = " " * indent
lines = []
for it in items:
lines.append(f"{pad}- {yaml_quote(it)}")
return "\n".join(lines)
def build_yaml(trigrams, hexagrams) -> str:
chunks = []
for t in trigrams:
chunks.append(
"\n".join(
[
f"- id: {t['id']}",
f" name: {yaml_quote(t['yaml_name'])}",
" names_ru:",
yaml_list(t["names"], 4),
" aspects:",
yaml_list(t["aspects"], 4),
" shadow:",
yaml_list(t["shadow"], 4),
"",
]
)
)
for h in hexagrams:
hid = f"hex_{h['n']:02d}_{h['pid']}"
name = f"{h['n']}. {h['ru']} ({h['py']})"
chunks.append(
"\n".join(
[
f"- id: {hid}",
f" name: {yaml_quote(name)}",
" names_ru:",
yaml_list(h["names"], 4),
" aspects:",
yaml_list(h["aspects"], 4),
" shadow:",
yaml_list(h["shadow"], 4),
"",
]
)
)
return "\n".join(chunks).rstrip() + "\n"
def build_stations(by_word, dart_desc):
stations = []
missing = []
for t in TRIGRAMS:
primary, cluster = resolve(t["names"], by_word, dart_desc)
if primary is None:
missing.append(t["id"])
stations.append(
{
"id": t["id"],
"name": t["name"],
"symbol": t["symbol"],
"hanzi": t.get("hanzi", ""),
"group": t["group"],
"lore": t["lore"],
"primary": primary,
"cluster": cluster,
}
)
for h in HEXAGRAMS:
primary, cluster = resolve(h["names"], by_word, dart_desc)
hid = f"hex_{h['n']:02d}_{h['pid']}"
if primary is None:
missing.append(hid)
stations.append(
{
"id": hid,
"name": f"Гексаграмма {h['n']:02d} {_hex(h['n'])} {h['py']} ({h['ru']})",
"symbol": _hex(h["n"]),
"hanzi": HEX_HANZI.get(h["n"], ""),
"group": _grp(h["n"]),
"lore": h["lore"],
"primary": primary,
"cluster": cluster,
}
)
return stations, missing
def patch_html(canon: dict) -> None:
html = HTML_PATH.read_text(encoding="utf-8")
start = html.find(" const TRADITIONS_CANON = ")
end = html.find(" const WORDS_DATA = ")
if start < 0 or end < 0 or end <= start:
raise SystemExit("не нашёл TRADITIONS_CANON / WORDS_DATA в lexicon_viewer.html")
blob = " const TRADITIONS_CANON = " + json.dumps(canon, ensure_ascii=False, indent=2) + ";\n\n"
HTML_PATH.write_text(html[:start] + blob + html[end:], encoding="utf-8")
def main() -> None:
_, by_word = load_lexicon()
dart_desc = load_dart_desc()
print(f"lexicon={len(by_word)} dart_desc={len(dart_desc)}")
stations, missing = build_stations(by_word, dart_desc)
if missing:
print("WARNING no primary:", ", ".join(missing))
iching = {
"id": "iching",
"name": "И Цзин (Книга Перемен)",
"icon": "☯️",
"count": len(stations),
"description": "8 триграмм Ба Гуа и 64 гексаграммы Книги Перемен в порядке царя Вэня: первородные силы Инь и Ян и полные оракульные фигуры из двух триграмм.",
"stations": stations,
}
canon = json.loads(CANON_PATH.read_text(encoding="utf-8"))
canon["iching"] = iching
text = json.dumps(canon, ensure_ascii=False, indent=2) + "\n"
CANON_PATH.write_text(text, encoding="utf-8")
ASSET_PATH.write_text(text, encoding="utf-8")
patch_html(canon)
YAML_PATH.write_text(build_yaml(TRIGRAMS, HEXAGRAMS), encoding="utf-8")
n_tri = sum(1 for s in stations if s["id"] in {t["id"] for t in TRIGRAMS})
n_hex = len(stations) - n_tri
n_prim = sum(1 for s in stations if s.get("primary"))
print(f"iching stations={len(stations)} trigrams={n_tri} hexagrams={n_hex} with_primary={n_prim}")
print("wrote", CANON_PATH.relative_to(ROOT))
print("wrote", ASSET_PATH.relative_to(ROOT))
print("patched", HTML_PATH.relative_to(ROOT))
print("wrote", YAML_PATH.relative_to(ROOT))
if __name__ == "__main__":
main()