add locomotion-action analyzer
parent
1482537b66
commit
97f5d380a4
|
|
@ -1,2 +1,3 @@
|
|||
from .analyzer import *
|
||||
from .biogames import *
|
||||
from .locomotion_action import *
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import json
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, Iterable
|
||||
|
||||
from log_analyzer import LogSettings
|
||||
|
||||
|
|
@ -11,21 +11,31 @@ class Analyzer:
|
|||
def process(self, entry: dict) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
def result(self) -> object:
|
||||
def result(self) -> Iterable:
|
||||
raise NotImplementedError()
|
||||
|
||||
def name(self):
|
||||
return self.__name__
|
||||
|
||||
|
||||
class LocationAnalyzer(Analyzer):
|
||||
"""
|
||||
store spatial log entries
|
||||
"""
|
||||
entries = []
|
||||
__name__ = "Location"
|
||||
|
||||
class Formats:
|
||||
geojson = 0
|
||||
|
||||
def __init__(self, settings: LogSettings):
|
||||
super().__init__(settings)
|
||||
|
||||
def result(self) -> object:
|
||||
def result(self) -> list:
|
||||
return self.entries
|
||||
|
||||
def render(self, format="geojson"):
|
||||
if format is "geojson":
|
||||
def render(self, format: int = Formats.geojson):
|
||||
if format is self.Formats.geojson:
|
||||
return json.dumps([entry['location']['coordinates'] for entry in self.entries])
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -36,7 +46,12 @@ class LocationAnalyzer(Analyzer):
|
|||
|
||||
|
||||
class LogEntryCountAnalyzer(Analyzer):
|
||||
def result(self) -> object:
|
||||
"""
|
||||
count occurrences of log entry types
|
||||
"""
|
||||
__name__ = "LogEntryCount"
|
||||
|
||||
def result(self) -> defaultdict:
|
||||
return self.store
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
|
|
@ -48,3 +63,34 @@ class LogEntryCountAnalyzer(Analyzer):
|
|||
self.store = defaultdict(lambda: 0)
|
||||
|
||||
|
||||
class LogEntrySequenceAnalyzer(Analyzer):
|
||||
"""
|
||||
store sequence of all log entry types
|
||||
"""
|
||||
__name__ = "LogEntrySequence"
|
||||
|
||||
def result(self) -> list:
|
||||
return self.store
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
entry_type = entry[self.settings.entry_type]
|
||||
self.store.append(entry_type)
|
||||
return False
|
||||
|
||||
def __init__(self, settings: LogSettings):
|
||||
super().__init__(settings)
|
||||
self.store = []
|
||||
|
||||
|
||||
class ActionSequenceAnalyzer(LogEntrySequenceAnalyzer):
|
||||
"""
|
||||
find sequence of non-spatial log entry types
|
||||
"""
|
||||
__name__ = "ActionSequenceAnalyzer"
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
entry_type = entry[self.settings.entry_type]
|
||||
if entry_type in self.settings.spatials:
|
||||
return False
|
||||
self.store.append(entry_type)
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -6,13 +6,41 @@ from .analyzer import Analyzer
|
|||
|
||||
|
||||
class BoardDurationAnalyzer(Analyzer):
|
||||
def result(self) -> object:
|
||||
return self.store
|
||||
"""
|
||||
calculate display duration of boards
|
||||
"""
|
||||
__name__ = "BoardDuration"
|
||||
|
||||
def render(self) -> str:
|
||||
return "\n".join(["{}\t{}".format(entry["active"], entry["id"]) for entry in self.result()])
|
||||
|
||||
def result(self) -> list:
|
||||
result = []
|
||||
last_timestamp = None
|
||||
last_board = None
|
||||
for board in self.store:
|
||||
board_id, timestamp = board["id"], board["timestamp"]
|
||||
|
||||
if not last_timestamp is None:
|
||||
result.append(self.save_entry(last_board, last_timestamp, timestamp - last_timestamp))
|
||||
last_timestamp = timestamp
|
||||
last_board = board_id
|
||||
# TODO: last board?
|
||||
return result
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
self.store[entry[self.settings.entry_type]] += 1
|
||||
entry_type = entry[self.settings.entry_type]
|
||||
if entry_type in self.settings.boards:
|
||||
self.store.append(self.save_entry(entry["board_id"], entry["timestamp"])) # TODO: make configurable
|
||||
return False
|
||||
|
||||
def save_entry(self, board_id: str, timestamp: int, active: int = None):
|
||||
entry = {"id": board_id, "timestamp": timestamp}
|
||||
if not active is None:
|
||||
entry["active"] = active
|
||||
return entry
|
||||
|
||||
def __init__(self, settings: LogSettings):
|
||||
super().__init__(settings)
|
||||
self.store = defaultdict(lambda: 0)
|
||||
self.store = []
|
||||
self.last = {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
from collections import defaultdict
|
||||
|
||||
from log_analyzer import LogSettings
|
||||
|
||||
from .analyzer import Analyzer
|
||||
from util import combinate
|
||||
|
||||
|
||||
def init_filter(settings: LogSettings, state: str) -> callable:
|
||||
# this implies OR for lists; AND for dicts
|
||||
if type(settings.sequences[state]) in (str, list):
|
||||
return lambda entry: entry[settings.entry_type] in settings.sequences[state]
|
||||
else:
|
||||
return lambda entry: combinate(settings.sequences[state], entry)
|
||||
|
||||
|
||||
class LocomotionActionAnalyzer(Analyzer): # TODO
|
||||
"""
|
||||
calculate locomation/action times and ratio
|
||||
|
||||
Anything between LogEntryCache and CacheEnableAction
|
||||
is counted as ActionTime, the rest as LocomotionTime.
|
||||
"""
|
||||
__name__ = "LocomotionAction"
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
self.last_timestamp = entry["timestamp"]
|
||||
if self.instance_start is None:
|
||||
self.instance_start = self.last_timestamp
|
||||
self.last = self.last_timestamp
|
||||
if self.cache_time is None:
|
||||
self.cache_time = self.last_timestamp
|
||||
offset = self.last_timestamp - self.cache_time
|
||||
|
||||
if self.current_cache is None and self.filter_start(entry):
|
||||
if entry['cache'] is None:
|
||||
return False
|
||||
self.current_cache = entry['cache']['@id']
|
||||
self.cache_time = self.last_timestamp
|
||||
self.locomotion.append(offset)
|
||||
self.last = None
|
||||
elif self.current_cache and self.filter_end(entry):
|
||||
self.actions.append(offset)
|
||||
self.cache_time = self.last_timestamp
|
||||
self.current_cache = None
|
||||
self.last = None
|
||||
|
||||
def result(self) -> dict:
|
||||
if self.last is not None:
|
||||
if self.current_cache is None:
|
||||
self.locomotion.append(self.last - self.cache_time)
|
||||
else:
|
||||
self.actions.append(self.last - self.cache_time)
|
||||
locomotion = sum(self.locomotion)
|
||||
action = sum(self.actions)
|
||||
return {
|
||||
'loco_sum': locomotion,
|
||||
'action_sum': action,
|
||||
'loco': self.locomotion,
|
||||
'act': self.actions,
|
||||
'dur': (self.last_timestamp - self.instance_start)
|
||||
}
|
||||
|
||||
def __init__(self, settings: LogSettings):
|
||||
super().__init__(settings)
|
||||
self.filter_start = init_filter(settings, "start")
|
||||
self.filter_end = init_filter(settings, "end")
|
||||
self.current_cache = None
|
||||
self.cache_time = None
|
||||
self.locomotion = []
|
||||
self.actions = []
|
||||
self.instance_start = None
|
||||
self.last_timestamp = None
|
||||
self.last = None
|
||||
|
||||
|
||||
class CacheSequenceAnalyzer(Analyzer): # TODO
|
||||
__name__ = "CacheSequence"
|
||||
|
||||
def process(self, entry: dict) -> bool:
|
||||
if self.filter(entry):
|
||||
if entry['cache']:
|
||||
self.store.append((entry['timestamp'],entry['cache']['@id']))
|
||||
else:
|
||||
self.store.append((entry['timestamp'],entry['cache']))
|
||||
|
||||
def result(self) -> list:
|
||||
return self.store
|
||||
|
||||
def __init__(self, settings: LogSettings):
|
||||
super().__init__(settings)
|
||||
self.store = []
|
||||
self.filter = init_filter(settings, "start")
|
||||
|
|
@ -1,14 +1,36 @@
|
|||
{
|
||||
"logFormat": "sqlite",
|
||||
"entryType": "@class",
|
||||
"spatials":["de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"],
|
||||
"actions":["...QuestionAnswerEvent", "...SimuAnswerEvent"],
|
||||
"spatials": [
|
||||
"de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"
|
||||
],
|
||||
"actions": [
|
||||
"...QuestionAnswerEvent",
|
||||
"...SimuAnswerEvent"
|
||||
],
|
||||
"boards": [
|
||||
"de.findevielfalt.games.game2.instance.log.entry.ShowBoardLogEntry"
|
||||
],
|
||||
"analyzers": {
|
||||
"analyzer": [
|
||||
"LocationAnalyzer"
|
||||
],
|
||||
"LocationAnalyzer",
|
||||
"LogEntryCountAnalyzer",
|
||||
"LogEntrySequenceAnalyzer",
|
||||
"ActionSequenceAnalyzer"
|
||||
],
|
||||
"analyzer.biogames": [
|
||||
"BoardDurationAnalyzer"
|
||||
],
|
||||
"analyzer.locomotion_action": [
|
||||
"LocomotionActionAnalyzer",
|
||||
"CacheSequenceAnalyzer"
|
||||
]
|
||||
},
|
||||
"sequences": {
|
||||
"start": "de.findevielfalt.games.game2.instance.log.entry.LogEntryCache",
|
||||
"end": {
|
||||
"@class": "de.findevielfalt.games.game2.instance.log.entry.LogEntryInstanceAction",
|
||||
"action.@class": "de.findevielfalt.games.game2.instance.action.CacheEnableAction"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,15 +10,19 @@ class LogSettings:
|
|||
spatials = None
|
||||
actions = None
|
||||
analyzers = []
|
||||
boards = None
|
||||
sequences = None
|
||||
|
||||
def __init__(self, json_dict):
|
||||
self.log_format = json_dict['logFormat']
|
||||
self.entry_type = json_dict['entryType']
|
||||
self.spatials = json_dict['spatials']
|
||||
self.actions = json_dict['actions']
|
||||
self.boards = json_dict['boards']
|
||||
for mod in json_dict['analyzers']:
|
||||
for name in json_dict['analyzers'][mod]:
|
||||
self.analyzers.append(getattr(sys.modules[mod], name))
|
||||
self.sequences = json_dict['sequences']
|
||||
|
||||
def __repr__(self):
|
||||
return str({
|
||||
|
|
@ -26,11 +30,13 @@ class LogSettings:
|
|||
"entryType": self.entry_type,
|
||||
"spatials": self.spatials,
|
||||
"actions": self.actions,
|
||||
"analyzers": self.analyzers
|
||||
"analyzers": self.analyzers,
|
||||
"boards": self.boards,
|
||||
"sequences": self.sequences,
|
||||
})
|
||||
|
||||
|
||||
def load_settings(file:str) -> LogSettings:
|
||||
def load_settings(file: str) -> LogSettings:
|
||||
return LogSettings(json.load(open(file)))
|
||||
|
||||
|
||||
|
|
@ -51,8 +57,17 @@ if __name__ == '__main__':
|
|||
if analyzer.process(entry):
|
||||
break
|
||||
for analyzer in analyzers:
|
||||
print("* Result for " + str(type(analyzer)))
|
||||
print("* Result for " + analyzer.name())
|
||||
print(analyzer.result())
|
||||
coords = analyzers[0].render()
|
||||
with open("test.js", "w") as out:
|
||||
out.write("coords = "+coords)
|
||||
#for analyzer in analyzers:
|
||||
# if analyzer.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]:
|
||||
# print(json.dumps(analyzer.result(), indent=2))
|
||||
|
||||
#for analyzer in analyzers:
|
||||
# if analyzer.name() in ["BoardDuration"]:
|
||||
# print(json.dumps(analyzer.result(), indent=2))
|
||||
# print(analyzer.render())
|
||||
|
||||
# coords = analyzers[1].render()
|
||||
# with open("test.js", "w") as out:
|
||||
# out.write("coords = "+coords)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
from .iter import *
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
def json_path(obj: dict, key: str):
|
||||
"""Query a nested dict with a dot-separated path"""
|
||||
if not type(obj) is dict:
|
||||
return None
|
||||
if "." not in key:
|
||||
return obj[key]
|
||||
child_key = key.split(".")
|
||||
if child_key[0] not in obj:
|
||||
return None
|
||||
return json_path(obj[child_key[0]], ".".join(child_key[1:]))
|
||||
|
||||
|
||||
def combinate(settings: dict, entry:dict)-> bool:
|
||||
"""combine all settings {<key>: <expected>} with entry using AND"""
|
||||
result = True
|
||||
for key, value in settings.items():
|
||||
result = result and json_path(entry, key) == value
|
||||
return result
|
||||
Loading…
Reference in New Issue