constructs data model updates

pull/1183/head
ggurdin 1 year ago
parent c6186fcdc9
commit e96c0e34db

@ -8,7 +8,6 @@ import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/enum/time_span.dart';
import 'package:fluffychat/pangea/models/analytics/analytics_event.dart';
import 'package:fluffychat/pangea/models/analytics/constructs_event.dart';
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
import 'package:fluffychat/pangea/models/analytics/summary_analytics_event.dart';
import 'package:fluffychat/pangea/pages/analytics/base_analytics.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
@ -520,13 +519,11 @@ class AnalyticsController extends BaseController {
List<ConstructAnalyticsEvent>? get constructs => _constructs;
Future<List<ConstructAnalyticsEvent>> allMyConstructs({
ConstructType? type,
}) async {
Future<List<ConstructAnalyticsEvent>> allMyConstructs() async {
final List<Room> analyticsRooms =
_pangeaController.matrixState.client.allMyAnalyticsRooms;
List<ConstructAnalyticsEvent> allConstructs = [];
final List<ConstructAnalyticsEvent> allConstructs = [];
for (final Room analyticsRoom in analyticsRooms) {
final List<ConstructAnalyticsEvent>? roomEvents =
(await analyticsRoom.getAnalyticsEvents(
@ -538,17 +535,12 @@ class AnalyticsController extends BaseController {
allConstructs.addAll(roomEvents ?? []);
}
allConstructs = type == null
? allConstructs
: allConstructs.where((e) => e.content.type == type).toList();
final List<String> adminSpaceRooms =
await _pangeaController.matrixState.client.teacherRoomIds;
for (final construct in allConstructs) {
final lemmaUses = construct.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere((u) => adminSpaceRooms.contains(u.chatId));
}
construct.content.uses.removeWhere(
(use) => adminSpaceRooms.contains(use.chatId),
);
}
return allConstructs
@ -557,9 +549,8 @@ class AnalyticsController extends BaseController {
}
Future<List<ConstructAnalyticsEvent>> allSpaceMemberConstructs(
Room space, {
ConstructType? type,
}) async {
Room space,
) async {
await space.postLoad();
await space.requestParticipants();
final String? langCode = _pangeaController.languageController.activeL2Code(
@ -595,19 +586,16 @@ class AnalyticsController extends BaseController {
final List<String> spaceChildrenIds = space.allSpaceChildRoomIds;
final List<ConstructAnalyticsEvent> allConstructs = [];
for (final constructEvent in constructEvents) {
final lemmaUses = constructEvent.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere((u) => !spaceChildrenIds.contains(u.chatId));
}
constructEvent.content.uses.removeWhere(
(use) => !spaceChildrenIds.contains(use.chatId),
);
if (constructEvent.content.uses.isNotEmpty) {
allConstructs.add(constructEvent);
}
}
return type == null
? allConstructs
: allConstructs.where((e) => e.content.type == type).toList();
return allConstructs;
}
List<ConstructAnalyticsEvent> filterStudentConstructs(
@ -626,10 +614,7 @@ class AnalyticsController extends BaseController {
) {
final List<ConstructAnalyticsEvent> filtered = [...unfilteredConstructs];
for (final construct in filtered) {
final lemmaUses = construct.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere((u) => u.chatId != roomID);
}
construct.content.uses.removeWhere((u) => u.chatId != roomID);
}
return filtered;
}
@ -642,10 +627,9 @@ class AnalyticsController extends BaseController {
final List<ConstructAnalyticsEvent> filtered =
List<ConstructAnalyticsEvent>.from(unfilteredConstructs);
for (final construct in filtered) {
final lemmaUses = construct.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere((u) => !directChatIds.contains(u.chatId));
}
construct.content.uses.removeWhere(
(use) => !directChatIds.contains(use.chatId),
);
}
return filtered;
}
@ -664,10 +648,9 @@ class AnalyticsController extends BaseController {
List<ConstructAnalyticsEvent>.from(unfilteredConstructs);
for (final construct in filtered) {
final lemmaUses = construct.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere((u) => !chatIds.contains(u.chatId));
}
construct.content.uses.removeWhere(
(use) => !chatIds.contains(use.chatId),
);
}
return filtered;
@ -723,9 +706,7 @@ class AnalyticsController extends BaseController {
AnalyticsSelected? selected,
}) async {
final List<ConstructAnalyticsEvent> unfilteredConstructs =
await allMyConstructs(
type: constructType,
);
await allMyConstructs();
final Room? space = selected?.type == AnalyticsEntryType.space
? _pangeaController.matrixState.client.getRoomById(selected!.id)
@ -748,7 +729,6 @@ class AnalyticsController extends BaseController {
final List<ConstructAnalyticsEvent> unfilteredConstructs =
await allSpaceMemberConstructs(
space,
type: constructType,
);
return filterConstructs(
@ -772,12 +752,9 @@ class AnalyticsController extends BaseController {
for (int i = 0; i < unfilteredConstructs.length; i++) {
final construct = unfilteredConstructs[i];
final lemmaUses = construct.content.uses;
for (final lemmaUse in lemmaUses) {
lemmaUse.uses.removeWhere(
(u) => u.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate),
);
}
construct.content.uses.removeWhere(
(use) => use.timeStamp.isBefore(currentAnalyticsTimeSpan.cutOffDate),
);
}
unfilteredConstructs.removeWhere((e) => e.content.uses.isEmpty);
@ -918,31 +895,6 @@ class AnalyticsController extends BaseController {
settingConstructs = false;
return _constructs;
}
// used to aggregate ConstructEvents from
// multiple senders (students) with the same lemma
List<AggregateConstructUses> aggregateConstructData(
List<ConstructAnalyticsEvent> constructs,
) {
final Map<String, List<LemmaConstructsModel>> lemmasToConstructs = {};
for (final construct in constructs) {
for (final lemmaUses in construct.content.uses) {
lemmasToConstructs[lemmaUses.lemma] ??= [];
lemmasToConstructs[lemmaUses.lemma]!.add(lemmaUses);
}
}
final List<AggregateConstructUses> aggregatedConstructs = [];
for (final lemmaToConstructs in lemmasToConstructs.entries) {
final List<LemmaConstructsModel> lemmaConstructs =
lemmaToConstructs.value;
final AggregateConstructUses aggregatedData = AggregateConstructUses(
lemmaUses: lemmaConstructs,
);
aggregatedConstructs.add(aggregatedData);
}
return aggregatedConstructs;
}
}
abstract class CacheEntry {

@ -1,7 +1,5 @@
import 'package:fluffychat/pangea/enum/construct_type_enum.dart';
import 'package:fluffychat/pangea/models/analytics/analytics_event.dart';
import 'package:fluffychat/pangea/models/analytics/constructs_model.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:matrix/matrix.dart';
import '../../constants/pangea_event_types.dart';
@ -25,35 +23,8 @@ class ConstructAnalyticsEvent extends AnalyticsEvent {
Room analyticsRoom,
List<OneConstructUse> uses,
) async {
// create a map of lemmas to their uses
final Map<String, List<OneConstructUse>> lemmasToUses = {};
for (final use in uses) {
if (use.lemma == null) {
ErrorHandler.logError(
e: "use has no lemma in sendConstructsEvent",
s: StackTrace.current,
);
continue;
}
lemmasToUses[use.lemma!] ??= [];
lemmasToUses[use.lemma]!.add(use);
}
// convert the map of lemmas to uses into a list of LemmaConstructsModel
// each entry in this list contains one lemma to many uses
final List<LemmaConstructsModel> lemmaUses = lemmasToUses.entries
.map(
(entry) => LemmaConstructsModel(
lemma: entry.key,
uses: entry.value,
),
)
.toList();
// finally, send the construct analytics event to the analytics room
final ConstructAnalyticsModel constructsModel = ConstructAnalyticsModel(
type: ConstructType.grammar,
uses: lemmaUses,
uses: uses,
);
final String? eventId = await analyticsRoom.sendEvent(

@ -1,17 +1,15 @@
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/analytics/analytics_model.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:flutter/material.dart';
import 'package:matrix/matrix.dart';
import '../../enum/construct_type_enum.dart';
class ConstructAnalyticsModel extends AnalyticsModel {
ConstructType type;
List<LemmaConstructsModel> uses;
List<OneConstructUse> uses;
ConstructAnalyticsModel({
required this.type,
this.uses = const [],
});
@ -19,24 +17,16 @@ class ConstructAnalyticsModel extends AnalyticsModel {
factory ConstructAnalyticsModel.fromJson(Map<String, dynamic> json) {
return ConstructAnalyticsModel(
type: ConstructTypeUtil.fromString(json['type']),
uses: json[_usesKey]
.values
.map((lemmaUses) => LemmaConstructsModel.fromJson(lemmaUses))
.cast<LemmaConstructsModel>()
.map((use) => OneConstructUse.fromJson(use))
.cast<OneConstructUse>()
.toList(),
);
}
toJson() {
final Map<String, dynamic> usesMap = {};
for (final use in uses) {
usesMap[use.lemma] = use.toJson();
}
return {
'type': type.string,
_usesKey: usesMap,
_usesKey: uses.map((use) => use.toJson()).toList(),
};
}
@ -44,53 +34,34 @@ class ConstructAnalyticsModel extends AnalyticsModel {
List<PangeaMessageEvent> recentMsgs,
) {
final List<PangeaMessageEvent> filtered = List.from(recentMsgs);
final List<OneConstructUse> uses = filtered
.map(
(msg) => msg.originalSent?.choreo?.toGrammarConstructUse(
msg.eventId,
msg.room.id,
msg.originServerTs,
),
)
.where((element) => element != null)
.cast<List<OneConstructUse>>()
.expand((element) => element)
.toList();
final List<OneConstructUse> uses = [];
for (final msg in filtered) {
if (msg.originalSent?.choreo == null) continue;
uses.addAll(
msg.originalSent!.choreo!.toGrammarConstructUse(
msg.eventId,
msg.room.id,
msg.originServerTs,
),
);
final List<PangeaToken>? tokens = msg.originalSent?.tokens;
if (tokens == null) continue;
uses.addAll(
msg.originalSent!.choreo!.toVocabUse(
tokens,
msg.room.id,
msg.eventId,
msg.originServerTs,
),
);
}
return uses;
}
}
class LemmaConstructsModel {
String lemma;
List<OneConstructUse> uses;
LemmaConstructsModel({
required this.lemma,
this.uses = const [],
});
factory LemmaConstructsModel.fromJson(Map<String, dynamic> json) {
return LemmaConstructsModel(
lemma: json[ModelKey.lemma],
uses: (json['uses'] ?? [] as Iterable)
.map<OneConstructUse?>(
(use) => use != null ? OneConstructUse.fromJson(use) : null,
)
.where((element) => element != null)
.cast<OneConstructUse>()
.toList(),
);
}
Map<String, dynamic> toJson() {
return {
ModelKey.lemma: lemma,
'uses': uses.map((use) => use.toJson()).toList(),
};
}
}
enum ConstructUseType {
/// produced in chat by user, igc was run, and we've judged it to be a correct use
wa,
@ -206,7 +177,7 @@ class OneConstructUse {
);
}
Map<String, dynamic> toJson([bool condensed = true]) {
Map<String, dynamic> toJson([bool condensed = false]) {
final Map<String, String?> data = {
'useType': useType.string,
'chatId': chatId,
@ -234,24 +205,14 @@ class OneConstructUse {
}
}
class AggregateConstructUses {
final List<LemmaConstructsModel> _lemmaUses;
class ConstructUses {
final List<OneConstructUse> uses;
final ConstructType constructType;
final String lemma;
AggregateConstructUses({required List<LemmaConstructsModel> lemmaUses})
: _lemmaUses = lemmaUses;
String get lemma {
assert(
_lemmaUses.isNotEmpty &&
_lemmaUses.every(
(construct) => construct.lemma == _lemmaUses.first.lemma,
),
);
return _lemmaUses.first.lemma;
}
List<OneConstructUse> get uses => _lemmaUses
.map((lemmaUse) => lemmaUse.uses)
.expand((element) => element)
.toList();
ConstructUses({
required this.uses,
required this.constructType,
required this.lemma,
});
}

@ -116,6 +116,7 @@ class ConstructListView extends StatefulWidget {
}
class ConstructListViewState extends State<ConstructListView> {
final ConstructType constructType = ConstructType.grammar;
final Map<String, Timeline> _timelinesCache = {};
final Map<String, PangeaMessageEvent> _msgEventCache = {};
final List<PangeaMessageEvent> _msgEvents = [];
@ -128,7 +129,7 @@ class ConstructListViewState extends State<ConstructListView> {
refreshSubscription = widget.refreshStream.stream.listen((forceUpdate) {
widget.pangeaController.analytics
.setConstructs(
constructType: ConstructType.grammar,
constructType: constructType,
removeIT: true,
defaultSelected: widget.defaultSelected,
selected: widget.selected,
@ -218,21 +219,46 @@ class ConstructListViewState extends State<ConstructListView> {
}
}
List<AggregateConstructUses>? get constructs {
List<ConstructUses>? get constructs {
if (widget.pangeaController.analytics.constructs == null) {
return null;
}
return widget.pangeaController.analytics
.aggregateConstructData(widget.pangeaController.analytics.constructs!)
.where((lemmaUses) => lemmaUses.uses.isNotEmpty)
.sorted((a, b) {
final int cmp = b.uses.length.compareTo(a.uses.length);
if (cmp != 0) return cmp;
final List<OneConstructUse> filtered =
List.from(widget.pangeaController.analytics.constructs!)
.map((event) => event.content.uses)
.expand((uses) => uses)
.cast<OneConstructUse>()
.where((use) => use.constructType == constructType)
.toList();
final Map<String, List<OneConstructUse>> lemmaToUses = {};
for (final use in filtered) {
if (use.lemma == null) continue;
lemmaToUses[use.lemma!] ??= [];
lemmaToUses[use.lemma!]!.add(use);
}
final constructUses = lemmaToUses.entries
.map(
(entry) => ConstructUses(
lemma: entry.key,
uses: entry.value,
constructType: constructType,
),
)
.toList();
constructUses.sort((a, b) {
final comp = b.uses.length.compareTo(a.uses.length);
if (comp != 0) return comp;
return a.lemma.compareTo(b.lemma);
}).toList();
});
return constructUses;
}
AggregateConstructUses? get currentConstruct => constructs?.firstWhereOrNull(
ConstructUses? get currentConstruct => constructs?.firstWhereOrNull(
(element) => element.lemma == widget.controller.currentLemma,
);
@ -456,21 +482,21 @@ class ConstructMessage extends StatelessWidget {
class ConstructMessageBubble extends StatelessWidget {
final String errorText;
final String replacementText;
final int? start;
final int? end;
final int start;
final int end;
const ConstructMessageBubble({
super.key,
required this.errorText,
required this.replacementText,
this.start,
this.end,
required this.start,
required this.end,
});
@override
Widget build(BuildContext context) {
final defaultStyle = TextStyle(
color: Theme.of(context).colorScheme.onBackground,
color: Theme.of(context).colorScheme.onSurface,
fontSize: AppConfig.messageFontSize * AppConfig.fontSizeFactor,
height: 1.3,
);
@ -498,7 +524,7 @@ class ConstructMessageBubble extends StatelessWidget {
vertical: 8,
),
child: RichText(
text: (start == null || end == null)
text: (end == null)
? TextSpan(
text: errorText,
style: defaultStyle,
@ -510,7 +536,7 @@ class ConstructMessageBubble extends StatelessWidget {
style: defaultStyle,
),
TextSpan(
text: errorText.substring(start!, end),
text: errorText.substring(start, end),
style: defaultStyle.merge(
TextStyle(
backgroundColor: Colors.red.withOpacity(0.25),
@ -529,7 +555,7 @@ class ConstructMessageBubble extends StatelessWidget {
),
),
TextSpan(
text: errorText.substring(end!),
text: errorText.substring(end),
style: defaultStyle,
),
],

Loading…
Cancel
Save