feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets

This commit is contained in:
2026-10-09 15:26:58 +05:00
parent 7f91366201
commit 0b12e4583b
244 changed files with 611461 additions and 1617 deletions
+106 -4
View File
@@ -3,6 +3,7 @@ import '../models/interpreter_persona.dart';
import '../models/word_model.dart';
import '../g4f/g4f.dart';
import 'settings_repository.dart';
import 'words_repository.dart';
class AiOracleService {
static final AiOracleService _instance = AiOracleService._internal();
@@ -32,6 +33,47 @@ class AiOracleService {
];
}
/// Generates an enhanced revision of a symbol based on its full pre-prompt
Future<String> generateSymbolRevision({
required String prePrompt,
}) async {
try {
final g4fResult = await _client.chat.completions.create(
model: G4FModels.gpt4o,
messages: [
Message(
role: Role.system,
content:
'Ты — Архитектор Смыслов, верховный мифолог, культуролог и онтологический герменевт. '
'Твоя задача — провести глубочайшее, бескомпромиссное и многомерное ОБОГАЩЕНИЕ сакрального Архетипа. '
'Напиши захватывающую, монументальную, живую и литературную статью-эссе, которую по-настоящему интересно читать. '
'СТРОЖАЙШИЕ ПРАВИЛА: '
'Абсолютный запрет на гадательную терминологию (никаких оракулов, раскладов, карт, вопрошающих, гаданий). '
'Запрет на лень и краткость: длинный, плотный, фактурный текст. '
'Обязательный мультикультурный синтез: этимология, космогонии (Шумер, Египет, Греция, Иудаизм, Кельты, Суфизм, Дао), наука (квантовая механика, биология), алхимия. '
'Формат ответа строго в markdown с заголовками ##:\n'
'## ЯДРО\n'
'## ОРАКУЛ\n'
'## ТЕНЬ\n'
'## ВОПРОС\n'
'## ЖЕСТ\n'
'Метрика: в самом конце точное количество символов.',
),
Message(
role: Role.user,
content: prePrompt,
),
],
);
if (g4fResult.trim().length > 20) {
return g4fResult.trim();
}
} catch (e) {
print('G4F Revision Error: $e');
}
return '';
}
/// Interprets a full multi-word session narrative (1 to 9 words)
Future<String> interpretSession({
required List<SessionWord> words,
@@ -77,7 +119,8 @@ class AiOracleService {
$wordsNarrative
${newlyAddedWord != null ? "Только что вытянут уточняющий знак: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning})." : ""}
Ответь в своём голосе, 3–4 предложения. Привяжи ответ к знакам и вопросу. Без тумана. Один ясный вывод или шаг.''';
Ответь в своём голосе, 3–5 предложений. Привяжи ответ к законам знаков и вопросу.
Закон знака побеждает голос: не выдумывай людей, руны и события, которых нет в законах. Если канон знака пуст — не раздувай его.''';
final messages = [
Message(role: Role.system, content: voice.systemPrompt),
@@ -119,7 +162,7 @@ ${newlyAddedWord != null ? "Только что вытянут уточняющ
final wordsStr = words
.asMap()
.entries
.map((e) => 'Знак ${e.key + 1}: #${e.value.formattedSessionNumber} «${e.value.word.toUpperCase()}» (${e.value.category}). Смысл знака: ${e.value.symbolicMeaning}')
.map((e) => _signDossier(e.key + 1, e.value))
.join('\n');
return '''$qStr
@@ -127,7 +170,64 @@ ${newlyAddedWord != null ? "Только что вытянут уточняющ
Выпавшие знаки жребия:
$wordsStr
${persona.userTask}''';
${persona.userTask}
$_fidelityRules''';
}
static const String _fidelityRules = '''Закон знака побеждает голос.
- Разбери знаки по порядку. Назови каждый и опирайся только на переданный закон.
- Голос — интонация. Он не подменяет смысл и не превращает слова в руны, нити, тропы, круг близких, утробу или протоколы, если этого нет в законе.
- Если у знака написано «канон пуст», так и скажи. Читай само слово буквально и не раздувай заготовку в историю.
- Знак-сдвиг (наречие, обстоятельство, оператор «сдвиг») не отдельный миф: он меняет соседний знак — срок, место или способ.
- Если задание голоса спорит с законом знака, побеждает закон знака.
- В конце один физический шаг, который следует из законов.''';
String _signDossier(int index, SessionWord w) {
final live = WordsRepository().getWordByName(w.word);
final law = ((live != null && live.core.trim().isNotEmpty) ? live.core : w.symbolicMeaning).trim();
final stock = _isStockCanon(law);
final buf = StringBuffer()
..writeln('Знак $index: #${w.formattedSessionNumber} «${w.word.toUpperCase()}»')
..writeln('Часть речи: ${w.partOfSpeech.isEmpty ? 'не указана' : w.partOfSpeech}. Разряд: ${w.category}.');
if (live != null) {
final op = WordItemLocalization.localizeOperator(live.operator);
if (op.isNotEmpty) buf.writeln('Оператор: $op.');
}
if (stock) {
buf.writeln('Закон знака: КАНОН ПУСТ. В досье заготовка, не смысл. Читай слово «${w.word}» буквально.');
} else {
buf.writeln('Закон знака (единственный разрешённый смысл): $law');
}
final shadow = live?.shadow.trim() ?? '';
if (shadow.isNotEmpty && !_isStockShadow(shadow)) {
buf.writeln('Тень: $shadow');
}
final desc = live?.cleanDescription.trim() ?? '';
if (desc.isNotEmpty && desc != law && !_isStockCanon(desc)) {
buf.writeln('Описание: $desc');
}
return buf.toString().trimRight();
}
bool _isStockCanon(String text) {
final t = text.toLowerCase();
return t.contains('сакральный символ') ||
t.contains('архетипический образ в поле судьбы') ||
t.contains('ключевую сферу кристаллизации') ||
t.contains('вектор трансформации') ||
t.contains('модус судьбы') ||
t.contains('активный процесс духовной динамики');
}
bool _isStockShadow(String text) {
final t = text.toLowerCase();
return t.contains('искажение первообраза') ||
t.contains('суета, поспешность, насилие над естественным ритмом');
}
String _synthesizeDeepSacredNarrative(List<SessionWord> words, String question) {
@@ -174,9 +274,11 @@ ${persona.userTask}''';
String _withPersonaPrefix(String body) {
switch (persona.id) {
case 'orc':
return 'Слушай. $body';
return 'Вааагх. $body';
case 'techpriest':
return 'Диагностика. $body';
case 'aeldari':
return 'По нитям судеб. $body';
case 'analyst':
return 'Рабочая гипотеза. $body';
default:
+89
View File
@@ -0,0 +1,89 @@
/// Свёртка календарной даты в число судьбы суток.
///
/// Та же школа, что на экране психоматрицы: день, месяц и год свёртываются
/// по отдельности, затем складываются. 11, 22 и 33 не свёртываются дальше.
class DayCode {
final int value;
final int reducedDay;
final int reducedMonth;
final int reducedYear;
final String word;
final String title;
const DayCode({
required this.value,
required this.reducedDay,
required this.reducedMonth,
required this.reducedYear,
required this.word,
required this.title,
});
String get formula => '$reducedDay + $reducedMonth + $reducedYear = $value';
String get clause =>
'Нумерологический код суток: $formula, знак «$word». '
'Мастер-числа 11, 22 и 33 не свёртываются.';
}
class DayNumerology {
static const Map<int, String> words = {
1: 'один',
2: 'два',
3: 'три',
4: 'четыре',
5: 'пять',
6: 'шесть',
7: 'семь',
8: 'восемь',
9: 'девять',
11: 'одиннадцать',
22: 'двадцать два',
33: 'тридцать три',
};
static const Map<int, String> titles = {
1: '1 — Первопроходец (Монада)',
2: '2 — Дипломат (Дуада)',
3: '3 — Творец (Триада)',
4: '4 — Строитель (Тетрада)',
5: '5 — Странник (Пентада)',
6: '6 — Хранитель очага (Гексада)',
7: '7 — Искатель истины (Септенер)',
8: '8 — Владыка материи (Октава)',
9: '9 — Мудрец (Эннеада)',
11: '11 — Духовный проводник',
22: '22 — Великий архитектор',
33: '33 — Служение и 33 ряда домов',
};
static int reduce(int n, {bool keepMaster = true}) {
var cur = n.abs();
while (cur > 9) {
if (keepMaster && (cur == 11 || cur == 22 || cur == 33)) return cur;
var sum = 0;
var x = cur;
while (x > 0) {
sum += x % 10;
x ~/= 10;
}
cur = sum;
}
return cur;
}
static DayCode ofDate(DateTime date) {
final day = reduce(date.day);
final month = reduce(date.month);
final year = reduce(date.year);
final value = reduce(day + month + year);
return DayCode(
value: value,
reducedDay: day,
reducedMonth: month,
reducedYear: year,
word: words[value] ?? '',
title: titles[value] ?? '$value',
);
}
}
@@ -0,0 +1,69 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import '../models/external_symbol_model.dart';
class ExternalSymbolsRepository {
static final ExternalSymbolsRepository _instance = ExternalSymbolsRepository._internal();
factory ExternalSymbolsRepository() => _instance;
ExternalSymbolsRepository._internal();
List<ExternalSymbol> _symbols = [];
bool _isLoaded = false;
List<ExternalSymbol> get allSymbols => _symbols;
bool get isLoaded => _isLoaded;
int get totalCount => _symbols.length;
Future<void> init() async {
if (_isLoaded) return;
try {
String jsonStr = '';
// 1. Попытка чтения напрямую из файловой системы (для dev / test)
for (final p in ['assets/data/oracle_external_symbols.json', '../assets/data/oracle_external_symbols.json']) {
final f = File(p);
if (f.existsSync()) {
jsonStr = f.readAsStringSync();
break;
}
}
// 2. В релизе загружаем через rootBundle
if (jsonStr.isEmpty && !Platform.environment.containsKey('FLUTTER_TEST')) {
try {
jsonStr = await rootBundle.loadString('assets/data/oracle_external_symbols.json');
} catch (_) {}
}
if (jsonStr.isNotEmpty) {
final decoded = json.decode(jsonStr) as Map<String, dynamic>;
_symbols = decoded.values
.map((v) => ExternalSymbol.fromMap(Map<String, dynamic>.from(v as Map)))
.toList();
// Сортируем по алфавиту
_symbols.sort((a, b) => a.term.toLowerCase().compareTo(b.term.toLowerCase()));
_isLoaded = true;
}
} catch (e) {
debugPrint('[ExternalSymbolsRepository] Ошибка загрузки: $e');
}
}
List<ExternalSymbol> search(String query, {bool onlyWithImages = false}) {
final q = query.trim().toLowerCase();
return _symbols.where((s) {
if (onlyWithImages && !s.hasIllustrations) return false;
if (q.isEmpty) return true;
if (s.term.toLowerCase().contains(q)) return true;
if (s.normalized.contains(q)) return true;
if (s.sources.any((src) => src.author.toLowerCase().contains(q) || src.excerpt.toLowerCase().contains(q))) {
return true;
}
return false;
}).toList();
}
}
+411
View File
@@ -0,0 +1,411 @@
import 'dart:math';
import 'package:flutter/material.dart';
import '../models/houses_session_model.dart';
import '../models/word_model.dart';
import 'words_repository.dart';
class HousesService {
static final HousesService _instance = HousesService._internal();
factory HousesService() => _instance;
HousesService._internal();
static const List<Color> sectorColors = [
Color(0xFFFFB300), // 0: Золото Солнца
Color(0xFFE53935), // 1: Рубиновое Пламя
Color(0xFF1E88E5), // 2: Лазурный Эфир
Color(0xFF43A047), // 3: Изумрудная Чаща
Color(0xFF8E24AA), // 4: Аметистовая Тайна
Color(0xFF00ACC1), // 5: Бирюзовая Бездна
Color(0xFFFF7043), // 6: Коралловый Горн
Color(0xFF90A4AE), // 7: Лунное Серебро
Color(0xFF5C6BC0), // 8: Индиго Сумерек
Color(0xFF8D6E63), // 9: Бронзовая Твердь
];
static const List<String> sectorTitles = [
'I. Сектор Золотого Сияния',
'II. Сектор Алого Огня',
'III. Сектор Небесных Вод',
'IV. Сектор Вечного Роста',
'V. Сектор Глубинных Тайн',
'VI. Сектор Предельного Взора',
'VII. Сектор Творящей Воли',
'VIII. Сектор Зеркальной Памяти',
'IX. Сектор Полночного Суда',
'X. Сектор Земного Основания',
];
/// Generates a freshly shuffled 33x30 field (990 houses + 10 Archons).
/// If [querentWord] is given, guarantees that the querent resides in one of the 990 houses
/// and dynamically calculates [querentHouseIndex] to point to that exact house.
HousesSession createNewSession({String? question, WordItem? querentWord, int? querentHouseIndex}) {
final repo = WordsRepository();
final allWords = List<WordItem>.from(repo.allWords);
// Cryptographic shuffle for pristine randomness
final rnd = Random.secure();
allWords.shuffle(rnd);
bool isNoun(WordItem w) {
final pos = w.partOfSpeech.toLowerCase().trim();
if (pos != 'существительное') return false;
final cat = w.category.toLowerCase();
if (cat.contains('действие') || w.operator == 'shifter') {
return false;
}
return true;
}
// 1. Ensure Archons (indexes 0..9) are strictly nouns (существительное)
for (int i = 0; i < 10; i++) {
if (!isNoun(allWords[i])) {
final swapIdx = allWords.indexWhere((w) => isNoun(w), 10);
if (swapIdx != -1) {
final temp = allWords[i];
allWords[i] = allWords[swapIdx];
allWords[swapIdx] = temp;
}
}
}
// 2. If querentWord is provided, ensure it is in the Houses pool (indexes 10..999),
// not in the Archons (indexes 0..9), so Querent always has a physical House.
if (querentWord != null) {
final qLower = querentWord.word.toLowerCase().trim();
if (querentHouseIndex != null && querentHouseIndex >= 0 && querentHouseIndex < 990) {
final qIdx = allWords.indexWhere(
(w) => w.id == querentWord.id || w.word.toLowerCase().trim() == qLower,
);
final targetIdx = 10 + querentHouseIndex;
if (qIdx >= 0 && qIdx != targetIdx) {
final temp = allWords[targetIdx];
allWords[targetIdx] = allWords[qIdx];
allWords[qIdx] = temp;
// If the swap displaced a noun from archon, re-verify archon is a noun
if (qIdx < 10 && !isNoun(allWords[qIdx])) {
final nounIdx = allWords.indexWhere(
(w) => isNoun(w) &&
w.id != querentWord.id &&
w.word.toLowerCase().trim() != qLower,
10,
);
if (nounIdx != -1) {
final t2 = allWords[qIdx];
allWords[qIdx] = allWords[nounIdx];
allWords[nounIdx] = t2;
}
}
}
} else {
final archonMatchIdx = allWords.sublist(0, 10).indexWhere(
(w) => w.id == querentWord.id || w.word.toLowerCase().trim() == qLower,
);
if (archonMatchIdx >= 0) {
// Swap with a noun from the houses pool
final swapTarget = allWords.indexWhere(
(w) => isNoun(w),
10,
);
final validTarget = swapTarget != -1 ? swapTarget : 10 + rnd.nextInt(990);
final temp = allWords[archonMatchIdx];
allWords[archonMatchIdx] = allWords[validTarget];
allWords[validTarget] = temp;
}
}
}
// Final absolute guarantee: strictly ensure every Archon (0..9) is a noun
for (int i = 0; i < 10; i++) {
if (!isNoun(allWords[i])) {
final swapIdx = allWords.indexWhere((w) => isNoun(w), 10);
if (swapIdx != -1) {
final temp = allWords[i];
allWords[i] = allWords[swapIdx];
allWords[swapIdx] = temp;
}
}
}
// 1. First 10 words -> 10 Archons ruling the 10 column triads
final sectors = <ArchonSector>[];
for (int i = 0; i < 10; i++) {
sectors.add(
ArchonSector(
index: i,
archonWord: allWords[i],
startCol: i * 3,
endCol: i * 3 + 2,
sectorColor: sectorColors[i],
title: sectorTitles[i],
),
);
}
// 2. Remaining 990 words -> 33 rows x 30 columns
final grid = <HouseCell>[];
final housesPool = allWords.sublist(10, 1000); // exactly 990 words
int? resolvedQuerentIndex;
for (int i = 0; i < housesPool.length; i++) {
final row = i ~/ 30; // 0..32
final col = i % 30; // 0..29
final sectorIndex = col ~/ 3; // 0..9
final houseWord = housesPool[i];
if (querentWord != null &&
resolvedQuerentIndex == null &&
(houseWord.id == querentWord.id ||
houseWord.word.toLowerCase().trim() == querentWord.word.toLowerCase().trim())) {
resolvedQuerentIndex = i;
}
grid.add(
HouseCell(
row: row,
col: col,
houseIndex: i,
houseWord: houseWord,
sectorIndex: sectorIndex,
),
);
}
final effectiveQuerentIndex = querentHouseIndex ?? resolvedQuerentIndex;
final id = 'houses_${DateTime.now().millisecondsSinceEpoch}_${rnd.nextInt(10000)}';
return HousesSession(
id: id,
createdAt: DateTime.now(),
question: question,
querentWord: querentWord,
querentHouseIndex: effectiveQuerentIndex,
sectors: sectors,
grid: grid,
selectedHouses: const [],
chains: const [],
);
}
/// Casts guest symbols into the selected houses (1..9) and computes the house chains.
/// Adds one newly revealed house into the session and recalculates chains.
HousesSession revealHouse(
HousesSession session, {
required HouseCell cell,
required WordItem guestWord,
}) {
final newOrder = session.selectedHouses.length + 1;
final updatedCell = cell.copyWith(
guestWord: guestWord,
selectionOrder: newOrder,
);
final updatedSelected = List<HouseCell>.from(session.selectedHouses)..add(updatedCell);
final fullGridMap = <String, HouseCell>{};
for (final h in session.grid) {
fullGridMap[h.houseWord.word.toLowerCase().trim()] = h;
}
final openedHousesMap = <String, HouseCell>{};
for (final h in updatedSelected) {
openedHousesMap[h.houseWord.word.toLowerCase().trim()] = h;
}
final chains = _computeChains(updatedSelected, openedHousesMap, fullGridMap, session.sectors);
final updatedGrid = session.grid.map((c) {
if (c.houseIndex == cell.houseIndex) {
return updatedCell;
}
return c;
}).toList();
return HousesSession(
id: session.id,
createdAt: session.createdAt,
question: session.question,
querentWord: session.querentWord,
querentHouseIndex: session.querentHouseIndex,
sectors: session.sectors,
grid: updatedGrid,
selectedHouses: updatedSelected,
chains: chains,
);
}
HousesSession completeDivination(
HousesSession session,
List<HouseCell> selectedHouses,
) {
if (selectedHouses.isEmpty) return session;
final rnd = Random.secure();
final repo = WordsRepository();
final availableGuests = List<WordItem>.from(repo.allWords)..shuffle(rnd);
// Assign guests uniquely
final populatedHouses = <HouseCell>[];
for (int i = 0; i < selectedHouses.length; i++) {
final guest = availableGuests[i];
populatedHouses.add(
selectedHouses[i].copyWith(
guestWord: guest,
selectionOrder: i + 1,
),
);
}
// Map houses on the full grid by word name for instant lookup
final fullGridMap = <String, HouseCell>{};
for (final h in session.grid) {
fullGridMap[h.houseWord.word.toLowerCase().trim()] = h;
}
// Map populated/selected houses by word name
final openedHousesMap = <String, HouseCell>{};
for (final h in populatedHouses) {
openedHousesMap[h.houseWord.word.toLowerCase().trim()] = h;
}
// Compute House Chains
final chains = _computeChains(populatedHouses, openedHousesMap, fullGridMap, session.sectors);
// Update grid state with guests
final updatedGrid = session.grid.map((cell) {
final found = populatedHouses.firstWhere(
(p) => p.row == cell.row && p.col == cell.col,
orElse: () => cell,
);
return found;
}).toList();
return HousesSession(
id: session.id,
createdAt: session.createdAt,
question: session.question,
querentWord: session.querentWord,
querentHouseIndex: session.querentHouseIndex,
sectors: session.sectors,
grid: updatedGrid,
selectedHouses: populatedHouses,
chains: chains,
);
}
List<HouseChain> _computeChains(
List<HouseCell> populatedHouses,
Map<String, HouseCell> openedMap,
Map<String, HouseCell> fullGridMap,
List<ArchonSector> sectors,
) {
final chains = <HouseChain>[];
final repo = WordsRepository();
final allWords = repo.allWords;
final archonMap = <String, ArchonSector>{};
for (final s in sectors) {
archonMap[s.archonWord.word.toLowerCase().trim()] = s;
}
int chainIdx = 1;
for (final startHouse in populatedHouses) {
final steps = <HouseChainStep>[];
final visitedHousesInChain = <String>{};
HouseCell curHouse = startHouse;
WordItem curGuest = startHouse.guestWord!;
ChainEndType endType = ChainEndType.fieldVector;
String desc = '';
// Max 9 links per chain; early break if loop or archon throne reached
for (int stepNum = 0; stepNum < 9; stepNum++) {
final curHouseKey = curHouse.houseWord.word.toLowerCase().trim();
final curGuestKey = curGuest.word.toLowerCase().trim();
visitedHousesInChain.add(curHouseKey);
// 1. Check if curGuest is one of the 10 Archons
final archonSector = archonMap[curGuestKey];
if (archonSector != null) {
steps.add(
HouseChainStep(
house: curHouse,
guest: curGuest,
leadsToArchon: true,
destinationArchon: archonSector,
),
);
endType = ChainEndType.archonThrone;
desc = 'Восхождение к Престолу Архонта «${curGuest.word.toUpperCase()}» (Триада ${archonSector.index + 1})';
break;
}
// 2. Find next house on the 990 field
final nextHouse = fullGridMap[curGuestKey];
if (nextHouse == null) {
steps.add(HouseChainStep(house: curHouse, guest: curGuest));
desc = 'Импульс уходит за пределы полотна к знаку «${curGuest.word.toUpperCase()}»';
break;
}
final nextHouseKey = nextHouse.houseWord.word.toLowerCase().trim();
// 3. Check if loop closed
if (visitedHousesInChain.contains(nextHouseKey)) {
steps.add(
HouseChainStep(
house: curHouse,
guest: curGuest,
isLoopClosing: true,
),
);
endType = ChainEndType.loop;
desc = 'Замкнутая петля судьбы: замыкается на Дом «${nextHouse.houseWord.word.toUpperCase()}»';
break;
}
// 4. Advance to next house
steps.add(HouseChainStep(house: curHouse, guest: curGuest));
// Determine next guest. An unopened house does not invent a chain.
final openedNext = openedMap[nextHouseKey];
if (openedNext != null && openedNext.guestWord != null) {
curGuest = openedNext.guestWord!;
curHouse = nextHouse;
} else {
final guestIdx = (nextHouse.houseIndex * 7919 + 101) % allWords.length;
final computedGuest = allWords[guestIdx];
steps.add(
HouseChainStep(
house: nextHouse,
guest: computedGuest,
computed: true,
),
);
endType = ChainEndType.fieldVector;
desc =
'Дом «${nextHouse.houseWord.word.toUpperCase()}» не открыт. Знак «${computedGuest.word.toUpperCase()}» вычислен из поля, это не жребий. Цепь здесь останавливается.';
break;
}
}
if (desc.isEmpty) {
desc = 'Линейный вектор: луч энергии уходит сквозь полотно к знаку «${steps.last.guest.word.toUpperCase()}»';
}
chains.add(
HouseChain(
chainIndex: chainIdx++,
startingHouse: startHouse,
steps: steps,
endType: endType,
description: desc,
),
);
}
return chains;
}
}
@@ -0,0 +1,85 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import '../models/lenormand_pairs.dart';
class LenormandPairsRepository {
static final LenormandPairsRepository _instance =
LenormandPairsRepository._internal();
factory LenormandPairsRepository() => _instance;
LenormandPairsRepository._internal();
final Map<int, LenormandCardEntry> _byN = {};
List<LenormandTriad> _triads = const [];
List<LenormandExtra> _extras = const [];
bool _loaded = false;
bool get isLoaded => _loaded;
List<LenormandCardEntry> get cards =>
List.unmodifiable(_byN.values.toList()..sort((a, b) => a.n.compareTo(b.n)));
List<LenormandTriad> get triads => List.unmodifiable(_triads);
List<LenormandExtra> get extras => List.unmodifiable(_extras);
LenormandCardEntry? byNumber(int n) => _byN[n];
LenormandCardEntry? byStationId(String id) {
if (id.startsWith('card_')) {
final n = int.tryParse(id.substring(5));
if (n != null) return _byN[n];
}
return null;
}
Future<void> init() async {
if (_loaded && _byN.isNotEmpty) return;
Map<String, dynamic>? decoded;
final cwd = Directory.current.path;
final candidates = <String>{
'assets/data/lenormand_pairs.json',
'../assets/data/lenormand_pairs.json',
'$cwd/assets/data/lenormand_pairs.json',
'$cwd/../assets/data/lenormand_pairs.json',
};
for (final path in candidates) {
try {
final file = File(path);
if (file.existsSync()) {
decoded = json.decode(file.readAsStringSync()) as Map<String, dynamic>;
break;
}
} catch (e) {
debugPrint('lenormand_pairs.json skip $path: $e');
}
}
if (decoded == null) {
try {
final jsonStr = await rootBundle.loadString('assets/data/lenormand_pairs.json');
decoded = json.decode(jsonStr) as Map<String, dynamic>;
} catch (e) {
debugPrint('Could not load lenormand_pairs.json: $e');
}
}
if (decoded == null) {
_loaded = true;
return;
}
_byN.clear();
for (final raw in (decoded['cards'] as List? ?? const [])) {
if (raw is! Map<String, dynamic>) continue;
final card = LenormandCardEntry.fromMap(raw);
_byN[card.n] = card;
}
_triads = (decoded['triads'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.map(LenormandTriad.fromMap)
.toList();
_extras = (decoded['extras'] as List? ?? const [])
.whereType<Map<String, dynamic>>()
.map(LenormandExtra.fromMap)
.toList();
_loaded = true;
}
}
+369
View File
@@ -0,0 +1,369 @@
import 'dart:convert';
import '../models/culture_branch.dart';
import '../models/word_model.dart';
/// Роли ручного бурения. Человек сам отвечает вместо моделей.
class DiveRole {
final String id;
final String title;
final String hint;
const DiveRole(this.id, this.title, this.hint);
}
const List<DiveRole> kDiveRoles = [
DiveRole(
'route_cultures',
'Маршрут культур',
'Три традиции, где у знака есть именной след. Ответ — названия через запятую.',
),
DiveRole(
'route_languages',
'Маршрут языков',
'Три языка с прямым корнем или устойчивым именем образа. Ответ — названия через запятую.',
),
DiveRole(
'mystic',
'Мистик',
'Закон знака: устройство, свет, тень и какие системы держат именно это слово.',
),
DiveRole(
'psycho',
'Психоаналитик',
'Один психический механизм этого знака, не архетип вообще.',
),
DiveRole(
'synth',
'Синтезатор',
'Сжимает уже вписанные ответы в карточку. JSON можно разложить по полям.',
),
];
class DiveSlot {
final String id;
final String title;
const DiveSlot(this.id, this.title);
}
const List<DiveSlot> kDiveLanguages = [
DiveSlot('шумерский', 'шумерский'),
DiveSlot('древнеегипетский', 'древнеегипетский'),
DiveSlot('иврит', 'иврит'),
DiveSlot('древнегреческий', 'древнегреческий'),
DiveSlot('латынь', 'латынь'),
DiveSlot('древнеирландский', 'древнеирландский'),
DiveSlot('древнескандинавский', 'древнескандинавский'),
DiveSlot('старославянский', 'старославянский'),
DiveSlot('санскрит', 'санскрит'),
DiveSlot('авестийский', 'авестийский'),
DiveSlot('древнекитайский', 'древнекитайский'),
DiveSlot('японский', 'японский'),
DiveSlot('арабский', 'арабский'),
DiveSlot('майя', 'майя'),
DiveSlot('церковнославянский', 'церковнославянский'),
];
const List<DiveSlot> kDiveTraditions = [
DiveSlot('runes', 'Руны'),
DiveSlot('tarot', 'Таро'),
DiveSlot('ogham', 'Огам'),
DiveSlot('kabbalah', 'Каббала'),
DiveSlot('lenormand', 'Ленорман'),
DiveSlot('geomancy', 'Геомантия'),
DiveSlot('iching', 'И Цзин'),
DiveSlot('alchemy', 'Алхимия'),
DiveSlot('astrology', 'Астрология'),
DiveSlot('cosmology', 'Космология'),
DiveSlot('slavic', 'Славянский канон'),
];
const Map<String, String> _languageAliases = {
'греческий': 'древнегреческий',
'китайский': 'древнекитайский',
'египетский': 'древнеегипетский',
'скандинавский': 'древнескандинавский',
'ирландский': 'древнеирландский',
'славянский': 'старославянский',
'церковный славянский': 'церковнославянский',
};
String _dossier(WordItem word) {
final pos = word.partOfSpeech.trim().isEmpty ? 'не указана' : word.partOfSpeech.trim();
final category = word.category.trim().isEmpty ? 'не указана' : word.category.trim();
final buffer = StringBuffer()
..writeln('Знак Оракула 999: «${word.word}».')
..writeln('Часть речи: $pos. Категория: $category.');
if (word.core.trim().isNotEmpty) {
buffer.writeln('Текущее ядро, его можно уточнить, но не подменяй другим знаком: ${word.core.trim()}');
}
return buffer.toString().trimRight();
}
String cultureRoutePrompt(WordItem word) {
final menu = kCultureBranches.map((b) => b.name).join('; ');
return '${_dossier(word)}\n'
'Выбери ровно 3 традиции, где у ЭТОГО образа есть именной миф, текст или обряд, '
'а не просто «похожая идея».\n'
'Только из списка: $menu.\n'
'Ответ: три названия через запятую. Без пояснений.';
}
String languageRoutePrompt(WordItem word) {
final menu = kDiveLanguages.map((b) => b.title).join('; ');
return '${_dossier(word)}\n'
'Выбери ровно 3 языка, где есть прямой корень, устойчивый перевод этого образа '
'или его иероглиф. Не выбирай язык только потому, что он древний.\n'
'Только из списка: $menu.\n'
'Ответ: три названия через запятую. Без пояснений.';
}
String mysticPrompt(WordItem word) {
return 'Ты мистик Оракула 999. Разбираешь один знак как закон, не как гороскоп. '
'Пиши по-русски. Не предсказывай любовь, деньги и здоровье. '
'Не занимай чужую карту, руну или гексаграмму, если этот знак ею не является. '
'Если след тонок — так и скажи.\n\n'
'${_dossier(word)}\n'
'Дай: устройство образа; светлую сторону того же закона; теневую сторону того же закона; '
'какие системы (Таро, руны, Ленорман, геомантия, И Цзин, алхимия, астрология) '
'держат именно это слово, а какие только соседние. Соседние не присваивай.';
}
String psychoPrompt(WordItem word) {
return 'Ты юнгианский аналитик. Один знак — один психический механизм. '
'Пиши по-русски, без лекции про индивидуацию вообще. '
'Назови, что человек делает с этим образом, когда он интегрирован, '
'и что делает, когда вытесняет.\n\n'
'${_dossier(word)}\n'
'Разбери только его, не архетип «вообще».';
}
String cultureSlicePrompt(WordItem word, String culture) {
return 'Ты мифолог одной традиции: $culture. Другие культуры запрещены. '
'Пиши по-русски. Если у традиции нет достоверного следа этого образа, '
'ответь ровно фразой «следа нет» и остановись. Не заполняй пустоту соседним мифом. '
'Нужны: исконное слово или знак, транслитерация, имя текста или мифа, '
'что образ делает, чего не позволяет.\n\n'
'${_dossier(word)}\n'
'Традиция: $culture.';
}
String languageSlicePrompt(WordItem word, String language) {
return 'Ты этимолог одного языка: $language. Другие языки запрещены. '
'Пиши по-русски. Если у русского слова нет корня в этом языке, '
'скажи «прямого корня нет» и приведи только ближайшее культурное слово этого языка '
'для того же предмета, не выдавая его за этимон. '
'Нужны: форма, транслитерация, сдвиг значения, один источник или памятник.\n\n'
'${_dossier(word)}\n'
'Язык: $language.';
}
String traditionSlicePrompt(WordItem word, String tradition) {
return 'Ты хранитель одной системы: $tradition. Другие системы запрещены. '
'Пиши по-русски. Если этот знак сам не является картой, руной, фигурой или станцией '
'этой системы, не занимай чужую станцию: напиши, чем знак лишь сосед, '
'или что прямого места нет.\n\n'
'${_dossier(word)}\n'
'Система: $tradition.\n'
'Ответ: 2–5 предложений закона именно этого слова внутри системы.';
}
String rolePrompt(String roleId, WordItem word) {
switch (roleId) {
case 'route_cultures':
return cultureRoutePrompt(word);
case 'route_languages':
return languageRoutePrompt(word);
case 'mystic':
return mysticPrompt(word);
case 'psycho':
return psychoPrompt(word);
default:
return _dossier(word);
}
}
String synthesizerPrompt({
required WordItem word,
required Map<String, String> reports,
required String core,
required String oracle,
required String shadow,
required String asks,
required String step,
required String binds,
required Map<String, String> cultures,
required Map<String, String> languages,
required Map<String, String> traditions,
}) {
final payload = <String, dynamic>{
'word': word.word,
'reports': reports,
'draft': {
'core': core,
'oracle': oracle,
'shadow': shadow,
'asks': asks,
'step': step,
'binds': binds,
'cultures': cultures,
'languages': languages,
'traditions': traditions,
},
};
return 'Ты архитектор Оракула 999. Сожми уже собранные ответы в один JSON без markdown. '
'Не выдумывай культуру, язык или систему, если во входе пусто или стоит «следа нет».\n'
'Ключи: word, core, oracle, shadow, asks, step, binds, layers, cultures, languages.\n'
'core — одно предложение, закон знака.\n'
'oracle — устройство знака, сосед в поле и цена. Культуры поимённо сюда не копируй.\n'
'shadow — обратная сторона того же закона.\n'
'asks — один вопрос. step — один физический жест, не «подумай».\n'
'binds — до 6 русских слов знаков из тысячи, не сам знак.\n'
'layers — список {system, title, law}. system из: '
'${kDiveTraditions.map((s) => s.id).join(', ')}.\n'
'cultures — объект. Ключ строго из списка традиций. Значение: 2–5 фраз.\n'
'languages — объект. Ключ — имя языка, значение — этимологическая заметка.\n'
'Запрещены заготовки: «сакральный символ», «созерцать символ», '
'«готов ли ты принять этот урок», «ключевую сферу кристаллизации».\n'
'Никакого текста вокруг JSON.\n\n'
'${_dossier(word)}\n'
'${jsonEncode(payload)}';
}
/// Имена из ответа маршрута → точные ключи реестра.
List<String> matchCultureNames(String answer) {
final found = <String>[];
for (final raw in answer.split(RegExp(r'[,;\n]+'))) {
final token = raw.trim();
if (token.isEmpty) continue;
final branch = cultureBranchByName(token);
if (branch != null && !found.contains(branch.name)) found.add(branch.name);
}
return found;
}
List<String> matchLanguageNames(String answer) {
final known = {for (final slot in kDiveLanguages) slot.id: slot.id};
final found = <String>[];
for (final raw in answer.split(RegExp(r'[,;\n]+'))) {
final token = raw.trim().toLowerCase();
if (token.isEmpty) continue;
final name = known[token] ?? _languageAliases[token];
if (name != null && !found.contains(name)) found.add(name);
}
return found;
}
class DiveCanonDraft {
final String? core;
final String? oracle;
final String? shadow;
final String? asks;
final String? step;
final List<String>? binds;
final Map<String, String>? cultures;
final Map<String, String>? languages;
final List<SymbolLayer>? layers;
const DiveCanonDraft({
this.core,
this.oracle,
this.shadow,
this.asks,
this.step,
this.binds,
this.cultures,
this.languages,
this.layers,
});
}
DiveCanonDraft? parseDiveCanon(String text) {
var src = text.trim();
if (src.isEmpty) return null;
if (src.startsWith('```')) {
src = src.replaceFirst(RegExp(r'^```[a-zA-Z]*\n?'), '');
if (src.endsWith('```')) src = src.substring(0, src.length - 3).trim();
}
final start = src.indexOf('{');
final end = src.lastIndexOf('}');
if (start < 0 || end <= start) return null;
final dynamic decoded;
try {
decoded = jsonDecode(src.substring(start, end + 1));
} catch (_) {
return null;
}
if (decoded is! Map) return null;
final map = decoded.map((key, value) => MapEntry(key.toString(), value));
List<String>? binds;
final rawBinds = map['binds'];
if (rawBinds is List) {
binds = rawBinds.map((e) => e.toString().trim()).where((e) => e.isNotEmpty).toList();
} else if (rawBinds is String && rawBinds.trim().isNotEmpty) {
binds = rawBinds.split(RegExp(r'[,;]+')).map((e) => e.trim()).where((e) => e.isNotEmpty).toList();
}
Map<String, String>? cultures;
if (map['cultures'] is Map) {
cultures = {};
for (final entry in (map['cultures'] as Map).entries) {
final branch = cultureBranchByName(entry.key.toString());
final key = branch?.name ?? entry.key.toString().trim();
final value = entry.value?.toString().trim() ?? '';
if (key.isNotEmpty && value.isNotEmpty && value != 'следа нет') cultures[key] = value;
}
}
Map<String, String>? languages;
if (map['languages'] is Map) {
languages = {};
for (final entry in (map['languages'] as Map).entries) {
final names = matchLanguageNames(entry.key.toString());
final key = names.isEmpty ? entry.key.toString().trim().toLowerCase() : names.first;
final value = entry.value?.toString().trim() ?? '';
if (key.isNotEmpty && value.isNotEmpty) languages[key] = value;
}
}
List<SymbolLayer>? layers;
if (map['layers'] is List) {
layers = [];
for (final item in map['layers'] as List) {
if (item is! Map) continue;
final law = item['law']?.toString().trim() ?? '';
if (law.isEmpty) continue;
final systemRaw = (item['system'] ?? item['title'] ?? '').toString().trim().toLowerCase();
DiveSlot? slot;
for (final candidate in kDiveTraditions) {
if (candidate.id == systemRaw || candidate.title.toLowerCase() == systemRaw) {
slot = candidate;
break;
}
}
if (slot == null) continue;
layers.add(SymbolLayer(system: slot.id, title: slot.title, law: law));
}
}
String? textOf(String key) {
final value = map[key];
if (value == null) return null;
final text = value.toString().trim();
return text.isEmpty ? null : text;
}
return DiveCanonDraft(
core: textOf('core'),
oracle: textOf('oracle'),
shadow: textOf('shadow'),
asks: textOf('asks'),
step: textOf('step'),
binds: binds,
cultures: cultures,
languages: languages,
layers: layers,
);
}
+63
View File
@@ -0,0 +1,63 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import '../models/lenormand_pairs.dart';
class OghamFedaRepository {
static final OghamFedaRepository _instance = OghamFedaRepository._internal();
factory OghamFedaRepository() => _instance;
OghamFedaRepository._internal();
final Map<int, LenormandCardEntry> _byN = {};
final Map<String, LenormandCardEntry> _byId = {};
bool _loaded = false;
LenormandCardEntry? byNumber(int n) => _byN[n];
LenormandCardEntry? byCanonId(String id) => _byId[id];
List<LenormandCardEntry> get allFedas => _byN.values.toList()..sort((a, b) => a.n.compareTo(b.n));
Future<void> init() async {
if (_loaded && _byN.isNotEmpty) return;
Map<String, dynamic>? decoded;
final cwd = Directory.current.path;
for (final path in {
'assets/data/ogham_feda.json',
'../assets/data/ogham_feda.json',
'$cwd/assets/data/ogham_feda.json',
}) {
try {
final file = File(path);
if (file.existsSync()) {
decoded = json.decode(file.readAsStringSync()) as Map<String, dynamic>;
break;
}
} catch (e) {
debugPrint('ogham_feda.json skip $path: $e');
}
}
if (decoded == null) {
try {
decoded = json.decode(await rootBundle.loadString('assets/data/ogham_feda.json'))
as Map<String, dynamic>;
} catch (e) {
debugPrint('Could not load ogham_feda.json: $e');
}
}
if (decoded == null) {
_loaded = true;
return;
}
_byN.clear();
_byId.clear();
for (final raw in (decoded['cards'] as List? ?? const [])) {
if (raw is! Map<String, dynamic>) continue;
final card = LenormandCardEntry.fromMap(raw);
_byN[card.n] = card;
_byId[card.id] = card;
}
_loaded = true;
}
}
@@ -0,0 +1,977 @@
import '../models/culture_branch.dart';
import '../models/syncretic_session_model.dart';
import '../models/history_entry_model.dart';
import 'words_repository.dart';
import '../models/houses_session_model.dart';
import '../models/word_model.dart';
import 'traditions_repository.dart';
import 'lenormand_pairs_repository.dart';
class OracleMasterPromptGenerator {
/// Generates the Grand Cross-System Comparison Master Prompt for Grok, Claude 3.5 Sonnet, GPT-4o, and Gemini 1.5 Pro.
static String generateCrossSystemComparison(SyncreticSession session) {
final buffer = StringBuffer();
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln('ВЕЛИКИЙ МЕТА-МАСТЕР-ПРОМПТ: СРАВНИТЕЛЬНЫЙ СИНХРОНИСТИЧЕСКИЙ АНАЛИЗ');
buffer.writeln('МУЛЬТИСИСТЕМНЫЙ СИНТЕЗ ТРАДИЦИЙ И САМОСОГЛАСОВАННОСТИ ЗНАКОВ');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln();
buffer.writeln('### СИСТЕМНАЯ РОЛЬ:');
buffer.writeln('Ты — архимагистр синкретической герменевтики, мастер сравнительного мистицизма и глубинной юнгианской психологии.');
buffer.writeln('Перед тобой единый протокол СИНХРОНИСТИЧЕСКОЙ СЕССИИ ДНЯ. Вопрошающий исследовал один вопрос через несколько независимых мантических систем древности и современности.');
buffer.writeln('Твоя миссия — провести мета-анализ: сопоставить язык каждой системы, выявить скрытые узлы схождения, обнаружить противоречия и выдать монолитный синтез высшего порядка.');
buffer.writeln();
// 1. Querent Question & Context
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. ФОКУС ВОПРОШАНИЯ (КВЕРЕНТ И КОНТЕКСТ ДНЯ):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('• Дата сессии: ' + session.formattedDate);
if (session.mainQuestion != null && session.mainQuestion!.trim().isNotEmpty) {
buffer.writeln('• Ключевой вопрос/тема: «' + session.mainQuestion!.trim() + '»');
} else {
buffer.writeln('• Ключевой вопрос/тема: Общий срез судьбы, скрытые подводные течения и фазовый переход кверента.');
}
buffer.writeln('• Число независимых гаданий в сессии: ' + session.readings.length.toString());
buffer.writeln();
// 2. Protocols of all readings
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('2. ПРОТОКОЛЫ ПОЛУЧЕННЫХ РАСКЛАДОВ ПО СИСТЕМАМ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final allMentionedWords = <WordItem>[];
for (int i = 0; i < session.readings.length; i++) {
final r = session.readings[i];
buffer.writeln('ВХОДНОЙ РАСКЛАД #' + (i + 1).toString() + ': ' + r.systemIcon + ' ' + r.systemName.toUpperCase());
buffer.writeln(' Время проведения: ' + r.formattedTime + ' | Метод ввода: ' + (r.inputMethod == 'dice' ? 'Жребий костей' : 'Прямой ввод со стола'));
buffer.writeln(' Формат расклада: ' + r.layoutTitle);
if (r.question != null && r.question!.trim().isNotEmpty && r.question != session.mainQuestion) {
buffer.writeln(' Уточняющий аспект: «' + r.question!.trim() + '»');
}
if (r.systemId == 'geomancy') {
final isShield = r.items.any((it) => it.title.toUpperCase().contains('СУДЬЯ') || it.title.toLowerCase().contains('мать'));
if (isShield) {
buffer.writeln(' 📐 СВЯЩЕННАЯ АРХИТЕКТУРА ВЕЛИКОГО ГЕОМАНТИЧЕСКОГО ЩИТА (16 САКРАЛЬНЫХ ФИГУР):');
buffer.writeln(' (Тетраграммная пирамида: Матери ➔ Дочери ➔ Племянники ➔ Свидетели ➔ Судья ➔ Примиритель)');
buffer.writeln();
void writeShieldItem(String roleHeader, ReadingSignItem it) {
buffer.writeln(' ' + roleHeader);
buffer.writeln(' • ' + (it.symbol != null ? it.symbol! + ' ' : '') + '«' + it.title.toUpperCase() + '»');
if (it.subtitle != null && it.subtitle!.isNotEmpty) {
buffer.writeln(' Атрибут: ' + it.subtitle!);
}
if (it.lore != null && it.lore!.isNotEmpty) {
buffer.writeln(' Канонический закон/значение: ' + it.lore!);
}
if (it.linkedWord != null) {
allMentionedWords.add(it.linkedWord!);
buffer.writeln(' 🔗 Связанный сакральный знак 999: #' + it.linkedWord!.formattedSessionNumber + ' «' + it.linkedWord!.word.toUpperCase() + '» (' + it.linkedWord!.category + ')');
if (it.linkedWord!.core.isNotEmpty) {
buffer.writeln(' Формула знака: ' + it.linkedWord!.core);
}
}
}
ReadingSignItem? findFirst(bool Function(ReadingSignItem) predicate) {
for (final it in r.items) {
if (predicate(it)) return it;
}
return null;
}
final judge = findFirst((it) => it.title.toUpperCase().contains('СУДЬЯ'));
final reconciler = findFirst((it) => it.title.toUpperCase().contains('ПРИМИРИТЕЛЬ'));
final rightWitness = findFirst((it) => it.title.toUpperCase().contains('ПРАВЫЙ СВИДЕТЕЛЬ'));
final leftWitness = findFirst((it) => it.title.toUpperCase().contains('ЛЕВЫЙ СВИДЕТЕЛЬ'));
final mothers = r.items.where((it) => it.title.toLowerCase().contains('мать')).toList();
final daughters = r.items.where((it) => it.title.toLowerCase().contains('дочь')).toList();
final nephews = r.items.where((it) => it.title.toLowerCase().contains('племянник')).toList();
if (judge != null) {
writeShieldItem('👑 ВЕРХОВНЫЙ СУДЬЯ (AL-QADI / JUDEX — ИТОГОВЫЙ ПРИГОВОР И ИСХОД СИТУАЦИИ):', judge);
buffer.writeln();
}
if (reconciler != null) {
writeShieldItem("🕊️ ПРИМИРИТЕЛЬ (AL-MUJAMMI' / 16-Я ФИГУРА — СНЯТИЕ ПРОТИВОРЕЧИЯ ИСТОКА И СУДА):", reconciler);
buffer.writeln();
}
if (rightWitness != null || leftWitness != null) {
buffer.writeln(' ⚖️ ДВА СВИДЕТЕЛЯ (SHAHIDAYN — ДИАЛЕКТИКА ВНЕШНЕГО И ВНУТРЕННЕГО):');
if (rightWitness != null) {
writeShieldItem(' [ПРАВЫЙ СВИДЕТЕЛЬ / ВНЕШНИЙ МИР И ОБСТОЯТЕЛЬСТВА]:', rightWitness);
}
if (leftWitness != null) {
writeShieldItem(' [ЛЕВЫЙ СВИДЕТЕЛЬ / ВНУТРЕННЕЕ СОСТОЯНИЕ И ДУША КВЕРЕНТА]:', leftWitness);
}
buffer.writeln();
}
if (mothers.isNotEmpty) {
buffer.writeln(' 🏛️ ЧЕТЫРЕ МАТЕРИ (UMMAHAAT — КОРНИ И ПЕРВИЧНЫЙ СТИХИЙНЫЙ ИМПУЛЬС):');
for (final m in mothers) {
writeShieldItem(' [МАТЬ]:', m);
}
buffer.writeln();
}
if (daughters.isNotEmpty) {
buffer.writeln(' 🌿 ЧЕТЫРЕ ДОЧЕРИ (BANAAT — РАЗВЁРТЫВАНИЕ В ПРОСТРАНСТВЕ И ВНЕШНЕЙ СРЕДЕ):');
for (final d in daughters) {
writeShieldItem(' [ДОЧЬ]:', d);
}
buffer.writeln();
}
if (nephews.isNotEmpty) {
buffer.writeln(' ⚡ ЧЕТЫРЕ ПЛЕМЯННИКА (ABNAA — СОБЫТИЙНЫЙ СРЕЗ И ПСИХОЛОГИЯ ОКРУЖЕНИЯ):');
for (final n in nephews) {
writeShieldItem(' [ПЛЕМЯННИК]:', n);
}
buffer.writeln();
}
buffer.writeln();
continue;
}
}
for (final item in r.items) {
final rev = item.isReversed ? ' [ПЕРЕВЁРНУТОЕ ПОЛОЖЕНИЕ / МЕРКСТЕЙВ]' : '';
buffer.writeln(' • ' + (item.symbol != null ? item.symbol! + ' ' : '') + '«' + item.title.toUpperCase() + '»' + rev);
if (item.subtitle != null && item.subtitle!.isNotEmpty) {
buffer.writeln(' Атрибут: ' + item.subtitle!);
}
if (item.lore != null && item.lore!.isNotEmpty) {
buffer.writeln(' Канонический закон/значение: ' + item.lore!);
}
if (item.linkedWord != null) {
allMentionedWords.add(item.linkedWord!);
buffer.writeln(' 🔗 Связанный сакральный знак 999: #' + item.linkedWord!.formattedSessionNumber + ' «' + item.linkedWord!.word.toUpperCase() + '» (' + item.linkedWord!.category + ')');
if (item.linkedWord!.core.isNotEmpty) {
buffer.writeln(' Формула знака: ' + item.linkedWord!.core);
}
}
}
buffer.writeln();
}
// 3. Cross-system Resonances
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3. КРОСС-СИСТЕМНЫЕ СВЯЗИ И СТИХИЙНЫЙ РЕЗОНАНС:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
var hasResonances = false;
for (final r in session.readings) {
if (r.systemId == 'geomancy') {
for (final it in r.items) {
if (it.title.toUpperCase().contains('СУДЬЯ')) {
buffer.writeln('• Геомантический вектор: Итоговый вердикт выносит Верховный Судья «' + it.title + '» (' + (it.subtitle ?? '') + '). Его стихийный закон является фокусом разрешения всей сессии.');
hasResonances = true;
}
}
}
}
if (allMentionedWords.isNotEmpty) {
final resonances = _detectSyncreticResonances(allMentionedWords);
if (resonances.isNotEmpty) {
for (final res in resonances) {
buffer.writeln('• ' + res);
hasResonances = true;
}
}
}
if (!hasResonances) {
buffer.writeln('• Прямой полифонический диалог традиций: системы дополняют друг друга через разные уровни восприятия (ментальный, событийный, бессознательный).');
}
buffer.writeln();
// 4. Instructions for Comparative Synthesis
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('4. ТРЕБОВАНИЯ К ТВОЕМУ СРАВНИТЕЛЬНОМУ МЕТА-АНАЛИЗУ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('Составь исчерпывающий, структурированный и философски безупречный отчёт:');
buffer.writeln('1. ТОЧКА КОНВЕРГЕНЦИИ (В ЧЁМ ВСЕ СИСТЕМЫ ЕДИНОГЛАСНЫ): Какая центральная истина повторяется на разных символических языках (в рунах, арканах, гексаграммах, знаках Оракула 999 и сакральных фигурах Арабской Геомантии)?');
buffer.writeln('2. ТОЧКИ ДИВЕРГЕНЦИИ И ПОЛЯРНОСТИ (СКРЫТЫЙ ДИАЛОГ СИСТЕМ): Где одна система предостерегает, а другая указывает на возможность? Какое диалектическое противоречие вскрыто?');
buffer.writeln('3. ПОУРОВНЕВАЯ КАРТА РЕАЛЬНОСТИ:');
buffer.writeln(' - Каузальный план (причины и судьба),');
buffer.writeln(' - Психологический план (состояние ума и тени кверента),');
buffer.writeln(' - Событийный план (ближайшая материя и динамика).');
buffer.writeln('4. ПРИГОВОР И ВЕРХОВНЫЙ СИНТЕЗ: К какому единственному выводу неизбежно подводят все проведённые расклады? Обязательно сопоставь вердикт Верховного Судьи Геомантии (Аль-Кади) и гармонизацию Примирителя с результирующими картами/рунами сессии.');
buffer.writeln('5. ТОЧНЫЙ НЕОБРАТИМЫЙ ЖЕСТ: Какое конкретное действие или внутреннее решение вопрошающий должен совершить прямо сейчас?');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
return buffer.toString();
}
/// Generates a Master Prompt for a standard Cleromancy Oracle Session (any number of words drawn).
static String generateForSession(OracleSession session) {
final buffer = StringBuffer();
final repo = WordsRepository();
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln('СВЯЩЕННЫЙ РАСКЛАД КЛЕРОМАНТИИ — СИСТЕМА «ОРАКУЛ»');
buffer.writeln('МАСТЕР-ПРОМПТ ДЛЯ ГЛУБИННОЙ ИНТЕРПРЕТАЦИИ НЕЙРОСЕТЬЮ');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln();
buffer.writeln('### СИСТЕМНАЯ РОЛЬ:');
buffer.writeln('Ты — верховный толкователь Оракула, мастер герметического синкретизма, онтологической семантики и глубинной психологии.');
buffer.writeln('Перед тобой полный протокол сакрального расклада Клеромантии (гадания по жребию знаков из канонической тысячи 000–999).');
buffer.writeln('Наша система включает канонические соответствия Таро, Старшего Футарка, Огама, Ленорман, Каббалы (Сефирот), Алхимии, Геомантии, И Цзин, Классического и Китайского Зодиака, а также Септенера (7 Дней Недели) и Додекады (12 Месяцев Года).');
buffer.writeln('Твоя задача — дать бескомпромиссный, психологически точный и эзотерически глубокий анализ ситуации, без клише, размытых фраз и банальностей.');
buffer.writeln();
// 1. Context and Question
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. КОНТЕКСТ И ЗАПРОС ВОПРОШАЮЩЕГО (КВЕРЕНТА):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('• Дата и время сессии: ' + session.formattedDate);
if (session.question != null && session.question!.trim().isNotEmpty) {
buffer.writeln('• Фокус вопроса: «' + session.question!.trim() + '»');
} else {
buffer.writeln('• Фокус вопроса: Общее прорицание узлов судьбы, скрытых напряжений и грядущего вектора.');
}
buffer.writeln('• Количество выпавших знаков в цепи: ' + session.words.length.toString());
buffer.writeln();
// 2. The Chain of Signs
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('2. ЦЕПЬ ВЫПАВШИХ САКРАЛЬНЫХ ЗНАКОВ (ПОЛНОЕ ДОСЬЕ И СЛОИ):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final fullWords = <WordItem>[];
for (int i = 0; i < session.words.length; i++) {
final sw = session.words[i];
final fw = repo.getWordByName(sw.word) ??
repo.getWordBySessionNumber(sw.sessionNumber) ??
WordItem(
id: sw.sessionNumber,
word: sw.word,
partOfSpeech: sw.partOfSpeech,
frequency: 0,
category: sw.category,
description: sw.symbolicMeaning,
);
fullWords.add(fw);
final posTitle = sw.positionTitle != null ? ' [' + sw.positionTitle! + ']' : '';
buffer.writeln('ЗНАК #' + (i + 1).toString() + ': #' + sw.formattedSessionNumber + ' «' + fw.word.toUpperCase() + '»' + posTitle);
buffer.writeln(' Часть речи: ' + fw.partOfSpeech + ' | Категория: ' + fw.category + ' | Оператор: ' + fw.localizedOperator + ' | Валентность: ' + fw.localizedValence);
if (fw.core.isNotEmpty) {
buffer.writeln(' ⚡ [ЯДРО ЗНАКА]: ' + fw.core);
}
final cleanDesc = fw.description.replaceAll(RegExp(r'\[.*?\]\s*'), '').trim();
if (cleanDesc.isNotEmpty) {
buffer.writeln(' 📖 [КАНОНИЧЕСКИЙ СМЫСЛ]: ' + cleanDesc);
}
if (fw.oracle.isNotEmpty) {
buffer.writeln(' 🔮 [ТОЛКОВАНИЕ ОРАКУЛА]: ' + fw.oracle);
}
if (fw.shadow.isNotEmpty) {
buffer.writeln(' 🌑 [ТЕНЬ И ИСКУШЕНИЕ]: ' + fw.shadow);
}
if (fw.asks.isNotEmpty) {
buffer.writeln(' ❓ [ДИАГНОСТИЧЕСКИЙ ВОПРОС]: ' + fw.asks);
}
if (fw.step.isNotEmpty) {
buffer.writeln(' ✨ [НЕОБРАТИМЫЙ ЖЕСТ / ДЕЙСТВИЕ]: ' + fw.step);
}
if (fw.binds.isNotEmpty) {
buffer.writeln(' 🔗 [СОЗВУЧНЫЕ СВЯЗКИ]: ' + fw.binds.join(', '));
}
final trad = _formatTraditions(fw, prefix: ' ');
if (trad.isNotEmpty) {
buffer.write(trad);
}
buffer.writeln();
}
// 3. Syncretic Resonances and Geometry of Signs
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3. МЕЖСИСТЕМНЫЕ РЕЗОНАНСЫ И ПАРНЫЕ СВЯЗИ В ЦЕПИ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final resonances = _detectSyncreticResonances(fullWords);
if (resonances.isNotEmpty) {
for (final r in resonances) {
buffer.writeln('• ' + r);
}
} else {
buffer.writeln('• Прямых полярных пар не обнаружено; знаки образуют распределённую волну смыслов.');
}
// Valence balance
final activeCount = fullWords.where((w) => w.valence.toLowerCase() == 'active' || w.valence.toLowerCase() == 'light').length;
final receptiveCount = fullWords.where((w) => w.valence.toLowerCase() == 'receptive' || w.valence.toLowerCase() == 'dark').length;
final neutralCount = fullWords.length - activeCount - receptiveCount;
buffer.writeln('• Энергетический баланс цепи: Активных (Ян/Огонь-Воздух) = ' + activeCount.toString() + ', Восприимчивых (Инь/Вода-Земля) = ' + receptiveCount.toString() + ', Нейтральных = ' + neutralCount.toString());
buffer.writeln();
// 4. Detailed instructions for LLM
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('4. ТРЕБОВАНИЯ К ТВОЕМУ ОТВЕТУ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('Построй своё прорицание по следующим блокам:');
buffer.writeln('1. ДРАМАТУРГИЯ ЦЕПИ: Как знаки перетекают друг в друга от первого к последнему? Какой сюжет разворачивается?');
buffer.writeln('2. РАЗБОР КАЖДОГО ЗНАКА В КОНТЕКСТЕ ВОПРОСА: Проанализируй влияние каждого символа, его ядро и проявление через традиции.');
buffer.writeln('3. ТЕНЕВОЙ УЗЕЛ И ЛОВУШКА: Где вопрошающий рискует оступиться? Что скрыто за фасадом очевидного?');
buffer.writeln('4. РЕЗОНАНСЫ ТРАДИЦИЙ: Как вскрытые арканы, руны, планеты и гексаграммы обогащают понимание?');
buffer.writeln('5. ИТОГОВЫЙ ПРИГОВОР ОРАКУЛА И ТОЧНЫЙ ШАГ: Сформулируй ясный, бескомпромиссный вывод и конкретный внутренний или внешний жест для вопрошающего.');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
return buffer.toString();
}
/// Generates a comprehensive, high-caliber Master Prompt for advanced LLMs (Grok, Claude 3.5 Sonnet, Gemini 1.5 Pro, GPT-4o).
static String generate(HousesSession session) {
final buffer = StringBuffer();
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln('СВЯЩЕННЫЙ РАСКЛАД ПОЛОТНА ДОМОВ 33×30 — СИСТЕМА «ОРАКУЛ»');
buffer.writeln('МАСТЕР-ПРОМПТ ДЛЯ ГЛУБИННОЙ ИНТЕРПРЕТАЦИИ НЕЙРОСЕТЬЮ');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln();
buffer.writeln('### СИСТЕМНАЯ РОЛЬ:');
buffer.writeln('Ты — верховный толкователь Оракула, мастер герметического синкретизма, онтологической семантики и глубинной психологии.');
buffer.writeln('Перед тобой полный протокол сакрального расклада на поле 33×30 (990 земных Домов и 10 верховных Архонтов-управителей).');
buffer.writeln('Десять Архонтов — существительные, вытянутые заново для ЭТОЙ сессии. Это не закреплённые клетки 000 и 991–999.');
buffer.writeln('Наша система включает канонические соответствия Таро, Старшего Футарка, Огама, Ленорман, Каббалы (Сефирот), Алхимии, Геомантии, И Цзин, Классического и Китайского Зодиака, а также Септенера (7 Дней Недели) и Додекады (12 Месяцев Года).');
buffer.writeln('Твоя задача — дать бескомпромиссный, психологически точный и эзотерически глубокий анализ ситуации, без клише, размытых фраз и банальностей.');
buffer.writeln();
// 1. Querent (Significator)
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. СУБЪЕКТ ВОПРОШАНИЯ (КВЕРЕНТ / СИГНИФИКАТОР):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
if (session.querentWord != null) {
final q = session.querentWord!;
var loc = '';
if (session.querentHouseIndex != null) {
final hIdx = (session.querentHouseIndex! + 1).toString().padLeft(3, '0');
final r = session.querentHouseIndex! ~/ 30 + 1;
final c = session.querentHouseIndex! % 30 + 1;
loc = ' | Локация: Дом #' + hIdx + ' (Ряд ' + r.toString() + ', Столбец ' + c.toString() + ')';
}
buffer.writeln('• Кверент (Сигнификатор): «' + q.word.toUpperCase() + '»' + loc);
final summary = q.core.isNotEmpty
? q.core
: (q.description.isNotEmpty
? q.description.replaceAll(RegExp(r'\[.*?\]\s*'), '').trim()
: q.category);
buffer.writeln(' Краткая сводка: ' + summary);
} else {
buffer.writeln('• Кверент: Открытый фокус поля (вопрошающий без фиксированного сигнификатора).');
}
if (session.question != null && session.question!.trim().isNotEmpty) {
buffer.writeln('• Вопрос кверента: «' + session.question!.trim() + '»');
} else {
buffer.writeln('• Вопрос кверента: Общее прорицание узлов судьбы и скрытых течений ситуации.');
}
buffer.writeln();
// 2. The 3-tier hierarchy: Archon -> House -> Guest
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('2. АРХИТЕКТУРА РАСКЛАДА (ИЕРАРХИЯ: АРХОНТ ➔ ДОМ ➔ ГОСТЬ):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('В каждом открытом доме действуют 3 уровня реальности:');
buffer.writeln(' [А] Верховный Архонт — надмирный закон сектора (триады столбцов), небесный климат.');
buffer.writeln(' [Б] Земной Дом — конкретная сфера бытия, почва, материя и форма.');
buffer.writeln(' [В] Знак-Гость — импульс жребия, живая сила, вошедшая в этот дом.');
buffer.writeln();
final allGuests = <WordItem>[];
for (int i = 0; i < session.selectedHouses.length; i++) {
final house = session.selectedHouses[i];
final sector = session.sectors[house.sectorIndex];
final guest = house.guestWord ?? house.houseWord;
allGuests.add(guest);
final order = house.selectionOrder ?? (i + 1);
final hPad = (house.houseIndex + 1).toString().padLeft(3, '0');
buffer.writeln('ПОЗИЦИЯ #' + order.toString() + ' (Дом #' + hPad + ', Ряд ' + (house.row + 1).toString() + ', Столбец ' + (house.col + 1).toString() + '):');
buffer.writeln(' 👑 [АРХОНТ]: «' + sector.archonWord.word.toUpperCase() + '» (' + sector.title + ')');
if (sector.archonWord.core.isNotEmpty) {
buffer.writeln(' Закон Архонта: ' + sector.archonWord.core);
}
final archonTrad = _formatTraditions(sector.archonWord, prefix: ' ');
if (archonTrad.isNotEmpty) {
buffer.write(archonTrad);
}
buffer.writeln(' 🏛️ [ДОМ]: «' + house.houseWord.word.toUpperCase() + '» (' + house.houseWord.category + ')');
if (house.houseWord.core.isNotEmpty) {
buffer.writeln(' Ядро Дома: ' + house.houseWord.core);
}
final houseTrad = _formatTraditions(house.houseWord, prefix: ' ');
if (houseTrad.isNotEmpty) {
buffer.write(houseTrad);
}
buffer.writeln(' ⚡ [ГОСТЬ В ДОМЕ]: «' + guest.word.toUpperCase() + '» (' + guest.partOfSpeech + ', ' + guest.category + ')');
if (guest.core.isNotEmpty) {
buffer.writeln(' Суть гостя: ' + guest.core);
}
if (guest.oracle.isNotEmpty) {
buffer.writeln(' Физика действия гостя: ' + guest.oracle);
}
if (guest.shadow.isNotEmpty) {
buffer.writeln(' Тень и ловушка гостя: ' + guest.shadow);
}
if (guest.asks.isNotEmpty) {
buffer.writeln(' Диагностический вопрос: ' + guest.asks);
}
if (guest.step.isNotEmpty) {
buffer.writeln(' Конкретный шаг заземления: ' + guest.step);
}
if (guest.binds.isNotEmpty) {
buffer.writeln(' Связанные союзные знаки: ' + guest.binds.join(', '));
}
final guestTrad = _formatTraditions(guest, prefix: ' ');
if (guestTrad.isNotEmpty) {
buffer.write(guestTrad);
}
buffer.writeln();
}
// 3. Sacred Geometry of the Field
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3. САКРАЛЬНАЯ ГЕОМЕТРИЯ МАТРИЦЫ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final pointsCount = session.selectedHouses.length;
String figureName;
switch (pointsCount) {
case 1: figureName = 'Монада (Точка Первоистока)'; break;
case 2: figureName = 'Диада (Вектор Полярности)'; break;
case 3: figureName = 'Триада (Дельта Творения)'; break;
case 4: figureName = 'Тетрада (Земной Четверик)'; break;
case 5: figureName = 'Пентаграмма (Узел Пяти Стихий)'; break;
case 6: figureName = 'Гексаграмма (Печать Равновесия)'; break;
case 7: figureName = 'Гептада (Семизвездный Венец)'; break;
case 8: figureName = 'Октада (Роза Пространства)'; break;
case 9: figureName = 'Эннеаграмма (Девятикратное Полотно Судьбы)'; break;
default: figureName = 'Сакральный Сигил (' + pointsCount.toString() + ' точек)';
}
buffer.writeln('• Фигура расклада: ' + figureName);
final visitedSectors = session.selectedHouses.map((h) => h.sectorIndex + 1).toSet().toList()..sort();
buffer.writeln('• Задействованные Секторы Архонтов: Триады столбцов № ' + visitedSectors.join(', ') + ' из 10');
buffer.writeln('• Координаты узлов на сетке 33×30:');
for (final h in session.selectedHouses) {
buffer.writeln(' - Узел #' + (h.selectionOrder ?? 0).toString() + ': Дом «' + h.houseWord.word + '» в координатах (r: ' + (h.row + 1).toString() + ', c: ' + (h.col + 1).toString() + ')');
}
buffer.writeln();
// 3.1. Syncretic Resonances across drawn symbols
final resonances = _detectSyncreticResonances(allGuests);
if (resonances.isNotEmpty) {
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3.1. СИНКРЕТИЧЕСКИЕ РЕЗОНАНСЫ И ПАРЫ В РАСКЛАДЕ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
for (final r in resonances) {
buffer.writeln('• ' + r);
}
buffer.writeln();
}
// 4. Chains of Houses & Fate Loops
if (session.chains.isNotEmpty) {
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('4. ЦЕПОЧКИ ДОМОВ И ПЕТЛИ СУДЬБЫ (ТРАССИРОВКА ДО 9 ЗВЕНЬЕВ):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('Энергия каждого гостя ищет свой дом на матрице, создавая цепочки перетекания силы:');
for (final chain in session.chains) {
buffer.writeln('• ЦЕПОЧКА #' + chain.chainIndex.toString() + ' [' + chain.endTypeTitle + '] (' + chain.steps.length.toString() + ' звеньев):');
buffer.writeln(' Итог: ' + chain.description);
buffer.writeln(' Маршрут импульса:');
for (int s = 0; s < chain.steps.length; s++) {
final step = chain.steps[s];
final sector = session.sectors[step.house.sectorIndex];
final sIdx = (step.house.houseIndex + 1).toString();
String stepDesc = ' ' + (s + 1).toString() + '. Дом #' + sIdx + ' «' + step.house.houseWord.word + '» [Архонт: ' + sector.archonWord.word + '] ➔ Гость «' + step.guest.word + '»';
if (step.isLoopClosing) stepDesc += ' 🔄 [ПЕТЛЯ ЗАМКНУЛАСЬ]';
if (step.leadsToArchon) stepDesc += ' 👑 [ПРЕСТОЛ АРХОНТА «' + (step.destinationArchon?.archonWord.word ?? '') + '»]';
if (step.computed) stepDesc += ' ◎ [ВЫЧИСЛЕНО ИЗ ПОЛЯ, НЕ ЖРЕБИЙ]';
buffer.writeln(stepDesc);
}
buffer.writeln();
}
}
// 5. Instruction for the AI Interpreter
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('5. ИНСТРУКЦИЯ ДЛЯ ТОЛКОВАНИЯ (ТВОЙ ПЛАН ОТВЕТА):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('Составь исчерпывающий мета-анализ расклада, следуя структуре:');
buffer.writeln('1. СУТЬ И ЦЕНТР ТЯЖЕСТИ: Как ситуация соотносится с Кверентом? Что является главным нервом вопроса?');
buffer.writeln('2. СИНТЕЗ ДОМОВ И ГОСТЕЙ: Раскрой взаимодействие: как каждый Гость трансформирует свой Дом под волей правящего Архонта, опираясь на сакральные слои (Таро, Руны, Огам, Ленорман, Каббалу, Септенер дней недели и Додекаду месяцев).');
buffer.writeln('3. СИНКРЕТИЧЕСКИЕ СВЯЗИ И РЕЗОНАНСЫ: Учти взаимное влияние планет, дней, месяцев, пар Ленорман и стихий, проявившихся в раскладе.');
buffer.writeln('4. АНАЛИЗ ЦЕПОЧЕК И ПЕТЕЛЬ: В чем главная скрытая динамика? Куда утекает или где зацикливается энергия (петли судьбы)?');
buffer.writeln('5. ТЕНИ И СКРЫТЫЕ ЛОВУШКИ: От каких иллюзий, спешки или самообмана предостерегает жребий?');
buffer.writeln('6. ИТОГОВЫЙ ПРИГОВОР И РИТУАЛЬНОЕ ДЕЙСТВИЕ: Три честных вывода и один бескомпромиссный физический шаг, который вопрошающий должен совершить прямо сейчас.');
return buffer.toString();
}
static String _formatTraditions(WordItem item, {required String prefix}) {
final sb = StringBuffer();
// 1. Explicit layers
if (item.layers.isNotEmpty) {
for (final l in item.layers) {
sb.writeln(prefix + '🏛️ [Слой «' + WordItemLocalization.localizeSystem(l.system) + '»]: ' + l.title + ' — ' + l.law);
}
}
// 2. Traditions Repository matches
final repo = TraditionsRepository();
final matches = repo.findMatchesForWord(item.word, wordId: item.id);
if (matches.isNotEmpty) {
for (final m in matches) {
final t = m.tradition;
final st = m.station;
final status = m.isPrimary ? 'Ключевой Архетип' : 'Кластер / Эманация';
sb.writeln(prefix + '📜 [' + t.name + ']: ' + st.name + ' ' + st.symbol + ' (' + status + ')');
if (m.isPrimary && st.lore.isNotEmpty) {
final loreSnippet = st.lore.length > 220 ? st.lore.substring(0, 220).trim() + '...' : st.lore.trim();
sb.writeln(prefix + ' Мистерия: ' + loreSnippet);
}
}
}
return sb.toString();
}
static List<String> _detectSyncreticResonances(List<WordItem> words) {
final resonances = <String>[];
final wordNames = words.map((w) => w.word.trim().toLowerCase()).toSet();
// Check Day of Week & Planet alignments
const dayPlanetPairs = {
'понедельник': 'луна',
'вторник': 'марс',
'среда': 'меркурий',
'четверг': 'юпитер',
'пятница': 'венера',
'суббота': 'сатурн',
'воскресенье': 'солнце',
};
for (final entry in dayPlanetPairs.entries) {
if (wordNames.contains(entry.key) && wordNames.contains(entry.value)) {
resonances.add('Планетарный Септенер: созвучие дня «' + entry.key.toUpperCase() + '» и управляющей планеты «' + entry.value.toUpperCase() + '» — прямая манифестация космического ритма.');
}
}
// Check Month & Zodiac resonances
const monthZodiacPairs = {
'март': 'овен',
'апрель': 'телец',
'май': 'близнецы',
'июнь': 'рак',
'июль': 'лев',
'август': 'дева',
'сентябрь': 'весы',
'октябрь': 'скорпион',
'ноябрь': 'стрелец',
'декабрь': 'козерог',
'январь': 'козерог',
'февраль': 'водолей',
};
for (final entry in monthZodiacPairs.entries) {
if (wordNames.contains(entry.key) && wordNames.contains(entry.value)) {
resonances.add('Зодиакальный цикл: солнечные врата месяца «' + entry.key.toUpperCase() + '» активируют знак «' + entry.value.toUpperCase() + '».');
}
}
// Check Solstices / Equinoxes
if (wordNames.contains('март') && wordNames.contains('сентябрь')) {
resonances.add('Ось Равноденствий: в раскладе проявились врата Весны (Март) и Осени (Сентябрь) — тема великого равновесия дня и ночи.');
}
if (wordNames.contains('июнь') && wordNames.contains('декабрь')) {
resonances.add('Ось Солнцестояний: противоположности Света (Июнь) и Тьмы (Декабрь) — максимальная амплитуда года.');
}
// Check Lenormand Cards present
final lenormandRepo = LenormandPairsRepository();
if (lenormandRepo.isLoaded) {
final lenormandFound = <String>[];
for (final w in words) {
final wClean = w.word.trim().toLowerCase();
for (final c in lenormandRepo.cards) {
if (c.name.toLowerCase().contains(wClean) || c.id.toLowerCase().contains(wClean)) {
lenormandFound.add('№${c.n} ${c.name}');
}
}
}
if (lenormandFound.length >= 2) {
resonances.add('Связка Ленорман: в раскладе открылись карты ' + lenormandFound.join(' и ') + ' — их комбинаторное чтение усиливает динамику домов.');
}
}
return resonances;
}
/// Generates the Master Prompt for Deep Culturological & Mythological Enrichment of an Archetype.
static String generateArchetypeEnrichmentPrompt(
WordItem word, {
String? customCore,
String? customOracle,
String? customShadow,
String? customAsks,
String? customStep,
}) {
final buffer = StringBuffer();
buffer.writeln('ТВОЯ РОЛЬ И ЗАДАЧА:');
buffer.writeln('Ты — Архитектор Смыслов, верховный мифолог, культуролог и онтологический герменевт.');
buffer.writeln('Твоя задача — провести глубочайшее, бескомпромиссное и многомерное ОБОГАЩЕНИЕ сакрального Архетипа. Напиши захватывающую, монументальную, живую и литературную статью-эссе, которую по-настоящему интересно читать.');
buffer.writeln();
buffer.writeln('СТРОЖАЙШИЕ ПРАВИЛА:');
buffer.writeln('Абсолютный запрет на гадательную терминологию: НИКАКИХ упоминаний «оракулов», «раскладов», «карт», «вопрошающих», «гаданий» и «ответов на вопрос». Архетип описывается как фундаментальный, безжалостный закон Вселенной, физический и мистический принцип, формирующий саму реальность и событийное поле человека.');
buffer.writeln('Запрет на лень и краткость: Текст должен быть длинным, плотным, перенасыщенным фактурой. Разворачивай мысль на полную мощность.');
buffer.writeln('Обязательный мультикультурный синтез. В тексте должны быть органично сплетены:');
buffer.writeln('• Точная этимология (праиндоевропейская и др.).');
buffer.writeln('• Древние космогонии максимального спектра: Шумер/Аккад, Египет, Древняя Греция, Иудаизм, Кельты, Суфизм/Арабская традиция и Геомантия (\'Илм ар-рамл / стихийные тетраграммы), Китай (Дао), Полинезия.');
buffer.writeln('• Научный срез: квантовая механика (наблюдатель, суперпозиция, коллапс), астрофизика, биология или нейрофизиология.');
buffer.writeln('• Алхимия и герметизм (трансформация элементов).');
buffer.writeln('Метрика: В самом конце ответа ты обязан выдать точное количество символов, потраченных на генерацию текста.');
buffer.writeln('═══════════════════════════════════════');
buffer.writeln('ВХОДНЫЕ ДАННЫЕ АРХЕТИПА:');
buffer.writeln('• Знак: #' + word.formattedSessionNumber + ' «' + word.word.toUpperCase() + '» ' + word.emoji);
final coreVal = (customCore != null && customCore.trim().isNotEmpty)
? customCore.trim()
: (word.core.isNotEmpty ? word.core : word.cleanDescription);
buffer.writeln('• Ядро/Текущая формула: ' + coreVal);
if (word.partOfSpeech.isNotEmpty) buffer.writeln('• Часть речи: ' + word.partOfSpeech);
if (word.category.isNotEmpty) buffer.writeln('• Категория: ' + word.category);
if (word.operator.isNotEmpty) buffer.writeln('• Оператор: ' + word.localizedOperator);
if (word.valence.isNotEmpty) buffer.writeln('• Полярность: ' + word.localizedValence);
final oracleVal = (customOracle != null && customOracle.trim().isNotEmpty)
? customOracle.trim()
: (word.oracle.isNotEmpty ? word.oracle : '');
if (oracleVal.isNotEmpty) {
buffer.writeln('• Текущая наработка: ' + oracleVal);
}
final shadowVal = (customShadow != null && customShadow.trim().isNotEmpty)
? customShadow.trim()
: word.shadow;
if (shadowVal.isNotEmpty) {
buffer.writeln('• Текущая тень: ' + shadowVal);
}
if (word.layers.isNotEmpty) {
buffer.writeln('• Традиции и пласты:');
for (final l in word.layers) {
final sysRu = WordItemLocalization.localizeSystem(l.system);
buffer.writeln(' - [' + sysRu + ']: ' + l.title + ' — ' + l.law);
}
}
if (word.binds.isNotEmpty) {
buffer.writeln('• Созвучные узлы: ' + word.binds.join(', '));
}
buffer.writeln('═══════════════════════════════════════');
buffer.writeln('ТРЕБОВАНИЯ К ФОРМАТУ ВЫДАЧИ (СТРОГО MARKDOWN С ЗАГОЛОВКАМИ ##):');
buffer.writeln('## ЯДРО');
buffer.writeln('(1–2 чеканных, безжалостных афористичных предложения: онтологическая формула непреложного закона бытия. Никакой воды, только концентрат сути).');
buffer.writeln();
buffer.writeln('## ОРАКУЛ');
buffer.writeln('(Монолитная, развернутая статья-эссе на 5–8 глубоких абзацев.');
buffer.writeln('• Абзац 1-2: Этимология и физическая/биологическая/квантовая природа явления. Разрушение обыденного восприятия.');
buffer.writeln('• Абзац 3-5: Глубокая мифология, геомантия и космогония (Шумер, Египет, Иврит, Кельты, Арабская геомантия \'Илм ар-рамл, Дальний Восток и т.д.) — как разные цивилизации и сакральные науки описывали этот закон.');
buffer.writeln('• Абзац 6-7: Алхимия и событийное поле. Как этот закон действует как химический реагент в реальности человека, как он ломает и трансформирует ситуацию. Без эзотерической воды — суровая прикладная философия).');
buffer.writeln();
buffer.writeln('## ТЕНЬ');
buffer.writeln('(Теневая ловушка принципа. Описать искажение, избыток или дефицит, слепую зону, эскапизм, паралич или выгорание. Как человек обманывает себя, попадая в искривленное поле этого архетипа).');
buffer.writeln();
buffer.writeln('## ВОПРОС');
buffer.writeln('(Острый, как скальпель, экзистенциальный коан, бьющий в самую суть ситуации. Безжалостный вопрос для глубокой рефлексии).');
buffer.writeln();
buffer.writeln('## ЖЕСТ');
buffer.writeln('(Одно конкретное материальное, физиологическое микродействие в реальном физическом мире для закрепления осознания. Это НЕ должна быть медитация в уме — это должно быть физическое действие с материей, пространством или телом).');
buffer.writeln();
buffer.writeln('## КУЛЬТУРЫ');
buffer.writeln('(Учебный сравнительный атлас: как этот знак понимали разные ветви человечества. Для КАЖДОЙ традиции — отдельный подзаголовок ### с точным названием из списка ниже и 2–5 предложений строго фактического материала: исконное слово/иероглиф/знак с транслитерацией, божества и мифы, ритуалы, первоисточник (текст, эпос, памятник). Не выдумывай: если у традиции нет достоверного материала по этому знаку — пропусти её, а не фантазируй. В конце — подзаголовок ### Сравнение: 3–4 предложения о том, где традиции сходятся и где принципиально расходятся.');
for (final b in kCultureBranches) {
buffer.writeln('### ' + b.name);
}
buffer.writeln('### Сравнение');
return buffer.toString();
}
/// Generates the Grand Master Prompt for Arabic Geomancy ('Ilm al-Raml) & the Great Shield.
static String generateGeomancyMasterPrompt({
required String question,
required String layoutTitle,
required bool isShield,
List<GeomancyPromptFigure> figures = const [],
List<GeomancyPromptFigure> mothers = const [],
List<GeomancyPromptFigure> daughters = const [],
List<GeomancyPromptFigure> nephews = const [],
GeomancyPromptFigure? rightWitness,
GeomancyPromptFigure? leftWitness,
GeomancyPromptFigure? judge,
GeomancyPromptFigure? reconciler,
}) {
final buffer = StringBuffer();
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln("ВЕЛИКИЙ МАСТЕР-ПРОМПТ: АРАБСКАЯ ГЕОМАНТИЯ ('ИЛМ АР-РАМЛ)");
buffer.writeln('САКРАЛЬНАЯ ПУНКТУАЦИЯ ПЕСКА, ВЕЛИКИЙ ЩИТ И СТИХИЙНЫЕ ТЕТРАГРАММЫ');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln();
buffer.writeln('### СИСТЕМНАЯ РОЛЬ:');
buffer.writeln("Ты — верховный геомант, хранитель традиции 'Илм ар-рамл («науки о песке») и мастер герметической астро-геомантии.");
buffer.writeln('Перед тобой священный протокол геомантического гадания. Каждая фигура представляет собой тетраграмму четырёх первоэлементов (Огонь, Воздух, Вода, Земля): нечётное число точек (1) обозначает активную проявленную силу, чётное (2) — скрытую потенциальную емкость.');
buffer.writeln('Твоя миссия — провести всесторонний, глубокий и бескомпромиссный анализ расклада песка, вскрыть динамику стихий и выдать монолитный, мудрый и прикладной вердикт.');
buffer.writeln();
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. ФОКУС И ВОПРОС КВЕРЕНТА:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final q = question.trim().isNotEmpty ? question.trim() : 'Общий вектор судьбы, скрытые стихийные силы и неизбежный исход.';
buffer.writeln('• Вопрос кверента: «' + q + '»');
buffer.writeln('• Формат расклада: ' + layoutTitle);
buffer.writeln("• Мантическая традиция: Классическая арабская геомантия ('Илм ар-рамл) и средневековый европейский геомантический щит");
buffer.writeln();
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('2. КАРТА ВЫПАВШИХ ФИГУР:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
if (isShield) {
if (judge != null) {
buffer.writeln('ВЕРХОВНЫЙ СУДЬЯ (AL-QADI / JUDEX):');
buffer.writeln('• ' + judge.name + ' (' + judge.latinName + (judge.arabicName.isNotEmpty ? ' / ' + judge.arabicName : '') + ')');
buffer.writeln(' Стихия: ' + judge.element + ' | Планета/Зодиак: ' + judge.planet);
buffer.writeln(' Тетраграмма стихий:');
buffer.writeln(judge.dotsDisplay);
buffer.writeln(' Канонический смысл: ' + judge.lore);
if (judge.linkedWord != null) {
buffer.writeln(' Связанный архетип Оракула 999: #' + judge.linkedWord!.formattedSessionNumber + ' «' + judge.linkedWord!.word.toUpperCase() + '» (' + judge.linkedWord!.core + ')');
}
buffer.writeln();
}
if (reconciler != null) {
buffer.writeln("ПРИМИРИТЕЛЬ (AL-MUJAMMI' / RECONCILER — 16-я сакральная фигура):");
buffer.writeln('• ' + reconciler.name + ' (' + reconciler.latinName + (reconciler.arabicName.isNotEmpty ? ' / ' + reconciler.arabicName : '') + ')');
buffer.writeln(' Стихия: ' + reconciler.element + ' | Планета/Зодиак: ' + reconciler.planet);
buffer.writeln(' Тетраграмма стихий:');
buffer.writeln(reconciler.dotsDisplay);
buffer.writeln(' Связь: Сложение Судьи и 1-й Матери (разрешение конфликта между истоком и исходом)');
buffer.writeln(' Канонический смысл: ' + reconciler.lore);
if (reconciler.linkedWord != null) {
buffer.writeln(' Связанный архетип Оракула 999: #' + reconciler.linkedWord!.formattedSessionNumber + ' «' + reconciler.linkedWord!.word.toUpperCase() + '» (' + reconciler.linkedWord!.core + ')');
}
buffer.writeln();
}
buffer.writeln('ДВА СВИДЕТЕЛЯ (SHAHIDAYN):');
if (rightWitness != null) {
buffer.writeln('• Правый Свидетель (Объективный мир, действие, прошлое): ' + rightWitness.name);
buffer.writeln(' ' + rightWitness.element + ' • ' + rightWitness.planet + ' | ' + rightWitness.lore);
if (rightWitness.linkedWord != null) {
buffer.writeln(' Связанный знак: #' + rightWitness.linkedWord!.formattedSessionNumber + ' «' + rightWitness.linkedWord!.word.toUpperCase() + '»');
}
}
if (leftWitness != null) {
buffer.writeln('• Левый Свидетель (Субъективный мир, чувства, будущее): ' + leftWitness.name);
buffer.writeln(' ' + leftWitness.element + ' • ' + leftWitness.planet + ' | ' + leftWitness.lore);
if (leftWitness.linkedWord != null) {
buffer.writeln(' Связанный знак: #' + leftWitness.linkedWord!.formattedSessionNumber + ' «' + leftWitness.linkedWord!.word.toUpperCase() + '»');
}
}
buffer.writeln();
buffer.writeln('ЧЕТЫРЕ МАТЕРИ (UMMAHAAT — Первичный стихийный исток ситуации):');
for (int i = 0; i < mothers.length; i++) {
final m = mothers[i];
buffer.writeln('• Мать #' + (i + 1).toString() + ': ' + m.name + ' (' + m.element + ', ' + m.planet + ') — ' + m.lore);
if (m.linkedWord != null) {
buffer.writeln(' Знак 999: #' + m.linkedWord!.formattedSessionNumber + ' «' + m.linkedWord!.word.toUpperCase() + '»');
}
}
buffer.writeln();
buffer.writeln('ЧЕТЫРЕ ДОЧЕРИ (BANAAT — Горизонтальная проекция, внешняя среда):');
for (int i = 0; i < daughters.length; i++) {
final d = daughters[i];
buffer.writeln('• Дочь #' + (i + 1).toString() + ': ' + d.name + ' (' + d.element + ', ' + d.planet + ') — ' + d.lore);
}
buffer.writeln();
buffer.writeln('ЧЕТЫРЕ ПЛЕМЯННИКА (ABNAA — Психика кверента и окружение):');
for (int i = 0; i < nephews.length; i++) {
final n = nephews[i];
buffer.writeln('• Племянник #' + (i + 1).toString() + ': ' + n.name + ' (' + n.element + ', ' + n.planet + ') — ' + n.lore);
}
buffer.writeln();
} else {
for (final f in figures) {
buffer.writeln('• Позиция: ' + f.position);
buffer.writeln(' Фигура: ' + f.name + ' (' + f.latinName + (f.arabicName.isNotEmpty ? ' / ' + f.arabicName : '') + ')');
buffer.writeln(' Стихия: ' + f.element + ' | Планета/Зодиак: ' + f.planet);
buffer.writeln(' Тетраграмма:');
buffer.writeln(f.dotsDisplay);
buffer.writeln(' Суть: ' + f.lore);
if (f.linkedWord != null) {
buffer.writeln(' Связанный знак 999: #' + f.linkedWord!.formattedSessionNumber + ' «' + f.linkedWord!.word.toUpperCase() + '» (' + f.linkedWord!.core + ')');
}
buffer.writeln();
}
}
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3. ТРЕБОВАНИЯ К ИНТЕРПРЕТАЦИИ НЕЙРОСЕТЬЮ (СТРОГАЯ СТРУКТУРА):');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('Предоставь глубокое, литературное, эзотерически безупречное толкование:');
buffer.writeln('1. АНАЛИЗ СТИХИЙНОГО СУБСТРАТА:');
buffer.writeln(' Определи распределение сил четырёх стихий (Огонь, Воздух, Вода, Земля). Где происходит избыточное напряжение, а где истощение? Как стихии диктуют темп событий?');
buffer.writeln('2. ДИАЛЕКТИКА СВИДЕТЕЛЕЙ И ВЕРДИКТ СУДЬИ:');
buffer.writeln(' Как Правый Свидетель (объективные обстоятельства) взаимодействует с Левым Свидетелем (внутреннее состояние кверента)? К какому неизбежному синтезу приводит их сложение в фигуру Судьи?');
if (isShield) {
buffer.writeln('3. ТАЙНЫЙ СОВЕТ ПРИМИРИТЕЛЯ:');
buffer.writeln(" Проанализируй 16-ю фигуру Примирителя (сложение Судьи и 1-й Матери). Как она снимает конфликт между тем, «с чего всё началось», и тем, «к чему всё пришло»?");
}
buffer.writeln('4. СИНХРОНИСТИЧЕСКИЙ СИНТЕЗ С ОРАКУЛОМ:');
buffer.writeln(' Свяжи выпавшие фигуры с архетипами лексикона 999, подчеркнув ключевой закон бытия.');
buffer.writeln('5. ПРИКЛАДНАЯ СТРАТЕГИЯ И ПРЕДОСТЕРЕЖЕНИЕ:');
buffer.writeln(' Дай конкретный и кристально ясный совет кверенту: какие действия предпринять немедленно, чего категорически избегать и в чём заключается главный духовный урок.');
return buffer.toString();
}
/// Generates the Grand Master Prompt for Celtic Tree Ogham (20 Fedas of the Sacred Grove).
static String generateOghamMasterPrompt({
required String question,
required String layoutTitle,
required List<OghamPromptFeda> fedas,
}) {
final buffer = StringBuffer();
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln('ВЕЛИКИЙ МАСТЕР-ПРОМПТ: КЕЛЬТСКИЙ ДРЕВЕСНЫЙ ОГАМ');
buffer.writeln('20 ФЕД СВЯЩЕННОЙ РОЩИ ДРУИДОВ И ТАЙНОПИСЬ БОГА ОГМЫ');
buffer.writeln('═══════════════════════════════════════════════════════════════════');
buffer.writeln();
buffer.writeln('### СИСТЕМНАЯ РОЛЬ:');
buffer.writeln('Ты — верховный друид и филид, хранитель Священной Рощи, знаток тайнописи бога Огмы и древесного календаря кельтов.');
buffer.writeln('Перед тобой священный расклад древесного Огама. Каждая феда олицетворяет священное дерево или кустарник, несущий архетипическую силу духов природы и космических законов.');
buffer.writeln('Твоя миссия — истолковать шёпот деревьев, раскрыть тайный баланс прямых и теневых сил и дать кверенту глубокий, поэтичный и практический совет.');
buffer.writeln();
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. ФОКУС И ВОПРОС КВЕРЕНТА:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
final q = question.trim().isNotEmpty ? question.trim() : 'Мудрость Священной Рощи Огама и древесные силы судьбы.';
buffer.writeln('• Вопрос кверента: «' + q + '»');
buffer.writeln('• Формат расклада: ' + layoutTitle);
buffer.writeln('• Традиция: Кельтский сакральный Огам (4 Аикмы Священной Рощи)');
buffer.writeln();
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('2. ВЫПАВШИЕ ФЕДЫ РОЩИ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
for (final f in fedas) {
buffer.writeln('• Позиция «' + f.position + '»: ' + f.symbol + ' ' + f.name + ' [' + (f.isReversed ? 'ТЕНЕВОЕ ПОЛОЖЕНИЕ' : 'ПРЯМОЕ ПОЛОЖЕНИЕ') + ']');
buffer.writeln(' Семья (Аикма): ' + f.aicme);
buffer.writeln(' Древесная сила: ' + f.lore);
if (f.linkedWord != null) {
buffer.writeln(' Связанный архетип Оракула 999: #' + f.linkedWord!.formattedSessionNumber + ' «' + f.linkedWord!.word.toUpperCase() + '» (' + f.linkedWord!.core + ')');
}
buffer.writeln();
}
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('3. ТРЕБОВАНИЯ К ДРУИДИЧЕСКОМУ ТОЛКОВАНИЮ НЕЙРОСЕТЬЮ:');
buffer.writeln('───────────────────────────────────────────────────────────────────');
buffer.writeln('1. АНАЛИЗ ДРЕВЕСНЫХ СИЛ: Как сочетаются качества выпавших деревьев (например, гибкость Ивы, несгибаемость Дуба, очищение Берёзы, защита Тис)?');
buffer.writeln('2. ДИАГНОСТИКА ТЕНЕЙ: Если выпали перевёрнутые феды, вскрой их предупреждение — где заблокирован жизненный сок или где кверент проявляет гордыню / самоизоляцию?');
buffer.writeln('3. ДИНАМИКА ПОЗИЦИЙ РАСКЛАДА: Как раскладывается временная или стихийная линия вопроса?');
buffer.writeln('4. СИНТЕЗ СО ЗНАКАМИ ОРАКУЛА 999: Как символы 999 подкрепляют наставление Рощи?');
buffer.writeln('5. ДРУИДИЧЕСКИЙ НАКАЗ КВЕРЕНТУ: Точное, прикладное наставление — как поступить в материальном мире.');
return buffer.toString();
}
}
class OghamPromptFeda {
final String position;
final String name;
final String symbol;
final String aicme;
final bool isReversed;
final String lore;
final WordItem? linkedWord;
const OghamPromptFeda({
required this.position,
required this.name,
required this.symbol,
required this.aicme,
required this.isReversed,
required this.lore,
this.linkedWord,
});
}
class GeomancyPromptFigure {
final String position;
final String name;
final String latinName;
final String arabicName;
final List<int> lines;
final String element;
final String planet;
final String lore;
final WordItem? linkedWord;
const GeomancyPromptFigure({
required this.position,
required this.name,
this.latinName = '',
this.arabicName = '',
required this.lines,
required this.element,
required this.planet,
required this.lore,
this.linkedWord,
});
String get dotsDisplay {
final elements = ['Огонь', 'Воздух', 'Вода', 'Земля'];
if (lines.length != 4) return lines.join('-');
return List.generate(
4,
(i) => ' [' + elements[i] + ']: ' + (lines[i] == 1 ? '• ' : '•• ') + ' (' + (lines[i] == 1 ? 'активный импульс' : 'пассивный сосуд') + ')',
).join('\n');
}
}
+941
View File
@@ -0,0 +1,941 @@
// GENERATED FILE - DO NOT EDIT MANUALLY
// Passport Schema & Pre-Prompt Generator for Oracle 999
import '../models/word_model.dart';
class PassportFieldDef {
final int index;
final String key;
final String sectionNum;
final String sectionTitle;
final String title;
final String promptTemplate;
final String formatTemplate;
const PassportFieldDef({
required this.index,
required this.key,
required this.sectionNum,
required this.sectionTitle,
required this.title,
required this.promptTemplate,
required this.formatTemplate,
});
String buildPrompt(WordItem word) {
final idPad = word.sessionNumber.toString().padLeft(3, '0');
final wordName = word.word;
final emoji = word.emoji;
final pos = word.partOfSpeech.isNotEmpty ? word.partOfSpeech : 'существительное';
final cat = word.category.isNotEmpty ? word.category : 'Образ / Архетип';
var t = promptTemplate;
t = t.replaceAll('{word}', wordName);
t = t.replaceAll('{id_pad}', idPad);
t = t.replaceAll('{emoji}', emoji);
t = t.replaceAll('{pos}', pos);
t = t.replaceAll('{cat}', cat);
t = t.replaceAll('{word_upper}', wordName.toUpperCase());
t = t.replaceAll('{extra}', '');
return t;
}
}
const List<PassportFieldDef> kPassportFields78 = [
PassportFieldDef(
index: 1,
key: 'index_and_name',
sectionNum: 'I',
sectionTitle: "Заголовочный паспорт (Метаданные реестра)",
title: "Индекс и имя",
promptTemplate: "Для сакрального знака «{word}» (ID: {id_pad}) системы «Оракул» сформируй каноническую заголовочную строку строго по шаблону: «[НОМЕР 000–999] | [СЛОВО В ВЕРХНЕМ РЕГИСТРЕ] | [2–4 ТОЧНЫХ ЭМОДЗИ/ГЛИФА]». Подбери наиболее точные символические эмодзи, передающие метафизическую сущность знака. Верни только эту одну строку.",
formatTemplate: "{id_pad} | {word_upper} | {emojis}",
),
PassportFieldDef(
index: 2,
key: 'grammatical_class',
sectionNum: 'I',
sectionTitle: "Заголовочный паспорт (Метаданные реестра)",
title: "Грамматический класс",
promptTemplate: "Для слова «{word}» укажи точный грамматический класс в русском языке, выбрав строго один вариант из списка: существительное, глагол, прилагательное, наречие, числительное, предлог, местоимение. Верни только одно слово — выбранный грамматический класс в нижнем регистре.",
formatTemplate: "{pos}",
),
PassportFieldDef(
index: 3,
key: 'functional_category',
sectionNum: 'I',
sectionTitle: "Заголовочный паспорт (Метаданные реестра)",
title: "Функциональная категория",
promptTemplate: "Для знака «{word}» (часть речи: {pos}) определи наиболее точную функциональную категорию Оракула 999. Выбери строго один вариант из канонического перечня: Образ / Архетип, Космический Знак, Священный Предмет, Сакральный Символ, Сакральное Место, Тотем, Архетип Человека, Действие / Трансформация, Свойство / Качество, Вектор / Направление, Числовой код, Фокус сознания. Верни только точное название категории.",
formatTemplate: "{category}",
),
PassportFieldDef(
index: 4,
key: 'network_status',
sectionNum: 'I',
sectionTitle: "Заголовочный паспорт (Метаданные реестра)",
title: "Сетевой статус",
promptTemplate: "Для знака «{word}» определи сетевой статус в матрице кросс-мантических соответствий Оракула 999. Укажи: является ли знак 'Узловой станцией' (прямой первичный якорь или узел пересечения традиций, перечисли ключевые традиции в скобках) ИЛИ 'Свободным знаком' (автономный архетип поля). Кратко обоснуй формулировку в одну строку.",
formatTemplate: "{network_status}",
),
PassportFieldDef(
index: 5,
key: 'mantic_runes',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Старший Футарк (Руны)",
promptTemplate: "Определи соответствие знака «{word}» в скандинавском Старшем Футарке (24 руны). Укажи строго по шаблону: [Имя руны / Роль: Первичное, Пересечение или Архетипическая аналогия / Смысловые сиблинги в лексиконе 999]. Кратко укажи атт руны и закон действия.",
formatTemplate: "[{rune_name} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 6,
key: 'mantic_ogham',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Кельтский Огам",
promptTemplate: "Определи соответствие знака «{word}» в кельтском Огаме (20 сакральных деревьев священной рощи друидов). Укажи: [Имя феды и дерева / Роль: Первичное или Пересечение / Сиблинги]. Укажи айкме и природно-тотемический смысл.",
formatTemplate: "[{feda_name} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 7,
key: 'mantic_tarot',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Старшие Арканы Таро",
promptTemplate: "Определи соответствие знака «{word}» в Старших Арканах Таро (0 Шут — XXI Мир). Укажи: [Номер и название Аркана / Роль: Первичный архетип, Эманация или Пересечение / Родственные карты и сиблинги 999]. Опиши архетипический узел.",
formatTemplate: "[{arcanum} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 8,
key: 'mantic_lenormand',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Малая колода Ленорман",
promptTemplate: "Определи соответствие знака «{word}» в малой французской колоде Ленорман (36 карт от Всадника до Креста). Укажи: [Номер и название карты / Роль / Сиблинги]. Приведи ключевые синтагматические связки.",
formatTemplate: "[{card} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 9,
key: 'mantic_kabbalah',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Каббала / Древо Жизни",
promptTemplate: "Определи соответствие знака «{word}» в Каббале и Древе Жизни (Эц Хаим). Укажи: [Сфира (Кетер..Малкут) или Буквенный Путь между сфирот / Столп (Милосердие/Суровость/Равновесие) / Роль / Сиблинги].",
formatTemplate: "[{sephira_or_path} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 10,
key: 'mantic_geomancy',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Арабская Геомантия ('Илм ар-Рамл)",
promptTemplate: "Определи соответствие знака «{word}» среди 16 священных фигур Арабской Геомантии ('Илм ар-Рамл: Via, Populus, Fortuna Major/Minor, Acquisitio, Amissio, Conjunctio, Carcer, Albus, Rubeus, Puella, Puer, Tristitia, Laetitia, Caput/Cauda Draconis). Укажи: [Имя фигуры (латынь/арабский) / Стихийная тетраграмма / Роль / Сиблинги].",
formatTemplate: "[{figure} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 11,
key: 'mantic_iching',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "И Цзин (Книга Перемен)",
promptTemplate: "Определи соответствие знака «{word}» в древнекитайской Книге Перемен (И Цзин). Укажи: [Номер и китайское/русское имя гексаграммы / Верхняя и нижняя триграммы Багуа / Роль / Сиблинги].",
formatTemplate: "[{hexagram} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 12,
key: 'mantic_alchemy',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Алхимия (Magnum Opus)",
promptTemplate: "Определи соответствие знака «{word}» в герметической Алхимии (Magnum Opus). Укажи: [Стадия (Nigredo, Albedo, Citrinitas, Rubedo) / Алхимическая операция (кальцинация, дистилляция, конъюнкция и др.) / Роль / Сиблинги].",
formatTemplate: "[{stage_or_element} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 13,
key: 'mantic_zodiac_west',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Классический Зодиак",
promptTemplate: "Определи соответствие знака «{word}» в классическом западном Зодиаке. Укажи: [Знак (Овен — Рыбы) / Планетарный управитель / Стихия и крест качеств / Роль / Сиблинги].",
formatTemplate: "[{zodiac_sign} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 14,
key: 'mantic_zodiac_chinese',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Китайский Зодиак",
promptTemplate: "Определи соответствие знака «{word}» в китайском Зодиаке (12 Земных Ветвей). Укажи: [Земная Ветвь и тотем (Крыса — Свинья) / Стихия У-Син / Инь или Ян / Роль / Сиблинги].",
formatTemplate: "[{branch} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 15,
key: 'mantic_septener',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Септенер",
promptTemplate: "Определи соответствие знака «{word}» в космическом Септенере (7 священных планет древности и 7 дней недели: Луна/Пн..Солнце/Вс). Укажи: [Планета / День недели / Роль / Сиблинги].",
formatTemplate: "[{planet_day} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 16,
key: 'mantic_dodecade',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Додекада Месяцев",
promptTemplate: "Определи соответствие знака «{word}» в солярной Додекаде (12 месяцев года). Укажи: [Месяц года / Сезонное дыхание / Солярные врата (равноденствие/солнцестояние) / Роль / Сиблинги].",
formatTemplate: "[{month_season} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 17,
key: 'mantic_numerology',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Нумерология",
promptTemplate: "Определи нумерологический код знака «{word}» с учётом его ID: {id_pad} и внутренней вибрации. Укажи: [Базовое число (1–9, 11, 22) / Качество числа / Связь с ячейкой Квадрата Пифагора / Роль / Сиблинги].",
formatTemplate: "[{number} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 18,
key: 'mantic_cosmology',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Космология Трёх Миров",
promptTemplate: "Определи топологию знака «{word}» в Космологии Трёх Миров: Верхний Мир (дух, сверхсознание, ноосфера), Срединный Мир (материя, социум, явь) или Нижний Мир (тень, хтоническое, навь), либо Мировая Ось (Axis Mundi). Укажи: [Мир / Локация на Мировом Древе / Роль / Сиблинги].",
formatTemplate: "[{world_axis} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 19,
key: 'mantic_tzolkin',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "20 Навалей Цолькин (Майя)",
promptTemplate: "Определи соответствие знака «{word}» среди 20 священных Навалей календаря Майя Цолькин (Имиш, Ик, Акбаль, Кан, Чикчан, Кими, Маник, Ламат, Мулук, Ок, Чуэн, Эб, Бен, Хиш, Мен, Киб, Кабан, Эцнаб, Кавак, Ахау). Укажи: [Имя Наваля / Тотем и стихия / Энергия дня / Роль / Сиблинги].",
formatTemplate: "[{nahual} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 20,
key: 'mantic_nakshatras',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "27 Накшатр Джйотиш (Индия)",
promptTemplate: "Определи соответствие знака «{word}» в системе 27 Накшатр (лунных стоянок) ведической астрологии Джйотиш (от Ашвини до Ревати). Укажи: [Имя Накшатры / Правящее божество (Девата) / Символ / Сакральное качество / Роль / Сиблинги].",
formatTemplate: "[{nakshatra} / Роль: {role} / Сиблинги: {siblings}]",
),
PassportFieldDef(
index: 21,
key: 'mantic_station_laws',
sectionNum: 'II',
sectionTitle: "Кросс-мантическая сеть и станции",
title: "Действующие законы станций (выжимка)",
promptTemplate: "Для знака «{word}» сформулируй концентрированную смысловую выжимку действующих сакральных законов тех мантических станций, на которых этот знак активирован. Как эти традиции сходятся в одну монолитную формулу? Сформулируй плотно в 2–4 предложения.",
formatTemplate: "{laws_summary}",
),
PassportFieldDef(
index: 22,
key: 'etymology_roots',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Этимология и праформы",
promptTemplate: "Для слова «{word}» реконструируй древнейшие праформы и этимологию: праиндоевропейский корень (*PIE) или иной праязыковый корень, его первичный конкретно-физический смысл до абстрактного обобщения и исторический путь семантического сдвига. Сформулируй ёмко и академически точно.",
formatTemplate: "{etymology}",
),
PassportFieldDef(
index: 23,
key: 'script_sumer_akkad',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Шумер / Аккад",
promptTemplate: "Для понятия «{word}» приведи клинописный эквивалент шумеро-аккадской традиции: [Клинописный знак или транслитерация / Шумерское или аккадское название / Фонетическое чтение и сакральное значение].",
formatTemplate: "[{sign} / {name} / {phonetics}]",
),
PassportFieldDef(
index: 24,
key: 'script_egypt',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Древний Египет",
promptTemplate: "Для понятия «{word}» приведи древнеегипетский иероглифический аналог: [Иероглиф или описание детерминатива / Научная транскрипция / Священное религиозное значение в мифе об Осирисе/Ра].",
formatTemplate: "[{hieroglyph} / {transcription} / {meaning}]",
),
PassportFieldDef(
index: 25,
key: 'script_hebrew',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Иудейская традиция",
promptTemplate: "Для понятия «{word}» приведи соответствие на библейском иврите: [Слово на иврите с огласовками / Буквенно-мистический код / Числовое значение гематрии и каббалистический смысл].",
formatTemplate: "[{hebrew_word} / {letter_code} / {gematria}]",
),
PassportFieldDef(
index: 26,
key: 'script_antiquity',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Античность",
promptTemplate: "Для понятия «{word}» приведи античные термины: [Древнегреческий термин с ударением / Классическая латынь / Философский концепт греко-римской античности].",
formatTemplate: "[{greek} / {latin} / {concept}]",
),
PassportFieldDef(
index: 27,
key: 'script_celtic',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Кельты",
promptTemplate: "Для понятия «{word}» приведи кельтский аналог: [Огамическая буква или знак / Древнеирландский термин / Мифологический контекст друидов].",
formatTemplate: "[{ogham_letter} / {old_irish}]",
),
PassportFieldDef(
index: 28,
key: 'script_runic_germanic',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Скандинавы / Германцы",
promptTemplate: "Для понятия «{word}» приведи скандинавско-германский аналог: [Руна / Древнескандинавский или прагерманский термин / Значение в мифологии Старшей Эдды].",
formatTemplate: "[{rune} / {old_norse}]",
),
PassportFieldDef(
index: 29,
key: 'script_slavic',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Славяне",
promptTemplate: "Для слова «{word}» приведи древнеславянский срез: [Праславянский корень (*) / Глаголическая буква или буквица / Сакральное мифопоэтическое значение в славянской традиции].",
formatTemplate: "[{proto_slavic} / {glagolitic} / {meaning}]",
),
PassportFieldDef(
index: 30,
key: 'script_india_sanskrit',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Ведическая Индия",
promptTemplate: "Для понятия «{word}» приведи ведический/санскритский срез: [Слово письмом деванагари / Транслитерация IAST / Санскритский корень (дхату) и философский смысл в Упанишадах].",
formatTemplate: "[{devanagari} / {sanskrit_iast} / {root}]",
),
PassportFieldDef(
index: 31,
key: 'script_persia',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Персия",
promptTemplate: "Для понятия «{word}» приведи персидский аналог: [Авестийский или пехлевийский термин / Его роль в зороастрийской космологии Авесты].",
formatTemplate: "[{avestan_or_pahlavi} / {meaning}]",
),
PassportFieldDef(
index: 32,
key: 'script_china',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Китай",
promptTemplate: "Для понятия «{word}» приведи древнекитайский иероглифический образ: [Иероглиф / Транскрипция пиньинь / Разбор пиктографических ключей древнего начертания и даосский смысл].",
formatTemplate: "[{hanzi} / {pinyin} / {pictogram_sense}]",
),
PassportFieldDef(
index: 33,
key: 'script_japan',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Япония",
promptTemplate: "Для понятия «{word}» приведи японский аналог: [Кандзи / Ромадзи / Синтоистский сакральный концепт или эстетическая категория (ками, макото, моно-но аварэ)].",
formatTemplate: "[{kanji} / {romaji} / {shinto_concept}]",
),
PassportFieldDef(
index: 34,
key: 'script_islamic',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Исламский мир",
promptTemplate: "Для понятия «{word}» приведи арабско-исламский эквивалент: [Арабское слово с огласовками / Трёхбуквенный корень / Число по системе Абджад и суфийский смысл].",
formatTemplate: "[{arabic_script} / {root} / {abjad}]",
),
PassportFieldDef(
index: 35,
key: 'script_western_europe',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Западная Европа",
promptTemplate: "Для понятия «{word}» приведи западноевропейский герметический/алхимический глиф или знак средневековых гримуаров и его толкование.",
formatTemplate: "[{alchemical_glyph} / {hermetic_term}]",
),
PassportFieldDef(
index: 36,
key: 'script_mesoamerica',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Мезоамерика",
promptTemplate: "Для понятия «{word}» приведи мезоамериканский эквивалент: [Майянский иероглифический глиф или ацтекская пиктограмма науатль / Произношение и сакральный смысл].",
formatTemplate: "[{mayan_glyph_or_nahuatl} / {meaning}]",
),
PassportFieldDef(
index: 37,
key: 'script_native_american',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Коренные народы Сев. Америки",
promptTemplate: "Для понятия «{word}» приведи символический пиктограф или тотемный знак коренных народов Северной Америки (навахо, оджибве, лакота, хопи) и его значение.",
formatTemplate: "[{pictograph} / {totem_symbol}]",
),
PassportFieldDef(
index: 38,
key: 'script_polynesia',
sectionNum: 'III',
sectionTitle: "Мультилингвальный и графический паспорт",
title: "Полинезия",
promptTemplate: "Для понятия «{word}» приведи геометрический мотив полинезийского татау / маорийского та-моко, коренной термин и его связь с концепциями Мана (сила) и Тапу (священность).",
formatTemplate: "[{tatau_motif} / {mana_concept}]",
),
PassportFieldDef(
index: 39,
key: 'state_neutral',
sectionNum: 'IV',
sectionTitle: "Трехполярная матрица состояний",
title: "0 Нейтральный статус (База / Бытие / Среда / Субстанция)",
promptTemplate: "Для знака «{word}» опиши НЕЙТРАЛЬНЫЙ СТАТУС [0]: онтологический и физический факт его присутствия в мире до человеческой оценки, эмоций и субъективной воли. Как этот феномен существует как чистая субстанция или естественная среда бытия? Сформулируй ёмко в 2–3 предложения.",
formatTemplate: "{state_0}",
),
PassportFieldDef(
index: 40,
key: 'state_creative',
sectionNum: 'IV',
sectionTitle: "Трехполярная матрица состояний",
title: "[+1] Созидательный вектор (Свет / Ресурс / Эволюция)",
promptTemplate: "Для знака «{word}» опиши СОЗИДАТЕЛЬНЫЙ ВЕКТОР [+1]: его светлое, ресурсное и конструктивное проявление. Как этот архетип действует в созидании порядка, исцелении, росте осознанности, эволюции духа и преумножении жизни? Сформулируй ёмко в 2–3 предложения.",
formatTemplate: "{state_plus}",
),
PassportFieldDef(
index: 41,
key: 'state_destructive',
sectionNum: 'IV',
sectionTitle: "Трехполярная матрица состояний",
title: "[-1] Деструктивный вектор (Тень / Вызов / Искажение)",
promptTemplate: "Для знака «{word}» опиши ДЕСТРУКТИВНЫЙ ВЕКТОР [-1]: его теневое, искажённое и разрушительное проявление при потере меры и осознанности. В какие аффекты, застои, хаос, выгорание или слепоту вырождается этот знак? Сформулируй ёмко в 2–3 предложения.",
formatTemplate: "{state_minus}",
),
PassportFieldDef(
index: 42,
key: 'culture_sumer_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Шумер и Месопотамия: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в культуре Шумера и Месопотамии (мифы, боги, цивилизационный дар, законы Мэ). Сформулируй в 1–2 предложения.",
formatTemplate: "{sumer_light}",
),
PassportFieldDef(
index: 43,
key: 'culture_sumer_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Шумер и Месопотамия: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в культуре Шумера и Месопотамии (хаос Тиамат, проклятия, подземный мир Кура, гнев богов). Сформулируй в 1–2 предложения.",
formatTemplate: "{sumer_shadow}",
),
PassportFieldDef(
index: 44,
key: 'culture_egypt_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Древний Египет: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в религии Древнего Египта (закон Маат, солярная сила Ра, возрождение Осириса). Сформулируй в 1–2 предложения.",
formatTemplate: "{egypt_light}",
),
PassportFieldDef(
index: 45,
key: 'culture_egypt_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Древний Египет: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в мифологии Древнего Египта (тьма Апопа, пустыня и ярость Сета, суд загробного мира Дуат). Сформулируй в 1–2 предложения.",
formatTemplate: "{egypt_shadow}",
),
PassportFieldDef(
index: 46,
key: 'culture_kabbalah_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Иудейская традиция и Каббала: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в иудейской традиции и Каббале (божественный свет Ор Эйн Соф, исправление мира Тиккун Олам, благодать сфирот). Сформулируй в 1–2 предложения.",
formatTemplate: "{kabbalah_light}",
),
PassportFieldDef(
index: 47,
key: 'culture_kabbalah_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Иудейская традиция и Каббала: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в Каббале и иудейском мифе (оболочки Клипот, суд Дин/Гвура, изгнание, падение). Сформулируй в 1–2 предложения.",
formatTemplate: "{kabbalah_shadow}",
),
PassportFieldDef(
index: 48,
key: 'culture_antiquity_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Античность (Греция / Рим): [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в античной культуре Греции и Рима (аполлонический разум, доблесть арете, гармония космоса, право). Сформулируй в 1–2 предложения.",
formatTemplate: "{antiquity_light}",
),
PassportFieldDef(
index: 49,
key: 'culture_antiquity_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Античность (Греция / Рим): [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в античной традиции (гордыня хюбрис, слепой рок ананке, безумие Тартара, возмездие Немезиды). Сформулируй в 1–2 предложения.",
formatTemplate: "{antiquity_shadow}",
),
PassportFieldDef(
index: 50,
key: 'culture_celtic_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Кельтская традиция: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в кельтской традиции (священная мудрость друидов, священный огонь Бригитты, магия природы). Сформулируй в 1–2 предложения.",
formatTemplate: "{celtic_light}",
),
PassportFieldDef(
index: 51,
key: 'culture_celtic_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Кельтская традиция: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в кельтском мифе (гнев фоморов, нарушение табу гейса, морок Сидов, безумие битвы). Сформулируй в 1–2 предложения.",
formatTemplate: "{celtic_shadow}",
),
PassportFieldDef(
index: 52,
key: 'culture_nordic_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Скандинавско-германская традиция: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в скандинавско-германской традиции (мудрость Одина, верность Тюра, плодородие Фрейра, сила Асгарда). Сформулируй в 1–2 предложения.",
formatTemplate: "{nordic_light}",
),
PassportFieldDef(
index: 53,
key: 'culture_nordic_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Скандинавско-германская традиция: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в скандинавском мифе (коварен Локи, хладный Ётунхейм, сумерки богов Рагнарёк, ярость Фенрира). Сформулируй в 1–2 предложения.",
formatTemplate: "{nordic_shadow}",
),
PassportFieldDef(
index: 54,
key: 'culture_slavic_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Славянская традиция: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в славянской традиции (правь, свет Белобога, плодородие Матери-Сырой-Земли, благодать Лады). Сформулируй в 1–2 предложения.",
formatTemplate: "{slavic_light}",
),
PassportFieldDef(
index: 55,
key: 'culture_slavic_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Славянская традиция: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в славянском мифе (навь, мгла Чернобога, хлад Морены, навьи чары и морок). Сформулируй в 1–2 предложения.",
formatTemplate: "{slavic_shadow}",
),
PassportFieldDef(
index: 56,
key: 'culture_india_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Ведическая Индия и Тантра: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в ведической традиции Индии (Дхарма, Саттва, освобождение Мокша, пробуждение Кундалини). Сформулируй в 1–2 предложения.",
formatTemplate: "{india_light}",
),
PassportFieldDef(
index: 57,
key: 'culture_india_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Ведическая Индия и Тантра: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в индийской традиции (иллюзия Майя, Тамас, оковы сансары, разрушительный гнев Кали). Сформулируй в 1–2 предложения.",
formatTemplate: "{india_shadow}",
),
PassportFieldDef(
index: 58,
key: 'culture_persia_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Персия и Зороастризм: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в зороастризме и древней Персии (вечный свет Аша, мудрость Ахура Мазды, чистая мысль, слово и дело). Сформулируй в 1–2 предложения.",
formatTemplate: "{persia_light}",
),
PassportFieldDef(
index: 59,
key: 'culture_persia_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Персия и Зороастризм: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в персидском мифе (ложь Друдж, морок Ангро-Майнью/Аримана, осквернение стихий). Сформулируй в 1–2 предложения.",
formatTemplate: "{persia_shadow}",
),
PassportFieldDef(
index: 60,
key: 'culture_china_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Китай (Даосизм, И-цзин, У-син): [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в китайской традиции (следование пути Дао, недеяние У-вэй, баланс пяти стихий У-син). Сформулируй в 1–2 предложения.",
formatTemplate: "{china_light}",
),
PassportFieldDef(
index: 61,
key: 'culture_china_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Китай (Даосизм, И-цзин, У-син): [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в китайской традиции (дисбаланс стихий, застой Ци, истощение из-за борьбы с переменами). Сформулируй в 1–2 предложения.",
formatTemplate: "{china_shadow}",
),
PassportFieldDef(
index: 62,
key: 'culture_japan_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Япония (Синтоизм, Сингон): [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в синтоизме и культуре Японии (очищение Мисоги, почитание ками, созерцание красоты, искренность сердца Макото). Сформулируй в 1–2 предложения.",
formatTemplate: "{japan_light}",
),
PassportFieldDef(
index: 63,
key: 'culture_japan_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Япония (Синтоизм, Сингон): [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в японской традиции (скверна Кэгарэ, гнев мстительных духов Онрё, бездна Ёми). Сформулируй в 1–2 предложения.",
formatTemplate: "{japan_shadow}",
),
PassportFieldDef(
index: 64,
key: 'culture_islam_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Исламская традиция и суфизм: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в исламской традиции и суфизме (божественный свет Нур, растворение эго в Боге Фана, мистическая любовь Ишк). Сформулируй в 1–2 предложения.",
formatTemplate: "{islam_light}",
),
PassportFieldDef(
index: 65,
key: 'culture_islam_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Исламская традиция и суфизм: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в исламе и суфизме (низменные страсти Нафс, наущения Иблиса/Шайтана, гордыня). Сформулируй в 1–2 предложения.",
formatTemplate: "{islam_shadow}",
),
PassportFieldDef(
index: 66,
key: 'culture_hermetic_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Западноевропейская традиция (Алхимия / Герметизм): [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в западноевропейском герметизме и алхимии (Великое Делание, трансмутация свинца в золото, философский камень). Сформулируй в 1–2 предложения.",
formatTemplate: "{hermetic_light}",
),
PassportFieldDef(
index: 67,
key: 'culture_hermetic_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Западноевропейская традиция (Алхимия / Герметизм): [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в европейской традиции (ложная алхимия суфлёров, фаустовская гордыня, ядовитый осадок нигредо). Сформулируй в 1–2 предложения.",
formatTemplate: "{hermetic_shadow}",
),
PassportFieldDef(
index: 68,
key: 'culture_mesoamerica_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Мезоамерика и Анды (Майя, Ацтеки, Инки): [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в культуре Мезоамерики и Анд (космическое обновление, мудрость Кетцалькоатля/Кукулькана, солнце Инти). Сформулируй в 1–2 предложения.",
formatTemplate: "{mesoamerica_light}",
),
PassportFieldDef(
index: 69,
key: 'culture_mesoamerica_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Мезоамерика и Анды (Майя, Ацтеки, Инки): [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в мифологии Мезоамерики (кровавый долг богам, бездна Шибальбы, зеркало Тескатлипоки). Сформулируй в 1–2 предложения.",
formatTemplate: "{mesoamerica_shadow}",
),
PassportFieldDef(
index: 70,
key: 'culture_native_american_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Традиции коренных народов Северной Америки: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в шаманизме индейцев Северной Америки (священная связь с Великим Духом Вакан Танка, гармония Круга Жизни). Сформулируй в 1–2 предложения.",
formatTemplate: "{native_american_light}",
),
PassportFieldDef(
index: 71,
key: 'culture_native_american_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Традиции коренных народов Северной Америки: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в индейских мифах (прожорливый каннибальский дух Вендиго, злые чары, разрушение круга). Сформулируй в 1–2 предложения.",
formatTemplate: "{native_american_shadow}",
),
PassportFieldDef(
index: 72,
key: 'culture_polynesia_light',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Полинезия и Океания: [+] Свет",
promptTemplate: "Для символа «{word}» опиши СОЗИДАТЕЛЬНЫЙ АСПЕКТ [+] в культуре Полинезии (величие духовной силы Мана, навигация океана, связь с предками Атуа). Сформулируй в 1–2 предложения.",
formatTemplate: "{polynesia_light}",
),
PassportFieldDef(
index: 73,
key: 'culture_polynesia_shadow',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "Полинезия и Океания: [-] Тень",
promptTemplate: "Для символа «{word}» опиши ДЕСТРУКТИВНЫЙ / ТЕНЕВОЙ АСПЕКТ [-] в мифах Океании (нарушение священного запрета Тапу, безжалостная ярость волн, утрата маны). Сформулируй в 1–2 предложения.",
formatTemplate: "{polynesia_shadow}",
),
PassportFieldDef(
index: 74,
key: 'meta_synthesis',
sectionNum: 'V',
sectionTitle: "Культурологическая матрица",
title: "17-й Интегральный мета-синтез (Универсальный культурологический код)",
promptTemplate: "Для символа «{word}» сформируй 17-й ИНТЕГРАЛЬНЫЙ МЕТА-СИНТЕЗ, обобщающий все 16 культур человечества: 1. Мета-параллели: сквозная роль этого символа в мировой цивилизации; 2. Точка преломления: точная грань между созидательным (+) и деструктивным (-) проявлением знака; 3. Кросс-культурный инвариант: единый универсальный закон архетипа, одинаковый от Шумера до Полинезии. Сформулируй строго структурированным текстом.",
formatTemplate: "Мета-параллели: {meta_parallels}\nТочка преломления: {inflection_point}\nКросс-культурный инвариант: {cross_cultural_invariant}",
),
PassportFieldDef(
index: 75,
key: 'semantic_core',
sectionNum: 'VI',
sectionTitle: "Мантический & Обучающий синтез",
title: "Смысловое ядро",
promptTemplate: "Для знака «{word}» (ID: {id_pad}, {pos}, {category}) сформулируй концентрированное СМЫСЛОВОЕ ЯДРО в 1–2 чеканных, философски точных предложениях. Какова глубинная метафизическая формула этого знака?",
formatTemplate: "{semantic_core}",
),
PassportFieldDef(
index: 76,
key: 'grammar_interpretation',
sectionNum: 'VI',
sectionTitle: "Мантический & Обучающий синтез",
title: "Интерпретация по грамматическому типу",
promptTemplate: "Для знака «{word}» дай мантическую интерпретацию через призму его грамматического класса ({pos}): как именно его часть речи определяет характер проявления в раскладе — как стабильную среду/архетип (существительное), как прямой импульс/трансформацию (глагол), как модус/окраску ситуации (прилагательное) или иной вектор? Сформулируй в 2–3 предложения.",
formatTemplate: "{grammar_interpretation}",
),
PassportFieldDef(
index: 77,
key: 'will_vector',
sectionNum: 'VI',
sectionTitle: "Мантический & Обучающий синтез",
title: "Вектор Воли (К чему призывает)",
promptTemplate: "Для знака «{word}» сформулируй ВЕКТОР ВОЛИ: к какому точному, конструктивному и необратимому действию или внутренней стратегии призывает этот знак кверента? Практический совет прямого действия.",
formatTemplate: "{will_vector}",
),
PassportFieldDef(
index: 78,
key: 'shadow_warning',
sectionNum: 'VI',
sectionTitle: "Мантический & Обучающий синтез",
title: "Предостережение / Тень (О чем предупреждает)",
promptTemplate: "Для знака «{word}» сформулируй ПРЕДОСТЕРЕЖЕНИЕ / ТЕНЬ: от какой слепой ловушки, бессознательного импульса, иллюзии или гибельной крайности строго предупреждает этот знак? Точное указание на опасность.",
formatTemplate: "{shadow_warning}",
),
];
final Map<String, PassportFieldDef> kPassportFieldByKey = {
for (final f in kPassportFields78) f.key: f,
};
String buildSectionPrePrompt(String sectionNum, WordItem word) {
final fields = kPassportFields78.where((f) => f.sectionNum == sectionNum).toList();
if (fields.isEmpty) return '';
final sb = StringBuffer();
sb.writeln('ПАКЕТ ПРЕДПРОМПТОВ: РАЗДЕЛ $sectionNum. ${fields.first.sectionTitle}');
sb.writeln('Знак: «${word.word.toUpperCase()}» (ID: ${word.sessionNumber.toString().padLeft(3, "0")})');
sb.writeln('Ответь строго по пунктам, сохраняя названия строк:');
sb.writeln();
for (final f in fields) {
sb.writeln('--- [Пункт ${f.index}/78] ${f.title} ---');
sb.writeln(f.buildPrompt(word));
sb.writeln();
}
return sb.toString().trim();
}
String buildAllPassportPrompts(WordItem word) {
final sb = StringBuffer();
final idPad = word.sessionNumber.toString().padLeft(3, '0');
sb.writeln('═══════════════════════════════════════════════════════════════════');
sb.writeln('ПОЛНЫЙ ПАКЕТ ИЗ 78 ПРОМПТОВ: ЗНАК #$idPad «${word.word.toUpperCase()}»');
sb.writeln('═══════════════════════════════════════════════════════════════════');
sb.writeln();
for (final f in kPassportFields78) {
sb.writeln('### [ПРОМПТ ${f.index.toString().padLeft(2, "0")}/78] | Раздел ${f.sectionNum} | ${f.title}');
sb.writeln(f.buildPrompt(word));
sb.writeln();
sb.writeln('-------------------------------------------------------------------');
}
return sb.toString().trim();
}
/// Интеллектуальный парсер текста паспорта или разделов в 78 атомарных полей
Map<String, String> parsePassportMarkdownToFields(String md) {
final result = <String, String>{};
final lines = md.split('\n');
final simplePrefixes = <String, String>{
'Индекс и имя:': 'index_and_name',
'Грамматический класс:': 'grammatical_class',
'Функциональная категория:': 'functional_category',
'Сетевой статус:': 'network_status',
'Старший Футарк (Руны):': 'mantic_runes',
'Кельтский Огам:': 'mantic_ogham',
'Старшие Арканы Таро:': 'mantic_tarot',
'Малая колода Ленорман:': 'mantic_lenormand',
'Каббала / Древо Жизни:': 'mantic_kabbalah',
"Арабская Геомантия ('Илм ар-Рамл):": 'mantic_geomancy',
'Арабская Геомантия:': 'mantic_geomancy',
'И Цзин (Книга Перемен):': 'mantic_iching',
'Алхимия (Magnum Opus):': 'mantic_alchemy',
'Классический Зодиак:': 'mantic_zodiac_west',
'Китайский Зодиак:': 'mantic_zodiac_chinese',
'Септенер:': 'mantic_septener',
'Додекада Месяцев:': 'mantic_dodecade',
'Нумерология:': 'mantic_numerology',
'Космология Трёх Миров:': 'mantic_cosmology',
'20 Навалей Цолькин (Майя):': 'mantic_tzolkin',
'27 Накшатр Джйотиш (Индия):': 'mantic_nakshatras',
'Этимология и праформы:': 'etymology_roots',
'Шумер / Аккад:': 'script_sumer_akkad',
'Древний Египет:': 'script_egypt',
'Иудейская традиция:': 'script_hebrew',
'Античность:': 'script_antiquity',
'Кельты:': 'script_celtic',
'Скандинавы / Германцы:': 'script_runic_germanic',
'Славяне:': 'script_slavic',
'Ведическая Индия:': 'script_india_sanskrit',
'Персия:': 'script_persia',
'Китай:': 'script_china',
'Япония:': 'script_japan',
'Исламский мир:': 'script_islamic',
'Западная Европа:': 'script_western_europe',
'Мезоамерика:': 'script_mesoamerica',
'Коренные народы Сев. Америки:': 'script_native_american',
'Полинезия:': 'script_polynesia',
'Смысловое ядро:': 'semantic_core',
'Интерпретация по грамматическому типу:': 'grammar_interpretation',
'Вектор Воли (К чему призывает):': 'will_vector',
'Предостережение / Тень (О чем предупреждает):': 'shadow_warning',
};
final culturePrefixes = <String, String>{
'Шумер и Месопотамия:': 'culture_sumer',
'Древний Египет:': 'culture_egypt',
'Иудейская традиция и Каббала:': 'culture_kabbalah',
'Античность (Греция / Рим):': 'culture_antiquity',
'Кельтская традиция:': 'culture_celtic',
'Скандинавско-германская традиция:': 'culture_nordic',
'Славянская традиция:': 'culture_slavic',
'Ведическая Индия и Тантра:': 'culture_india',
'Персия и Зороастризм:': 'culture_persia',
'Китай (Даосизм, И-цзин, У-син):': 'culture_china',
'Япония (Синтоизм, Сингон):': 'culture_japan',
'Исламская традиция и суфизм:': 'culture_islam',
'Западноевропейская традиция (Алхимия / Герметизм):': 'culture_hermetic',
'Мезоамерика и Анды (Майя, Ацтеки, Инки):': 'culture_mesoamerica',
'Традиции коренных народов Северной Америки:': 'culture_native_american',
'Полинезия и Океания:': 'culture_polynesia',
};
int i = 0;
while (i < lines.length) {
final line = lines[i].trim();
// Check simple 1:1 prefixes
bool matched = false;
for (final entry in simplePrefixes.entries) {
if (line.startsWith(entry.key)) {
final val = line.substring(entry.key.length).trim();
if (val.isNotEmpty) {
result[entry.value] = val;
}
matched = true;
break;
}
}
if (matched) {
i++;
continue;
}
// Check cultures with [+] and [-]
for (final entry in culturePrefixes.entries) {
if (line.startsWith(entry.key)) {
final rest = line.substring(entry.key.length).trim();
if (rest.contains('[-]') && rest.contains('[+]')) {
final parts = rest.split('[-]');
final light = parts[0].replaceAll('[+]', '').replaceAll('/', '').trim();
final shadow = parts.length > 1 ? parts[1].trim() : '';
if (light.isNotEmpty) result['${entry.value}_light'] = light;
if (shadow.isNotEmpty) result['${entry.value}_shadow'] = shadow;
matched = true;
break;
}
}
}
if (matched) {
i++;
continue;
}
// Tri-polar states
if (line.startsWith('0 Нейтральный статус')) {
final idx = line.indexOf(':');
if (idx != -1) {
result['state_neutral'] = line.substring(idx + 1).trim();
}
} else if (line.startsWith('[+1] Созидательный вектор')) {
final idx = line.indexOf(':');
if (idx != -1) {
result['state_creative'] = line.substring(idx + 1).trim();
}
} else if (line.startsWith('[-1] Деструктивный вектор')) {
final idx = line.indexOf(':');
if (idx != -1) {
result['state_destructive'] = line.substring(idx + 1).trim();
}
} else if (line.startsWith('Действующие законы станций')) {
final sb = StringBuffer();
i++;
while (i < lines.length && !lines[i].startsWith('---') && !lines[i].startsWith('III.')) {
if (lines[i].trim().isNotEmpty) sb.writeln(lines[i].trim());
i++;
}
if (sb.isNotEmpty) {
result['mantic_station_laws'] = sb.toString().trim();
}
continue;
} else if (line.startsWith('Б. 17-й Интегральный мета-синтез') || line.startsWith('17-й Интегральный мета-синтез:')) {
final sb = StringBuffer();
i++;
while (i < lines.length && !lines[i].startsWith('---') && !lines[i].startsWith('VI.')) {
if (lines[i].trim().isNotEmpty) sb.writeln(lines[i].trim());
i++;
}
if (sb.isNotEmpty) {
result['meta_synthesis'] = sb.toString().trim();
}
continue;
}
i++;
}
return result;
}
+68
View File
@@ -0,0 +1,68 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import '../models/lenormand_pairs.dart';
/// Same shape as Lenormand pairs JSON: n, name, meaning, pairs[].
class TarotMajorsRepository {
static final TarotMajorsRepository _instance = TarotMajorsRepository._internal();
factory TarotMajorsRepository() => _instance;
TarotMajorsRepository._internal();
final Map<int, LenormandCardEntry> _byN = {};
final Map<String, LenormandCardEntry> _byId = {};
bool _loaded = false;
bool get isLoaded => _loaded;
List<LenormandCardEntry> get cards =>
List.unmodifiable(_byN.values.toList()..sort((a, b) => a.n.compareTo(b.n)));
LenormandCardEntry? byNumber(int n) => _byN[n];
LenormandCardEntry? byCanonId(String id) => _byId[id];
Future<void> init() async {
if (_loaded && _byN.isNotEmpty) return;
Map<String, dynamic>? decoded;
final cwd = Directory.current.path;
for (final path in {
'assets/data/tarot_majors.json',
'../assets/data/tarot_majors.json',
'$cwd/assets/data/tarot_majors.json',
'$cwd/../assets/data/tarot_majors.json',
}) {
try {
final file = File(path);
if (file.existsSync()) {
decoded = json.decode(file.readAsStringSync()) as Map<String, dynamic>;
break;
}
} catch (e) {
debugPrint('tarot_majors.json skip $path: $e');
}
}
if (decoded == null) {
try {
final jsonStr = await rootBundle.loadString('assets/data/tarot_majors.json');
decoded = json.decode(jsonStr) as Map<String, dynamic>;
} catch (e) {
debugPrint('Could not load tarot_majors.json: $e');
}
}
if (decoded == null) {
_loaded = true;
return;
}
_byN.clear();
_byId.clear();
for (final raw in (decoded['cards'] as List? ?? const [])) {
if (raw is! Map<String, dynamic>) continue;
final card = LenormandCardEntry.fromMap(raw);
_byN[card.n] = card;
_byId[card.id] = card;
}
_loaded = true;
}
}
+110
View File
@@ -0,0 +1,110 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import '../models/tradition_model.dart';
class TraditionsRepository {
static final TraditionsRepository _instance = TraditionsRepository._internal();
factory TraditionsRepository() => _instance;
TraditionsRepository._internal();
List<TraditionCanon> _traditions = const [];
bool _loaded = false;
List<TraditionCanon> get traditions => List.unmodifiable(_traditions);
bool get isLoaded => _loaded;
static const Map<String, String> filterToCanonId = {
'ᚱ Руны': 'runes',
'᚛ Огам': 'ogham',
'✡️ Каббала': 'kabbalah',
'⚗️ Алхимия': 'alchemy',
'🎴 Таро': 'tarot',
'🪶 Ленорман': 'lenormand',
'☯️ И Цзин': 'iching',
'⌛ Геомантия': 'geomancy',
'♈ Гороскоп': 'western_zodiac',
'🐉 Китайский зодиак': 'zodiac',
'🌌 Космология': 'cosmology',
'📅 Дни недели': 'days_of_week',
'🗓️ Месяцы года': 'months_of_year',
'🔢 Нумерология': 'numerology',
};
TraditionCanon? byId(String id) {
for (final t in _traditions) {
if (t.id == id) return t;
}
return null;
}
/// Finds all stations across traditions that relate to this word (either as primary station or in cluster)
List<({TraditionCanon tradition, TraditionStation station, bool isPrimary})> findMatchesForWord(String word, {int? wordId}) {
final cleanWord = word.trim().toLowerCase();
final results = <({TraditionCanon tradition, TraditionStation station, bool isPrimary})>[];
for (final tradition in _traditions) {
for (final station in tradition.stations) {
if (station.primary != null &&
(station.primary!.word.trim().toLowerCase() == cleanWord ||
(wordId != null && station.primary!.id == wordId))) {
results.add((tradition: tradition, station: station, isPrimary: true));
} else if (station.cluster.any((cw) =>
cw.word.trim().toLowerCase() == cleanWord ||
(wordId != null && cw.id == wordId))) {
results.add((tradition: tradition, station: station, isPrimary: false));
}
}
}
return results;
}
Future<void> init() async {
if (_loaded && _traditions.isNotEmpty) return;
Map<String, dynamic>? decoded;
final cwd = Directory.current.path;
final candidates = <String>{
'assets/data/traditions_canon.json',
'../assets/data/traditions_canon.json',
'$cwd/assets/data/traditions_canon.json',
'$cwd/../assets/data/traditions_canon.json',
};
for (final path in candidates) {
try {
final file = File(path);
if (file.existsSync()) {
decoded = json.decode(file.readAsStringSync()) as Map<String, dynamic>;
break;
}
} catch (e) {
debugPrint('traditions_canon.json skip $path: $e');
}
}
if (decoded == null) {
try {
final jsonStr = await rootBundle.loadString('assets/data/traditions_canon.json');
decoded = json.decode(jsonStr) as Map<String, dynamic>;
} catch (e) {
debugPrint('Could not load traditions_canon.json: $e');
}
}
if (decoded == null) {
_traditions = const [];
_loaded = true;
return;
}
_traditions = decoded.values
.whereType<Map<String, dynamic>>()
.map(TraditionCanon.fromMap)
.toList();
_loaded = true;
}
}
+2
View File
@@ -1,3 +1,4 @@
import 'dart:io';
import 'dart:convert';
import 'dart:math';
@@ -80,6 +81,7 @@ class TrueRandomService {
/// Warm the pool so the next roll does not wait on the network.
void prefetch({int count = 3}) {
if (Platform.environment.containsKey('FLUTTER_TEST')) return;
if (_pool != null || _inflight != null) return;
_inflight = () async {
try {
+323
View File
@@ -0,0 +1,323 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../data/words_data.dart';
import '../models/word_model.dart';
import '../models/word_of_day_model.dart';
import 'words_repository.dart';
class WordOfTheDayService extends ChangeNotifier {
static final WordOfTheDayService _instance = WordOfTheDayService._internal();
factory WordOfTheDayService() => _instance;
WordOfTheDayService._internal();
static const String _kRecordsPrefsKey = 'oracle_word_of_day_records_v1';
static const String _kGlobalTriptychKey = 'oracle_word_of_day_is_triptych_v1';
final Map<String, WordOfDayRecord> _records = {};
bool _isInitialized = false;
bool _globalTriptychMode = false;
bool get isInitialized => _isInitialized;
bool get globalTriptychMode => _globalTriptychMode;
Future<void> init() async {
if (_isInitialized) return;
try {
final prefs = await SharedPreferences.getInstance();
_globalTriptychMode = prefs.getBool(_kGlobalTriptychKey) ?? false;
final raw = prefs.getString(_kRecordsPrefsKey);
if (raw != null) {
final decoded = json.decode(raw) as Map<String, dynamic>;
_records.clear();
for (final entry in decoded.entries) {
if (entry.value is Map<String, dynamic>) {
_records[entry.key] = WordOfDayRecord.fromMap(
entry.value as Map<String, dynamic>,
);
}
}
}
} catch (e) {
debugPrint('Error initializing WordOfTheDayService: $e');
}
_isInitialized = true;
notifyListeners();
}
Future<void> setGlobalTriptychMode(bool enabled) async {
_globalTriptychMode = enabled;
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_kGlobalTriptychKey, enabled);
} catch (_) {}
notifyListeners();
}
static String formatDateKey(DateTime date) {
final y = date.year.toString().padLeft(4, '0');
final m = date.month.toString().padLeft(2, '0');
final d = date.day.toString().padLeft(2, '0');
return '$y-$m-$d';
}
static DateTime parseDateKey(String key) {
final parts = key.split('-');
if (parts.length == 3) {
final y = int.tryParse(parts[0]) ?? 2026;
final m = int.tryParse(parts[1]) ?? 1;
final d = int.tryParse(parts[2]) ?? 1;
return DateTime(y, m, d);
}
return DateTime.now();
}
/// High-dispersion deterministic hash that maps any calendar date to a consistent word (0..N-1).
/// Every device on the same date receives the exact same resonant words.
static int calculateWordIndexForDate(DateTime date, int totalWords, {String phase = 'main'}) {
if (totalWords <= 0) return 0;
final y = date.year;
final m = date.month;
final d = date.day;
int phaseSalt;
switch (phase) {
case 'morning':
phaseSalt = 0x11111111;
break;
case 'day':
phaseSalt = 0x22222222;
break;
case 'evening':
phaseSalt = 0x33333333;
break;
case 'main':
default:
phaseSalt = 0x5A5A5A5A;
break;
}
final rawHash = ((y * 10000 + m * 100 + d) * 2654435761) & 0x7FFFFFFF;
final rng = Random(rawHash ^ phaseSalt);
var index = rng.nextInt(totalWords);
// Guaranteed distinctness between morning, day, and evening
if (phase == 'day') {
final morningIdx = calculateWordIndexForDate(date, totalWords, phase: 'morning');
if (index == morningIdx) {
index = (index + 1) % totalWords;
}
} else if (phase == 'evening') {
final morningIdx = calculateWordIndexForDate(date, totalWords, phase: 'morning');
final dayIdx = calculateWordIndexForDate(date, totalWords, phase: 'day');
while (index == morningIdx || index == dayIdx) {
index = (index + 1) % totalWords;
}
}
return index;
}
/// Returns the deterministic WordItem for the given date and phase ('main', 'morning', 'day', 'evening').
WordItem getWordForDate(DateTime date, {String phase = 'main'}) {
final repoWords = WordsRepository().allWords;
final wordsPool = repoWords.isNotEmpty ? repoWords : kRussianTop1000Words;
final index = calculateWordIndexForDate(date, wordsPool.length, phase: phase);
return wordsPool[index];
}
/// Returns the 3 words of the day triptych (Morning, Day, Evening).
({WordItem morning, WordItem day, WordItem evening}) getTriptychWordsForDate(DateTime date) {
return (
morning: getWordForDate(date, phase: 'morning'),
day: getWordForDate(date, phase: 'day'),
evening: getWordForDate(date, phase: 'evening'),
);
}
/// Retrieves the record for the given date (cached or initialized).
WordOfDayRecord getRecord(DateTime date) {
final key = formatDateKey(date);
final mainWord = getWordForDate(date, phase: 'main');
final triptych = getTriptychWordsForDate(date);
if (_records.containsKey(key)) {
final existing = _records[key]!;
// Ensure triptych word IDs are set if they were 0
if (existing.morningWordId == 0 || existing.dayWordId == 0 || existing.eveningWordId == 0) {
return existing.copyWith(
morningWordId: triptych.morning.id,
dayWordId: triptych.day.id,
eveningWordId: triptych.evening.id,
);
}
return existing;
}
return WordOfDayRecord(
dateKey: key,
wordId: mainWord.id,
isRevealed: false,
isStepCompleted: false,
diaryNote: '',
isTriptychMode: _globalTriptychMode,
morningWordId: triptych.morning.id,
dayWordId: triptych.day.id,
eveningWordId: triptych.evening.id,
isMorningRevealed: false,
isDayRevealed: false,
isEveningRevealed: false,
isDayStepCompleted: false,
);
}
/// Sets the mode (1-Word vs 3-Words Triptych) for the given date record.
Future<WordOfDayRecord> setRecordTriptychMode(DateTime date, bool isTriptych) async {
final key = formatDateKey(date);
final current = getRecord(date);
final updated = current.copyWith(isTriptychMode: isTriptych);
_records[key] = updated;
await _saveRecordsToPrefs();
notifyListeners();
return updated;
}
/// Reveals the seal for the given phase: 'main', 'morning', 'day', 'evening', or 'all'.
Future<WordOfDayRecord> revealWord(DateTime date, {String phase = 'main'}) async {
final key = formatDateKey(date);
final current = getRecord(date);
final now = DateTime.now();
WordOfDayRecord updated;
switch (phase) {
case 'morning':
updated = current.copyWith(
isMorningRevealed: true,
morningRevealedAt: current.morningRevealedAt ?? now,
);
break;
case 'day':
updated = current.copyWith(
isDayRevealed: true,
dayRevealedAt: current.dayRevealedAt ?? now,
);
break;
case 'evening':
updated = current.copyWith(
isEveningRevealed: true,
eveningRevealedAt: current.eveningRevealedAt ?? now,
);
break;
case 'all':
updated = current.copyWith(
isRevealed: true,
isMorningRevealed: true,
isDayRevealed: true,
isEveningRevealed: true,
revealedAt: current.revealedAt ?? now,
morningRevealedAt: current.morningRevealedAt ?? now,
dayRevealedAt: current.dayRevealedAt ?? now,
eveningRevealedAt: current.eveningRevealedAt ?? now,
);
break;
case 'main':
default:
updated = current.copyWith(
isRevealed: true,
revealedAt: current.revealedAt ?? now,
);
break;
}
_records[key] = updated;
await _saveRecordsToPrefs();
notifyListeners();
return updated;
}
/// Toggles whether the practical step was completed.
Future<WordOfDayRecord> toggleStepCompleted(
DateTime date,
bool completed, {
bool isDayStep = false,
}) async {
final key = formatDateKey(date);
final current = getRecord(date);
final now = DateTime.now();
final updated = isDayStep
? current.copyWith(
isDayRevealed: true,
isDayStepCompleted: completed,
completedAt: completed ? (current.completedAt ?? now) : null,
)
: current.copyWith(
isRevealed: true,
isStepCompleted: completed,
completedAt: completed ? (current.completedAt ?? now) : null,
);
_records[key] = updated;
await _saveRecordsToPrefs();
notifyListeners();
return updated;
}
/// Saves the user's evening reflection note for this date.
Future<WordOfDayRecord> saveDiaryNote(DateTime date, String note) async {
final key = formatDateKey(date);
final current = getRecord(date);
final updated = current.copyWith(diaryNote: note.trim());
_records[key] = updated;
await _saveRecordsToPrefs();
notifyListeners();
return updated;
}
/// Returns all stored records ordered chronologically (newest first).
List<WordOfDayRecord> getAllRecords() {
final list = _records.values.toList();
list.sort((a, b) => b.dateKey.compareTo(a.dateKey));
return List.unmodifiable(list);
}
/// Calculates the current streak of consecutive active days.
int calculateStreak() {
int streak = 0;
var checkDate = DateTime.now();
final todayRecord = _records[formatDateKey(checkDate)];
if (todayRecord == null || (!todayRecord.isAnyRevealed && !todayRecord.isAnyStepCompleted)) {
checkDate = checkDate.subtract(const Duration(days: 1));
}
while (true) {
final key = formatDateKey(checkDate);
final rec = _records[key];
if (rec != null && (rec.isAnyRevealed || rec.isAnyStepCompleted)) {
streak++;
checkDate = checkDate.subtract(const Duration(days: 1));
} else {
break;
}
}
return streak;
}
int get totalCompletedPractices =>
_records.values.where((r) => r.isAnyStepCompleted).length;
int get totalRevealedDays =>
_records.values.where((r) => r.isAnyRevealed).length;
Future<void> _saveRecordsToPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final mapToSave = _records.map((k, v) => MapEntry(k, v.toMap()));
await prefs.setString(_kRecordsPrefsKey, json.encode(mapToSave));
} catch (e) {
debugPrint('Error saving WordOfTheDay records: $e');
}
}
}
+634 -82
View File
@@ -1,4 +1,10 @@
import '../models/word_passport_model.dart';
import '../models/syncretic_session_model.dart';
import 'dart:io';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:shared_preferences/shared_preferences.dart';
import '../data/words_data.dart';
import '../models/history_entry_model.dart';
@@ -14,9 +20,17 @@ class WordsRepository {
static const String _kMappingDateKey = 'oracle_daily_mapping_date_v1';
static const String _kMappingOrderKey = 'oracle_daily_mapping_order_v1';
static const String _kMappingSourceKey = 'oracle_daily_mapping_source_v1';
static const String _kCustomEditsKey = 'oracle_user_custom_edits_v1';
Map<String, Map<String, dynamic>> _userEdits = {};
List<WordItem> _sessionWords = [];
final List<OracleSession> _sessions = [];
Map<String, Map<String, dynamic>> _kbData = {};
Map<int, WordPassport> _passports = {};
WordPassport? getPassport(int wordId) => _passports[wordId];
Map<int, WordPassport> get allPassports => Map.unmodifiable(_passports);
bool _useRemoteShuffle = true;
String? _dailyMappingDate;
RandomSource _dailyMappingSource = RandomSource.local;
@@ -25,105 +39,441 @@ class WordsRepository {
List<OracleSession> get sessions => List.unmodifiable(_sessions);
List<OracleSession> get history => sessions; // Alias for compatibility
int get totalWords => _sessionWords.length;
int get customEditsCount => _userEdits.length;
Map<String, Map<String, dynamic>> get allCustomEdits => Map.unmodifiable(_userEdits);
String? get dailyMappingDate => _dailyMappingDate;
RandomSource get dailyMappingSource => _dailyMappingSource;
Future<void> init({bool remoteDailyShuffle = true}) async {
_useRemoteShuffle = remoteDailyShuffle;
await _loadKbAsset();
await _loadPassportsAsset();
await _loadUserEditsFromPrefs();
await _restoreOrFetchDailyMapping(force: false);
await _loadHistoryFromPrefs();
await _loadSyncreticSessionsFromPrefs();
}
Future<RandomSource> reshuffleSessionNumbers() {
return _restoreOrFetchDailyMapping(force: true);
Future<void> _loadPassportsAsset() async {
for (final path in ['assets/data/oracle_passports.json', '../assets/data/oracle_passports.json']) {
try {
final file = File(path);
if (file.existsSync()) {
final jsonStr = file.readAsStringSync();
final decoded = json.decode(jsonStr) as Map<String, dynamic>;
_passports = decoded.map((k, v) => MapEntry(int.tryParse(k) ?? 0, WordPassport.fromMap(v as Map<String, dynamic>)));
return;
}
} catch (_) {}
}
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return;
}
try {
final jsonStr = await rootBundle.loadString('assets/data/oracle_passports.json');
final decoded = json.decode(jsonStr) as Map<String, dynamic>;
_passports = decoded.map((k, v) => MapEntry(int.tryParse(k) ?? 0, WordPassport.fromMap(v as Map<String, dynamic>)));
} catch (_) {}
}
Future<void> _loadKbAsset() async {
// 1. In local dev/testing environments, load synchronously from filesystem
for (final path in ['assets/data/oracle_kb.json', '../assets/data/oracle_kb.json']) {
try {
final file = File(path);
if (file.existsSync()) {
final jsonStr = file.readAsStringSync();
final decoded = json.decode(jsonStr) as Map<String, dynamic>;
_kbData = decoded.map((k, v) => MapEntry(k.toLowerCase().trim(), v as Map<String, dynamic>));
return;
}
} catch (_) {}
}
if (Platform.environment.containsKey('FLUTTER_TEST')) {
return; // Avoid hanging on rootBundle during headless tests
}
// 2. In packaged release app, load via rootBundle
try {
final jsonStr = await rootBundle.loadString('assets/data/oracle_kb.json');
final decoded = json.decode(jsonStr) as Map<String, dynamic>;
_kbData = decoded.map((k, v) => MapEntry(k.toLowerCase().trim(), v as Map<String, dynamic>));
} catch (e) {
debugPrint('Could not load oracle_kb.json asset: $e');
}
}
WordItem _enrichedWord(WordItem base) {
final key = base.word.toLowerCase().trim();
var result = base;
final kb = _kbData[key] ?? _kbData[base.id.toString()];
if (kb != null) {
final kbWord = (kb['word'] as String?)?.toLowerCase().trim();
if (kbWord == null || kbWord == key) {
result = result.enrichWithKb(kb);
}
}
final edit = _userEdits[key];
if (edit != null) {
result = result.copyWith(
core: edit['core'] as String? ?? result.core,
oracle: edit['oracle'] as String? ?? result.oracle,
shadow: edit['shadow'] as String? ?? result.shadow,
asks: edit['asks'] as String? ?? result.asks,
step: edit['step'] as String? ?? result.step,
cultures: edit.containsKey('cultures') ? _stringMap(edit['cultures']) : result.cultures,
languages: edit.containsKey('languages') ? _stringMap(edit['languages']) : result.languages,
layers: edit.containsKey('layers') ? _layerList(edit['layers']) : result.layers,
binds: edit.containsKey('binds') ? _stringList(edit['binds']) : result.binds,
isUserEdited: true,
);
}
return result;
}
Map<String, String> _stringMap(dynamic raw) {
if (raw is! Map) return {};
return raw.map((k, v) => MapEntry(k.toString(), v.toString()));
}
List<String> _stringList(dynamic raw) {
if (raw is! List) return [];
return raw.map((e) => e.toString().trim()).where((e) => e.isNotEmpty).toList();
}
List<SymbolLayer> _layerList(dynamic raw) {
if (raw is! List) return [];
return [
for (final item in raw)
if (item is Map)
SymbolLayer(
system: item['system']?.toString() ?? '',
title: item['title']?.toString() ?? '',
law: item['law']?.toString() ?? '',
),
].where((layer) => layer.system.isNotEmpty && layer.law.trim().isNotEmpty).toList();
}
Map<String, dynamic>? customEditOf(String word) {
final edit = _userEdits[word.toLowerCase().trim()];
if (edit == null) return null;
return Map<String, dynamic>.from(edit);
}
void _applyEditsToSessionWords() {
_sessionWords = _sessionWords.map((w) {
final base = kRussianTop1000Words.firstWhere(
(b) => b.id == w.id,
orElse: () => w,
);
return _enrichedWord(base).copyWith(sessionNumber: w.sessionNumber);
}).toList();
}
Future<void> savePassport(WordPassport passport) async {
_passports[passport.id] = passport;
final key = passport.word.toLowerCase().trim();
final previous = Map<String, dynamic>.from(_userEdits[key] ?? {});
previous['passport'] = passport.toMap();
if (passport.semanticCore.isNotEmpty) previous['core'] = passport.semanticCore;
if (passport.willVector.isNotEmpty) previous['asks'] = passport.willVector;
if (passport.shadowWarning.isNotEmpty) previous['shadow'] = passport.shadowWarning;
previous['updatedAt'] = DateTime.now().toUtc().toIso8601String();
_userEdits[key] = previous;
await _saveUserEditsToPrefs();
}
Future<void> saveCustomEdit(String word, {
required String core,
required String oracle,
required String shadow,
required String asks,
required String step,
Map<String, String>? cultures,
List<SymbolLayer>? layers,
Map<String, String>? languages,
List<String>? binds,
Map<String, String>? reports,
}) async {
final key = word.toLowerCase().trim();
final previous = Map<String, dynamic>.from(_userEdits[key] ?? {});
previous
..['core'] = core.trim()
..['oracle'] = oracle.trim()
..['shadow'] = shadow.trim()
..['asks'] = asks.trim()
..['step'] = step.trim()
..['updatedAt'] = DateTime.now().toUtc().toIso8601String();
if (cultures != null) previous['cultures'] = cultures;
if (layers != null) {
previous['layers'] = [
for (final layer in layers)
if (layer.law.trim().isNotEmpty)
{'system': layer.system, 'title': layer.title, 'law': layer.law.trim()},
];
}
if (languages != null) previous['languages'] = languages;
if (binds != null) previous['binds'] = binds;
if (reports != null) previous['reports'] = reports;
_userEdits[key] = previous;
await _saveUserEditsToPrefs();
_applyEditsToSessionWords();
}
Future<void> resetCustomEdit(String word) async {
final key = word.toLowerCase().trim();
if (_userEdits.remove(key) != null) {
await _saveUserEditsToPrefs();
_applyEditsToSessionWords();
}
}
Future<void> clearAllCustomEdits() async {
_userEdits.clear();
await _saveUserEditsToPrefs();
_applyEditsToSessionWords();
}
Future<int> importCustomEditsJson(String jsonStr) async {
try {
final decoded = json.decode(jsonStr);
var count = 0;
Map<String, dynamic> rawMap = {};
if (decoded is Map<String, dynamic>) {
if (decoded['symbols'] is Map<String, dynamic>) {
rawMap = Map<String, dynamic>.from(decoded['symbols'] as Map);
} else if (decoded['symbols'] is List) {
for (final item in decoded['symbols'] as List) {
if (item is Map && item['word'] != null) {
rawMap[item['word'].toString()] = Map<String, dynamic>.from(item);
}
}
} else {
rawMap = decoded;
}
} else if (decoded is List) {
for (final item in decoded) {
if (item is Map && item['word'] != null) {
rawMap[item['word'].toString()] = Map<String, dynamic>.from(item);
}
}
}
for (final entry in rawMap.entries) {
final key = entry.key.toLowerCase().trim();
if (entry.value is Map<String, dynamic>) {
final val = Map<String, dynamic>.from(entry.value as Map);
final core = val['core']?.toString().trim() ?? '';
final oracle = (val['oracle']?.toString() ?? val['description']?.toString() ?? '').trim();
final shadow = val['shadow']?.toString().trim() ?? '';
final asks = val['asks']?.toString().trim() ?? '';
final step = val['step']?.toString().trim() ?? '';
final cultures = val['cultures'] is Map ? _stringMap(val['cultures']) : null;
final languages = val['languages'] is Map ? _stringMap(val['languages']) : null;
final layers = val['layers'] is List ? _layerList(val['layers']) : null;
final binds = val['binds'] is List ? _stringList(val['binds']) : null;
final reports = val['reports'] is Map ? _stringMap(val['reports']) : null;
final hasBody = core.isNotEmpty ||
oracle.isNotEmpty ||
shadow.isNotEmpty ||
asks.isNotEmpty ||
step.isNotEmpty ||
(cultures != null && cultures.isNotEmpty) ||
(languages != null && languages.isNotEmpty) ||
(layers != null && layers.isNotEmpty);
if (hasBody) {
_userEdits[key] = {
'core': core,
'oracle': oracle,
'shadow': shadow,
'asks': asks,
'step': step,
'cultures': ?cultures,
'languages': ?languages,
if (layers != null)
'layers': [
for (final layer in layers)
{'system': layer.system, 'title': layer.title, 'law': layer.law},
],
'binds': ?binds,
'reports': ?reports,
'updatedAt': val['updatedAt']?.toString() ?? DateTime.now().toUtc().toIso8601String(),
};
count++;
}
}
}
if (count > 0) {
await _saveUserEditsToPrefs();
_applyEditsToSessionWords();
}
return count;
} catch (e) {
debugPrint('Error importing custom edits: $e');
return 0;
}
}
String exportCustomEditsJson() {
final exportMap = <String, dynamic>{};
for (final entry in _userEdits.entries) {
final key = entry.key;
final wordItem = getWordByName(key);
exportMap[key] = {
'word': key,
if (wordItem != null) 'id': wordItem.id,
if (wordItem != null) 'sessionNumber': wordItem.sessionNumber,
if (wordItem != null) 'emoji': wordItem.emoji,
if (wordItem != null) 'category': wordItem.category,
if (wordItem != null) 'pos': wordItem.partOfSpeech,
...entry.value,
};
}
return const JsonEncoder.withIndent(' ').convert({
'version': '1.0',
'type': 'oracle_custom_edits',
'exportedAt': DateTime.now().toUtc().toIso8601String(),
'count': exportMap.length,
'symbols': exportMap,
});
}
String exportFullLexiconJson() {
final list = _sessionWords.map((w) {
return {
'id': w.id,
'sessionNumber': w.sessionNumber,
'word': w.word,
'emoji': w.emoji,
'pos': w.partOfSpeech,
'category': w.category,
'core': w.core,
'oracle': w.oracle,
'shadow': w.shadow,
'asks': w.asks,
'step': w.step,
'binds': w.binds,
'cultures': w.cultures,
'languages': w.languages,
'layers': w.layers.map((l) => l.toMap()).toList(),
'description': w.description,
'family': w.family,
'operator': w.operator,
'valence': w.valence,
'jurisdictions': w.jurisdictions,
'isUserEdited': w.isUserEdited,
};
}).toList();
return const JsonEncoder.withIndent(' ').convert({
'version': '1.0',
'type': 'oracle_full_lexicon',
'exportedAt': DateTime.now().toUtc().toIso8601String(),
'count': list.length,
'symbols': list,
});
}
Future<void> _loadUserEditsFromPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_kCustomEditsKey);
if (raw != null) {
final map = json.decode(raw) as Map<String, dynamic>;
_userEdits = map.map((k, v) => MapEntry(k.toLowerCase().trim(), Map<String, dynamic>.from(v as Map)));
for (final entry in _userEdits.entries) {
if (entry.value['passport'] is Map) {
final p = WordPassport.fromMap(Map<String, dynamic>.from(entry.value['passport'] as Map));
_passports[p.id] = p;
}
}
}
} catch (_) {}
}
Future<void> _saveUserEditsToPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_kCustomEditsKey, json.encode(_userEdits));
} catch (_) {}
}
void _initCanonicalWords() {
_sessionWords = [
for (var i = 0; i < kRussianTop1000Words.length; i++)
_enrichedWord(kRussianTop1000Words[i]).copyWith(sessionNumber: i),
];
_applyEditsToSessionWords();
}
Future<RandomSource> reshuffleSessionNumbers() async {
_initCanonicalWords();
await _healAllHistoryAndSessions();
return RandomSource.local;
}
Future<void> _healAllHistoryAndSessions() async {
await _loadHistoryFromPrefs();
await _loadSyncreticSessionsFromPrefs();
}
Future<RandomSource> _restoreOrFetchDailyMapping({required bool force}) async {
final today = utcDateStamp();
if (!force) {
final restored = await _loadPersistedMapping(today);
if (restored) return _dailyMappingSource;
}
final lastId = kRussianTop1000Words.length - 1;
RangeShuffle shuffle;
if (_useRemoteShuffle) {
shuffle = await TrueRandomService.instance.permutation(
min: 0,
max: lastId,
);
} else {
shuffle = RangeShuffle(
TrueRandomService.instance.localPermutation(0, lastId),
RandomSource.local,
);
}
_applyIndexOrder(shuffle.order);
_initCanonicalWords();
_dailyMappingDate = today;
_dailyMappingSource = shuffle.source;
await _persistMapping(today, shuffle.order, shuffle.source);
return shuffle.source;
}
void _applyIndexOrder(List<int> order) {
_sessionWords = [
for (var i = 0; i < order.length; i++)
kRussianTop1000Words[order[i]].copyWith(sessionNumber: i),
];
}
Future<bool> _loadPersistedMapping(String today) async {
_dailyMappingSource = _useRemoteShuffle ? RandomSource.randomOrg : RandomSource.local;
try {
final prefs = await SharedPreferences.getInstance();
final date = prefs.getString(_kMappingDateKey);
final stored = prefs.getStringList(_kMappingOrderKey);
if (date != today || stored == null) return false;
final order = stored.map(int.parse).toList();
if (!isPermutation(order, 0, kRussianTop1000Words.length - 1)) {
return false;
}
_applyIndexOrder(order);
_dailyMappingDate = date;
final sourceName = prefs.getString(_kMappingSourceKey);
_dailyMappingSource = sourceName == RandomSource.randomOrg.name
? RandomSource.randomOrg
: RandomSource.local;
return true;
} catch (_) {
return false;
}
}
Future<void> _persistMapping(
String date,
List<int> order,
RandomSource source,
) async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_kMappingDateKey, date);
await prefs.setStringList(
_kMappingOrderKey,
order.map((e) => e.toString()).toList(),
);
await prefs.setString(_kMappingSourceKey, source.name);
await prefs.remove(_kMappingOrderKey);
await prefs.setString(_kMappingDateKey, today);
await prefs.setString(_kMappingSourceKey, _dailyMappingSource.name);
} catch (_) {}
return _dailyMappingSource;
}
// Compatibility helpers
void applyIndexOrder(List<int> order) => _initCanonicalWords();
Future<bool> loadPersistedMapping(String today) async => true;
Future<void> persistMapping(String date, List<int> order, RandomSource source) async {}
static String utcDateStamp([DateTime? now]) {
final d = (now ?? DateTime.now()).toUtc();
final y = d.year.toString().padLeft(4, '0');
final m = d.month.toString().padLeft(2, '0');
final day = d.day.toString().padLeft(2, '0');
return '$y-$m-$day';
final y = d.year.toString().padLeft(4, "0");
final m = d.month.toString().padLeft(2, "0");
final day = d.day.toString().padLeft(2, "0");
return "$y-$m-$day";
}
Future<void> deleteSession(String id) => removeSession(id);
WordItem? getWordBySessionNumber(int number) {
if (number < 0 || number >= _sessionWords.length) return null;
return _sessionWords[number];
}
WordItem? getWordByName(String name) {
final query = name.trim().toLowerCase();
for (final w in _sessionWords) {
if (w.word.toLowerCase() == query) return w;
}
return null;
}
WordItem? getWordById(int id) {
for (final w in _sessionWords) {
if (w.id == id) return w;
}
return null;
}
WordItem getRandomWord() {
final random = Random.secure();
final idx = random.nextInt(_sessionWords.length);
@@ -162,7 +512,10 @@ class WordsRepository {
} else if (lower.contains(query) ||
w.description.toLowerCase().contains(query) ||
w.category.toLowerCase().contains(query) ||
w.partOfSpeech.toLowerCase().contains(query)) {
w.partOfSpeech.toLowerCase().contains(query) ||
w.family.toLowerCase().contains(query) ||
w.operator.toLowerCase().contains(query) ||
w.core.toLowerCase().contains(query)) {
contains.add(w);
}
}
@@ -190,6 +543,16 @@ class WordsRepository {
return set.toList();
}
List<String> getOperators() {
final set = <String>{'Все'};
for (final w in _sessionWords) {
if (w.operator.isNotEmpty) {
set.add(w.operator);
}
}
return set.toList();
}
// --- Session History Management ---
Future<void> saveSession(OracleSession session) async {
@@ -223,12 +586,13 @@ class WordsRepository {
Future<void> updateHistoryEntry(String id, {String? aiInterpretation, String? question}) =>
updateSession(id, aiInterpretation: aiInterpretation, question: question);
Future<void> deleteSession(String id) async {
_sessions.removeWhere((e) => e.id == id);
Future<void> removeSession(String id) async {
_sessions.removeWhere((s) => s.id == id);
await _saveHistoryToPrefs();
}
Future<void> deleteHistoryEntry(String id) => deleteSession(id);
// Backward compatibility alias
Future<void> deleteHistoryEntry(String id) => removeSession(id);
Future<void> clearHistory() async {
_sessions.clear();
@@ -238,14 +602,65 @@ class WordsRepository {
Future<void> _loadHistoryFromPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final jsonList = prefs.getStringList(_kHistoryKey);
_sessions.clear();
if (jsonList != null) {
for (final item in jsonList) {
final historyJson = prefs.getStringList(_kHistoryKey);
if (historyJson != null) {
_sessions.clear();
bool needsResave = false;
for (final itemStr in historyJson) {
try {
_sessions.add(OracleSession.fromJson(item));
final map = json.decode(itemStr) as Map<String, dynamic>;
final session = OracleSession.fromMap(map);
// Heal session words to match their canonical sacred IDs
final healedWords = session.words.map((sw) {
final canonical = getWordByName(sw.word);
if (canonical != null && canonical.sessionNumber != sw.sessionNumber) {
needsResave = true;
return SessionWord(
word: canonical.word,
sessionNumber: canonical.sessionNumber,
partOfSpeech: canonical.partOfSpeech.isNotEmpty ? canonical.partOfSpeech : sw.partOfSpeech,
category: canonical.category.isNotEmpty ? canonical.category : sw.category,
symbolicMeaning: sw.symbolicMeaning.isNotEmpty ? sw.symbolicMeaning : canonical.displayMeaning,
positionTitle: sw.positionTitle,
);
}
return sw;
}).toList();
final healedMessages = session.messages.map((m) {
if (m.attachedWord != null) {
final canonical = getWordByName(m.attachedWord!.word);
if (canonical != null && canonical.sessionNumber != m.attachedWord!.sessionNumber) {
needsResave = true;
return SessionChatMessage(
id: m.id,
sender: m.sender,
text: m.text,
timestamp: m.timestamp,
attachedWord: SessionWord(
word: canonical.word,
sessionNumber: canonical.sessionNumber,
partOfSpeech: canonical.partOfSpeech.isNotEmpty ? canonical.partOfSpeech : m.attachedWord!.partOfSpeech,
category: canonical.category.isNotEmpty ? canonical.category : m.attachedWord!.category,
symbolicMeaning: m.attachedWord!.symbolicMeaning,
positionTitle: m.attachedWord!.positionTitle,
),
);
}
}
return m;
}).toList();
_sessions.add(session.copyWith(
words: healedWords,
messages: healedMessages,
));
} catch (_) {}
}
if (needsResave) {
await _saveHistoryToPrefs();
}
}
} catch (_) {}
}
@@ -253,8 +668,145 @@ class WordsRepository {
Future<void> _saveHistoryToPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final jsonList = _sessions.map((e) => e.toJson()).toList();
await prefs.setStringList(_kHistoryKey, jsonList);
final historyJson = _sessions.map((s) => json.encode(s.toMap())).toList();
await prefs.setStringList(_kHistoryKey, historyJson);
} catch (_) {}
}
static const String _kSyncreticSessionsKey = 'oracle_999_syncretic_sessions_v1';
List<SyncreticSession> _syncreticSessions = [];
SyncreticSession? _currentSyncreticSession;
List<SyncreticSession> get syncreticSessions => List.unmodifiable(_syncreticSessions);
SyncreticSession get currentSyncreticSession {
if (_currentSyncreticSession == null) {
final todayKey = DateTime.now().toIso8601String().substring(0, 10);
final existing = _syncreticSessions.where((s) => s.dateKey == todayKey);
if (existing.isNotEmpty) {
_currentSyncreticSession = existing.first;
} else {
_currentSyncreticSession = SyncreticSession(
id: 'syn_${DateTime.now().millisecondsSinceEpoch}',
date: DateTime.now(),
);
}
}
return _currentSyncreticSession!;
}
Future<void> addReadingToSyncreticSession(SyncreticReading reading, {String? question}) async {
final current = currentSyncreticSession;
final existingIdx = current.readings.indexWhere((r) => r.id == reading.id);
final List<SyncreticReading> updatedReadings;
if (existingIdx >= 0) {
updatedReadings = [...current.readings];
updatedReadings[existingIdx] = reading;
} else {
updatedReadings = [...current.readings, reading];
}
final updatedSession = current.copyWith(
readings: updatedReadings,
mainQuestion: (question != null && question.trim().isNotEmpty)
? question.trim()
: current.mainQuestion,
);
await saveSyncreticSession(updatedSession);
}
Future<void> saveSyncreticSession(SyncreticSession session) async {
_currentSyncreticSession = session;
final idx = _syncreticSessions.indexWhere((s) => s.id == session.id);
if (idx >= 0) {
_syncreticSessions[idx] = session;
} else {
_syncreticSessions.insert(0, session);
}
await _saveSyncreticSessionsToPrefs();
}
Future<void> startNewSyncreticSession({String? question}) async {
final newSession = SyncreticSession(
id: 'syn_${DateTime.now().millisecondsSinceEpoch}',
date: DateTime.now(),
mainQuestion: question,
);
await saveSyncreticSession(newSession);
}
Future<void> _loadSyncreticSessionsFromPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final list = prefs.getStringList(_kSyncreticSessionsKey);
if (list != null) {
bool needsResave = false;
_syncreticSessions = list
.map((str) {
try {
final session = SyncreticSession.fromJson(str);
final healedReadings = session.readings.map((r) {
final healedItems = r.items.map((item) {
if (item.linkedWord != null) {
final canonical = getWordByName(item.linkedWord!.word);
if (canonical != null && canonical.sessionNumber != item.linkedWord!.sessionNumber) {
needsResave = true;
var newTitle = item.title;
final numRe = RegExp(r'#\d{3}');
if (numRe.hasMatch(newTitle)) {
newTitle = newTitle.replaceFirst(numRe, '#${canonical.formattedSessionNumber}');
}
return ReadingSignItem(
title: newTitle,
subtitle: item.subtitle,
symbol: item.symbol,
lore: item.lore,
isReversed: item.isReversed,
linkedWord: canonical,
meta: item.meta,
);
}
}
return item;
}).toList();
return SyncreticReading(
id: r.id,
systemId: r.systemId,
systemName: r.systemName,
systemIcon: r.systemIcon,
timestamp: r.timestamp,
layoutTitle: r.layoutTitle,
inputMethod: r.inputMethod,
items: healedItems,
note: r.note,
question: r.question,
);
}).toList();
return session.copyWith(readings: healedReadings);
} catch (_) {
return null;
}
})
.whereType<SyncreticSession>()
.toList();
if (needsResave) {
await _saveSyncreticSessionsToPrefs();
}
}
} catch (e) {
debugPrint('Error loading syncretic sessions: $e');
}
}
Future<void> _saveSyncreticSessionsToPrefs() async {
try {
final prefs = await SharedPreferences.getInstance();
final list = _syncreticSessions.map((s) => s.toJson()).toList();
await prefs.setStringList(_kSyncreticSessionsKey, list);
} catch (e) {
debugPrint('Error saving syncretic sessions: $e');
}
}
}