Add method-stage drill backend packet

This commit is contained in:
axiomlogicnexus 2026-04-26 01:13:04 +02:00
parent ca345ec5b6
commit 0bc79a8173
11 changed files with 2619 additions and 1 deletions

View file

@ -20,6 +20,80 @@ namespace HyperTwistContractLibraryInternal
{
return FJsonObjectConverter::JsonObjectStringToUStruct(Json, &OutValue, 0, 0);
}
FHyperTwistTrainingSourceAttribution MakeMethodDrillCleanRoomAttribution()
{
FHyperTwistTrainingSourceAttribution Attribution;
Attribution.SourceMode = EHyperTwistTrainingSourceMode::CleanRoom;
Attribution.SourceRepo = TEXT("onionhoney/roux-trainers");
Attribution.SourceLicense = TEXT("GPLv3 clean-room behavioral handoff");
Attribution.SourceNotes = TEXT("First-party HyperTwist method-stage drill sample built from scrubbed clean-room guidance.");
return Attribution;
}
FHyperTwistTrainingDeck MakeSampleMethodDrillDeck()
{
const FHyperTwistTrainingSourceAttribution Attribution = MakeMethodDrillCleanRoomAttribution();
FHyperTwistTrainingCase RecognitionCaseA;
RecognitionCaseA.CaseId = TEXT("roux-block-case-01");
RecognitionCaseA.PuzzleId = TEXT("cube/3x3x3");
RecognitionCaseA.PromptKind = EHyperTwistTrainingPromptKind::Recognition;
RecognitionCaseA.PromptLabel = TEXT("Left Block Recognition A");
RecognitionCaseA.CanonicalStateRef = TEXT("cube/3x3x3:roux:first-block:left:a");
RecognitionCaseA.CanonicalNotation = TEXT("R U R'");
RecognitionCaseA.ScrambleNotation = TEXT("U R U' L' U");
RecognitionCaseA.SubsetId = TEXT("first-block");
RecognitionCaseA.Difficulty = 0.45f;
RecognitionCaseA.Tags = {
TEXT("classic"),
TEXT("roux"),
TEXT("block-building"),
TEXT("subset:first-block"),
TEXT("stage-recognition")
};
RecognitionCaseA.AllowedDeliveryModes = {
EHyperTwistTrainingDeliveryMode::VirtualCube,
EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
RecognitionCaseA.TimeTargetMs = 0;
RecognitionCaseA.SourceAttribution = Attribution;
FHyperTwistTrainingCase RecognitionCaseB = RecognitionCaseA;
RecognitionCaseB.CaseId = TEXT("roux-block-case-02");
RecognitionCaseB.PromptLabel = TEXT("Left Block Recognition B");
RecognitionCaseB.CanonicalStateRef = TEXT("cube/3x3x3:roux:first-block:left:b");
RecognitionCaseB.CanonicalNotation = TEXT("r U' r'");
RecognitionCaseB.ScrambleNotation = TEXT("R U2 R' F U'");
RecognitionCaseB.Difficulty = 0.62f;
FHyperTwistTrainingDeck DrillDeck;
DrillDeck.DeckId = TEXT("roux-first-block-recognition");
DrillDeck.Title = TEXT("First Block Recognition");
DrillDeck.DeliveryModes = {
EHyperTwistTrainingDeliveryMode::VirtualCube,
EHyperTwistTrainingDeliveryMode::RecognitionAssisted,
EHyperTwistTrainingDeliveryMode::CoachReviewed
};
DrillDeck.SelectionPolicy = EHyperTwistTrainingSelectionPolicy::Scripted;
DrillDeck.bUsesRealScrambles = false;
DrillDeck.bSupportsAlgorithmReveal = true;
DrillDeck.bSupportsVirtualCube = true;
DrillDeck.bSupportsSmartCube = false;
DrillDeck.Cases = {RecognitionCaseA, RecognitionCaseB};
DrillDeck.DifficultyBand = TEXT("stage");
DrillDeck.Tags = {
TEXT("classic"),
TEXT("roux"),
TEXT("block-building"),
TEXT("subset:first-block"),
TEXT("method-drill")
};
DrillDeck.SubsetId = TEXT("first-block");
DrillDeck.SourceAttribution = Attribution;
return DrillDeck;
}
}
FHyperTwistPuzzleDefinitionRef UHyperTwistContractLibrary::MakeSampleClassicPuzzleDefinition()
@ -329,6 +403,96 @@ FHyperTwistTrainingRunState UHyperTwistContractLibrary::MakeSampleTrainingRunSta
);
}
FHyperTwistTrainingMethodDrillDefinition UHyperTwistContractLibrary::MakeSampleMethodDrillDefinition()
{
FHyperTwistTrainingMethodDrillDefinition DrillDefinition =
UHyperTwistTrainingLibrary::BuildMethodDrillDefinitionFromDeck(
HyperTwistContractLibraryInternal::MakeSampleMethodDrillDeck(),
EHyperTwistTrainingDrillModeKind::Recognition,
EHyperTwistTrainingDrillVisibilityPolicy::MaskedState,
true,
EHyperTwistTrainingDrillEntrySource::Catalog
);
DrillDefinition.Title = TEXT("Roux First Block Recognition Drill");
DrillDefinition.StageId = TEXT("first-block");
DrillDefinition.StageLabel = TEXT("First Block");
return DrillDefinition;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistContractLibrary::MakeSampleMethodDrillRunState()
{
const FHyperTwistTrainingMethodDrillDefinition DrillDefinition = MakeSampleMethodDrillDefinition();
if (!DrillDefinition.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillRunState();
}
const FHyperTwistTrainingMethodDrillRunState ReadyRun = UHyperTwistTrainingLibrary::StartMethodDrillRun(
DrillDefinition,
TEXT("local-user"),
TEXT("method_drill_run_01")
);
FHyperTwistTrainingMethodDrillRunState ActiveRun = UHyperTwistTrainingLibrary::StartNextMethodDrillCase(ReadyRun);
if (!ActiveRun.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveRun.StartedAtUtc = TEXT("2026-04-25T12:12:00Z");
ActiveRun.LastUpdatedAtUtc = TEXT("2026-04-25T12:12:05Z");
return ActiveRun;
}
FHyperTwistTrainingMethodDrillSummary UHyperTwistContractLibrary::MakeSampleMethodDrillSummary()
{
return UHyperTwistTrainingLibrary::DeriveMethodDrillSummary(MakeSampleMethodDrillRunState());
}
FHyperTwistTrainingMethodDrillFavoriteEntry UHyperTwistContractLibrary::MakeSampleMethodDrillFavoriteEntry()
{
return UHyperTwistTrainingRepositoryLibrary::MakeMethodDrillFavoriteEntry(
MakeSampleMethodDrillDefinition(),
TEXT("local-user"),
TEXT("roux-block-case-01"),
TEXT("2026-04-25T12:11:00Z")
);
}
FHyperTwistTrainingMethodDrillBatch UHyperTwistContractLibrary::MakeSampleMethodDrillBatch()
{
FHyperTwistTrainingMethodDrillRunState RunState = MakeSampleMethodDrillRunState();
if (!RunState.IsStructurallyValid())
{
return FHyperTwistTrainingMethodDrillBatch();
}
RunState = UHyperTwistTrainingLibrary::RevealMethodDrillCase(RunState);
FHyperTwistTrainingMethodDrillResponseRecord ResponseRecord;
ResponseRecord.ResponseId = TEXT("method_drill_response_01");
ResponseRecord.DrillRunId = RunState.RunId;
ResponseRecord.CaseId = RunState.CurrentCase.CaseId;
ResponseRecord.PresentedAtUtc = RunState.StartedAtUtc;
ResponseRecord.RespondedAtUtc = TEXT("2026-04-25T12:13:00Z");
ResponseRecord.DrillMode = RunState.DrillDefinition.DrillMode;
ResponseRecord.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
ResponseRecord.bAnswerWasRevealed = true;
ResponseRecord.bMarkedCorrect = true;
ResponseRecord.bWasRepeat = false;
ResponseRecord.ElapsedMs = 1850;
ResponseRecord.ResponseText = TEXT("identified left block anchor and follow-up pair");
RunState.Responses.Add(ResponseRecord);
RunState.LastUpdatedAtUtc = ResponseRecord.RespondedAtUtc;
return UHyperTwistTrainingLibrary::BuildMethodDrillBatch(
RunState,
TEXT("method_drill_batch_01"),
TEXT("Pinned Roux Recognition Queue"),
true
);
}
FHyperTwistTrainingRunStepResult UHyperTwistContractLibrary::MakeSampleTrainingRunStepResult()
{
const FHyperTwistTrainingRunState RunState = MakeSampleTrainingRunState();
@ -434,6 +598,24 @@ FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainin
);
}
const FHyperTwistTrainingMethodDrillFavoriteEntry FavoriteEntry = MakeSampleMethodDrillFavoriteEntry();
if (FavoriteEntry.IsStructurallyValid())
{
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillFavorite(
RepositoryState,
FavoriteEntry
);
}
const FHyperTwistTrainingMethodDrillBatch DrillBatch = MakeSampleMethodDrillBatch();
if (DrillBatch.IsStructurallyValid())
{
RepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillBatch(
RepositoryState,
DrillBatch
);
}
return RepositoryState;
}
@ -468,6 +650,22 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistContractLibrary::MakeSam
}
RepositoryState.StoredSessionTemplates.Add(FHyperTwistTrainingSessionTemplate());
const FHyperTwistTrainingMethodDrillFavoriteEntry FavoriteEntry = MakeSampleMethodDrillFavoriteEntry();
if (FavoriteEntry.IsStructurallyValid())
{
RepositoryState.StoredMethodDrillFavorites.Add(FavoriteEntry);
RepositoryState.StoredMethodDrillFavorites.Add(FavoriteEntry);
}
RepositoryState.StoredMethodDrillFavorites.Add(FHyperTwistTrainingMethodDrillFavoriteEntry());
const FHyperTwistTrainingMethodDrillBatch DrillBatch = MakeSampleMethodDrillBatch();
if (DrillBatch.IsStructurallyValid())
{
RepositoryState.StoredMethodDrillBatches.Add(DrillBatch);
RepositoryState.StoredMethodDrillBatches.Add(DrillBatch);
}
RepositoryState.StoredMethodDrillBatches.Add(FHyperTwistTrainingMethodDrillBatch());
return UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(RepositoryState);
}
@ -845,6 +1043,34 @@ FString UHyperTwistContractLibrary::SerializeContentPackToJson(const FHyperTwist
return HyperTwistContractLibraryInternal::SerializeStructToJson(ContentPack);
}
FString UHyperTwistContractLibrary::SerializeMethodDrillDefinitionToJson(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition
)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(DrillDefinition);
}
FString UHyperTwistContractLibrary::SerializeMethodDrillRunStateToJson(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(RunState);
}
FString UHyperTwistContractLibrary::SerializeMethodDrillFavoriteEntryToJson(
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(FavoriteEntry);
}
FString UHyperTwistContractLibrary::SerializeMethodDrillBatchToJson(
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(DrillBatch);
}
FString UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState)
{
return HyperTwistContractLibraryInternal::SerializeStructToJson(RepositoryState);
@ -872,6 +1098,38 @@ bool UHyperTwistContractLibrary::DeserializeContentPackFromJson(const FString& J
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutContentPack);
}
bool UHyperTwistContractLibrary::DeserializeMethodDrillDefinitionFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillDefinition& OutDrillDefinition
)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutDrillDefinition);
}
bool UHyperTwistContractLibrary::DeserializeMethodDrillRunStateFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillRunState& OutRunState
)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutRunState);
}
bool UHyperTwistContractLibrary::DeserializeMethodDrillFavoriteEntryFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillFavoriteEntry& OutFavoriteEntry
)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutFavoriteEntry);
}
bool UHyperTwistContractLibrary::DeserializeMethodDrillBatchFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillBatch& OutDrillBatch
)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutDrillBatch);
}
bool UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState)
{
return HyperTwistContractLibraryInternal::DeserializeStructFromJson(Json, OutRepositoryState);

View file

@ -1,5 +1,6 @@
#include "HyperTwistTraining/HyperTwistTrainingLibrary.h"
#include "HyperTwistTraining/HyperTwistTrainingCatalogLibrary.h"
#include "HyperTwistReplay/HyperTwistReplayLibrary.h"
#include "JsonObjectConverter.h"
@ -471,6 +472,171 @@ namespace HyperTwistTrainingLibraryInternal
FJsonObjectConverter::UStructToJsonObjectString(Payload, PayloadJson);
return PayloadJson;
}
FString ResolveDrillTimestamp(const FString& Value)
{
return Value.IsEmpty() ? FDateTime::UtcNow().ToIso8601() : Value;
}
FString DrillModeToToken(EHyperTwistTrainingDrillModeKind DrillMode)
{
switch (DrillMode)
{
case EHyperTwistTrainingDrillModeKind::Recognition:
return TEXT("recognition");
case EHyperTwistTrainingDrillModeKind::Recall:
return TEXT("recall");
case EHyperTwistTrainingDrillModeKind::Execution:
return TEXT("execution");
case EHyperTwistTrainingDrillModeKind::Reconstruction:
return TEXT("reconstruction");
default:
return TEXT("drill");
}
}
FString DrillVisibilityToToken(EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy)
{
switch (VisibilityPolicy)
{
case EHyperTwistTrainingDrillVisibilityPolicy::FullInformation:
return TEXT("full");
case EHyperTwistTrainingDrillVisibilityPolicy::PromptOnly:
return TEXT("prompt-only");
case EHyperTwistTrainingDrillVisibilityPolicy::MaskedState:
return TEXT("masked");
case EHyperTwistTrainingDrillVisibilityPolicy::PartialState:
return TEXT("partial");
default:
return TEXT("visibility");
}
}
TArray<FString> BuildDrillCaseIds(const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition)
{
TArray<FString> CaseIds;
if (!DrillDefinition.SourceDeck.IsStructurallyValid())
{
return CaseIds;
}
if (DrillDefinition.FocusCaseIds.Num() > 0)
{
for (const FString& CaseId : DrillDefinition.FocusCaseIds)
{
const FHyperTwistTrainingCase* MatchingCase = FindCaseById(DrillDefinition.SourceDeck, CaseId);
if (MatchingCase != nullptr && MatchingCase->IsStructurallyValid())
{
CaseIds.AddUnique(CaseId);
}
}
}
if (CaseIds.Num() == 0)
{
for (const FHyperTwistTrainingCase& TrainingCase : DrillDefinition.SourceDeck.Cases)
{
if (TrainingCase.IsStructurallyValid())
{
CaseIds.AddUnique(TrainingCase.CaseId);
}
}
}
return CaseIds;
}
const FHyperTwistTrainingCase* FindDrillCaseById(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& CaseId
)
{
return FindCaseById(DrillDefinition.SourceDeck, CaseId);
}
void NormalizeDrillQueueAgainstDefinition(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
TArray<FString>& PendingCaseIds,
TArray<FString>& CompletedCaseIds,
FString& CurrentCaseId
)
{
const TArray<FString> AllowedCaseIds = BuildDrillCaseIds(DrillDefinition);
TSet<FString> AllowedCaseSet(AllowedCaseIds);
TArray<FString> NormalizedPending;
for (const FString& CaseId : PendingCaseIds)
{
if (AllowedCaseSet.Contains(CaseId))
{
NormalizedPending.AddUnique(CaseId);
}
}
TArray<FString> NormalizedCompleted;
for (const FString& CaseId : CompletedCaseIds)
{
if (AllowedCaseSet.Contains(CaseId))
{
NormalizedCompleted.AddUnique(CaseId);
}
}
PendingCaseIds = MoveTemp(NormalizedPending);
CompletedCaseIds = MoveTemp(NormalizedCompleted);
if (!CurrentCaseId.IsEmpty())
{
if (!AllowedCaseSet.Contains(CurrentCaseId))
{
CurrentCaseId.Reset();
}
else
{
CompletedCaseIds.Remove(CurrentCaseId);
}
}
}
FHyperTwistTrainingMethodDrillSummary BuildDrillSummaryInternal(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillSummary Summary;
if (!RunState.IsStructurallyValid())
{
return Summary;
}
TSet<FString> KnownCaseIds;
for (const FString& CaseId : RunState.PendingCaseIds)
{
KnownCaseIds.Add(CaseId);
}
for (const FString& CaseId : RunState.CompletedCaseIds)
{
KnownCaseIds.Add(CaseId);
}
if (!RunState.CurrentCase.CaseId.IsEmpty())
{
KnownCaseIds.Add(RunState.CurrentCase.CaseId);
}
Summary.RunId = RunState.RunId;
Summary.DrillId = RunState.DrillDefinition.DrillId;
Summary.UserId = RunState.UserId;
Summary.LifecycleState = RunState.LifecycleState;
Summary.TotalCaseCount = KnownCaseIds.Num();
Summary.PendingCaseCount = RunState.PendingCaseIds.Num();
Summary.CompletedCaseCount = RunState.CompletedCaseIds.Num();
Summary.ResponseCount = RunState.Responses.Num();
Summary.RevealCount = RunState.RevealCount;
Summary.RepeatCount = RunState.RepeatCount;
Summary.bAnswerHiddenUntilReveal = RunState.DrillDefinition.bAnswerHiddenUntilReveal;
Summary.CurrentCaseId = RunState.CurrentCase.CaseId;
Summary.LastUpdatedAtUtc = RunState.LastUpdatedAtUtc;
return Summary;
}
}
FHyperTwistContentPack UHyperTwistTrainingLibrary::MakeSampleClassicContentPack()
@ -1449,3 +1615,351 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingLibrary::ResolveTrainingRunA
);
return StepResult;
}
FHyperTwistTrainingMethodDrillDefinition UHyperTwistTrainingLibrary::BuildMethodDrillDefinitionFromDeck(
const FHyperTwistTrainingDeck& Deck,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal,
EHyperTwistTrainingDrillEntrySource EntrySource
)
{
FHyperTwistTrainingMethodDrillDefinition DrillDefinition;
if (!Deck.IsStructurallyValid())
{
return DrillDefinition;
}
DrillDefinition.MethodDescriptor = UHyperTwistTrainingCatalogLibrary::DescribeTrainingDeckMethod(Deck);
if (!DrillDefinition.MethodDescriptor.IsStructurallyValid())
{
return DrillDefinition;
}
DrillDefinition.SourceDeckId = Deck.DeckId;
DrillDefinition.SourceDeck = Deck;
DrillDefinition.FocusCaseIds = HyperTwistTrainingLibraryInternal::BuildDrillCaseIds(DrillDefinition);
DrillDefinition.DrillMode = DrillMode;
DrillDefinition.VisibilityPolicy = VisibilityPolicy;
DrillDefinition.EntrySource = EntrySource;
DrillDefinition.bAnswerHiddenUntilReveal = bAnswerHiddenUntilReveal;
DrillDefinition.bAllowImmediateRepeat = true;
DrillDefinition.SourceAttribution = Deck.SourceAttribution;
DrillDefinition.StageId = !Deck.SubsetId.IsEmpty()
? Deck.SubsetId.ToLower()
: DrillDefinition.MethodDescriptor.SegmentId;
DrillDefinition.StageLabel = !Deck.SubsetId.IsEmpty()
? Deck.SubsetId
: DrillDefinition.MethodDescriptor.MethodLabel;
DrillDefinition.DrillId = FString::Printf(
TEXT("%s/drill/%s/%s"),
*Deck.DeckId,
*HyperTwistTrainingLibraryInternal::DrillModeToToken(DrillMode),
*HyperTwistTrainingLibraryInternal::DrillVisibilityToToken(VisibilityPolicy)
);
DrillDefinition.Title = FString::Printf(TEXT("%s Drill"), *Deck.Title);
DrillDefinition.Tags = Deck.Tags;
DrillDefinition.Tags.AddUnique(TEXT("method-drill"));
DrillDefinition.Tags.AddUnique(FString::Printf(
TEXT("drill-mode:%s"),
*HyperTwistTrainingLibraryInternal::DrillModeToToken(DrillMode)
));
DrillDefinition.Tags.AddUnique(FString::Printf(
TEXT("visibility:%s"),
*HyperTwistTrainingLibraryInternal::DrillVisibilityToToken(VisibilityPolicy)
));
DrillDefinition.Tags.AddUnique(FString::Printf(
TEXT("method-segment:%s"),
*DrillDefinition.MethodDescriptor.SegmentId.ToLower()
));
return DrillDefinition;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::StartMethodDrillRun(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
)
{
FHyperTwistTrainingMethodDrillRunState RunState;
if (!DrillDefinition.IsStructurallyValid())
{
return RunState;
}
RunState.DrillDefinition = DrillDefinition;
RunState.RunId = RunId.IsEmpty()
? FString::Printf(TEXT("method_drill_%s"), *DrillDefinition.DrillId)
: RunId;
RunState.UserId = UserId.IsEmpty() ? TEXT("local-user") : UserId;
RunState.StartedAtUtc = FDateTime::UtcNow().ToIso8601();
RunState.LastUpdatedAtUtc = RunState.StartedAtUtc;
RunState.LifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
RunState.PendingCaseIds = HyperTwistTrainingLibraryInternal::BuildDrillCaseIds(DrillDefinition);
if (RunState.PendingCaseIds.Num() == 0)
{
return FHyperTwistTrainingMethodDrillRunState();
}
return RunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::ResumeMethodDrillRunFromBatch(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
)
{
FHyperTwistTrainingMethodDrillRunState RunState;
if (!DrillDefinition.IsStructurallyValid()
|| !DrillBatch.IsStructurallyValid()
|| DrillBatch.DrillId != DrillDefinition.DrillId
|| DrillBatch.SourceDeckId != DrillDefinition.SourceDeckId)
{
return RunState;
}
RunState.DrillDefinition = DrillDefinition;
RunState.RunId = RunId.IsEmpty()
? FString::Printf(TEXT("method_drill_resume_%s"), *DrillBatch.BatchId)
: RunId;
RunState.UserId = DrillBatch.UserId;
RunState.SourceBatchId = DrillBatch.BatchId;
RunState.LifecycleState = DrillBatch.SavedLifecycleState;
RunState.StartedAtUtc = HyperTwistTrainingLibraryInternal::ResolveDrillTimestamp(DrillBatch.CreatedAtUtc);
RunState.LastUpdatedAtUtc = HyperTwistTrainingLibraryInternal::ResolveDrillTimestamp(DrillBatch.LastUpdatedAtUtc);
RunState.PendingCaseIds = DrillBatch.PendingCaseIds;
RunState.CompletedCaseIds = DrillBatch.CompletedCaseIds;
RunState.Responses = DrillBatch.Responses;
RunState.RevealCount = DrillBatch.RevealCount;
RunState.RepeatCount = DrillBatch.RepeatCount;
RunState.bAnswerVisible = RunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Revealed;
FString CurrentCaseId = DrillBatch.CurrentCaseId;
HyperTwistTrainingLibraryInternal::NormalizeDrillQueueAgainstDefinition(
RunState.DrillDefinition,
RunState.PendingCaseIds,
RunState.CompletedCaseIds,
CurrentCaseId
);
if (!CurrentCaseId.IsEmpty())
{
const FHyperTwistTrainingCase* CurrentCase = HyperTwistTrainingLibraryInternal::FindDrillCaseById(
RunState.DrillDefinition,
CurrentCaseId
);
if (CurrentCase != nullptr && CurrentCase->IsStructurallyValid())
{
RunState.CurrentCase = *CurrentCase;
}
}
if ((RunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Active
|| RunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Revealed)
&& !RunState.CurrentCase.IsStructurallyValid())
{
RunState.LifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
RunState.bAnswerVisible = false;
}
if (RunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Completed
|| RunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Aborted)
{
RunState.EndedAtUtc = RunState.LastUpdatedAtUtc;
RunState.CurrentCase = FHyperTwistTrainingCase();
RunState.bAnswerVisible = false;
}
return RunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::StartNextMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillRunState UpdatedRunState = RunState;
if (!UpdatedRunState.IsStructurallyValid()
|| UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Ready)
{
return UpdatedRunState;
}
FString CurrentCaseId = UpdatedRunState.CurrentCase.CaseId;
HyperTwistTrainingLibraryInternal::NormalizeDrillQueueAgainstDefinition(
UpdatedRunState.DrillDefinition,
UpdatedRunState.PendingCaseIds,
UpdatedRunState.CompletedCaseIds,
CurrentCaseId
);
if (UpdatedRunState.PendingCaseIds.Num() == 0)
{
return FinishMethodDrillRun(UpdatedRunState, false);
}
const FHyperTwistTrainingCase* NextCase = HyperTwistTrainingLibraryInternal::FindDrillCaseById(
UpdatedRunState.DrillDefinition,
UpdatedRunState.PendingCaseIds[0]
);
if (NextCase == nullptr || !NextCase->IsStructurallyValid())
{
UpdatedRunState.PendingCaseIds.RemoveAt(0);
return StartNextMethodDrillCase(UpdatedRunState);
}
UpdatedRunState.CurrentCase = *NextCase;
UpdatedRunState.LifecycleState = EHyperTwistTrainingDrillLifecycleState::Active;
UpdatedRunState.bAnswerVisible = !UpdatedRunState.DrillDefinition.bAnswerHiddenUntilReveal;
UpdatedRunState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601();
return UpdatedRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::RevealMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillRunState UpdatedRunState = RunState;
if (!UpdatedRunState.IsStructurallyValid()
|| UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Active
|| !UpdatedRunState.CurrentCase.IsStructurallyValid())
{
return UpdatedRunState;
}
if (!UpdatedRunState.bAnswerVisible)
{
UpdatedRunState.RevealCount += 1;
}
UpdatedRunState.bAnswerVisible = true;
UpdatedRunState.LifecycleState = EHyperTwistTrainingDrillLifecycleState::Revealed;
UpdatedRunState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601();
return UpdatedRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::RepeatMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillRunState UpdatedRunState = RunState;
if (!UpdatedRunState.IsStructurallyValid()
|| (UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Active
&& UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Revealed)
|| !UpdatedRunState.CurrentCase.IsStructurallyValid()
|| !UpdatedRunState.DrillDefinition.bAllowImmediateRepeat)
{
return UpdatedRunState;
}
UpdatedRunState.RepeatCount += 1;
UpdatedRunState.bAnswerVisible = !UpdatedRunState.DrillDefinition.bAnswerHiddenUntilReveal;
UpdatedRunState.LifecycleState = EHyperTwistTrainingDrillLifecycleState::Active;
UpdatedRunState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601();
return UpdatedRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::AdvanceMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
FHyperTwistTrainingMethodDrillRunState UpdatedRunState = RunState;
if (!UpdatedRunState.IsStructurallyValid()
|| (UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Active
&& UpdatedRunState.LifecycleState != EHyperTwistTrainingDrillLifecycleState::Revealed)
|| !UpdatedRunState.CurrentCase.IsStructurallyValid())
{
return UpdatedRunState;
}
const FString CompletedCaseId = UpdatedRunState.CurrentCase.CaseId;
UpdatedRunState.PendingCaseIds.Remove(CompletedCaseId);
UpdatedRunState.CompletedCaseIds.AddUnique(CompletedCaseId);
UpdatedRunState.CurrentCase = FHyperTwistTrainingCase();
UpdatedRunState.bAnswerVisible = false;
UpdatedRunState.LastUpdatedAtUtc = FDateTime::UtcNow().ToIso8601();
FString CurrentCaseId;
HyperTwistTrainingLibraryInternal::NormalizeDrillQueueAgainstDefinition(
UpdatedRunState.DrillDefinition,
UpdatedRunState.PendingCaseIds,
UpdatedRunState.CompletedCaseIds,
CurrentCaseId
);
UpdatedRunState.LifecycleState = UpdatedRunState.PendingCaseIds.Num() > 0
? EHyperTwistTrainingDrillLifecycleState::Ready
: EHyperTwistTrainingDrillLifecycleState::Completed;
if (UpdatedRunState.LifecycleState == EHyperTwistTrainingDrillLifecycleState::Completed)
{
UpdatedRunState.EndedAtUtc = UpdatedRunState.LastUpdatedAtUtc;
}
return UpdatedRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingLibrary::FinishMethodDrillRun(
const FHyperTwistTrainingMethodDrillRunState& RunState,
bool bAbortRun
)
{
FHyperTwistTrainingMethodDrillRunState UpdatedRunState = RunState;
if (!UpdatedRunState.IsStructurallyValid())
{
return UpdatedRunState;
}
UpdatedRunState.LifecycleState = bAbortRun
? EHyperTwistTrainingDrillLifecycleState::Aborted
: EHyperTwistTrainingDrillLifecycleState::Completed;
UpdatedRunState.EndedAtUtc = FDateTime::UtcNow().ToIso8601();
UpdatedRunState.LastUpdatedAtUtc = UpdatedRunState.EndedAtUtc;
UpdatedRunState.CurrentCase = FHyperTwistTrainingCase();
UpdatedRunState.bAnswerVisible = false;
return UpdatedRunState;
}
FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingLibrary::DeriveMethodDrillSummary(
const FHyperTwistTrainingMethodDrillRunState& RunState
)
{
return HyperTwistTrainingLibraryInternal::BuildDrillSummaryInternal(RunState);
}
FHyperTwistTrainingMethodDrillBatch UHyperTwistTrainingLibrary::BuildMethodDrillBatch(
const FHyperTwistTrainingMethodDrillRunState& RunState,
const FString& BatchId,
const FString& Title,
bool bPinnedFavoritesOnly
)
{
FHyperTwistTrainingMethodDrillBatch DrillBatch;
if (!RunState.IsStructurallyValid())
{
return DrillBatch;
}
DrillBatch.UserId = RunState.UserId;
DrillBatch.BatchId = BatchId.IsEmpty()
? FString::Printf(TEXT("method_drill_batch_%s"), *RunState.RunId)
: BatchId;
DrillBatch.Title = Title.IsEmpty() ? RunState.DrillDefinition.Title : Title;
DrillBatch.DrillId = RunState.DrillDefinition.DrillId;
DrillBatch.SourceDeckId = RunState.DrillDefinition.SourceDeckId;
DrillBatch.MethodDescriptor = RunState.DrillDefinition.MethodDescriptor;
DrillBatch.DrillMode = RunState.DrillDefinition.DrillMode;
DrillBatch.VisibilityPolicy = RunState.DrillDefinition.VisibilityPolicy;
DrillBatch.bAnswerHiddenUntilReveal = RunState.DrillDefinition.bAnswerHiddenUntilReveal;
DrillBatch.SavedLifecycleState = RunState.LifecycleState;
DrillBatch.PendingCaseIds = RunState.PendingCaseIds;
DrillBatch.CompletedCaseIds = RunState.CompletedCaseIds;
DrillBatch.CurrentCaseId = RunState.CurrentCase.CaseId;
DrillBatch.Responses = RunState.Responses;
DrillBatch.RevealCount = RunState.RevealCount;
DrillBatch.RepeatCount = RunState.RepeatCount;
DrillBatch.CreatedAtUtc = HyperTwistTrainingLibraryInternal::ResolveDrillTimestamp(RunState.StartedAtUtc);
DrillBatch.LastUpdatedAtUtc = HyperTwistTrainingLibraryInternal::ResolveDrillTimestamp(RunState.LastUpdatedAtUtc);
DrillBatch.bPinnedFavoritesOnly = bPinnedFavoritesOnly;
return DrillBatch;
}

View file

@ -282,6 +282,54 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
return UserId + CaseKeySeparator + TemplateId;
}
FString MakeMethodDrillFavoriteKey(
const FString& UserId,
const FString& DrillId,
const FString& CaseId
)
{
return UserId + CaseKeySeparator + DrillId + CaseKeySeparator + CaseId;
}
FString MakeMethodDrillBatchKey(const FString& UserId, const FString& BatchId)
{
return UserId + CaseKeySeparator + BatchId;
}
TArray<FString> DeduplicateCaseIds(const TArray<FString>& CaseIds)
{
TArray<FString> UniqueCaseIds;
for (const FString& CaseId : CaseIds)
{
if (!CaseId.IsEmpty())
{
UniqueCaseIds.AddUnique(CaseId);
}
}
return UniqueCaseIds;
}
TArray<FHyperTwistTrainingMethodDrillResponseRecord> DeduplicateMethodDrillResponses(
const TArray<FHyperTwistTrainingMethodDrillResponseRecord>& Responses
)
{
TArray<FHyperTwistTrainingMethodDrillResponseRecord> UniqueResponses;
TSet<FString> SeenResponseIds;
for (const FHyperTwistTrainingMethodDrillResponseRecord& Response : Responses)
{
if (!Response.IsStructurallyValid() || SeenResponseIds.Contains(Response.ResponseId))
{
continue;
}
SeenResponseIds.Add(Response.ResponseId);
UniqueResponses.Add(Response);
}
return UniqueResponses;
}
bool SplitCaseKey(const FString& CaseKey, FString& OutUserId, FString& OutDeckId, FString& OutCaseId)
{
TArray<FString> Parts;
@ -308,6 +356,45 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
}
}
FHyperTwistTrainingMethodDrillFavoriteEntry NormalizeMethodDrillFavoriteEntry(
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
)
{
FHyperTwistTrainingMethodDrillFavoriteEntry NormalizedFavorite = FavoriteEntry;
NormalizedFavorite.Tags = DeduplicateTemplateCaseIds(FavoriteEntry.Tags);
NormalizedFavorite.Tags.AddUnique(TEXT("favorite"));
if (NormalizedFavorite.AddedAtUtc.IsEmpty())
{
NormalizedFavorite.AddedAtUtc = FDateTime::UtcNow().ToIso8601();
}
return NormalizedFavorite;
}
FHyperTwistTrainingMethodDrillBatch NormalizeMethodDrillBatch(
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
)
{
FHyperTwistTrainingMethodDrillBatch NormalizedBatch = DrillBatch;
NormalizedBatch.PendingCaseIds = DeduplicateCaseIds(DrillBatch.PendingCaseIds);
NormalizedBatch.CompletedCaseIds = DeduplicateCaseIds(DrillBatch.CompletedCaseIds);
if (!NormalizedBatch.CurrentCaseId.IsEmpty())
{
NormalizedBatch.CompletedCaseIds.Remove(NormalizedBatch.CurrentCaseId);
}
NormalizedBatch.Responses = DeduplicateMethodDrillResponses(DrillBatch.Responses);
if (NormalizedBatch.CreatedAtUtc.IsEmpty())
{
NormalizedBatch.CreatedAtUtc = FDateTime::UtcNow().ToIso8601();
}
if (NormalizedBatch.LastUpdatedAtUtc.IsEmpty())
{
NormalizedBatch.LastUpdatedAtUtc = NormalizedBatch.CreatedAtUtc;
}
return NormalizedBatch;
}
FHyperTwistTrainingCaseLearnerState DeriveLearnerStateFromReviewHistory(
const FString& UserId,
const FString& DeckId,
@ -1369,6 +1456,273 @@ FHyperTwistTrainingRunRecord UHyperTwistTrainingRepositoryLibrary::BuildRunRecor
return RunRecord;
}
FHyperTwistTrainingMethodDrillFavoriteEntry UHyperTwistTrainingRepositoryLibrary::MakeMethodDrillFavoriteEntry(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& CaseId,
const FString& AddedAtUtc
)
{
FHyperTwistTrainingMethodDrillFavoriteEntry FavoriteEntry;
if (!DrillDefinition.IsStructurallyValid() || UserId.IsEmpty() || CaseId.IsEmpty())
{
return FavoriteEntry;
}
const FHyperTwistTrainingCase* MatchingCase = DrillDefinition.SourceDeck.Cases.FindByPredicate(
[&CaseId](const FHyperTwistTrainingCase& TrainingCase)
{
return TrainingCase.CaseId == CaseId;
}
);
if (MatchingCase == nullptr || !MatchingCase->IsStructurallyValid())
{
return FavoriteEntry;
}
FavoriteEntry.UserId = UserId;
FavoriteEntry.DrillId = DrillDefinition.DrillId;
FavoriteEntry.CaseId = CaseId;
FavoriteEntry.SourceDeckId = DrillDefinition.SourceDeckId;
FavoriteEntry.MethodDescriptor = DrillDefinition.MethodDescriptor;
FavoriteEntry.AddedAtUtc = AddedAtUtc.IsEmpty() ? FDateTime::UtcNow().ToIso8601() : AddedAtUtc;
FavoriteEntry.Tags = DrillDefinition.Tags;
for (const FString& Tag : MatchingCase->Tags)
{
FavoriteEntry.Tags.AddUnique(Tag);
}
FavoriteEntry.Tags.AddUnique(TEXT("favorite"));
return FavoriteEntry;
}
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> UHyperTwistTrainingRepositoryLibrary::ListMethodDrillFavorites(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& MethodSegmentId
)
{
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> Favorites;
for (const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry : RepositoryState.StoredMethodDrillFavorites)
{
if (!FavoriteEntry.IsStructurallyValid())
{
continue;
}
const bool bMatchesUser = UserId.IsEmpty() || FavoriteEntry.UserId == UserId;
const bool bMatchesSegment = MethodSegmentId.IsEmpty()
|| FavoriteEntry.MethodDescriptor.SegmentId == MethodSegmentId;
if (bMatchesUser && bMatchesSegment)
{
Favorites.Add(FavoriteEntry);
}
}
Favorites.Sort([](
const FHyperTwistTrainingMethodDrillFavoriteEntry& Left,
const FHyperTwistTrainingMethodDrillFavoriteEntry& Right
)
{
const int32 AddedAtCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
Left.AddedAtUtc,
Right.AddedAtUtc
);
if (AddedAtCompare != 0)
{
return AddedAtCompare > 0;
}
if (Left.DrillId != Right.DrillId)
{
return Left.DrillId < Right.DrillId;
}
return Left.CaseId < Right.CaseId;
});
return Favorites;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillFavorite(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
)
{
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
if (!FavoriteEntry.IsStructurallyValid())
{
return UpdatedRepositoryState;
}
const FHyperTwistTrainingMethodDrillFavoriteEntry NormalizedFavorite =
HyperTwistTrainingRepositoryLibraryInternal::NormalizeMethodDrillFavoriteEntry(FavoriteEntry);
FHyperTwistTrainingMethodDrillFavoriteEntry* ExistingFavorite =
UpdatedRepositoryState.StoredMethodDrillFavorites.FindByPredicate(
[&NormalizedFavorite](const FHyperTwistTrainingMethodDrillFavoriteEntry& Candidate)
{
return Candidate.UserId == NormalizedFavorite.UserId
&& Candidate.DrillId == NormalizedFavorite.DrillId
&& Candidate.CaseId == NormalizedFavorite.CaseId;
}
);
if (ExistingFavorite != nullptr)
{
*ExistingFavorite = NormalizedFavorite;
}
else
{
UpdatedRepositoryState.StoredMethodDrillFavorites.Add(NormalizedFavorite);
}
return UpdatedRepositoryState;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveMethodDrillFavorite(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DrillId,
const FString& CaseId
)
{
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
if (UserId.IsEmpty() || DrillId.IsEmpty() || CaseId.IsEmpty())
{
return UpdatedRepositoryState;
}
UpdatedRepositoryState.StoredMethodDrillFavorites.RemoveAll(
[&UserId, &DrillId, &CaseId](const FHyperTwistTrainingMethodDrillFavoriteEntry& Candidate)
{
return Candidate.UserId == UserId
&& Candidate.DrillId == DrillId
&& Candidate.CaseId == CaseId;
}
);
return UpdatedRepositoryState;
}
TArray<FHyperTwistTrainingMethodDrillBatch> UHyperTwistTrainingRepositoryLibrary::ListMethodDrillBatches(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& MethodSegmentId
)
{
TArray<FHyperTwistTrainingMethodDrillBatch> Batches;
for (const FHyperTwistTrainingMethodDrillBatch& DrillBatch : RepositoryState.StoredMethodDrillBatches)
{
if (!DrillBatch.IsStructurallyValid())
{
continue;
}
const bool bMatchesUser = UserId.IsEmpty() || DrillBatch.UserId == UserId;
const bool bMatchesSegment = MethodSegmentId.IsEmpty()
|| DrillBatch.MethodDescriptor.SegmentId == MethodSegmentId;
if (bMatchesUser && bMatchesSegment)
{
Batches.Add(DrillBatch);
}
}
Batches.Sort([](const FHyperTwistTrainingMethodDrillBatch& Left, const FHyperTwistTrainingMethodDrillBatch& Right)
{
const int32 UpdatedAtCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
Left.LastUpdatedAtUtc,
Right.LastUpdatedAtUtc
);
if (UpdatedAtCompare != 0)
{
return UpdatedAtCompare > 0;
}
return Left.BatchId < Right.BatchId;
});
return Batches;
}
bool UHyperTwistTrainingRepositoryLibrary::TryGetMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
)
{
OutBatch = FHyperTwistTrainingMethodDrillBatch();
if (UserId.IsEmpty() || BatchId.IsEmpty())
{
return false;
}
const FHyperTwistTrainingMethodDrillBatch* MatchingBatch =
RepositoryState.StoredMethodDrillBatches.FindByPredicate(
[&UserId, &BatchId](const FHyperTwistTrainingMethodDrillBatch& Candidate)
{
return Candidate.UserId == UserId && Candidate.BatchId == BatchId;
}
);
if (MatchingBatch == nullptr)
{
return false;
}
OutBatch = *MatchingBatch;
return OutBatch.IsStructurallyValid();
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
)
{
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
if (!DrillBatch.IsStructurallyValid())
{
return UpdatedRepositoryState;
}
const FHyperTwistTrainingMethodDrillBatch NormalizedBatch =
HyperTwistTrainingRepositoryLibraryInternal::NormalizeMethodDrillBatch(DrillBatch);
FHyperTwistTrainingMethodDrillBatch* ExistingBatch =
UpdatedRepositoryState.StoredMethodDrillBatches.FindByPredicate(
[&NormalizedBatch](const FHyperTwistTrainingMethodDrillBatch& Candidate)
{
return Candidate.UserId == NormalizedBatch.UserId
&& Candidate.BatchId == NormalizedBatch.BatchId;
}
);
if (ExistingBatch != nullptr)
{
*ExistingBatch = NormalizedBatch;
}
else
{
UpdatedRepositoryState.StoredMethodDrillBatches.Add(NormalizedBatch);
}
return UpdatedRepositoryState;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& BatchId
)
{
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
if (UserId.IsEmpty() || BatchId.IsEmpty())
{
return UpdatedRepositoryState;
}
UpdatedRepositoryState.StoredMethodDrillBatches.RemoveAll(
[&UserId, &BatchId](const FHyperTwistTrainingMethodDrillBatch& Candidate)
{
return Candidate.UserId == UserId && Candidate.BatchId == BatchId;
}
);
return UpdatedRepositoryState;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingRunRecord& RunRecord
@ -2176,6 +2530,53 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
SeenStoredTemplateKeys.Add(TemplateKey);
}
Report.TotalMethodDrillFavoriteCount = RepositoryState.StoredMethodDrillFavorites.Num();
TSet<FString> SeenMethodDrillFavoriteKeys;
for (const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry : RepositoryState.StoredMethodDrillFavorites)
{
if (!FavoriteEntry.IsStructurallyValid())
{
Report.InvalidMethodDrillFavoriteCount += 1;
continue;
}
const FString FavoriteKey = HyperTwistTrainingRepositoryLibraryInternal::MakeMethodDrillFavoriteKey(
FavoriteEntry.UserId,
FavoriteEntry.DrillId,
FavoriteEntry.CaseId
);
if (SeenMethodDrillFavoriteKeys.Contains(FavoriteKey))
{
Report.DuplicateMethodDrillFavoriteCount += 1;
continue;
}
SeenMethodDrillFavoriteKeys.Add(FavoriteKey);
}
Report.TotalMethodDrillBatchCount = RepositoryState.StoredMethodDrillBatches.Num();
TSet<FString> SeenMethodDrillBatchKeys;
for (const FHyperTwistTrainingMethodDrillBatch& DrillBatch : RepositoryState.StoredMethodDrillBatches)
{
if (!DrillBatch.IsStructurallyValid())
{
Report.InvalidMethodDrillBatchCount += 1;
continue;
}
const FString BatchKey = HyperTwistTrainingRepositoryLibraryInternal::MakeMethodDrillBatchKey(
DrillBatch.UserId,
DrillBatch.BatchId
);
if (SeenMethodDrillBatchKeys.Contains(BatchKey))
{
Report.DuplicateMethodDrillBatchCount += 1;
continue;
}
SeenMethodDrillBatchKeys.Add(BatchKey);
}
if (Report.InvalidRunRecordCount > 0)
{
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
@ -2235,6 +2636,34 @@ FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrar
TEXT("normalize-and-deduplicate-stored-session-templates")
);
}
if (Report.InvalidMethodDrillFavoriteCount > 0)
{
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
Report.RepairActions,
TEXT("drop-invalid-method-drill-favorites")
);
}
if (Report.DuplicateMethodDrillFavoriteCount > 0)
{
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
Report.RepairActions,
TEXT("normalize-and-deduplicate-method-drill-favorites")
);
}
if (Report.InvalidMethodDrillBatchCount > 0)
{
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
Report.RepairActions,
TEXT("drop-invalid-method-drill-batches")
);
}
if (Report.DuplicateMethodDrillBatchCount > 0)
{
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
Report.RepairActions,
TEXT("normalize-and-deduplicate-method-drill-batches")
);
}
return Report;
}
@ -2375,6 +2804,120 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairR
return Left.TemplateId < Right.TemplateId;
});
TMap<FString, int32> MethodDrillFavoriteIndexByKey;
for (const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry : RepositoryState.StoredMethodDrillFavorites)
{
if (!FavoriteEntry.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingMethodDrillFavoriteEntry NormalizedFavorite =
HyperTwistTrainingRepositoryLibraryInternal::NormalizeMethodDrillFavoriteEntry(FavoriteEntry);
if (!NormalizedFavorite.IsStructurallyValid())
{
continue;
}
const FString FavoriteKey = HyperTwistTrainingRepositoryLibraryInternal::MakeMethodDrillFavoriteKey(
NormalizedFavorite.UserId,
NormalizedFavorite.DrillId,
NormalizedFavorite.CaseId
);
if (const int32* ExistingIndex = MethodDrillFavoriteIndexByKey.Find(FavoriteKey))
{
RepairedState.StoredMethodDrillFavorites[*ExistingIndex] = NormalizedFavorite;
}
else
{
MethodDrillFavoriteIndexByKey.Add(
FavoriteKey,
RepairedState.StoredMethodDrillFavorites.Add(NormalizedFavorite)
);
}
}
RepairedState.StoredMethodDrillFavorites.Sort([](
const FHyperTwistTrainingMethodDrillFavoriteEntry& Left,
const FHyperTwistTrainingMethodDrillFavoriteEntry& Right
)
{
if (Left.UserId != Right.UserId)
{
return Left.UserId < Right.UserId;
}
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
Left.AddedAtUtc,
Right.AddedAtUtc
);
if (UtcCompare != 0)
{
return UtcCompare > 0;
}
if (Left.DrillId != Right.DrillId)
{
return Left.DrillId < Right.DrillId;
}
return Left.CaseId < Right.CaseId;
});
TMap<FString, int32> MethodDrillBatchIndexByKey;
for (const FHyperTwistTrainingMethodDrillBatch& DrillBatch : RepositoryState.StoredMethodDrillBatches)
{
if (!DrillBatch.IsStructurallyValid())
{
continue;
}
const FHyperTwistTrainingMethodDrillBatch NormalizedBatch =
HyperTwistTrainingRepositoryLibraryInternal::NormalizeMethodDrillBatch(DrillBatch);
if (!NormalizedBatch.IsStructurallyValid())
{
continue;
}
const FString BatchKey = HyperTwistTrainingRepositoryLibraryInternal::MakeMethodDrillBatchKey(
NormalizedBatch.UserId,
NormalizedBatch.BatchId
);
if (const int32* ExistingIndex = MethodDrillBatchIndexByKey.Find(BatchKey))
{
RepairedState.StoredMethodDrillBatches[*ExistingIndex] = NormalizedBatch;
}
else
{
MethodDrillBatchIndexByKey.Add(
BatchKey,
RepairedState.StoredMethodDrillBatches.Add(NormalizedBatch)
);
}
}
RepairedState.StoredMethodDrillBatches.Sort([](
const FHyperTwistTrainingMethodDrillBatch& Left,
const FHyperTwistTrainingMethodDrillBatch& Right
)
{
if (Left.UserId != Right.UserId)
{
return Left.UserId < Right.UserId;
}
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
Left.LastUpdatedAtUtc,
Right.LastUpdatedAtUtc
);
if (UtcCompare != 0)
{
return UtcCompare > 0;
}
return Left.BatchId < Right.BatchId;
});
return RepairedState;
}

