reload derived analytics on server analyics update

This commit is contained in:
ggurdin 2026-02-11 14:03:35 -05:00
parent 62c91d9dcc
commit 811ba58c73
No known key found for this signature in database
GPG key ID: A01CB41737CBB478
7 changed files with 63 additions and 54 deletions

View file

@ -130,29 +130,24 @@ class AnalyticsDataService {
await _analyticsClientGetter.database.updateUserID(client.userID!);
}
final resp = await client.getUserProfile(client.userID!);
final analyticsProfile = AnalyticsProfileModel.fromJson(
resp.additionalProperties,
);
_syncController?.dispose();
_syncController = AnalyticsSyncController(
client: client,
dataService: this,
);
await _syncController!.bulkUpdate();
final vocab = await getAggregatedConstructs(ConstructTypeEnum.vocab);
final morphs = await getAggregatedConstructs(ConstructTypeEnum.morph);
final constructs = [...vocab.values, ...morphs.values];
final totalXP = constructs.fold(0, (total, c) => total + c.points);
await _analyticsClientGetter.database.updateDerivedStats(
DerivedAnalyticsDataModel(
totalXP: totalXP,
offset: analyticsProfile.xpOffset ?? 0,
),
final resp = await client.getUserProfile(client.userID!);
final analyticsProfile = AnalyticsProfileModel.fromJson(
resp.additionalProperties,
);
final l2 = MatrixState.pangeaController.userController.userL2;
if (l2 != null) {
await updateXPOffset(analyticsProfile.xpOffsetByLanguage(l2) ?? 0);
}
_syncController!.start();
await _initMergeTable();
@ -161,7 +156,7 @@ class AnalyticsDataService {
} finally {
Logs().i("Analytics database initialized.");
initCompleter.complete();
updateDispatcher.sendConstructAnalyticsUpdate(AnalyticsUpdate([]));
updateDispatcher.sendLocalAnalyticsUpdate(AnalyticsUpdate([]));
updateDispatcher.sendActivityAnalyticsUpdate(null);
}
}
@ -422,7 +417,7 @@ class AnalyticsDataService {
events.add(XPGainedEvent(points, update.targetID));
}
final newData = prevData.copyWith(totalXP: prevData.totalXP + points);
final newData = prevData.addXP(points);
await _analyticsClientGetter.database.updateDerivedStats(newData);
// Update public profile each time that new analytics are added.
@ -485,15 +480,25 @@ class AnalyticsDataService {
return events;
}
Future<void> updateServerAnalytics(
Future<List<AnalyticsUpdateEvent>> updateServerAnalytics(
List<ConstructAnalyticsEvent> events,
) async {
final List<AnalyticsUpdateEvent> updates = [];
_invalidateCaches();
final blocked = blockedConstructs;
for (final event in events) {
_mergeTable.addConstructsByUses(event.content.uses, blocked);
}
await _analyticsClientGetter.database.updateServerAnalytics(events);
final vocab = await getAggregatedConstructs(ConstructTypeEnum.vocab);
final morphs = await getAggregatedConstructs(ConstructTypeEnum.morph);
final constructs = [...vocab.values, ...morphs.values];
final totalXP = constructs.fold(0, (total, c) => total + c.points);
await _analyticsClientGetter.database.updateTotalXP(totalXP);
updates.add(XPGainedEvent(0, null));
return updates;
}
Future<void> updateBlockedConstructs(ConstructIdentifier constructId) async {
@ -512,12 +517,10 @@ class AnalyticsDataService {
level: newLevel,
);
await _analyticsClientGetter.database.updateDerivedStats(
DerivedAnalyticsDataModel(totalXP: newXP),
);
await _analyticsClientGetter.database.updateTotalXP(newXP);
_invalidateCaches();
updateDispatcher.sendConstructAnalyticsUpdate(
updateDispatcher.sendLocalAnalyticsUpdate(
AnalyticsUpdate([], blockedConstruct: constructId),
);
}

View file

@ -468,7 +468,15 @@ class AnalyticsDatabase with DatabaseFileStorage {
Future<void> updateXPOffset(int offset) {
return _transaction(() async {
final stats = await getDerivedStats();
final updatedStats = stats.copyWith(offset: offset);
final updatedStats = stats.copyWithOffset(offset);
await _derivedStatsBox.put('derived_stats', updatedStats.toJson());
});
}
Future<void> updateTotalXP(int totalXP) {
return _transaction(() async {
final stats = await getDerivedStats();
final updatedStats = stats.copyWithTotalXP(totalXP);
await _derivedStatsBox.put('derived_stats', updatedStats.toJson());
});
}

View file

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
import 'package:fluffychat/pangea/analytics_data/analytics_update_dispatcher.dart';
import 'package:fluffychat/pangea/analytics_misc/client_analytics_extension.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart';
@ -50,13 +49,8 @@ class AnalyticsSyncController {
.toList();
if (constructEvents.isEmpty) return;
await dataService.updateServerAnalytics(constructEvents);
// Server updates do not usually need to update the UI, since usually they are only
// transfering local data to the server. However, if a user if using multiple devices,
// we do need to update the UI when new data comes from the server.
dataService.updateDispatcher.sendConstructAnalyticsUpdate(
AnalyticsUpdate([]),
await dataService.updateDispatcher.sendServerAnalyticsUpdate(
constructEvents,
);
}

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:fluffychat/pangea/analytics_data/analytics_data_service.dart';
import 'package:fluffychat/pangea/analytics_data/analytics_update_events.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_event.dart';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
import 'package:fluffychat/pangea/constructs/construct_level_enum.dart';
@ -85,9 +86,16 @@ class AnalyticsUpdateDispatcher {
UserSetLemmaInfo lemmaInfo,
) => _lemmaInfoUpdateStream.add(MapEntry(constructId, lemmaInfo));
Future<void> sendConstructAnalyticsUpdate(
AnalyticsUpdate analyticsUpdate,
Future<void> sendServerAnalyticsUpdate(
List<ConstructAnalyticsEvent> events,
) async {
final updates = await dataService.updateServerAnalytics(events);
for (final event in updates) {
_dispatch(event);
}
}
Future<void> sendLocalAnalyticsUpdate(AnalyticsUpdate analyticsUpdate) async {
final events = await dataService.updateLocalAnalytics(analyticsUpdate);
for (final event in events) {
_dispatch(event);

View file

@ -62,7 +62,7 @@ class AnalyticsUpdateService {
List<OneConstructUse> newConstructs, {
bool forceUpdate = false,
}) async {
await dataService.updateDispatcher.sendConstructAnalyticsUpdate(
await dataService.updateDispatcher.sendLocalAnalyticsUpdate(
AnalyticsUpdate(newConstructs, targetID: targetID),
);

View file

@ -1,6 +1,5 @@
import 'dart:math';
import 'package:fluffychat/pangea/analytics_misc/constructs_model.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
class DerivedAnalyticsDataModel {
@ -12,7 +11,7 @@ class DerivedAnalyticsDataModel {
int get totalXP => _totalXP + offset;
int get level => calculateLevelWithXp(_totalXP);
int get level => calculateLevelWithXp(totalXP);
// the minimum XP required for a given level
int get _minXPForLevel => calculateXpWithLevel(level);
@ -23,7 +22,7 @@ class DerivedAnalyticsDataModel {
// the progress within the current level as a percentage (0.0 to 1.0)
double get levelProgress {
final progress =
(_totalXP - _minXPForLevel) / (minXPForNextLevel - _minXPForLevel);
(totalXP - _minXPForLevel) / (minXPForNextLevel - _minXPForLevel);
return progress >= 0 ? progress : 0;
}
@ -60,35 +59,29 @@ class DerivedAnalyticsDataModel {
}
}
DerivedAnalyticsDataModel update(List<OneConstructUse> uses) {
int xp = _totalXP;
for (final u in uses) {
xp += u.xp;
}
return copyWith(totalXP: xp);
DerivedAnalyticsDataModel copyWithOffset(int offset) {
return DerivedAnalyticsDataModel(totalXP: _totalXP, offset: offset);
}
DerivedAnalyticsDataModel merge(DerivedAnalyticsDataModel other) {
DerivedAnalyticsDataModel copyWithTotalXP(int totalXP) {
return DerivedAnalyticsDataModel(totalXP: totalXP, offset: offset);
}
DerivedAnalyticsDataModel addXP(int xpToAdd) {
return DerivedAnalyticsDataModel(
totalXP: _totalXP + other.totalXP,
totalXP: _totalXP + xpToAdd,
offset: offset,
);
}
DerivedAnalyticsDataModel copyWith({int? totalXP, int? offset}) {
factory DerivedAnalyticsDataModel.fromJson(Map<String, dynamic> map) {
return DerivedAnalyticsDataModel(
totalXP: totalXP ?? this.totalXP,
offset: offset ?? this.offset,
totalXP: map['total_xp'] ?? 0,
offset: map['offset'] ?? 0,
);
}
factory DerivedAnalyticsDataModel.fromJson(Map<String, dynamic> map) {
return DerivedAnalyticsDataModel(totalXP: map['total_xp'] ?? 0);
}
Map<String, dynamic> toJson() {
return {'total_xp': _totalXP};
return {'total_xp': _totalXP, 'offset': offset};
}
}

View file

@ -136,6 +136,9 @@ class AnalyticsProfileModel {
int? get level => languageAnalytics?[targetLanguage]?.level;
int? get xpOffset => languageAnalytics?[targetLanguage]?.xpOffset;
int? xpOffsetByLanguage(LanguageModel language) =>
languageAnalytics?[language]?.xpOffset;
}
class LanguageAnalyticsProfileEntry {