import logging from collections import defaultdict, namedtuple, OrderedDict from types import SimpleNamespace from typing import List, NamedTuple from util import json_path, combinate from util.download import download_board from . import Result, LogSettings, Analyzer, ResultStore from .default import CategorizerStub, Store 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)/1000)) 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: instance_config_id = self.instance_config_id for active_segment in self.store: # active_segment → sequence or None (None → map active) host = self.settings.custom["host"] seq_data_url = "{host}/game2/editor/config/{config_id}/sequence/{sequence_id}/".format( host=host, config_id=instance_config_id, sequence_id=active_segment.sequence, ) source = self.settings.source seq_data = source._get(seq_data_url) if not seq_data.ok: logger.error("HTTP ERROR:", seq_data) seq_data = {} else: seq_data = seq_data.json() # TODO: use sequence names logger.warning(seq_data) for event in active_segment.events: if event[self.settings.type_field] in self.settings.boards: sequence_id = active_segment.sequence board_id = event["board_id"] local_file = download_board(board_id, host, instance_config_id, sequence_id, source) if local_file is not None: event["image"] = local_file[16:] store.add(Result(type(self), {"instance": 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 class BiogamesStore(Store): __name__ = "BiogamesStore" def result(self, store: ResultStore) -> None: result = OrderedDict() for event in self.store: if event[self.settings.type_field] in self.settings.boards: sequence_id = json_path(event, json_path(self.settings.custom, "sequences2.id_field")) board_id = event["board_id"] local_file = download_board( board_id=board_id, host=self.settings.custom["host"], instance_config_id=json_path(self.store[0], self.settings.custom["instance_config_id"]), sequence_id=sequence_id, source=self.settings.source) if local_file is not None: event["image"] = local_file[16:] result[event["timestamp"]] = event store.add(Result(type(self), result)) def process(self, entry: dict) -> bool: self.store.append(entry) return False