constructs data model updates
This commit is contained in:
parent
c6186fcdc9
commit
e96c0e34db
4 changed files with 105 additions and 195 deletions
|
|
@ -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…
Add table
Reference in a new issue