Русский
Русский
English
Статистика
Реклама

Fsm

Как выбрать Service Desk для управления мобильными сотрудниками? И на что обратить внимание при внедрении?

17.11.2020 16:15:10 | Автор: admin

Статья рекомендуется к прочтению тем, у кого болит вопрос управления и контроля персонала на выезде, а также тем, кто собирает полезную информацию перед внедрением FSM или Service Desk системы для управления мобильными сотрудниками.

Приветствую всех. С кем не знаком - Андрей Балякин: 7 лет в сервисном бизнесе, 20 лет в ИТ. Предприниматель. Последние несколько лет - CEO проекта HubEx (ИТ платформы автоматизации выездного обслуживания и управления сервисными процессами).

За время развития проекта, общения с клиентами и разработки продукта в голове собралось приличное количество ценного и полезного опыта. Им и решил поделиться ниже. Развитие отрасли выездного обслуживания, в России, увы в зачатках. Информации крайне мало, так что уверен, статья многим будет полезна.

Цель публикации - собрать мнения и всесторонний опыт тех, кто внедрил какие-либо системы для управления выездными сотрудниками в своей компании и готов поделиться опытом с остальными.

Для начала давайте определимся, о чем пойдет речь: ИТ сообщество подобные системы часто называет ITSM или Service desk с приложением для мобильных сотрудников. В англоязычном мире под этот класс систем есть отдельный термин: Field Service Management или сокращенно - FSM (не путать с Workforce management). В переводе это звучит как управление полевым сервисом, но более понятным будет - управление выездным обслуживанием или управление мобильными сотрудниками.

Чем я поделюсь:

  1. Представлю собранный в течении нескольких лет чек-лист для компаний, которые внедряют FSM решение для управления выездным обслуживанием. Он поможет четко сформулировать свои требования к системе

  2. Расскажу о различиях продуктов класса управление выездным обслуживанием

  3. Обсудим, для каких компаний какие типы систем предпочтительнее

  4. Объясню, на что следует обращать внимание перед внедрением решения для управления мобильным персоналом в своей компании

  5. Приведу пример сервисного процесса и его стадий для более глубокого понимания требований к системе, а также удобной настройки процесса по шаблону, если систему вы уже выбрали

  6. Предоставлю перечень возможных функций, которые могут потребоваться от системы для управления выездным сервисом. Поможет сравнить и выбрать подходящее решение

Чтобы избежать субъективных мнений по основным вопросам и принципиальным отличиям различных систем управления выездным обслуживанием, начну с мнения аналитического агентства Gartner. Он недавно выпустил свежую публикацию на тему Field Service Management-а в дополнении к обновлению магических квадрантов. Вот ссылка на статью для англо-понимающих: https://www.gartner.com/doc/reprints?id=1-2438LY1F&ct=200903&st=sb

Что говорит уважаемый Gartner?

Во первых:

Решения по управлению мобильными сотрудниками делятся на 2 категории, а при выборе следует учитывать следующие моменты:

Исходя из отрасли вашей организации, сложности требований и готовности вкладывать в дорогостоящую разработку необходимого именно вам кастомного функционала, выберите, какое решение наилучшим образом подходит для вашей организации:

  1. мировой лидер в классе (имеется в ввиду следующее: берем SAP, Microsoft, Oracle и т.п. мировых лидеров + стартуем дорогостоящий проект адаптации/доработки/внедрения и пилим, пилим, пилим. Стоимость таких проектов, в среднем, от 200k$ до 900k$ по РФ)

  2. выбираем нишевое отраслевое решение от локального вендора.

Gartner, советует: Выбирайте подходящее решение, исходя из вашей сферы деятельности, сложности и объема требований, а также необходимости кастомизированной настройки и готовности к доработкам.

Получается, что первый вариант - дорого, богато, никаких компромиссов. Будет сделано именно то, что нужно вам (тут все часто зависит от зрелости команды внедрения и готовности бизнеса к долгострою и серьезным инвестициям) . Второй - значительно меньшие инвестиции и возможность в разумные сроки закрыть 80% функциональных требований. Но именно 80, не 100%!

Во вторых:

Gartner делит вендоров (разработчиков) систем управления выездным персоналом (FSM-систем) на три категории.

Appointment-Centric: в основе лежит управление заявками и расписаниями сотрудников

Признаки: у сотрудников множество поездок/заявок в день. В этом случае, при выборе системы, наиболее важным для компании будут возможности системы удобно управлять заявками и оптимизировать расписания сотрудников.

Equipment-Centric: в основе лежит выездное техническое обслуживание и ремонт оборудования

Признаки: компания специализируется на выездном обслуживании оборудования, технические специалисты имеют узкую специализацию. Для них важно оперативно получать информационную поддержку и помощь. Gartner обращает внимание на то, что такая поддержка и сопровождение выездных специалистов с узкой специализацией наиболее приоритетная задача для компании.

В этом случае важно обратить внимание на наличие у решения мобильного приложения с функционалом, которого будет достаточно для поддержания полноценного рабочего процесса мобильного выездного инженера. Думаю, очевидно, почему я говорю именно о наличии мобильного приложения, а не просто мобильной веб-версии? Если нет - спрашивайте, обсудим в комментариях.

Для примера:

  1. Приложение мобильного сотрудника должно помогать ему получить необходимую документацию по ремонтируемому оборудованию, технологические карты или коды ошибок

  2. Выполнение сложных работ должно контролироваться специализированными чек-листами, а помощь экспертов по оборудованию должна быть доступна онлайн через смартфон.

Gartner обращает внимание на то, что поддержка и сопровождение выездных специалистов с узкой специализацией через мобильное приложение наиболее важная задача.

Outcome-Centric: для компаний, использующих модель Equipment-as-a-Service (Оборудование, как сервис/услуга)

Смысл следующий: организация (подрядчик) предоставляет, а заказчик оплачивает услугу, а не приобретает в собственность оборудование, которое его этой услугой могло бы обеспечить. Пример такого контракта из ИТ-тематики - покопийная печать. Подрядчик продает услугу печати документов с оплатой за поштучные копии (отпечатки), а не устройство печати (принтер/МФУ) или контракт по обслуживанию оргтехники.

Онлайн мониторинг работы оборудования, в этом случае, наиболее важен, так как поломка и простой оборудования равен простою бизнеса.

Для таких компаний часто важнее всего быстрая диагностика оборудования и получение данных о работе (поломках) онлайн.

Что требуется от ИТ-системы в этом случае?

  1. Наличие мобильного приложения, чтобы в режиме реального времени получать данные мониторинга оборудования, в том числе на выездах, и оперативно принимать меры

  2. Модуль мониторинга оборудования, который, во-первых, сможет измерять показатель, за который платит заказчик, а во-вторых, контролировать работоспособность и фиксировать ошибки в работе оборудования

В дополнение к классификации Gartner, в России точно стоит добавить еще один вид ИТ-систем, которые часто выбирают российские компании при поиске решений для управления выездным обслуживанием.

Ticket-centric: сервис деск на базе ITSM service desk/service management систем, расширенный мобильным приложением для выездных сотрудников и модулем управления расписаниями.

Фактически получаются системы вида Appointment-centric (ориентированные на работу с заявками), но, в отличии от последних, обычно имеющие ряд ограничений. Возможности этого класса решений обычно упираются в возможности ITSM систем, сделанных для ИТ и работающих в рамках ITIL практик. Как итог - выездным сервисным сотрудникам оказывается неудобно работать по процессам ИТ, у них своя специфика, отличная от ИТ-сервиса.

Для лучшего понимания различий между этими системами приведу упрощенные схемы процессов, которые заложены в фундамент подобных решений.

Appointment-centric: в основе лежит управление заявками и расписаниями сотрудников

Сотрудник и расписание вынесены в начало схемы намеренно, так как именно эти сущности определяют логику работы систем данного класса. Продвинутые Appointment-centric FSM системы позволяют удобно планировать загрузку персонала с учетом расписаний работы объектов. Они учитывают сменный график работы сотрудников, автоматически планируют и оптимизируют расписания, динамически перераспределяют заявки в случае форс-мажора.

Equipment-centric: выездное техническое обслуживание и ремонт оборудования

В начало процесса добавляется оборудование с иерархией до узлов и агрегатов, когда необходимо.

Все заявки привязываются к оборудованию. Выполнение плановых работ также привязывается к оборудованию.

В итоге, оборудование обрастает историей обслуживания, технической документаций и информацией, которая позволяет выездным специалистам качественно и быстро производить обслуживание и эффективно ремонтировать технически-сложные устройства.

Вся аналитика ремонтов, обслуживание и устранение неисправностей собирается вокруг оборудования. Фактически это получается FSM по модели ТОиР.

Для общего понимания: ТОиР системы (системы технического обслуживания и ремонтов) - отдельный класс решений, созданный специально для обслуживания технически-сложного оборудования. К ТОиР нужна еще достаточно мощная система диспетчеризации и управления мобильным персоналом (FSM), чтобы внедрение ТОиР привело к повышению эффективности эксплуатации оборудования, а не наоборот.

Equipment-Centric системы управления выездным обслуживанием для большинства компаний могут заменить ТОиР решения. Такой вариант подходит в тех случаях, когда в процессе деятельности компании ремонты осуществляются мобильными сотрудниками или подрядчиками, а базовых ТОиР функций вполне достаточно для контроля обслуживания и ремонтов оборудования.

Outcome-centric: в основе лежит оборудование как услуга

Тут мы видим обвязку к упрощенной модели Equipment-centric FSM. Создание вложенных заявок на нескольких сотрудников обычно не требуется, однако добавляется обязательное условие получения информации о работе оборудования. Например, интеграция решения, по управлению выездным персоналом с IoT или мониторинговой системой, сообщающей о состоянии и работоспособности оборудования.

Outcome-centric: оплата за результат или достижение согласованных показателей

Например, клиент платит абонентскую плату за достижение показателя бесперебойной работы оборудования. Если уровень доступности ниже согласованного контрактом SLA - плата снижается или отсутствует вовсе.

Ticket-centric: в основе лежит работа с заявками. Своего рода сервис деск, расширенный приложением для мобильных сотрудников

Я сознательно не добавил сюда свойственные FSM решениям блоки планирование расписаний, оборудование или выполнение, так как мобильное приложение в таких решениях обычно очень простое, а его возможности ограничены базовыми функциями: посмотреть текст заявки и отчитаться о выполнении в простой форме. Кэш режим, который необходим мобильным сотрудникам, тоже либо вовсе отсутствует, либо ограничен просмотром заявок. Поменять стадию заявки или заполнить чек листы - только при наличии доступа в интернет.

Ниже, в отдельном разделе, я приведу примеры возможностей FSM-решений, дефицит которых испытывают компании, которые ошиблись в выборе и внедрили решение не того класса.

Еще один тип систем, которые пытаются использовать ряд компаний при реализации задачи управления выездными сотрудниками - настройка CRM под задачи FSM.

Какие функции вам точно потребуется или почему не стоит заменять специализированные решения на всемогущие CRM

Почему я добавил CRM? Причины две:

  1. Среди ИТ-решений по управлению мобильными специалистами иногда встречается такое понятие как CRM для выездных сотрудников. По факту это то же самое FSM-решение с урезанной функциональностью или Service desk с примитивным мобильным приложением. Осмелюсь предположить, что CRM в названии присутствует по простой причине: FSM у нас в России не раскачена, такие запросы пользователи не ищут, а в поисковиках светиться нужно. И чем проще и понятнее ты назовешься - тем лучше. А CRM, как известно знают все

  2. Потому, что часть наших клиентов начинали автоматизировать сервис выездного обслуживания на базе имеющихся в компании CRM. При этом попытки большинства компаний выстроить работу с выездными сотрудниками на базе CRM решений обычно заканчиваются разочарованиями. В некоторых очень простых случаях это кажется возможным, но выстроить эффективный сервисный процесс не получится.

Заявка хоть и похожа на воронку со стадиями, которые можно как-то назвать, но в CRM нет и половины функций, которые требуются компаниям с мобильными сотрудниками. Например:

  1. расписания сотрудников и объектов обслуживания в связке с специализаций и географией, позволяющие системе автоматизировано распределять заявки

  2. учет оборудования и привязки к нему заявок / выполняемых работ

  3. GPS-контроль персонала

  4. ТОиР-модуль для работы с плановыми заявками и с возможностью создавать группы заявок по типам объектов, оборудования, видам работ и других сущностей системы

  5. история обслуживания

  6. расчет плановых сроков закрытия заявок согласно SLA с заказчиком

  7. специализированные инструменты для экспресс-подачи заявок заказчиком

  8. учет квалификации сотрудников и специальных документов, которые необходимо иметь для выполнения конкретных видов работ и без которых не получится назначить заявку на специалиста

  9. справочник работ и услуг

  10. иерархия объектов обслуживания, как и самих объектов с гео-привязкой на местности

  11. оффлайн режим в мобильных приложениях

  12. расчет стоимости выполненных работ и оказанных услуг через приложение

  13. возможность администрирования складов, учета складских запасов и остатка материалов, указания в заявке использованных материалов со склада

Это, конечно, далеко не полный список того, что не получится сделать в CRM тем, кому нужно управление заявками и выездными сотрудниками. Если я что-то забыл - дополняйте, пожалуйста, в комментариях. Интересен ваш опыт.

  • CRM - это система преимущественно для отдела продаж. Ее можно использовать для других целей, понимая и принимая ограничения, но тут определенно не стоит рассчитывать на гибкость и высокий уровень автоматизации операционных процессов сервиса.

  • Сервис деск с мобильным приложением для выездных сотрудников дают больше возможностей. Однако стоит внимательно оценить их возможности при выборе подходящей системы под задачи компании.

При выборе системы важно оценить возможность масштабирования вашей организации на основе выбранной системы. По опыту, только после внедрения к компаниям, внедривших систему автоматизации, приходит аппетит, и она начинает понимать и делать те изменения, которые несут нужный эффект.

В следующем разделе я попытался собрать список проблем и вопросом с которыми сталкиваются компании, внедряющие систему управления выездными сотрудниками.

На этот раздел крайне рекомендую обратить особое внимание всех, кто планирует автоматизировать работу выездных сервисных сотрудников.

Для удобства структуру сделал в виде чек-листа, заполнив который по выбранным решениям можно сделать осознанный вывод: подходит решение или не совсем. Если подходит на 85% и больше - повод примерить.

Чек-лист для компаний, внедряющих FSM решение по управлению выездными обслуживанием: на что обратить внимание?

Ниже перечислены те функции, которые могут быть важными или даже критичными для компаний, выбирающих решение по управлению мобильными сотрудниками.

