#!/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()