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