You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/models/choreo_record.dart

331 lines
10 KiB
Dart

import 'dart:convert';
import 'package:fluffychat/pangea/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import '../constants/choreo_constants.dart';
import '../enum/construct_type_enum.dart';
import 'constructs_analytics_model.dart';
import 'it_step.dart';
import 'lemma.dart';
/// this class lives within a [PangeaIGCEvent]
/// it always has a [RepresentationEvent] parent
/// These live as separate event so that anyone can add and edit grammar checks
/// to a representation
/// It represents the real-time changes to a text
/// TODO - start saving senderL2Code in choreoRecord to be able better decide the useType
class ChoreoRecord {
/// ordered versions of the representation, with first being original and last
/// being the final sent text
/// there is not a 1-to-1 map from steps to matches
List<ChoreoRecordStep> choreoSteps;
// String current;
List<PangeaMatch> openMatches;
ChoreoRecord({
required this.choreoSteps,
required this.openMatches,
// required this.current,
});
factory ChoreoRecord.fromJson(Map<String, dynamic> json) {
final stepsRaw = json[_stepsKey];
return ChoreoRecord(
choreoSteps: (jsonDecode(stepsRaw ?? "[]") as Iterable)
.map((e) {
return ChoreoRecordStep.fromJson(e);
})
.toList()
.cast<ChoreoRecordStep>(),
openMatches: (jsonDecode(json[_openMatchesKey] ?? "[]") as Iterable)
.map((e) {
return PangeaMatch.fromJson(e);
})
.toList()
.cast<PangeaMatch>(),
// current: json[_currentKey],
);
}
static const _stepsKey = "stps";
static const _openMatchesKey = "mtchs";
// static const _currentKey = "crnt";
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data[_stepsKey] = jsonEncode(choreoSteps.map((e) => e.toJson()).toList());
data[_openMatchesKey] =
jsonEncode(openMatches.map((e) => e.toJson()).toList());
// data[_currentKey] = current;
return data;
}
addRecord(String text, {PangeaMatch? match, ITStep? step}) {
if (match != null && step != null) {
throw Exception("match and step should not both be defined");
}
choreoSteps.add(
ChoreoRecordStep(
text: text,
acceptedOrIgnoredMatch: match,
itStep: step,
),
);
}
bool get hasAcceptedMatches => choreoSteps.any(
(element) =>
element.acceptedOrIgnoredMatch?.status ==
PangeaMatchStatus.accepted,
);
bool get hasIgnoredMatches => choreoSteps.any(
(element) =>
element.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.ignored,
);
// bool get includedIT => choreoSteps.any((step) {
// return step.acceptedOrIgnoredMatch?.status ==
// PangeaMatchStatus.accepted &&
// (step.acceptedOrIgnoredMatch?.isITStart ?? false);
// });
bool get includedIT => choreoSteps.any((step) {
return step.acceptedOrIgnoredMatch?.status ==
PangeaMatchStatus.accepted &&
(step.acceptedOrIgnoredMatch?.isOutOfTargetMatch ?? false);
});
bool get includedIGC => choreoSteps.any((step) {
return step.acceptedOrIgnoredMatch?.status ==
PangeaMatchStatus.accepted &&
(step.acceptedOrIgnoredMatch?.isGrammarMatch ?? false);
});
static ChoreoRecord get newRecord => ChoreoRecord(
choreoSteps: [],
openMatches: [],
);
/// [tokens] is the final list of tokens that were sent
/// if no ga or ta,
/// make wa use for each and return
/// else
/// for each saveable vocab in the final message
/// if vocab is contained in an accepted replacement, make ga use
/// if vocab is contained in ta choice,
/// if selected as choice, corIt
/// if written as customInput, corIt? (account for score in this)
/// for each it step
/// for each continuance
/// if not within the final message, save ignIT/incIT
List<OneConstructUse> toVocabUse(
List<PangeaToken> tokens,
String chatId,
String msgId,
) {
final List<OneConstructUse> uses = [];
final DateTime now = DateTime.now();
List<OneConstructUse> lemmasToVocabUses(
List<Lemma> lemmas,
ConstructUseType type,
) {
final List<OneConstructUse> uses = [];
for (final lemma in lemmas) {
if (lemma.saveVocab) {
uses.add(
OneConstructUse(
useType: type,
chatId: chatId,
timeStamp: now,
lemma: lemma.text,
form: lemma.form,
msgId: msgId,
constructType: ConstructType.vocab,
),
);
}
}
return uses;
}
List<OneConstructUse> getVocabUseForToken(PangeaToken token) {
for (final step in choreoSteps) {
/// if 1) accepted match 2) token is in the replacement and 3) replacement
/// is in the overall step text, then token was a ga
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted &&
(step.acceptedOrIgnoredMatch!.match.choices?.any(
(r) =>
r.value.contains(token.text.content) &&
step.text.contains(r.value),
) ??
false)) {
return lemmasToVocabUses(token.lemmas, ConstructUseType.ga);
}
if (step.itStep != null) {
final bool pickedThroughIT = step.itStep!.chosenContinuance?.text
.contains(token.text.content) ??
false;
if (pickedThroughIT) {
return lemmasToVocabUses(token.lemmas, ConstructUseType.corIt);
//PTODO - check if added via custom input in IT flow
}
}
}
return lemmasToVocabUses(token.lemmas, ConstructUseType.wa);
}
/// for each token, record whether selected in ga, ta, or wa
for (final token in tokens) {
uses.addAll(getVocabUseForToken(token));
}
for (final itStep in itSteps) {
for (final continuance in itStep.continuances) {
// this seems to always be false for continuances right now
if (finalMessage.contains(continuance.text)) {
continue;
}
if (continuance.wasClicked) {
//PTODO - account for end of flow score
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
uses.addAll(
lemmasToVocabUses(continuance.lemmas, ConstructUseType.incIt),
);
}
} else {
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
uses.addAll(
lemmasToVocabUses(continuance.lemmas, ConstructUseType.ignIt),
);
}
}
}
}
return uses;
}
List<OneConstructUse> toGrammarConstructUse(String msgId, String chatId) {
final List<OneConstructUse> uses = [];
final DateTime now = DateTime.now();
for (final step in choreoSteps) {
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted) {
final String name = step.acceptedOrIgnoredMatch!.match.rule?.id ??
step.acceptedOrIgnoredMatch!.match.shortMessage ??
step.acceptedOrIgnoredMatch!.match.type.typeName.name;
uses.add(
OneConstructUse(
useType: ConstructUseType.ga,
chatId: chatId,
timeStamp: now,
lemma: name,
form: name,
msgId: msgId,
constructType: ConstructType.grammar,
),
);
}
}
return uses;
}
List<ITStep> get itSteps =>
choreoSteps.where((e) => e.itStep != null).map((e) => e.itStep!).toList();
String get finalMessage =>
choreoSteps.isNotEmpty ? choreoSteps.last.text : "";
}
/// new step are saved
/// 1) before every system-provided text is accepted, if final text is different
/// from last step
/// 2) on the acceptance of system-provided text
/// 3) on message send, if final text is different from last step
///
/// user edit
/// "hey ther"
/// user accepts "there" correction
/// "hey there"
/// step made for user edits and step made for system suggestion
/// user goes through IT, chooses "hola"
/// "hola"
/// step saved
/// adds "amigo"
/// step saved
class ChoreoRecordStep {
/// text after changes have been made
String text;
/// all matches throughout edit process,
/// including those open, accepted and ignored
/// last step in list may contain open
PangeaMatch? acceptedOrIgnoredMatch;
ITStep? itStep;
ChoreoRecordStep({
required this.text,
this.acceptedOrIgnoredMatch,
this.itStep,
}) {
if (itStep != null && acceptedOrIgnoredMatch != null) {
throw Exception(
"itStep and acceptedOrIgnoredMatch should not both be defined",
);
}
}
factory ChoreoRecordStep.fromJson(Map<String, dynamic> json) {
return ChoreoRecordStep(
text: json[_textKey],
acceptedOrIgnoredMatch: json[_acceptedOrIgnoredMatchKey] != null
? PangeaMatch.fromJson(json[_acceptedOrIgnoredMatchKey])
: null,
itStep: json[_stepKey] != null ? ITStep.fromJson(json[_stepKey]) : null,
);
}
static const _textKey = "txt";
static const _acceptedOrIgnoredMatchKey = "mtch";
static const _stepKey = "stp";
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data[_textKey] = text;
data[_acceptedOrIgnoredMatchKey] = acceptedOrIgnoredMatch?.toJson();
data[_stepKey] = itStep?.toJson();
return data;
}
}
// Example flow
// hello my name is Jordan! Im typing a message
// igc called, step saved with matchIndex = null
// click Im and fixed
// hello my name is Jordan! I'm typing a message
// igc called, step saved with matchIndex equal to i'm index
// hello my name is Jordan! I'm typing a message
// igc called, step saved with matchIndex = null
// class ITStepRecord {
// List<ContinuanceRecord> allContinuances;
// /// continuances that were clicked but not selected
// List<int> clicked;
// ITStepRecord({required this.continuances});
// }
// class ContinuanceRecord {}