177 lines
6.0 KiB
Python
177 lines
6.0 KiB
Python
import logging
|
|
from collections import defaultdict, namedtuple
|
|
from types import SimpleNamespace
|
|
from typing import List, NamedTuple
|
|
|
|
import os
|
|
|
|
from util import json_path, combinate
|
|
from . import Result, LogSettings, Analyzer, ResultStore
|
|
from .default import CategorizerStub
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class BoardDurationAnalyzer(Analyzer):
|
|
"""
|
|
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().get()])
|
|
|
|
def result(self, store: ResultStore) -> None:
|
|
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?
|
|
store.add(Result(type(self), result))
|
|
|
|
def process(self, entry: dict) -> bool:
|
|
entry_type = entry[self.settings.type_field]
|
|
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 = []
|
|
self.last = {}
|
|
|
|
|
|
class SimulationRoundsAnalyzer(Analyzer):
|
|
__name__ = "SimuRounds"
|
|
|
|
def __init__(self, settings: LogSettings):
|
|
super().__init__(settings)
|
|
self.store = defaultdict(lambda: -1) # TODO verify
|
|
|
|
def result(self, store: ResultStore) -> None:
|
|
store.add(Result(type(self), dict(self.store)))
|
|
|
|
def process(self, entry: dict) -> bool:
|
|
entry_type = entry[self.settings.type_field]
|
|
if entry_type in self.settings.custom['simulation_rounds']:
|
|
if entry["answers"][self.settings.type_field] in self.settings.custom["simu_data"]:
|
|
simu_id = entry['answers']["@id"]
|
|
self.store[simu_id] += 1
|
|
return False
|
|
|
|
|
|
class ActivationSequenceAnalyzer(Analyzer):
|
|
__name__ = "ActivationSequence"
|
|
|
|
def __init__(self, settings: LogSettings):
|
|
super().__init__(settings)
|
|
self.store = []
|
|
|
|
def result(self, store: ResultStore) -> None:
|
|
store.add(Result(type(self), self.store))
|
|
|
|
def process(self, entry: dict) -> bool:
|
|
if entry[self.settings.type_field] in self.settings.sequences['start']:
|
|
if entry['cache']:
|
|
self.store.append(entry['cache']['@id'])
|
|
else:
|
|
logger.error("null cache")
|
|
return False
|
|
|
|
|
|
class BiogamesCategorizer(CategorizerStub):
|
|
__name__ = "BiogamesCategorizer"
|
|
def __init__(self, settings: LogSettings):
|
|
super().__init__(settings)
|
|
|
|
def process(self, entry: dict) -> bool:
|
|
if self.key is "default":
|
|
if entry[self.settings.type_field] in self.settings.custom['instance_start']:
|
|
self.key = entry[self.settings.custom['instance_id']]
|
|
return False
|
|
|
|
|
|
class ActivityMapper(Analyzer):
|
|
__name__ = "ActivityMapper"
|
|
def __init__(self, settings: LogSettings) -> None:
|
|
super().__init__(settings)
|
|
self.store: List[self.State] = []
|
|
self.instance_config_id: str = None
|
|
self.filters = SimpleNamespace()
|
|
self.filters.start = lambda entry: combinate(self.settings.custom["sequences2"]["start"], entry)
|
|
self.filters.end = lambda entry: combinate(self.settings.custom["sequences2"]["end"], entry)
|
|
|
|
self.State: NamedTuple = namedtuple("State", ["sequence", "events", "track", "timestamp"])
|
|
|
|
def result(self, store: ResultStore) -> None:
|
|
for active_segment in self.store: # active_segment → sequence or None (None → map active)
|
|
seq_data_url = "{host}/game2/editor/config/{config_id}/sequence/{sequence_id}/".format(
|
|
host=self.settings.custom["host"],
|
|
config_id=self.instance_config_id,
|
|
sequence_id=active_segment.sequence,
|
|
)
|
|
seq_data = self.settings.source._get(seq_data_url).json()
|
|
#TODO: use sequence names
|
|
for event in active_segment.events:
|
|
if event[self.settings.type_field] in self.settings.boards:
|
|
local_file = "static/progress/images/{config_id}/{sequence_id}/{board_id}".format(
|
|
config_id=self.instance_config_id,
|
|
sequence_id=active_segment.sequence,
|
|
board_id=event["board_id"])
|
|
event["image"] = local_file[16:]
|
|
if os.path.exists(local_file):
|
|
continue
|
|
url = "{host}/game2/editor/config/{config_id}/sequence/{sequence_id}/board/{board_id}/".format(
|
|
host=self.settings.custom["host"],
|
|
config_id=self.instance_config_id,
|
|
sequence_id=active_segment.sequence,
|
|
board_id=event["board_id"]
|
|
)
|
|
board = self.settings.source._get(url)
|
|
if not board.ok:
|
|
raise ConnectionError()
|
|
data = board.json()
|
|
preview_url = json_path(data, "preview_url.medium")
|
|
logger.debug(preview_url)
|
|
os.makedirs(local_file[:-len(event["board_id"])], exist_ok=True)
|
|
self.settings.source.download_file(self.settings.custom['host'] + preview_url, local_file)
|
|
store.add(Result(type(self), {"instance": self.instance_config_id, "store": [x._asdict() for x in self.store]}))
|
|
|
|
def process(self, entry: dict) -> bool:
|
|
if self.instance_config_id is None:
|
|
if entry[self.settings.type_field] in self.settings.custom['instance_start']:
|
|
self.instance_config_id = json_path(entry, self.settings.custom['instance_config_id'])
|
|
if self.filters.start(entry):
|
|
self.store.append(
|
|
self.State(
|
|
sequence=json_path(entry, json_path(self.settings.custom, "sequences2.id_field")),
|
|
events=[],
|
|
track=[],
|
|
timestamp=entry['timestamp']))
|
|
elif self.filters.end(entry) or not self.store:
|
|
self.store.append(self.State(sequence=None, events=[], track=[], timestamp=entry['timestamp']))
|
|
|
|
if entry[self.settings.type_field] in self.settings.spatials:
|
|
self.store[-1].track.append(
|
|
{
|
|
'timestamp': entry['timestamp'],
|
|
'coordinates': json_path(entry, "location.coordinates"),
|
|
'accuracy': entry['accuracy']
|
|
}
|
|
)
|
|
else:
|
|
self.store[-1].events.append(entry)
|
|
return False
|