Кроме того, крайне рекомендую обращать внимание на то, КАК они реализованы в конкретной системе, а еще лучше сравнивайте эту реализацию у разных вендоров напрямую.Дьявол, обычно, кроется в деталях.

Выбор по обложке приводит значительным затратам времени, сил и последующему негативному опыту от автоматизации. Сотрудникам только дай повод покритиковать менеджеров, навязывающих им систему. Повторное внедрение это двойной стресс для всех и снижение шансов на успех.

Важный совет для компаний, планирующих автоматизацию управления выездными сотрудниками (это работает и для любых других внедрений):

  • Найдите у себя в компании лидеров мнений.

  • Обсуждайте с ними новые процессы с самого начала.

  • Показывайте систему.

  • Дайте попробовать поработать и собирайте фидбэк.

  • Мотивируйте их на результат (премии по итогам проекта, слава и поощрения, кому что)

Свои люди помогут другим сотрудникам и на доступном языке расскажут об удобстве системы, поддержат советом и покажут, как с системой работать тем, кто с компьютерными новинками на Вы.

Итак, чек-лист по выбору FSM решения.

Отметьте те пункты, которые важны для вашей компании, и оцените по ним различные решения на рынке. У кого есть опыт в работе /внедрениях Service desk или FSM решений для управления заявками и работой выездного персонала: возможно я что-то упустил в списке, так что дополняйте в комментариях. Буду обновлять список. В итоге получим удобный чек-лист для тех, кто выбирает систему управления выездным обслуживанием.2

Сравнение возможностей

Требуется

(да/нет)

Система 1

Система 2

1.

Работа с заявками:

1.1

Возможность интеграции по входящим заявкам с системами заказчиков.

Чтобы диспетчеру не пришлось заводить все заявки вручную

1.2

Возможность загружать заявки пакетами.

Это часто требуется, когда заказчики, подающие заявки, обязуют подрядчика работать в своей системе и не дают возможностей для интеграции

1.3

Омниканальность по входящим обращениям

1.4

Возможность настраивать правила парсинга при импорте заявок через почту.

Чтобы структурированная входящая заявка попадала в систему, автоматически заполняя максимальное количество полей

1.5

История изменения заявок с возможность просмотра кто, где, когда и что делал

1.6

Ролевая модель - возможность выдавать доступ и отображать только ту информацию, которая может быть доступна для сотрудника с данной ролью

1.7

Удобство работы со списком заявок, сортировка, добавление пользовательских полей, перемещение полей в списке, фильтрации - стоит попробовать самостоятельно

1.8

Сохранение быстрых фильтров - сколько сотрудников, столько и вариантов фильтрации.

Один руководитель хочет по нажатию кнопки фильтровать заявки своих сотрудников, другой - выбирать заявки электриков на своем участке

1.9

Создание заявок по расписанию. Обратите внимание, что часто требуется не просто создавать заявки по определенному интервалу (каждый вторник, каждую 2 неделю, каждый месяц), но и за определенное количество дней до дня Х. Т.е. заявка должна появляться не в заданный расписанием день, а за Х дней или часов до него

1.10

Аналитика по заявкам.

Это отдельный тонкий вопрос, детально расскажу ниже

2.

Дочерние/вложенные заявки:

2.1

Дочерние заявки требуются тогда, когда ремонт или обслуживание могут проводиться разными сотрудниками в разное время. При этом требуется собирать статистику работ и управлять ремонтом, как мини-проектом: получили заявку, декомпозировали ее на несколько сотрудников, поставили в расписание и контролируем выполнение заявки

2.2

Полноценная работа с дочерними заявками как с родительскими заявками (не в форме чек-листа: получил заявку, выполнил, а полноценно с движением заявки по регламентированному процессу)

3.

GPS-контроль выездного персонала - важная функция при работе с мобильными сотрудниками

3.1

GPS-контроль сотрудников и отображение их фактического положения на карте. Сбор координат перемещений сотрудников из мобильного приложения. Подключение данных с трекера - если актуально. Большинству достаточно сбора геопозиции из приложений сотрудников, но иногда предпочтительнее трекеры

3.2

Возможность отключить сбор координат для офисных сотрудников, которые пользуются системой, а также сбор координат только в рабочее время сотрудников

3.3

Возможность контроля путевых листов: сверка задекларированного пробега сотрудника с фактическим и расчетным

3.4

Возможность для диспетчера видеть на карте не только местоположение сотрудников, но и заявок, с возможностью их распределения на сотрудников в соответствии с их квалификацией и занятостью

3.5

История перемещений сотрудников в привязке к заявкам

ВАЖНО: для контроля недостаточно понимать, где находится сотрудник, важно знать что он делает: какую заявку выполняет или выполнял, на каком объекте и сколько времени. Это основная причина, почему без системы диспетчеризации заявок геотрекинг мало что дает

4.

Мобильное приложение выездных сотрудников:

4.1

Поддержка iOS и Android смартфонов. Одинаковые возможности приложений.

По опыту, неудобно, когда пользователи iOS и Android имеют разный функционал

4.2

Работа приложения сотрудника в офлайн

На случай, если ваши сотрудники часто бывают на объектах без WiFi и устойчивого интернета, важно иметь возможность просмотра заявок, перевода их по стадиям, заполнения чек-листов, оформлять акты выполненных работ и закрытия заявок офлайн.

4.3

Встроенные чаты между исполнителем и диспетчером

4.4

Возможность опционально добавлять других сотрудников в чат

4.5

Разделение на чаты внутри компании и чаты с клиентами. Ценно, когда ваши заказчики могут подавать заявки через мобильное приложение клиента

4.6

Просмотр истории выполненных заявок по оборудованию (объекту) и истории ремонтов/обслуживания

4.7

Возможность добавлять к заявке не только фото, но и видео-файлы с описанием проблемы

4.8

Возможность переназначения заявки на другого сотрудника (для определенных ролей)

4.9

Добавление кнопок-действий в мобильное приложение без разработки

Понадобится тогда, когда захотите настроить различные сценарии прохождения заявок разного вида. Если эти задачи потребуют выпуска новых версий мобильного приложения и их публикаций в сторах - будет сложно настраивать систему под задачи компании

4.10

Расчет стоимости выполненных работ и автоматическое формирование акта выполненных работ в смартфоне с подписью пальцем или стилусом на экране

4.11

Возможность при помощи мобильного приложения взять оборудование на обслуживание (процесс онбординга нового клиентского оборудования в систему) и промаркировать объект или оборудование

4.12

Согласование заявок из смартфона

4.13

Возможность сделать комментарии обязательными при выполнении определенных действий

5.

Чек-листы

5.1

Возможность подать заявку по пункту чек-листа во время его заполнения

Требуется тогда, когда во время контроля необходимо зафиксировать отклонения от нормы и устранить их

5.2

Возможность затребовать в чек-листе фото- или видео отчет

5.3

Запрет на добавление фото и видео из галереи - только из камеры. Это поможет предотвратить повторное использование фотографий и фальсификацию результатов

5.4

Фиксация места/даты/координат съемки фото и видео

5.5

Привязка чек-листа к оборудованию, объекту или услуге

Чек-лист по оборудованию

Необходим, когда вы хотите регламентировать ремонт конкретного оборудования. Например, до начала ремонта расписаться в журнале, переключить на байпасс и убедиться, что напряжение отсутствует. Либо когда нужно проверить оборудование перед запуском, после остановки и ремонта

Чек-лист по объекту

Например вы можете регламентировать процесс выполнения заявок у конкретного заказчика на конкретном объекте

Чек-лист по работам

Научит новых сотрудников стандартам вашей организации, проконтролирует процесс выполнения всех обязательных пунктов для данного вида работ и поможет зафиксировать результат

Чек-лист по сбору контролируемых параметров

Поможет зафиксировать параметры работы или какие-то показатели на объекте

5.6

Возможность запросить в чек-листе информацию в требуемом формате: ввод чисел, выбор из выпадающего списка, приложение фото- или видео к отдельным пунктам чек-листа

6.

Работа с оборудованием и объектами обслуживания:

6.1

База данных установленного оборудования с иерархией

6.2

Пользовательские поля у оборудования

Например, для учета одного оборудования требуется: парт-номер, серийный номер, год выпуска. Для другого - цвет, срок эксплуатации, производительность и список подрядчиков на сервис. Все индивидуально

6.3

Привязка выполняемых заявок к оборудованию или объектам обслуживания

6.4

Возможность заполнить в мобильном приложении акт выполненных работ и получить подпись клиента на экране смартфона

6.5

Автоматизированный расчет стоимости выполненных работ в мобильном приложении при закрытии заявки

6.6

Возможность просмотра истории обслуживания по оборудованию

6.7

Группировка оборудования по типам (печи, кондиционеры и т.п.) для удобства аналитики

6.8

Принятие оборудования на обслуживание через мобильное приложение

6.9

Маркировка оборудования для безошибочной экспресс-подачи заявки клиентом (снижает количество ошибок и нагрузку на диспетчеров)

6.10

Привязка видов работ к оборудованию - какие работы можно выполнять на оборудовании и по контракту

6.11

Возможность создать электронный паспорт оборудования для сбора всей необходимой информации по оборудованию в одном месте

6.12

Возможность создания контактных лиц в привязке к оборудованию для удобства работы мобильных сотрудников

6.13

Графики работы оборудования или объектов обслуживания

6.14

Признаки, находится ли оборудование на гарантии

6.15

Навыки или допуск, которым должен обладать сотрудник для назначения на ремонт

6.16

Чек-листы по оборудованию

6.18

Выбор сотрудников, ответственных за оборудования

Поможет назначать заявки на тех, кто знает и умеет ремонтировать этот тип оборудования

6.19

Импорт, экспорт, интеграции

6.20

Привязка к оборудованию QR или NFC-метки, которая позволяет удобно подать заявку, если промаркировано оборудование или объект заказчика

6.21

Иерархия оборудования и объектов: объект - оборудование - компонент и тд

6.22

Мобильность оборудования - возможность привязать геопозицию и перемещения гео-метки при перевозке оборудования с объекта на объект

7.

Функции CRM

7.1

Ведение карточек клиентов с реквизитами и платежной информацией (если необходимо выставлять счета)

7.2

Контактные лица

7.3

Возможность привязать к компании обслуживаемое оборудование

7.4

Опция приложить файлы/документы к карточке

Удобно для хранения прайс-листа или контракта в доступе для диспетчера или офисных сотрудников

7.5

Перечень стандартных реквизитов: сайт, почта и тд

7.6

Возможность вести заявки в разрезе заказчиков или объектов оборудования

7.7

Определите другие функции CRM, которые для вас критически важны, если вы планируете использовать FSM-систему и для контроля взаимоотношений с клиентами

8.

Работа с расписаниями

8.1

Распределение заявок в расписание сотрудников на карте

8.2

Управление расписанием в интерфейсе (drag&drop) с просмотром заявок с таблице расписаний или календаре

8.3

Отображение рейтинга сотрудников в расписании

8.4

Выбор рекомендуемых исполнителей (по компетенциям и навыкам)

8.5

Авто-распределение заявок в расписание по различным правилам

9.

Уведомления - какие способы нотификации персонала для вас важны?

9.1

SMS (дорого, но надежно)

9.2

PUSH

9.3

Уведомления в приложении

9.4

Нужно ли диспетчеру видеть, что сотрудник не подтвердил прием заявки и звонить ему

9.5

Требуется ли настраивать текст и логику уведомлений

10.

Автоматическое распределение заявок

10.1

Определите для себя критерии, по которым обучите систему распределять заявки за диспетчера. Чек-листом тут не обойтись, поэтому рекомендую обратить на это внимание при выборе системы, если для вас это важно. Например - должна ли система сама выбирать сотрудника в зависимости от навыков, видов работ, графика и тд?

11.

Настройка стадий заявок и логики перехода со стадии на стадию

11.1

В ряде систем есть конструктор стадий, позволяющий настраивать логику прохождения заявки. В начале пути кажется, что настраивать нечего и у заявки только три стадии: новая, в работе и выполнена. Но все не так просто.

Вот очень простой базовый процесс прохождения заявки на выездное обслуживание:

Вот такой базовый шаблон наш департамент внедрения разработал для того, чтобы настраивать для заказчиков HubEx типовые процессы прохождения заявок:

Далее, на данный процесс накладываются полномочия (что можно делать сотруднику с конкретной ролью на конкретной стадии, а что нельзя).

Дополнительно настраивается система уведомлений (кто что и когда должен получать при поступлении заявок на конкретные стадии или при просрочках / Опозданиях итп).

Аналитика: the last, but not least, как сказали бы англичане: последний по порядку, но не по значению.

С моей точки зрения, это первое, над чем стоит подумать при внедрении. Не последнее уж точно. Делюсь с вами теми показателями, которые те или иные наши клиенты рассматривают, когда говорят об автоматизации.

Прежде отмечу, что важно иметь возможность видеть данные показатели в динамике, а также во всех разрезах, которые так или иначе присутствуют в системе, например в разрезе:

1. Периодов

2. Клиентов

3. Сотрудников

4. Оборудования

5. Объектов

6. Типам заявок

7. Типам работ

8. и т.д.

Оперативные показатели:

1. Кол-во открытых заявок

2. Кол-во не назначенных заявок

3. Кол-во заявок, не принятых в работу

4. Кол-во просроченных заявок

5. Кол-во заявок, по которым исполнитель опаздывает на объект

6. Кол-во заявок, которые истекают сегодня

7. Среднее время реакции диспетчера

8. Кол-во футбольных заявок, по которым исполнители несколько раз отказались от выполнения

Данные показатели предназначены, преимущественно, для диспетчеров или сотрудников, контролирующих назначение или исполнение заявок в срок.

Метрики для руководителей

Для руководителей сервисных организаций важно не только понимать текущую ситуацию в текущий момент, но анализировать множество показателей по разным периодам, а также иметь возможность эти периоды сравнивать, анализировать тренды и динамику.

Условно выделяемые нами показатели можно разделить на следующие категории:

Количественные:

  1. Кол-во созданных заявок за период

  2. Кол-во закрытых заявок за период

  3. Кол-во эскалированных заявок за период

  4. Кол-во закрытых в срок / не в срок заявок

  5. Кол-во отказов исполнителей по заявкам

  6. Динамика создания/закрытия заявок с графиком их сгорания

  7. Соотношение заявок по типу заявки

  8. Соотношения заявок по срочности

  9. Соотношение заявок по типу работ

  10. Соотношение заявок, созданных через разные источники (диспетчера, клиенты самостоятельно, через интеграцию со сторонними сервисами и др.)

  11. Кол-во обслуживаемых объектов / оборудования

  12. Кол-во клиентов

SLA показатели:

  1. Contract Uptime Rate % бесперебойной работы оборудования

  2. First Time Fix Rate - % выполненных заявок с первого посещения

  3. Repeat Visit кол-во повторных визитов на объект по одной и той же заявке

  4. Нарушения сроков разрешения заявок, указанных в SLA

  5. Оценки клиентов по выполненными работам

  6. Превышение фактического времени выполнения заявок относительно планового по типам работ

Временные показатели:

  1. Average Time to Fix среднее время выполнения работ исполнителями

  2. Average Time to Complete среднее время от создания до закрытия заявки

  3. Среднее время реакции диспетчера

  4. Среднее время принятия исполнителем заявки в работу

  5. Среднее время от создания заявки до назначения на исполнителя

  6. Среднее время от принятия заявки исполнителем до переведения в статус В пути

  7. Среднее время исполнителя на дорогу

Финансовые показатели:

  1. Выручка

  2. Прибыль

  3. Средний чек

  4. ТОП клиентов по выручке и прибыли

  5. Доля выполненных заявок по контракту и по сдельной схеме

  6. Service to Cash Time время от завершения работ исполнителем до поступления оплаты от клиента

  7. ABC анализ клиентов

Показатели по исполнителям

  1. Utilization Rate % рабочего времени исполнителей, которое было потрачено на работу и оплачено клиентами

  2. Tasks Per Person среднее кол-во выполненных заявок на одного исполнителя

  3. Отработанные сотрудниками часы

  4. Переработки сотрудников

  5. Оценки клиентов по выполненным исполнителями работам

Это далеко не полный набор показателей, и у каждого бизнеса могут быть свои, индивидуальные показатели и метрики. Однако без понимания ряда из них, компании не могут оценивать эффекты от проводимых изменений.

Итого

Выше я постарался описать базовые принципы и ряд практик, которые должны помочь компаниям войти в автоматизацию сервисных процессов, а именно - осознанно сравнить и выбрать систему по управлению выездным персоналом:

Шаги внедрения

  1. Определите, к какому подклассу должна относиться целевая FSM система для вашей организации: appointment-centric / equipment-centric / outcome-centric или ticket-centric.

  2. Найдите поддержку среди лидеров мнений в вашей организации

  3. Зафиксируйте цели внедрения системы и показатели, оцифровка которых крайне важна

  4. Сформируйте бюджет проекта так как от него будет в первую очередь зависеть класс решений, на которые стоит смотреть и ориентироваться при выборе

  5. Сформулируйте требования к системе, воспользовавшись предложенными выше шаблонами

  6. Проанализируйте рынок решений с учетом типа FSM решения из п.1,

Удачи в нелегком, но важном и абсолютно верном начинании.

Рынок растет, конкуренция становится все плотней среди сервисных компаний и организаций управляющих выездным персоналом. Эффективные компании растут и развиваются значительно быстрее других.

В комментариях буду рад получить обратную связь и дополним статью недостающей информацией. Все оценочные суждения - в другой раз. Также информацию о FSM-системах и трендах в управлении персоналом я публикую в телеграм-канале, кому интересно - welcome.

Заранее благодарю!

Подробнее..

Разделяй и властвуй Использование FSM в Unity

23.04.2021 22:09:05 | Автор: admin

Грамотная архитектура играет ключевую роль при разработке любого программного продукта. Корни большинства распространенных проблем с производительностью, расширяемостью или понятностью кода растут именно из ее отсутствия. Отсутствие строго определенной структуры проекта лишает разработчиков возможности мыслить абстракциями, понимать написанный коллегой код с первого взгляда и предугадывать место возникновения ошибки. А в некоторых случаях человек может запутаться даже в своем собственном коде, перенасыщенном сущностями и компонентами. Но практически каждый программист рано или поздно, сам ли или с помощью умной книжки, знакомится с решениями, которые хороши вне зависимости от контекста. Они настолько эффективны и универсальны, что находят место в решении множества задач, и... Да, знаю, можно не продолжать, все уже и так поняли, что я о шаблонах проектирования. Одни на них молятся, другие находили среди них свои велосипеды. Некоторые утверждают на собеседовании, что изучили их вдоль и поперек и уличили в полной бесполезности. Но все, так или иначе, слышали о них. Сегодня речь пойдет об одном из паттернов - "Состояние". А точнее, о конечных автоматах. Даже если вы относитесь к последней из перечисленных выше групп, вы наверняка сталкивались со следующим инструментом:

Минимальный аниматор главного героя в платформереМинимальный аниматор главного героя в платформере

Аниматоры в Unity построены как раз на конечных автоматах. Каждая анимация группы объектов представлена в виде состояния. Условия и порядок переходов между ними определяется в аниматоре, который является конечным автоматом. Также, неоднократно поднималась тема использования конечных автоматов для описания логики работы объектов со сложным поведением. AI ботов, управление главным героем, вот это все.

Я бы хотел сделать акцент на менее изъезженной теме. Конечный автомат позволяет эффективно управлять состоянием игры в целом. Более того, с его помощью можно однажды описать бизнес-логику абстрактной игры, и отталкиваться в дальнейшем от этой заготовки при разработке прототипов. Так, многие ГК игры, построенные по такому принципу могут отличаться друг от друга на полсотни строк и структуру сцены, реализуя всю логику на автомате. А в сложнейших и запутанных проектах новый функционал может быть внедрен максимально просто путем добавления в существующий автомат нового состояния и добавления переходов в него, исключая возможность возникновения багов и неожиданного поведения. Также, среди плюсов построения бизнес-логики приложения на автоматах можно выделить следующее:

  • Каждое состояние описывает свою логику максимально просто. Нет никаких условных блоков, каждое состояние представляет собой последовательность действий на входе и выходе и список сигналов, которые автомат обрабатывает в текущем состоянии. Таким образом, мы отображаем джойстик на входе в состояние Play и скрываем на выходе. Но даже если каким-то чудом игрок увидел джойстик на экране паузы, он не сможет двигать персонажа. И не потому, что активен еще один флаг isPaused, а просто потому, что в состоянии паузы не предусмотрено принимать сигналы с джойстика.

  • В каждый момент времени однозначно видно, в каком состоянии мы сейчас находимся. Логируя переходы между состояниями и сигналы, посылаемые в автомат мы видим четкую последовательность событий, и состояние, в котором мы находимся.

  • В дополнение к предыдущему пункту, следует отметить, что это также очень удобно в контексте дебага. Посмотрев логи, мы сразу видим, что бот сломался потому, что был инициализирован не после старта уровня, а во время генерации сцены, когда еще не был готов контекст для работы его AI.

  • Многие баги становятся просто невозможны, потому что мы строго определяем условия переходов. Мы точно не попадем в состояние Play, пока состояние WaitMatch не получит сигнал "match_ready", а если мы захотим вернуться в лобби, мы сначала отправим серверу команду об этом, и только после сигнала "room_left" выполним переход.

  • Сама идея логики на автоматах максимально прозрачна. Видя ТЗ мы сразу понимаем, каким будет список состояний, логика, реализованная в каждом из них и граф переходов. Причем, как было отмечено выше, "внешняя" часть графа в большинстве игр будет оставаться неизменной.

Но давайте по порядку. А начну я пожалуй с одного важного принципа, встречающегося в программировании на всех уровнях. Разделение реализации и интерфейса. Логики и визуализации. Разница между серверной архитектурой и клиентской. На каждом уровне мы изолируем сложность внутренней системы, обеспечивая корректную ее работу и предоставляя удобный интерфейс для работы с ней.

Теория автоматов - довольно крупный раздел дискретной математики. Автоматов бывает много, хороших и разных. Так что попытаемся сформулировать наши требования к абстрактному автомату, который будет лежать в основе архитектуры приложения и будет наилучшим образом реализовывать алгоритм его работы.

Определим, что из себя представляет наш конечный автомат. Для этого опишем его структуру:

FSM

AState

- public FSM(AState initState)

- public void Signal(string name, object data = null)

- private void ChangeState(AState newState)

- void Enter()

- void Exit()

- AState Signal()

Итак, мы имеем 2 сущности:

Конечный автомат. Интерфейс у него невелик. Начиная работу в некотором состоянии, он может принимать сигналы извне, передавая их в текущее состояние. И если оно в ответ на сигнал вернуло новое состояние, автомат выполняет переход в него. В момент перехода мы выполняем Exit для последнего состояния и Enter для следующего. Таким образом, у нас получается следующая конструкция:

 public class FSM {   private AState currentState;   public FSM(AState initState) => ChangeState(initState);      private void ChangeState(AState newState)   {     if (newState == null) return;     currentState?.Exit();     currentState = newState;     currentState.Enter();   }   public void Signal(string name, object arg = null)   {     var result = currentState.Signal(name, arg);     ChangeState(result);   } }

Абстрактное состояние. Из соображений удобства и микрооптимизации его методы имеют нехитрую реализацию, которая наследуется потомками, в которых оные не определены.

public class AState{  public virtual void Enter() => null;  public virtual void Exit() => null;  public virtual AState Signal(string name, object arg) => null;}

А в самих состояниях мы просто описываем логику

public class SLoad : AState{    public override void Enter()    {        Game.Data.Set("loader_visible",true);        var load = SceneManager.LoadSceneAsync("SceneGameplay");        load.completed+=a=>Game.Fsm.Signal("scene_loaded");    }    public override void Exit()    {        Game.Data.Set("loader_visible",false);    }        public override AState Signal(string name, object arg)    {        if (name == "scene_loaded")            return new SLobby();        return null;    }    }

Как вы могли заметить, я описал логику переходов между состояниями, но даже вскользь не затронул тему хранения и изменения их списка. Дело в том, что в этом нет смысла, мы можем просто создавать новые состояния при переходе. Экземпляр состояния в большинстве случаев представляет собой 3 ссылки на функции, хранящиеся статически. И переходы между состояниями - не такое частое действие, чтобы дать сколько-то существенный оверхед. Зато мы получаем гору синтаксического сахара и статический анализ в подарок! При желании, проблема аллокаций решается очень легко, но это будет не так удобно. Кроме того, вот пример того, как такое состояние можно легко параметризовать, не усложняя при этом общий интерфейс

public class SMessage : AState{    private string msgText;    private AState next;    public SMessage(string messageText, AState nextState)    {        msgText = messageText;        btnText = buttonText;        next = nextState;    }        public override void Enter()    {        Game.Data.Set("message_text", msgText);        Game.Data.Set("window_message_visible",true);    }    public override void Exit()    {        Game.Data.Set("window_message_visible",false);    }        public override AState Signal(string name, object arg)    {        if (name == "message_btn_ok")             return next;        return null;    }}

Таким образом вместо того, чтобы делать усложненную систему переходов c аргументами, мы передаем их в конструктор и создаем экземпляр состояния с желаемыми параметрами.

...case "iap_ok":return new SMessage("Item purchased! Going back to store.", new SStore());...

В этой статье я не буду задерживаться на устройстве структуры Game.Data, используемой в состояниях, достаточно просто отметить, что это словарь, в элементах которого реализован шаблон "Наблюдатель". Объекты сцены, например, элементы UI, могут подписываться на изменения в нем и отображать гарантированно актуальные данные. Она играет ключевую роль в связи между визуализацией и логикой в моих примерах, но не является необходимым условием для работы с конечными автоматами. Сигналы в автомат же можно отправлять откуда угодно, но одним из самых универсальных примеров будет следующий скрипт.

public class ButtonFSM : MonoBehaviour, IPointerClickHandler{    public string key;        public override void OnPointerClick(PointerEventData eventData)    {        Game.Fsm.Signal(key);    }}

Иными словами, мы при клике по кнопке(на самом деле, любому CanvasRenderer) передаем соответствующий сигнал в автомат. При переходе между состояниями мы можем любым удобным нам способом включать и выключать разные Canvas, менять маски, используемые в Physics.Raycast и даже иногда менять Time.timeScale! Как бы ужасно и бескультурно это ни казалось на первый взгляд, пока сделанное в Enter отменяется в Exit, оно гарантированно не может доставить каких-либо неудобств, так что вперед! Главное - не переусердствуйте.

Подробнее..

KPI сервиса с выездными сотрудниками какие цели ставить и как достигать?

28.05.2021 10:14:13 | Автор: admin

Каждый день к нам приходят сервисные компании, управляющие штатом мобильных сотрудников и подрядчиков, с задачами оптимизации сервисных процессов на базе нашей ИТ-платформы HubEx. Чтобы помочь определиться с целями внедрения и системно ступить на путь автоматизации сервиса, и подготовлен данный материал.

Из чего состоит сервисный процесс почти любой компании с мобильными сотрудниками

  1. Персонал: поиск, обучение и работа с персоналом

  2. Операционные процессы:

    1. диспетчеризация (приём и обработка клиентских обращений),

    2. выполнение заявок,

    3. закупка расходных материалов или запчастей,

    4. работа с подрядчиками,

    5. управление уровнем клиентского сервиса и сбор обратной связи,

    6. контроль перемещений сотрудников и контроль за расходом ГСМ

    7. в компаниях, обслуживающих оборудование, добавляется плановое обслуживание или выполнение регулярных (контрактных) работ (планирование и выполнение ППР)

Если говорить о первом шаге автоматизации в сервисных компаниях с выездным персоналом, то 25% компаний начинают автоматизацию с внедрения CRM системы, а 75% - автоматизируют работу по заявкам и контроль выездного персонала. И это не случайно, ведь операционка для b2b сервиса - это прежде всего ежедневная кропотливая работа с обращениями клиентов и их заявками.

Какие цели чаще всего ставят перед собой компании, ступившие на путь автоматизации сервиса с выездными сотрудниками?

Довольно часто руководители сервисных организаций ставят перед сервисом следующие цели:

1. снижение затрат на диспетчерскую

2. оптимизация затрат на фронт-офис персонал (сервисные специалисты)

3. снижение затрат на подрядчиков

4. снижение затрат на бэкофис, то есть административный персонал, не оказывающий сервис напрямую, но обеспечивающий бесперебойный процесс работы компании: отдел кадров, бухгалтерия, транспортный цех, отдел закупок, служба работы с подрядчиками, административный отдел, финансовая служба, тендерный отдел, служба безопасности и т.д.

В сервисе все вышеперечисленные цели можно назвать верхнеуровневыми. Они хорошо подходят как среднесрочные стратегические цели как для сервисной службы, оказывающей сервис внутри организации, так и для сервисной компании, работающей на открытом рынке. Реалистичный срок достижения таких целей - от полугода до года, а порой даже дольше. Чтобы увидеть и измерить результат за более короткий период, нужно декомпозировать цели на более узкие и научиться их измерять в интервале не более месяца.

По опыту многих сервисных организаций, если ставить среднесрочные цели и не задумываться об их декомпозиции, а также не формализовывать процесс контроля и измерения, цели остаются невыполненными.

Давайте разберем все эти цели на составляющие и определим показатели нижнего уровня (так называемые тактические цели), которые помогут достичь указанные выше среднесрочные цели

1. Цель 1: Снижение затрат на диспетчерскую и процессы диспетчеризации

Диспетчеризация в сервисе - это процесс приема обращений клиентов, регистрации заявок, обработка и распределение заявок по сотрудникам, поиск требуемого специалиста, а также контроль исполнения заявок в срок.

Прием клиентских обращений может быть автоматизированным или ручным:

Автоматизированный прием обращенийэто процесс, при котором заявки в сервисное подразделение поступают максимально пре-квалифицированными и не требуют ручной обработки для их дальнейшего назначения на сервисных специалистов. Например, в поступающей заявке уже указан объект обслуживания или даже конкретная единица оборудования. К заявке приложены фото или видео проблемы, указан вид неисправности или тип услуги и определен срок выполнения работ согласно SLA с заказчиком. То есть для назначения заявки не требуется дополнительной ее ручной обработки.

Ручной прием обращенийэто обычно телефон, мессенджеры или почта без средств автоматизации.

В большинствеслучаев, в начале пути автоматизации, процесс диспетчеризации в сервисной компании полностью ручной.

Так, для организации работы по заявке диспетчеру требуется:

  1. пообщаться с заказчиком по телефону или обработать обращение поступившее на почту / в мессенджер,

  2. зарегистрировать заявку,

  3. вручную классифицировать обращение,

  4. рассчитать срок выполнения по SLA,

  5. обзвоном найти подходящего свободного исполнителя, выбрать ближайшего с необходимыми компетенциями,

  6. договориться с клиентом о времени визита, согласовать его с исполнителем и далее проконтролировать выполнение заявки в срок

  7. После выполнения заявки - собрать с исполнителя отчет о выполненных работах и запустить процесс по оплате работ, в случае b2b сервиса и при постоплатной схеме работ

Все эти действия требуют значительных временных затрат со стороны диспетчера, руководителя сотрудника и сервисной организации целом, что не способствуют масштабированию бизнеса и увеличивает себестоимость услуг.

Автоматизация процессов диспетчерской может снизить эти расходы до 90%.

Пример: в среднем по сервисной отрасли, один диспетчер обрабатывает до 500 заявок, с учетом неравномерности их поступления. Для отказоустойчивости или приема обращений 24x7, уже требуется 3-5 диспетчеров работающих посменно. А если заявок тысячи? Количество диспетчеров растет пропорционально.

В среднем, на 20-50 мобильных (выездных) сотрудников требуется два диспетчера (даже без круглосуточной поддержки). Зарплата диспетчера сравнима с зарплатой сервисного специалиста, а значит до 10% затрат на ФОТ сервиса уходит на обслуживание процессов диспетчеризации. Это может составлять до 20% недополученной прибыли всего бизнеса.

Кроме снижения затрат на диспетчерскую, автоматизация процессов диспетчеризации позволяет значительно улучшить клиентский сервис, исключить испорченный телефон при передаче заявок между клиентом / диспетчером / мастером, снизить нагрузку на сервисных специалистов и уменьшить количество повторных выездов из-за ошибок диспетчера или неверно переданной информации. Кроме этого - значительно ускорить процесс получения заявки ответственным сотрудником.

Динамику каких показателей следует контролировать для минимизации затрат на диспетчеризацию клиентских обращений?

  1. Доля и количество заявок, полученных по электронным каналам связи, в которых не требуется участие диспетчера при получении, регистрации и распределении заявок

  2. Соотношение исполнителей и диспетчеров

  3. Скорость назначения заявок на исполнителей

  4. Скорость закрытия заявок

  5. Среднее время в пути

  6. First Time Fix Rate - доля заявок, закрытых исполнителями с первого посещения объекта

  7. Доля и кол-во заявок, закрытых с нарушением срока SLA

  8. Оценка от клиентов

Цель 2: Оптимизация затрат на персонал сервисной службы

Затраты на персонал напрямую зависят от оптимальности и уровня автоматизации выстроенного сервисного процесса и, конечно, эффективности сотрудников. Рабочий день сотрудника состоит из полезного времени (время, когда сотрудник выполняет работы или оказывает услугу, которую оплачивает заказчик) и остального времени (время на дорогу, заполнение отчетности, общение с диспетчерами по получению заявок или планированию расписания, поездок в офис и т.п.).

Повышение полезной загрузки исполнителей и оптимизация процесса оказания сервиса совместно позволяют оптимизировать численность персонала до 20-30%.

Контроль каких показателей, может позволить снизить затраты на персонал и оптимизировать сервис?

  1. Количество закрытых заявок на сотрудника

  2. Коэффициент полезной загрузки исполнителей

  3. Отношение количества офисных сотрудников к мобильным

  4. Среднее количество выполненных заявок на человека в день

  5. Рейтинг эффективности персонала

  6. Объем переработок

  7. Пробег автотранспорта по сотрудникам

  8. Пробег автотранспорта в расчете на заявку

  9. Средняя полезная загрузка сотрудника в день в часах

  10. Среднее количество открытых заявок по исполнителям

  11. Равномерность загрузки исполнителей по дням недели/месяца

  12. Суммарное время выполнения работ по исполнителям

  13. Доля и количество заявок, выполненных с просрочкой

  14. Доля и количество повторных выездов

  15. В RCM контрактах - MTBF (среднее время между отказами оборудования), MTTR (среднее время на устранение неисправности), % оборудования, по которому были заявки

  16. % выполнения плановых (профилактических) работ

  17. Наличие наиболее используемых материалов/запчастей на складе и у исполнителя в начале смены

При постановке целей и оптимизации численности персонала, рекомендуется выявлять показатели, наиболее явно влияющие на загрузку персонала и устранять непроизводственные потери, проводя мероприятия по улучшению сервисных процессов. Собирая и отслеживая выбранные показатели ежемесячно, в среднесрочной перспективе от полугода до года можно достичь значительного увеличения эффективности работы сервиса и снизить затраты, до 20-30% повысив полезное время и равномерность загрузки мобильных сотрудников.

Цель 3: Снижение затрат на подрядчиков

Для системной работы с подрядчиками, необходимо выстроить автоматизированный процесс работы с ними и установить точки контроля их работы. Существует 2 основных способа снижать затраты на подрядчиков: уменьшать стоимость контрактов, снижая объем или стоимость работ, либо увеличивать строгость SLA и штрафовать при его нарушении. В обоих случаях необходимо четко регламентировать процесс работы подрядчиков и контролировать не только уровень сервиса, но и его объем.

Контроль каких показателей, поможет снизить затраты на подрядчиков?

  1. уровень сервиса (соблюдения SLA)

  2. количество заявок, выполненные с просрочкой

  3. фиксация факта присутствия на объекте при закрытии заявки

  4. указание выполненных работ и фиксация объекта ремонта

  5. контроль количества повторных ремонтов

  6. В RCM контрактах - MTBF (среднее время между отказами обслуживаемого оборудования), MTTR (среднее время на устранение неисправности), % оборудования по которому были заявки

  7. общее количество выполненных заявок

  8. количество заявок выполненных по видам и единицам оборудования

  9. скорость решения проблем

  10. простои оборудования

  11. рейтинг исполнителей подрядчика

  12. рейтинги и сравнительная оценка уровня сервиса различных подрядчиков

  13. количество заявок на согласовании у заказчика

  14. время поставки запчастей

  15. оборачиваемость запчастей и актуальная матрица запчастей

  16. среднее время приема заявки подрядчиком в работу

По всем показателям важно контролировать не только фактические значения, но и тенденцию, а также трендыизменения показателей в период времени. При правильно выбранных показателях заказчик видит, какие из требований или проводимых операцийне влияют на уровень сервиса, оказываемого подрядчиками, что позволяет снижать стоимость и держать под контролем уровень предоставляемого подрядчиками сервиса.

Цель 4: Снижение затрат на административные функции и бэкофис.

Снижение затрат напрямую зависит от степени автоматизации таких административных функций как: отдел кадров, бухгалтерия, транспортный цех, отдел закупок, служба работы с подрядчиками, административный отдел, финансовая служба, тендерный отдел, служба безопасности и т.д.

В рамках целей по достижению большей эффективности работы бэкофиса и администрации, сервисные компании оптимизируют ряд процессов. При этом целями могут быть как снижение численности персонала бэкофиса, так и возможность увеличить численность персонала, оказывающего сервис клиентам (мобильных сотрудников), без увеличения количества сотрудников бэкофиса.

Тут правильнее перечислить процессы, оптимизация которых снижает затраты на административные функции:

  1. Оптимизация первичного документооборота: переход на электронные первичные сервисные акты вместо бумажных

  2. Переход на ЭДО по юридически значимым документам (бухгалтерские акты, СФ, договора и т.д.)

  3. Автоматическое формирование бухгалтерских актов из первичных электронных, которые оформляют мобильные сотрудники при выполнении обслуживания

  4. Внедрения систем систем электронного архива как входящих, так иисходящих документов с QR-кодированием и хранением всех бухгалтерских документов в компании в электронном виде

  5. Автоматизация процессов закупки запчастей, от оформления электронной заявки на закупку сотрудником из мобильного приложения, до получения запчастей без оформления бумажных документов при их выдачи подотчетным лицам (использование простой электронной подписи при перемещении ТМЦ между материально ответственными сотрудниками и складом)

  6. Электронные авансовые отчеты сотрудников по купленным в магазинах запчастям и расходным материалам

  7. Автоматизированный GPS-контроль перемещения персонала и автотранспорта

  8. Автоматизация планирования маршрутов по заявкам

  9. Оптимизация матрицы запчастей и управление товарным запасом

  10. Электронный склад запчастей в смартфоне мобильного сотрудника с поиском требуемых запчастей

  11. Стандартизация стандартных или плановых работ через чек-листы или карты операций

  12. Онбординг и обучение персонала через электронные помощники в смартфоне

Как правильно ставить цели в сервисной компании?

В процессе постановки целей в сервисе, как и в большинстве других организаций, ставить цели лучше сверху вниз, декомпозируя их до уровня функций и отделов, а далее до уровня сотрудников. Очень важно учитывать специфику компании и отделять стратегию от тактики.

Если говорить о сервисной компании, то ее жизнь - это бесконечный сервисный процесс или процесс предоставления клиентского сервиса. Быстрые победы тут бывают крайне редко, так как любое изменение затрагивает множество сотрудников, которые привыкли годами работать по какому-либо сценарию.

Любое изменение требует не только обучения всех вовлеченных в процесс сотрудников, но и наличия инструментов контроля эффективности данных изменений в организации, а также обязательной оцифровки процесса, чтобы оценка была максимально объективной и не требовала огромных трудозатрат на ее выполнение.

Еще сложнее ситуация в сервисе с выездным или мобильным персоналом, который большую часть времени проводит в полях. Поэтому процесс постановки целей лучше всего выстраивать следующим образом:

  1. Определить верхнеуровневые стратегические цели (финансовые / клиентские (рынок) / процессные или цели, связанные с персоналом)

  2. Декомпозировать цели до уровня отдела или функций в компании

  3. Определить показатели, контроль и изменение которых позволит, в среднесрочной перспективе, достигнуть поставленных целей

  4. Решить задачи по регулярному расчету и оценки выбранных показателей (показатели следует рассчитывать и оценивать не реже 1 раза в месяц. Нижнеуровневые - понедельно или даже ежедневно).

Тут часто встает вопрос о необходимости внедрения какого-либо ИТ-решения, позволяющего

  • оцифровывать текущие сервисные процессы,

  • измерять требуемые показатели

  • помогать проводить цифровую трансформацию сервисной организации автоматизируя ключевые бизнес-процессы сервиса.

  • После измерения текущих показателей работы сервиса, пора переходить к выявлению наиболее неоптимальных (ресурсоемких) процессов и их оптимизации. Шаг за шагом, процесс за процессом.

Для оцифровки сервисного бизнеса с выездным персоналом лучше всего подходят решения класса FSM (Field Service Management). В России этот класс систем представлен решением HubEx (мы - разработчики платформы HubEx FSM). Так что если при прочтении остались вопросы - спрашивайте в комментариях. И удачи в цифровизации в своих компаниях!

Подробнее..

Параллелизм и эффективность Python vs FSM

14.06.2020 12:18:17 | Автор: admin
Признаюсь, но я не знаю Python. Просто потому, что не использую. Тем не менее, взявшись за его освоение, а также в попытках расшифровать загадочную аббревиатуру GIL, вышел на статью с описанием необъяснимых магических явлений параллельного варианта CPU-зависимой функции на Python. Возникло желание перепроверить данный тест и сравнить с эквивалентной реализацией в форме модели конечного автомата (Finite-state machine или сокращенно FSM) в среде Визуального Компонентного Программирования (автоматного) ВКП(а).

Очевидно любая программа в определенной мере CPU-зависима. С другой стороны, если это только не ассемблер, то тестированием на том или ином языке высокого уровня мы в большей степени исследуем программную прослойку, скрываемую им. Поэтому, рассматривая Python, правильнее было бы говорить о CPU-зависимости его интерпретатора. Можно даже утверждать, что программа на Python будет иметь скорость, зависимую от версии интерпретатора, и обладать характерной для него мистикой.

В то же время есть ситуации, когда зависимости от CPU может почти не быть (в этом мы убедимся). Речь идет о языках, вычислительная модель которых отлична от типовой архитектуры процессоров. Вычислительная модель Python, ей соответствует, а автоматная модель вычислений, о которой далее пойдет речь, имеет другую архитектуру и это будет определять специфику ее тестирования. Какая будет скорость и будет ли иметь место мистика выяснится в процессе тестирования автоматного кода.


Код на Python, заимствованный из статьи, в последовательном и параллельном вариантах представлен соответственно на листингах 1 и 2. В нашем случае он дополнен строками, фиксирующими время работы, а последовательный тест заключен в обертку, позволяющую задавать число повторений (следствие моего знакомства с декораторами в Python).

Листинг 1. Последовательный тест CPU на Python
import timedef count(n):  st_time = time.time()  while n > 0:    n -= 1  t = time.time() - st_time  print("speed CPU------%s---" % t)  return tdef TestTime(fn, n):  def wrapper(*args):    tsum=0    st = time.time()    i=1    while (i<=n):      t = fn(*args)      tsum +=t      i +=1    return tsum  return wrappertest1 = TestTime(count, 2)tt = test1(100000000)print("Total ---%s seconds ---" % tt)



Листинг 2. Параллельный тест CPU
import timefrom threading import Threaddef count(n):  start_time = time.time()  while n > 0:    n -= 1  print("speed CPU-%s seconds ---" % (time.time() - start_time))k = 2while k > 0:  print("k=%d " % k)  t1 = Thread(target=count,args=(100000000,)); t1.start()  t2 = Thread(target=count,args=(100000000,)); t2.start()  t1.join();  t2.join();  k -= 1


Рис.1 и рис.2 демонстрируют результаты тестирования, которые получены для двух последовательных прогонов теста (Python 3.8.3 64-bit) для процессора Intel Core(TM) CPU @2.5 GHz, ОЗУ 6.0 ГБ. Результаты следующие:
последовательный тест 14.221 сек
параллельный тест 13.995 сек

В статье о GIL (далее исходной статье) это было соответственно 24.6 сек и 45.5 сек. Обращает на себя внимание время параллельного теста. В сравнении с результатами исходной статьи это демонстрирует, что дело продвинулось, хотя бы с точки зрения реализации интерпретатором параллелизма (хотя это лишь только предположение).
image
Рис.1. Время работы последовательного теста на языке Python
image
Рис.2. Время работы параллельного теста на языке Python

Обратим внимание, что параллельный вариант почти в два раза медленнее аналогичного последовательного. При этом, если мы создадим три потока, то и результат замедлится пропорционально (см. рис. 3). Из этого можно заключить, что увеличение числа потоков ведет к кратному увеличению времени работы. Если не лезть под капот, то на текущем уровне понимания Python это может выглядеть, как мистика.
image
Рис.3. Тест CPU на трех потоках на языке Python

Прежде чем перейти к созданию автоматного кода и тестированию в среде ВКП(а), рассмотрим реализацию автоматов вне ее. Эквивалентная алгоритму исходной статьи модель в форме графа автомата показана на рис. 4. Из разнообразия возможных вариантов (см., к примеру, статью) рассмотрим наиболее типовой и известный в формате, так называемой, SWITCH-технологии (подробнее о ней см. на сайте ИТМО).

Далее мы будем сравнивать программу на Python с реализацией на С++. Соответствующий графу код на С++ с кодом его запуска (см. функцию main) приведен на листинге 3.
image
Рис.4. Автоматная модель CPU-зависимой функции

Листинг 3. Реализация теста на С++
class FsmCount{public:    FsmCount(int n) { nCount = n; nState = 0; };    void ExecOneStep();    int nState;protected:    int nCount;    int x1();    void y1();};#include "FsmCount.h"int FsmCount::x1() { return nCount > 0; }void FsmCount::y1() { nCount--; }void FsmCount::ExecOneStep(){    switch (nState) {    case 0:        if (x1())            y1();        else nState = 1;        break;    }}#include <QCoreApplication>#include <QTime>#include "FsmCount.h"int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    QTime time;    FsmCount *pFsmCount{nullptr};    pFsmCount = new FsmCount(100000000);    time.start();    while (!pFsmCount->nState)        pFsmCount->ExecOneStep();    double dTime = QString::number(time.elapsed()).toDouble();    printf("%f", dTime);    if (pFsmCount) delete pFsmCount;    return a.exec();}



Время работы теста на упомянутой аппаратной базе находится в пределах 250 мсек. Это более чем в 55 раз быстрее по сравнению с кодом Python. Сравнение проведено с параллельным вариантом тестирования для двух потоков (см. рис.2).

Но скорость можно повысить еще больше, если функцию вычислять, не выходя за пределы действия/метода y1 (см. листинг 4). В результате скорость теста уменьшилась до менее чем 1 мсек (точнее сказать сложно, т.к. тест выдает значение равное нулю). Если взять за основу даже это значение, то скорость получаем более чем в 14000 (!) раз выше, чем на Python. Становится понятным, почему С++ отдают предпочтение при проектировании тех же систем реального времени.

Листинг 4. Вариант быстрого метода для класса FsmCount
void FsmCount::y1() {    while (nCount>0)        nCount--;}



Наша дальнейшая цель сравнение Python со средой ВКП(а). Автоматный тест, как и выше, должен соответствовать модели на рис. 4. Его код демонстрирует листинг 5.

Листинг 5. Автоматная программа теста CPU
#include "lfsaappl.h"#include <QTime>class FCount :    public LFsaAppl{public:    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FCount(nameFsa); }    bool FCreationOfLinksForVariables();    FCount(string strNam);    CVar *pVarCount;    CVar *pVarExtVarCount{nullptr};    CVar *pVarStrNameExtVarCount;    CVar *pVarWorkingHours;protected:    int x1(); int x12();    void y1(); void y2(); void y3(); void y12();    QTime time;};#include "stdafx.h"#include "FCount.h"static LArc TBL_Count[] = {    LArc("st","st","^x12","y12"),    LArc("st","s1","x12", "y2"),    LArc("s1","s1","x1",  "y1"),    LArc("s1","st","^x1", "y3"),    LArc()};FCount::FCount(string strNam):    LFsaAppl(TBL_Count, strNam){ }bool FCount::FCreationOfLinksForVariables() {    pVarCount = CreateLocVar("n", CLocVar::vtInteger, "local counter");    pVarStrNameExtVarCount = CreateLocVar("strNameExtVarCount", CLocVar::vtString, "external counter name");;    string str = pVarStrNameExtVarCount->strGetDataSrc();    if (str != "") { pVarExtVarCount = pTAppCore->GetAddressVar(pVarStrNameExtVarCount->strGetDataSrc().c_str(), this); }    pVarWorkingHours  = CreateLocVar("strWorkingHours", CLocVar::vtString, "working hours");    return true;}int FCount::x1() { return pVarCount->GetDataSrc() > 0; }int FCount::x12() { return pVarExtVarCount != nullptr; }void FCount::y1() {    int n = int(pVarCount->GetDataSrc());    pVarCount->SetDataSrc(this, --n);}void FCount::y2() {    time.start();    pVarCount->SetDataSrc(this, pVarExtVarCount->GetDataSrc());}void FCount::y3() {    pVarWorkingHours->SetDataSrc(nullptr, QString::number(time.elapsed()).toStdString(), nullptr);}void FCount::y12() { FInit(); }



Время исполнения теста в пределах 5.35 сек при количестве циклов 100000. Т.е. он, написанный на быстром С++, работает более чем в 2600 раз (!?) медленнее параллельного теста на Python. Такой результат не может не огорчать :( Но отметим, что длительность дискретного такта при этом равно 0.0535 мсек. Например, стандартный цикл ПЛК типа ОВЕН-100 равен 1 мсек, а поэтому с точки зрения систем управления реального времени результат вполне достойный. Хотя, еще раз, на первый взгляд результат не в пользу АП, т.к. и код объемный, да и скорость вызывает определенную тревогу (например, для чисто вычислительных задач).

Использование С++ в качестве языка проектирования ВКП(а) позволяет достаточно просто решить возникшую проблему скорости. Листинг 6 демонстрирует код автомата, использующего стандартные системные потоки на базе класса QThread библиотеки Qt. В результате время упало до 70-80 мсек даже при увеличении циклов до 100000000. Таким образом, тест, использующий поток, работает уже в 200 раз быстрее, чем на Python.

Заметим, что полученный результат представляет замедленную версию теста, синхронизированную со средой ВКП(а). Если закомментировать строку (листинг 6):
while (bIfExecuteStep) { bIfExecuteStep = false; }, 

то время рухнет до 1 мсек и менее. В результате скорость по отношению к Python уже будет выше более чем в 14000 раз.

Замечание 1. Флаг bIfExecuteStep управляется средой ВКП(а). Устанавливается ею в начале текущего дискретного такта. Введен для синхронизации с дискретным временем автоматного пространства процесса.


С целью увеличения скорости мы создали комбинацию автомата и потока, где последний реализует нужную функциональность, а автомат его обертка. Получился достаточно специфический объект, но и мы решали прежде всего задачу увеличения скорости работы, которую в результате и решили.

Листинг 6. Тест CPU на базе потока
#include "lfsaappl.h"#include <QThread>#include <QTime>class QCount;class ThCount : public QThread{    Q_OBJECTpublic:    ThCount(QCount *p, QObject *parent);protected:    bool bIfRun{false};    bool bIfStop{false};    bool bIfExecuteStep{false};    QCount *pQCount;    void run();friend class QCount;};class QCount :    public LFsaAppl{public:    void ExecuteThreadStep();    void WaitForThreadToFinish();    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new QCount(nameFsa); }    bool FCreationOfLinksForVariables();    QCount(string strNam);    virtual ~QCount(void);    ThCount  *pThCount{nullptr};    CVar *pVarCount;    CVar *pVarExtVarCount;    CVar *pVarStrNameExtVarCount;    CVar *pVarWorkingHours;protected:    int x1(); int x12();    void y1(); void y2(); void y12();    QTime time;friend class ThCount;};#include "stdafx.h"#include "QCount.h"#include <QTimer>static LArc TBL_Count[] = {    LArc("st","st","^x12","y12"),//    LArc("st","s2","x12","y1"),//    LArc("s2","s3","^x1","y2"),//    LArc("s3","s2","^x1","--"),//    LArc()};QCount::QCount(string strNam):    LFsaAppl(TBL_Count, strNam, nullptr, nullptr){ }QCount::~QCount(void) { }bool QCount::FCreationOfLinksForVariables() {    pVarCount = CreateLocVar("n", CLocVar::vtInteger, "local counter");    pVarExtVarCount = nullptr;    pVarStrNameExtVarCount = CreateLocVar("strNameExtVarCount", CLocVar::vtString, "external counter name");;    string str = pVarStrNameExtVarCount->strGetDataSrc();    if (str != "") { pVarExtVarCount = pTAppCore->GetAddressVar(pVarStrNameExtVarCount->strGetDataSrc().c_str(), this); }    pVarWorkingHours  = CreateLocVar("strWorkingHours", CLocVar::vtString, "working hours");    return true;}void QCount::ExecuteThreadStep() { if (pThCount) pThCount->bIfExecuteStep = true; }void QCount::WaitForThreadToFinish() {    if (pThCount) {        // завершить поток        pThCount->bIfRun = false;        pThCount->quit();        pThCount->wait(500);        pThCount->terminate();    }    delete pThCount;}int QCount::x1() { return pVarCount->GetDataSrc() > 0; }int QCount::x12() { return pVarExtVarCount != nullptr; }// создаем потокvoid QCount::y1() {    pThCount    = new ThCount(this, pTAppCore->pMainFrame);}void QCount::y2() { pVarCount->SetDataSrc(this, pVarExtVarCount->GetDataSrc()); }void QCount::y12() { FInit(); }// поток  поток  поток  поток  поток  поток  поток  поток  поток  поток  поток  поток  потокThCount::ThCount(QCount *p, QObject *parent) :    QThread(parent){    pQCount = p;    bIfRun = true;      // установить признак запуска/завершения потока    start(QThread::IdlePriority);            // запустить поток}// цикл исполнения потокаvoid ThCount::run(){    bIfStop = false;       // сбросить признак останова потока    pQCount->pVarWorkingHours->SetDataSrc(nullptr, "", nullptr);    pQCount->pVarCount->SetDataSrc(pQCount, pQCount->pVarExtVarCount->GetDataSrc());    pQCount->pVarCount->UpdateVariable();    while(bIfRun)  {        if (pQCount->pVarCount->GetDataSrc() > 0) {            bIfExecuteStep = false;            int n = pQCount->pVarCount->GetDataSrc();            if (n>0) {                pQCount->time.start();            }            pQCount->time.start();            while (n>0) {                n--;                while (bIfExecuteStep) { bIfExecuteStep = false; }            }            string str = QString::number(pQCount->time.elapsed()).toStdString();            pQCount->pVarWorkingHours->SetDataSrc(nullptr, QString::number(pQCount->time.elapsed()).toStdString(), nullptr);            pQCount->pVarCount->SetDataSrc(pQCount, 0.0);            while (n<=0) {                n = int(pQCount->pVarCount->GetDataSrc());            }        }    }    bIfStop = true;        // установить признак останова потока} 



Рассмотрим еще один тест, обратив внимание на время работы потоков. Поскольку они абсолютно одинаковы, то, запущенные одновременно, они должны одновременно и завершить работу. Если для Python это справедливо лишь в пределах определенной погрешности (см. рис. 5), то для процессов в ВКП(а) время полностью совпадает (см. рис. 6). Заметим, что дополнительно созданный объект-секундомер, определяющий время работы теста извне его, показывает фактически такое же время, как и время, отмеряемое самим тестом/тестами (см. листинг 4).

Замечание 2. Секундомер автоматный процесс, начинающий отсчет, когда контролируемый процесс попадает в состояние s1 и фиксирующий время, когда тот переходит в состоянии st (см. таблицу переходов автомата на листинге 4).

image
Рис.5. Время работы тестов в Python
image
Рис.6. Время работы тестов в среде ВКП(а)

Автоматы в сравнении с другими моделями имеют дискретное время. Величина дискретного такта может быть фиксированной для синхронных автоматов или плавающей для асинхронных (классификация по Глушкову В.М.). Следующим тестом будет проверка среды ВКП(а) на синхронность способность поддерживать постоянное значение такта дискретного времени. Напомним, что режим синхронной работы автоматов особенно востребован при проектировании систем автоматического управления (см. подробнее [1]). Одновременно подобный режим служит примером реализации фактической независимости вычислительной модели от скорости CPU.

Время работы теста при дискретном такте (в теории автоматического управления (ТАУ) именуемом интервалом квантования) длительностью 1 мсек и числе циклов 100000 должно быть 100 сек. Полученные при тестировании результаты демонстрирует рис. 7.
image
Рис.7. Тестирование в синхронном режиме (1 мсек)

Основное тестирование мы фактически закончили и теперь можно обсудить объем кода, т.к., глядя на листинг 3, критики не преминут указать на это. Но они в очередной раз забудут о том, о чем не раз уже говорилось. Код включает оболочку, организующую взаимодействие со средой ВКП(а). Какой вид примет автомат, если ее исключить, поясняет листинг 7, представляющий чистый код автоматной функции.

Листинг 7. Чистый код автомата
#include "lfsaappl.h"extern LArc TBL_CountMini[];class FCountMini :    public LFsaAppl{public:    FCountMini(string strNam): LFsaAppl(TBL_CountMini, strNam) {};    int nCount;protected:    int x1();    void y1();};#include "stdafx.h"#include "fcountmini.h"LArc TBL_CountMini[] = {    LArc("с1","с1","x1","y1"),    LArc("с1","00","^x1","--"),    LArc()};int FCountMini::x1() { return nCount > 0; }void FCountMini::y1() { --nCount; }



Получилось, не правда ли, заметно проще, хотя время работы осталось прежним. Но, как мы уже выяснили, время функции, заключенной в поток, составляет буквально единицы микросекунд. Подобное действие уже было рассмотрено (см. листинг 4). С ним время теста будет исчисляться, как и ранее, микросекундами (см. рис. 8).

Таким образом, многие проблемы автоматных программ можно разрешить, если представлять, как можно управлять их эффективностью. Возможности С++ в этом смысле фактически ни чем не ограничены.
image
Рис.8. Результаты тестирование действия y1 (листинг 6).
Осталось рассмотреть автоматную обертку для функции в листинге 7. Ее демонстрирует листинг 8. Код объемный, но, еще раз, типовой, выполняющий стандартные для среды функции.

Листинг 8. Обертка для автоматной функции на листинге 5
#include "lfsaappl.h"#include <QTime>class FCountMini;class FCount :    public LFsaAppl{public:    LFsaAppl* Create(CVarFSA *pCVF) { Q_UNUSED(pCVF)return new FCount(nameFsa); }    bool FCreationOfLinksForVariables();    FCount(string strNam);    virtual ~FCount(void);    CVar *pVarCount;    CVar *pVarExtVarCount{nullptr};    CVar *pVarStrNameExtVarCount;    CVar *pVarWorkingHours;protected:    int x1(); int x12();    void void y3(); void y4(); void y12();    QTime time;    FCountMini *pCountMini{nullptr};};#include "stdafx.h"#include "FCount.h"#include "fcountmini.h"static LArc TBL_Count[] = {    LArc("st","s1","--","y4"),    LArc("s1","st","--","y3"),    LArc()};FCount::FCount(string strNam):    LFsaAppl(TBL_Count, strNam){ }FCount::~FCount(void){    if (pCountMini) delete pCountMini;}bool FCount::FCreationOfLinksForVariables() {    pVarCount = CreateLocVar("n", CLocVar::vtInteger, "local counter");    pVarStrNameExtVarCount = CreateLocVar("strNameExtVarCount", CLocVar::vtString, "external counter name");;    string str = pVarStrNameExtVarCount->strGetDataSrc();    if (str != "") { pVarExtVarCount = pTAppCore->GetAddressVar(pVarStrNameExtVarCount->strGetDataSrc().c_str(), this); }    pVarWorkingHours  = CreateLocVar("strWorkingHours", CLocVar::vtString, "working hours");    return true;}int FCount::x1() { return pVarCount->GetDataSrc() > 0; }int FCount::x12() { return pVarExtVarCount != nullptr; }void FCount::y3() {    pVarWorkingHours->SetDataSrc(nullptr, QString::number(time.elapsed()).toStdString(), nullptr);}void FCount::y4() {    time.start();    if (pCountMini) delete pCountMini;        pCountMini = new FCountMini("CountMini");    pCountMini->nCount = pVarExtVarCount->GetDataSrc();    pCountMini->FCall(this);}void FCount::y12() { FInit(); }



Итак, можно подвести итоги. В общем случае автоматы в ВКП(а) работают медленнее почти в 2600 раз в сравнении с процессами на Python. Такова фактически оценка автоматного интерпретатора среды ВКП(а). С одной стороны, в этом сокрыты колоссальные резервы роста, а, с другой, с точки зрения реактивности среды даже такой скорости вполне достаточно. Но, напомним, использование потоков позволяет справиться и с этой проблемой уже сейчас и, наоборот, на этой базе быть быстрее Phython более чем в 200 раз. В дополнение к потокам существует даже более простое и теоретически более верное автоматное решение вызов проблемной функции в рамках одного или нескольких действий (аналог разделения операции или процесса на чанки см. статью). В любом случае с С++ ситуация в АП с точки зрения производительности программ не выглядит безвыходной, а в определенной ситуации (проектирование, например, систем реального времени) даже выигрышная.

И в заключение немного о том, что выходит за пределы простого тестирования. Меня зацепила статья. Я согласен с тем, что хватит уже бояться субъективно красивых решений в коде. С одной существенной поправкой я за объективность. Код автоматной программы может казаться громоздким (что достаточно поправимо), не очень красивым (это весьма субъективно) и даже не устраивать по скорости (здесь, как мы убедились, в АП есть решение и не одно), но постижение его сути открывает скрытую от стороннего взгляда красоту. Причем красоту объективную.

Что здесь выступает критерием объективности? Теория. В нашем случае теория автоматов. Ее реализация, как бы она не выглядела, по определению не может быть некрасивой ;) И наоборот, если реализация не соответствует ей, то как бы выигрышно она не выглядела, она не имеет, как правило, перспективы с точки зрения теории. А потому в каком бы запале ни пропагандируй SWITCH-технологию, как ни расхваливай UniMod, но если они не соответствуют требованиям теории автоматов, искажая ее красоту, то мы поневоле и объективно сталкиваемся с ситуацией, что автомат многим не друг. И одними камланиями и танцами с бубном такую ситуацию не исправишь. Но, если следовать теории, этого и не понадобится.

Пуская критические стрелы в сторону автоматов, я, как тот вшивый о бане, не мог не вспомнить о корутинах ;) Хотелось бы их тоже протестировать, а заодно и оценить их красоту, т.к. неужели красота конструкций, подобных представленным на рис. 9 (см. также), которые реализуют, если не ошибаюсь, два параллельных (?) оператора a = a-b и b = a+b, могут вызывать хоть минимальные приступы восхищения?

image
Рис.9. Пример корутин

Может быть этот код даже эффективен, а для кого-то несомненно красив, но речь-то о другом. Я здесь не о маниакальном желании описать параллелизм шаблонами последовательного мышления. Хотя это, если уж прямо, напрягает, т.к. мышление нужно менять. Я совсем о другом. Есть ли независимая от языка программирования теория корутин, которая позволяла бы просто и наглядно преодолеть загогулины языка (см. еще раз рис. 9) и дать однозначный ответ на заданный вопрос? В отношении корутин я такой теории не знаю, хотя уже, если честно, обыскался ;) Для конечных автоматов это теория автоматов, в которой автомат всегда автомат в любом виде (граф, таблица, матрица и т.п.) вне связи с языком/языками программирования.

Дернув за, казалось бы, простейшую тестовую ниточку, мы затронули клубок параллельных проблем. Может быть, правда, лишь немножко его распушили. А начали-то, если кто еще не забыл, с элементарной CPU-зависимой функции.

Кто же мог подумать, во что это выльется :)

Литература
1. Мирошник И.В. Теория автоматического управления. Линейные системы. СПб.: Питер, 2005. 336 с.
Подробнее..

Достучаться до небес, или FSM на шаблонах

05.02.2021 02:04:48 | Автор: admin

Здравствуйте! Меня зовут Александр, я работаю инженером-программистом микроконтроллеров.

Пишу на С/С++, причем предпочитаю плюсы, ибо верую в их эволюционную неизбежность в embedded.

Мир встроенного ПО, язык С++ динамично развиваются, поэтому разработчикам важно не отставать и поддерживать свои скиллы и наработки актуальными моменту.

Я стараюсь следовать этому очевидному посылу, благо небожители ведущие С++ программисты и консультанты щедро делятся своим опытом и идеями на разных площадках (например здесь, или здесь).

Некоторое время назад я посмотрел мощный доклад Сергея Федорова про построение конечного автомата с таблицей переходов на шаблонах.

Если внезапно: "а что такое конечный автомат?"

Конечный автомат, или FSM(finite state maсhine) - один из самых востребованных и популярных приемов в программировании на МК. В свое время за кратким и практическим руководством по готовке FSM я ходил заброшенные, земли.

Одна из идей доклада - определить состояния, эвенты и действия через пользовательские типы, а таблицу переходов реализовать через шаблонный параметр, меня очень

впечатлила
// Transition table definitionusing transitions =  transition_table<  /*  State       Event       Next       */  tr< initial,    start,      running    >,  tr< running,    stop,       terminated >>;};// State machine objectusing minimal = state_machine<transitions>;minimal fsm;//...and then callfsm.process_event(start{});fsm.process_event(stop{});

А если добавить к этому перенос части функциональности кода в компайл тайм, заявленную автором потокобезопасность, улучшенные по сравнению с Boost::MSM выразительность, читаемость кода и скорость сборки, header only модель библиотеки, то - надо брать, решил я.

Вот только попытка собрать и запустить даже простейший пример на STM-ке закончилась матерком компилятора: "cannot use 'typeid' with "-fno-rtti" и "exception handling disabled".

Да, все так. Более того, помимо отключенной поддержки RTTI и исключений, у меня также выставлены флаги -fno-cxa-atexit, -fno-threadsafe-static. А еще в линкере применены настройки --specs=nano.specs (используем урезанную версию стандартной библиотеки с++ newlib-nano), --specs=nosys.specs (применяем легковесные заглушки для системных вызовов).

Зачем же таскать на себе вериги?

Embedded разработчикам хорошо известны особенности и ограничения при разработке встроенного ПО, а именно:

  • лимитированная память с недопустимостью фрагментации;

  • детерменированность времени выполнения;

  • штатно исполняющаяся программа никогда не выходит из main

С++ имеет в своем могучем арсенале средства и методы, неосторожное использование которых может войти в критическое противоречие с указанными выше условиями.

Как закружить в гармоничном танце С++ и bare metal отлично разъяснено у этого автора. Также порекомендую этот доклад.

Исходники проекта докладчика, включая зависимости, - это двадцать файлов со смертоноснейшей шаблонной магией. Перекроить их так, чтобы не юзать typeid и exceptions, простому смертному в моем лице - too much.

Делать нечего, поступимся принципами, включим поддержку RTTI, а вместо throw в исходниках автора проставим заглушки.

На этот раз все собралось. Вот только при использовании тулчейна gcc-arm-none-eabi-9-2020-q2-update и уровне оптимизации -O3, размер исполняемого файла превысил 200Кб.

Это несколько огорчительно, хотя какие могут быть претензии - библиотека изначально разрабатывалась под "большого брата".

И хотя старшие камни, например у STM, имеют на борту флеш от 1Мб, отдавать почти четверть только под конечный автомат, пусть и на закиси азота, как у докладчика, согласитесь, довольно расточительно.

Итак, с наскоку взять высоту не удалось, и я на некоторое время переключился на другие задачи. Но красота идеи меня не отпускала, и на днях я все-таки решился "достучаться до небес" - написать extra light embedded версию FSM из упомянутого доклада самостоятельно.

Уточню свои хотелки:

  • Оперировать состояниями, эвентами и действиями как пользовательскими типами.

  • Таблицу переходов реализовать в виде шаблонного параметра

  • Перетащить что возможно в компайл тайм

  • Асинхронно и атомарно постить эвенты

  • Переключать состояния за константное время

  • Выйти в итоге на приемлемый по меркам встроенного ПО размер кода

  • Повторить header only модель библиотеки

Забегая вперед, скажу, что в итоге что-то получилось и даже взлетело.

А вот как - не сочтите за труд, давайте посмотрим вместе. И не сдерживайте себя в метании тухлых яиц, ибо лучшей питательной среды для самосовершенстования трудно представить.

Первым делом опишем базовые сущности:

Состояние/State
struct StateBase{};template <base_t N, typename Action = void>struct State : StateBase{  static constexpr base_t idx = N;  using action_t = Action;  };

Здесь и далее base_t - платформозависимый тип, машинное слово. В моем случае это unsigned int.

Состояния пусть будут двух типов - пассивное, в котором никаких действий не происходит, и активное - при нахождении в котором будет выполнятся переданный шаблонным параметром функтор, action_t.

Цель статического члена idx уточню далее по тексту.

Событие/Event
struct EventBase{};template <base_t N>struct Event : EventBase{  static constexpr base_t idx = N;};

Элементарная структура, все ясно.

Действие при наступлении события и смене состояний:

Action
struct action{  void operator()(void){    // do something};

Безусловно, сигнатура operator() может и должна варьироваться от задач приложения, пока же для упрощения остановимся на самом легковесном варианте.

Сторож состояния:

Guard
enum class Guard : base_t{  OFF,  CONDITION_1,  CONDITION_2,  //etc.};

Идея сторожа - допустить переход в новое состояние, только если в данный момент выполнения программы текущее значение сторожа соответствует заданному пользователем значению в типе перехода/transition-a. Если такого соответствия нет, то переход в новое состояние не происходит. Но тут возможны варианты. Например, все же переходить, но не выполнять действие, переданное в состояние. Up to you.

Итак, пока все тривиально. Идем дальше.

Переход:

Transition
struct TrBase{};template <typename Source,          typename Event,          typename Target,          typename Action,          Guard G,          class =          std::enable_if_t<std::is_base_of_v<StateBase, Source>&&          std::is_base_of_v<EventBase, Event> &&          std::is_base_of_v<StateBase, Target>>          >  struct Tr : TrBase{  using source_t = Source;  using event_t  = Event;  using target_t = Target;  using action_t = Action;    static constexpr Guard guard = G;};

Структура Tr тоже элементарна. Она параметризуется типом исходного состояния - Source, типом события Event, по наступлению которого произойдет переход в целевое состояние Target, и типом Guard.

Также прояснилась причина наследования рассмотренных сущностей от базового типа. Страхуем себя от передачи в шаблон некорректного параметра через отлов ошибки на этапе компиляции.

Таблица переходов:

Transition table
struct TransitionTableBase{};template<typename... T>struct TransitionTable : TransitionTableBase{    using test_t = typename NoDuplicates<Collection<T...>>::Result;    static_assert(std::is_same_v<test_t, Collection<T...>>,                "Repeated transitions");    using transition_p = type_pack<T...>;    using state_collection = typename NoDuplicates   <Collection<typename T::source_t... ,typename T::target_t...>   >::Result;    using event_collection = typename NoDuplicates  <Collection<typename T::event_t...>    >::Result;    using state_v = decltype(get_var(state_collection{}));  using event_v = decltype(get_var(event_collection{}));  using transition_v = std::variant<T...>;};

Нуу, тут я набросил на вентилятор, конечно. Хотя все не настолько пугающе, как выглядит.

Структура TransitionTable параметризуется списком переходов/transition-ов, которые собственно и описывают всю логику конечного автомата.

Первым делом нам необходимо подстраховать себя от копипаста и просигналить при компиляции, что у нас повторы в списке. Исполняем это с помощью алгоритма NoDuplicates из всем известной библиотеки Loki. Результирующий тип под псевдонимом test_t сравниваем в static_assert-e с исходным списком переходов.

Далее, допуская что static_assert пройден, параметризуем некую структуру type_pack списком переходов и выведенному типу назначаем псевдоним transition_p. Структура type_pack, а также современные алгоритмы и методы по работе со списками типов собраны в файле typelist.h. Данный хедер написан под чутким руководством этого продвинутого парня.

Тип transition_p понадобится нам далее в конструкторе класса StateMachine.

Следом проходим по списку переходов, вытаскиваем, очищаем от повторов и сохраняем в отдельные коллекции состояния и эвенты. Эти коллекции alias-им как state_collection и event_collection соответственно.

К чему эта эквилибристика?

Нам необходимо как-то хранить и процессировать в ходе работы программы информацию о событиях, эвентах, действиях и их взаимодействии при переходах, выраженную в типах.

Удобным вариантом для этой цели является std::variant (тафтология умышленна).

Последовательно параметризуем std::variant списком переходов (выведенному типу назначим псевдоним transition_v); списком состояний и списком эвентов и назначаем для удобства псевдонимы state_v и event_v соответственно.

Тут нюанс. Чтобы вывести transition_v нам достаточно пробросить в шаблонный параметр std::variant variadic pack (T...) из шаблонного параметра класса TransitionTable.

А вот чтобы вывести state_v и event_v мы используем

вспомогательную constexpr функцию
template<typename... Types>constexpr auto get_var (th::Collection<Types...>){return std::variant<Types...>{};}

Далее мы инстанцируем получившиеся типы в конструкторе класса StateMachine и сохраним их для дальнейшего использования в подходящих контейнерах, о чем совсем скоро.

Оставшихся к этому моменту читателей я не обрадую - начинается основной замес.

Целиком приводить класс StateMachine не буду, он громоздок, прокомментирую его для удобства восприятия по частям.

Контейнер transitions
template<typename Table>class StateMachine{//other stuffprivate:using map_type =std::unordered_map < Key, transition_v, KeyHash, KeyEqual>;Key key;map_type transitions;};

Основной контейнер, в котором мы храним информацию о переходах. Unordered - потому что мы хотим константное время для переключения между событиями. Память под контейнер выделяем стандартным аллокатором из кучи, но делаем это единожды, на этапе инициализации, до входа в основной цикл.

Объект типа Key хранит у себя значения индексов состояния и эвента:

Key
struct Key{  base_t state_idx = 0;  base_t event_idx = 0;};

Теперь стало понятно назначение статических членов idx в базовых сущностях. Я просто не знаю, как писать хэшеры для пустых структур. Тащить в строку название самого типа через typeid и _cxa_demangle для нас не вариант, мы же условились, что не пользуем RTTI.

Контейнер events
template<typename Table>class StateMachine{//other stuffprivate:using queue_type =  RingBufferPO2 <EVENT_STACK_SIZE, event_v, Atomic>;    queue_type events;};

events - очередь, в которую будут прилетать эвенты. Так как это чисто рантаймовая история, необходимо избежать динамических аллокаций. Поэтому реализуем ее на базе статического кольцевого буффера RingBufferPO2, который я позаимствовал здесь (отличная для своего времени работа!).

Помимо указанных контейнеров, в объекте типа StateMachine мы будем хранить текущее состояние/state и значение сторожа/guard:

state and guard
template<typename Table>class StateMachine{//other stuffprivate:state_v current_state;Guard guard = Guard::OFF;};

Саспенс уже не за горами.

Конструктор
template<typename Table>class StateMachine{public:using transition_pack = typename Table::transition_p;StateMachine(){  set(transition_pack{});} // other stuff};

В конструкторе метод set принимает аргументом объект с информацией о списке переходов, пробегается по нему, достает инфо о каждом состоянии и эвенте, заполняет контейнер transitions, а также запоминает начальные состояние и значение сторожа:

Метод set
template <class... Ts>void set (type_pack<Ts...>){(set_impl(just_type<Ts>{}), ...);};template <typename T>void set_impl (just_type<T> t){using transition = typename decltype(t)::type;using state_t = typename transition::source_t;using event_t = typename transition::event_t;Guard g = transition::guard;Key k;k.state_idx = state_t::idx;k.event_idx = event_t::idx;transitions.insert( {k, transition{}} );if (0 == key.state_idx) {key.state_idx = k.state_idx;guard = g;current_state = state_t{};}}

Итак, объект StateMachine сконструирован, пора его как-то шевелить.

Но перед этим забудем как страшный сон суммируем что уже рассмотрели к этому моменту:

  • Определили типы компонентов конечного автомата: состояние/state, событие/event, действие/action, сторож/guard

  • Определили тип переход/transition, который должен параметризоваться типами source state, event, target state, guard.

  • Определили тип таблицы переходов. В качестве шаблонных параметров ему передается список переходов/transition-ов, который и определяет алгоритмы работы автомата.

  • При компиляции в классе TransitionTable, на основе std::variant выводятся типы-коллекции переходов, состояний и эвентов, которые впоследствии при конструировании объекта StateMachine инстанцируются и сохраняются в контейнеры, с которыми уже можно работать в рантайме.

Стержневая идея моей имплементации автомата такова (вдохнули): при наступлении события, мы достаем из его типа индекс (idx), объединяем его с индексом текущего состояния в объекте Key, по которому в контейнере transitions находим нужный нам переход, где получаем знания о целевом состоянии, стороже и действии, которое требуется выполнить в этом переходе, а также сверяем значения сторожа с текущим, для подтверждения или отмены перехода/действия(выдохнули).

Теперь рассмотрим методы API нашего автомата, реализующие эту логику.

Переключать состояния мы можем двумя способами: вызывать немедленный переход методом fsm.on_event(event{}) (шаблонная версиия fsm.on_event<Event>() если тип события известен на этапе проектирования), или можем складывать события в очередь методом fsm.push_event(event{}), чтобы потом, например в основном цикле, разобрать ее методом fsm.process(). Также, если в состояние передано какое-то действие, то мы можем вызывать его методом fsm.state_action().

Рассмотрим их детальнее, начиная с последнего

Метод state action
template <typename... Args>void state_action (const Args&... args){state_v temp_v{current_state};    auto l = [&](const auto& arg){      using state_t =  std::decay_t<decltype(arg)>;    using functor_t = typename state_t::action_t;        if constexpr (!std::is_same_v<functor_t, void>){    functor_t{}(args...);      }  };    std::visit(l, temp_v);}  

В методе мы создаем локальную переменную типа std::variant<State...> temp_v и инициализируем ее текущим состоянием. Далее определяем лямбду, которая послужит аргументом в методе std::visit.

"Нырнув" с ее помощью в variant, мы выведем тип текущего состояния, из него в свою очередь вытащим тип переданного функтора, инстанцируем его (проверив, если его тип не void) и вызовем с паком аргументов, захваченных лямбдой по ссылке, и проброшенных с головного вызова.

Знаю, что иногда лямбда может юзать кучу, но похоже, это не мой случай. Поправьте меня, пожалуйста, если я заблуждаюсь. В этом случае не сложно будет заменить лямбду на callable object.

Метод on_event
template <typename Event,class = std::enable_if_t<std::is_base_of_v<EventBase, Event>>>void on_event(const Event& e){Key k;  k.event_idx = e.idx;  k.state_idx = key.state_idx;  on_event_impl(k);}void on_event_impl (Key& k){transition_v tr_var = transitions[k];    Key &ref_k = key;  Guard &ref_g = guard;  state_v &ref_state = current_state;    auto l = [&](const auto& arg){    using tr_t =  std::decay_t<decltype(arg)>;    using functor_t = typename tr_t::action_t;        if ( GuardEqual{}(ref_g, tr_t::guard) ){          using target_t = typename tr_t::target_t;            ref_k.state_idx = target_t::idx;      ref_state = target_t{};            functor_t{}();      }   };      std::visit(l, tr_var);}

Здесь, как я уже описывал, мы достаем индекс из эвента, объединяем его в Key с индексом текущего состояния, и в качестве ключа передаем в приватный метод on_event_impl(Key& k).

Там мы по принятому ключу достаем из контенера transitions объект типа std::variant<Tr...> и инициализируем им локальную переменную tr_var. Ну а далее - логика, схожая с предыдущим примером. Вызываем std::visit c tr_var и лямдой l, в которой из типа Tr получаем сведения о состоянии, в которое нужно перейти (target_t), стороже (tr_t::guard)и типе действия (functor_t) к исполнению.

Сверив значение сторожа перехода с текущим сторожем, мы или оcуществляем переход, инстанцируя и вызывая functor_t, и сохраняя target_t в переменную с текущим состоянием(current_state), или возвращаемся в исходное состояние. Где ждем смены значения сторожа и нового события.

Метод push_event
template <unsigned int N>void push_event (const Event<N>& e){  events.push_back(e);}

Тут все просто.

Метод set_guard
void set_guard (const Guard& g){  guard = g;}

Вызываем, когда в программе сложились условия для перехода в следующее состояние.

Метод process
void process (void){    state_action();    auto it = transitions.begin();    Key k;  k.state_idx = key.state_idx;    for (uint32_t i = 0; i != events.size(); ++i){        auto v = events.front();     auto l = [&](const auto& arg){      using event_t =  std::decay_t<decltype(arg)>;      k.event_idx = event_t::idx;      it = transitions.find(k);    }        std::visit(l, v);        if ( it != transitions.end() ){            events.pop_front();      on_event_impl(k);      return;        } else {      events.push_back(v);      events.pop_front();    }  }}

При вызове метода мы первым делом выполняем некое полезное действие (если не void), переданное в состояние, state_action().

Ну а далее пробегаемся по очереди эвентов и просто воспроизводим логику, уже описанную для метода fsm.on_event(event{}).

Разумеется, работу с событиями можно значительно ускорить, при этом расширив функционал автомата. Тип Event модернизируем

так
template <base_t N, base_t Priority>struct Event : EventBase{  static constexpr base_t idx = N;  static constexpr base_t pri = Priority;};

Теперь мы можем не пушить все события в одну очередь, а завести, скажем, std::array<queue_t, PRIRITY_NUM>, где индексом ячейки будет служить приоритет события. Тогда у нас получится приняв эвент, вытащить его приоритет, по нему, как по индексу за константное время попасть в нужную очередь событий, которая будет гораздо меньше, чем общая и быстрее в обработке.

И, что не менее важно, так мы сможем прыгать между состояниями не по очередности принятых эвентов, но по их приоритету.

На самом деле в следующей версии своего FSM у меня так и реализовано, но здесь я привожу упрощенный вариант.

Хорошо, каков же будет практический результат этой разнузданной шаблонной вакханалии?

Детектор нейтрино(нет)
struct green_a {/*toogle green led every 50ms*/}struct yellow_a {/*toogle yellow led every 50ms*/}struct red_a {/*toogle red led every 50ms*/}struct green_f {/*toogle green led every 150ms*/}struct yellow_f {/*toogle yellow led every 150ms*/}struct red_f {/*toogle red led every 150ms*/}using STATE_A(green_s, green_f);using STATE_A(yellow_s, yellow_f);using STATE_A(red_s, red_f);using EVENT(green_e);using EVENT(yellow_e);using EVENT(red_e);using fsm_table = TransitionTable    <    Tr<green_s, yellow_e, yellow_s, yellow_a, Guard::NO_GUARD>,    Tr<yellow_s, red_e, red_s, red_a, Guard::NO_GUARD>,    Tr<red_s, green_e, green_s, green_a, Guard::NO_GUARD>    >;int main(void){  //some other stuff  StateMachine<fsm_table> fsm;  fsm.push_event(red_e{});  fsm.push_event(yellow_e{});  fsm.push_event(green_e{});  while (1){    fsm.process();  }}

В этом примере структуры типа color_a(ction) - это действия при переходе; color_f(unctor) - функторы, которые будут выполняться каждый раз при заходе в стейт, ну и далее понятно.

Объявляем стейты, эвенты, переходы, таблицу переходов. Конструируем из класса StateMachine<fsm_table> наш конечный автомат fsm. Пушим события, заходим в while и наблюдаем аквасветодискотеку на нашей отладке.

Обращу еще ваше внимание на макросы, через которые организована декларация состояний и событий. Задача была исхитриться и не делать так:

using even_t = Event<1, 15>;

using state_t = State<1, state_functor>;

Очевидно, почему это плохо. Ручная индексация - практически неизбежные ошибки и очепятки.

Что ж, реализуем constexpr функцию, которая будет преобразовывать переданное ей желаемое название типа в число, которым и будем параметризовать шаблон. Ну и спрячем это все в удобную обертку.

Как-то так
#define STATE_A(str, act) str = State<name(#str), act>#define EVENT(str) str = Event<name(#str)>constexpr base_t name (const char* n){    base_t  res = 0;    for (base_t i = 0; n[i] != '\0'; i++){        char data = n[i];        for (base_t j = sizeof (char) * 8; j > 0; j--){            res = ((res ^ data) & 1) ? (res >> 1) ^ 0x8C : (res >> 1);      data >>= 1;    }  }  return res;};

После крайнего проекта на работе у меня на руках осталась отладка NUCLEO-H743ZI2, на ней я и запилил тестовый вариант (забирайте здесь).

С оптимизацией -O3 реализация приведенного примера (только сам FSM) заняла 6,8Кб, с HAL-ом и моргалками - 14,4Кб.

Конечно же, пока это не более чем эксперимент, проверка концепции. Но агрегат завелся, черт его дери.

Будет очень здорово, если сообщество укажет на неизбежные факапы и укажет путь к улучшениям. Также смею надеяться, что кто-то выделит из материала и что-то полезное для себя.

Спасибо за внимание!

Подробнее..

Конечные автоматы и django

01.06.2021 00:14:15 | Автор: admin

При работе над django-проектом, есть ряд must-have сторонних библиотек, если не хочется бесконечно изобретать велосипед. Средстав отладки sql запросов(debug-toolbar, silk, --print-sql из django-extensions), что-нибудь для хранения древовидных структур, переодических/отложенных задач(кстати, cron-like интерфейс есть у uswgi. EAV всё ещё бывает нужен, хотя часто его можно заменить jsonfield. И одна из таких крайне полезных вещей, но почему-то реже обсуждаемая в сети - FSM. Не так часто почему-то сталкиваюсь с ними в чужом коде.

Практически у каждой записи в БД есть некоторое состояние. Например, для комментария это может быть - опубликован/удален/удален модератором. Для заказа в магазине - оформлен/оплачен/доставлен/возврат и т.п. Причем переход из одного состояния в другое часто бывает размазан по коду и в нем присутствует бизнес-логика, которую надо обильно покрывать тестами(всё равно придется, но можно избежать тестирования элементарных вещей, например, что заказ может перейти в состояние "возврат денег" только после того, как он побывал в состоянии "оплачен".

Вполне логично было бы описывать такие переходы более декларативно и в одном месте. Вместе с необходимой логикой и проверкой доступа.

Вот пример кода из тестов библиотеки django-fsm

class BlogPost(models.Model):    """    Test workflow    """    state = FSMField(default='new', protected=True)    def can_restore(self, user):        return user.is_superuser or user.is_staff    @transition(field=state, source='new', target='published',                on_error='failed', permission='testapp.can_publish_post')    def publish(self):        pass    @transition(field=state, source='published')    def notify_all(self):        pass    @transition(field=state, source='published', target='hidden', on_error='failed',)    def hide(self):        pass    @transition(        field=state,        source='new',        target='removed',        on_error='failed',        permission=lambda self, u: u.has_perm('testapp.can_remove_post'))    def remove(self):        raise Exception('No rights to delete %s' % self)    @transition(field=state, source='new', target='restored',                on_error='failed', permission=can_restore)    def restore(self):        pass    @transition(field=state, source=['published', 'hidden'], target='stolen')    def steal(self):        pass    @transition(field=state, source='*', target='moderated')    def moderate(self):        pass    class Meta:        permissions = [            ('can_publish_post', 'Can publish post'),            ('can_remove_post', 'Can remove post'),        ]

Помимо прочего это прекрасно подходит для rest api. Мы может создавать эндпоинты для переходов между состояниями автоматически. Например, запрос /orders/id/cancel выглядит вполне логичным action`ом для viewset. И у нас уже есть необходимая информация для проверки доступа! А также для кнопочек в админке, и возможность рисовать красивые чарты с workflow :) Есть даже визуальных редакторы workflow т.е. не-программисты могут описывать бизнесс-процессы

Чем более декларативный и обобщенный код мы пишем, тем он надежней. Меньше объем кода, меньше его дублирование, меньше ошибок. Тестирование частично перекладывается на автора библоитеки, и можно сосредоточиться на бизнес-логике уникальной для проекта.

Подробнее..
Категории: Python , Fsm , Django , Django rest framework

Оптимизация цифрового автомата (FSM)

29.11.2020 14:18:58 | Автор: admin
О чём пост?

Данный материал представляет краткое описание проблемы в теории цифровых автоматов и объясняет один из способов решения данной проблемы, который был найден при попытке автоматизации процесса построения цифрововых автоматов.

Введение

Автомат система механизмов, устройств, в которой полностью автоматизированы процессы получения, преобразования, передачи энергии, материалов, информации.

Термин <<автомат>> в основном используется в двух аспектах:

  • техническом;

  • математическом.

При математическом подходе под автоматом понимается математическая модель, у которой должны быть входы, внутренние состояния и выходы. Детали структуры устройства не учитываюся и не рассматриваются.

В техническом подходе под автоматом понимается вполне реальное устройство, например, телефонный автомат, торговый автомат и т. д. В данном случае, естественно, известными являются детали внутреннего строения устройства.

С точки зрения сигналов цифровой автомат (ЦА) система, которая может принимать входные сигналы, под их воздействием переходить из одного состояния в другое, сохранять его до прихода следующего входного сигнала, выдавать выходные сигналы.

В данной работе рассматриваются цифровые сигналы и двоичная логика на базе логических элементов.

Структурно-функциональная схема цифрового конечного автоматаСтруктурно-функциональная схема цифрового конечного автомата

Применение

Теория автоматов лежит в основе всех цифровых технологий и программного обеспечения. Часть математического аппарата теории автоматов напрямую применяется при разработке лексических и синтаксических анализаторов для формальных языков, в том числе языков программирования, а также при построении компиляторов и разработке самих языков программирования, описания аппаратуры, а также разметки.

Другое важнейшее применение теории автоматов математически строгое нахождение разрешимости и сложности задач.

Автоматы Мура и Мили широко применяются при проектировании цифровых устройств на основе программируемых логических интегральных схем (ПЛИС). Наличие минимальной выходной задержки, связанной с переключением выходного регистра, отсутствие нестабильности переходного процесса на выходе автомата, отсутствие сквозного распространения сигнала через комбинационную схему от входа до выхода автомата, простота описания на языках описания аппаратуры делает автомат Мура практически незаменимым. Также автоматы Мура и взаимодействующие автоматы Мили используются в генетическом программировании.

Описание проблемы

Построение цифрового автомата -- довольно трудоёмкий процесс. Можно выделить следующие этапы разработки ЦА:

1) Очень часто разработка ЦА начинается с реализации графа, который отражает закладываемую логику в простом и понятном для человека виде.

2) Оптимизация графа -- с этой задачей человек может справиться довольно быстро.

3) Определение разрядности памяти. Минимальное число триггеров можно вычислить по формуле:

n=ceil(log_2(S))

где, S -- это число состояний, ceil -- функция приведения значения до ближайшего целого числа, которое не меньше исходного.

4) Присвоение состояниям кодов. Алгоритма для правильного задания кодов для состояний нет. Именно от этого зависит сложность уравнений, которые мы получим для входов триггеров и количество элементов необходимых для сборки схемы.

5) Составление таблицы состояний-переходов.

6) Составление булевых арифметических уравнений для входов триггеров. Карты Карно составляются по таблице состояний-переходов, уравнения минимизируются.

7) Преобразование уравнений для согласования с элементной базой.

8) Разработка электрической схемы.

Основная проблема -- отсутствие алгоритма для задания кодов состояниям автомата таким образом, чтобы уравнения для входов триггеров были как можно проще.

Решение

Была разработана программа для построения цифровых автоматов. На вход программа принимает граф. В программе граф представляется в наборе вершин и рёбер(вершина, входной сигнал, вершина для перехода). Итерируясь по рёбрам составляются таблицы истинности для каждого разряда в СКНФ и СДНФ. Методом Куайна-Мак-Класки минимизируются обе формы уравнений. Для каждого разряда выбирается выражение с минимальным количеством логических операций <<И>>, <<ИЛИ>>. Общее количество этих операций является критерием качества данной кодировки.

Количество возможных вариантов задания состояний можно рассчитать зная разрядность памяти(M) и количество состояний(S).

Количество кодов:

C=2^M;

Количество вариантов выборки(V) нужного количества состояний(S) из всего количества кодов(C), формула из комбинаторики:

V=\frac{C!}{(C-S)! \cdot S!};

Количество возможных вариантов задания состояний(A) равно:

A=S! \cdot V= \frac{C!}{(C-S)!};

Если перебирать все варианты и потом отобрать лучший, то в зависимости от графа программа может исполняться слишком долго. Такой вариант подойдёт для автоматов с небольшим числом вариантов задания кодов состояний.

Для автоматов с большим числом возможных вариантов задания состояний был разработан генетический алгоритм перебора вариантов состояний.

Схема генетического алгоритмаСхема генетического алгоритма

Результаты

Для исследования был спроектирован автомат с числом возможных вариантов задания состояний равным 6720. Для каждого варианта было рассчитано количество необходимых элементов для реализации.

Данный ЦА описывает поведение пчелы (для простоты восприятия), входной сигнал представляет 0(всё спокойно) или 1(пчела видит шершня).

Граф цифрового автомата, описывающий поведение пчелыГраф цифрового автомата, описывающий поведение пчелы

Описание ЦА:

  • Количество состояний: 5

  • Разрядность памяти: ceil(log2(5)) = 3

  • Разрядность входного сигнала: 1

Пример расчёта числа всех возможных вариантов построения автомата:

C=2^M=2^3=8;V=\frac{C!}{(C-S)! \cdot S!}=\frac{8!}{(8-5)! \cdot 5!}=56;A=S! \cdot V= 5! \cdot 56=6720;

Для любой выборки(V) нашлось не менее X(X<S!) перестановок с наилучшим исходом. Наилучший исход -- исход с минимальным числом элементов необходимых для реализации данного автомата. Для поиска способа кодирования c наилучшим исходом достаточно перебрать S! вариантов.

Анализ показал, что наибольшая вероятность встретить автомат с наилучшим исходом -- если количество 0 и 1 в кодах состояний будет равнозначным.

Для сложных автоматов, где перебор занимает много времени, эффективным решением будет применить генетический алгоритм, он не обязательно найдёт наилучший исход, но позволит быстро найти решение близкое к нему.

Подробнее..

Лаконичная реализация конечных автоматов в Matlab, Octave, C

16.06.2021 18:22:08 | Автор: admin

Актуальность


Конечные автоматы (finite state machines, fsm) штука полезная. Особенно они могут быть востребованы в средах, где в принципе нет развитой многозадачности (например, в Octave, который является в значительной степени бесплатным аналогом Matlab) или в программах для микроконтроллеров, где не используется по каким-то причинам RTOS. До недавнего времени у меня не получалось лаконично описать конечный автомат, хотя и очень хотелось это сделать. Лаконично, т.е. без воды, без создания лишних классов, структур данных, и т.д. Сейчас это, кажется, получилось и я спешу поделиться своей находкой. Возможно, я изобрёл велосипед, но возможно также, что кому-нибудь такой велосипед окажется полезен.


Начальные сведения


Конечный автомат задаётся:
  • набором состояний
  • набором событий
  • таблицей переходов (т.е. в каком состоянии по какому событию что делается и в какое новое состояние осуществляется переход)


Цель, которая стояла передо мной


Есть императивный язык, я буду рассматривать Octave, но это может быть и Matlab и C, например. Этот язык поддерживает:
  • функции
  • указатели на функции
  • то, что обычно поддерживают императивные языки (циклы, условные операторы и т.д.)


Хочется, чтоб базовые понятия языка (функции, структуры данных, массивы или что-то ещё) каким-то элегантным образом соответствовали тому, что нужно при реализации FSM. Профит в том, что
  • код будет самодокументированным
  • Doxygen или другие утилиты для анализа кода и генерации документации по коду будут давать дополнительную пользу


Описание идеи


  • Поведение внутри состояния должно описываться функцией. Поэтому функция хороший кандидат для того, чтоб её имя соответствовало состоянию.
  • Событие должно детектироваться тоже функцией, поэтому и для названий событий тоже можно использовать функции
  • Таблицу переходов можно задавать в либо в виде структуры данных, либо в виде switch/case-выражений внутри состояний


В чём проблема задания таблицы переходов в виде структуры данных?
  • Таблица может быть достаточно большой и сложной. В этом случае структура данных перестанет влезать в экран и поддержка такой таблицы будет не такой уж и удобной.
  • Структура данных требует какого-то объекта в памяти. Это дополнительное неудобство.
  • Структура данных требует специального её конструирования (скорее всего пошагового) это делает структуру программы более красивой, но анализировать такую машину состояний потом будет не так-то удобно.


Поэтому здесь я буду использовать switch/case инструкцию.

Единственной структурой данных будет переменная, где будет храниться состояние автомата.
Сами состояния будут идентифицироваться хэндлерами функций (function handlers), которые будут обрабатывать поведение машины в этом состоянии. Например:

function [new_state  data] = state_idle(data)    if data.block_index == 10        new_state = @state_stop;    else        % do something        data.block_index = data.block_index + 1;        printf('block_index = %d\n', data.block_index);    endendfunction [new_state data] = state_stop(data)    % set break flag    data.stop= 1;endfsm_state = @state_idle;data = struct();data.block_index = 0;data.stop = 0;while (1)    [fsm_state data] = fsm_state(data)    if data.stop        break;    endend


В этом коде вся идея, собственно, и описана. В Си, вместо хэндлера функции будет указатель на функцию, всё остальное останется так же.

Пример из жизни :)


В качестве примера я реализовал на Octave игру Life, Джона Конвея. Если сконфигурировать её в режиме 100 х 100, то она будет симулировать работу 10 000 конечных автоматов и при этом работает она достаточно эффективно. В простейшем варианте (без событий), код для игры выглядит следующим образом:

function [new_state] = state_alive(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        new_state = @state_alive;    else        new_state = @state_dead;    endendfunction [new_state] = state_dead(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));        if (alive_count == 3)        new_state = @state_alive;    else        new_state = @state_dead;    endend% main scriptaddpath('fsm_life')debug_on_error(1)size_x = 30;size_y = 30;init_alive_percentage = 30;% initialization selection:%init = 'random';%init = 'cycle';init = 'glider';field = cell(size_y, size_x);[field{:}] = deal(@state_dead);switch (init)case 'random'    init_alive_count = round((size_x * size_y) * init_alive_percentage / 100);    for n=(1:init_alive_count)        x = floor((size_x-0.0000001)*rand())+1;        y = floor((size_y-0.0000001)*rand())+1;        field{y,x} = @state_alive;    endcase 'cycle'    field{2,1} = @state_alive;    field{2,2} = @state_alive;    field{2,3} = @state_alive;case 'glider'    field{1,3} = @state_alive;    field{2,3} = @state_alive;    field{3,3} = @state_alive;    field{3,2} = @state_alive;    field{2,1} = @state_alive;endprintf("Initial distribution:\n");cellfun(@(x)x == @state_alive, field)% simulationfor step = (1:100)    field_new = cell(size(field));    for x=(1:size_x)        for y=(1:size_y)            x_min = max(x-1, 1);            x_max = min(x+1, size_x);            y_min = max(y-1, 1);            y_max = min(y+1, size_y);            neighbours = field(y_min:y_max, x_min:x_max);            field_new{y,x} = field{y,x}(neighbours);        end    end    field = field_new;    printf('Distribution after step %d\n', step );    cellfun(@(x)x == @state_alive, field)    figure(1); imagesc(cellfun(@(x)x == @state_alive, field));    pause(0.05);end


Если хочется больше самодокументируемости и явного определения событий, тогда к двум функциям, отвечающим за состояния, добавится ещё 3 функции, отвечающие за события:
function event = event_die(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        event = '';    else        event = 'die';    endendfunction event = event_spawn(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    if (alive_count == 3)        event = 'spawn';    else        event = '';    endendfunction event = event_survive(neighbours)    alive_count = sum(sum(cellfun(@(x)x == @state_alive, neighbours)));    alive_count -= 1;    if (alive_count == 2) || (alive_count == 3)        event = 'survive';    else        event = '';    endendfunction [new_state] = state_alive(neighbours)    event = '';    event = [event, event_die(neighbours)];    event = [event, event_survive(neighbours)];    switch event    case 'die'        new_state = @state_dead;    case 'survive'        new_state = @state_alive;    otherwise        msg = sprintf('Unknown event: %s\n', event);        error(msg);    endendfunction [new_state] = state_dead(neighbours)        event = event_spawn(neighbours);        switch event    case 'spawn'        new_state = @state_alive;    case ''        new_state = @state_dead;    otherwise        msg = sprintf('Unknown event: %s\n', event);        error(msg);    endend


Основной скрипт в этом случае останется тем же самым.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru