Files
999/lib/models/word_model.dart

623 lines
24 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'culture_branch.dart';
class SymbolLayer {
final String system;
final String title;
final String law;
const SymbolLayer({
required this.system,
required this.title,
required this.law,
});
factory SymbolLayer.fromMap(Map<String, dynamic> map) {
return SymbolLayer(
system: map['system'] as String? ?? '',
title: map['title'] as String? ?? '',
law: map['law'] as String? ?? '',
);
}
Map<String, dynamic> toMap() => {
'system': system,
'title': title,
'law': law,
};
}
class SymbolSource {
final String system;
final String ref;
final double weight;
const SymbolSource({
required this.system,
required this.ref,
required this.weight,
});
factory SymbolSource.fromMap(Map<String, dynamic> map) {
return SymbolSource(
system: map['system'] as String? ?? '',
ref: map['ref'] as String? ?? '',
weight: (map['weight'] as num?)?.toDouble() ?? 1.0,
);
}
Map<String, dynamic> toMap() => {
'system': system,
'ref': ref,
'weight': weight,
};
}
class SymbolGrammar {
final String operator;
final String valence;
final String modifies;
const SymbolGrammar({
this.operator = 'substance',
this.valence = 'liminal',
this.modifies = '',
});
factory SymbolGrammar.fromMap(Map<String, dynamic> map) {
return SymbolGrammar(
operator: map['operator'] as String? ?? 'substance',
valence: map['valence'] as String? ?? 'liminal',
modifies: map['modifies'] as String? ?? '',
);
}
Map<String, dynamic> toMap() => {
'operator': operator,
'valence': valence,
'modifies': modifies,
};
}
class WordItem {
final int id; // Base index 0..999
final int sessionNumber; // Dynamic randomized session number 0..999
final String word;
final String partOfSpeech;
final double frequency;
final String category;
final String description;
final String emoji;
// Enriched Oracle Knowledge Base fields
final String family;
final String operator;
final String valence;
final List<String> jurisdictions;
final String lookup; // 'A', 'B', 'C'
final bool hasCanon;
final String core;
final String oracle;
final List<SymbolLayer> layers;
final String shadow;
final String asks;
final String step;
final List<String> binds;
final List<SymbolSource> sources;
final SymbolGrammar? grammar;
final bool isUserEdited;
final Map<String, String> cultures;
final Map<String, String> languages;
const WordItem({
required this.id,
this.sessionNumber = 0,
required this.word,
required this.partOfSpeech,
required this.frequency,
this.category = '',
this.description = '',
this.emoji = '',
this.family = '',
this.operator = 'substance',
this.valence = 'liminal',
this.jurisdictions = const [],
this.lookup = 'C',
this.hasCanon = false,
this.core = '',
this.oracle = '',
this.layers = const [],
this.shadow = '',
this.asks = '',
this.step = '',
this.binds = const [],
this.sources = const [],
this.grammar,
this.isUserEdited = false,
this.cultures = const {},
this.languages = const {},
});
WordItem copyWith({
int? id,
int? sessionNumber,
String? word,
String? partOfSpeech,
double? frequency,
String? category,
String? description,
String? emoji,
String? family,
String? operator,
String? valence,
List<String>? jurisdictions,
String? lookup,
bool? hasCanon,
String? core,
String? oracle,
List<SymbolLayer>? layers,
String? shadow,
String? asks,
String? step,
List<String>? binds,
List<SymbolSource>? sources,
SymbolGrammar? grammar,
bool? isUserEdited,
Map<String, String>? cultures,
Map<String, String>? languages,
}) {
return WordItem(
id: id ?? this.id,
sessionNumber: sessionNumber ?? this.sessionNumber,
word: word ?? this.word,
partOfSpeech: partOfSpeech ?? this.partOfSpeech,
frequency: frequency ?? this.frequency,
category: category ?? this.category,
description: description ?? this.description,
emoji: emoji ?? this.emoji,
family: family ?? this.family,
operator: operator ?? this.operator,
valence: valence ?? this.valence,
jurisdictions: jurisdictions ?? this.jurisdictions,
lookup: lookup ?? this.lookup,
hasCanon: hasCanon ?? this.hasCanon,
core: core ?? this.core,
oracle: oracle ?? this.oracle,
layers: layers ?? this.layers,
shadow: shadow ?? this.shadow,
asks: asks ?? this.asks,
step: step ?? this.step,
binds: binds ?? this.binds,
sources: sources ?? this.sources,
grammar: grammar ?? this.grammar,
isUserEdited: isUserEdited ?? this.isUserEdited,
cultures: cultures ?? this.cultures,
languages: languages ?? this.languages,
);
}
/// Enrich existing WordItem with data from oracle_kb.json entry
WordItem enrichWithKb(Map<String, dynamic> kb) {
final rawLayers = kb['layers'] as List<dynamic>? ?? [];
final parsedLayers = rawLayers
.whereType<Map<String, dynamic>>()
.map((m) => SymbolLayer.fromMap(m))
.toList();
final rawSourcesList = kb['sources'] is List ? (kb['sources'] as List) : [];
final parsedSources = rawSourcesList
.whereType<Map<String, dynamic>>()
.map((m) => SymbolSource.fromMap(m))
.toList();
final rawJurisdictions = kb['jurisdictions'] is List
? (kb['jurisdictions'] as List).map((e) => e.toString()).toList()
: <String>[];
final rawBinds = kb['binds'] is List
? (kb['binds'] as List).map((e) => e.toString()).toList()
: (kb['binds'] is String ? [kb['binds'].toString()] : <String>[]);
final rawGrammar = kb['grammar'] as Map<String, dynamic>?;
final rawKbCultures = kb['cultures'] as Map<String, dynamic>?;
final parsedKbCultures = rawKbCultures != null
? rawKbCultures.map((k, v) => MapEntry(k.toString(), v.toString()))
: <String, String>{};
final rawKbLanguages = kb['languages'] as Map<String, dynamic>?;
final parsedKbLanguages = rawKbLanguages != null
? rawKbLanguages.map((k, v) => MapEntry(k.toString(), v.toString()))
: <String, String>{};
final kbCore = (kb['core'] as String? ?? '').trim();
final kbOracle = (kb['oracle'] as String? ?? '').trim();
final kbHasCanon = (kb['has_canon'] as bool? ?? false) || (kbCore.isNotEmpty && kbOracle.isNotEmpty);
return copyWith(
partOfSpeech: partOfSpeech.isNotEmpty ? partOfSpeech : ((kb['pos'] as String?) ?? partOfSpeech),
category: (kb['category'] as String?)?.isNotEmpty == true ? kb['category'] as String : category,
family: kb['family'] as String? ?? family,
operator: kb['operator'] as String? ?? operator,
valence: kb['valence'] as String? ?? valence,
jurisdictions: rawJurisdictions.isNotEmpty ? rawJurisdictions : jurisdictions,
lookup: kb['lookup'] as String? ?? lookup,
hasCanon: kbHasCanon,
core: kbCore,
oracle: kbOracle,
layers: parsedLayers,
shadow: kb['shadow'] as String? ?? shadow,
asks: kb['asks'] as String? ?? asks,
step: kb['step'] as String? ?? step,
binds: rawBinds,
sources: parsedSources,
emoji: (kb['emoji'] as String?)?.isNotEmpty == true ? kb['emoji'] as String : emoji,
grammar: rawGrammar != null ? SymbolGrammar.fromMap(rawGrammar) : grammar,
cultures: parsedKbCultures.isNotEmpty ? parsedKbCultures : cultures,
languages: parsedKbLanguages.isNotEmpty ? parsedKbLanguages : languages,
);
}
/// Returns description stripped of raw metadata bracket tags like [Таро: ...]
String get cleanDescription {
final clean = description.replaceAll(RegExp(r'\[[^\]]+\]\s*'), '').trim();
return clean.isNotEmpty ? clean : description;
}
/// Культурные ветви знака: явная редакция (cultures) приоритетна,
/// иначе — автоматическое разложение толкования по традициям.
Map<String, String> get resolvedCultures {
if (cultures.isNotEmpty) return cultures;
return extractCulturalBranches('$oracle\n\n$shadow\n\n$cleanDescription');
}
/// Primary poetic/canonical essence: core if available, otherwise clean description
String get displayMeaning {
if (core.isNotEmpty) return core;
if (cleanDescription.isNotEmpty) return cleanDescription;
return description;
}
String get formattedSessionNumber => sessionNumber.toString().padLeft(3, '0');
String get formattedId => formattedSessionNumber;
Map<String, dynamic> toMap() {
return {
'id': id,
'sessionNumber': sessionNumber,
'word': word,
'partOfSpeech': partOfSpeech,
'frequency': frequency,
'category': category,
'description': description,
'emoji': emoji,
'family': family,
'operator': operator,
'valence': valence,
'jurisdictions': jurisdictions,
'lookup': lookup,
'hasCanon': hasCanon,
'core': core,
'oracle': oracle,
'layers': layers.map((l) => l.toMap()).toList(),
'shadow': shadow,
'asks': asks,
'step': step,
'binds': binds,
'sources': sources.map((s) => s.toMap()).toList(),
'grammar': grammar?.toMap(),
'cultures': cultures,
'languages': languages,
};
}
factory WordItem.fromMap(Map<String, dynamic> map) {
final rawLayers = map['layers'] as List<dynamic>? ?? [];
final parsedLayers = rawLayers
.whereType<Map<String, dynamic>>()
.map((m) => SymbolLayer.fromMap(m))
.toList();
final rawSources = map['sources'] as List<dynamic>? ?? [];
final parsedSources = rawSources
.whereType<Map<String, dynamic>>()
.map((m) => SymbolSource.fromMap(m))
.toList();
final rawJurisdictions = (map['jurisdictions'] as List<dynamic>? ?? [])
.map((e) => e.toString())
.toList();
final rawBinds = (map['binds'] as List<dynamic>? ?? [])
.map((e) => e.toString())
.toList();
final rawGrammar = map['grammar'] as Map<String, dynamic>?;
final rawCultures = map['cultures'] as Map<String, dynamic>?;
final parsedCultures = rawCultures != null
? rawCultures.map((k, v) => MapEntry(k.toString(), v.toString()))
: <String, String>{};
final rawLanguages = map['languages'] as Map<String, dynamic>?;
final parsedLanguages = rawLanguages != null
? rawLanguages.map((k, v) => MapEntry(k.toString(), v.toString()))
: <String, String>{};
return WordItem(
id: map['id'] as int,
sessionNumber: map['sessionNumber'] as int? ?? (map['id'] as int),
word: map['word'] as String,
partOfSpeech: map['partOfSpeech'] as String? ?? '',
frequency: (map['frequency'] as num?)?.toDouble() ?? 50.0,
category: map['category'] as String? ?? '',
description: map['description'] as String? ?? '',
emoji: map['emoji'] as String? ?? '',
family: map['family'] as String? ?? '',
operator: map['operator'] as String? ?? 'substance',
valence: map['valence'] as String? ?? 'liminal',
jurisdictions: rawJurisdictions,
lookup: map['lookup'] as String? ?? 'C',
hasCanon: map['hasCanon'] as bool? ?? false,
core: map['core'] as String? ?? '',
oracle: map['oracle'] as String? ?? '',
layers: parsedLayers,
shadow: map['shadow'] as String? ?? '',
asks: map['asks'] as String? ?? '',
step: map['step'] as String? ?? '',
binds: rawBinds,
sources: parsedSources,
grammar: rawGrammar != null ? SymbolGrammar.fromMap(rawGrammar) : null,
isUserEdited: map['isUserEdited'] as bool? ?? false,
cultures: parsedCultures,
languages: parsedLanguages,
);
}
@override
String toString() => '#: ()';
}
extension WordItemLocalization on WordItem {
String get localizedValence => localizeValence(valence);
String get localizedOperator => localizeOperator(operator);
String get localizedFamily => localizeFamily(family);
static String localizeValence(String v) {
switch (v.trim().toLowerCase()) {
case 'active':
return 'Активная';
case 'receptive':
return 'Восприимчивая';
case 'liminal':
return 'Лиминальная';
case 'stable':
return 'Устойчивая';
case 'cutting':
return 'Отсекающая';
case 'binding':
return 'Связующая';
case 'solar':
return 'Солярная';
case 'lunar':
return 'Лунарная';
case 'light':
return 'Светлая';
case 'dark':
return 'Теневая';
case 'neutral':
return 'Нейтральная';
case 'chthonic':
return 'Хтоническая';
default:
final s = v.trim();
if (s.isEmpty) return '';
return s[0].toUpperCase() + s.substring(1);
}
}
static String localizeOperator(String op) {
switch (op.trim().toLowerCase()) {
case 'substance':
return 'Субстанция';
case 'act':
return 'Действие';
case 'relation':
return 'Связь';
case 'shifter':
return 'Сдвиг';
case 'qualifier':
return 'Качество';
case 'filter':
return 'Фильтр';
case 'pronoun':
return 'Указатель';
case 'number':
return 'Число';
case 'nexus':
return 'Узел';
case 'vector':
return 'Вектор';
case 'catalyst':
return 'Катализатор';
default:
final s = op.trim();
if (s.isEmpty) return '';
return s[0].toUpperCase() + s.substring(1);
}
}
static String localizeFamily(String f) {
switch (f.trim().toLowerCase()) {
case 'totem':
return 'Тотем';
case 'sacred_symbol':
return 'Сакральный символ';
case 'sacred_place':
return 'Сакральное место';
case 'element':
return 'Стихия';
case 'cosmos':
return 'Космос';
case 'body':
return 'Тело';
case 'moral':
return 'Этика';
case 'quality':
return 'Качество';
case 'time_calendar':
return 'Календарь';
case 'time_cycle':
return 'Цикл времени';
case 'cult_object':
return 'Культ';
case 'person_archetype':
return 'Архетип';
case 'social':
return 'Социум';
case 'speech':
return 'Речь';
case 'circumstance':
return 'Обстоятельство';
case 'conjunction':
return 'Связка';
case 'preposition':
return 'Отношение';
case 'polarity':
return 'Полярность';
case 'verb_transform':
return 'Трансформация';
case 'particle':
return 'Частица';
case 'determiner':
return 'Определитель';
case 'adverb_index':
return 'Индекс';
case 'misc':
return 'Разное';
default:
final s = f.trim();
if (s.isEmpty) return '';
return s[0].toUpperCase() + s.substring(1);
}
}
static String localizeSystem(String sys) {
switch (sys.trim().toLowerCase()) {
case 'tarot':
return 'Таро';
case 'runes':
return 'Руны';
case 'lenormand':
return 'Ленорман';
case 'iching':
case 'i ching':
return 'И Цзин';
case 'astrology':
return 'Астрология';
case 'alchemy':
return 'Алхимия';
case 'kabbalah':
return 'Каббала';
case 'ogham':
return 'Огам';
case 'cosmology':
return 'Космология';
case 'slavic':
return 'Славянский канон';
case 'bestiary':
return 'Бестиарий';
case 'dream':
return 'Сновидения';
case 'geomancy':
return 'Геомантия';
default:
final s = sys.trim();
if (s.isEmpty) return '';
return s[0].toUpperCase() + s.substring(1);
}
}
static String cleanTagDetail(String det) {
var s = det.trim();
// Remove Latin transliterations like (Cancer / Вода / Луна) -> (Вода · Луна)
s = s.replaceAllMapped(RegExp(r'\([A-Za-z]+\s*[/·]\s*([^)]+)\)'), (m) => '(${m.group(1)})');
s = s.replaceAll(' / ', ' · ');
// Geomancy 12 Puer (Юноша) -> 12 Юноша
s = s.replaceAllMapped(RegExp(r'(\d+\s+)[A-Za-z\s_]+\(([А-Яа-яЁё\s·-]+)\)'), (m) => '${m.group(1)}${m.group(2)}');
// Alchemy Огонь (Ignis) -> Огонь
if (!RegExp(r'[ᚠ-ᛪ]').hasMatch(s)) {
s = s.replaceAllMapped(RegExp(r'([А-Яа-яЁё\s]+)\s*\([A-Za-z\s_]+\)'), (m) => m.group(1)!);
}
// Runes: (Kenaz / ᚲ) -> (ᚲ)
s = s.replaceAllMapped(RegExp(r'\([A-Za-z]+\s*[/·]\s*([ᚠ-ᛪ])\)'), (m) => '(${m.group(1)})');
// Clean leftover English in parens
s = s.replaceAll(RegExp(r'\([A-Za-z\s_]+\)'), '');
return s.trim();
}
static String localizeSourceRef(String system, String ref) {
final r = ref.trim().toLowerCase();
const map = <String, String>{
// Runes
'fehu': 'Феху (ᚠ)', 'uruz': 'Уруз (ᚢ)', 'thurisaz': 'Турисаз (ᚦ)', 'ansuz': 'Ансуз (ᚨ)',
'raidho': 'Райдо (ᚱ)', 'kenaz': 'Кеназ (ᚲ)', 'gebo': 'Гебо (ᚷ)', 'wunjo': 'Вуньо (ᚹ)',
'hagalaz': 'Хагалаз (ᚺ)', 'nauthiz': 'Наутиз (ᚾ)', 'isa': 'Иса (ᛁ)', 'jera': 'Йера (ᛃ)',
'eihwaz': 'Эйваз (ᛇ)', 'perthro': 'Перт (ᛈ)', 'algiz': 'Альгиз (ᛉ)', 'sowilo': 'Совило (ᛊ)',
'tiwaz': 'Тейваз (ᛏ)', 'berkano': 'Беркана (ᛒ)', 'ehwaz': 'Эваз (ᛖ)', 'mannaz': 'Манназ (ᛗ)',
'laguz': 'Лагуз (ᛚ)', 'ingwaz': 'Ингваз (ᛜ)', 'dagaz': 'Дагаз (ᛞ)', 'othala': 'Отала (ᛟ)',
// Tarot
'wands': 'Жезлы (Огонь)', 'pentacles': 'Пентакли (Земля)', 'cups': 'Кубки (Вода)', 'swords': 'Мечи (Воздух)',
'fool': 'Шут (0)', 'magician': 'Маг (I)', 'priestess': 'Жрица (II)', 'empress': 'Императрица (III)',
'emperor': 'Император (IV)', 'hierophant': 'Иерофант (V)', 'lovers': 'Влюблённые (VI)', 'chariot': 'Колесница (VII)',
'strength': 'Сила (VIII)', 'hermit': 'Отшельник (IX)', 'wheel': 'Колесо Фортуны (X)', 'justice': 'Справедливость (XI)',
'hanged': 'Повешенный (XII)', 'death': 'Смерть (XIII)', 'temperance': 'Умеренность (XIV)', 'devil': 'Дьявол (XV)',
'tower': 'Башня (XVI)', 'star': 'Звезда (XVII)', 'moon': 'Луна', 'sun': 'Солнце',
'judgement': 'Суд (XX)', 'world': 'Мир (XXI)',
// I Ching
'li': 'Ли ☲ (Огонь)', 'kan': 'Кань ☵ (Вода)', 'xun': 'Сюнь ☴ (Ветер)', 'kun': 'Кунь ☷ (Земля)',
'qian': 'Цянь ☰ (Небо)', 'zhen': 'Чжэнь ☳ (Гром)', 'gen': 'Гэнь ☶ (Гора)', 'dui': 'Дуй ☱ (Водоём)',
'kan_repeat': 'Кань ䷜ (Повторная опасность)', 'pi': 'Пи ䷋ (Упадок)', 'tai': 'Тай ䷊ (Расцвет)',
'gu': 'Гу ䷑ (Исправление)', 'fu': 'Фу ䷗ (Возврат)', 'meng': 'Мэн ䷃ (Юность)', 'ge': 'Гэ ䷰ (Смена)',
'wei_ji': 'Вэй-цзи ䷿ (Ещё не конец)',
// Alchemy
'ignis': 'Огонь', 'aqua': 'Вода', 'aer': 'Воздух', 'terra': 'Земля',
'sulfur': 'Сера (Душа)', 'mercury': 'Ртуть (Дух)', 'sal': 'Соль (Тело)',
'albedo': 'Альбедо (Белизна)', 'nigredo': 'Нигредо (Чернота)', 'rubedo': 'Рубедо (Краснота)',
'ash': 'Пепел', 'luna': 'Луна (Серебро)', 'egg': 'Философское Яйцо', 'vessel': 'Сосуд Гермеса', 'forge': 'Горн',
// Astrology
'comet': 'Комета', 'eclipse': 'Затмение', 'dawn': 'Рассвет', 'dusk': 'Закат',
'zenith': 'Зенит', 'nadir': 'Надир', 'constellation': 'Созвездие', 'planet': 'Планета', 'meteor': 'Метеор',
'equinox': 'Равноденствие', 'orbit': 'Орбита', 'libra': 'Весы', 'aries': 'Овен', 'taurus': 'Телец',
'gemini': 'Близнецы', 'cancer': 'Рак', 'leo': 'Лев', 'virgo': 'Дева', 'scorpio': 'Скорпион',
'sagittarius': 'Стрелец', 'capricorn': 'Козерог', 'aquarius': 'Водолей', 'pisces': 'Рыбы',
// Bestiary
'dog': 'Собака (Страж)', 'snake': 'Змея (Мудрость)', 'eagle': 'Орёл (Высота)', 'wolf': 'Волк (Чутьё)',
'raven': 'Ворон (Память)', 'lion': 'Лев (Царственность)', 'owl': 'Сова (Ночное зрение)',
'serpent_feather': 'Пернатый Змей', 'dragon': 'Дракон', 'phoenix': 'Феникс', 'bear': 'Медведь (Сила)',
'fox': 'Лиса (Хитрость)', 'stag': 'Олень (Благородство)', 'horse': 'Конь (Порыв)', 'spider': 'Паук (Нить судьбы)',
// Virtues & Archetypes
'courage': 'Мужество', 'charity': 'Милосердие', 'karma': 'Карма / Воздаяние', 'guilt': 'Вина',
'shame': 'Стыд', 'pride': 'Гордыня', 'humility': 'Смирение', 'envy': 'Зависть', 'vow': 'Обет',
'greed': 'Алчность', 'gluttony': 'Чревоугодие', 'sloth': 'Уныние', 'wrath': 'Гнев', 'lust': 'Страсть',
'donor': 'Даритель', 'dispatcher': 'Отправитель', 'shadow': 'Тень', 'hero': 'Герой', 'self': 'Самость',
'helper': 'Помощник', 'witch': 'Ведунья', 'princess': 'Царевна', 'exile': 'Изгнанник',
'false_hero': 'Ложный герой', 'judge': 'Судия', 'trickster': 'Трикстер',
// Slavic
'mesyats': 'Месяц ясный', 'oak': 'Священный Дуб', 'willow': 'Плакучая Ива', 'birch': 'Берёза',
'ivy_field': 'Чисто поле', 'crossroads': 'Перепутье', 'threshold': 'Порог', 'well': 'Студёный колодец', 'hearth': 'Родной очаг',
// Ogham
'dair': 'Дуб (Даир / ᚇ)', 'sail': 'Ива (Салье / ᚄ)', 'beith': 'Берёза (Бэйт / ᚁ)', 'gort': 'Плющ (Горт / ᚌ)',
// Symbols & Dreams
'fire': 'Огонь', 'water': 'Вода', 'air': 'Воздух', 'earth': 'Земля',
'key': 'Ключ от врат', 'spark': 'Живая искра', 'path': 'Тропа / Путь', 'book': 'Книга судеб',
'blood': 'Кровь / Род', 'heart': 'Сердце',
'fire_d': 'Сон об огне', 'water_d': 'Сон о глубине', 'death_d': 'Сон о переходе', 'key_d': 'Сон о тайне',
'path_d': 'Сон о странствии', 'child_d': 'Сон о дитяти', 'mirror_d': 'Сон о зеркале', 'wolf_d': 'Сон о звере',
'dream_fire': 'Сон об огне',
};
if (map.containsKey(r)) return map[r]!;
final s = ref.trim().replaceAll('_', ' ');
if (s.isEmpty) return '';
return s[0].toUpperCase() + s.substring(1);
}
}