feat: complete academic symbol lexicon heal, 2-column Cooper extraction, and updated assets
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user