Files
eryao/apps/lib/features/divination/data/models/divination_backend_models.dart
T
qzl 55bac03eb0 fix: 修复历史卦象列表无法显示及六爻解卦提示词逻辑错误
后端 Pydantic schema 添加 Hant 繁体字段支持(guaNameHant,
targetGuaNameHant, spiritNameHant, relationNameHant),解决
DerivedDivinationData extra=forbid 拒绝 AI 输出的繁体字段导致
agent_output 解析失败、历史记录为空的问题。

六爻解卦提示词修复:增加静卦五行生克链分析、假破假空降权、
六冲中性判断、用神核对防捏造、空亡数据对照等硬约束。

前端 Dart model 同步添加 Hant 字段(反向兼容,缺省为空字符串)。

其他:硬币翻转动画修复、弹窗单按钮居中、起卦按钮布局调整、
繁体 l10n 清理、pre-commit 排除集成测试。
2026-04-14 12:37:19 +08:00

362 lines
11 KiB
Dart

import 'divination_params.dart';
import 'divination_result.dart';
class PointsBalanceData {
const PointsBalanceData({
required this.balance,
required this.frozenBalance,
required this.availableBalance,
required this.runCost,
required this.canRun,
});
factory PointsBalanceData.fromJson(Map<String, dynamic> json) {
return PointsBalanceData(
balance: _requiredInt(json, 'balance'),
frozenBalance: _requiredInt(json, 'frozenBalance'),
availableBalance: _requiredInt(json, 'availableBalance'),
runCost: _requiredInt(json, 'runCost'),
canRun: _requiredBool(json, 'canRun'),
);
}
final int balance;
final int frozenBalance;
final int availableBalance;
final int runCost;
final bool canRun;
}
class RunAcceptedData {
const RunAcceptedData({
required this.taskId,
required this.threadId,
required this.runId,
required this.created,
});
factory RunAcceptedData.fromJson(Map<String, dynamic> json) {
return RunAcceptedData(
taskId: _requiredString(json, 'taskId'),
threadId: _requiredString(json, 'threadId'),
runId: _requiredString(json, 'runId'),
created: _requiredBool(json, 'created'),
);
}
final String taskId;
final String threadId;
final String runId;
final bool created;
}
class DivinationRunAggregate {
const DivinationRunAggregate({
this.threadId,
required this.derived,
required this.signLevel,
required this.conclusion,
required this.focusPoints,
required this.advice,
required this.keywords,
required this.answer,
});
final DerivedDivinationData derived;
final String? threadId;
final String signLevel;
final List<String> conclusion;
final List<String> focusPoints;
final List<String> advice;
final List<String> keywords;
final String answer;
DivinationResultData toViewData(DivinationParams params) {
return DivinationResultData(
params: params,
threadId: threadId,
binaryCode: derived.binaryCode,
changedBinaryCode: derived.changedBinaryCode,
guaName: derived.guaName,
targetGuaName: derived.targetGuaName,
upperName: derived.upperName,
lowerName: derived.lowerName,
signType: signLevel,
keywords: keywords.join(''),
focusPoints: focusPoints,
conclusion: _asBullet(conclusion),
analysis: answer,
suggestion: _asBullet(advice),
ganzhi: GanzhiData(
yearGanZhi: derived.ganzhi.yearGanZhi,
monthGanZhi: derived.ganzhi.monthGanZhi,
dayGanZhi: derived.ganzhi.dayGanZhi,
timeGanZhi: derived.ganzhi.timeGanZhi,
yearKongWang: derived.ganzhi.yearKongWang,
monthKongWang: derived.ganzhi.monthKongWang,
dayKongWang: derived.ganzhi.dayKongWang,
timeKongWang: derived.ganzhi.timeKongWang,
yueJian: derived.ganzhi.yueJian,
riChen: derived.ganzhi.riChen,
yuePo: derived.ganzhi.yuePo,
riChong: derived.ganzhi.riChong,
),
wuXingStatus: derived.wuXingStatuses,
yaoLines: derived.yaoInfoList
.map((line) => line.toViewModel())
.toList(growable: false),
targetYaoLines: derived.targetYaoInfoList
.map((line) => line.toViewModel())
.toList(growable: false),
);
}
String _asBullet(List<String> lines) {
if (lines.isEmpty) {
return '';
}
return List<String>.generate(
lines.length,
(i) => '${i + 1}. ${lines[i]}',
growable: false,
).join('\n');
}
}
class DerivedDivinationData {
const DerivedDivinationData({
required this.question,
required this.questionType,
required this.divinationMethod,
required this.divinationTime,
required this.binaryCode,
required this.changedBinaryCode,
required this.guaName,
this.guaNameHant = '',
required this.upperName,
required this.lowerName,
required this.targetGuaName,
this.targetGuaNameHant = '',
required this.worldPosition,
required this.responsePosition,
required this.hasChangingYao,
required this.ganzhi,
required this.wuXingStatuses,
required this.yaoInfoList,
required this.targetYaoInfoList,
});
factory DerivedDivinationData.fromJson(Map<String, dynamic> json) {
final wuXingRaw = _requiredMap(json, 'wuXingStatuses');
return DerivedDivinationData(
question: _requiredString(json, 'question'),
questionType: _requiredString(json, 'questionType'),
divinationMethod: _requiredString(json, 'divinationMethod'),
divinationTime: _requiredString(json, 'divinationTime'),
binaryCode: _requiredString(json, 'binaryCode'),
changedBinaryCode: _requiredString(json, 'changedBinaryCode'),
guaName: _requiredString(json, 'guaName'),
guaNameHant: json['guaNameHant'] as String? ?? '',
upperName: _requiredString(json, 'upperName'),
lowerName: _requiredString(json, 'lowerName'),
targetGuaName: _requiredString(json, 'targetGuaName'),
targetGuaNameHant: json['targetGuaNameHant'] as String? ?? '',
worldPosition: _requiredInt(json, 'worldPosition'),
responsePosition: _requiredInt(json, 'responsePosition'),
hasChangingYao: _requiredBool(json, 'hasChangingYao'),
ganzhi: GanzhiBackend.fromJson(_requiredMap(json, 'ganzhi')),
wuXingStatuses: wuXingRaw.map(
(key, value) => MapEntry(key, value.toString()),
),
yaoInfoList: _parseYaoList(json['yaoInfoList']),
targetYaoInfoList: _parseYaoList(json['targetYaoInfoList']),
);
}
final String question;
final String questionType;
final String divinationMethod;
final String divinationTime;
final String binaryCode;
final String changedBinaryCode;
final String guaName;
final String guaNameHant;
final String upperName;
final String lowerName;
final String targetGuaName;
final String targetGuaNameHant;
final int worldPosition;
final int responsePosition;
final bool hasChangingYao;
final GanzhiBackend ganzhi;
final Map<String, String> wuXingStatuses;
final List<YaoBackendLine> yaoInfoList;
final List<YaoBackendLine> targetYaoInfoList;
static List<YaoBackendLine> _parseYaoList(Object? raw) {
final list = raw as List<dynamic>?;
if (list == null) {
throw const FormatException(
'Missing required list: yaoInfoList/targetYaoInfoList',
);
}
return list
.map((item) {
if (item is! Map<String, dynamic>) {
throw const FormatException('Invalid yao line item');
}
return YaoBackendLine.fromJson(item);
})
.toList(growable: false);
}
}
class GanzhiBackend {
const GanzhiBackend({
required this.yearGanZhi,
required this.monthGanZhi,
required this.dayGanZhi,
required this.timeGanZhi,
required this.yearKongWang,
required this.monthKongWang,
required this.dayKongWang,
required this.timeKongWang,
required this.yueJian,
required this.riChen,
required this.yuePo,
required this.riChong,
});
factory GanzhiBackend.fromJson(Map<String, dynamic> json) {
return GanzhiBackend(
yearGanZhi: _requiredString(json, 'yearGanZhi'),
monthGanZhi: _requiredString(json, 'monthGanZhi'),
dayGanZhi: _requiredString(json, 'dayGanZhi'),
timeGanZhi: _requiredString(json, 'timeGanZhi'),
yearKongWang: _requiredString(json, 'yearKongWang'),
monthKongWang: _requiredString(json, 'monthKongWang'),
dayKongWang: _requiredString(json, 'dayKongWang'),
timeKongWang: _requiredString(json, 'timeKongWang'),
yueJian: _requiredString(json, 'yueJian'),
riChen: _requiredString(json, 'riChen'),
yuePo: _requiredString(json, 'yuePo'),
riChong: _requiredString(json, 'riChong'),
);
}
final String yearGanZhi;
final String monthGanZhi;
final String dayGanZhi;
final String timeGanZhi;
final String yearKongWang;
final String monthKongWang;
final String dayKongWang;
final String timeKongWang;
final String yueJian;
final String riChen;
final String yuePo;
final String riChong;
}
class YaoBackendLine {
const YaoBackendLine({
required this.position,
required this.spiritName,
this.spiritNameHant = '',
required this.relationName,
this.relationNameHant = '',
required this.tiganName,
required this.elementName,
required this.isYang,
required this.isChanging,
required this.specialMark,
});
factory YaoBackendLine.fromJson(Map<String, dynamic> json) {
return YaoBackendLine(
position: _requiredInt(json, 'position'),
spiritName: _requiredString(json, 'spiritName'),
spiritNameHant: json['spiritNameHant'] as String? ?? '',
relationName: _requiredString(json, 'relationName'),
relationNameHant: json['relationNameHant'] as String? ?? '',
tiganName: _requiredString(json, 'tiganName'),
elementName: _requiredString(json, 'elementName'),
isYang: _requiredBool(json, 'isYang'),
isChanging: _requiredBool(json, 'isChanging'),
specialMark: _requiredStringAllowEmpty(json, 'specialMark'),
);
}
final int position;
final String spiritName;
final String spiritNameHant;
final String relationName;
final String relationNameHant;
final String tiganName;
final String elementName;
final bool isYang;
final bool isChanging;
final String specialMark;
YaoLineData toViewModel() {
final type = switch ((isYang, isChanging)) {
(true, false) => YaoType.youngYang,
(false, false) => YaoType.youngYin,
(true, true) => YaoType.oldYang,
(false, true) => YaoType.oldYin,
};
return YaoLineData(
index: position - 1,
spirit: spiritName,
relation: relationName,
branch: tiganName,
element: elementName,
type: type,
mark: specialMark,
);
}
}
String _requiredStringAllowEmpty(Map<String, dynamic> json, String key) {
if (!json.containsKey(key)) {
throw FormatException('Invalid or missing field: $key');
}
final value = json[key];
if (value is! String) {
throw FormatException('Invalid or missing field: $key');
}
return value;
}
String _requiredString(Map<String, dynamic> json, String key) {
final value = json[key];
if (value is! String || value.isEmpty) {
throw FormatException('Invalid or missing field: $key');
}
return value;
}
int _requiredInt(Map<String, dynamic> json, String key) {
final value = json[key];
if (value is! num) {
throw FormatException('Invalid or missing field: $key');
}
return value.toInt();
}
bool _requiredBool(Map<String, dynamic> json, String key) {
final value = json[key];
if (value is! bool) {
throw FormatException('Invalid or missing field: $key');
}
return value;
}
Map<String, dynamic> _requiredMap(Map<String, dynamic> json, String key) {
final value = json[key];
if (value is! Map<String, dynamic>) {
throw FormatException('Invalid or missing field: $key');
}
return value;
}