add locomotion-action analyzer
parent
1482537b66
commit
97f5d380a4
|
|
@ -1,2 +1,3 @@
|
||||||
from .analyzer import *
|
from .analyzer import *
|
||||||
from .biogames import *
|
from .biogames import *
|
||||||
|
from .locomotion_action import *
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict, Iterable
|
||||||
|
|
||||||
from log_analyzer import LogSettings
|
from log_analyzer import LogSettings
|
||||||
|
|
||||||
|
|
@ -11,21 +11,31 @@ class Analyzer:
|
||||||
def process(self, entry: dict) -> bool:
|
def process(self, entry: dict) -> bool:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def result(self) -> object:
|
def result(self) -> Iterable:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self.__name__
|
||||||
|
|
||||||
|
|
||||||
class LocationAnalyzer(Analyzer):
|
class LocationAnalyzer(Analyzer):
|
||||||
|
"""
|
||||||
|
store spatial log entries
|
||||||
|
"""
|
||||||
entries = []
|
entries = []
|
||||||
|
__name__ = "Location"
|
||||||
|
|
||||||
|
class Formats:
|
||||||
|
geojson = 0
|
||||||
|
|
||||||
def __init__(self, settings: LogSettings):
|
def __init__(self, settings: LogSettings):
|
||||||
super().__init__(settings)
|
super().__init__(settings)
|
||||||
|
|
||||||
def result(self) -> object:
|
def result(self) -> list:
|
||||||
return self.entries
|
return self.entries
|
||||||
|
|
||||||
def render(self, format="geojson"):
|
def render(self, format: int = Formats.geojson):
|
||||||
if format is "geojson":
|
if format is self.Formats.geojson:
|
||||||
return json.dumps([entry['location']['coordinates'] for entry in self.entries])
|
return json.dumps([entry['location']['coordinates'] for entry in self.entries])
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -36,7 +46,12 @@ class LocationAnalyzer(Analyzer):
|
||||||
|
|
||||||
|
|
||||||
class LogEntryCountAnalyzer(Analyzer):
|
class LogEntryCountAnalyzer(Analyzer):
|
||||||
def result(self) -> object:
|
"""
|
||||||
|
count occurrences of log entry types
|
||||||
|
"""
|
||||||
|
__name__ = "LogEntryCount"
|
||||||
|
|
||||||
|
def result(self) -> defaultdict:
|
||||||
return self.store
|
return self.store
|
||||||
|
|
||||||
def process(self, entry: dict) -> bool:
|
def process(self, entry: dict) -> bool:
|
||||||
|
|
@ -48,3 +63,34 @@ class LogEntryCountAnalyzer(Analyzer):
|
||||||
self.store = defaultdict(lambda: 0)
|
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):
|
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:
|
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
|
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):
|
def __init__(self, settings: LogSettings):
|
||||||
super().__init__(settings)
|
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",
|
"logFormat": "sqlite",
|
||||||
"entryType": "@class",
|
"entryType": "@class",
|
||||||
"spatials":["de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"],
|
"spatials": [
|
||||||
"actions":["...QuestionAnswerEvent", "...SimuAnswerEvent"],
|
"de.findevielfalt.games.game2.instance.log.entry.LogEntryLocation"
|
||||||
|
],
|
||||||
|
"actions": [
|
||||||
|
"...QuestionAnswerEvent",
|
||||||
|
"...SimuAnswerEvent"
|
||||||
|
],
|
||||||
|
"boards": [
|
||||||
|
"de.findevielfalt.games.game2.instance.log.entry.ShowBoardLogEntry"
|
||||||
|
],
|
||||||
"analyzers": {
|
"analyzers": {
|
||||||
"analyzer": [
|
"analyzer": [
|
||||||
"LocationAnalyzer"
|
"LocationAnalyzer",
|
||||||
],
|
"LogEntryCountAnalyzer",
|
||||||
|
"LogEntrySequenceAnalyzer",
|
||||||
|
"ActionSequenceAnalyzer"
|
||||||
|
],
|
||||||
"analyzer.biogames": [
|
"analyzer.biogames": [
|
||||||
"BoardDurationAnalyzer"
|
"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
|
spatials = None
|
||||||
actions = None
|
actions = None
|
||||||
analyzers = []
|
analyzers = []
|
||||||
|
boards = None
|
||||||
|
sequences = None
|
||||||
|
|
||||||
def __init__(self, json_dict):
|
def __init__(self, json_dict):
|
||||||
self.log_format = json_dict['logFormat']
|
self.log_format = json_dict['logFormat']
|
||||||
self.entry_type = json_dict['entryType']
|
self.entry_type = json_dict['entryType']
|
||||||
self.spatials = json_dict['spatials']
|
self.spatials = json_dict['spatials']
|
||||||
self.actions = json_dict['actions']
|
self.actions = json_dict['actions']
|
||||||
|
self.boards = json_dict['boards']
|
||||||
for mod in json_dict['analyzers']:
|
for mod in json_dict['analyzers']:
|
||||||
for name in json_dict['analyzers'][mod]:
|
for name in json_dict['analyzers'][mod]:
|
||||||
self.analyzers.append(getattr(sys.modules[mod], name))
|
self.analyzers.append(getattr(sys.modules[mod], name))
|
||||||
|
self.sequences = json_dict['sequences']
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str({
|
return str({
|
||||||
|
|
@ -26,11 +30,13 @@ class LogSettings:
|
||||||
"entryType": self.entry_type,
|
"entryType": self.entry_type,
|
||||||
"spatials": self.spatials,
|
"spatials": self.spatials,
|
||||||
"actions": self.actions,
|
"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)))
|
return LogSettings(json.load(open(file)))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -51,8 +57,17 @@ if __name__ == '__main__':
|
||||||
if analyzer.process(entry):
|
if analyzer.process(entry):
|
||||||
break
|
break
|
||||||
for analyzer in analyzers:
|
for analyzer in analyzers:
|
||||||
print("* Result for " + str(type(analyzer)))
|
print("* Result for " + analyzer.name())
|
||||||
print(analyzer.result())
|
print(analyzer.result())
|
||||||
coords = analyzers[0].render()
|
#for analyzer in analyzers:
|
||||||
with open("test.js", "w") as out:
|
# if analyzer.name() in ["LogEntryCount", "ActionSequenceAnalyzer"]:
|
||||||
out.write("coords = "+coords)
|
# 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