Skip to content

Commit

Permalink
epand template generators #2
Browse files Browse the repository at this point in the history
  • Loading branch information
claasga committed Feb 3, 2025
1 parent acbf3d2 commit d0568d4
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 43 deletions.
35 changes: 33 additions & 2 deletions backend/dps_training_k/game/channel_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
import game.models as models # needed to avoid circular imports
import template.models as template

import datetime
import threading
import time

"""
This package is responsible to decide when to notify which consumers.
This also implies that it should be as transparent as possible to the models it watches.
Expand All @@ -27,6 +31,7 @@ class ChannelEventTypes:
RESOURCE_ASSIGNMENT_EVENT = "resource.assignment.event"
RELOCATION_START_EVENT = "relocation.start.event"
RELOCATION_END_EVENT = "relocation.end.event"
RULE_VIOLATION_EVENT = "rule.violation.event"


class ChannelNotifier:
Expand Down Expand Up @@ -337,7 +342,7 @@ def unsubscribe_from_exercise(cls, exercise, subscriber):

@classmethod
def _publish_obj(cls, obj, exercise):
print("Evaluating validity")
# print("Evaluating validity")
if not obj.is_valid():
return

Expand Down Expand Up @@ -378,6 +383,33 @@ def _notify_log_update_event(cls, log_entry):
cls._notify_group(channel, event)


class LogRuleDispatcher:
@classmethod
def get_group_name(cls, exercise):
return f"log_rules_{exercise.id}_log"

@classmethod
async def violation_found(cls, exercise, log_rule_violation):
channel = cls.get_group_name(exercise)
event = {
"type": ChannelEventTypes.RULE_VIOLATION_EVENT,
"log_entry_id": log_rule_violation,
}
await get_channel_layer().group_send(channel, event)

@classmethod
def start_periodic_violation_thread(cls, exercise):
def run_periodically():
while True:
current_time = datetime.datetime.now().strftime("%H:%M:%S")
log_rule_violation = f"log rule violation: {current_time}"
asyncio.run(cls.violation_found(exercise, log_rule_violation))
time.sleep(10)

thread = threading.Thread(target=run_periodically, daemon=True)
thread.start()


class MaterialInstanceDispatcher(ChannelNotifier):
@classmethod
def dispatch_event(cls, material, changes, is_updated):
Expand Down Expand Up @@ -434,7 +466,6 @@ def create_trainer_log(cls, material, changes, is_updated):
is_dirty=True,
)
if isinstance(current_location, models.Area):
message += f" zu {current_location.frontend_model_name()} {current_location.name}"
log_entry = models.LogEntry.objects.create(
exercise=cls.get_exercise(material),
category=category,
Expand Down
12 changes: 11 additions & 1 deletion backend/dps_training_k/game/consumers/trainer_consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from template.constants import MaterialIDs
from template.models import PatientInformation, Material
from .abstract_consumer import AbstractConsumer
from ..channel_notifications import ChannelNotifier, LogEntryDispatcher
from ..channel_notifications import (
ChannelNotifier,
LogEntryDispatcher,
LogRuleDispatcher,
)
from ..serializers import LogEntrySerializer


Expand Down Expand Up @@ -179,6 +183,8 @@ def handle_create_exercise(self, exercise):
self._send_exercise(self.exercise)
self.subscribe(ChannelNotifier.get_group_name(self.exercise))
self.subscribe(LogEntryDispatcher.get_group_name(self.exercise))
self.subscribe(LogRuleDispatcher.get_group_name(self.exercise))
LogRuleDispatcher.start_periodic_violation_thread(self.exercise)

def handle_end_exercise(self, exercise):
exercise.update_state(Exercise.StateTypes.FINISHED)
Expand Down Expand Up @@ -413,3 +419,7 @@ def log_update_event(self, event):
self.TrainerOutgoingMessageTypes.LOG_UPDATE,
logEntries=[LogEntrySerializer(log_entry).data],
)

def rule_violation_event(self, event):
print("Rule violation event")
print(event)
2 changes: 1 addition & 1 deletion backend/dps_training_k/game/models/log_entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def generate_local_id(self, exercise):
def is_valid(self):
if self.timestamp and self.local_id and not self.is_dirty:
return True
print("not valid")
# print("not valid")
return False

def set_dirty(self, new_dirty):
Expand Down
9 changes: 1 addition & 8 deletions backend/dps_training_k/game/templmon/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
from .signature_mapping import *
from .log_rule import LogRule, LogRuleRunner
from .log_transformer import *
from .signature_mapping import (
RuleProperty,
assigned_personnel,
unassigned_personnel,
changed_state,
patient_arrived,
patient_relocated,
)
54 changes: 27 additions & 27 deletions backend/dps_training_k/game/templmon/log_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import subprocess

# import log_transformer as lt
import signature_mapping as sm
from . import signature_mapping as sm


class LogRule:
Expand Down Expand Up @@ -289,11 +289,11 @@ def generate(

RP = sm.RuleProperty
patient_arrived = sm.PatientArrived()
changed_state, c_changed_state, vital_paramters_sub = None, None, None
changed_state, c_changed_state, vital_parameters_sub = None, None, None

if vital_parameters:
[changed_state, c_changed_state] = sm.ChangedState.bulk_create(2)
vital_paramters_sub = f"""{changed_state.mfotl()} AND {changed_state.compare_values_mfotl(vital_parameters)}"""
vital_parameters_sub = f"""{changed_state.mfotl()} AND {changed_state.compare_values_mfotl(vital_parameters)}"""

examination_formulas, examination_results_base_subs, c_examination_formulas = (
None,
Expand Down Expand Up @@ -334,6 +334,7 @@ def generate(
action_canceled = sm.ActionCanceled()
action_started.match([action_canceled], [RP.PATIENT.name, RP.ACTION.name])
recent_conditions = []
examination_results_subs = []
if examination_results:
examination_results_subs = [
f"""(NOT (EXISTS {examination_formulas[i].bind([RP.PATIENT], False)}. {examination_formulas[i].mfotl()})
Expand All @@ -346,19 +347,31 @@ def generate(
vital_parameters_sub = f"""(NOT EXISTS {c_changed_state.bind([RP.PATIENT.name], False)}. {c_changed_state.mfotl()}
SINCE[0,*]
{changed_state.mfotl()} AND {changed_state.compare_values_mfotl(vital_parameters)})"""
recent_conditions.append(vital_paramters_sub)
recent_conditions.append(vital_parameters_sub)
unfinished_rule = f"""
(EXISTS {patient_arrived.bind([RP.PATIENT.name], False)}. ONCE[0,*] {patient_arrived.mfotl()})
AND
{action_canceled.mfotl()}
AND
(NOT {action_started.mfotl()}
SINCE[0,120] # hier dauer der Aktion eintragen!
SINCE[0,{timeframe}] # hier dauer der Aktion eintragen!
({action_started.mfotl()} #Currently uses examination and breathing values of the action started timpoint. Might be unintented behaviour. Or not.
AND
{" AND ".join(recent_conditions)}))
# struggles with overlapping actions of the same type. When canceled, it always asumes the most"""

NOW_versions = examination_results_base_subs + [
f"{changed_state.mfotl()} AND {changed_state.compare_values_mfotl(vital_parameters)}"
]
SINCE_versions = examination_results_subs + [vital_parameters_sub]
NOW_SINCE_CONSTRUCTS = []
for i in range(len(NOW_versions)):
NOW_SINCE_CONSTRUCTS.append(
" AND ".join(
NOW_versions[i] + SINCE_versions[:i] + SINCE_versions[i + 1 :]
)
)

to_late_rule = f"""
(EXISTS {patient_arrived.bind([RP.PATIENT.name], False)}. ONCE[0,*] {patient_arrived.mfotl()})
AND
Expand All @@ -367,22 +380,8 @@ def generate(
(EXISTS new_results. patient_examination_result(patient_id, "selected_examination_id", new_results))
OR
(EXISTS {c_changed_state.bind([RP.PATIENT.name], False)}. {c_changed_state.mfotl()}))
SINCE[120,*] #hier andere struktur nötig, da diesmal die zeit relevant und nicht mit "*" als Obergrenze geschummelt werden kann
((patient_examination_result(patient_id, "selected_examination_id", results)
AND
results = "bad"
AND
(NOT(EXISTS {c_changed_state.bind([RP.PATIENT.name], False)}. {c_changed_state.mfotl()})
SINCE[0,*]
({vital_paramters_sub})))
OR
(NOT(EXISTS re. patient_examination_result(patient_id, "selected_examination_id", re))
SINCE[0,*]
(patient_examination_result(patient_id, "selected_examination_id", results)
AND
results = "bad"))
AND
{vital_paramters_sub}))"""
SINCE[{timeframe},*] #hier andere struktur nötig, da diesmal die zeit relevant und nicht mit "*" als Obergrenze geschummelt werden kann
({" OR ".join(NOW_SINCE_CONSTRUCTS)})"""
wrong_order_rule = f"""
((EXISTS {patient_arrived.bind([RP.PATIENT.name], False)}. ONCE[0,*] {patient_arrived.mfotl()})
AND
Expand All @@ -403,7 +402,7 @@ def generate(
AND
(EXISTS {c_action_started_2.bind([RP.PATIENT.name], False)}. {c_action_started_2.mfotl()}))
SINCE[0,*]
{vital_paramters_sub}))"""
{vital_parameters_sub}))"""

pass

Expand Down Expand Up @@ -624,8 +623,9 @@ def stop_log_rule(self):
# (personnel_count >= 4)"""
# test_rule = LogRule.create(test_rule_str, "test_rule")
# log_rule_runner = LogRuleRunner(None, LogEntryDispatcher, test_rule)
RP = sm.RuleProperty
PersonnelCheckRule.generate(operator="<")
PersonnelPrioritizationRule.generate(
RP.AIRWAY.name, ">", 4, 2, RP.CIRCULATION.name, "<", 4, 2
)
if __name__ == "main":
RP = sm.RuleProperty
PersonnelCheckRule.generate(operator="<")
PersonnelPrioritizationRule.generate(
RP.AIRWAY.name, ">", 4, 2, RP.CIRCULATION.name, "<", 4, 2
)
9 changes: 9 additions & 0 deletions backend/dps_training_k/game/tests/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,12 @@ def deactivate_dispatching(self, class_name: str):

def activate_dispatching(self):
self._deactivate_dispatching_patch.stop()

def deactivate_pretreatments(self):
self._deactivate_pretreatments_patch = patch(
"game.models.PatientInstance.apply_pretreatments"
)
self._deactivate_pretreatments = self._deactivate_pretreatments_patch.start()

def activate_pretreatments(self):
self._deactivate_pretreatments_patch.stop()
16 changes: 12 additions & 4 deletions backend/dps_training_k/game/tests/test_logging.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from django.test import TestCase
from unittest.mock import patch
from game.models import LogEntry, Exercise
from .factories import ExerciseFactory
from .factories import ExerciseFactory, PatientFactory
from .mixin import TestUtilsMixin


class TrainerLogTestCase(TestCase):
class TrainerLogTestCase(TestUtilsMixin, TestCase):
def setUp(self):
self.deactivate_pretreatments()
self.exercise = ExerciseFactory()
self.log_entry, _ = LogEntry.objects.get_or_create(
exercise=self.exercise, message="Test 1"
exercise=self.exercise,
category=LogEntry.CATEGORIES.PATIENT,
type=LogEntry.TYPES.ARRIVED,
patient_instance=PatientFactory(),
)

@patch("game.channel_notifications.LogEntryDispatcher._notify_log_update_event")
Expand All @@ -34,7 +39,10 @@ def test_logs_timestamped_on_active_exercise(self, _notify_log_update_event):
self.assertEqual(_notify_log_update_event.call_count, log_entries.count())

self.log_entry = LogEntry.objects.create(
exercise=self.exercise, message="Test 2"
exercise=self.exercise,
category=LogEntry.CATEGORIES.PATIENT,
type=LogEntry.TYPES.MOVED,
patient_instance=PatientFactory(),
)
self.assertNotEqual(self.log_entry.timestamp, None)
self.assertEqual(self.log_entry.is_valid(), True)
Expand Down

0 comments on commit d0568d4

Please sign in to comment.