Harden training repository integrity flows
This commit is contained in:
parent
e0a3057091
commit
6a212cfef8
10 changed files with 774 additions and 45 deletions
|
|
@ -437,6 +437,40 @@ FHyperTwistTrainingRepositoryState UHyperTwistContractLibrary::MakeSampleTrainin
|
|||
return RepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistContractLibrary::MakeSampleTrainingRepositoryIntegrityReport()
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState RepositoryState = MakeSampleTrainingRepositoryState();
|
||||
if (RepositoryState.RunRecords.Num() > 0)
|
||||
{
|
||||
RepositoryState.RunRecords.Add(RepositoryState.RunRecords[0]);
|
||||
}
|
||||
RepositoryState.RunRecords.Add(FHyperTwistTrainingRunRecord());
|
||||
|
||||
if (RepositoryState.LearnerStates.Num() > 0)
|
||||
{
|
||||
RepositoryState.LearnerStates.Add(RepositoryState.LearnerStates[0]);
|
||||
}
|
||||
RepositoryState.LearnerStates.Add(FHyperTwistTrainingCaseLearnerState());
|
||||
|
||||
const FHyperTwistTrainingReviewPlanState ReviewPlanState = MakeSampleTrainingReviewPlanState();
|
||||
if (ReviewPlanState.IsStructurallyValid())
|
||||
{
|
||||
RepositoryState.ReviewPlans.Add(ReviewPlanState);
|
||||
RepositoryState.ReviewPlans.Add(ReviewPlanState);
|
||||
}
|
||||
RepositoryState.ReviewPlans.Add(FHyperTwistTrainingReviewPlanState());
|
||||
|
||||
const TArray<FHyperTwistTrainingSessionTemplate> SessionTemplates = MakeSampleTrainingSessionTemplates();
|
||||
if (SessionTemplates.Num() > 0)
|
||||
{
|
||||
RepositoryState.StoredSessionTemplates.Add(SessionTemplates[0]);
|
||||
RepositoryState.StoredSessionTemplates.Add(SessionTemplates[0]);
|
||||
}
|
||||
RepositoryState.StoredSessionTemplates.Add(FHyperTwistTrainingSessionTemplate());
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(RepositoryState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistContractLibrary::MakeSampleTrainingDeckHistorySummary()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
|
|
@ -501,6 +535,25 @@ FHyperTwistTrainingTimerExportPacket UHyperTwistContractLibrary::MakeSampleTrain
|
|||
);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimerExportIntegrityReport UHyperTwistContractLibrary::MakeSampleTrainingTimerExportIntegrityReport()
|
||||
{
|
||||
FHyperTwistTrainingTimerExportPacket TimerExportPacket = MakeSampleTrainingTimerExportPacket();
|
||||
TimerExportPacket.FormatVersion.Reset();
|
||||
TimerExportPacket.ExportedAtUtc.Reset();
|
||||
if (TimerExportPacket.Entries.Num() > 0)
|
||||
{
|
||||
TimerExportPacket.Entries.Add(TimerExportPacket.Entries[0]);
|
||||
|
||||
FHyperTwistTrainingTimerExportEntry MismatchedEntry = TimerExportPacket.Entries[0];
|
||||
MismatchedEntry.AttemptId += TEXT("-mismatch");
|
||||
MismatchedEntry.UserId = TEXT("other-user");
|
||||
TimerExportPacket.Entries.Add(MismatchedEntry);
|
||||
}
|
||||
TimerExportPacket.Entries.Add(FHyperTwistTrainingTimerExportEntry());
|
||||
|
||||
return UHyperTwistTrainingRepositoryLibrary::DeriveTimerExportIntegrityReport(TimerExportPacket);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseScopedStats UHyperTwistContractLibrary::MakeSampleTrainingCaseScopedStats()
|
||||
{
|
||||
const FHyperTwistTrainingRunStepResult StepResult = MakeSampleTrainingRunStepResult();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "HyperTwistTraining/HyperTwistTrainingPersistenceLibrary.h"
|
||||
|
||||
#include "HyperTwistBootstrap/HyperTwistContractLibrary.h"
|
||||
#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"
|
||||
#include "HAL/FileManager.h"
|
||||
#include "Misc/FileHelper.h"
|
||||
#include "Misc/Paths.h"
|
||||
|
|
@ -93,7 +94,11 @@ bool UHyperTwistTrainingPersistenceLibrary::SaveTrainingRepositoryStateToFile(
|
|||
IFileManager::Get().MakeDirectory(*Directory, true);
|
||||
}
|
||||
|
||||
const FString Json = UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(RepositoryState);
|
||||
const FHyperTwistTrainingRepositoryState RepairedRepositoryState =
|
||||
UHyperTwistTrainingRepositoryLibrary::RepairRepositoryState(RepositoryState);
|
||||
const FString Json = UHyperTwistContractLibrary::SerializeTrainingRepositoryStateToJson(
|
||||
RepairedRepositoryState
|
||||
);
|
||||
return !Json.IsEmpty() && FFileHelper::SaveStringToFile(Json, *OutResolvedPath);
|
||||
}
|
||||
|
||||
|
|
@ -103,9 +108,11 @@ bool UHyperTwistTrainingPersistenceLibrary::SaveTrainingTimerExportPacketToFile(
|
|||
FString& OutResolvedPath
|
||||
)
|
||||
{
|
||||
const FHyperTwistTrainingTimerExportPacket NormalizedPacket =
|
||||
UHyperTwistTrainingRepositoryLibrary::NormalizeTimerExportPacket(TimerExportPacket);
|
||||
OutResolvedPath = ResolveTrainingTimerExportPath(
|
||||
TimerExportPacket.UserId,
|
||||
TimerExportPacket.DeckId,
|
||||
NormalizedPacket.UserId,
|
||||
NormalizedPacket.DeckId,
|
||||
ExportPath
|
||||
);
|
||||
const FString Directory = FPaths::GetPath(OutResolvedPath);
|
||||
|
|
@ -114,7 +121,7 @@ bool UHyperTwistTrainingPersistenceLibrary::SaveTrainingTimerExportPacketToFile(
|
|||
IFileManager::Get().MakeDirectory(*Directory, true);
|
||||
}
|
||||
|
||||
const FString Json = UHyperTwistContractLibrary::SerializeTrainingTimerExportPacketToJson(TimerExportPacket);
|
||||
const FString Json = UHyperTwistContractLibrary::SerializeTrainingTimerExportPacketToJson(NormalizedPacket);
|
||||
return !Json.IsEmpty() && FFileHelper::SaveStringToFile(Json, *OutResolvedPath);
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +140,13 @@ bool UHyperTwistTrainingPersistenceLibrary::LoadTrainingRepositoryStateFromFile(
|
|||
return false;
|
||||
}
|
||||
|
||||
return UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(Json, OutRepositoryState);
|
||||
if (!UHyperTwistContractLibrary::DeserializeTrainingRepositoryStateFromJson(Json, OutRepositoryState))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutRepositoryState = UHyperTwistTrainingRepositoryLibrary::RepairRepositoryState(OutRepositoryState);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UHyperTwistTrainingPersistenceLibrary::LoadTrainingTimerExportPacketFromFile(
|
||||
|
|
@ -153,5 +166,13 @@ bool UHyperTwistTrainingPersistenceLibrary::LoadTrainingTimerExportPacketFromFil
|
|||
return false;
|
||||
}
|
||||
|
||||
return UHyperTwistContractLibrary::DeserializeTrainingTimerExportPacketFromJson(Json, OutTimerExportPacket);
|
||||
if (!UHyperTwistContractLibrary::DeserializeTrainingTimerExportPacketFromJson(Json, OutTimerExportPacket))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OutTimerExportPacket = UHyperTwistTrainingRepositoryLibrary::NormalizeTimerExportPacket(
|
||||
OutTimerExportPacket
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,6 +277,11 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return UserId + CaseKeySeparator + DeckId + CaseKeySeparator + CaseId;
|
||||
}
|
||||
|
||||
FString MakeTemplateKey(const FString& UserId, const FString& TemplateId)
|
||||
{
|
||||
return UserId + CaseKeySeparator + TemplateId;
|
||||
}
|
||||
|
||||
bool SplitCaseKey(const FString& CaseKey, FString& OutUserId, FString& OutDeckId, FString& OutCaseId)
|
||||
{
|
||||
TArray<FString> Parts;
|
||||
|
|
@ -295,6 +300,21 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return true;
|
||||
}
|
||||
|
||||
void AppendRepairAction(TArray<FString>& RepairActions, const FString& Action)
|
||||
{
|
||||
if (!Action.IsEmpty())
|
||||
{
|
||||
RepairActions.AddUnique(Action);
|
||||
}
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState DeriveLearnerStateFromReviewHistory(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
const FString& CaseId,
|
||||
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ReviewHistory
|
||||
);
|
||||
|
||||
FHyperTwistTrainingReviewHistoryEntry MakeReviewHistoryEntryFromAttemptHistory(
|
||||
const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry,
|
||||
const TArray<FHyperTwistTrainingReviewHistoryEntry>& ExistingReviewHistory
|
||||
|
|
@ -382,6 +402,49 @@ namespace HyperTwistTrainingRepositoryLibraryInternal
|
|||
return ReviewHistoryByCase;
|
||||
}
|
||||
|
||||
TArray<FHyperTwistTrainingCaseLearnerState> DeriveLearnerStatesFromRunRecords(
|
||||
const TArray<FHyperTwistTrainingRunRecord>& RunRecords
|
||||
)
|
||||
{
|
||||
TArray<FHyperTwistTrainingCaseLearnerState> LearnerStates;
|
||||
const TMap<FString, TArray<FHyperTwistTrainingReviewHistoryEntry>> ReviewHistoryByCase =
|
||||
BuildReviewHistoryByCase(RunRecords);
|
||||
for (const TPair<FString, TArray<FHyperTwistTrainingReviewHistoryEntry>>& Pair : ReviewHistoryByCase)
|
||||
{
|
||||
FString UserId;
|
||||
FString DeckId;
|
||||
FString CaseId;
|
||||
if (!SplitCaseKey(Pair.Key, UserId, DeckId, CaseId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState LearnerState =
|
||||
DeriveLearnerStateFromReviewHistory(UserId, DeckId, CaseId, Pair.Value);
|
||||
if (LearnerState.IsStructurallyValid())
|
||||
{
|
||||
LearnerStates.Add(LearnerState);
|
||||
}
|
||||
}
|
||||
|
||||
LearnerStates.Sort([](
|
||||
const FHyperTwistTrainingCaseLearnerState& Left,
|
||||
const FHyperTwistTrainingCaseLearnerState& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
if (Left.DeckId != Right.DeckId)
|
||||
{
|
||||
return Left.DeckId < Right.DeckId;
|
||||
}
|
||||
return Left.CaseId < Right.CaseId;
|
||||
});
|
||||
return LearnerStates;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseLearnerState DeriveLearnerStateFromReviewHistory(
|
||||
const FString& UserId,
|
||||
const FString& DeckId,
|
||||
|
|
@ -1812,7 +1875,7 @@ FHyperTwistTrainingTimerExportPacket UHyperTwistTrainingRepositoryLibrary::Expor
|
|||
}
|
||||
}
|
||||
|
||||
return TimerExportPacket;
|
||||
return NormalizeTimerExportPacket(TimerExportPacket);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::ImportTimerResults(
|
||||
|
|
@ -1821,13 +1884,14 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::ImportT
|
|||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
|
||||
if (!TimerExportPacket.IsStructurallyValid())
|
||||
const FHyperTwistTrainingTimerExportPacket NormalizedPacket = NormalizeTimerExportPacket(TimerExportPacket);
|
||||
if (!NormalizedPacket.IsStructurallyValid())
|
||||
{
|
||||
return UpdatedRepositoryState;
|
||||
}
|
||||
|
||||
TMap<FString, TArray<FHyperTwistTrainingTimerExportEntry>> EntriesBySessionId;
|
||||
for (const FHyperTwistTrainingTimerExportEntry& TimerExportEntry : TimerExportPacket.Entries)
|
||||
for (const FHyperTwistTrainingTimerExportEntry& TimerExportEntry : NormalizedPacket.Entries)
|
||||
{
|
||||
if (!TimerExportEntry.IsStructurallyValid())
|
||||
{
|
||||
|
|
@ -1848,15 +1912,470 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::ImportT
|
|||
|
||||
FHyperTwistTrainingRunRecord ImportedRunRecord =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::BuildRunRecordFromTimerExportEntries(
|
||||
TimerExportPacket.UserId,
|
||||
TimerExportPacket.DeckId,
|
||||
NormalizedPacket.UserId,
|
||||
NormalizedPacket.DeckId,
|
||||
SessionId,
|
||||
SessionEntries
|
||||
);
|
||||
UpdatedRepositoryState = UpsertRunRecord(UpdatedRepositoryState, ImportedRunRecord);
|
||||
}
|
||||
|
||||
return UpdatedRepositoryState;
|
||||
return RepairRepositoryState(UpdatedRepositoryState);
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimerExportIntegrityReport UHyperTwistTrainingRepositoryLibrary::DeriveTimerExportIntegrityReport(
|
||||
const FHyperTwistTrainingTimerExportPacket& TimerExportPacket
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingTimerExportIntegrityReport Report;
|
||||
Report.UserId = TimerExportPacket.UserId;
|
||||
Report.DeckId = TimerExportPacket.DeckId;
|
||||
Report.TotalEntryCount = TimerExportPacket.Entries.Num();
|
||||
|
||||
if (TimerExportPacket.FormatVersion.IsEmpty())
|
||||
{
|
||||
Report.MissingPacketMetadataCount += 1;
|
||||
}
|
||||
if (TimerExportPacket.SourceSystem.IsEmpty())
|
||||
{
|
||||
Report.MissingPacketMetadataCount += 1;
|
||||
}
|
||||
if (TimerExportPacket.UserId.IsEmpty())
|
||||
{
|
||||
Report.MissingPacketMetadataCount += 1;
|
||||
}
|
||||
if (TimerExportPacket.DeckId.IsEmpty())
|
||||
{
|
||||
Report.MissingPacketMetadataCount += 1;
|
||||
}
|
||||
if (TimerExportPacket.ExportedAtUtc.IsEmpty())
|
||||
{
|
||||
Report.MissingPacketMetadataCount += 1;
|
||||
}
|
||||
|
||||
TSet<FString> SeenAttemptIds;
|
||||
for (const FHyperTwistTrainingTimerExportEntry& Entry : TimerExportPacket.Entries)
|
||||
{
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidEntryCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SeenAttemptIds.Contains(Entry.AttemptId))
|
||||
{
|
||||
Report.DuplicateEntryCount += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
SeenAttemptIds.Add(Entry.AttemptId);
|
||||
}
|
||||
|
||||
const bool bScopeMismatch =
|
||||
(!TimerExportPacket.UserId.IsEmpty() && Entry.UserId != TimerExportPacket.UserId)
|
||||
|| (!TimerExportPacket.DeckId.IsEmpty() && Entry.DeckId != TimerExportPacket.DeckId);
|
||||
if (bScopeMismatch)
|
||||
{
|
||||
Report.MismatchedScopeEntryCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (Report.MissingPacketMetadataCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("fill-missing-timer-export-metadata")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidEntryCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-timer-export-entries")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateEntryCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("deduplicate-timer-export-attempt-ids")
|
||||
);
|
||||
}
|
||||
if (Report.MismatchedScopeEntryCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-mismatched-timer-export-scope-entries")
|
||||
);
|
||||
}
|
||||
|
||||
return Report;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingTimerExportPacket UHyperTwistTrainingRepositoryLibrary::NormalizeTimerExportPacket(
|
||||
const FHyperTwistTrainingTimerExportPacket& TimerExportPacket
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingTimerExportPacket NormalizedPacket = TimerExportPacket;
|
||||
if (NormalizedPacket.FormatVersion.IsEmpty())
|
||||
{
|
||||
NormalizedPacket.FormatVersion = TEXT("ht-timer-export/v1");
|
||||
}
|
||||
if (NormalizedPacket.SourceSystem.IsEmpty())
|
||||
{
|
||||
NormalizedPacket.SourceSystem = TEXT("HyperTwistTraining");
|
||||
}
|
||||
if (NormalizedPacket.ExportedAtUtc.IsEmpty())
|
||||
{
|
||||
NormalizedPacket.ExportedAtUtc = FDateTime::UtcNow().ToIso8601();
|
||||
}
|
||||
|
||||
FString ScopedUserId = NormalizedPacket.UserId;
|
||||
FString ScopedDeckId = NormalizedPacket.DeckId;
|
||||
if (ScopedUserId.IsEmpty() || ScopedDeckId.IsEmpty())
|
||||
{
|
||||
for (const FHyperTwistTrainingTimerExportEntry& Entry : TimerExportPacket.Entries)
|
||||
{
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ScopedUserId.IsEmpty())
|
||||
{
|
||||
ScopedUserId = Entry.UserId;
|
||||
}
|
||||
if (ScopedDeckId.IsEmpty())
|
||||
{
|
||||
ScopedDeckId = Entry.DeckId;
|
||||
}
|
||||
if (!ScopedUserId.IsEmpty() && !ScopedDeckId.IsEmpty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedPacket.UserId = ScopedUserId;
|
||||
NormalizedPacket.DeckId = ScopedDeckId;
|
||||
NormalizedPacket.Entries.Reset();
|
||||
|
||||
TSet<FString> SeenAttemptIds;
|
||||
for (const FHyperTwistTrainingTimerExportEntry& Entry : TimerExportPacket.Entries)
|
||||
{
|
||||
if (!Entry.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!ScopedUserId.IsEmpty() && Entry.UserId != ScopedUserId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!ScopedDeckId.IsEmpty() && Entry.DeckId != ScopedDeckId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (SeenAttemptIds.Contains(Entry.AttemptId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenAttemptIds.Add(Entry.AttemptId);
|
||||
NormalizedPacket.Entries.Add(Entry);
|
||||
}
|
||||
|
||||
return NormalizedPacket;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryIntegrityReport Report;
|
||||
|
||||
Report.TotalRunRecordCount = RepositoryState.RunRecords.Num();
|
||||
TSet<FString> SeenRunRecordIds;
|
||||
for (const FHyperTwistTrainingRunRecord& RunRecord : RepositoryState.RunRecords)
|
||||
{
|
||||
if (!RunRecord.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidRunRecordCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (SeenRunRecordIds.Contains(RunRecord.Session.TrainingSessionId))
|
||||
{
|
||||
Report.DuplicateRunRecordCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenRunRecordIds.Add(RunRecord.Session.TrainingSessionId);
|
||||
}
|
||||
|
||||
Report.TotalLearnerStateCount = RepositoryState.LearnerStates.Num();
|
||||
TSet<FString> SeenLearnerStateKeys;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : RepositoryState.LearnerStates)
|
||||
{
|
||||
if (!LearnerState.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidLearnerStateCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString LearnerStateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
);
|
||||
if (SeenLearnerStateKeys.Contains(LearnerStateKey))
|
||||
{
|
||||
Report.DuplicateLearnerStateCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenLearnerStateKeys.Add(LearnerStateKey);
|
||||
}
|
||||
|
||||
Report.TotalReviewPlanCount = RepositoryState.ReviewPlans.Num();
|
||||
TSet<FString> SeenReviewPlanIds;
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans)
|
||||
{
|
||||
if (!ReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidReviewPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
if (SeenReviewPlanIds.Contains(ReviewPlan.PlanId))
|
||||
{
|
||||
Report.DuplicateReviewPlanCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenReviewPlanIds.Add(ReviewPlan.PlanId);
|
||||
}
|
||||
|
||||
Report.TotalStoredSessionTemplateCount = RepositoryState.StoredSessionTemplates.Num();
|
||||
TSet<FString> SeenStoredTemplateKeys;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates)
|
||||
{
|
||||
if (!SessionTemplate.IsStructurallyValid())
|
||||
{
|
||||
Report.InvalidStoredSessionTemplateCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString TemplateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeTemplateKey(
|
||||
SessionTemplate.UserId,
|
||||
SessionTemplate.TemplateId
|
||||
);
|
||||
if (SeenStoredTemplateKeys.Contains(TemplateKey))
|
||||
{
|
||||
Report.DuplicateStoredSessionTemplateCount += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
SeenStoredTemplateKeys.Add(TemplateKey);
|
||||
}
|
||||
|
||||
if (Report.InvalidRunRecordCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-run-records")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateRunRecordCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("deduplicate-run-records-by-session-id")
|
||||
);
|
||||
}
|
||||
if (Report.TotalRunRecordCount > 0
|
||||
&& (Report.TotalLearnerStateCount == 0
|
||||
|| Report.InvalidLearnerStateCount > 0
|
||||
|| Report.DuplicateLearnerStateCount > 0))
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("rebuild-learner-states-from-run-records")
|
||||
);
|
||||
}
|
||||
else if (Report.InvalidLearnerStateCount > 0 || Report.DuplicateLearnerStateCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("filter-and-deduplicate-learner-states")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidReviewPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-review-plans")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateReviewPlanCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("deduplicate-review-plans-by-plan-id")
|
||||
);
|
||||
}
|
||||
if (Report.InvalidStoredSessionTemplateCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("drop-invalid-stored-session-templates")
|
||||
);
|
||||
}
|
||||
if (Report.DuplicateStoredSessionTemplateCount > 0)
|
||||
{
|
||||
HyperTwistTrainingRepositoryLibraryInternal::AppendRepairAction(
|
||||
Report.RepairActions,
|
||||
TEXT("normalize-and-deduplicate-stored-session-templates")
|
||||
);
|
||||
}
|
||||
|
||||
return Report;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RepairRepositoryState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState
|
||||
)
|
||||
{
|
||||
FHyperTwistTrainingRepositoryState RepairedState;
|
||||
|
||||
TMap<FString, int32> RunRecordIndexBySessionId;
|
||||
for (const FHyperTwistTrainingRunRecord& RunRecord : RepositoryState.RunRecords)
|
||||
{
|
||||
if (!RunRecord.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString& SessionId = RunRecord.Session.TrainingSessionId;
|
||||
if (const int32* ExistingIndex = RunRecordIndexBySessionId.Find(SessionId))
|
||||
{
|
||||
RepairedState.RunRecords[*ExistingIndex] = RunRecord;
|
||||
}
|
||||
else
|
||||
{
|
||||
RunRecordIndexBySessionId.Add(SessionId, RepairedState.RunRecords.Add(RunRecord));
|
||||
}
|
||||
}
|
||||
RepairedState.RunRecords = HyperTwistTrainingRepositoryLibraryInternal::SortRunRecordsByUpdateUtc(
|
||||
RepairedState.RunRecords
|
||||
);
|
||||
|
||||
if (RepairedState.RunRecords.Num() > 0)
|
||||
{
|
||||
RepairedState.LearnerStates = HyperTwistTrainingRepositoryLibraryInternal::DeriveLearnerStatesFromRunRecords(
|
||||
RepairedState.RunRecords
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
TMap<FString, int32> LearnerStateIndexByKey;
|
||||
for (const FHyperTwistTrainingCaseLearnerState& LearnerState : RepositoryState.LearnerStates)
|
||||
{
|
||||
if (!LearnerState.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString LearnerStateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeCaseKey(
|
||||
LearnerState.UserId,
|
||||
LearnerState.DeckId,
|
||||
LearnerState.CaseId
|
||||
);
|
||||
if (const int32* ExistingIndex = LearnerStateIndexByKey.Find(LearnerStateKey))
|
||||
{
|
||||
RepairedState.LearnerStates[*ExistingIndex] = LearnerState;
|
||||
}
|
||||
else
|
||||
{
|
||||
LearnerStateIndexByKey.Add(LearnerStateKey, RepairedState.LearnerStates.Add(LearnerState));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TMap<FString, int32> ReviewPlanIndexById;
|
||||
for (const FHyperTwistTrainingReviewPlanState& ReviewPlan : RepositoryState.ReviewPlans)
|
||||
{
|
||||
if (!ReviewPlan.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const int32* ExistingIndex = ReviewPlanIndexById.Find(ReviewPlan.PlanId))
|
||||
{
|
||||
RepairedState.ReviewPlans[*ExistingIndex] = ReviewPlan;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReviewPlanIndexById.Add(ReviewPlan.PlanId, RepairedState.ReviewPlans.Add(ReviewPlan));
|
||||
}
|
||||
}
|
||||
|
||||
TMap<FString, int32> StoredTemplateIndexByKey;
|
||||
for (const FHyperTwistTrainingSessionTemplate& SessionTemplate : RepositoryState.StoredSessionTemplates)
|
||||
{
|
||||
if (!SessionTemplate.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FHyperTwistTrainingSessionTemplate NormalizedTemplate =
|
||||
HyperTwistTrainingRepositoryLibraryInternal::NormalizeStoredTemplate(SessionTemplate);
|
||||
if (!NormalizedTemplate.IsStructurallyValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const FString TemplateKey = HyperTwistTrainingRepositoryLibraryInternal::MakeTemplateKey(
|
||||
NormalizedTemplate.UserId,
|
||||
NormalizedTemplate.TemplateId
|
||||
);
|
||||
if (const int32* ExistingIndex = StoredTemplateIndexByKey.Find(TemplateKey))
|
||||
{
|
||||
RepairedState.StoredSessionTemplates[*ExistingIndex] = NormalizedTemplate;
|
||||
}
|
||||
else
|
||||
{
|
||||
StoredTemplateIndexByKey.Add(
|
||||
TemplateKey,
|
||||
RepairedState.StoredSessionTemplates.Add(NormalizedTemplate)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RepairedState.StoredSessionTemplates.Sort([](
|
||||
const FHyperTwistTrainingSessionTemplate& Left,
|
||||
const FHyperTwistTrainingSessionTemplate& Right
|
||||
)
|
||||
{
|
||||
if (Left.UserId != Right.UserId)
|
||||
{
|
||||
return Left.UserId < Right.UserId;
|
||||
}
|
||||
if (Left.bUserCreated != Right.bUserCreated)
|
||||
{
|
||||
return Left.bUserCreated && !Right.bUserCreated;
|
||||
}
|
||||
|
||||
const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
|
||||
Left.LastUpdatedAtUtc,
|
||||
Right.LastUpdatedAtUtc
|
||||
);
|
||||
if (UtcCompare != 0)
|
||||
{
|
||||
return UtcCompare > 0;
|
||||
}
|
||||
|
||||
return Left.TemplateId < Right.TemplateId;
|
||||
});
|
||||
|
||||
return RepairedState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingCaseScopedStats UHyperTwistTrainingRepositoryLibrary::DeriveCaseScopedStats(
|
||||
|
|
|
|||
|
|
@ -68,6 +68,18 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingRuntimeLibrary::GetTrainin
|
|||
return FHyperTwistTrainingRepositoryState();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingRepositoryIntegrityReport(
|
||||
UObject* WorldContextObject
|
||||
)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
{
|
||||
return TrainingSubsystem->GetActiveRepositoryIntegrityReport();
|
||||
}
|
||||
|
||||
return FHyperTwistTrainingRepositoryIntegrityReport();
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingRuntimeLibrary::GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject)
|
||||
{
|
||||
if (UHyperTwistTrainingSubsystem* TrainingSubsystem = GetTrainingSubsystem(WorldContextObject))
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ FHyperTwistTrainingRepositoryState UHyperTwistTrainingSubsystem::GetTrainingRepo
|
|||
return TrainingRepositoryState;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingRepositoryIntegrityReport UHyperTwistTrainingSubsystem::GetActiveRepositoryIntegrityReport() const
|
||||
{
|
||||
return ActiveRepositoryIntegrityReport;
|
||||
}
|
||||
|
||||
FHyperTwistTrainingDeckHistorySummary UHyperTwistTrainingSubsystem::GetActiveDeckHistorySummary() const
|
||||
{
|
||||
return ActiveDeckHistorySummary;
|
||||
|
|
@ -114,11 +119,13 @@ FHyperTwistTrainingTimerExportPacket UHyperTwistTrainingSubsystem::BuildTraining
|
|||
const FString& ReferenceUtc
|
||||
) const
|
||||
{
|
||||
return UHyperTwistTrainingRepositoryLibrary::ExportTimerResultsForDeck(
|
||||
TrainingRepositoryState,
|
||||
UserId,
|
||||
DeckId,
|
||||
ReferenceUtc
|
||||
return UHyperTwistTrainingRepositoryLibrary::NormalizeTimerExportPacket(
|
||||
UHyperTwistTrainingRepositoryLibrary::ExportTimerResultsForDeck(
|
||||
TrainingRepositoryState,
|
||||
UserId,
|
||||
DeckId,
|
||||
ReferenceUtc
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +196,7 @@ bool UHyperTwistTrainingSubsystem::SaveTrainingSessionTemplate(
|
|||
TrainingRepositoryState,
|
||||
SessionTemplate
|
||||
);
|
||||
if (ActiveRunState.Session.UserId == SessionTemplate.UserId)
|
||||
{
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -212,10 +216,7 @@ bool UHyperTwistTrainingSubsystem::RemoveTrainingSessionTemplate(
|
|||
UserId,
|
||||
TemplateId
|
||||
);
|
||||
if (ActiveRunState.Session.UserId == UserId)
|
||||
{
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -259,19 +260,18 @@ bool UHyperTwistTrainingSubsystem::SaveTrainingTimerExportPacketToDefaultPath(
|
|||
|
||||
bool UHyperTwistTrainingSubsystem::ImportTrainingTimerExportPacket(const FHyperTwistTrainingTimerExportPacket& TimerExportPacket)
|
||||
{
|
||||
if (!TimerExportPacket.IsStructurallyValid())
|
||||
const FHyperTwistTrainingTimerExportPacket NormalizedPacket =
|
||||
UHyperTwistTrainingRepositoryLibrary::NormalizeTimerExportPacket(TimerExportPacket);
|
||||
if (!NormalizedPacket.IsStructurallyValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::ImportTimerResults(
|
||||
TrainingRepositoryState,
|
||||
TimerExportPacket
|
||||
NormalizedPacket
|
||||
);
|
||||
if (ActiveRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
RefreshRepositoryViews();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -590,22 +590,9 @@ void UHyperTwistTrainingSubsystem::ClearActiveRun()
|
|||
{
|
||||
ActiveRunState = FHyperTwistTrainingRunState();
|
||||
ActiveRunSummary = FHyperTwistTrainingSessionSummary();
|
||||
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
|
||||
ActiveTimingTrendSummary = FHyperTwistTrainingTimingTrendSummary();
|
||||
ActivePersonalBestSummary = FHyperTwistTrainingPersonalBestSummary();
|
||||
ActiveDeckScopedStats = FHyperTwistTrainingDeckScopedStats();
|
||||
ActiveCurrentCaseScopedStats = FHyperTwistTrainingCaseScopedStats();
|
||||
ActiveLearnerDeckStateSummary = FHyperTwistTrainingLearnerDeckStateSummary();
|
||||
ActiveLearnerProfile = FHyperTwistTrainingLearnerProfile();
|
||||
ActiveCoachMemorySnapshot = FHyperTwistTrainingCoachMemorySnapshot();
|
||||
ActiveLearnerMethodSegments.Reset();
|
||||
ActiveLearnerPresets.Reset();
|
||||
ActiveTrainingSessionTemplates.Reset();
|
||||
ActiveCaseRecommendations.Reset();
|
||||
ActiveReviewPlanState = FHyperTwistTrainingReviewPlanState();
|
||||
ActiveReviewFlowStatus = FHyperTwistTrainingReviewFlowStatus();
|
||||
ActiveReviewProgramSummary = FHyperTwistTrainingReviewProgramSummary();
|
||||
bHasActiveRun = false;
|
||||
RefreshRepositoryViews();
|
||||
}
|
||||
|
||||
void UHyperTwistTrainingSubsystem::RefreshSummary()
|
||||
|
|
@ -624,6 +611,11 @@ void UHyperTwistTrainingSubsystem::RefreshSummary()
|
|||
|
||||
void UHyperTwistTrainingSubsystem::RefreshRepositoryViews()
|
||||
{
|
||||
ActiveRepositoryIntegrityReport = UHyperTwistTrainingRepositoryLibrary::DeriveRepositoryIntegrityReport(
|
||||
TrainingRepositoryState
|
||||
);
|
||||
TrainingRepositoryState = UHyperTwistTrainingRepositoryLibrary::RepairRepositoryState(TrainingRepositoryState);
|
||||
|
||||
if (!ActiveRunState.Session.IsStructurallyValid())
|
||||
{
|
||||
ActiveDeckHistorySummary = FHyperTwistTrainingDeckHistorySummary();
|
||||
|
|
|
|||
|
|
@ -80,6 +80,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRepositoryState MakeSampleTrainingRepositoryState();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingRepositoryIntegrityReport MakeSampleTrainingRepositoryIntegrityReport();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingDeckHistorySummary MakeSampleTrainingDeckHistorySummary();
|
||||
|
||||
|
|
@ -92,6 +95,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingTimerExportPacket MakeSampleTrainingTimerExportPacket();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingTimerExportIntegrityReport MakeSampleTrainingTimerExportIntegrityReport();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
static FHyperTwistTrainingCaseScopedStats MakeSampleTrainingCaseScopedStats();
|
||||
|
||||
|
|
|
|||
|
|
@ -127,6 +127,26 @@ public:
|
|||
const FHyperTwistTrainingTimerExportPacket& TimerExportPacket
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingTimerExportIntegrityReport DeriveTimerExportIntegrityReport(
|
||||
const FHyperTwistTrainingTimerExportPacket& TimerExportPacket
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingTimerExportPacket NormalizeTimerExportPacket(
|
||||
const FHyperTwistTrainingTimerExportPacket& TimerExportPacket
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRepositoryIntegrityReport DeriveRepositoryIntegrityReport(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingRepositoryState RepairRepositoryState(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training|Repository")
|
||||
static FHyperTwistTrainingCaseScopedStats DeriveCaseScopedStats(
|
||||
const FHyperTwistTrainingRepositoryState& RepositoryState,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRepositoryState GetTrainingRepositoryState(UObject* WorldContextObject);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingRepositoryIntegrityReport GetActiveTrainingRepositoryIntegrityReport(
|
||||
UObject* WorldContextObject
|
||||
);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training", meta = (WorldContext = "WorldContextObject"))
|
||||
static FHyperTwistTrainingDeckHistorySummary GetActiveTrainingDeckHistorySummary(UObject* WorldContextObject);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ public:
|
|||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRepositoryState GetTrainingRepositoryState() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingRepositoryIntegrityReport GetActiveRepositoryIntegrityReport() const;
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "HyperTwist|Training")
|
||||
FHyperTwistTrainingDeckHistorySummary GetActiveDeckHistorySummary() const;
|
||||
|
||||
|
|
@ -216,6 +219,9 @@ private:
|
|||
UPROPERTY()
|
||||
FHyperTwistTrainingRepositoryState TrainingRepositoryState;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingRepositoryIntegrityReport ActiveRepositoryIntegrityReport;
|
||||
|
||||
UPROPERTY()
|
||||
FHyperTwistTrainingDeckHistorySummary ActiveDeckHistorySummary;
|
||||
|
||||
|
|
|
|||
|
|
@ -1287,6 +1287,44 @@ struct FHyperTwistTrainingTimerExportPacket
|
|||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingTimerExportIntegrityReport
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString UserId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
FString DeckId;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 MismatchedScopeEntryCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 MissingPacketMetadataCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> RepairActions;
|
||||
|
||||
bool HasIntegrityIssues() const
|
||||
{
|
||||
return InvalidEntryCount > 0
|
||||
|| DuplicateEntryCount > 0
|
||||
|| MismatchedScopeEntryCount > 0
|
||||
|| MissingPacketMetadataCount > 0;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingCaseScopedStats
|
||||
{
|
||||
|
|
@ -2242,3 +2280,60 @@ struct FHyperTwistTrainingRepositoryState
|
|||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FHyperTwistTrainingSessionTemplate> StoredSessionTemplates;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FHyperTwistTrainingRepositoryIntegrityReport
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalRunRecordCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidRunRecordCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateRunRecordCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalLearnerStateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidLearnerStateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateLearnerStateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalReviewPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidReviewPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateReviewPlanCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 TotalStoredSessionTemplateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 InvalidStoredSessionTemplateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
int32 DuplicateStoredSessionTemplateCount = 0;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "HyperTwist")
|
||||
TArray<FString> RepairActions;
|
||||
|
||||
bool HasIntegrityIssues() const
|
||||
{
|
||||
return InvalidRunRecordCount > 0
|
||||
|| DuplicateRunRecordCount > 0
|
||||
|| InvalidLearnerStateCount > 0
|
||||
|| DuplicateLearnerStateCount > 0
|
||||
|| InvalidReviewPlanCount > 0
|
||||
|| DuplicateReviewPlanCount > 0
|
||||
|| InvalidStoredSessionTemplateCount > 0
|
||||
|| DuplicateStoredSessionTemplateCount > 0;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue