Exemple #1
0
 def __init__(self, file, from_pickle=False):
     if from_pickle:
         data = pickle.load(file)
         Checker.clean_global_init(data["checker_meta"])
         Seat.counters = data["seats_meta"]
         self.__dict__.update(data["controller"].__dict__)
         return
     self.email_handle = list()
     self.mode = {"people": "None"}
     self.last_change = None
     self.people = pd.DataFrame()
     self.auds = dict()
     self.inds = list()
     self.teams = list()
     self.seed = 1
     found_main_settings = False
     excel_file = ExcelFile(file)
     for name in excel_file.sheet_names:
         raw_frame = excel_file.parse(name, index_col=None, header=None)
         unresolved_dict = splitter(raw_frame, named=True)
         if "main_settings" in unresolved_dict.keys():
             if found_main_settings:
                 raise ControllerException("Две страницы с общими настройками!")
             found_main_settings = True
             Checker.raw_global_init(unresolved_dict)
             self.checker = Checker()
     if not found_main_settings:
         raise TypeError("Настройки не найдены, на странице с настройками нужен ключ main_settings")
     for name in excel_file.sheet_names:
         raw_frame = excel_file.parse(name, index_col=None, header=None)
         unresolved_dict = splitter(raw_frame, named=True)
         if "main_settings" not in unresolved_dict.keys():
             tmp = Auditory(unresolved_dict, outer_name=name)
             if tmp.inner_name in self.auds.keys():
                 del tmp
                 raise TypeError("Есть одинаковые аудитории")
             else:
                 self.auds[tmp.inner_name] = tmp
     self._message_upd()
Exemple #2
0
    def __init__(self, raw_settings, outer_name):
        """
        :param raw_settings: dict
        :param outer_name: str
        :return:
        """
        self.checker = Checker()
        self.team_handler = set()
        self.outer_name = outer_name

        if not self._check_settings(fact=set(raw_settings.keys()),
                                    req=self._required_general_options):
            raise NotEnoughSettings(fact=set(raw_settings.keys()),
                                    req=self._required_general_options,
                                    name="Проверка основных тегов на листе",
                                    aud=self.outer_name)
        self._init_settings(raw_settings["settings"])
        klass_yx = self._read_klass(raw_settings["klass"])
        school_yx = self._read_school(raw_settings["school"])
        self._init_seats(raw_settings["seats"])
        self.klass_school_town_dyx = self._eval_map_conditions(school=school_yx, klass=klass_yx)
Exemple #3
0
class Auditory(SafeClass):

    CHECK = ["available", "class_8", "class_9",             # Для виджета
             "class_10", "class_11", "individual", "command"]
    export_names = oDict([("old_capacity", "Вместительность"), ("capacity", "Вместительность с доп проходами"),
                          ("total", "Сидит"), ("arrived", "Сидит(+)"),
                          ("teams", "Команд"), ("teams_arrived", "Команд(+)"),
                          ("team_members", "Командников"), ("team_members_arrived", "Командников(+)"),
                          ("n8", "8кл"), ("n8_a", "8кл(+)"),
                          ("n9", "9кл"), ("n9_a", "9кл(+)"),
                          ("n10", "10кл"), ("n10_a", "10кл(+)"),
                          ("n11", "11кл"), ("n11_a", "11кл(+)")])

    _required_general_options = {"settings", "seats", "klass", "school"}

    _required_settings_options = {"name", "available", "class_8", "class_9",
                                  "class_10", "class_11", "individual", "command",
                                  "over_place", "over_row"}

    _required_klass_values = {"far": 'Далеко', "close": 'Рядом', "target": 'Участник'}

    _required_school_values = {"far": 'Далеко', "close": 'Рядом', "target": 'Участник'}

    _required_seats_values = {"seat": 'Место', 'fake_seat': 'Не Место', "not_allowed": 'Проход'}

    _standard_settings_column_names = ["key", "description", "code", "result"]

    _required_klass_values_condition = {_required_klass_values["far"]: Ch(None, None),
                                        _required_klass_values["close"]: Ch(None, None),
                                        _required_klass_values["target"]: Ch(lambda x: x == 1, "== 1")}

    _required_school_values_condition = {_required_school_values["far"]: Ch(None, None),
                                         _required_school_values["close"]: Ch(None, None),
                                         _required_school_values["target"]: Ch(lambda x: x == 1, "== 1")}

    _required_settings_values_condition = {"name": Ch(None, None),
                                           "available": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "class_8": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "class_9": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "class_10": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "class_11": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "individual": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "command": Ch(lambda x: x in {1, 0}, "in {0, 1}"),
                                           "over_place": Ch(lambda x: x in {1, 2}, "in {1, 2}"),
                                           "over_row": Ch(lambda x: x in {1, 2, 3}, "in {1, 2, 3}")}

    _required_klass_shape = (7, 7)

    _required_school_shape = (7, 7)

    _required_seats_shape = None

    _required_settings_shape = (11, 4)

    def _create_paths(self):
        self.old_capacity = self.map.capacity
        if self.settings["over_row"] != 1:
            trigger = 1
            for row in range(self.map.shape[0]):
                if any([seat.status for seat in self.map[row, :]]):
                    if trigger % self.settings["over_row"] == 0:
                        for seat in range(self.map.shape[1]):
                            self.map.switch_off_by_yx((row, seat))
                    trigger += 1
                else:
                    trigger = 1
        if self.settings["over_place"] != 1:
            trigger = 1
            for seat in range(self.map.shape[1]):
                if any([seat.status for seat in self.map[:, seat]]):
                    if trigger % self.settings["over_place"] == 0:
                        for row in range(self.map.shape[0]):
                            self.map.switch_off_by_yx((row, seat))
                    trigger += 1
                else:
                    trigger = 1
        # тут убираются те места, которые мы исключили сами
        for yx in product(range(self.map.shape[0]), range(self.map.shape[1])):
            if self.map[yx].meta_status == 2:
                self.map.switch_off_by_yx(yx)

    @staticmethod
    def _get_matrix_condition_places(matrix) -> set:
        """
        :type matrix: np.matrix
        :return:
        """
        center = np.where(matrix == "target")
        close = np.where(matrix == "close")
        y = close[0] - center[0]      # смещение по у
        x = close[1] - center[1]      # смещение по х
        close = set(zip(y, x))
        return close

    def _init_settings_from_dict(self, settings):
        self.settings = settings
        self.inner_name = str(self.settings["name"])
        restricted = set()
        for cl in ["class_8", "class_9", "class_10", "class_11"]:
            if not self.settings[cl]:
                restricted.add(cl)
        self.restricted_klasses = restricted

    def _init_settings(self, matrix):
        """
        :type matrix: np.matrix
        :return:
        """
        # Проверяем наличие ошибок неправильного заполнения таблицы свойств
        if not self._check_shape(fact=matrix.shape,
                                 req=self._required_settings_shape):
            raise WrongShapeException(fact=matrix.shape,
                                      req=self._required_settings_shape,
                                      name="Проверка размерности таблицы с настройками аудитории",
                                      aud=self.outer_name)
        if not self._check_nans(fact=matrix):
            raise NansInMatrixException(name="Проверка наличия отсутствующих значений в настройках аудитории",
                                        aud=self.outer_name)
        # Чтобы проверить саму табличку надо проделать несколько махинаций, ведь по умолчанию все в виде матриц
        settings = pd.DataFrame(matrix[1:], columns=matrix[0])
        settings.columns = self._standard_settings_column_names
        settings.set_index("key", inplace=True)
        # Проверяем все ли настнойки внесены в табличку
        if not self._check_settings(fact=set(settings.index),
                                    req=self._required_settings_options):
            raise NotEnoughSettings(fact=set(settings.index),
                                    req=self._required_settings_options,
                                    name="Проверка вхождения всех необходимых\
переменных по ключу в настройках аудитории",
                                    aud=self.outer_name)
        # Проверяем, что это именно то, что мы ожидали получить на входе
        if not self._check_values_condition(fact=settings["code"].to_dict(),
                                            req=self._required_settings_values_condition):
            raise ValuesConditionException(fact=settings["code"].to_dict(),
                                           req=self._required_settings_values_condition,
                                           name="Проверка валидности ввода настроек в таблицу",
                                           aud=self.outer_name)
        self._init_settings_from_dict(settings["code"].to_dict())

    def _read_klass(self, matrix) -> set:
        """
        :type matrix: np.matrix
        :return:
        """
        klass_condition = matrix
        if not self._check_shape(fact=klass_condition.shape,
                                 req=self._required_klass_shape):
            raise WrongShapeException(fact=klass_condition.shape,
                                      req=self._required_klass_shape,
                                      name="Проверка размерности матрицы близости для класса",
                                      aud=self.outer_name)
        if not self._check_nans(fact=klass_condition):
            raise NansInMatrixException(name="Проверка наличия отсутствующих значений \
в матрице близости для класса",
                                        aud=self.outer_name)
        # Чтобы убелиться что там присутстуют все необходимые значения и их нужное количество,
        # Можно составить словарь(см ниже)
        key, frequency = np.unique(klass_condition.flatten(), return_counts=True)
        # Проверяем есть ли там ожидаемые значения
        if not self._check_settings(fact=set(key),
                                    req=set(self._required_klass_values.values()),
                                    way="<="):
            raise NotEnoughSettings(fact=set(key),
                                    req=set(self._required_klass_values.values()),
                                    name="Проверка на допустимые значения в матрице близости для класса\n\
Если там нет ни одного 'Близко', это будет считаться ошибкой",
                                    way="<=",
                                    aud=self.outer_name)
        klass_freq_dict = dict(zip(key, frequency))
        # Проверяем, что там указано ровно одно место для участника
        if not self._check_values_condition(fact=klass_freq_dict,
                                            req=self._required_klass_values_condition):
            raise ValuesConditionException(fact=klass_freq_dict,
                                           req=self._required_klass_values_condition,
                                           name="Проверка, что в матрице близости для класса ровно один 'Участник'",
                                           aud=self.outer_name)
        klass_condition[klass_condition == self._required_klass_values["far"]] = "far"
        klass_condition[klass_condition == self._required_klass_values["close"]] = "close"
        klass_condition[klass_condition == self._required_klass_values["target"]] = "target"
        klass_yx = self._get_matrix_condition_places(klass_condition)
        return klass_yx

    def _read_school(self, matrix) -> set:
        """
        :type matrix: np.matrix
        :return:
        """
        school_condition = matrix
        if not self._check_shape(fact=school_condition.shape,
                                 req=self._required_school_shape):
            raise WrongShapeException(fact=school_condition.shape,
                                      req=self._required_school_shape,
                                      name="Проверка размерности матрицы близости для школы",
                                      aud=self.outer_name)
        self._check_nans(fact=school_condition)
        key, frequency = np.unique(school_condition.flatten(), return_counts=True)
        # Значения заполненных ячеек
        if not self._check_settings(fact=set(key),
                                    req=set(self._required_school_values.values()),
                                    way="<="):
            raise NotEnoughSettings(fact=set(key),
                                    req=set(self._required_school_values.values()),
                                    name="Проверка значений в матрице",
                                    way="<=",
                                    aud=self.outer_name)
        school_freq_dict = dict(zip(key, frequency))
        # Участник ровно один
        if not self._check_values_condition(fact=school_freq_dict,
                                            req=self._required_school_values_condition):
            raise ValuesConditionException(fact=school_freq_dict,
                                           req=self._required_school_values_condition,
                                           name="Проверка, что в матрице близости для школы ровно один 'Участник'",
                                           aud=self.outer_name)
        school_condition[school_condition == self._required_school_values["far"]] = "far"
        school_condition[school_condition == self._required_school_values["close"]] = "close"
        school_condition[school_condition == self._required_school_values["target"]] = "target"
        school_yx = self._get_matrix_condition_places(school_condition)
        return school_yx

    def _init_seats(self, matrix):
        """
        :type matrix: np.matrix
        :return:
        """
        # Карта рассадки
        seats_map = matrix
        # Для нее отлько NaN и значения
        if not self._check_nans(fact=seats_map):      # В данном случае пофиг на размерность
            raise NansInMatrixException(name="Проверка наличия отсутствующих значений в карте рассадки",
                                        aud=self.outer_name)
        # Надо убедиться, что нигде не допущено опечаток
        key, frequency = np.unique(seats_map.flatten(), return_counts=True)
        if not self._check_settings(fact=set(key),
                                    req=set(self._required_seats_values.values()),
                                    way="<="):      # Это может оказаться просто пустой лист, на который можно забить
            raise NotEnoughSettings(fact=set(key),
                                    req=set(self._required_seats_values.values()),
                                    way="<=",
                                    aud=self.outer_name)
        seats_map[seats_map == self._required_seats_values["seat"]] = 1
        seats_map[seats_map == self._required_seats_values["fake_seat"]] = 2
        seats_map[seats_map == self._required_seats_values["not_allowed"]] = 0
        self.map = Mapping(seats_map, str(self.settings["name"]))
        self._create_paths()

    @staticmethod
    def _eval_map_conditions(school, klass) -> dict:
        """
        Преобразовывает входные диапазоны в вид,
        подготовленный для единообразной проверки
        *пока что буду считать, что там,
        где проверяется школа, должен проверяться и город
        :type school: set
        :type klass: set
        """
        klass_school_town = school & klass
        sc_and_town_only = school - klass_school_town
        kl_only = klass - klass_school_town
        res = dict()
        for dyx in klass_school_town:
            res[dyx] = {"klass": True, "school": True, "town": True}
        for dyx in sc_and_town_only:
            res[dyx] = {"klass": False, "school": True, "town": True}
        for dyx in kl_only:
            res[dyx] = {"klass": True, "school": False, "town": False}
        return res

    def __init__(self, raw_settings, outer_name):
        """
        :param raw_settings: dict
        :param outer_name: str
        :return:
        """
        self.checker = Checker()
        self.team_handler = set()
        self.outer_name = outer_name

        if not self._check_settings(fact=set(raw_settings.keys()),
                                    req=self._required_general_options):
            raise NotEnoughSettings(fact=set(raw_settings.keys()),
                                    req=self._required_general_options,
                                    name="Проверка основных тегов на листе",
                                    aud=self.outer_name)
        self._init_settings(raw_settings["settings"])
        klass_yx = self._read_klass(raw_settings["klass"])
        school_yx = self._read_school(raw_settings["school"])
        self._init_seats(raw_settings["seats"])
        self.klass_school_town_dyx = self._eval_map_conditions(school=school_yx, klass=klass_yx)

    def _rand_loop_insert(self, data, available):
        """
        :type data: dict
        :type available: set
        :return:
        """
        if not available:
            raise EndLoopException
        for_check = random.sample(available, 1)[0]
        available.remove(for_check)
        if self._scan(for_check, data):
            self.map.insert(for_check, data)
            self.team_handler.add(for_check)
        else:
            self._rand_loop_insert(data, available)

    def _scan(self, yx, person) -> bool:
        """
        :type yx: (int, int)
        :type person: dict
        :return:
        """
        for dyx, todo in self.klass_school_town_dyx.items():
            coord = (yx[0] + dyx[0], yx[1] + dyx[1])
            if not self.checker.compare(one=person,
                                        two=self.map.get_data(coord),
                                        task=todo):
                return False
        return True

    def __getattr__(self, item):
        where = object.__getattribute__(self, "map")
        return getattr(where, item)

    def __repr__(self):
        res = "<{name}[{av}]:{capacity}({total})>".format(**self.info)
        return res

    def __hash__(self):
        return hash(self.inner_name)

    def __lt__(self, other):
        return self.inner_name < other.inner_name

    def __le__(self, other):
        return self.inner_name <= other.inner_name

    def __gt__(self, other):
        return self.inner_name > other.inner_name

    def __ge__(self, other):
        return self.inner_name >= other.inner_name

    def __eq__(self, other):
        return self.inner_name == other.inner_name

    @property
    def info(self) -> dict:
        info = self.map.mapping_info
        info["name"] = self.inner_name
        info["old_capacity"] = self.old_capacity
        info["av"] = "+" if self.settings["available"] else "-"
        info["com"] = "+" if self.settings["command"] else "-"
        info["ind"] = "+" if self.settings["individual"] else "-"
        info["kl8"] = "+" if self.settings["class_8"] else "-"
        info["kl9"] = "+" if self.settings["class_9"] else "-"
        info["kl10"] = "+" if self.settings["class_10"] else "-"
        info["kl11"] = "+" if self.settings["class_11"] else "-"
        return info

    @property
    def summary(self) -> str:
        message = """
Аудитрия [{av}] {name}
Доступность: K[{com}], И[{ind}]
Всего мест:         {capacity: <3}     | 8  класс[{kl8}]:{n8:<3}({n8_a})
Посажено:           {total: <3}({arrived:<3})| 9  класс[{kl9}]:{n9:<3}({n9_a})
Из них командных:   {team_members: <3}({team_members_arrived:<3})| 10 класс[{kl10}]:{n10:<3}({n10_a})
Всего команд:       {teams: <3}({teams_arrived:<3})| 11 класс[{kl11}]:{n11:<3}({n11_a})
""".format(**self.info)
        return message

    @property
    def people_table(self) -> pd.DataFrame:
        table = pd.DataFrame.from_records(self.map.get_all_seated())
        return table

    def rand_insert(self, data):
        """
        :type data: dict
        :return:
        """
        # доступна ли аудитория вообще?
        if not self.settings["available"]:
            raise EndLoopException
        # Проверка, можно ли сажать данного участника
        # Критерий "командный/индивидуальный"
        if data["team"] == "и":
            if not self.settings["individual"]:
                raise EndLoopException
        else:
            if not self.settings["command"]:
                raise EndLoopException
        # Критерий по классу
        if data["klass"] in self.restricted_klasses:
            raise EndLoopException
        not_visited = self.map.available_seats.copy()
        self._rand_loop_insert(data=data, available=not_visited)

    def rand_insert_team(self, team):
        """
        :type team: list
        :return:
        """
        if not self.map.capacity > 0:
            raise EndLoopException
        # поместится ли команда?
        if self.checker.settings["max_compart"] < (self.map.counter + len(team)) / self.old_capacity:
            raise EndLoopException
        # Дальнейшик проверки для простоты находятся во вспомогательной функции
        self.team_handler = set()
        try:
            for member in team:
                self.rand_insert(member)
        except EndLoopException:
            for ups in self.team_handler:
                self.map.remove(ups)
            # Для конроллера необходимо словить исключение в этом случае
            raise EndLoopException

    def map_with_data_to_writer(self, writer, seats_format, data):
        writer.write(0, 0, "Абс.")
        for y in range(self.map.shape[0]):
            writer.write(y + 1, 0, "ряд " + str(y + 1))
        for x in range(self.map.shape[1]):
            writer.write(0, x + 1, "мст " + str(x + 1))
        for y, x in product(range(self.map.shape[0]), range(self.map.shape[1])):
            person = self.map.get_data((y, x))
            if self.map.m[(y, x)]:
                task = person[data]
            elif self.map.m[(y, x)].status:
                task = "..."
            else:
                task = "______"
            writer.write(y + 1, x + 1, task, seats_format)

    def map_with_status_to_writer(self, writer, seats_format):
        writer.write(0, 0, "Абс.")
        for y in range(self.map.shape[0]):
            writer.write(y + 1, 0, "ряд " + str(y + 1))
        for x in range(self.map.shape[1]):
            writer.write(0, x + 1, "мст " + str(x + 1))
        for y, x in product(range(self.map.shape[0]), range(self.map.shape[1])):
            task = "______"
            if self.map[y, x].status:
                task = str(self.map[y, x].yx)
            writer.write(y + 1, x + 1, task, seats_format)

    def switch_on(self):
        if self.settings["available"] != 1:
            self.settings["available"] = 1

    def switch_off(self):
        if self.settings["available"] != 0:
            self.settings["available"] = 0

    def __del__(self):
        Seat.counters["seated"] -= self.info["total"]
        Seat.counters["arrived"] -= self.info["arrived"]

    def refresh(self, new_settings):
        self.settings.update(new_settings)
        self._init_settings_from_dict(self.settings)
Exemple #4
0
class Controller(SafeClass):
    inner_name = Checker.inner_name
    required_data_cols = oDict([("email", "email"), ("fam", "Фамилия"), ("name", "Имя"), ("otch", "Отчество"),
                                ("town", "Город"), ("school", "Школа"), ("team", "Команда"),
                                ("klass", "Класс")])
    _default_full_dict = required_data_cols.copy()
    _default_full_dict.update([("aud", "Ауд."), ("row", "Ряд"),
                               ("col", "Место"), ("arrived", "Отметка о прибытии"), ("key", "Ключ")])
    _mini_out = ["fam", "name", "otch", "aud", "row", "col"]
    _razdatka_cols = ["fam", "name", "otch", "klass", "row", "col", "Пришел?", "Справка?"]
    max_iter = 20

    CHECK = ["cl7_8", "cl7_9", "cl7_10", "cl7_11", "cl8_9", "cl8_10", "cl8_11", "cl9_10",  # Для виджета
             "cl9_11", "cl10_11", "one_school",
             "one_town", "com_in_one",
             "debug_mode"]

    SCALE = [{"name": "max_compart", "var": (0, 1)}]

    @property
    def settings(self):
        return self.checker.settings

    def __getitem__(self, item):
        return self.auds[item]

    def __init__(self, file, from_pickle=False):
        if from_pickle:
            data = pickle.load(file)
            Checker.clean_global_init(data["checker_meta"])
            Seat.counters = data["seats_meta"]
            self.__dict__.update(data["controller"].__dict__)
            return
        self.email_handle = list()
        self.mode = {"people": "None"}
        self.last_change = None
        self.people = pd.DataFrame()
        self.auds = dict()
        self.inds = list()
        self.teams = list()
        self.seed = 1
        found_main_settings = False
        excel_file = ExcelFile(file)
        for name in excel_file.sheet_names:
            raw_frame = excel_file.parse(name, index_col=None, header=None)
            unresolved_dict = splitter(raw_frame, named=True)
            if "main_settings" in unresolved_dict.keys():
                if found_main_settings:
                    raise ControllerException("Две страницы с общими настройками!")
                found_main_settings = True
                Checker.raw_global_init(unresolved_dict)
                self.checker = Checker()
        if not found_main_settings:
            raise TypeError("Настройки не найдены, на странице с настройками нужен ключ main_settings")
        for name in excel_file.sheet_names:
            raw_frame = excel_file.parse(name, index_col=None, header=None)
            unresolved_dict = splitter(raw_frame, named=True)
            if "main_settings" not in unresolved_dict.keys():
                tmp = Auditory(unresolved_dict, outer_name=name)
                if tmp.inner_name in self.auds.keys():
                    del tmp
                    raise TypeError("Есть одинаковые аудитории")
                else:
                    self.auds[tmp.inner_name] = tmp
        self._message_upd()

    def coords_by_email(self, email) -> dict:
        """
        Ищет место, на котором сидит участник с указанным email
        :param email:
        :return:
        """
        for aud in self.auds.values():
            try:
                result = aud.coords_by_email_in_aud(email)
                return result
            except KeyError:
                continue
        raise KeyError(email)

    def _rand_loop_insert(self, data, available):
        """
        Рекурсивная часть
        :param dict data: чел
        :param set available: оставшиеся аудитрии на проверку
        :return:
        """
        if not available:
            raise NoFreeAuditory("Нет свободных аудиторий")
        for_check = random.sample(available, 1)[0]
        available.remove(for_check)
        try:
            for_check.rand_insert(data)
        except EndLoopException:
            self._rand_loop_insert(data, available)

    def _rand_loop_team_insert(self, data, available):
        """
        Рекурсивная часть
        :param dict data:
        :param set available:
        :return:
        """
        if not available:
            raise NoFreeAuditory("Нет свободных аудиторий")
        for_check = random.sample(available, 1)[0]
        available.remove(for_check)
        try:
            for_check.rand_insert_team(data)
        except EndLoopException:
            self._rand_loop_team_insert(data, available)

    def _split_people(self):
        """
        Блок разбивает загруженных людей
        в пачки команд и индивидуалов в
        соответствии с настройками
        """
        self.inds = list()
        self.teams = list()
        tmp = pd.DataFrame(self.people.drop(["aud", "row", "col"], errors="ignore", axis=1))
        self.inds = tmp.query("team == 'и'").to_dict(orient="records")
        print(self.checker.settings["com_in_one"])
        if not self.checker.settings["com_in_one"]:
            self.inds.extend(tmp.query("team != 'и'").to_dict(orient="records"))
        else:
            for team_number in list(np.unique(tmp.query("team != 'и'")["team"])):
                query = "team == " + str(team_number)
                self.teams.append(tmp.query(query).to_dict(orient="records"))

    @mutable
    def switch_on_aud(self, audname):
        self.auds[audname].switch_on()

    @mutable
    def switch_off_aud(self, audname):
        self.auds[audname].switch_off()

    @mutable
    def load_auditory(self, file):
        """
        Повторяющиеся загружены не будут
        :param file:
        :return:
        """
        excel_file = ExcelFile(file)
        for name in excel_file.sheet_names:
            raw_frame = excel_file.parse(name, index_col=None, header=None)
            unresolved_dict = splitter(raw_frame, named=True)
            if "settings" in unresolved_dict.keys():
                tmp = Auditory(unresolved_dict, outer_name=name)
                if tmp.inner_name in self.auds.keys():
                    del tmp
                else:
                    self.auds[tmp.inner_name] = tmp

    @mutable
    def delete_auditory(self, audname):
        try:
            del self.auds[str(audname)]
        except KeyError:
            raise ControllerException("Такой аудитории не существует: {}".format(audname))

    @mutable
    def load_emails(self, file):
        table = pd.read_excel(file, sheet_name=0).applymap(clr)
        if not self._check_settings(fact=set(table.columns),
                                    req={"email"},
                                    way=">="):
            raise NotEnoughSettings(fact=set(table.columns),
                                    req={"email"},
                                    way=">=")
        self.email_handle = list(table.to_dict()["email"].values())

    @mutable
    def load_people(self, file):
        """
        Требования к входным данным:
        1) см required_data_cols
        2) если с целью получить права editor, то
           надо дополнительно указать все места
           Аудитория, Ряд, Место
        """
        people = pd.read_excel(file, sheet_name=0).applymap(clr)
        if not self._check_settings(fact=set(people.columns),
                                    req=set(self.required_data_cols.values()),
                                    way=">="):
            raise NotEnoughSettings(fact=set(people.columns),
                                    req=set(self.required_data_cols.values()),
                                    way=">=")
        people = people.rename(columns=swap(self._default_full_dict))
        if (any([item in people.columns for item in ["aud", "row", "col"]]) and
                not all([item in people.columns for item in ["aud", "row", "col"]])):
            raise ControllerException("Некорректно заданы столбцы с местами")
        # Присвоение уровня доступа
        if len(people) == 0:
            self.mode["people"] = "None"            # Нельзя ничего делать с рассаженными участниками
            return
        elif "aud" in people.columns:
            people['aud'] = np.vectorize(str)(people['aud'])
            self.mode["people"] = "input/edit"      # Можно менять информацию, изымать и добавлять
        else:
            self.mode["people"] = "input"           # Можно только добавлять

        self.people = people
        self._split_people()

    def rand_aud_insert_team(self, data):
        not_visited = set(self.auds.values())
        self._rand_loop_team_insert(data=data, available=not_visited)

    def rand_aud_insert(self, data):
        not_visited = set(self.auds.values())
        self._rand_loop_insert(data=data, available=not_visited)

    @mutable
    def clean_seated(self):
        """
        Изымает абсолютно всех незаблокированных(!)
        участников из аудиторий
        :return:
        """
        for aud in self.auds.values():
            aud.clean_all()

    @mutable
    def lock_seated_on_key_by_email(self, key: str):
        """
        Блокироует участников по ключу.
        Это позволяет получать определенного
        рода спокойствие за исполняемые опасные
        действия над рассаженными участниками
        :param key: Ключ
        :return:
        """
        if not key:
            raise ControllerException("Некорректный ключ")
        if not self.email_handle:
            raise ControllerException("Список email-ов пуст")
        for email in self.email_handle:
            coords = self.coords_by_email(email)
            self.auds[coords["aud"]].lock_by_coords((coords["row"], coords["col"]), key)

    @mutable
    def unlock_seated_by_email(self):
        """
        Разблокировывает участников по email игнорирует ключи
        """
        if not self.email_handle:
            raise ControllerException("Список email-ов пуст")
        for email in self.email_handle:
            coords = self.coords_by_email(email)
            self.auds[coords["aud"]].unlock_by_coords((coords["row"], coords["col"]))

    @mutable
    def lock_seated_on_key(self, key: str):
        """
        Блокироует участников по ключу.
        Это позволяет получать определенного
        рода спокойствие за исполняемые опасные
        действия над рассаженными участниками
        :param key: Ключ
        :return:
        """
        if not key:
            raise ControllerException(key)
        for aud in self.auds.values():
            aud.lock_all(key)

    @mutable
    def unlock_seated_by_key(self, key):
        """
        Разблокировывает участников по ключу
        :param str key: Ключ
        :return:
        """
        if key in self.key_holder:
            for aud in self.auds.values():
                aud.unlock_all(key)
        else:
            raise ControllerException("Key Error %s" % key)

    @mutable
    def mark_arrival_by_email(self):
        """
        Ставит отметку о прибытии для участников, чьи email подгружены
        """
        if not self.email_handle:
            raise ControllerException("Список email-ов пуст")
        for email in self.email_handle:
            try:
                seat = self.coords_by_email(email)
                self.auds[str(seat["aud"])].mark_arrival_by_coords((seat["row"], seat["col"]))
            except KeyError:
                continue

    @mutable
    def update_seated_by_coords(self, forced=False):
        """
        Меняет всю информацию на актуальную.
        Необходимо иметь места, где менять и
        загруженных людей(что менять)
        :param bool forced: менять ли людей на местах, которые заблокированы?
        """
        if "edit" not in self.mode["people"].split("/"):     # Проверка уровня доступа
            raise ControllerException("PermissionError")
        for new_data in self.people.to_dict(orient="records"):
            for_insert = new_data.copy()
            del for_insert["aud"], for_insert["row"], for_insert["col"]
            self.auds[new_data["aud"]].update_by_coords((new_data["row"], new_data["col"]), for_insert, forced=forced)

    @mutable
    def update_seated_by_email(self, forced=False):
        """
        Меняет всю информацию на актуальную.
        Необходимо иметь обычную допустимую входную загрузку
        :param bool forced: менять ли людей на местах, которые заблокированы?
        """
        if "input" not in self.mode["people"].split("/"):     # Проверка уровня доступа
            raise ControllerException("PermissionError")
        for new_data in self.people.to_dict(orient="records"):
            for_insert = new_data.copy()
            coords = self.coords_by_email(for_insert["email"])
            if "edit" in self.mode["people"].split("/"):
                del for_insert["aud"], for_insert["row"], for_insert["col"]
            self.auds[coords["aud"]].update_by_coords((coords["row"], coords["col"]), for_insert, forced=forced)

    @mutable
    def remove_seated_by_coords(self):
        """
        Изымает всех людей по указанным местам.
        Необходимо иметь места.
        Не изымает людей с заблокированных мест.
        """
        if "edit" not in self.mode["people"].split("/"):     # Проверка уровня доступа
            raise ControllerException("PermissionError")
        for remove_data in self.people.to_dict(orient="records"):
            self.auds[remove_data["aud"]].remove_by_coords((remove_data["row"], remove_data["col"]))

    @mutable
    def remove_seated_by_email(self):
        """
        Изымает всех людей по указанным email.
        Необходимо иметь подгруженные email.
        Не изымает с заблокированных мест.
        """
        if not bool(self.email_handle):
            raise ControllerException("Список email-ов пуст")
        for email in self.email_handle:
            try:
                seat = self.coords_by_email(email)
                self.auds[seat["aud"]].remove_by_coords((seat["row"], seat["col"]))
            except KeyError:
                continue

    @mutable
    def clear_buffer(self):
        """
        Очистить подгруженных людей и emails.
        Обнуляет уровень доступа.
        """
        self.people = pd.DataFrame(columns=self.required_data_cols.keys())
        self.inds = list()
        self.teams = list()
        self.email_handle = list()
        self.mode["people"] = "None"

    @mutable
    def place_loaded_people(self):
        """
        Рассаживает подгруженных участников
        """
        if "input" not in self.mode["people"].split("/"):
            raise ControllerException("PermissionError")
        if len(self.people) and len(self.seated_people):
            if (set(self.people["email"])) & set(self.seated_people["email"]):
                message = "{} загруженных участников рассажены!"
                message = message.format(len(set(self.people["email"]) & set(self.seated_people["email"])))
                raise ControllerException(message)
        random.seed(self.seed)
        for team in self.teams:
            self.rand_aud_insert_team(team)
        for individual in self.inds:
            self.rand_aud_insert(individual)

    @mutable
    def refresh(self, new_settings):
        self.checker.refresh(new_settings)
        self._split_people()

    @property
    def seated_people(self) -> pd.DataFrame:
        """
        Собирает в общую табличку всех рассаженных участников
        """
        seated = list()
        for aud in sorted(self.auds.values()):
            seated.extend(aud.get_all_seated())
        frame = pd.DataFrame.from_records(seated)
        return frame

    def comparison(self):
        if len(self.people) and len(self.seated_people):
            other = self.people.set_index("email", drop=False)
            seated = self.seated_people.set_index("email", drop=False)
            emails = set(seated.index.tolist()) & set(other.index.tolist())
            not_seated = set(other.index.tolist()) - set(seated.index.tolist())
            result = {"there": list(), "here": list()}
            for email in emails:
                here = seated.loc[email].to_dict()
                there = other.loc[email].to_dict()
                for key in self.required_data_cols.keys():
                    if clr(here[key]) != clr(there[key]):
                        result["there"].append(there)
                        result["here"].append(here)
                        break
            result["not_seated"] = other.loc[not_seated].rename(columns=self._default_full_dict)
        else:
            result = {"there": list(), "here": list(), "not_seated": pd.DataFrame()}
        if len(self.email_handle) and len(self.seated_people):
            seated = self.seated_people.set_index("email", drop=False)
            result["emails_not_seated"] = pd.DataFrame(list(set(self.email_handle) - set(seated.index.tolist())))
        else:
            result["emails_not_seated"] = pd.DataFrame()
        return {"here": pd.DataFrame.from_records(result["here"]).rename(columns=self._default_full_dict),
                "there": pd.DataFrame.from_records(result["there"]).rename(columns=self._default_full_dict),
                "not_seated": result["not_seated"],
                "emails_not_seated": result["emails_not_seated"]}

    @property
    def not_seated(self):
        if not len(self.people) or not len(self.seated_people):
            raise ControllerException("Нету людей для сравнения")
        other = self.people.set_index("email", drop=False)
        seated = self.seated_people.set_index("email", drop=False)
        emails = set(other.index.tolist()) - set(seated.index.tolist())
        return other.loc[emails]

    def save_summary_to_txt(self, file):
        """
        <Статистика по аудиториям>
        :param file:
        :return:
        """
        message = ""
        for aud in sorted(self.auds.values()):
            message += aud.summary + "\n"
        file.write(message)

    def save_summary_to_excel(self, file):
        """
        <Статистика по аудиториям>
        :param file:
        :return:
        """
        with pd.ExcelWriter(file) as writer:
            summary = list()
            for aud in sorted(self.auds.values()):
                summary.append(aud.info)
            table = pd.DataFrame.from_records(summary, index="name")
            table.ix[:, Auditory.export_names.keys()].rename(columns=Auditory.export_names).to_excel(writer)

    def save_seated_to_excel(self, file, full=False):
        """
        <Участники>
        [True, False]
        Выводит в эксель инфу по участникам, с их местами и тд
        :param file: куда выводить
        :param bool full: всю ли инфу выводить или только на стенд?
        :return:
        """
        with pd.ExcelWriter(file) as writer:
            if not full:
                select = self._mini_out
            else:
                select = list(self._default_full_dict.keys())
            frame = self.seated_people
            frame.ix[:, select].sort_values("fam", ascending=True).rename(
                columns=self._default_full_dict).reset_index(drop=True).to_excel(writer, "На стенд")
            sheet = writer.sheets["На стенд"]
            sheet.set_column("B:D", 15)
            sheet.repeat_rows(0)
            sheet.hide_gridlines(0)
            sheet.set_paper(9)

    def save_maps_with_data_to_excel(self, file, data):
        """
        <Карта мест с>
        [email, fam, name, otch, town, school, team, klass, arrived, key]
        Выводит карту рассадки в эксель с необходимой информацией
        :param file: куда выводим
        :param data: какая информация нужна
        """
        with xlsxwriter.Workbook(file) as workbook:
            form = workbook.add_format()
            form.set_align('center')
            form.set_bold()
            for aud in sorted(self.auds.values()):
                sheet = workbook.add_worksheet(aud.inner_name)
                aud.map_with_data_to_writer(sheet, form, data)
                sheet.set_header("&L&30 " + aud.inner_name)
                sheet.set_column(0, aud.shape[1], 6.5)
                sheet.hide_gridlines(0)
                if aud.inner_name.startswith("П"):
                    sheet.set_paper(8)
                    sheet.set_print_scale(75)
                else:
                    sheet.set_paper(9)
                sheet.set_landscape()
                sheet.fit_to_pages(1, 1)
                sheet.set_page_view()

    def save_maps_with_status_to_excel(self, file):
        """
        <Карта с местами>
        Выводит карту рассадка с пропечатанными местами в эксель
        :param file: куда выводим
        """
        with xlsxwriter.Workbook(file) as workbook:
            form = workbook.add_format()
            form.set_align('center')
            form.set_bold()
            for aud in sorted(self.auds.values()):
                sheet = workbook.add_worksheet(aud.inner_name)
                aud.map_with_status_to_writer(sheet, form)
                sheet.set_header("&L&30 " + aud.inner_name)
                sheet.hide_gridlines(0)
                sheet.set_column(0, aud.shape[1], 6.5)
                if aud.inner_name.startswith("П"):
                    sheet.set_paper(8)
                    sheet.set_print_scale(75)
                else:
                    sheet.set_paper(9)
                sheet.set_landscape()
                sheet.fit_to_pages(1, 1)
                sheet.set_page_view()

    def save_razdatka_to_excel(self, file):
        """
        <Раздатка>
        Выводит списки с участниками в каждой аудитории в ексель
        :param file: куда выводим
        :return:
        """
        with pd.ExcelWriter(file) as writer:
            for aud in sorted(self.auds.values()):
                aud.people_table.ix[:, self._razdatka_cols].sort_values("fam", ascending=True).rename(
                    columns=self._default_full_dict).to_excel(
                    writer, aud.inner_name, index=False)
                sheet = writer.sheets[aud.inner_name]
                sheet.set_column("A:C", 15)
                sheet.set_column("D:H", 9)
                sheet.set_margins(left=0.2, right=0.2)
                sheet.repeat_rows(0)
                sheet.hide_gridlines(0)
                sheet.set_paper(9)
                sheet.set_header("&L&30 " + aud.inner_name)
                sheet.set_page_view()

    def to_pickle(self, file):
        """
        Сохраняемся вместе с классовой инфой
        :param file: куда сохраняемся
        """
        prepared = dict([("checker_meta", Checker.settings),
                         ("seats_meta", Seat.counters),
                         ("controller", self)])
        pickle.dump(prepared, file)

    @property
    def key_holder(self):
        return np.unique(reduce(list.__add__, [aud.keys for aud in self.auds.values()], []))

    @property
    def info(self):
        def s(x, subset, condition=None):
            if not len(x):
                return set()
            else:
                if not condition:
                    return set(x.ix[:, subset])
                else:
                    return set(x.query(condition).ix[:, subset])

        info = dict()
        info["last_change"] = self.last_change
        info["n_auds"] = len(self.auds)
        info["n_used_auds"] = sum([aud.settings["available"] for aud in self.auds.values()])
        info["seated_teams"] = len(reduce(lambda x, y: x.union(y), [aud.teams_set for aud in self.auds.values()]))
        info["arrived_teams"] = len(reduce(lambda x, y: x.union(y),
                                           [aud.teams_arrived_set for aud in self.auds.values()]))
        info["seats_available"] = sum([aud.capacity for aud in self.auds.values() if aud.settings["available"]])
        info["seated"] = Seat.total_seated()
        info["seats_total"] = sum([aud.capacity for aud in self.auds.values()])
        info["arrived"] = Seat.total_arrived()
        info["mode"] = self.mode["people"]
        info["emails"] = len(self.email_handle)
        info["people"] = len(self.people)
        info["intersect_teams"] = len(s(self.people, "team", condition="team != 'и'") & s(self.seated_people, "team"))
        info["intersect_people"] = len(s(self.people, "email") & s(self.seated_people, "email"))
        info["intersect_emails"] = len(set(self.email_handle) & s(self.seated_people, "email"))
        info["n_teams"] = len(self.teams)
        all_keys = reduce(list.__add__, [aud.keys for aud in self.auds.values()], [])
        key, frequency = np.unique(all_keys, return_counts=True)
        info["keys"] = dict(zip(key, frequency))
        return info

    def __str__(self):
        return self._message

    def update(self):
        self.last_change = datetime.datetime.now().strftime("%A, %d. %B %Y %I:%M%p")
        self._message_upd()

    def _message_upd(self):
        self._message = """
Последнее изменение {last_change}
Режим -{mode}-

Временные списки для взаимодействия с рассаженными
    Загружено(сидит) человек    {people:<5}({intersect_people})
                     команд     {n_teams:<5}({intersect_teams})
                     emails     {emails:<5}({intersect_emails})

--------------------------
Доступно(всего)  аудиторий  {n_used_auds:<5}({n_auds})
                 мест       {seats_available:<5}({seats_total})
Посажено(пришло) человек    {seated:<5}({arrived})
                 команд     {seated_teams:<5}({arrived_teams})
Ключи блокировки мест {{ключ: количество}}
    {keys}""".format(**self.info)