Rebuild CCS case workflow
This commit is contained in:
+50
-53
@@ -1,8 +1,5 @@
|
||||
extends SceneTree
|
||||
|
||||
const Style := preload("res://scripts/ui/ui_style.gd")
|
||||
const Brand := preload("res://scripts/ui/medmind_style.gd")
|
||||
|
||||
var failures := 0
|
||||
var ccs_service
|
||||
|
||||
@@ -13,8 +10,6 @@ func _initialize() -> void:
|
||||
|
||||
func _run() -> void:
|
||||
ccs_service = root.get_node("CCSService")
|
||||
var theme_service = root.get_node("UIThemeService")
|
||||
var prior_dark_mode: bool = theme_service.is_dark_mode()
|
||||
var prior_attempt: Dictionary = ccs_service.attempt.duplicate(true)
|
||||
var prior_orders: Array = ccs_service.orders.duplicate(true)
|
||||
var prior_scale: float = ccs_service.text_scale
|
||||
@@ -33,58 +28,42 @@ func _run() -> void:
|
||||
player._render()
|
||||
await process_frame
|
||||
|
||||
_assert(player.heading.text == "CCS Case | Emergency Department", "CCS header should show the readable location without a case id")
|
||||
_assert(player._ordered_vital_keys(ccs_service.attempt.vitals) == ["temperature", "bloodPressure", "pulse", "respirations", "oxygenSaturation"], "vital chips should use the clinical priority order")
|
||||
_assert(player.vitals_flow.get_child_count() == 5, "every supplied vital should render as a chip")
|
||||
_assert(player.search_button.icon != null, "Order Clerk search should use a bundled icon rather than a font glyph")
|
||||
_assert(player.summary.get_meta("ccs_region", "") == "summary", "summary highlighting should use its own region")
|
||||
_assert(player.history.get_meta("ccs_region", "") == "history", "history highlighting should use its own region")
|
||||
_assert(player.appearance.get_meta("ccs_region", "") == "appearance", "appearance highlighting should use its own region")
|
||||
_assert(player.chart.get_meta("ccs_region", "") == "chart", "chart highlighting should use its own region")
|
||||
_assert(not player.workspace.vertical, "wide CCS screens should use chart and Order Clerk columns")
|
||||
_assert(player.orders.get_child_count() == 1, "active orders should render in the Order Clerk")
|
||||
var active_order_row := player.orders.get_child(0) as HBoxContainer
|
||||
var active_order_label := active_order_row.get_child(0) as Label
|
||||
_assert(active_order_label.text == "Normal Saline Bolus", "internal order codes should be human readable")
|
||||
_assert(player.actions.get_child_count() == 2, "available physical examinations should render as actions")
|
||||
_assert(player.locations.get_child_count() == 2, "allowed locations should render as move actions")
|
||||
_assert(not player.clock.visible, "the normal active-case countdown should remain hidden")
|
||||
|
||||
_assert(player.heading.text == "CCS Case | Emergency Department", "header should show the readable location")
|
||||
_assert(player.action_buttons.size() == 4, "CCS workspace should expose four persistent clinical tasks")
|
||||
_assert(player.tabs.get_tab_count() == 7, "CCS chart should expose seven predictable tabs")
|
||||
_assert(player.tabs.get_tab_title(0) == "Order Sheet", "the first chart tab should be the order sheet")
|
||||
_assert(player.allotted_label.text.contains("18 minutes + 2 minutes"), "20-minute cases should reserve two final-order minutes")
|
||||
_assert(player.active_orders.get_child_count() == 1, "active orders should render as structured rows")
|
||||
_assert(player._ordered_vital_keys(ccs_service.attempt.vitals) == ["temperature", "bloodPressure", "pulse", "respirations", "oxygenSaturation"], "vitals should use clinical priority order")
|
||||
_assert(player._chart_entries("lab").size() == 1, "categorized results should render in their assigned tab")
|
||||
_assert(player._simulated_time_text(60).contains("10:00 AM"), "simulated time should derive from the case start")
|
||||
_assert(not player.opening_active, "completed opening flows should not replay on resume")
|
||||
_assert(not player.final_orders_notice.visible, "active cases should not show final-order instructions")
|
||||
var clerk_font_size: int = player.search.get_theme_font_size("font_size")
|
||||
var chart_font_size: int = player.summary.get_theme_font_size("normal_font_size")
|
||||
var original_size: int = player.clinical_labels[0].get_theme_font_size("normal_font_size")
|
||||
var prior_profile_api_url: String = ProjectSettings.get_setting("step3/profile_api_url", "")
|
||||
ProjectSettings.set_setting("step3/profile_api_url", "http://127.0.0.1:1")
|
||||
player._zoom(ccs_service.TEXT_SCALE_STEP)
|
||||
ProjectSettings.set_setting("step3/profile_api_url", prior_profile_api_url)
|
||||
_assert(player.summary.get_theme_font_size("normal_font_size") > chart_font_size, "text zoom should enlarge clinical chart text")
|
||||
_assert(player.search.get_theme_font_size("font_size") == clerk_font_size, "text zoom should not resize Order Clerk controls")
|
||||
_assert(player.clinical_labels[0].get_theme_font_size("normal_font_size") > original_size, "clinical text zoom should remain available")
|
||||
_assert(player.search.get_theme_font_size("font_size") == clerk_font_size, "clinical text zoom should not resize order controls")
|
||||
|
||||
player._stage_order("bmp")
|
||||
_assert(player.staged_order_codes == ["bmp"], "orders should stage before submission")
|
||||
_assert(not player.commit_orders_button.disabled, "staged orders should enable confirmation")
|
||||
player._clear_staged_orders()
|
||||
_assert(player.commit_orders_button.disabled, "clearing staged orders should disable confirmation")
|
||||
|
||||
player.size = Vector2(800, 900)
|
||||
player._update_responsive_layout()
|
||||
_assert(player.workspace.vertical, "narrow CCS screens should stack the Order Clerk below the chart")
|
||||
_assert(player.action_grid.columns == 2, "narrow CCS screens should use a two-column action workspace")
|
||||
|
||||
ccs_service.attempt.state = "final_orders"
|
||||
ccs_service.attempt.finalOrdersDeadlineUtc = Time.get_datetime_string_from_unix_time(Time.get_unix_time_from_system() + 90.0, true)
|
||||
player._render()
|
||||
_assert(player.final_orders_notice.visible and player.clock.visible, "final-orders mode should show instructions and its deadline")
|
||||
_assert(not player.time_row.visible, "time advancement should be unavailable during final orders")
|
||||
_assert(player.final_orders_notice.visible, "final-orders mode should show instructions")
|
||||
_assert(player.exit_button.text == "Submit Final Orders", "final-orders mode should expose final submission")
|
||||
_assert(player.action_buttons[0].disabled and not player.action_buttons[1].disabled, "only order review should remain available in final orders")
|
||||
|
||||
theme_service.set_dark_mode(true)
|
||||
await process_frame
|
||||
theme_service.set_dark_mode(false)
|
||||
Style.refresh_tree(player, true)
|
||||
await process_frame
|
||||
var light_panel := player.chart_panel.get_theme_stylebox("panel") as StyleBoxFlat
|
||||
_assert(light_panel.bg_color.is_equal_approx(Brand.content_card_box(false, false).bg_color), "CCS light styles should win after the shell palette translation")
|
||||
_assert(player.summary.get_theme_color("default_color").is_equal_approx(Brand.text_color(false)), "CCS light chart text should remain readable")
|
||||
theme_service.set_dark_mode(true)
|
||||
Style.refresh_tree(player, false)
|
||||
await process_frame
|
||||
var dark_panel := player.chart_panel.get_theme_stylebox("panel") as StyleBoxFlat
|
||||
_assert(dark_panel.bg_color.is_equal_approx(Brand.content_card_box(true, false).bg_color), "CCS dark styles should win after the shell palette translation")
|
||||
_assert(player.summary.get_theme_color("default_color").is_equal_approx(Brand.text_color(true)), "CCS dark chart text should remain readable")
|
||||
|
||||
theme_service.set_dark_mode(prior_dark_mode)
|
||||
await process_frame
|
||||
host.queue_free()
|
||||
await process_frame
|
||||
ccs_service.attempt = prior_attempt
|
||||
@@ -94,14 +73,24 @@ func _run() -> void:
|
||||
|
||||
|
||||
func _sample_attempt() -> Dictionary:
|
||||
var now := Time.get_unix_time_from_system()
|
||||
return {
|
||||
"id": "attempt-1",
|
||||
"state": "active",
|
||||
"revision": 1,
|
||||
"openingCompleted": true,
|
||||
"location": "emergency_department",
|
||||
"patientSummary": "24-year-old woman with type 1 diabetes mellitus",
|
||||
"reasonForEncounter": "Two days of vomiting and progressive lethargy.",
|
||||
"history": "She stopped insulin when oral intake declined.",
|
||||
"historySections": [{"code": "hpi", "title": "History of present illness", "text": "She stopped insulin when oral intake declined."}],
|
||||
"appearance": "She is lethargic but arousable.",
|
||||
"simulatedStartUtc": "2026-07-15T09:00:00Z",
|
||||
"simulatedMinute": 0,
|
||||
"realTimeMinutes": 20,
|
||||
"createdUtc": Time.get_datetime_string_from_unix_time(now - 60, true),
|
||||
"deadlineUtc": Time.get_datetime_string_from_unix_time(now + 1020, true),
|
||||
"hardDeadlineUtc": Time.get_datetime_string_from_unix_time(now + 1140, true),
|
||||
"vitals": {
|
||||
"oxygenSaturation": "98% on room air",
|
||||
"pulse": "118/min",
|
||||
@@ -109,14 +98,22 @@ func _sample_attempt() -> Dictionary:
|
||||
"respirations": "28/min",
|
||||
"bloodPressure": "88/56 mm Hg"
|
||||
},
|
||||
"chart": [{"simulatedMinute": 0, "category": "progress", "text": "Initial evaluation is in progress."}],
|
||||
"activeOrderCodes": ["normal_saline_bolus"],
|
||||
"availableExams": [
|
||||
{"code": "general", "name": "General and hydration examination"},
|
||||
{"code": "neurologic", "name": "Neurologic examination"}
|
||||
"vitalHistory": [{"simulatedMinute": 0, "values": {"pulse": "118/min"}}],
|
||||
"chart": [
|
||||
{"simulatedMinute": 0, "category": "progress", "code": "reason", "title": "Reason for encounter", "text": "Initial evaluation."},
|
||||
{"simulatedMinute": 12, "category": "lab", "code": "bmp", "title": "Basic metabolic panel", "text": "Glucose 580 mg/dL."}
|
||||
],
|
||||
"allowedLocations": ["emergency_department", "icu"],
|
||||
"deadlineUtc": Time.get_datetime_string_from_unix_time(Time.get_unix_time_from_system() + 600.0, true)
|
||||
"activeOrderCodes": ["normal_saline_bolus"],
|
||||
"activeOrders": [{
|
||||
"code": "normal_saline_bolus", "name": "0.9% sodium chloride intravenous bolus",
|
||||
"category": "medication", "route": "IV", "frequency": "Once",
|
||||
"orderedMinute": 0, "reportMinute": 0
|
||||
}],
|
||||
"availableExams": [
|
||||
{"code": "general", "name": "General and hydration examination", "durationMinutes": 1},
|
||||
{"code": "neurologic", "name": "Neurologic examination", "durationMinutes": 1}
|
||||
],
|
||||
"allowedLocations": ["emergency_department", "icu"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -161,15 +161,40 @@ test("public API and disposable learner journey", async () => {
|
||||
expect(Array.isArray(cases)).toBeTruthy();
|
||||
expect(cases.length).toBeGreaterThan(0);
|
||||
const { data: attempt } = await call("/api/ccs/attempts", {
|
||||
method: "POST", token, body: { caseExternalId: "", mode: "practice" }
|
||||
method: "POST", token, body: { caseExternalId: "", mode: "timed" }
|
||||
});
|
||||
activeCcsAttempt = attempt.id;
|
||||
expect(activeCcsAttempt).toBeTruthy();
|
||||
expect(attempt.caseExternalId).toBeTruthy();
|
||||
expect(attempt.state).toBe("active");
|
||||
await call(`/api/ccs/attempts/${activeCcsAttempt}/abandon`, {
|
||||
method: "POST", token, expected: [204]
|
||||
const { data: opened } = await call(`/api/ccs/attempts/${activeCcsAttempt}/commands`, {
|
||||
method: "POST", token, body: {
|
||||
commandId: `smoke-opening-${suffix}`, expectedRevision: attempt.revision,
|
||||
type: "acknowledge_opening", code: "", minutes: 0
|
||||
}
|
||||
});
|
||||
expect(opened.openingCompleted).toBe(true);
|
||||
const exam = opened.availableExams[0];
|
||||
const { data: examined } = await call(`/api/ccs/attempts/${activeCcsAttempt}/commands/batch`, {
|
||||
method: "POST", token, body: {
|
||||
commandId: `smoke-exam-${suffix}`, expectedRevision: opened.revision,
|
||||
commands: [{ type: "exam", code: exam.code, minutes: 0 }]
|
||||
}
|
||||
});
|
||||
const { data: finalOrders } = await call(`/api/ccs/attempts/${activeCcsAttempt}/commands`, {
|
||||
method: "POST", token, body: {
|
||||
commandId: `smoke-final-${suffix}`, expectedRevision: examined.revision,
|
||||
type: "end_case", code: "", minutes: 0
|
||||
}
|
||||
});
|
||||
expect(finalOrders.state).toBe("final_orders");
|
||||
const { data: completedCcs } = await call(`/api/ccs/attempts/${activeCcsAttempt}/commands`, {
|
||||
method: "POST", token, body: {
|
||||
commandId: `smoke-complete-${suffix}`, expectedRevision: finalOrders.revision,
|
||||
type: "exit_case", code: "", minutes: 0
|
||||
}
|
||||
});
|
||||
expect(completedCcs.state).toBe("completed");
|
||||
activeCcsAttempt = "";
|
||||
|
||||
// Flashcards: create a deck and card, study and grade it, verify it drops out of the due
|
||||
|
||||
Reference in New Issue
Block a user