View file

@ -38,6 +38,16 @@ bool UHyperTwistTrainingRuntimeLibrary::HasActiveTrainingRun(UObject* WorldConte
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::HasActiveMethodDrillRun(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->HasActiveMethodDrillRun();
}
return false;
}
FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRunState(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -48,6 +58,18 @@ FHyperTwistTrainingRunState UHyperTwistTrainingRuntimeLibrary::GetActiveTraining
return FHyperTwistTrainingRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::GetActiveMethodDrillRunState(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveMethodDrillRunState();
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingSessionSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRunSummary(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -58,6 +80,18 @@ FHyperTwistTrainingSessionSummary UHyperTwistTrainingRuntimeLibrary::GetActiveTr
return FHyperTwistTrainingSessionSummary();
}
FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingRuntimeLibrary::GetActiveMethodDrillSummary(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetActiveMethodDrillSummary();
}
return FHyperTwistTrainingMethodDrillSummary();
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRuntimeLibrary::GetTrainingRepositoryState(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -204,6 +238,34 @@ TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingRuntimeLibrary::Ge
return TArray<FHyperTwistTrainingSessionTemplate>();
}
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> UHyperTwistTrainingRuntimeLibrary::GetMethodDrillFavoritesForUser(
UObject* WorldContextObject,
const FString& UserId,
const FString& MethodSegmentId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetMethodDrillFavoritesForUser(UserId, MethodSegmentId);
}
return TArray<FHyperTwistTrainingMethodDrillFavoriteEntry>();
}
TArray<FHyperTwistTrainingMethodDrillBatch> UHyperTwistTrainingRuntimeLibrary::GetMethodDrillBatchesForUser(
UObject* WorldContextObject,
const FString& UserId,
const FString& MethodSegmentId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->GetMethodDrillBatchesForUser(UserId, MethodSegmentId);
}
return TArray<FHyperTwistTrainingMethodDrillBatch>();
}
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingCaseRecommendations(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
@ -335,6 +397,77 @@ bool UHyperTwistTrainingRuntimeLibrary::SaveTrainingSessionTemplate(
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::SaveMethodDrillFavorite(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->SaveMethodDrillFavorite(FavoriteEntry);
}
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::RemoveMethodDrillFavorite(
UObject* WorldContextObject,
const FString& UserId,
const FString& DrillId,
const FString& CaseId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->RemoveMethodDrillFavorite(UserId, DrillId, CaseId);
}
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::TryGetStoredMethodDrillBatch(
UObject* WorldContextObject,
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->TryGetStoredMethodDrillBatch(UserId, BatchId, OutBatch);
}
OutBatch = FHyperTwistTrainingMethodDrillBatch();
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::SaveMethodDrillBatch(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->SaveMethodDrillBatch(DrillBatch);
}
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::RemoveMethodDrillBatch(
UObject* WorldContextObject,
const FString& UserId,
const FString& BatchId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->RemoveMethodDrillBatch(UserId, BatchId);
}
return false;
}
bool UHyperTwistTrainingRuntimeLibrary::RemoveTrainingSessionTemplate(
UObject* WorldContextObject,
const FString& UserId,
@ -548,6 +681,122 @@ FHyperTwistTrainingRunStepResult UHyperTwistTrainingRuntimeLibrary::SubmitTraini
return FHyperTwistTrainingRunStepResult();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::StartMethodDrillRun(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartMethodDrillRun(DrillDefinition, UserId, RunId);
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::StartMethodDrillRunFromDeck(
UObject* WorldContextObject,
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
const FString& RunId,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartMethodDrillRunFromDeck(
Deck,
UserId,
RunId,
DrillMode,
VisibilityPolicy,
bAnswerHiddenUntilReveal
);
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::ResumeMethodDrillRunFromBatch(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->ResumeMethodDrillRunFromBatch(DrillDefinition, DrillBatch, RunId);
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::StartNextActiveMethodDrillCase(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->StartNextActiveMethodDrillCase();
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::RevealActiveMethodDrillCase(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->RevealActiveMethodDrillCase();
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::RepeatActiveMethodDrillCase(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->RepeatActiveMethodDrillCase();
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::AdvanceActiveMethodDrillCase(
UObject* WorldContextObject
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->AdvanceActiveMethodDrillCase();
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingRuntimeLibrary::FinishActiveMethodDrillRun(
UObject* WorldContextObject,
bool bAbortRun
)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
return TrainingSubsystem->FinishActiveMethodDrillRun(bAbortRun);
}
return FHyperTwistTrainingMethodDrillRunState();
}
FHyperTwistTrainingSession UHyperTwistTrainingRuntimeLibrary::CompleteActiveTrainingRun(
UObject* WorldContextObject,
bool bAbortSession
@ -568,3 +817,11 @@ void UHyperTwistTrainingRuntimeLibrary::ClearActiveTrainingRun(UObject* WorldCon
TrainingSubsystem->ClearActiveRun();
}
}
void UHyperTwistTrainingRuntimeLibrary::ClearActiveMethodDrillRun(UObject* WorldContextObject)
{
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
{
TrainingSubsystem->ClearActiveMethodDrillRun();
}
}

View file

@ -10,16 +10,31 @@ bool UHyperTwistTrainingSubsystem::HasActiveRun() const
return bHasActiveRun && ActiveRunState.IsStructurallyValid();
}
bool UHyperTwistTrainingSubsystem::HasActiveMethodDrillRun() const
{
return bHasActiveMethodDrillRun && ActiveMethodDrillRunState.IsStructurallyValid();
}
FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::GetActiveRunState() const
{
return ActiveRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::GetActiveMethodDrillRunState() const
{
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingSessionSummary UHyperTwistTrainingSubsystem::GetActiveRunSummary() const
{
return ActiveRunSummary;
}
FHyperTwistTrainingMethodDrillSummary UHyperTwistTrainingSubsystem::GetActiveMethodDrillSummary() const
{
return ActiveMethodDrillSummary;
}
FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepositoryState() const
{
return TrainingRepositoryState;
@ -93,6 +108,30 @@ TArray<FHyperTwistTrainingSessionTemplate> UHyperTwistTrainingSubsystem::GetTrai
return BuildTrainingSessionTemplatesForUser(UserId, ReferenceUtc);
}
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> UHyperTwistTrainingSubsystem::GetMethodDrillFavoritesForUser(
const FString& UserId,
const FString& MethodSegmentId
) const
{
return UHyperTwistTrainingRepositoryLibrary::ListMethodDrillFavorites(
TrainingRepositoryState,
UserId,
MethodSegmentId
);
}
TArray<FHyperTwistTrainingMethodDrillBatch> UHyperTwistTrainingSubsystem::GetMethodDrillBatchesForUser(
const FString& UserId,
const FString& MethodSegmentId
) const
{
return UHyperTwistTrainingRepositoryLibrary::ListMethodDrillBatches(
TrainingRepositoryState,
UserId,
MethodSegmentId
);
}
TArray<FHyperTwistTrainingCaseRecommendation> UHyperTwistTrainingSubsystem::GetActiveCaseRecommendations() const
{
return ActiveCaseRecommendations;
@ -201,6 +240,94 @@ bool UHyperTwistTrainingSubsystem::SaveTrainingSessionTemplate(
return true;
}
bool UHyperTwistTrainingSubsystem::SaveMethodDrillFavorite(
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
)
{
if (!FavoriteEntry.IsStructurallyValid())
{
return false;
}
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillFavorite(
TrainingRepositoryState,
FavoriteEntry
);
RefreshRepositoryViews();
return true;
}
bool UHyperTwistTrainingSubsystem::RemoveMethodDrillFavorite(
const FString& UserId,
const FString& DrillId,
const FString& CaseId
)
{
if (UserId.IsEmpty() || DrillId.IsEmpty() || CaseId.IsEmpty())
{
return false;
}
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RemoveMethodDrillFavorite(
TrainingRepositoryState,
UserId,
DrillId,
CaseId
);
RefreshRepositoryViews();
return true;
}
bool UHyperTwistTrainingSubsystem::TryGetStoredMethodDrillBatch(
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
) const
{
return UHyperTwistTrainingRepositoryLibrary::TryGetMethodDrillBatch(
TrainingRepositoryState,
UserId,
BatchId,
OutBatch
);
}
bool UHyperTwistTrainingSubsystem::SaveMethodDrillBatch(
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
)
{
if (!DrillBatch.IsStructurallyValid())
{
return false;
}
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::UpsertMethodDrillBatch(
TrainingRepositoryState,
DrillBatch
);
RefreshRepositoryViews();
return true;
}
bool UHyperTwistTrainingSubsystem::RemoveMethodDrillBatch(
const FString& UserId,
const FString& BatchId
)
{
if (UserId.IsEmpty() || BatchId.IsEmpty())
{
return false;
}
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RemoveMethodDrillBatch(
TrainingRepositoryState,
UserId,
BatchId
);
RefreshRepositoryViews();
return true;
}
bool UHyperTwistTrainingSubsystem::RemoveTrainingSessionTemplate(
const FString& UserId,
const FString& TemplateId
@ -502,6 +629,118 @@ FHyperTwistTrainingRunState UHyperTwistTrainingSubsystem::StartSampleClassicTrai
return StartTrainingRunFromDeck(DefaultDeck, UserId, SessionId, Mode);
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::StartMethodDrillRun(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
)
{
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::StartMethodDrillRun(DrillDefinition, UserId, RunId);
bHasActiveMethodDrillRun = ActiveMethodDrillRunState.IsStructurallyValid();
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::StartMethodDrillRunFromDeck(
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
const FString& RunId,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal
)
{
const FHyperTwistTrainingMethodDrillDefinition DrillDefinition =
UHyperTwistTrainingLibrary::BuildMethodDrillDefinitionFromDeck(
Deck,
DrillMode,
VisibilityPolicy,
bAnswerHiddenUntilReveal,
EHyperTwistTrainingDrillEntrySource::Catalog
);
return StartMethodDrillRun(DrillDefinition, UserId, RunId);
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::ResumeMethodDrillRunFromBatch(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
)
{
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::ResumeMethodDrillRunFromBatch(
DrillDefinition,
DrillBatch,
RunId
);
bHasActiveMethodDrillRun = ActiveMethodDrillRunState.IsStructurallyValid();
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::StartNextActiveMethodDrillCase()
{
if (!HasActiveMethodDrillRun())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::StartNextMethodDrillCase(ActiveMethodDrillRunState);
bHasActiveMethodDrillRun = ActiveMethodDrillRunState.IsStructurallyValid();
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::RevealActiveMethodDrillCase()
{
if (!HasActiveMethodDrillRun())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::RevealMethodDrillCase(ActiveMethodDrillRunState);
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::RepeatActiveMethodDrillCase()
{
if (!HasActiveMethodDrillRun())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::RepeatMethodDrillCase(ActiveMethodDrillRunState);
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::AdvanceActiveMethodDrillCase()
{
if (!HasActiveMethodDrillRun())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::AdvanceMethodDrillCase(ActiveMethodDrillRunState);
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingMethodDrillRunState UHyperTwistTrainingSubsystem::FinishActiveMethodDrillRun(bool bAbortRun)
{
if (!HasActiveMethodDrillRun())
{
return FHyperTwistTrainingMethodDrillRunState();
}
ActiveMethodDrillRunState = UHyperTwistTrainingLibrary::FinishMethodDrillRun(
ActiveMethodDrillRunState,
bAbortRun
);
RefreshMethodDrillSummary();
return ActiveMethodDrillRunState;
}
FHyperTwistTrainingRunStepResult UHyperTwistTrainingSubsystem::SubmitTrainingAttempt(
const FHyperTwistTrainingAttempt& Attempt,
int32 TimeMs
@ -595,6 +834,13 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun()
RefreshRepositoryViews();
}
void UHyperTwistTrainingSubsystem::ClearActiveMethodDrillRun()
{
ActiveMethodDrillRunState = FHyperTwistTrainingMethodDrillRunState();
ActiveMethodDrillSummary = FHyperTwistTrainingMethodDrillSummary();
bHasActiveMethodDrillRun = false;
}
void UHyperTwistTrainingSubsystem::RefreshSummary()
{
if (!ActiveRunState.Session.IsStructurallyValid())
@ -609,6 +855,19 @@ void UHyperTwistTrainingSubsystem::RefreshSummary()
);
}
void UHyperTwistTrainingSubsystem::RefreshMethodDrillSummary()
{
if (!ActiveMethodDrillRunState.IsStructurallyValid())
{
ActiveMethodDrillSummary = FHyperTwistTrainingMethodDrillSummary();
return;
}
ActiveMethodDrillSummary = UHyperTwistTrainingLibrary::DeriveMethodDrillSummary(
ActiveMethodDrillRunState
);
}
void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
{
ActiveRepositoryIntegrityReport = UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(

View file

@ -65,6 +65,21 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingRunState MakeSampleTrainingRunState();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillDefinition MakeSampleMethodDrillDefinition();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState MakeSampleMethodDrillRunState();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillSummary MakeSampleMethodDrillSummary();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillFavoriteEntry MakeSampleMethodDrillFavoriteEntry();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillBatch MakeSampleMethodDrillBatch();
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
static FHyperTwistTrainingRunStepResult MakeSampleTrainingRunStepResult();
@ -161,6 +176,26 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeContentPackToJson(const FHyperTwistContentPack& ContentPack);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeMethodDrillDefinitionToJson(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeMethodDrillRunStateToJson(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeMethodDrillFavoriteEntryToJson(
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeMethodDrillBatchToJson(
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Serialization")
static FString SerializeTrainingRepositoryStateToJson(const FHyperTwistTrainingRepositoryState& RepositoryState);
@ -176,6 +211,30 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeContentPackFromJson(const FString& Json, FHyperTwistContentPack& OutContentPack);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeMethodDrillDefinitionFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillDefinition& OutDrillDefinition
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeMethodDrillRunStateFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillRunState& OutRunState
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeMethodDrillFavoriteEntryFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillFavoriteEntry& OutFavoriteEntry
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeMethodDrillBatchFromJson(
const FString& Json,
FHyperTwistTrainingMethodDrillBatch& OutDrillBatch
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Serialization")
static bool DeserializeTrainingRepositoryStateFromJson(const FString& Json, FHyperTwistTrainingRepositoryState& OutRepositoryState);

View file

@ -146,4 +146,66 @@ public:
const FHyperTwistTrainingAttempt& Attempt,
int32 TimeMs
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillDefinition BuildMethodDrillDefinitionFromDeck(
const FHyperTwistTrainingDeck& Deck,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal,
EHyperTwistTrainingDrillEntrySource EntrySource
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState StartMethodDrillRun(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState ResumeMethodDrillRunFromBatch(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState StartNextMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState RevealMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState RepeatMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState AdvanceMethodDrillCase(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillRunState FinishMethodDrillRun(
const FHyperTwistTrainingMethodDrillRunState& RunState,
bool bAbortRun
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillSummary DeriveMethodDrillSummary(
const FHyperTwistTrainingMethodDrillRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
static FHyperTwistTrainingMethodDrillBatch BuildMethodDrillBatch(
const FHyperTwistTrainingMethodDrillRunState& RunState,
const FString& BatchId,
const FString& Title,
bool bPinnedFavoritesOnly
);
};

View file

@ -40,6 +40,63 @@ public:
const FHyperTwistTrainingRunState& RunState
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingMethodDrillFavoriteEntry MakeMethodDrillFavoriteEntry(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& CaseId,
const FString& AddedAtUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> ListMethodDrillFavorites(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& MethodSegmentId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingRepositoryState UpsertMethodDrillFavorite(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingRepositoryState RemoveMethodDrillFavorite(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& DrillId,
const FString& CaseId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static TArray<FHyperTwistTrainingMethodDrillBatch> ListMethodDrillBatches(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& MethodSegmentId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Repository|Drill")
static bool TryGetMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingRepositoryState UpsertMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository|Drill")
static FHyperTwistTrainingRepositoryState RemoveMethodDrillBatch(
const FHyperTwistTrainingRepositoryState& RepositoryState,
const FString& UserId,
const FString& BatchId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
static FHyperTwistTrainingRepositoryState UpsertRunRecord(
const FHyperTwistTrainingRepositoryState& RepositoryState,

View file

@ -19,12 +19,21 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool HasActiveTrainingRun(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool HasActiveMethodDrillRun(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRunState GetActiveTrainingRunState(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState GetActiveMethodDrillRunState(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingSessionSummary GetActiveTrainingRunSummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillSummary GetActiveMethodDrillSummary(UObject* WorldContextObject);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingRepositoryState GetTrainingRepositoryState(UObject* WorldContextObject);
@ -73,6 +82,20 @@ public:
const FString& ReferenceUtc
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> GetMethodDrillFavoritesForUser(
UObject* WorldContextObject,
const FString& UserId,
const FString& MethodSegmentId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingMethodDrillBatch> GetMethodDrillBatchesForUser(
UObject* WorldContextObject,
const FString& UserId,
const FString& MethodSegmentId
);
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static TArray<FHyperTwistTrainingCaseRecommendation> GetActiveTrainingCaseRecommendations(UObject* WorldContextObject);
@ -130,6 +153,41 @@ public:
const FHyperTwistTrainingSessionTemplate& SessionTemplate
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool SaveMethodDrillFavorite(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool RemoveMethodDrillFavorite(
UObject* WorldContextObject,
const FString& UserId,
const FString& DrillId,
const FString& CaseId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool TryGetStoredMethodDrillBatch(
UObject* WorldContextObject,
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool SaveMethodDrillBatch(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static bool RemoveMethodDrillBatch(
UObject* WorldContextObject,
const FString& UserId,
const FString& BatchId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static bool RemoveTrainingSessionTemplate(
UObject* WorldContextObject,
@ -234,6 +292,54 @@ public:
int32 TimeMs
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState StartMethodDrillRun(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState StartMethodDrillRunFromDeck(
UObject* WorldContextObject,
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
const FString& RunId,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState ResumeMethodDrillRunFromBatch(
UObject* WorldContextObject,
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState StartNextActiveMethodDrillCase(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState RevealActiveMethodDrillCase(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState RepeatActiveMethodDrillCase(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState AdvanceActiveMethodDrillCase(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingMethodDrillRunState FinishActiveMethodDrillRun(
UObject* WorldContextObject,
bool bAbortRun
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill", meta = (WorldContext = "WorldContextObject"))
static void ClearActiveMethodDrillRun(UObject* WorldContextObject);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
static FHyperTwistTrainingSession CompleteActiveTrainingRun(
UObject* WorldContextObject,

View file

@ -14,12 +14,21 @@ public:
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
bool HasActiveRun() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
bool HasActiveMethodDrillRun() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingRunState GetActiveRunState() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState GetActiveMethodDrillRunState() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingSessionSummary GetActiveRunSummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillSummary GetActiveMethodDrillSummary() const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
@ -65,6 +74,18 @@ public:
const FString& ReferenceUtc
) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> GetMethodDrillFavoritesForUser(
const FString& UserId,
const FString& MethodSegmentId
) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Drill")
TArray<FHyperTwistTrainingMethodDrillBatch> GetMethodDrillBatchesForUser(
const FString& UserId,
const FString& MethodSegmentId
) const;
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
TArray<FHyperTwistTrainingCaseRecommendation> GetActiveCaseRecommendations() const;
@ -114,6 +135,32 @@ public:
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool SaveTrainingSessionTemplate(const FHyperTwistTrainingSessionTemplate& SessionTemplate);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
bool SaveMethodDrillFavorite(const FHyperTwistTrainingMethodDrillFavoriteEntry& FavoriteEntry);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
bool RemoveMethodDrillFavorite(
const FString& UserId,
const FString& DrillId,
const FString& CaseId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
bool TryGetStoredMethodDrillBatch(
const FString& UserId,
const FString& BatchId,
FHyperTwistTrainingMethodDrillBatch& OutBatch
) const;
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
bool SaveMethodDrillBatch(const FHyperTwistTrainingMethodDrillBatch& DrillBatch);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
bool RemoveMethodDrillBatch(
const FString& UserId,
const FString& BatchId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
bool RemoveTrainingSessionTemplate(
const FString& UserId,
@ -190,6 +237,48 @@ public:
EHyperTwistTrainingDeliveryMode Mode
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState StartMethodDrillRun(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FString& UserId,
const FString& RunId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState StartMethodDrillRunFromDeck(
const FHyperTwistTrainingDeck& Deck,
const FString& UserId,
const FString& RunId,
EHyperTwistTrainingDrillModeKind DrillMode,
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy,
bool bAnswerHiddenUntilReveal
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState ResumeMethodDrillRunFromBatch(
const FHyperTwistTrainingMethodDrillDefinition& DrillDefinition,
const FHyperTwistTrainingMethodDrillBatch& DrillBatch,
const FString& RunId
);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState StartNextActiveMethodDrillCase();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState RevealActiveMethodDrillCase();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState RepeatActiveMethodDrillCase();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState AdvanceActiveMethodDrillCase();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
FHyperTwistTrainingMethodDrillRunState FinishActiveMethodDrillRun(bool bAbortRun);
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training|Drill")
void ClearActiveMethodDrillRun();
UFUNCTION(BlueprintCallable, Category = "HyperTwist|Training")
FHyperTwistTrainingRunStepResult SubmitTrainingAttempt(
const FHyperTwistTrainingAttempt& Attempt,
@ -204,6 +293,7 @@ public:
private:
void RefreshSummary();
void RefreshMethodDrillSummary();
void RefreshRepositoryViews();
TArray<FHyperTwistTrainingSessionTemplate> BuildTrainingSessionTemplatesForUser(
const FString& UserId,
@ -216,6 +306,12 @@ private:
UPROPERTY()
FHyperTwistTrainingSessionSummary ActiveRunSummary;
UPROPERTY()
FHyperTwistTrainingMethodDrillRunState ActiveMethodDrillRunState;
UPROPERTY()
FHyperTwistTrainingMethodDrillSummary ActiveMethodDrillSummary;
UPROPERTY()
FHyperTwistTrainingRepositoryState TrainingRepositoryState;
@ -269,4 +365,7 @@ private:
UPROPERTY()
bool bHasActiveRun = false;
UPROPERTY()
bool bHasActiveMethodDrillRun = false;
};

View file

@ -102,6 +102,43 @@ enum class EHyperTwistTrainingSourceMode : uint8
Benchmark UMETA(DisplayName = "Benchmark")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingDrillModeKind : uint8
{
Recognition UMETA(DisplayName = "Recognition"),
Recall UMETA(DisplayName = "Recall"),
Execution UMETA(DisplayName = "Execution"),
Reconstruction UMETA(DisplayName = "Reconstruction")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingDrillVisibilityPolicy : uint8
{
FullInformation UMETA(DisplayName = "Full Information"),
PromptOnly UMETA(DisplayName = "Prompt Only"),
MaskedState UMETA(DisplayName = "Masked State"),
PartialState UMETA(DisplayName = "Partial State")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingDrillLifecycleState : uint8
{
Ready UMETA(DisplayName = "Ready"),
Active UMETA(DisplayName = "Active"),
Revealed UMETA(DisplayName = "Revealed"),
Completed UMETA(DisplayName = "Completed"),
Aborted UMETA(DisplayName = "Aborted")
};
UENUM(BlueprintType)
enum class EHyperTwistTrainingDrillEntrySource : uint8
{
Catalog UMETA(DisplayName = "Catalog"),
Favorites UMETA(DisplayName = "Favorites"),
QueuedBatch UMETA(DisplayName = "Queued Batch"),
Generated UMETA(DisplayName = "Generated")
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingSourceAttribution
{
@ -1825,6 +1862,345 @@ struct FHyperTwistTrainingMethodSegment
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillDefinition
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Title;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StageId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StageLabel;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingMethodDescriptor MethodDescriptor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingDeck SourceDeck;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> FocusCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillModeKind DrillMode = EHyperTwistTrainingDrillModeKind::Recognition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy =
EHyperTwistTrainingDrillVisibilityPolicy::FullInformation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillEntrySource EntrySource = EHyperTwistTrainingDrillEntrySource::Catalog;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAnswerHiddenUntilReveal = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAllowImmediateRepeat = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> Tags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingSourceAttribution SourceAttribution;
bool IsStructurallyValid() const
{
return !DrillId.IsEmpty()
&& !Title.IsEmpty()
&& MethodDescriptor.IsStructurallyValid()
&& SourceDeck.IsStructurallyValid();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillResponseRecord
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ResponseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillRunId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString PresentedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RespondedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillModeKind DrillMode = EHyperTwistTrainingDrillModeKind::Recognition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy =
EHyperTwistTrainingDrillVisibilityPolicy::FullInformation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAnswerWasRevealed = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bMarkedCorrect = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bWasRepeat = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ElapsedMs = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString ResponseText;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Notes;
bool IsStructurallyValid() const
{
return !ResponseId.IsEmpty() && !DrillRunId.IsEmpty() && !CaseId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillRunState
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingMethodDrillDefinition DrillDefinition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RunId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceBatchId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillLifecycleState LifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString StartedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString EndedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastUpdatedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingCase CurrentCase;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAnswerVisible = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> PendingCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> CompletedCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingMethodDrillResponseRecord> Responses;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevealCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RepeatCount = 0;
bool IsStructurallyValid() const
{
const bool bNeedsCurrentCase =
LifecycleState == EHyperTwistTrainingDrillLifecycleState::Active
|| LifecycleState == EHyperTwistTrainingDrillLifecycleState::Revealed;
const bool bHasValidCurrentCase = CurrentCase.CaseId.IsEmpty()
? !bNeedsCurrentCase
: CurrentCase.IsStructurallyValid();
return DrillDefinition.IsStructurallyValid()
&& !RunId.IsEmpty()
&& !UserId.IsEmpty()
&& bHasValidCurrentCase;
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillSummary
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString RunId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillLifecycleState LifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 PendingCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 CompletedCaseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 ResponseCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevealCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RepeatCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAnswerHiddenUntilReveal = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CurrentCaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastUpdatedAtUtc;
bool IsStructurallyValid() const
{
return !RunId.IsEmpty() && !DrillId.IsEmpty() && !UserId.IsEmpty();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillFavoriteEntry
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingMethodDescriptor MethodDescriptor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString AddedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> Tags;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Note;
bool IsStructurallyValid() const
{
return !UserId.IsEmpty()
&& !DrillId.IsEmpty()
&& !CaseId.IsEmpty()
&& !SourceDeckId.IsEmpty()
&& MethodDescriptor.IsStructurallyValid();
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingMethodDrillBatch
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString UserId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString BatchId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString Title;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString DrillId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString SourceDeckId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FHyperTwistTrainingMethodDescriptor MethodDescriptor;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillModeKind DrillMode = EHyperTwistTrainingDrillModeKind::Recognition;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillVisibilityPolicy VisibilityPolicy =
EHyperTwistTrainingDrillVisibilityPolicy::FullInformation;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bAnswerHiddenUntilReveal = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
EHyperTwistTrainingDrillLifecycleState SavedLifecycleState = EHyperTwistTrainingDrillLifecycleState::Ready;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> PendingCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> CompletedCaseIds;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CurrentCaseId;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingMethodDrillResponseRecord> Responses;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RevealCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 RepeatCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString CreatedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
FString LastUpdatedAtUtc;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
bool bPinnedFavoritesOnly = false;
bool IsStructurallyValid() const
{
return !UserId.IsEmpty()
&& !BatchId.IsEmpty()
&& !DrillId.IsEmpty()
&& !SourceDeckId.IsEmpty()
&& MethodDescriptor.IsStructurallyValid()
&& (!CurrentCaseId.IsEmpty() || PendingCaseIds.Num() > 0 || CompletedCaseIds.Num() > 0);
}
};
USTRUCT(BlueprintType)
struct FHyperTwistTrainingLearnerPreset
{
@ -2279,6 +2655,12 @@ struct FHyperTwistTrainingRepositoryState
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingSessionTemplate> StoredSessionTemplates;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingMethodDrillFavoriteEntry> StoredMethodDrillFavorites;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FHyperTwistTrainingMethodDrillBatch> StoredMethodDrillBatches;
};
USTRUCT(BlueprintType)
@ -2322,6 +2704,24 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DuplicateStoredSessionTemplateCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalMethodDrillFavoriteCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 InvalidMethodDrillFavoriteCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DuplicateMethodDrillFavoriteCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 TotalMethodDrillBatchCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 InvalidMethodDrillBatchCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
int32 DuplicateMethodDrillBatchCount = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
TArray<FString> RepairActions;
@ -2334,6 +2734,10 @@ struct FHyperTwistTrainingRepositoryIntegrityReport
|| InvalidReviewPlanCount > 0
|| DuplicateReviewPlanCount > 0
|| InvalidStoredSessionTemplateCount > 0
|| DuplicateStoredSessionTemplateCount > 0;
|| DuplicateStoredSessionTemplateCount > 0
|| InvalidMethodDrillFavoriteCount > 0
|| DuplicateMethodDrillFavoriteCount > 0
|| InvalidMethodDrillBatchCount > 0
|| DuplicateMethodDrillBatchCount > 0;
}
};