Connect graphical CCS interviews to cases

This commit is contained in:
hz
2026-08-05 20:14:27 -05:00
parent 31093ab80d
commit af2bbb673d
16 changed files with 547 additions and 28 deletions
+35 -1
View File
@@ -117,6 +117,7 @@ public sealed class CcsStore
CaseVersion = definition.Version,
Mode = CcsAttemptModes.Timed,
OpeningCompleted = false,
InterviewNodeId = definition.InitialInterview.StartNodeId,
Location = definition.PresentingLocation,
CurrentVitals = new Dictionary<string, string>(definition.InitialChart.Vitals),
VitalHistory =
@@ -227,6 +228,7 @@ public sealed class CcsStore
case "acknowledge_opening":
attempt.OpeningCompleted = true;
break;
case "dialogue_choice": ApplyDialogueChoice(attempt, definition, command.Code, now); break;
case "end_case": EnterFinalOrders(attempt); break;
case "exit_case": await CompleteAsync(attempt); break;
}
@@ -412,6 +414,19 @@ public sealed class CcsStore
List<CcsVitalSnapshot> vitalHistory = attempt.VitalHistory.Count > 0
? attempt.VitalHistory
: [new CcsVitalSnapshot { SimulatedMinute = attempt.SimulatedMinute, Values = new Dictionary<string, string>(attempt.CurrentVitals) }];
CcsInterviewStateResponse? interview = null;
var currentInterviewNode = definition.InitialInterview.Nodes.FirstOrDefault(value =>
value.Id.Equals(attempt.InterviewNodeId, StringComparison.OrdinalIgnoreCase));
if (currentInterviewNode is not null)
{
interview = new CcsInterviewStateResponse
{
CurrentNodeId = currentInterviewNode.Id,
CurrentNode = currentInterviewNode,
VisitedChoiceIds = attempt.InterviewChoiceIds,
Completed = attempt.OpeningCompleted ?? false
};
}
return new CcsAttemptResponse
{
Id = attempt.Id ?? "", CaseExternalId = attempt.CaseExternalId,
@@ -434,11 +449,29 @@ public sealed class CcsStore
}).ToList(),
AvailableExams = attempt.State == CcsAttemptStates.Active ? definition.Exams : [],
AllowedLocations = attempt.State == CcsAttemptStates.Active ? definition.AllowedLocations : [],
Interview = interview,
Score = completed ? attempt.Score : null, Rating = completed ? attempt.Rating : null,
References = completed ? definition.References : []
};
}
public static void ApplyDialogueChoice(CcsAttempt attempt, CcsCaseRevision definition, string choiceId, DateTime now)
{
if (attempt.OpeningCompleted == true) throw new InvalidOperationException("The initial interview is already complete.");
var node = definition.InitialInterview.Nodes.FirstOrDefault(value =>
value.Id.Equals(attempt.InterviewNodeId, StringComparison.OrdinalIgnoreCase))
?? throw new InvalidOperationException("The interactive interview is unavailable.");
var choice = node.Choices.FirstOrDefault(value => value.Id.Equals(choiceId, StringComparison.OrdinalIgnoreCase))
?? throw new ArgumentException("The dialogue choice is not available at the current interview node.");
var next = definition.InitialInterview.Nodes.FirstOrDefault(value =>
value.Id.Equals(choice.NextNodeId, StringComparison.OrdinalIgnoreCase))
?? throw new InvalidOperationException("The dialogue choice target is unavailable.");
attempt.Actions.Add(Action("dialogue_choice", choice.Id, attempt.SimulatedMinute, now));
attempt.InterviewChoiceIds.Add(choice.Id);
attempt.InterviewNodeId = next.Id;
if (next.IsTerminal) attempt.OpeningCompleted = true;
}
public async Task AbandonAsync(CcsAttempt attempt)
{
attempt.State = CcsAttemptStates.Abandoned;
@@ -693,7 +726,8 @@ public sealed class CcsStore
{
ExternalId = value.ExternalId, Title = value.Title, System = value.System,
PresentingLocation = value.PresentingLocation, RealTimeMinutes = value.RealTimeMinutes,
Difficulty = value.Difficulty, Tags = value.Tags
Difficulty = value.Difficulty, Tags = value.Tags,
InteractiveInterviewAvailable = !string.IsNullOrWhiteSpace(value.InitialInterview.StartNodeId) && value.InitialInterview.Nodes.Count > 0
};
internal static int Rank(CcsOrderDefinition order, string query)
{