You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
614 lines
21 KiB
614 lines
21 KiB
import 'package:get/get.dart';
|
|
import 'package:get_storage/get_storage.dart';
|
|
import '../models/translation_usage_stats.dart';
|
|
import '../models/meeting_usage_stats.dart';
|
|
import '../../core/utils/logger.dart';
|
|
import 'network/api.dart';
|
|
import '../models/goods.dart';
|
|
import '../models/user_Info.dart';
|
|
|
|
/// 使用量统计服务
|
|
///
|
|
class UsageStatsService extends GetxService {
|
|
static const String _dailyStatsKey = 'daily_translation_stats';
|
|
static const String _meetingDailyKey = 'daily_meeting_asr_stats';
|
|
|
|
final GetStorage _storage = GetStorage();
|
|
|
|
// 实时统计数据
|
|
final Rx<RealtimeUsageStats> realtimeTranslationStats =
|
|
RealtimeUsageStats().obs;
|
|
|
|
// 当日统计数据
|
|
final Rx<TranslationUsageStats> todayTranslationStats =
|
|
TranslationUsageStats.empty('').obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
_initializeService();
|
|
}
|
|
|
|
/// 初始化服务
|
|
void _initializeService() {
|
|
_loadTodayTranslationStats();
|
|
Logger.info('lxm--使用量统计服务初始化完成');
|
|
}
|
|
|
|
/// 加载当日翻译统计数据
|
|
void _loadTodayTranslationStats() {
|
|
try {
|
|
final today = DateTime.now();
|
|
final todayKey =
|
|
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}';
|
|
|
|
final statsData = _storage.read('${_dailyStatsKey}_$todayKey');
|
|
if (statsData != null) {
|
|
todayTranslationStats.value = TranslationUsageStats.fromJson(
|
|
Map<String, dynamic>.from(statsData));
|
|
} else {
|
|
todayTranslationStats.value =
|
|
TranslationUsageStats.empty(getCurrentUserId());
|
|
}
|
|
} catch (e) {
|
|
Logger.error('加载当日翻译统计数据失败: $e');
|
|
todayTranslationStats.value =
|
|
TranslationUsageStats.empty(getCurrentUserId());
|
|
}
|
|
}
|
|
|
|
/// 获取当前用户ID
|
|
String getCurrentUserId() {
|
|
try {
|
|
final userInfo = _storage.read('user_info');
|
|
if (userInfo != null && userInfo['user'] != null) {
|
|
return userInfo['user']['uid'] ?? 'anonymous';
|
|
}
|
|
} catch (e) {
|
|
Logger.warning('获取用户ID失败: $e');
|
|
}
|
|
return 'anonymous';
|
|
}
|
|
|
|
MeetingAsrDailyStats _loadMeetingDaily(String dateKey) {
|
|
final data = _storage.read('${_meetingDailyKey}_$dateKey');
|
|
if (data != null) {
|
|
return MeetingAsrDailyStats.fromJson(Map<String, dynamic>.from(data));
|
|
}
|
|
return MeetingAsrDailyStats(
|
|
date: dateKey, userId: getCurrentUserId(), items: []);
|
|
}
|
|
|
|
void _saveMeetingDaily(MeetingAsrDailyStats stats) {
|
|
_storage.write('${_meetingDailyKey}_${stats.date}', stats.toJson());
|
|
Logger.d('MeetingASRrecord',
|
|
'保存会议当日统计: date=${stats.date}, items=${stats.items.length}');
|
|
}
|
|
|
|
String _formatDate(DateTime d) {
|
|
final y = d.year;
|
|
final m = d.month.toString().padLeft(2, '0');
|
|
final day = d.day.toString().padLeft(2, '0');
|
|
return '$y-$m-$day';
|
|
}
|
|
|
|
/// 添加或更新会议ASR记录
|
|
/// [record] 会议ASR记录
|
|
/// 该方法用于记录一次会议语音识别(ASR)的提交记录。它会根据记录的时间戳确定日期,
|
|
/// 并将记录添加到该日期的会议ASR记录列表中。如果记录已存在,则会更新该记录。
|
|
void addMeetingAsrRecord(MeetingAsrRecord record) {
|
|
final dateKey = _formatDate(
|
|
DateTime.fromMillisecondsSinceEpoch(record.submitTimestamp));
|
|
final daily = _loadMeetingDaily(dateKey);
|
|
final idx = daily.items.indexWhere((e) => e.id == record.id);
|
|
if (idx >= 0) {
|
|
daily.items[idx] = record;
|
|
Logger.d('MeetingASRrecord',
|
|
'更新会议ASR记录: id=${record.id}, meetingId=${record.meetingId}, date=${dateKey}');
|
|
} else {
|
|
daily.items.add(record);
|
|
Logger.d('MeetingASRrecord',
|
|
'新增会议ASR记录: id=${record.id}, meetingId=${record.meetingId}, date=${dateKey}');
|
|
}
|
|
_saveMeetingDaily(daily);
|
|
Logger.i('MeetingASRrecord',
|
|
'会议ASR提交: id=${record.id}, meetingId=${record.meetingId}, audioSeconds=${record.audioSeconds}, fileSizeBytes=${record.fileSizeBytes}, language=${record.language}, date=${dateKey}');
|
|
}
|
|
|
|
/// 完成会议ASR记录
|
|
/// [id] 会议ASR记录ID
|
|
/// [transcriptBytes] 转写结果文本字节数
|
|
/// [completeTimestamp] 完成时间戳
|
|
/// 该方法用于标记一次会议语音识别(ASR)任务为已完成状态。
|
|
/// 它会根据记录ID在最近3天的会议ASR记录列表中查找匹配项,
|
|
/// 如果找到,则更新该记录的状态为已完成,并记录转写结果文本字节数和完成时间戳。
|
|
void completeMeetingAsrRecord({
|
|
required String id,
|
|
required int transcriptBytes,
|
|
required int completeTimestamp,
|
|
}) {
|
|
final now = DateTime.now();
|
|
for (int i = 0; i < 3; i++) {
|
|
final dateKey = _formatDate(now.subtract(Duration(days: i)));
|
|
final daily = _loadMeetingDaily(dateKey);
|
|
final idx = daily.items.indexWhere((e) => e.id == id);
|
|
if (idx >= 0) {
|
|
final old = daily.items[idx];
|
|
daily.items[idx] = MeetingAsrRecord(
|
|
id: old.id,
|
|
meetingId: old.meetingId,
|
|
taskId: old.taskId,
|
|
audioSeconds: old.audioSeconds,
|
|
fileSizeBytes: old.fileSizeBytes,
|
|
language: old.language,
|
|
submitTimestamp: old.submitTimestamp,
|
|
status: asrStatusCompleted,
|
|
transcriptBytes: transcriptBytes,
|
|
completeTimestamp: completeTimestamp,
|
|
);
|
|
_saveMeetingDaily(daily);
|
|
Logger.i('MeetingASRrecord',
|
|
'会议ASR完成: id=${id}, meetingId=${old.meetingId}, transcriptBytes=${transcriptBytes}, audioSeconds=${old.audioSeconds}, date=${dateKey}');
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 记录翻译API调用
|
|
void recordTranslationApiCall({
|
|
required String sourceText,
|
|
required String targetText,
|
|
required String sourceLanguage,
|
|
required String targetLanguage,
|
|
required String mode,
|
|
}) {
|
|
try {
|
|
// 更新实时统计
|
|
realtimeTranslationStats.value.translationCount++;
|
|
realtimeTranslationStats.value.sourceTextCharacters += sourceText.length;
|
|
realtimeTranslationStats.value.ttsTextCharacters += targetText.length;
|
|
realtimeTranslationStats.value.currentLanguagePair =
|
|
'$sourceLanguage-$targetLanguage';
|
|
realtimeTranslationStats.value.currentMode = mode;
|
|
realtimeTranslationStats.refresh();
|
|
|
|
// 更新当日统计 - 使用多维度统计
|
|
final languagePair = '$sourceLanguage→$targetLanguage';
|
|
|
|
// 更新语言对统计
|
|
final newLanguagePairStats = Map<String, LanguagePairStats>.from(
|
|
todayTranslationStats.value.languagePairStats);
|
|
if (newLanguagePairStats.containsKey(languagePair)) {
|
|
final existing = newLanguagePairStats[languagePair]!;
|
|
newLanguagePairStats[languagePair] = LanguagePairStats(
|
|
languagePair: languagePair,
|
|
translationCount: existing.translationCount + 1,
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length,
|
|
);
|
|
} else {
|
|
newLanguagePairStats[languagePair] = LanguagePairStats(
|
|
languagePair: languagePair,
|
|
translationCount: 1,
|
|
sourceCharacters: sourceText.length,
|
|
);
|
|
}
|
|
|
|
// 更新模式统计
|
|
final newModeStats = Map<String, TranslationModeStats>.from(
|
|
todayTranslationStats.value.modeStats);
|
|
if (newModeStats.containsKey(mode)) {
|
|
final existing = newModeStats[mode]!;
|
|
newModeStats[mode] = existing.copyWith(
|
|
apiCalls: existing.apiCalls + 1,
|
|
sourceCharacters: existing.sourceCharacters + sourceText.length,
|
|
ttsCharacters: existing.ttsCharacters + targetText.length,
|
|
);
|
|
} else {
|
|
newModeStats[mode] = TranslationModeStats(
|
|
apiCalls: 1,
|
|
sourceCharacters: sourceText.length,
|
|
ttsCharacters: targetText.length,
|
|
);
|
|
}
|
|
|
|
todayTranslationStats.value = todayTranslationStats.value.copyWith(
|
|
translationApiCalls:
|
|
todayTranslationStats.value.translationApiCalls + 1,
|
|
sourceTextCharacters: todayTranslationStats.value.sourceTextCharacters +
|
|
sourceText.length,
|
|
ttsTextCharacters:
|
|
todayTranslationStats.value.ttsTextCharacters + targetText.length,
|
|
languagePairStats: newLanguagePairStats,
|
|
modeStats: newModeStats,
|
|
);
|
|
|
|
_saveTodayStats();
|
|
|
|
Logger.info(
|
|
'记录翻译API调用: 源文本${sourceText.length}字符, 目标文本${targetText.length}字符');
|
|
} catch (e) {
|
|
Logger.error('记录翻译API调用失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 记录ASR API调用 - 优化版(精确统计参数)
|
|
void recordAsrApiCall({
|
|
required int effectiveDurationSeconds, // 有效语音时长(VAD检测的实际语音)
|
|
required int totalDurationSeconds, // 总会话时长(包含静音)
|
|
required String language,
|
|
required String mode, // 翻译模式
|
|
String? audioFormat, // 音频格式(可选)
|
|
int? sampleRate, // 采样率(可选)
|
|
}) {
|
|
try {
|
|
// 更新实时统计(使用有效语音时长作为主要计费依据)
|
|
realtimeTranslationStats.value.effectiveAudioDuration +=
|
|
effectiveDurationSeconds;
|
|
realtimeTranslationStats.value.totalSessionDuration +=
|
|
totalDurationSeconds;
|
|
realtimeTranslationStats.refresh();
|
|
|
|
// 更新模式统计
|
|
final newModeStats = Map<String, TranslationModeStats>.from(
|
|
todayTranslationStats.value.modeStats);
|
|
if (newModeStats.containsKey(mode)) {
|
|
final existing = newModeStats[mode]!;
|
|
newModeStats[mode] = existing.copyWith(
|
|
effectiveAudioDuration:
|
|
existing.effectiveAudioDuration + effectiveDurationSeconds,
|
|
);
|
|
} else {
|
|
newModeStats[mode] = TranslationModeStats(
|
|
effectiveAudioDuration: effectiveDurationSeconds,
|
|
);
|
|
}
|
|
|
|
// 更新当日统计
|
|
todayTranslationStats.value = todayTranslationStats.value.copyWith(
|
|
asrApiCalls: todayTranslationStats.value.asrApiCalls + 1,
|
|
effectiveAudioDuration:
|
|
todayTranslationStats.value.effectiveAudioDuration +
|
|
effectiveDurationSeconds,
|
|
totalSessionDuration: todayTranslationStats.value.totalSessionDuration +
|
|
totalDurationSeconds,
|
|
modeStats: newModeStats,
|
|
);
|
|
|
|
_saveTodayStats();
|
|
|
|
Logger.info(
|
|
'ASR统计: 有效语音${effectiveDurationSeconds}秒, 总时长${totalDurationSeconds}秒, 语言$language, 模式$mode');
|
|
} catch (e) {
|
|
Logger.error('记录ASR API调用失败: $e');
|
|
}
|
|
|
|
_reportAsrUsageToServer(
|
|
effectiveDurationSeconds: effectiveDurationSeconds,
|
|
//totalDurationSeconds: totalDurationSeconds,
|
|
language: language,
|
|
mode: mode,
|
|
);
|
|
}
|
|
|
|
/// 上报ASR用量到服务器
|
|
Future<void> _reportAsrUsageToServer({
|
|
required int effectiveDurationSeconds,
|
|
//required int totalDurationSeconds,
|
|
required String language,
|
|
required String mode,
|
|
}) async {
|
|
try {
|
|
final params = {
|
|
'usagetype': UsageType.Translation.toInt,
|
|
'usages': {
|
|
// 'TRANSLATION_ASR_EFFECTIVE_SECONDS': effectiveDurationSeconds,
|
|
//'TRANSLATION_ASR_TOTAL_SECONDS': totalDurationSeconds,
|
|
'TRANSLATION_MODE_$mode': effectiveDurationSeconds,
|
|
'TRANSLATION_ASR_LANGUAGE_$language': 1,
|
|
'TRANSLATION_SOURCE_CHARACTERS':
|
|
realtimeTranslationStats.value.sourceTextCharacters,
|
|
'TRANSLATION_TARGET_CHARACTERS':
|
|
realtimeTranslationStats.value.ttsTextCharacters,
|
|
},
|
|
};
|
|
|
|
Logger.info('lxm---ASR用量上报参数: $params');
|
|
|
|
final response = await Api.usagesResPoints(params);
|
|
|
|
Logger.info('lxm---ASR用量上报响应: $response');
|
|
|
|
_applyUsageResponse(response);
|
|
} catch (e) {
|
|
Logger.error('记录ASR API调用失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 根据服务器用量上报回应,更新用户积分(非总积分)
|
|
/// 用量类型:0 AI聊天、1 翻译、2 会议
|
|
void _applyUsageResponse(dynamic response) {
|
|
try {
|
|
if (response is! Map) return;
|
|
final usagesObj = response['usages'];
|
|
if (usagesObj is! Map) return;
|
|
final usages = Map<String, dynamic>.from(usagesObj as Map);
|
|
int _asInt(dynamic v) {
|
|
if (v == null) return 0;
|
|
if (v is int) return v;
|
|
if (v is num) return v.toInt();
|
|
if (v is String) return int.tryParse(v) ?? 0;
|
|
return 0;
|
|
}
|
|
|
|
final Map<String, dynamic> update = {};
|
|
usages.forEach((k, v) {
|
|
final key = int.tryParse(k.toString());
|
|
final val = _asInt(v);
|
|
if (key == null) return;
|
|
switch (key) {
|
|
case 0:
|
|
update['aichatintegral'] = val;
|
|
break;
|
|
case 1:
|
|
update['tradeintegral'] = val;
|
|
break;
|
|
case 2:
|
|
update['meetintegral'] = val;
|
|
break;
|
|
}
|
|
});
|
|
if (update.isNotEmpty) {
|
|
User.instance.updateUserInfo(update);
|
|
Logger.info('积分更新: $update');
|
|
}
|
|
} catch (e) {
|
|
Logger.error('解析用量回应并更新积分失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 记录TTS API调用
|
|
void recordTtsApiCall({
|
|
required String text,
|
|
required int durationSeconds,
|
|
required String language,
|
|
}) {
|
|
try {
|
|
// 更新实时统计
|
|
realtimeTranslationStats.value.totalSessionDuration += durationSeconds;
|
|
realtimeTranslationStats.refresh();
|
|
|
|
// 更新当日统计(TTS按字符数计费,不是按时长)
|
|
todayTranslationStats.value = todayTranslationStats.value.copyWith(
|
|
ttsApiCalls: todayTranslationStats.value.ttsApiCalls + 1,
|
|
ttsTextCharacters:
|
|
todayTranslationStats.value.ttsTextCharacters + text.length,
|
|
totalSessionDuration:
|
|
todayTranslationStats.value.totalSessionDuration + durationSeconds,
|
|
);
|
|
|
|
_saveTodayStats();
|
|
|
|
Logger.info('记录TTS API调用: 文本${text.length}字符, 时长${durationSeconds}秒');
|
|
} catch (e) {
|
|
Logger.error('记录TTS API调用失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 记录AI摘要API调用
|
|
void recordAiSummaryApiCall() {
|
|
try {
|
|
// 更新当日统计
|
|
todayTranslationStats.value = todayTranslationStats.value.copyWith(
|
|
aiSummaryApiCalls: todayTranslationStats.value.aiSummaryApiCalls + 1,
|
|
);
|
|
|
|
_saveTodayStats();
|
|
|
|
Logger.info('记录AI摘要API调用');
|
|
} catch (e) {
|
|
Logger.error('记录AI摘要API调用失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 开始新会话
|
|
void startNewSession() {
|
|
realtimeTranslationStats.value.reset();
|
|
|
|
// 更新当日会话数
|
|
todayTranslationStats.value = todayTranslationStats.value.copyWith(
|
|
sessionCount: todayTranslationStats.value.sessionCount + 1,
|
|
);
|
|
|
|
_saveTodayStats();
|
|
Logger.info('开始新的翻译会话');
|
|
}
|
|
|
|
/// 保存当日统计数据
|
|
void _saveTodayStats() {
|
|
try {
|
|
final today = DateTime.now();
|
|
final todayKey =
|
|
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}';
|
|
_storage.write(
|
|
'${_dailyStatsKey}_$todayKey', todayTranslationStats.value.toJson());
|
|
} catch (e) {
|
|
Logger.error('保存当日统计数据失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 获取历史统计数据
|
|
Future<List<TranslationUsageStats>> getHistoryStats({
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
}) async {
|
|
try {
|
|
final List<TranslationUsageStats> historyStats = [];
|
|
|
|
for (DateTime date = startDate;
|
|
date.isBefore(endDate) || date.isAtSameMomentAs(endDate);
|
|
date = date.add(const Duration(days: 1))) {
|
|
final dateKey =
|
|
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
|
final statsData = _storage.read('${_dailyStatsKey}_$dateKey');
|
|
|
|
if (statsData != null) {
|
|
historyStats.add(TranslationUsageStats.fromJson(
|
|
Map<String, dynamic>.from(statsData)));
|
|
}
|
|
}
|
|
|
|
return historyStats;
|
|
} catch (e) {
|
|
Logger.error('获取历史统计数据失败: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// 清理过期数据(保留最近90天)
|
|
void cleanupOldData() {
|
|
try {
|
|
final cutoffDate = DateTime.now().subtract(const Duration(days: 90));
|
|
final allKeys = _storage.getKeys();
|
|
|
|
for (final key in allKeys) {
|
|
if (key.startsWith(_dailyStatsKey)) {
|
|
final dateStr = key.replaceFirst('${_dailyStatsKey}_', '');
|
|
try {
|
|
final date = DateTime.parse(dateStr);
|
|
if (date.isBefore(cutoffDate)) {
|
|
_storage.remove(key);
|
|
Logger.info('清理过期统计数据: $key');
|
|
}
|
|
} catch (e) {
|
|
// 忽略解析错误的键
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
Logger.error('清理过期数据失败: $e');
|
|
}
|
|
}
|
|
|
|
/// 获取月度统计汇总
|
|
Future<TranslationUsageStats> getMonthlyStats(int year, int month) async {
|
|
try {
|
|
final startDate = DateTime(year, month, 1);
|
|
final endDate = DateTime(year, month + 1, 0); // 月末
|
|
|
|
final dailyStats = await getHistoryStats(
|
|
startDate: startDate,
|
|
endDate: endDate,
|
|
);
|
|
|
|
if (dailyStats.isEmpty) {
|
|
return TranslationUsageStats.empty(getCurrentUserId());
|
|
}
|
|
|
|
// 汇总月度数据
|
|
int totalTranslationCalls = 0;
|
|
int totalAsrCalls = 0;
|
|
int totalTtsCalls = 0;
|
|
int totalAiSummaryCalls = 0;
|
|
int totalSourceChars = 0;
|
|
int totalTargetChars = 0;
|
|
int totalRecordingDuration = 0;
|
|
int totalTtsPlayDuration = 0;
|
|
int totalSessions = 0;
|
|
//double totalCost = 0.0;
|
|
|
|
final Map<String, int> combinedLanguagePairUsage = {};
|
|
final Map<String, int> combinedModeUsage = {};
|
|
|
|
for (final stats in dailyStats) {
|
|
totalTranslationCalls += stats.translationApiCalls;
|
|
totalAsrCalls += stats.asrApiCalls;
|
|
totalTtsCalls += stats.ttsApiCalls;
|
|
totalAiSummaryCalls += stats.aiSummaryApiCalls;
|
|
totalSourceChars += stats.sourceCharacterCount;
|
|
totalTargetChars += stats.targetCharacterCount;
|
|
totalRecordingDuration += stats.audioRecordingDuration;
|
|
totalTtsPlayDuration += stats.ttsPlaybackDuration;
|
|
totalSessions += stats.sessionCount;
|
|
// totalCost += stats.estimatedCost;
|
|
|
|
// 合并语言对使用统计
|
|
stats.languagePairUsage.forEach((key, value) {
|
|
combinedLanguagePairUsage[key] =
|
|
(combinedLanguagePairUsage[key] ?? 0) + value;
|
|
});
|
|
|
|
// 合并模式使用统计
|
|
stats.modeUsage.forEach((key, value) {
|
|
combinedModeUsage[key] = (combinedModeUsage[key] ?? 0) + value;
|
|
});
|
|
}
|
|
|
|
return TranslationUsageStats(
|
|
id: 'monthly_${year}_$month',
|
|
date: startDate,
|
|
userId: getCurrentUserId(),
|
|
timeGranularity: 'month',
|
|
translationApiCalls: totalTranslationCalls,
|
|
asrApiCalls: totalAsrCalls,
|
|
ttsApiCalls: totalTtsCalls,
|
|
aiSummaryApiCalls: totalAiSummaryCalls,
|
|
sourceTextCharacters: totalSourceChars,
|
|
effectiveAudioDuration: totalRecordingDuration,
|
|
ttsTextCharacters: totalTargetChars,
|
|
totalSessionDuration: totalTtsPlayDuration,
|
|
sessionCount: totalSessions,
|
|
// estimatedCost: totalCost,
|
|
);
|
|
} catch (e) {
|
|
Logger.error('获取月度统计失败: $e');
|
|
return TranslationUsageStats.empty(getCurrentUserId());
|
|
}
|
|
}
|
|
|
|
Future<List<MeetingAsrDailyStats>> getMeetingHistoryStats({
|
|
required DateTime startDate,
|
|
required DateTime endDate,
|
|
}) async {
|
|
try {
|
|
final List<MeetingAsrDailyStats> list = [];
|
|
for (DateTime date = startDate;
|
|
date.isBefore(endDate) || date.isAtSameMomentAs(endDate);
|
|
date = date.add(const Duration(days: 1))) {
|
|
final key = _formatDate(date);
|
|
final daily = _loadMeetingDaily(key);
|
|
if (daily.items.isNotEmpty) {
|
|
list.add(daily);
|
|
}
|
|
}
|
|
Logger.i('MeetingASRrecord',
|
|
'获取会议历史统计: start=${_formatDate(startDate)}, end=${_formatDate(endDate)}, 有效天数=${list.length}');
|
|
return list;
|
|
} catch (e) {
|
|
Logger.error('获取会议历史统计失败: $e');
|
|
return [];
|
|
}
|
|
}
|
|
|
|
MeetingAsrHistoryStats aggregateMeetingStats(
|
|
List<MeetingAsrDailyStats> daily) {
|
|
int calls = 0;
|
|
int audioSeconds = 0;
|
|
int audioBytes = 0;
|
|
int transcriptBytes = 0;
|
|
for (final d in daily) {
|
|
calls += d.asrCalls;
|
|
audioSeconds += d.audioSecondsTotal;
|
|
audioBytes += d.audioBytesTotal;
|
|
transcriptBytes += d.transcriptBytesTotal;
|
|
}
|
|
Logger.i('MeetingASRrecord',
|
|
'聚合会议统计: calls=${calls}, audioSeconds=${audioSeconds}, audioBytes=${audioBytes}, transcriptBytes=${transcriptBytes}');
|
|
return MeetingAsrHistoryStats(
|
|
userId: getCurrentUserId(),
|
|
allAsrCalls: calls,
|
|
allAudioSeconds: audioSeconds,
|
|
allAudioFileBytes: audioBytes,
|
|
allTranscriptBytes: transcriptBytes,
|
|
);
|
|
}
|
|
}
|
|
|