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

Автоматизация

Автоматизация и промышленная электроника когда одним Arduino сыт не будешь

19.05.2021 12:19:31 | Автор: admin
Если играться с контроллерами, то почему с маленькими?

Очень часто, когда речь заходит об автоматизации чего-либо, то в разговоре всплывает Arduino, его производные или же Raspberry PI и прочие одноплатники. Но есть отличие от домашних поделок, где можно пользоваться чем угодно ради экономии и потому, что это простое и доступное решение. В сфере автоматизации/модернизации объектов, связанных с промышленностью, речь идёт исключительно о специализированных промышленных контроллерах и системах визуализации, диспетчеризации/удалённого управления и все это исключительно с сертификатами соответствия и лицензиями.
Решений такого класса море и порой сложно в них разобраться. Разумеется, все возможные варианты разобрать невозможно, но мы с коллегами уже несколько лет работаем в этой сфере и потому какое-то количество опыта набралось. Мы поделимся своим и если вам есть, что сказать просим писать комментарии.

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

Цель выполнить задание и не потерять заложенную прибыль. А вот как это получается и получается ли вообще это вопрос опыта.

Самая маленькая команда должна включать в себя:

  • проектанта
  • опытного инженера в области промышленной электроники
  • электромонтеров/электромехаников
  • программиста

Если же компания постоянно развивается и работает в этой области, то она неуклонно растёт, так как несколько объектов делается одновременно и все они находятся на разной стадии.

Стадия подготовки к тендеру


О начале тендера, на выполнение чего-то, мы не всегда узнаем заранее. Иногда информация запаздывает, тендер ожидается на днях, или долго шли подготовительные работы, чтобы принять участие. Но это не меняет сути: нужно посчитать стоимость автоматизации объекта, к примеру. Сначала изучается планируемый объем и срок работ (если, конечно, сроки внятно прописаны) и принимается решение о выполнимости своими силами, либо с привлечением подрядчиков.
Часто в нагрузку к системам автоматики докидывают смежные (в понимании заказчика/инвестора) вещи. Нам постоянно навязывают обычную электрику, вплоть до внешнего освещения, локальные сети и телефонные линии в промышленных и административных помещениях, электрические сети низкого напряжения и понижающие трансформаторные подстанции. И вот хотелось бы иметь только автоматику в чистом виде, да нет её. Так как для инвесторов, что электроника, что электрика одного поля ягоды. Попутно нужно понимать, что компания, занимающаяся автоматизацией, заканчивает объект последней. Именно она будет связывать воедино разрозненные структуры пожарные системы, контроли доступа, видео наблюдение, связь с серверной и оптическими линиями, диспетчеризацию и удалённый доступ. Если же технологический процесс объекта содержит в себе независимые самоуправляемые системы или машины (автоматические линии, вентиляционные централи, сигнализацию, запасной генератор, солнечные панели и преобразователи к ним и т.д.), то задача усложняется ещё и необходимостью соединить между собой оборудование разных производителей, порой с совсем разными протоколами промышленной связи.
Вооружившись знаниями за все предыдущие годы и выполненные объекты, можно попробовать посчитать общую себестоимость работ (включая материалы, покупку оборудования, количество человеко-часов, стоимость всех выездов на объект для проведения монтажа, пуско-наладки и последующих сервисных выездов в течении гарантийных 5-7 лет).
Q: На основании чего производится расчёт?
Пожеланий инвестора и приблизительных догадок об общем бюджете объекта. Всегда можно воспользоваться открытой информацией. Оттуда можно выудить сколько процентов от общей суммы пойдёт на данную сферу. Грубо говоря, если объект будет стоить 10 млн евро, то системы автоматизации плюс электрика это около 1,5-2 млн.
Хорошо, когда есть возможность пообщаться с генеральным подрядчиком, который координирует весь ход работ, от выравнивания территории под фундаменты и дороги, до капитального строительства и озеленения территории. Всегда есть кто-то, кто пытается, не выходя за рамки первичного бюджета, построить объект и сдать его срок. Не завидуем им совсем, но пытаемся сотрудничать на всех этапах реализации. Это действительно упрощает жизнь в дальнейшем.
Итак, получаем информацию от генерального подрядчика, что общая квота от этого бюджета на автоматику и электрику не сможет превысить тех же 2 млн. Теперь начинается самое интересное в тендере участвует минимум 3 компании на каждую сферу работ. А значит мы встретимся ещё, как минимум, с двумя предложениями от других компаний, что претендуют выполнить этот же объем работ, что и мы. Нужно предложить на тендере сумму меньше них (а мы, разумеется не знаем сколько они заявят), ну и не остаться без прибыли, тем более не уйти в минус.
Q: Что мы используем и почему?
Зависит от возможностей оборудования и пожеланий заказчика. Из контроллеров это Siemens s1200-s1500 и его аналоги. Панели к ним Weintek и Astraada. Реле, пускатели, клеммные колодки Eaton и т.д. Частотных преобразователи Danfoss, Mitsubishi, LS IS. Коммуникация Modbus RTU, Modbus TCP, Ethernet, Profibus и оптика. Подробнее о подборе оборудования будет в следующей статье относительно проектирования.

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

Сначала изучается ТЗ.Как известно без внятного ТЗ получается непонятно что. Инвестор не будет пояснять расплывчатые формулировки и нужно готовиться ко всему. Подводные камни ждут в каждом предложении и нужно уметь их находить. К примеру, если пишется, что необходимо выполнить систему автоматизации и удалённого управления с возможностью работы при кратковременных пропаданиях питания от сети, то это означает, что всего лишь необходимо доложить по одному блоку бесперебойного питания либо аккумуляторы в каждых шкаф управления техпроцессом и обычный UPS, через который запитана серверная стойка или компьютер диспетчера с системой управления. Задача поддержать работу всех контролёров, во всех шкафах управления и всех роутеров и модемов, чтобы собрать все статусы ошибок различных устройств, автоматических линий и машин, записать все это в логи и передать на сервер/компьютер с системой удалённого управления, который через SMS-шлюз сообщит наружу подготовленному списку номеров, что пропало напряжение сети и все оборудование остановилось.
Как можно понять из требований, это достаточно недорогое удовольствие обойдётся в несколько сотен евро, и его легко учесть на стадии проектирования. Однако формулировка может быть несколько иной выполнить систему автоматизации с возможностью продолжения работы объекта при пропадании питания от сети, до момента её появления. И вот это уже совсем другое. Здесь хотят иметь либо вторую линию резервного питания со своим понижающим трансформатором и шкаф автоматического переключения нагрузки с одного источника питания на другой с анализаторами параметров сети. Либо, что встречается гораздо чаще заказчик хочет иметь резервный источник питания в виде дизельного генератора, который сможет поддержать работу объекта в течении нескольких часов. Подобные генераторы стоят десятки тысяч евро. И не забываем про шкаф переключения нагрузки, благо в большинстве случаев современные дизельные генераторы имеют свои неплохие контроллеры с внешним LCD экраном и возможностью отслеживания параметров сети сразу по нескольким параметрам и управлением силовыми контакторами, что переключают нагрузку от одного источника (сети) на другой (генератор) и наоборот при восстановлении нормальной работы первичного источника.
К примеру, генераторы фирмы FOGO, Elem Power или их аналоги, с контроллером, к примеру, datacom-d500-lite или inteli lite amf 25. Тут шкаф переключения нагрузки (AVR) будет состоять всего из 2х силовых контакторов с дополнительными контактами, чтобы снимать состояние, какой из них включён и сделать их одновременно включение взаимоисключающим.

Фото панели управления генератором

После прочтения ТЗ все обычные работы:прокладка кабельных трасс, монтаж кабельных лотков, монтаж наружного освещения, заземления, громоотвода и молниезащиты (если есть в требованиях), изготовление шкафов управления (их мы делаем сами), всё калькулируется по нормам. В нашем случае эти нормы европейские. Потом прикидывается общая стоимость всех возможных шкафов управления с их наполнением в виде контроллеров и их обвязки, частотных преобразователей или устройств плавного пуска, серверных стоек, компьютеров и телевизоров (это относительная новинка, так как стали отказываться от синоптических таблиц и предпочитают иметь выведенную систему визуализации техпроцесса на 4-6 телевизоров по 50-55", склеенных в мульти-экран), считается за сколько рабоче-часов будет изготовлено, смонтировано. Сюда же добавляется прибыль. Есть несколько быстрых шаблонов по подсчёту стоимости к примеру, в конечную стоимость одного шкафа управления, включена оплата работы проектанта, стоимость сборки шкафа и стоимость программирования контроллера и панели HMI. Чтобы быстро считать многие вещи упрощаются. Итоговая стоимость шкафа управления будет = себестоимость всех компонентов * 1,5(2). Разумеется, сам по себе шкаф ничего не делает. Его ещё нужно привезти на объект, смонтировать на своём месте, проложить к нему кабельные линии и трассы, уложить питающие и управляющие кабеля, подключить это все и запустить. Приблизительно можно подсчитать общую длину будущих кабельных линий, учесть проколы под дорогами (это дорого), посмотреть требования к силовым линиям (медные или алюминиевые), подумать на счёт трансформаторной подстанции и генератора. Многие вещи сразу умножаются на 2, чтобы избежать нехватки средств в будущем. К примеру, тот же генератор весит 3-5 тонн. Значит нужно организовать выгрузку с помощью крана и прочный фундамент, соответственно ещё и заземление. Внимательно изучаются расположения разных технологических зданий внутри объекта. По современным нормам уже никто не хочет связывать отдалённые здания витой парой хоть 5й, хоть 6й категории. Только оптика, только хардкор. Считаем и стоимость укладки, сварки оптики и оборудования для соединения между собой разных зданий. Внутри зданий и на малых расстояниях, для экономии, используем utp категории 5е/6e.

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

После того, как мы все посчитали, оказывается что мы как обычно не вписываемся в ожидаемые рамки бюджета. В ход идут оптовые закупки оборудования напрямую у производителя и соответственно скидки за многолетнюю лояльность. Иногда скидки могут достигать десятков процентов. Это ничего не значит, если вы покупаете на объект 1 контроллер за тысячу евро и один частотный преобразователь за пару сотен. Но когда суммарно на объекте несколько десятков частотных преобразователей, несколько контроллеров и несколько шкафов, то экономия получается порядочной. Это наша подушка безопасности. Если что-то упущено на стадии подготовки к тендеру, то мы используем сэкономленные средства на это. Если же все хорошо, то это плюс к нашей прибыли. В конечном счёте, никто из нас не альтруист и все мы здесь за этим. Хотя есть ещё и моральное удовольствие от хорошо выполненной работы. Но это уже невозможно посчитать)

Фото запущенного объекта на память)

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


Подробнее..

Интервью с СЕО FitBase будущее за автоматизацией

28.05.2021 18:18:56 | Автор: admin

Знаем, как сложно родителям выбрать профессию для своего ребенка. Этот выбор определит, будет ли он доволен своей жизнью и будет ли он с удовольствием просыпаться по утрам, чтобы пойти на работу. Для того, чтобы немного облегчить выбор родителей, команда школы программирования для детей Пиксель провела серию интервью с состоявшимися специалистами в мире IT. В предыдущем интервью мы рассказали о менеджере проектов АСУ.

Василий Суворов, CEO FitBaseВасилий Суворов, CEO FitBase

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

Сколько тебе лет? Какой ВУЗ окончил?

Мне 33 года, окончил МГУС, факультет Информационные технологии.

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

Я плохой разработчик, я могу написать какие-то простые вещи на PHP или SQL-запрос. У меня лучше получается делать прототипы, писать технические задания и выяснять потребности наших клиентов. Вообще разработка тесно связана с автоматизацией любых задач. На мой взгляд, это основная задача инженера придумать как сделать процесс максимально автоматизированным. Это очень крутое чувство, когда что-то создаешь придумал, запрогал и видишь, как работает твое детище. И неважно, что это простой скрипт по парсингу данных или сложный продукт, который позволяет автоматизировать фитнес-клуб или большой завод.

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

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

Тогда не были распространены кружки, курсы по программированию. В веб-дизайне все просто открываешь сайт, который нравится и смотришь, как он написан (это то, что касается верстки). В помощь форумы, статьи и все, что возможно нагуглить. Еще у меня была книга PHP и MySQL. Разработка веб-приложений, автор Томсан Лора.

Расскажи о своей работе, какие у тебя обязанности, сколько зарабатываешь?

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

Команда FitBaseКоманда FitBase

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

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

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

Знаю, у тебя есть сын. Ты бы отдал его в программирование? Знаешь какие-нибудь школы по программированию?

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

Какие профессии в области IT наиболее перспективные по твоему мнению?

Я думаю, будущее за нейроинтерфейсами, робототехникой и автоматизацией.

Нейроинтерфейс это такая штука, которая позволяет связать мозг и компьютер напрямую. Мы не будем печатать или отдавать команды голосом, подумал и действие совершилось. Как рукой двигаем, так и компьютером, телефоном или роботом можно будет управлять. Это суперкрутой рынок. Илон Маск уже выпускает первые прототипы.

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

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

Подробнее..

Как я сделал свою сборку Gulp для быстрой, лёгкой и приятной вёрстки

03.06.2021 18:21:18 | Автор: admin

Серьёзно и профессионально я начал заниматься вёрсткой в 2019 году, хотя до этого ещё со школы интересовался данной темой как любитель. Поэтому новичком мне себя назвать сложно, но и профессионалом с опытом 5+ лет я тоже не являюсь. Тем не менее, я успел познакомиться со сборщиком Gulp, его плагинами и сделал для себя хорошую, как по мне, сборку для работы. О её возможностях сегодня и расскажу.

ВАЖНО! В этой статье речь пойдёт о самой последней версии сборки. Если вы пользуетесь версиями сборки, вышедшими до публикации этой статьи, информация будет для вас не релевантна, но полезна.

Какие задачи решает эта сборка?

  • вёрстка компонентами (вам не нужно в каждую страницу копировать head, header, footer и другие повторяющиеся элементы, вплоть до кнопок или кастомных чекбоксов);

  • вёрстка с препроцессорами (SASS/SCSS);

  • конвертация шрифтов из ttf в eot, woff, woff2;

  • лёгкое (почти автоматическое) подключение шрифтов;

  • лёгкое (почти автоматическое) создание псевдоэлементов-иконок;

  • обработка изображений "на лету";

  • минификация html/css/js файлов;

  • возможность вёрстки с использованием php;

  • выгрузка файлов на хостинг по FTP;

  • несколько мелких задач с помощью миксинов.

Для тех, кому лень читать и делать всё руками - сразу ссылка на сборку.

Собственно создание сборки

Начнём собирать нашу сборку (простите за тавтологию). Предварительно нам потребуется уже установленная на компьютере LTS-версия Node.js и NPM (входит в пакет Node.js) либо Yarn. Для нашей задачи не имеет значения, какой из этих пакетных менеджеров использовать, однако я буду объяснять на примере NPM, соответственно, для Yarn вам потребуется нагуглить аналоги NPM-команд.

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

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

Далее будет намного удобнее работать через Visual Studio Code (поскольку у него есть встроенный терминал) или любой другой удобный вам редактор + терминал.

Прежде всего, нам нужно установить сам Gulp. Делается это двумя командами npm i gulp -global - устанавливаем Gulp глобально на систему и npm i gulp --save-dev - устанавливаем Gulp локально в проект. Ключ --save здесь отвечает за сохранение версии плагина при дальнейшей установке (без него вам может установить более новую, несовместимую с другими плагинами версию), а ключ -dev указывает на то, что этот пакет необходим только во время разработки проекта, а не во время его выполнения. Например, если мы устанавливаем в проект пакет Swiper, который содержит скрипты слайдера и будет отображаться на странице, мы будем устанавливать его без ключа -dev, поскольку он нужен для выполнения, а не для разработки.

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

После этого нам нужно подключить Gulp в нашем файле, для того чтобы он исполнялся. Это делается с помощью require:

const gulp = require('gulp');

Далее, для каждой задачи будем использовать модули в отдельных файлах. Для того, чтобы не подключать каждый модуль отдельно, нужно установить и подключить плагин require-dir. Устанавливается он всё той же командой (как и все последующие плагины, поэтому далее повторяться не буду, просто знайте, что установить - это npm i $PLUGIN-NAME$ --save-dev). После установки подключаем его и прописываем путь к директории, в которую будем складывать модули (у меня это директория tasks):

const gulp = require('gulp');const requireDir = require('require-dir');const tasks = requireDir('./tasks');

Первая задача

Давайте проверим, всё ли мы правильно сделали. Создадим в директории tasks файл модуля с именем hello.js. В созданном файле напишем простейшую функцию, которая будет выводить в консоль строку "Hello Gulp!" (можете придумать что-то менее банальное, если хотите).

module.exports = function hello () {console.log("Hello Gulp!");}

Теперь вернёмся в gulpfile.js и зададим там задачу hello:

const gulp = require('gulp');const requireDir = require('require-dir');const tasks = requireDir('./tasks');exports.hello = tasks.hello;

Теперь командой gulp hello в терминале запустим нашу задачу. Если всё сделано правильно - в терминал должно вывестись приблизительно такое сообщение:

[13:17:15] Using gulpfile D:\Web projects\Easy-webdev-startpack-new\gulpfile.js[13:17:15] Starting 'hello'...Hello Gulp![13:17:15] The following tasks did not complete: hello[13:17:15] Did you forget to signal async completion?

Так же, можно получить список всех заданных задач командой gulp --tasks.

Файловая структура

Теперь, когда мы создали первую функцию, можно подумать о том, какая структура будет у наших проектов. Я предпочитаю использовать директорию (папку) /src для хранения исходных файлов и директорию /build для готовых файлов проекта.

В директории src/ нам понадобятся следующие поддиректории:
  • components/ - директория для компонентов

  • components/bem-blocks/ - директория для БЭМ-блоков

  • components/page-blocks/ - директория для типовых блоков страницы, таких как хедер, футер и т.п.

  • fonts/ - директория для шрифтов

  • img/ - директория для изображений

  • js/ - директория для файлов JavaScript

  • scss/ - директория для файлов стилей

  • scss/base/ - директория для базовых стилей, которые мы изменять не будем

  • svg/ - директория для файлов SVG

  • svg/css/ - директория для SVG-файлов, которые будут интегрироваться в CSS

Получиться в итоге должно приблизительно следующее:

 project/  build/ 
Подробнее..

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

10.06.2021 18:12:02 | Автор: admin

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

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

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

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

На первых годах жизни Pixel Gun 3D использовалась простая схема: весь контент добавлялся и редактировался вручную. Нужно поменять урон пушке? Заходишь в Unity, открываешь нужный файл и правишь руками. Дело на пару минут.

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

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

Нужно было глобально что-то менять.

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

Из гугл-таблиц в Unity

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

Для получения данных с таблиц мы используем Google Apps Script. Первое время заводили отдельные скрипты на каждую таблицу, в которых обрабатывали данные в JSON. Затем, получая в редакторе JSON, применяли их по назначению.

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

Так выглядит наш скрипт:
function doGet(e){ if (e === undefined || e.parameter === undefined) {   return FailWithMessage("nullable parameters"); } var tableId = e.parameter["table"]; var listName = e.parameter["list"]; if (listName !== undefined && listName !== "" && listName !== "null") {   var startRow = parseInt(e.parameter["startRow"]);   var startColumn = parseInt(e.parameter["startColumn"]);   var numRow = parseInt(e.parameter["numRow"]);   var numColumn = parseInt(e.parameter["numCol"]);   return GetSigleList(tableId, listName, startRow, startColumn, numRow, numColumn); } else {   return GetAllTable(tableId); }}  function GetSigleList(tableId, listName, startRow, startColumn, numRow, numColumn){ var ss = SpreadsheetApp.openById(tableId); if (ss == null) {   return FailWithMessage("table with name: " + tableId + "not found"); } var sheet = ss.getSheetByName(listName); if (sheet == null) {   return FailWithMessage("list with name: " + listName + "not found"); }  if (numRow < 1) numRow = sheet.getLastRow(); if (numColumn < 1) numColumn = sheet.getLastColumn(); var range = sheet.getRange(startRow, startColumn, numRow, numColumn); var data = range.getValues(); var str = JSON.stringify(data);  var resultObject = {   "resultCode": 2,   "message": str }; var result = JSON.stringify(resultObject); return ContentService.createTextOutput(result);}  function GetAllTable(tableId){ var ss = SpreadsheetApp.openById(tableId); if (ss == null) {   return FailWithMessage("table with name: " + tableId + "not found"); }  var result = {};  var listModes = ss.getSheets(); for(var i = 0; i< listModes.length; i++) {   var sheet = listModes[i];   var sheetName = sheet.getSheetName();     var range = sheet.getRange(1, 1, sheet.getLastRow(), sheet.getLastColumn());   var data = range.getValues();   result[sheetName] = data; }  var str = JSON.stringify(result);  var resultObject = {   "resultCode": 2,   "message": str };  var result = JSON.stringify(resultObject); return ContentService.createTextOutput(result);} function FailWithMessage(message){ var result = {   "resultCode": 1,   "message": message };   var str = JSON.stringify(result);  return ContentService.createTextOutput(str);}

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

После публикации получится ссылка такого формата:

https://script.google.com/macros/s/WwlCZODTDRXJaHhdjfwFRcKtHRQOHqzYisjndduZzDihMpXehLrNxdi/exec

Ее нужно использовать для запуска скрипта. Чтобы скрипт знал, с какой таблицы нужны данные, в get-запрос подставляем ID таблицы. Получить его можно из URL таблицы. Например, в https://docs.google.com/spreadsheets/d/example_habr/edit#gid=0, ID будет example_habr.

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

Полный запрос будет выглядеть так:

https://script.google.com/macros/s/WwlCZODTDRXJaHhdjfwFRcKtHRQOHqzYisjndduZzDihMpXehLrNxdi/exec?table=example_habr&list=MyList&startRow=1&startColumn=2&numRow=10&numCol=5

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

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

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

Но мы пошли дальше.

Из Unity в гугл-таблицы

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

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

Начали с простеньких задачек. Например, мы часто превышали лимит в 500 символов на описание апдейта в Google Play. Стор такое отклоняет, нужно переписывать и отправлять заново. Задались вопросом, а есть ли формула для подсчета символов в ячейке? Разумеется, в гугл-таблицах большой перечень базовых формул, которые можно комбинировать как угодно и решать практически любые задачи. Написали в ячейке, чтобы описание апдейта автоматически проверялось на количество символов =ДЛСТР(номер ячейки). Теперь проблемы нет.

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

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

Например, решили составить коллекции для галереи оружия (это такие подборки оружия по схожим признакам: цвет, сеттинг, сезон). Чтобы составить одну коллекцию, нужно оценить визуал, смотрятся ли они вместе, схожий ли у них класс и другие детали. Для этого сделали таблицу, в которую из базы данных выгрузили список всех пушек, написали формулу и подгрузили превьюшки, с которыми можно визуально работать. А уже из таблиц по балансу подтягиваем названия, теги и остальную информацию.

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

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

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

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

Где используем автоматизацию

Мы применяем ее практически везде, где это возможно. И всегда ищем новые способы, чтобы сделать себе жизнь проще и переложить часть работы на формулы.

Балансировка контента

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

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

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

Генерация сущностей

Автоматизацию можно использовать не только для баланса, но и для генерации нового контента. Так, в каждом батлпассе у нас есть порядка 140 разных челленджей (например, сыграть 10 игр в определенном режиме или убить 30 противников из пистолета). Каждый раз придумывать это вручную долго и нудно, поэтому сделали генерацию. Собрали подробный список условий и прямо из гугл-таблицы создаем квесты теперь одна формула придумывает нам все задачи на каждый сезон.

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

Подобным образом мы генерируем задачи и для клановых войн.

Пример формулы:

=ifs(I2="EndMatch";ifs(AE2<=TasksData!$O$34+TasksData!$N$34;"TeamDuel";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34;ЕСЛИ(A2=0;"TeamDuel";"Spleef");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34;"Duel";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34;ЕСЛИ(A2=0;"Duel";"BattleRoyale");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34;ЕСЛИ(A2=0;"TeamFight";"DeadlyGames");AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34;"CapturePoints";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34;"FlagCapture";AE2<=TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34+TasksData!$G$34;"Deathmatch";AE2>TasksData!$O$34+TasksData!$N$34+TasksData!$M$34+TasksData!$L$34+TasksData!$K$34+TasksData!$J$34+TasksData!$I$34+TasksData!$H$34+TasksData!$G$34;"TeamFight"); I2="killPlayer";ifs(AE2<=TasksData!$N$35;"TeamDuel";AE2<=TasksData!$N$35+TasksData!$L$35;"Duel";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35;ЕСЛИ(A2=0;"TeamFight";"BattleRoyale");AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35;"DeadlyGames";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35;"CapturePoints";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35;"FlagCapture";AE2<=TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35+TasksData!$G$35;"Deathmatch";AE2>TasksData!$N$35+TasksData!$L$35+TasksData!$K$35+TasksData!$J$35+TasksData!$I$35+TasksData!$H$35+TasksData!$G$35;"TeamFight"); I2="killPet";ifs(AE2<=TasksData!$G$36;"Deathmatch";AE2>TasksData!$G$36;"TeamFight"); I2="killPlayerThroughWall";ifs(AE2<=TasksData!$I$37;"CapturePoints";AE2<=TasksData!$I$37+TasksData!$H$37;"FlagCapture";AE2<=TasksData!$I$37+TasksData!$H$37+TasksData!$G$37;"Deathmatch";AE2>TasksData!$I$37+TasksData!$H$37+TasksData!$G$37;"TeamFight"); I2="killPlayerFlying";ifs(AE2<=TasksData!$I$38;"CapturePoints";AE2<=TasksData!$I$38+TasksData!$H$38;"FlagCapture";AE2<=TasksData!$I$38+TasksData!$H$38+TasksData!$G$38;"Deathmatch";AE2>TasksData!$I$38+TasksData!$H$38+TasksData!$G$38;"TeamFight");I2="ramEscort";"Siege";I2="escortDestroyGate";"Siege";I2="winBrNoChest";"BattleRoyale";I2="crashChest";"BattleRoyale";I2="winBrInParty";"BattleRoyale";I2="flagCapture";"FlagCapture";I2="pointCapture";"CapturePoints")

Пример таблицы с вводными для генератора:

Пример формулы (в ней представлена часть с генерацией описаний задач, Key_номер это ключи локализаций, из которых составляются задачи):

=IFS(I2="endMatch";(ЕСЛИ(T2=0;"Key_7220";"Key_7234"));I2="killPet";ЕСЛИ(W2="None";"Key_7228";"Key_7224");I2="killPlayer";ЕСЛИ(Q2=1;"Key_7227";(ЕСЛИ(W2="NONE";ЕСЛИ(R2=1;"Key_7232";ЕСЛИ(S2=1;"Key_7233";"Key_7221"));"Key_7216")));I2="killPlayerFlying";"Key_7225";I2="killPlayerThroughWall";"Key_7226";I2="ramEscort";"Key_7235";I2="escortDestroyGate";"Key_7236";I2="winBrNoChest";"Key_7229";I2="crashChest";"Key_7230";I2="winBrInParty";"Key_7231";I2="flagCapture";"Key_7237";I2="pointCapture";"Key_7238";I2="";"")

И еще более эпичная, уже проходящая через ячейки параметров и рандомайзеров:

=ifs(L3="DeadlyGames";0;L3="BattleRoyale";0;L3="TeamDuel";0;1=1;ЕСЛИ(I3="killPlayer";ifs(A3=0;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47)*6;1;0);0);A3=1;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$47)*6;1;0);0);A3=2;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")>=(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$47+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$C$47+TasksData!$C$48+TasksData!$D$34+TasksData!$D$36+TasksData!$D$38)*6;ЕСЛИ(СЧЁТЕСЛИ($I$2:I2;"killPlayer")<(TasksData!$B$34+TasksData!$B$36+TasksData!$B$38+TasksData!$B$48+TasksData!$C$34+TasksData!$C$36+TasksData!$C$38+TasksData!$D$34+TasksData!$D$36+TasksData!$D$38+TasksData!$B$47+TasksData!$C$47+TasksData!$C$48+TasksData!$D$47)*6;1;0);0));0))

Симуляция процессов

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

Пример части вводных данных:

Пример использованных формул в ячейках:

=СЛУЧМЕЖДУ(1;100)
=СРЗНАЧ(13;15)*6
=СУММ(B4:F4)
=IFS($A32<=G32;"Mythic";$A32<=F32;"Legend";$A32<=E32;"Epic";$A32<=D32;"Rare";$A32<=C32;"Common")

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

Пример результатов дропа по номерам открытий:

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

Воркфлоу создания таблицы

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

Расскажу подробнее по шагам (цифры в примере изменены в рамках конфиденциальности):

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

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

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

http://адрес_сервиса/путь/номер/имя картинки.jpg

Для добавления в таблицу используем простую функцию:

=IMAGE("https://files.fm/u/wdrhemgnk#/view/special_offer_pixelman_reward_big.png")

Пример формулы для подтягивания артов с перебором:

=ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!B:F);5;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!B:F);5;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!C:F);4;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!C:F);4;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!D:F);3;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!D:F);3;ЛОЖЬ); ЕСЛИ(ЕНД(ВПР(A4;importrange(имя_таблицы;Лист1!E:F);2;ЛОЖЬ))=ЛОЖЬ;ВПР(A4;importrange(имя_таблицы;Лист1!E:F);2;ЛОЖЬ);НИМА ТАКОГО))))

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

Важный момент: после подгрузки данных мы их вырезаем (ctrl+x) и вставляем без привязки к формуле (ctrl+x+v). Формулу затем удаляем, иначе после каждого обновления страницы она будет пересчитывать все строки. В данном случае более 800.

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

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

5. Также используем форматирование ячеек. Оно может выполнить множество полезных преобразований. Например, покрасить ячейки, в которых значение больше 2 или меньше 1.

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

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

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

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

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

График популярности оружияГрафик популярности оружия

Жизнь после автоматизации

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

Теперь легче что-то поменять, перезалить или вообще обновить целый класс оружия, в котором 100+ позиций. Достаточно вбить новые параметры все автоматически и с учетом механик пересчитается под норму (а часть параметров пересчитаются сами под новые вводные).

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

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

P.S. Данный подход меняет сам принцип работы с различными инструментами. Например, в том же Slack можно видеть BB-коды наглядно с помощью простой команды #:

Полезные ссылки

Подробнее..

Мониторим парк ИБП. Ч.3, заключительная

16.06.2021 12:16:05 | Автор: admin

Или что пригодится знать и уметь, если замена ИБП после поломки урон профессиональной гордости.

Часть 1
Часть 2
TL;DR

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

Disclaimer
  1. Речь идёт в основном о ИБП мощностью 400-800VA, "линейно-интерактивных", со свинцовыми батареями 12В;

  2. Бесперебойное обеспечение в основном офисных "печатных машинок": ЦП мощностью до 100 Вт и SSD в качестве системных дисков, без дискретных видеокарт;

  3. Централизованный ИБП отсутствует.

Решаемые задачи:

  1. Минимум: иметь хотя бы общее представление о состоянии парка устройств;

  2. Хорошо: менять устройства ДО возникновения сбоя, обеспечивая тем самых их фактический и экономический смысл. Да-да, просто поставить ИБП совершенно недостаточно;

  3. Отлично: иметь наглядные данные что было сделано IT-службой и почему это было необходимо сделать (потратив на это деньги и человекочасы).

Об офисных UPS

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

1. Качество соединения
На основании двухлетнего наблюдения могу с уверенностью сказать, что это беда.
Если не следить за подключениями достаточно внимательно, большинство из бесперебойников поотваливаются в течение полугода. Такое поведение сильно зависит от конкретных условий и меняется от машины к машине. Основные причины потери соединения (насколько мне удалось разобраться):
а) чисто физические. Сюда входят ненадёжные разъёмы (и они случаются чаще, чем хотелось бы), случайные нарушения соединений (уборщица слишком ретива, пользователь дёргает ногами или двигает системник) и, внезапно, качество соединительного кабеля похоже, что ИБП довольно чувствительны к этому параметру, особенно при активном опросе;
б) на втором месте не "глючные драйверы", что удивительно, а электроника самих ИБП. Похоже, бесперебойникам из нижнего ценового сегмента не очень нравится, когда их часто "дёргают".

чуть подробнее

вообще, обмен данным с ИБП идёт постоянно, но всё же не раз в миллисекунду. Драйвер usbhid-ups получает данные раз в 2 секунды (видно в дебаг-режиме, если запускать руками с параметром -D+), что-то похожее, наверняка, и в стандартном драйвере Windows и WMI. Но это только "частичное" обновление, "полное" обновление происходит раз в 30 секунд

есть вероятность, что "полный" запрос сильно нагружает электронику ИПБ, и при частых запросах она начинает подглючивать. Это напрямую касается реализации сервера, о котором идёт речь в статьях. Чем это чревато и что с этим делать см. далее

2. Работа внутренних систем, сенсоров и логики UPS
Во-первых, ИБП от разных производителей обращаются с батареей по-разному. В моей практике хуже всех обращаются с батареями ИБП Powercom, лучше всех IPPON (далее по батарее см. п.3). Отличие не принципиальное, Powercom тоже через пол-года не дохнут, но оно есть и весьма заметно, если анализировать накопленные данные за достаточно длительный период. Здесь переходим к во-вторых: наиболее интересные параметры, которые ИБП сам считает и выдаёт:
а) нагрузка (load)
б) предсказание времени работы от батареи (runtime), вычисляемое на основе нагрузки
в) текущий заряд батареи (charge)

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

К сожалению, есть куда больше параметров, которые анализировать, считать и даже просто получать стоило бы, но это не реализовано.
Простейший пример: просадка напряжения на батарее, когда пропадает питание. Это показательнейший параметр, на основании которого можно куда точнее сделать вывод об убитой батарее. Постоянная память EEPROM есть сейчас где угодно, и ИБП запросто может записывать такие данные самостоятельно. Но ни одного ИБП с таким функционалом мне не попалось.
Другой пример: Powercom'ы после потери питания и разряда батареи до 30 процентов могут "зарядить" 12-вольтовую батарею за 10 минут и утверждать, что всё прекрасно, а Self-test passed.

если вас ничего не смущает

время полного заряда 12 В свинцовой батареи даже в "агрессивном" режиме составит не меньше часа, и батарея после такого долго не живёт

Это прямо натуральный epic fail.
Те же Powercom не умеют в вольтаж батареи вообще, только в проценты. На основании чего там эти проценты внутри считаются покрыто китайской мракой.

Очень важного параметра "температура" (батареи, внутри корпуса, да хоть чего-нибудь) не найти днём с огнём в офисных ИБП, хотя датчик копеечный и вообще встроен в почти все микроконтроллеры. Тут переходим к третьему пункту раздела.

3. Батарея и её состояние
Из того зоопарка, с которым я имел дело, "посредственно" справляется с задачей анализа состояния батареи серверный ИБП IPPON. Потуги остальных иначе как "бессмысленно" назвать не могу. Очень важный параметр батареи кривая разряда просто игнорируется.

немного о кривой разряда

Свинцовые батареи ИБП разряжаются нелинейно. Проще говоря, чем меньше заряда осталось в батарее, тем быстрее она разряжается. Ещё проще: время разряда со 100% до 90% будет в разы больше, чем с 20% до 0%. Но и это ещё не всё. Кривая разряда становится круче в более старой батарее и/или в зависимости от внешних условий (та же температура). В итоге это выглядит так:

Чувствуете подвох? Угадайте, проводит ли ИБП запись скорости последнего разряда, анализ, учитывает ли дату установки батареи?

спойлер

ИБП ваще пофигу, не анализирует, не учитывает. Лучшее, что я видел через три года начинает орать "чота батарея старая".

Вспомним, с чего мы начали этот раздел: передача данных с ИБП нужна для того, чтобы сохранить данные, пока ещё осталось время. Но ни ИБП, ни драйвер не знают, какое время является безопасным: 50% новой батареи совсем не то же самое, что 50% годовалой батареи. И единственное решение проблемы на данный момент: параметр нужно периодически модифицировать вручную.

Запись и расчёт параметров реализован в очередном обновлении сервера мониторинга. В частности, теперь видно:

  • просадку заряда батареи непосредственно после потери питания. Если этот показатель низкий, сервер выдаст предупреждение;

  • скорость разряда батареи. Этот параметр требует для расчёта не менее 2 мин наблюдаемого разряда ИБП.

Для корректного учёта этих параметров требуется довольно частый опрос ИБП. Соответствующие изменения внесены в код сервера (см. раздел "NUT и сервер мониторинга"). К сожалению, здесь мы упираемся в ранее озвученный п. "б" ч. 1 текущего раздела при частых обращениях за данными возможны глюки. Более того, в первые секунды после потери питания ИБП данные не отдаёт вообще, а вместо этого передаёт "WAIT". По-хорошему, после потери питания ИБП для целей мониторинга нужно бы опрашивать как можно чаще. На данный момент частота опроса от 10 до 30 секунд, в целом для более-менее приличного анализа этого хватает. Более интенсивные опросы не тестировались достаточно долго, чтобы делать какие-то однозначные выводы.

Краткие итоги раздела:

  • обязательно нужен мониторинг самого факта подключения ИБП к машине;

  • нужно собирать и хранить данные о датах установки батарей;

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

NUT и сервер мониторинга

Изначально NUT был выбран как кроссплатформенное универсальное решение. В целом функции свои выполняет. Без нюансов, впрочем, не обошлось:
а) Не слишком дружелюбная и неочевидная установка на клиентских машинах, под Windows особенно. В частности:

  • иногда отказывается видеть библиотеки;

  • встроенный драйвер не обновлялся давно и половину ИБП не знает. Драйвер надо ставить вручную, и это отдельная песня;

  • на клиентах под Windows служба стартует "невовремя", раньше, чем фактически нужно (похожая история есть при поиске домена на Windows с некоторыми сетевыми адаптерами). Из-за этого приходится городить костыли.

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

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

По результатам изысканий решено было оставить NUT в качестве основного решения для сервера. При этом "сильно больше данных" в какой-то момент обернулось базами, раздутыми по 100 Мб на бесперебойник, что повлияло на производительность. В итоге, сервер был перенесён из среды Windows в Linux, и:

  • написан соответствующий скрипт-демон на bash для непрерывного опроса;

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

В саму веб-морду сервера, как уже говорилось, добавлено:

  • отображение заряда батареи сразу после последней потери питания и соответствующий алерт;

  • расчёт времени работы от батареи на основе реальных данных. Некоторым образом это тоже синтетика, т.к. реальная кривая разряда не строится. Однако, отлично себя показала следующая формула: рассчитанное на основе последних реальных данных время / 1+ лет с десятыми.

Итоги, советы, планы

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

  1. Тщательно выберите марку и модель ИБП и по возможности используйте только выбранный ИБП во всем офисе;

  2. Запишите серийные номера и/или присвойте каждому ИБП уникальный номер. Запишите дату установки батареи в каждый ИБП. Меняйте батареи раз в два года, и сразу, заранее, закладывайте эти расходы в бюджеты для бизнеса запланированные расходы гораздо удобнее внезапных;

  3. Любым способом следите за тем, чтобы машины, обеспеченные ИБП, имели с ним постоянную связь;

  4. Раз в пол-года обновляйте параметры battery low и battery warning в используемом вами драйвере/решении, прибавляя к ним от 5% до 20% в зависимости от опыта использования вашей модели ИБП, вручную или скриптами;

  5. По возможности проводите ручное тестирование ИБП (отключить от розетки и подождать) раз в квартал-полгода.

Это то, что нужно делать обязательно.

Использование постоянного и подробного мониторинга, в целом, скорее чрезмерно, хотя и может оказаться полезным и наглядным. Заводить для этого отдельный сервер или нет, решать вам. Некоторые параметры можно мониторить в том же Zabbix (и даже посредством WMI), однако лично у меня в целом не очень удачный опыт с Zabbix active parameters; к тому же, сложность реализации некоторых описанных решений внутри Zabbix по сложности приближается, на мой вкус, к сложности реализации представленного сервера мониторинга.

Я удивлён и рад, что довольно много читателей задумалось о мониторинге ИБП после первой статьи, и многие посоветовали довести решение "до энтерпрайза" после второй статьи. Благодарю за отклики и ответы!

Учитывая накопленный опыт, реализация "до энтерпрайза" может быть довольно длительной. Есть проблемы и на клиентской стороне, и в самом NUT; в веб-интерфейсе многое надо выносить в бэкэнд, нет даже банальной авторизации. Можно было бы даже в таком виде запихнуть всё это в контейнер Docker в качестве версии 0.1 alpha, но мой энтузиазм к теме несколько поугас. Если у кого-то энтузиазм найдётся пишите, буду рад поработать вместе!

Рад, если мой опыт вам пригодится. Спасибо всем, кто прочитал!

Подробнее..

Внутренняя автоматизация почему мы отказались от low-code системы в пользу Camunda

17.06.2021 10:17:44 | Автор: admin

Привет! Меня зовут Мирослав, я инженер-разработчик проекта по реализации BPM-решений для внутренней автоматизации КРОК.

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

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

Теперь пользователь просто прописывает путь к папке, выбирает тип доступа (чтение/редактирование), оставляет при желании какой-нибудь увлекательный комментарий и все, дальше все делает BPMN.

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

Каким мы хотели видеть BPMS

Потребность в новой BPM-системе стала очевидной еще в 2018 году. Текущая K2 4.7 не шла в ногу со временем, а K2 5.0 не устроила нашу команду. В конце 2018 года менеджер нашего проекта в компании пары аналитиков начали изучать рынок BPM-решений в поисках альтернативы.

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

Нашей идеальной системе предстояло усилить взаимодействие команды с заказчиками и ускорить процесс разработки. Для этого отлично подходили iBPMS, в которых, при нашем сценарии использования, ведущая роль разработки отводилась аналитику и автогенерируемому интерфейсу. Именно аналитик должен был проектировать BPM-схему, собирать модель данных и создавать страницы в UI-дизайнере. Разработчики, в свою очередь, должны были насытить BPM-схему логикой при помощи скриптов, а также развернуть окружение и наладить workflow внедрения новых бизнес-процессов.

Спустя пару месяцев наш выбор пал на Bonita, а в феврале 2019 года мне поручили ее внедрение на нашем проекте.

Опыт использования Bonita

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

Архитектура Bonita BPMАрхитектура Bonita BPM

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

Bonita Studio

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

С чем столкнулись

  • Моделлер рисования схем сам по себе показался не очень удобным, так как достаточно криво отрисовывал XML. Редактирование схем и приведение их к единому стилю иногда доставляло боль.

Вот так выглядит BPMN-схема для процесса "Выдача прав доступа" в BonitaВот так выглядит BPMN-схема для процесса "Выдача прав доступа" в Bonita
  • Ограничение лицензии не позволяло нам открыть два окна Bonita Studio, а значит, два проекта одновременно, чтобы скопировать схемы или скрипты.

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

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

    Дальше мы старались писать фронтенд по старинке, при помощи Angular 2+, и налаживать взаимодействие с Bonita через REST API.

Bonita Portal

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

С чем столкнулись

  • Такое пространство удобное решение, если нет необходимости его дорабатывать. У Bonita есть возможность кастомизировать портал при помощи архивов стилей, накатываемых прямо в Web. Мы легко перекрасили оформление и даже поменяли язык всего портала на русский. Но когда дело дошло до каких-то детальных изменений, не предусмотренных BonitaSoft, мы столкнулись с проблемой для подобных доработок Bonita Portal не был приспособлен.

  • В Bonita Portal можно редактировать скрипты и параметры бизнес-процессов в runtime. Это достаточно мощное решение, с которым можно здесь и сейчас устранить проблемы пользователей, но в перспективе эта опция создает огромные проблемы и хаос в Production среде. И естественно с этими перспективами мы столкнулись лицом к лицу. Но это уже совсем другая история :D.

Bonita Runtime

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

Version Control

Это опция Bonita Studio, которая обеспечивает взаимодействие с Git-репозиториями. Не очень красивая, и на самом деле, совершенно необязательная, так как можно воспользоваться каким угодно иным инструментом для работы с Git. Но работает исправно :)

Bonita Storage

С чем столкнулись

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

  • После создания модель данных содержалась в xml-файле, который необходимо было заархивировать и развернуть в Bonita Portal.

XML-схема, которую генерит Bonita на основе business data model. Далее в соответствии с этой схемой будут созданы таблицы в БДXML-схема, которую генерит Bonita на основе business data model. Далее в соответствии с этой схемой будут созданы таблицы в БД

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

  • После деплоя архива с xml-схемой в BonitaPortal на ее основе создавались таблицы в заранее указанной базе данных. Выглядели они не совсем так, как нам хотелось. При этом в самой Bonita были ограничения по именованию объектов BDM. Все таблицы лежали в одной БД, хранить их иначе было невозможно. Для того, чтобы исключить пересечения именований, мы добавили префиксы, но в названиях таблиц (сущностей в модели данных) нельзя было использовать точки или нижние подчеркивания. В результате это были просто буквы, с которых начинались все названия сущностей.

    Еще были сложности с изменением модели данных. Добавление нового столбца или изменение Nullable могло обернуться вынужденным сносом всех данных из таблицы этого в проде мы допустить не могли.

Information systems

Пласт коннекторов, позволяющий Bonita взаимодействовать с внешними системами.Этот механизм облегчает жизнь разработчику можно подключиться к БД, SOAP, REST, отправлять письма. Пласт коннекторов, позволяющий Bonita взаимодействовать с внешними системами.Этот механизм облегчает жизнь разработчику можно подключиться к БД, SOAP, REST, отправлять письма.

С чем столкнулись

  • Основная проблема этого механизма - в его предопределенности. Здесь ситуация очень сильно похожа на Bonita Portal. В рамках задач, обозначенных BonitaSoft, коннекторы справлялись хорошо, но расширить их настройки или спектр применения было попросту невозможно. Да, мы могли создать самописные коннекторы, но тогда какой же это low-code? В итоге некоторые взаимодействия с внешними системами происходили через проставку из самописного сервиса.

Выводы

Bonita отлично подошла нам для создания простых бизнес-процессов: в некоторых случаях нам пригодился UI-дизайнер и даже генерация несложной модели данных. Но при создании нетривиальных, многомерных BPM-приложений такой подход начинал пробуксовывать, порождать костыли, а само приложение становилось неподъемным в плане поддержки и развития.

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

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

Поиск новой BPMS

В этот раз в выборе BPM системы участвовала большая часть проектной команды все те, кто был причастен к разработке процессов на Bonita. Пропустив через этот опыт решения из прошлого исследования, выбрали четырех финалистов Bizagi, ELMA, BpmOnline и Camunda.

Bizagi не устроила нас по цене, ELMA показалась слишком похожей на Bonita. BpmOnline мы смогли изучить подробнее, благодаря опыту наших коллег она уже используется в КРОК в качестве внутренней CRM-системы. В нашем случае она казалась не самым удачным вариантом могли быть трудности с поддержкой нетривиальных схем. У Camunda не было богатого набора инструментов iBPMS как у Bonita (но именно к ним у нас и было больше всего вопросов). Но зато у нее была Community-версия что стало дополнительным аргументом "за". Нам хотелось быстро и безболезненно протестировать решение, и быстро отмести его, если вдруг что-то опять пойдет не так. В общем-то, по этой причине мы выбрали ее качестве объекта для исследования и внедрения в тестовой среде.

Новая BPMS

Camunda - BPM Engine

Рассказывать о том, что такое Camunda можно долго, но на самом деле об этом уже написано достаточно статей. Вот, например, отличный материал от Tinkoff, позволяющий быстро и легко погрузиться в особенности этого BPM движка.

Добавлю лишь немного лайфхаков.

  • Ссылка на git-репозиторий с огромным количеством тестовых проектов Camunda на любой вкус, которые помогут быстрому старту и поиску решений проблем, возникших при самостоятельном знакомстве.

  • Ссылка на telegram-канал русскоязычного сообщества Camunda, куда можно обратиться за помощью, когда документация и Google не смогли подсказать решение. Эти ребята ни раз меня выручали в период знакомства с движком, за что им огромное спасибо :)

Наша основная проблема

Начав исследовать Camunda я столкнулся с очевидной проблемой. Camunda написана на Java, разворачивается в среде Java и расширяется при помощи Java-подобных языков. А вся наша команда пишет на .NET (надеюсь, у вас тоже проигралась заставка из Ералаша).

Вероятных решений было два:

  • Научиться писать на Java или Kotlin, но не растерять при этом навыков .NET, так как на поддержке у команды остается еще более двадцати бизнес-процессов

  • Интегрировать Camunda в нашу экосистему таким образом, чтобы рассинхрон стека как можно слабее повлиял на получаемый результат

Мы решили попробовать пойти по второму сценарию и вот что из этого получилось.

Новая концепция взаимодействия с BPM

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

Spring Boot

Java-часть нашей BPMS выглядит так:

Здесь мы используем Spring Boot, в который как зависимость импортируем Camunda.

У Camunda есть свой REST API и, конечно, BPMN-составляющая, а также своя БД, используемая для хранения процессных данных.BPM-движок через схему обращается к коду, написанному на Kotlin, который, в свою очередь, обращается к нашему .NET приложению для получения бизнес-данных.

.NET

Основная часть бизнес-процесса представляет из себя .NET приложение:

Бизнес-данные хранятся в БД, с помощью EF Core мы потребляем их в слой сервисов, где рассчитываем бизнес-логику и откуда обращаемся к Camunda REST API. Также у приложения есть формы на Angular для взаимодействия с пользователями и REST API для доступа к бизнес данным из Camunda и внешних систем.

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

Как теперь все устроено

За 8 месяцев мы исследовали и внедрили движок Camunda, а также мигрировали на него все бизнес-процессы, тем самым полностью отказавшись от Bonita BPM. Теперь у нас есть 3 отдельных Spring Boot приложения Camunda, под каждый бизнес-контур (Sales, HR и Production), самописный CROC BPM Portal, агрегирующий информацию о состоянии экземпляров процессов, и 4 бизнес-процесса, работающих в production-среде вот таких:


Выдача прав доступа

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

Обходной лист

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

Согласование тендера

Мы упростили коммуникацию по согласованию участия в тендере и исключили из этого процесса электронную почту или мессенджеры. Теперь наши менеджеры используют автоматизированное приложение с продуманным WorkFlow.

Пульс проекта

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


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

Bonita Portal => CROC BPM Portal

Интерфейс, который отвечает за отображение скоупа задач и экземпляров процессов CROC BPM Portal теперь тоже самописный.Благодаря этому мы можем оперативно отвечать на требования пользователей и гибко его развивать.

Task-модуль CROC BPM Portal на тестеTask-модуль CROC BPM Portal на тесте

Bonita Runtime => Spring Boot и Camunda

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

Bonita Storage => EF Core

Еще мы полностью контролируем бизнес-данные, для обновления БД используем миграции, а таблицы хранятся в удобном для нас виде:

Information systems => Самописные сервисы

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

Почему BPM это круто?

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

Центр любой BPMS это BPM-движок, и мы используем его по полной. Это удивительный инструмент, который действительно помогает ускорить разработку и повысить качество поставляемых решений. Фокус вовсе не в том, чтобы переложить ответственность на аналитика и автогенерацию. Основное преимущество в том, что BPMN это исполняемая документация.

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

Что дальше?

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

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

Подробнее..

Зачем нам p3express в наших IT-проектах

20.05.2021 10:09:10 | Автор: admin

Зачем нам p3express в наших IT-проектах

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

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

Конечно, у кого-то из заказчиков есть свои стандарты управления проектами, но заточены они под их основную деятельность (например, для строительства домов), и пытаться с их помощью руководить IT-проектом такое себе приключение. Поэтому методологию ведения их ИТ-проектов обычно выбираем мы. Выбор у нас всегда был большой есть прекрасные классические инструменты, которые охватывают всё, что только можно охватить, плюс подходят к этому с разных сторон и классно документированы:

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

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

Всё жестко зафиксировано с самого начала и ничего менять нельзя. То есть мы говорим: У нас есть сроки, деньги и то, что должно в итоге получиться. Шаг влево/вправо расстрел. А проект мы считаем успешным, только если достигли вот такой цели. Это классно и замечательно, но часто даже сам клиент не готов к тому, чтобы все его требования были жестко зафиксированы в самом начале и чтобы потом их нельзя было менять. Всё равно в процессе зайдет речь о каких-то изменениях и корректировках. А если при этом вы не договорились, что это повлечет за собой изменение бюджетов, сроков или объема работ будет больно.

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

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

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

Используя этот подход, мы постепенно пришли и к тому, что для взаимодействия на одном языке с клиентами нужна общая проектная методология. Обязательно с низким порогом входа чтобы даже никогда не управляя проектами или используя другую методологию, вы могли бы быстро прочитать описание и сказать: Да, классно, я всё понял, поехали! Но под эти требования ни Agile, ни другие взрослые методологии не подходили. Зато подошел фреймворк p3express.

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

У p3express есть бешеное преимущество: он простой. Это 37 шагов, разрисованных на картинке и быстро читаемых, при этом есть примеры и шаблоны. Прочитать их за 10 минут вполне реально. Хотя, конечно, без опыта не получится начать управлять проектом прямо сразу, здесь и сейчас. Но получится понять, допустим, меня, когда я буду описывать, чем мы завтра будем заниматься, и вообще куда и как мы двигаемся. То есть если мы договорились, что работаем по p3express, то всем в любой момент времени будет понятно, как мы будем жить и куда пойдем.

Второе его преимущество p3express сбалансирован по цене/срокам/скоупу.

И еще один кайф (впрочем, как у многих других стандартов) это фреймворк. Он не описывает жестко всё, что мы должны делать (то есть не ограничивает и не сковывает), а говорит: примерно такие вопросы и такие задачи нужно решить примерно таким способом.

Дальше я поэтапно рассмотрю шаги p3express и прокомментирую, как мы с этим живем.

Подготовка проекта

Начало в p3express вполне классическое:

  • Шаг 01. Определить спонсора;

  • Шаг 02. Подготовить резюме проекта;

  • Шаг 03. Определить руководителя проекта;

  • Шаг 04. Развернуть рабочее пространство;

  • Шаг 05. Определить команду;

  • Шаг 06. Планирование проекта;

  • Шаг 07. Определить внешних исполнителей (если в проекте они есть) ;

  • Шаг 08. Провести аудит подготовки;

  • Шаг 09. Да/нет;

  • Шаг 10. Провести стартовую встречу;

  • Шаг 11. Фокусированная коммуникация.

У нас же получилось всё сильно сложнее, чем 11 шагов.

Шаг 01. Определить спонсора продукта

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

По моему мнению, владельцем является тот, кто за него платит и кто получает от него основную выгоду. На практике же это часто разные люди. Почему владелец продукта нам, как менеджерам, важен с практической точки зрения? Потому что это тот этап, когда мы спрашиваем: Кто будет определять, мы сделали всё хорошо и правильно, или нет? Кто будет принимать проект? И часто бывает, что деньги платят одни, а решение о том, заплатить эти деньги или нет, принимают другие люди. Это абсолютно нормальная история (делегирование и пр.), но нам нужно понимать, кто выставляет основные требования.

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

Шаг 02. Подготовить резюме проекта

Для нас это самое сложное перед запуском проекта. Взрослые методологии, тот же самый ISO 21500, предполагают, что на вход в проект придет бизнес-план или ТЭО, которые полностью описывают, что мы делаем и зачем, как на этом планируем зарабатывать и т.д., чтобы можно было прочитать и сказать: А, понятно!, и по нему уже запускать проект. Но у нас так не получается.

Поэтому перед стартом проекта мы обычно делаем очень много консалтинга. Эти работы мы даже выносим в отдельный контракт с фиксированной стоимостью. Обычно это относительно небольшое время от 2 до 6 недель при том, что проекты бывают от 2 до 12-24 месяцев.

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

Со стартапами мы обычно используем Lean Canvas. Это большая таблица, в которой грубо раскидано что, зачем, кому мы делаем, как нам за это будут платить и почему нам это выгодно. Это больше маркетинговая история, но на самом деле именно она определяет, что мы будем делать. Это некая канва бизнес-модели, и именно с ней мы сверяемся, когда хотим что-то в проекте поменять или выбрать альтернативный путь. Мы смотрим нашему продукту это ценности прибавит или, наоборот, убавит?

Из Lean Canvas вырастает MVP: мы видим, каким должен быть минимальный продукт, чтобы проверить, а правильно ли мы вообще напридумывали скетчи и прототипы, концепт дизайна, техническое задание? Это как раз тот пакет документов, который в больших методологиях называется бизнес-план, ТЭО или еще как-то.

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

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

И когда мы приходим автоматизировать, то заказчику кажется, что нужно автоматизировать буквально всё. И чтобы разделить реальность и единорогов, мы всегда проводим стратегическую сессию. Она помогает понять, куда компания вообще идет, почему вдруг на этом пути появилась потребность что-то автоматизировать (то есть как-то улучшить процессы) и что это даст на уровне глобальных показателей. Обычно это почти не больно 3-4 часа общения с топами. Они почти никогда не кусаются, но нам всё равно страшно, и мы приходим всей командой.

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

Параметры проекта

В итоге для любого вида бизнеса мы создаем описание проекта по таким параметрам:

Это есть во всех стандартах управления проектами, которые я знаю. Но мы используем очень простую методику Scope-Pak. Придумали ее не мы по ссылке в QR коде можно посмотреть первоисточник:

Scope-Pak это 8 шагов по 5-7 вопросов каждый. На них лучше всего отвечать всем вместе то есть собираем нужных людей, задаем им вопросы и фиксируем ответы. На этом этапе желательно, чтобы команда заказчика еще не была сформирована, так как именно в процессе встречи с теми людьми, которые могут вам помочь, вы поймете, кого из них брать в команду.

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

Рассмотрим кратко эти 8 шагов:

Шаг 1. Заинтересованные стороны:

  • Чьи деньги участвуют в реализации проекта?

  • Этот человек является спонсором проекта?

  • Кто еще будет участвовать в проекте?

Шаг 2. Компоненты:

  • Что предстоит сделать?

  • Нужны какие-то вспомогательные работы?

  • Нужны подготовительные работы?

  • Завершающие работы?

  • Если мы сделаем все это, мы завершим весь проект?

Когда мы читали оригинал, мы очень смеялись над советом: ОГРАНИЧЬТЕ СПИСОК 30 ПУНКТАМИ то есть не расписывайте подробнее, чем на 30 пунктов, что надо сделать. А на практике мы столкнулись с тем, что только первые 6 рождаются просто в бешеных муках, а потом сложно остановиться.

Шаг 3. Цели и результаты:

  • Зачем мы все это делаем и как правильно это назвать?

  • Что мы получим на выходе и как это измерить?

  • Когда мы хотим это получить?

  • То, что мы запланировали сделать даст нам эти результаты?

Наверное, этим меня и зацепил Scope-Pak. Обычно делается все наоборот: сначала говорят о целях, а потом что мы будем делать ради этого. А когда мы сначала спрашиваем: Что мы делаем?, а потом: Зачем? появляется ступор, ведь у клиента в голове был только один план надо сделать это, это и это. Ответ помогает нам с заказчиком и командой выйти из ступора и перейти к пункту: А может, можно пойти другим путем?

Шаг 4. Альтернативы

  • Есть ли более эффективный способ достичь своих целей?

  • То, что мы запланировали сделать единственный способ получить нужный нам результат?

  • Как еще можно получить тот же результат?

  • Что можно сделать лучше?

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

Шаг 5. Ресурсы и сложности

  • Какова стратегия финансирования проекта?

  • Насколько он важен в сравнении с другими проектами?

  • Какие ресурсы вам потребуются?

  • С какими препятствиями вы столкнетесь?

  • Кто спонсирует этот проект?

  • Какие разрешения вы должны получить?

  • Кто будет выполнять задания проекта?

  • Каковы временные рамки?

  • Каковы критические даты?

  • Что может быть еще включено в проект?

  • Что можно исключить из проекта?

Забавно то, что когда-то дубль вопроса Кто спонсирует? попал сюда по ошибке из первого шага, но на практике получилось, что он здесь к месту. Потому что часто выясняется, когда мы наконец расписали, с кем что согласовывается спонсоры не совсем те. Поэтому мы оставили этот вопрос здесь, он классно помогает.

Шаг 6. План наступления

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

Шаг 7. Риски:

  • Какие предположения вы сделали?

  • Какие предположения вы должны сделать?

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

  • Как можно сократить риски или найти обходные пути?

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

Шаг 8. Ключевые индикаторы достижения цели:

  • Кто 3-4 самые главные заинтересованные участники проекта?

  • Какой результат их скорее всего удовлетворит?

  • Как измерить каждый из них по завершении проекта?

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

Например, заказчик хочет оптимизировать логистику. Мы выясняем, что сейчас эффективность использования его автотранспорта 20%, а клиент хочет достигнуть 70%. Мы считаем на коленке и понимаем, что весь его процесс так устроен, что больше 30% просто не получится. Потому что хоть клиент и ожидает, что машина будет 70% в пути, ей на самом деле нужно больше времени для приезда, разгрузки, погрузки, отъезда и прочих факторов. И если мы соглашаемся на 70%, не оговаривая всё это сразу, то под конец приходим к тому, что проделали отличную работу, всё здорово, всё классно, все довольны, а клиент говорит: Ребята, ну не 70 же! Как минимум, это неприятно, а может закончиться и контрактными проблемами.

Шаг 9. Да / Нет

Этот шаг описан и в p3express, во многих других методологиях. Для нас он о честности. Потому что когда предварительные работы проведены, мы даем заказчику полный и честный расклад по предстоящему проекту. Бывает иногда соблазн нарисовать более радужную картинку, но это нечестно и обязательно выйдет нам боком плохие новости всё равно всплывут. Честно это не украшать картинку и не обещать, что всё будет обалденно, шикарно и классно. Может и будет, но надо понимать, сколько это будет стоить это тот самый сбалансированный подход.

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

Поэтому я согласен с тем, что этап Да/Нет должен происходить как раз на этом шаге. Этим мне тоже очень понравился p3express, потому что это мало где применяется. По некоторым методологиям, люди посидели, подумали и ТЭО составили, а потом принесли на старт проект и сразу начали проектировать. А в жизни как раз получается наоборот. Когда назначен менеджер и решили да, наверное, будем делать проект, очень многое еще непонятно. И вот именно на этом этапе есть смысл сесть и подумать а вы уверены, что в это ввязываетесь? Да? Тогда поскакали, всё классно, и все друг друга хорошо понимают.

Планирование этапа

Тут у P3express всё по канонам:

  • Шаг 12. Обновить планы;

  • Шаг 13. Определить внешних исполнителей (если в проекте они есть);

  • Шаг 14. Да/нет;

  • Шаг 15. Провести стартовую встречу цикла;

  • Шаг 16. Фокусированная коммуникация.

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

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

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

Дополнительно на этапе планирования мы делаем Lean-приоритезацию она поможет в случае наступления рисков остаться в ценностных рамках. Лучше всего это делать вместе с будущим владельцем проекта и/или с лицами, принимающими решение. Схема очень простая: по двум шкалам (ценность/сложность) оцениваем по 10-балльной шкале все компоненты (фичи и прочее, что решили делать):

  • какую ценность нам это принесет?

  • что будет стоить по трудозатратам, по ресурсам, по времени, etc.?

Получаем табличку. Иногда бывает, что ценно всё то есть все наши компоненты получают десятки. Это нехорошо, и мы тогда пытаемся их оценить по 100-балльной шкале и тут уже все компоненты обычно делятся на 4 категории (очень редко бывает, что какой-то из квадратиков остается пустым).

Здесь я опять возвращаюсь к тому подходу, что обычно наша задача не попасть день в день, доллар в доллар и строчка в строчку, а определить и создать основную ценность. А для этого нам в процессе надо понимать, какие задачи и какие требования мы (в случае чего) можем выкинуть, но ценность сохранить. Lean-приоритезация нам дает для этого те самые приоритеты. Если мы знаем, что это стоит больше, а то меньше, то ради второго можно пожертвовать первым, но не наоборот.

Реализация этапа

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

Еженедельные действия p3express определяет так:

  • Шаг 17. Оценить прогресс;

  • Шаг 18. Работать с отклонениями;

  • Шаг 19. Еженедельная встреча;

  • Шаг 20. Провести еженедельный аудит;

  • Шаг 21. Фокусированная коммуникация.

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

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

Два показателя особенно классные: плановый объем (PV) и освоенный объем (EV). Они помогают нам наглядно увидеть: отстаем ли мы, идем с опережением или что-то другое происходит.

Как это работает? Ресурсы редко тратятся линейно, поэтому обычно мы не можем утверждать, что за половину сроков мы потратим половину денег. На самом деле это почти всегда не так. Например, есть задачи, где в начале идет что-то очень емкое (необязательно это айтишная история) какая-то концепция, дизайн-исследование что угодно. Мы это делаем в первую неделю, а потом три недели тестируем и согласовываем то есть в это время ценности уже не добавляется, а время это занимает. Или наоборот сначала долго, но дешево копим данные, а потом быстро и дорого их анализируем. В этом случае первые 3 недели мы ничего не тратим, никакой ценности не приносим, а в конце тратим много ресурсов (денег), но получаем много ценности.

Из-за этой неровности невозможно сопоставить сроки и объем. Вроде бы прошла половина срока, и мы должны получить половину планируемого результата но мы получили 63% или 48%. Возникает вопрос: Это хорошо или плохо?. А по методике соответствия планового/освоенного мы заранее расписываем: мы будем тратить ресурсы именно так, и именно так получать ценность. А потом легко сравнить, получились ли наши запланированные 63% и 48%.

Ежедневные действия

На этом этапе p3express в основном предлагает работать с рисками:

  • Шаг 22. Зафиксировать RICs;

  • Шаг 23. Реагировать на RICs;

  • Шаг 24. Принять готовые продукты.

Мы их дополнили частью из SCRUMа, введя ежедневные митинги для обсуждения:

  • Что сделал вчера;

  • Что планируешь сегодня;

  • Что может помешать.

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

Закрытие этапа

Вот так это в p3express:

  • Шаг 25. Оценить удовлетворённость заказчика и команды;

  • Шаг 26. Запланировать улучшения;

  • Шаг 27. Фокусированная коммуникация.

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

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

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

Закрытие проекта

По p3express закрытие включает такие шаги:

  • Шаг 28. Получить одобрение и передать продукт

  • Шаг 29. Передать проект

  • Шаг 30. Оценить удовлетворенность заказчика и команды

  • Шаг 31. Провести аудит закрытия

  • Шаг 32. Извлечь уроки и заархивировать проект

  • Шаг 33. Объявить и отметить окончание проекта

  • Шаг 34. Фокусированная коммуникация

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

Сессия Скажи спасибо

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

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

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

После проекта

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

  • Шаг 35. Оценить полученные выгоды

  • Шаг 36. Спланировать дополнительные действия

  • Шаг 37. Фокусированная коммуникация

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

Такие регулярные сессии особенно важны, если проект длится годами. У нас был проект, который начался с лендинга, длился три с половиной года и в итоге перерос в огромную онлайн-академию с кучей народу, всякими бонусами, баллами и прочими фишками. За это время в проекте поменялось всё. И если бы мы не дробили его на маленькие кусочки и не проводили регулярные стратегические и проблемные сессии, то нам было бы сложно оставаться в рамках стратегии, планов и нормальных человеческих отношений.


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

Подробнее..

Умный особняк

04.06.2021 10:04:57 | Автор: admin

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

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

Какие задачи стояли

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

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

  2. Обеспечить единое управление всем и вся с графического интерфейса

  3. Построить слаботочные и ИТ системы. В частности WiFi, который работает везде :)

Как решали климат и его автоматика

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

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

Для доведения температуры до желаемой в отдельном помещении установлены радиаторы - для обогрева. И холодные потолки - для охлаждения без резких потоков воздуха. Плюс - тёплые полы - дабы было комфортно ходить босиком.

Система управления ("умный дом", или, если угодно - "умный особняк") в этом проекте была разделена на бэкенд (классическая система автоматизации/диспетчеризации на контроллерах Sauter) и фронт-енд (графический пользовательский интерфейс на базе ориентированных под это контроллеров Crestron; плюс сюда же задачи по управлению мультимедией). Общаются бэкенд и фроненд по сети по протоколу Bacnet/IP.

Более 600 датчиков (температуры, влажности, давления воздуха, движения); порядка 650 устройств, подключенных к выводам контроллеров (реле, приводов, клапанов, ...). Около 1600 переменных в SCADA системе. 53 контроллера автоматики и парочка контроллеров умного дома.

Немножко труб - тепловой пункт (БТП) живьём.Немножко труб - тепловой пункт (БТП) живьём.

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

скучные технические подробности :)

В составе системы автоматики были развёрнуты:

1. Комнатная автоматика, которая осуществляет:

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

- поддержание необходимой температуры теплого пола;

- контроль температуры, влажности воздуха;

- регулирование воздухообмена в комнатах, где предусмотрено курение;

- управление освещением, в том числе с димированием,

- управление шторами.

2. Автоматизация системы вентиляции, предусматривающая управление и контроль состояния:

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

- вытяжных вентиляторов.

3. Автоматизация системы отопления предусматривает управление и контроль состояния:

- котельной;

- ИТП дома и ИТП СПА.

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

- диспетчеризацию двух чиллеров;

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

5. Для управления и контроля состояния инженерного оборудования было предусмотрено:

- развёрнута Web-SCADA система;

- установлен диспетчерский компьютер;

- установлена панель в помещении охраны для отображения критичных аварий котельной и других систем;

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

И БТП в SCADA системе.И БТП в SCADA системе.

Умный дом

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

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

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

Также управление освещением продублировано с настенных выключателей. Особое внимание было уделено отказоустойчивости системы включение/выключение света будет возможно даже при выходе из строя контроллеров автоматики.

Дополнительной вишенкой на торте является система позиционирования, позволяющая в автоматическом режиме определять, в каком помещение находиться хозяин дома с главным, Master iPadом и выводить на экран планшета соответствующую страницу меню управления. С точки зрения техники, данный функционал реализован на Bluetooth BLE маячках (BLE PinPoint beacons), установленных по дому. При появления iPad в поле их действия, софт на iPadе сигнализирует о событии центральному контроллеру и получает команды на определенные действия. Никакие датчики в пользователя не встраиваются ;)

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

ИТ системы

Для обеспечения доступа хозяев дома в интернет, а также для работы системы управления во всём комплексе зданий развёрнута единая сеть WiFi, позволяющая перемещаться по помещениям, сохраняя соединение. В том числе не прерывая разговора по голосовым мессенджерам. Учитывая сложную радиообстановку (толстые стены, крайне высокое затухание сигнала), сеть создана на базе аппаратного контроллера WiFi и нескольких десятков WiFi точек доступа. Штукатурная сетка, появившаяся после радиопланирования и радиообследования объекта - добавила сложностей. Но в итоге всё кончилось хорошо - нас не расстреляли :)

Также на объекте создана проводная сеть Ethernet, обеспечивающая подсоединение всего оборудования: контроллеров автоматики, точек доступа WiFi, аудио-видео оборудования, камер видеонаблюдения. Для увеличения надёжности система разбита на сегменты VLAN.

Система фонового озвучивания обеспечивает простое воспроизведение музыки как с устройств Apple (через беспроводное Wi-Fi подключение, с использованием технологии AirPlay), так и воспроизведение интернет радио, CD, MP3 в зонах бассейна, SPA и веранды.

Что можно было бы сделать лучше.

По итогам эксплуатации считаю, что надо было настоять на использовании специализированных тач-панелей от производителя системы управления. Используемые в качестве сенсорных пультов управления выделенные iPad'ы в долговременной перспективе требуют к себе больше внимания, чем хотелось бы. То, несмотря на все настройки, обновляться соберутся, то ещё что-нибудь придумают :)

И да, замечания к дизайну SCADA, полученные в комментариях к прошлой статье, мы приняли с благодарностью. Но этот проект был реализован немножко раньше предыдущей статьи и учесть в нём замечания не представлялось возможным ;)

Заключение

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

Автор Кирилл Осовский, komrus@yandex.ru

Подробнее..

Кейс аналитика системы освещения в логистическом центре

17.06.2021 20:21:19 | Автор: admin

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

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

Что имеем? Установлен силовой шкаф управления освещением на 45 линий: складские ряды, коридоры, мезонин. На каждой линии установлен автомат защиты и модульный контактор. Помимо силового щита есть щит управления с ручным и автоматическим управлением. Автоматическое управление подразумевает включение и выключение линии по датчику движения, которые установлены в рядах, где двигается техника. Некоторые линии находятся в ручной режиме и постоянно включены, а некоторые, наоборот выключены. Задачу нам поставили следующую: отслеживать работу каждой линии, отслеживать залипание контакторов, оповещать о неисправностях и получать аналитику по наработке. Можно было бы полностью заменить шкафы, сделать дистанционное управление, расписание и много чего еще, но это было бы очень сильное удорожание проекта, а нас попросили именно вписаться в небольшой бюджет не меняя того, что и так работает стабильно.

Задача поставлена, начинаем реализацию. Есть несколько способов отследить работу линии освещения. Самый простой способ это использовать свободный или дополнительный контакт модульного контактора. Но конкретно в нашем случае эта схема не сработает, так как питание катушки контактора идет от общего автомата и возможна ситуация, когда на линии освещения контактор будет замкнут, а автомат выключен и питания на линии не будет. Этот способ будет работать только если питание катушки контактора идет из под автомата этого же контактора. Можно было бы поставить дополнительные контакты положения и сработки к автоматам, это даст более полную картину, но в нашем случае места в шкафу не было от слова совсем и раздвинуть автоматы для дополнительных контактов просто не было возможности. Решили пойти самым надежным путем, взять непосредственно фазу, идущую на питание линии и завести ее на контроллер. Так как у нас большая часть автоматики на объекте сделана на отечественных Контарах, то мы просто взяли модули расширения, которые воспринимают 220 вольт на входе. Это не частая опция у контроллеров, поэтому, если бы стоял другой производитель, пришлось бы ставить 45 промежуточных реле. Получился аккуратный и не большой шкаф диспетчеризации, мы повесили его рядом с силовым шкафом и обвязали все аккуратно многожильным кабелем. Расключение шкафа и отключение света согласовали заранее, а на все работы ушло около 1,5 часов.

Подключаем сигнальные линииПодключаем сигнальные линии

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

Силовой шкаф до расключенияСиловой шкаф до расключения

Дальше разрабатываем интерфейс пользователя.

Основное окно секции освещения Основное окно секции освещения

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

На этом экране можно получить оперативную информацию о состоянии освещения. Есть отдельные окна трендов и настроек.

Окно с настройкамиОкно с настройками

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

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

Временные трендыВременные тренды

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

Отчет за выбранное времяОтчет за выбранное время

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

Отчет за выбранное времяОтчет за выбранное время

Вторая страница отчета выводит ту же информацию, но в виде диаграмм.

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

Подробнее..

Перевод Как протестировать блокноты Jupyter с помощью pytest и nbmake

20.05.2021 16:23:11 | Автор: admin

Файлы блокнотов Jupyter, в смысле количества одного из самых быстрорастущих типов файлов на Github, предоставляют простой интерфейс для итераций при решении визуальных задач, будь то анализ наборов данных или написание документов с большим объёмом кода. Однако популярность блокнотов Jupyter сопровождается проблемами: в репозитории накапливается большое количество файлов ipynb, многие из которых находятся в нерабочем состоянии. В результате другим людям трудно повторно запустить или даже понять ваши блокноты. В этом руководстве рассказывается, как для автоматизации сквозного (end-to-end) тестирования блокнотов можно воспользоваться плагином pytest nbmake.

К старту флагманского курса о Data Science области, в которой блокноты Jupyter незаменимы делимся переводом статьи из блога CI Semaphore о том, как при помощи Semaphore проверить, что ваши блокноты Jupyter находятся в рабочем состоянии и для этого больше не запускать блокноты вручную.


Предварительные требования

Это руководство предполагает владение основными навыками тестирования проектов на Python, о них рассказывается в статьеНепрерывная интеграция и развёртывание на Python с нуля. Прежде чем продолжить, пожалуйста, убедитесь, что вы знакомы с основами и на вашем компьютере установлен набор инструментов Python 3, таких как pip + virtualenv.

Демонстрационное приложение

Обычно проекты Python содержат папки с файлами блокнотов с расширением .ipynb, это может быть:

  • код доказательства концепции;

  • документация с примерами применения API;

  • длинный научный туториал.

В этой статье мы разберём, как автоматизировать простые сквозные тесты некоторых блокнотов, содержащих упражнения на Python. Благодарим pytudes за предоставленные для нашего примера материалы; воспользуемся этим примером проекта, не стесняйтесь делать форк и клонировать его с GitHub:

fig:fig:

Внутри репозитория вы найдёте содержащую блокноты папку, она называется ipynb. Установите зависимости из requirements.txt, а при необходимости сначала создайте виртуальную среду.

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

Локальное тестирование блокнота

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

Давайте начнём автоматизировать этот процесс в вашей среде разработки; первым шагом в автоматизации станет nbmake. Nbmake это пакет python и плагин к pytest. Он разработан автором этого руководства и используется известными научными организациями, такими как Dask, Quansight и Kitware. Вы можете установить nbmake с помощью pip:

pip install nbmake==0.5

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

 pytest --collect-only --nbmake "./ipynb" ================================ test session starts =================================platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: /Users/a/git/alex-treebeard/semaphore-demo-python-jupyter-notebooksplugins: nbmake-0.5collected 3 items           ============================= 3 tests collected in 0.01s =============================

Мы видим, что pytest собрал несколько элементов.

Примечание: если вы получаете сообщение unrecognized arguments: --nbmake, оно означает, что плагин nbmake не установлен. Такое может произойти, если ваш CLI вызывает двоичный файл pytest за пределами текущей виртуальной среды. Проверьте, где находится ваш двоичный файл pytest, с помощью команды which pytest.

Теперь, когда мы проверили, что nbmake и pytest видят ваши блокноты и работают вместе, давайте выполним настоящие тесты:

 pytest --nbmake "./ipynb"================================ test session starts =================================platform darwin -- Python 3.8.2, pytest-6.2.4, py-1.10.0, pluggy-0.13.1rootdir: /Users/a/git/alex-treebeard/semaphore-demo-python-jupyter-notebooksplugins: nbmake-0.5collected 3 items                                                                    ipynb/Boggle.ipynb .                                                           [ 33%]ipynb/Cheryl-and-Eve.ipynb .                                                   [ 66%]ipynb/Differentiation.ipynb .                                                  [100%]================================= 3 passed in 37.65s =================================

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

Игнорирование ожидаемых ошибок в блокноте

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

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

  • откройте файл .ipynb в простом текстовом редакторе, например Sublime;

  • в метаданных блокнота найдите поле kernelspec;

  • добавьте объект execution в качестве родственного элемента kernelspec.

Должно получиться что-то вроде этого:

{  "cells": [ ... ],  "metadata": {    "kernelspec": { ... },    "execution": {      "allow_errors": true,      "timeout": 300    }  }}

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

Ускорение тестов с помощью pytest-xdist

В крупных проектах может быть много блокнотов, каждый из которых требует много времени на установку пакетов, извлечение данных из сети и выполнение анализа. Если ваши тесты занимают более нескольких секунд, стоит проверить, как на время выполнения повлияет распараллеливание. Сделать это можно с помощью другого плагина pytest pytest-xdist. Сначала установите пакет xdist. Это похожий на nbmake плагин pytest, он добавит новые параметры командной строки:

pip install pytest-xdist

Запустите команду ниже с количеством рабочих процессов, равным auto:

pytest --nbmake -n=auto "./ipynb"

Запись выполненных блокнотов обратно в репозиторий

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

  • отладить неработающие блокноты путём просмотра выходных данных;

  • создать коммит в вашем репозитории с блокнотами в воспроизводимом состоянии;

  • встроить выполненные блокноты в сайт с документацией при помощи nbsphinx или jupyter book.

Мы можем направить nbmake на сохранение выполненных блокнотов на диск с помощью флага overwrite:

pytest --nbmake --overwrite "./ipynb"

Исключение блокнотов из тестов

Некоторые блокноты может быть трудно протестировать автоматически из-за необходимости ввода данных через stdin самим пользователем или длительного времени запуска. Чтобы отменить выбор, воспользуйтесь флагом игнорирования pytest:

pytest --nbmake docs --overwrite --ignore=docs/landing-page.ipynb

Примечание: это не сработает, если вы выбираете все блокноты с помощью шаблона поиска, например (*ipynb), вручную переопределяющего флаги игнорирования pytest.

Автоматизация тестирования блокнотов на Semaphore CI

С помощью вышеописанных методов можно создать автоматизированный процесс тестирования с использованием непрерывной интеграции (CI) Semaphore. Ключ здесь прагматизм: нужно найти способ протестировать большую часть ваших блокнотов и проигнорировать сложные блокноты; это хороший шаг к улучшению качества.

Начните с создания содержащего ваши блокноты проекта для репозитория. Выберите Choose repository:

Затем подключите Semaphore к репозиторию с вашими блокнотами:

Пока пропустите Add People, чтобы мы могли настроить рабочий процесс с нуля (даже если вы работаете с деморепозиторием). Semaphore предоставляет некоторую начальную конфигурацию, настроим её перед запуском:

Создайте простой рабочий процесс в один блок; ниже о процессе в деталях:

  1. Названием блока будет Test.

  2. Названием задачи Test Notebooks.

  3. В поле Commands введите команды ниже:

checkoutcache restorepip install -r requirements.txtpip install nbmake pytest-xdistcache storepytest --nbmake -n=auto ./ipynb

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

  1. checkout клонирует репозиторий с GitHub;

  2. cache restore загружает зависимости Python из кэша Semaphore. Это ускоряет процесс установки;

  3. pip устанавливает зависимости и инструменты;

  4. cache store сохраняет загруженные зависимости обратно в кэш;

  5. pytest запускает тесты nbmake.

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

Конвейер должен заработать сразу же:

Устранение некоторых распространённых ошибок

Добавление отсутствующего ядра Jupyter в среду CI

Если вы используете имя ядра, отличное от имени по умолчанию python3, то при выполнении ноутбуков в свежей среде CI увидите сообщение об ошибке: Error - No such kernel: 'mycustomkernel'. Чтобы установить пользовательское ядро, воспользуйтесь командой ipykernel:

python -m ipykernel install --user --name mycustomkernel

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

Добавление недостающих секретов в среду CI

В некоторых случаях ваш блокнот получает данные от API, требующих токена или аутентифицированного CLI. Всё, что работает в вашей среде разработки, должно работать и на Semaphore. Чтобы увидеть, как настроить секреты, сначала посмотрите этот пост. Как только вы установили секреты в Semaphore, чтобы прочитать их из переменной среды в средах CI, вам может потребоваться настроить блокнот.

Добавление недостающих зависимостей в среду CI

Стек Data Science на Python часто требует установки собственных библиотек. Если вы работаете с CONDA, вполне вероятно, что они будут присутствовать в вашем нормальном процессе установки. Если же вы используете стандартные библиотеки Python, их присутствие немного менее вероятно.

Если вы пытаетесь установить необходимые вам библиотеки, посмотрите на статью о создании образа docker в CI. С ним тестирование упрощается, и по сравнению со стандартными средами Semaphore этот подход стабильнее во времени.

Пожалуйста, помните совет о прагматизме; 90 % полезности часто можно достичь за 10 % времени. Следование совету может привести к компромиссам, таким как игнорирование блокнотов, на которых запускаются требующие GPU модели ML.

Заключение

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

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

  • для удаления громоздких выходных данных блокнота перед коммитом запустите pre-commit и nbstripout;

  • для компиляции ваших блокнотов в красивый сайт с документацией используйте jupyter book;

  • для просмотра блокнотов в пул-реквестах используйте ReviewNB.

Действительно, процессы в разработке ПО и в научных исследованих всё дальше уходят с локальных компьютеров на арендуемые мощности самого разного рода, в том числе потому, что требуют всё больших ресурсов. То же касается и растущих объёмов данных, работа с которыми преобразилась в несколько смежных, но очень разных областей; среди них центральное место занимает Data Science. Если вам интересна эта сфера, вы можете присмотреться к нашему курсу Data Science, итог которого это 16 проектов, то есть 2-3 года постоянного самостоятельного изучения науки о данных.

Узнайте, как прокачаться и в других специальностях или освоить их с нуля:

Другие профессии и курсы
Подробнее..

Как обновить все сцены Unity-проекта в один клик

30.05.2021 12:21:38 | Автор: admin
Танюшка - автор канала IT DIVA и данной статьи, кофеголик и любитель автоматизировать рутинуТанюшка - автор канала IT DIVA и данной статьи, кофеголик и любитель автоматизировать рутину

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

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

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

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

А далее вы уже сможете использовать и модифицировать приведённый в примере код под свои нужды.

Зачем нужен такой инструмент

Представьте, что у вас есть 3 уровня в вашем проекте. Вы добавили новые скрипты, изменили интерфейс, обновили используемые 3D-модели. Но только в рамках одной сцены.

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

Но что будет, когда их станет 10? 20? 50?

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

Как эту проблему решить?

На самом деле, довольно просто!

Можно поступить несколькими способами. Например, использовать метод OnValidate() в классах, которые уже присутствуют на сцене. Но для этого нужно запустить каждую сцену и сохранить её вручную. Более того, не весь функционал изменения сцен нам будет доступен через OnValidate(), поскольку данный метод есть только у объектов, наследованных от MonoBehaviour.

Нам такой вариант не подходит. Но знать о нём тоже полезно.

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

Чтобы это сделать, мы создадим новый класс в папке Editor:

Пример возможной иерархии для расширений движкаПример возможной иерархии для расширений движка

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

Далее добавим необходимые пространства имён, а также укажем, что наследоваться будем от Editor Window (а не от MonoBehaviour, как происходит по умолчанию):

using UnityEngine;using UnityEditor;public class SceneUpdater : EditorWindow{    [MenuItem("Custom Tools/Scene Updater")]    public static void ShowWindow()    {        GetWindow(typeof(SceneUpdater));    }        private void OnGUI()    {        if (GUILayout.Button("Update scenes"))    Debug.Log("Updating")    }}

С помощью атрибута [MenuItem("Custom Tools/Scene Updater")] мы создадим элемент меню с заданной иерархией в самом движке. Таким образом мы будем вызывать диалоговое окно будущего инструмента:

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

Далее мы добавим кнопку, с помощью которой будем запускать наш дальнейший код:

using UnityEngine;using UnityEditor;public class SceneUpdater : EditorWindow{    [MenuItem("Custom Tools/Scene Updater")]    public static void ShowWindow()    {        GetWindow(typeof(SceneUpdater));    }        private void OnGUI()    {        if (GUILayout.Button("Update scenes"))    Debug.Log("Updating")    }}

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

Быстрое добавление компонентов к объектам

Для добавления компонентов к объектам с уникальными именами можно написать вот такую функцию:

/// <summary>/// Добавление компонента к объекту с уникальным названием/// </summary>/// <param name="objectName"> название объекта </param>/// <typeparam name="T"> тип компонента </typeparam>private void AddComponentToObject<T>(string objectName) where T : Component{    GameObject.Find(objectName)?.gameObject.AddComponent<T>();}

Использовать её можно вот так:

AddComponentToObject<BoxCollider>("Plane");AddComponentToObject<SampleClass>("EventSystem");

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

Быстрое удаление объектов по имени

Аналогично можно сделать и для удаления объектов:

/// <summary>/// Уничтожение объекта с уникальным названием/// </summary>/// <param name="objectName"> название объекта </param>private void DestroyObjectWithName(string objectName){    DestroyImmediate(GameObject.Find(objectName)?.gameObject);}

И использовать так:

DestroyObjectWithName("Sphere");

Перенос позиции, поворота и размера между объектами

Для компонентов Transform и RectTransform можно создать функции, с помощью которых будет происходить копирование локальной позиции, поворота и размера объекта (например, если нужно заменить старый объект новым или изменить настройки интерфейса):

/// <summary>/// Копирование позиции, поворота и размера с компонента Transform у одного объекта/// на такой же компонент другого объекта./// Для корректного переноса координат у parent root объеков должны быть нулевые координаты/// </summary>/// <param name="objectToCopyFrom"> объект, с которого копируются части компонента </param>/// <param name="objectToPasteTo"> объект, на который вставляются части компонента </param>/// <param name="copyPosition"> по умолчанию позиция копируется, с помощью данного параметра это можно отключить </param>/// <param name="copeRotation"> по умолчанию поворот копируется, с помощью данного параметра это можно отключить </param>/// <param name="copyScale"> по умолчанию размер копируется, с помощью данного параметра это можно отключить </param>private static void CopyTransformPositionRotationScale(GameObject objectToCopyFrom, GameObject objectToPasteTo,     bool copyPosition = true, bool copeRotation = true, bool copyScale = true){    var newTransform = objectToCopyFrom.GetComponent<Transform>();    var currentTransform = objectToPasteTo.GetComponent<Transform>();            if (copyPosition) currentTransform.localPosition = newTransform.localPosition;    if (copeRotation) currentTransform.localRotation = newTransform.localRotation;    if (copyScale) currentTransform.localScale = newTransform.localScale;}    /// <summary>/// Копирование позиции, поворота и размера с компонента RectTransform у UI-панели одного объекта/// на такой же компонент другого объекта. Не копируется размер самой панели (для этого использовать sizeDelta)/// Для корректного переноса координат у parent root объеков должны быть нулевые координаты/// </summary>/// <param name="objectToCopyFrom"> объект, с которого копируются части компонента </param>/// <param name="objectToPasteTo"> объект, на который вставляются части компонента </param>/// <param name="copyPosition"> по умолчанию позиция копируется, с помощью данного параметра это можно отключить </param>/// <param name="copeRotation"> по умолчанию поворот копируется, с помощью данного параметра это можно отключить </param>/// <param name="copyScale"> по умолчанию размер копируется, с помощью данного параметра это можно отключить </param>private static void CopyRectTransformPositionRotationScale(GameObject objectToCopyFrom, GameObject objectToPasteTo,    bool copyPosition = true, bool copeRotation = true, bool copyScale = true){    var newTransform = objectToCopyFrom.GetComponent<RectTransform>();    var currentTransform = objectToPasteTo.GetComponent<RectTransform>();            if (copyPosition) currentTransform.localPosition = newTransform.localPosition;    if (copeRotation) currentTransform.localRotation = newTransform.localRotation;    if (copyScale) currentTransform.localScale = newTransform.localScale;}

Причём, благодаря тому, что есть переменные-условия, мы сможем контролировать, какие параметры мы хотим скопировать:

var plane = GameObject.Find("Plane");var cube = GameObject.Find("Cube");CopyTransformPositionRotationScale(plane, cube, copyScale:false);

Изменение UI-компонентов

Для работы с интерфейсом могут быть полезны функции, позволяющие быстро настроить Canvas, TextMeshPro и RectTransform:

/// <summary>/// Изменение отображения Canvas/// </summary>/// <param name="canvasGameObject"> объект, в компонентам которого будет производиться обращение </param>/// <param name="renderMode"> способ отображения </param>/// <param name="scaleMode"> способ изменения масштаба </param>private void ChangeCanvasSettings(GameObject canvasGameObject, RenderMode renderMode, CanvasScaler.ScaleMode scaleMode){    canvasGameObject.GetComponentInChildren<Canvas>().renderMode = renderMode;    var canvasScaler = canvasGameObject.GetComponentInChildren<CanvasScaler>();    canvasScaler.uiScaleMode = scaleMode;    // выставление стандартного разрешения    if (scaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize)    {        canvasScaler.referenceResolution = new Vector2(720f, 1280f);        canvasScaler.matchWidthOrHeight = 1f;    }} /// <summary>/// Изменение настроек для TextMeshPro/// </summary>/// <param name="textMeshPro"> тестовый элемент </param>/// <param name="fontSizeMin"> минимальный размер шрифта </param>/// <param name="fontSizeMax"> максимальный размер шрифта </param>/// <param name="textAlignmentOption"> выравнивание текста </param>private void ChangeTMPSettings(TextMeshProUGUI textMeshPro, int fontSizeMin, int fontSizeMax, TextAlignmentOptions textAlignmentOption = TextAlignmentOptions.Center){    // замена стандартного шрифта    textMeshPro.font = (TMP_FontAsset) AssetDatabase.LoadAssetAtPath("Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Fallback.asset", typeof(TMP_FontAsset));    textMeshPro.enableAutoSizing = true;    textMeshPro.fontSizeMin = fontSizeMin;    textMeshPro.fontSizeMax = fontSizeMax;    textMeshPro.alignment = textAlignmentOption;}/// <summary>/// Изменение параметров RectTransform/// </summary>/// <param name="rectTransform"> изменяемый элемент </param>/// <param name="alignment"> выравнивание </param>/// <param name="position"> позиция в 3D-пространстве </param>/// <param name="size"> размер </param>private void ChangeRectTransformSettings(RectTransform rectTransform, AnchorPresets alignment, Vector3 position, Vector2 size){    rectTransform.anchoredPosition3D = position;    rectTransform.sizeDelta = size;    rectTransform.SetAnchor(alignment);}

Замечу, что для RectTransform я использую расширение самого класса, найденное когда-то давно на форумах по Unity. С его помощью очень удобно настраивать Anchor и Pivot. Такие расширения рекомендуется складывать в папку Utils:

Пример возможной иерархии для расширений стандартных классов Пример возможной иерархии для расширений стандартных классов

Код данного расширения оставляю для вас в спойлере:

RectTransformExtension.cs
using UnityEngine;public enum AnchorPresets{    TopLeft,    TopCenter,    TopRight,    MiddleLeft,    MiddleCenter,    MiddleRight,    BottomLeft,    BottomCenter,    BottomRight,    VertStretchLeft,    VertStretchRight,    VertStretchCenter,    HorStretchTop,    HorStretchMiddle,    HorStretchBottom,    StretchAll}public enum PivotPresets{    TopLeft,    TopCenter,    TopRight,    MiddleLeft,    MiddleCenter,    MiddleRight,    BottomLeft,    BottomCenter,    BottomRight,}/// <summary>/// Расширение возможностей работы с RectTransform/// </summary>public static class RectTransformExtension{    /// <summary>    /// Изменение якоря    /// </summary>    /// <param name="source"> компонент, свойства которого требуется изменить </param>    /// <param name="align"> способ выравнивания </param>    /// <param name="offsetX"> смещение по оси X </param>    /// <param name="offsetY"> смещение по оси Y </param>    public static void SetAnchor(this RectTransform source, AnchorPresets align, int offsetX = 0, int offsetY = 0)    {        source.anchoredPosition = new Vector3(offsetX, offsetY, 0);        switch (align)        {            case (AnchorPresets.TopLeft):            {                source.anchorMin = new Vector2(0, 1);                source.anchorMax = new Vector2(0, 1);                break;            }            case (AnchorPresets.TopCenter):            {                source.anchorMin = new Vector2(0.5f, 1);                source.anchorMax = new Vector2(0.5f, 1);                break;            }            case (AnchorPresets.TopRight):            {                source.anchorMin = new Vector2(1, 1);                source.anchorMax = new Vector2(1, 1);                break;            }            case (AnchorPresets.MiddleLeft):            {                source.anchorMin = new Vector2(0, 0.5f);                source.anchorMax = new Vector2(0, 0.5f);                break;            }            case (AnchorPresets.MiddleCenter):            {                source.anchorMin = new Vector2(0.5f, 0.5f);                source.anchorMax = new Vector2(0.5f, 0.5f);                break;            }            case (AnchorPresets.MiddleRight):            {                source.anchorMin = new Vector2(1, 0.5f);                source.anchorMax = new Vector2(1, 0.5f);                break;            }            case (AnchorPresets.BottomLeft):            {                source.anchorMin = new Vector2(0, 0);                source.anchorMax = new Vector2(0, 0);                break;            }            case (AnchorPresets.BottomCenter):            {                source.anchorMin = new Vector2(0.5f, 0);                source.anchorMax = new Vector2(0.5f, 0);                break;            }            case (AnchorPresets.BottomRight):            {                source.anchorMin = new Vector2(1, 0);                source.anchorMax = new Vector2(1, 0);                break;            }            case (AnchorPresets.HorStretchTop):            {                source.anchorMin = new Vector2(0, 1);                source.anchorMax = new Vector2(1, 1);                break;            }            case (AnchorPresets.HorStretchMiddle):            {                source.anchorMin = new Vector2(0, 0.5f);                source.anchorMax = new Vector2(1, 0.5f);                break;            }            case (AnchorPresets.HorStretchBottom):            {                source.anchorMin = new Vector2(0, 0);                source.anchorMax = new Vector2(1, 0);                break;            }            case (AnchorPresets.VertStretchLeft):            {                source.anchorMin = new Vector2(0, 0);                source.anchorMax = new Vector2(0, 1);                break;            }            case (AnchorPresets.VertStretchCenter):            {                source.anchorMin = new Vector2(0.5f, 0);                source.anchorMax = new Vector2(0.5f, 1);                break;            }            case (AnchorPresets.VertStretchRight):            {                source.anchorMin = new Vector2(1, 0);                source.anchorMax = new Vector2(1, 1);                break;            }            case (AnchorPresets.StretchAll):            {                source.anchorMin = new Vector2(0, 0);                source.anchorMax = new Vector2(1, 1);                break;            }        }    }    /// <summary>    /// Изменение pivot    /// </summary>    /// <param name="source"> компонент, свойства которого требуется изменить </param>    /// <param name="preset"> способ выравнивания </param>    public static void SetPivot(this RectTransform source, PivotPresets preset)    {        switch (preset)        {            case (PivotPresets.TopLeft):            {                source.pivot = new Vector2(0, 1);                break;            }            case (PivotPresets.TopCenter):            {                source.pivot = new Vector2(0.5f, 1);                break;            }            case (PivotPresets.TopRight):            {                source.pivot = new Vector2(1, 1);                break;            }            case (PivotPresets.MiddleLeft):            {                source.pivot = new Vector2(0, 0.5f);                break;            }            case (PivotPresets.MiddleCenter):            {                source.pivot = new Vector2(0.5f, 0.5f);                break;            }            case (PivotPresets.MiddleRight):            {                source.pivot = new Vector2(1, 0.5f);                break;            }            case (PivotPresets.BottomLeft):            {                source.pivot = new Vector2(0, 0);                break;            }            case (PivotPresets.BottomCenter):            {                source.pivot = new Vector2(0.5f, 0);                break;            }            case (PivotPresets.BottomRight):            {                source.pivot = new Vector2(1, 0);                break;            }        }    }}

Использовать данные функции можно так:

// изменение настроек отображения Canvasvar canvas = GameObject.Find("Canvas");ChangeCanvasSettings(canvas, RenderMode.ScreenSpaceOverlay, CanvasScaler.ScaleMode.ScaleWithScreenSize);// изменение настроек шрифтаvar tmp = canvas.GetComponentInChildren<TextMeshProUGUI>();ChangeTMPSettings(tmp, 36, 72, TextAlignmentOptions.BottomRight);// изменение RectTransformChangeRectTransformSettings(tmp.GetComponent<RectTransform>(), AnchorPresets.MiddleCenter, Vector3.zero, new Vector2(100f, 20f));

Аналогично, может пригодиться расширение для класса Transform для поиска дочернего элемента (при наличии сложной иерархии):

TransformExtension.cs
using UnityEngine;/// <summary>/// Расширение возможностей работы с Transform/// </summary>public static class TransformExtension{    /// <summary>    /// Рекурсивный поиск дочернего элемента с определённым именем    /// </summary>    /// <param name="parent"> родительский элемент </param>    /// <param name="childName"> название искомого дочернего элемента </param>    /// <returns> null - если элемент не найден,    ///           Transform элемента, если элемент найден    /// </returns>    public static Transform FindChildWithName(this Transform parent, string childName)    {        foreach (Transform child in parent)        {            if (child.name == childName)                return child;            var result = child.FindChildWithName(childName);            if (result)                return result;        }        return null;    }}

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

/// <summary>/// Добавление обработчика события на кнопку (чтобы было видно в инспекторе)/// </summary>/// <param name="uiButton"> кнопка </param>/// <param name="action"> требуемое действие </param>private static void AddPersistentListenerToButton(Button uiButton, UnityAction action){    try    {        // сработает, если уже есть пустое событие        if (uiButton.onClick.GetPersistentTarget(0) == null)            UnityEventTools.RegisterPersistentListener(uiButton.onClick, 0, action);    }    catch (ArgumentException)    {        UnityEventTools.AddPersistentListener(uiButton.onClick, action);    }}

То есть, если написать следующее:

// добавление события на кнопкуAddPersistentListenerToButton(canvas.GetComponentInChildren<Button>(), FindObjectOfType<SampleClass>().QuitApp);

То результат работы в движке будет таким:

Результат работы AddPersistentListenerРезультат работы AddPersistentListener

Добавление новых объектов и изменение иерархии на сцене

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

/// <summary>/// Изменение слоя объекта по названию слоя/// </summary>/// <param name="gameObject"> объект </param>/// <param name="layerName"> название слоя </param>private void ChangeObjectLayer(GameObject gameObject, string layerName){    gameObject.layer = LayerMask.NameToLayer(layerName);}/// <summary>/// Добавление префаба на сцену с возможностью определения родительского элемента и порядка в иерархии/// </summary>/// <param name="prefabPath"> путь к префабу </param>/// <param name="parentGameObject"> родительский объект </param>/// <param name="hierarchyIndex"> порядок в иерархии родительского элемента </param>private void InstantiateNewGameObject(string prefabPath, GameObject parentGameObject, int hierarchyIndex = 0){    if (parentGameObject)    {        var newGameObject = Instantiate((GameObject) AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)), parentGameObject.transform);                    // изменение порядка в иерархии сцены внутри родительского элемента        newGameObject.transform.SetSiblingIndex(hierarchyIndex);    }    else        Instantiate((GameObject) AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)));}

Таким образом, при выполнении следующего кода:

// изменение тэга и слоя объектаvar cube = GameObject.Find("Cube");cube.tag = "Player";ChangeObjectLayer(cube, "MainLayer");               // создание нового объекта на сцене и добавление его в иерархию к существующемуInstantiateNewGameObject("Assets/Prefabs/Capsule.prefab", cube, 1);

Элемент встанет не в конец иерархии, а на заданное место:

Цикл обновления сцен

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

/// <summary>/// Запускает цикл обновления сцен в Build Settings/// </summary>/// <param name="onSceneLoaded"> действие при открытии сцены </param>private void RunSceneUpdateCycle(UnityAction onSceneLoaded){    // получение путей к сценам для дальнейшего открытия    var scenes = EditorBuildSettings.scenes.Select(scene => scene.path).ToList();    foreach (var scene in scenes)    {        // открытие сцены        EditorSceneManager.OpenScene(scene);                    // пометка для сохранения, что на сцене были произведены изменения        EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());                    // проведение изменений        onSceneLoaded?.Invoke();                    // сохранение        EditorApplication.SaveScene();                    Debug.Log($"UPDATED {scene}");    }}

А теперь соединим всё вместе, чтобы запускать цикл обновления сцен по клику на кнопку:

Полный код SceneUpdater.cs
#if UNITY_EDITORusing System;using UnityEditor.Events;using TMPro;using UnityEngine.UI;using System.Collections.Generic;using UnityEngine.SceneManagement;using UnityEditor;using UnityEditor.SceneManagement;using System.Linq;using UnityEngine;using UnityEngine.Events;/// <summary>/// Класс для обновления сцен, включённых в список BuildSettings (активные и неактивные)/// </summary>public class SceneUpdater : EditorWindow{    [MenuItem("Custom Tools/Scene Updater")]    public static void ShowWindow()    {        GetWindow(typeof(SceneUpdater));    }    private void OnGUI()    {        // пример использования        if (GUILayout.Button("Update scenes"))            RunSceneUpdateCycle((() =>            {                // изменение тэга и слоя объекта                var cube = GameObject.Find("Cube");                cube.tag = "Player";                ChangeObjectLayer(cube, "MainLayer");                                // добавление компонента к объекту с уникальным названием                AddComponentToObject<BoxCollider>("Plane");                                // удаление объекта с уникальным названием                DestroyObjectWithName("Sphere");                                // создание нового объекта на сцене и добавление его в иерархию к существующему                InstantiateNewGameObject("Assets/Prefabs/Capsule.prefab", cube, 1);                // изменение настроек отображения Canvas                var canvas = GameObject.Find("Canvas");                ChangeCanvasSettings(canvas, RenderMode.ScreenSpaceOverlay, CanvasScaler.ScaleMode.ScaleWithScreenSize);                // изменение настроек шрифта                var tmp = canvas.GetComponentInChildren<TextMeshProUGUI>();                ChangeTMPSettings(tmp, 36, 72, TextAlignmentOptions.BottomRight);                // изменение RectTransform                ChangeRectTransformSettings(tmp.GetComponent<RectTransform>(), AnchorPresets.MiddleCenter, Vector3.zero, new Vector2(100f, 20f));                                // добавление события на кнопку                AddPersistentListenerToButton(canvas.GetComponentInChildren<Button>(), FindObjectOfType<SampleClass>().QuitApp);                // копирование настроек компонента                CopyTransformPositionRotationScale(GameObject.Find("Plane"), cube, copyScale:false);            }));    }    /// <summary>    /// Запускает цикл обновления сцен в Build Settings    /// </summary>    /// <param name="onSceneLoaded"> действие при открытии сцены </param>    private void RunSceneUpdateCycle(UnityAction onSceneLoaded)    {        // получение путей к сценам для дальнейшего открытия        var scenes = EditorBuildSettings.scenes.Select(scene => scene.path).ToList();        foreach (var scene in scenes)        {            // открытие сцены            EditorSceneManager.OpenScene(scene);                        // пометка для сохранения, что на сцене были произведены изменения            EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());                        // проведение изменений            onSceneLoaded?.Invoke();                        // сохранение            EditorApplication.SaveScene();                        Debug.Log($"UPDATED {scene}");        }    }    /// <summary>    /// Добавление обработчика события на кнопку (чтобы было видно в инспекторе)    /// </summary>    /// <param name="uiButton"> кнопка </param>    /// <param name="action"> требуемое действие </param>    private static void AddPersistentListenerToButton(Button uiButton, UnityAction action)    {        try        {            // сработает, если уже есть пустое событие            if (uiButton.onClick.GetPersistentTarget(0) == null)                UnityEventTools.RegisterPersistentListener(uiButton.onClick, 0, action);        }        catch (ArgumentException)        {            UnityEventTools.AddPersistentListener(uiButton.onClick, action);        }    }    /// <summary>    /// Изменение параметров RectTransform    /// </summary>    /// <param name="rectTransform"> изменяемый элемент </param>    /// <param name="alignment"> выравнивание </param>    /// <param name="position"> позиция в 3D-пространстве </param>    /// <param name="size"> размер </param>    private void ChangeRectTransformSettings(RectTransform rectTransform, AnchorPresets alignment, Vector3 position, Vector2 size)    {        rectTransform.anchoredPosition3D = position;        rectTransform.sizeDelta = size;        rectTransform.SetAnchor(alignment);    }    /// <summary>    /// Изменение настроек для TextMeshPro    /// </summary>    /// <param name="textMeshPro"> тестовый элемент </param>    /// <param name="fontSizeMin"> минимальный размер шрифта </param>    /// <param name="fontSizeMax"> максимальный размер шрифта </param>    /// <param name="textAlignmentOption"> выравнивание текста </param>    private void ChangeTMPSettings(TextMeshProUGUI textMeshPro, int fontSizeMin, int fontSizeMax, TextAlignmentOptions textAlignmentOption = TextAlignmentOptions.Center)    {        // замена стандартного шрифта        textMeshPro.font = (TMP_FontAsset) AssetDatabase.LoadAssetAtPath("Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF - Fallback.asset", typeof(TMP_FontAsset));        textMeshPro.enableAutoSizing = true;        textMeshPro.fontSizeMin = fontSizeMin;        textMeshPro.fontSizeMax = fontSizeMax;        textMeshPro.alignment = textAlignmentOption;    }    /// <summary>    /// Изменение отображения Canvas    /// </summary>    /// <param name="canvasGameObject"> объект, в компонентам которого будет производиться обращение </param>    /// <param name="renderMode"> способ отображения </param>    /// <param name="scaleMode"> способ изменения масштаба </param>    private void ChangeCanvasSettings(GameObject canvasGameObject, RenderMode renderMode, CanvasScaler.ScaleMode scaleMode)    {        canvasGameObject.GetComponentInChildren<Canvas>().renderMode = renderMode;        var canvasScaler = canvasGameObject.GetComponentInChildren<CanvasScaler>();        canvasScaler.uiScaleMode = scaleMode;        // выставление стандартного разрешения        if (scaleMode == CanvasScaler.ScaleMode.ScaleWithScreenSize)        {            canvasScaler.referenceResolution = new Vector2(720f, 1280f);            canvasScaler.matchWidthOrHeight = 1f;        }    }         /// <summary>    /// Получение всех верхних дочерних элементов    /// </summary>    /// <param name="parentGameObject"> родительский элемент </param>    /// <returns> список дочерних элементов </returns>    private static List<GameObject> GetAllChildren(GameObject parentGameObject)    {        var children = new List<GameObject>();                for (int i = 0; i< parentGameObject.transform.childCount; i++)            children.Add(parentGameObject.transform.GetChild(i).gameObject);                return children;    }    /// <summary>    /// Копирование позиции, поворота и размера с компонента Transform у одного объекта    /// на такой же компонент другого объекта.    /// Для корректного переноса координат у parent root объеков должны быть нулевые координаты    /// </summary>    /// <param name="objectToCopyFrom"> объект, с которого копируются части компонента </param>    /// <param name="objectToPasteTo"> объект, на который вставляются части компонента </param>    /// <param name="copyPosition"> по умолчанию позиция копируется, с помощью данного параметра это можно отключить </param>    /// <param name="copyRotation"> по умолчанию поворот копируется, с помощью данного параметра это можно отключить </param>    /// <param name="copyScale"> по умолчанию размер копируется, с помощью данного параметра это можно отключить </param>    private static void CopyTransformPositionRotationScale(GameObject objectToCopyFrom, GameObject objectToPasteTo,         bool copyPosition = true, bool copyRotation = true, bool copyScale = true)    {        var newTransform = objectToCopyFrom.GetComponent<Transform>();        var currentTransform = objectToPasteTo.GetComponent<Transform>();                if (copyPosition) currentTransform.localPosition = newTransform.localPosition;        if (copyRotation) currentTransform.localRotation = newTransform.localRotation;        if (copyScale) currentTransform.localScale = newTransform.localScale;    }        /// <summary>    /// Копирование позиции, поворота и размера с компонента RectTransform у UI-панели одного объекта    /// на такой же компонент другого объекта. Не копируется размер самой панели (для этого использовать sizeDelta)    /// Для корректного переноса координат у parent root объеков должны быть нулевые координаты    /// </summary>    /// <param name="objectToCopyFrom"> объект, с которого копируются части компонента </param>    /// <param name="objectToPasteTo"> объект, на который вставляются части компонента </param>    /// <param name="copyPosition"> по умолчанию позиция копируется, с помощью данного параметра это можно отключить </param>    /// <param name="copyRotation"> по умолчанию поворот копируется, с помощью данного параметра это можно отключить </param>    /// <param name="copyScale"> по умолчанию размер копируется, с помощью данного параметра это можно отключить </param>    private static void CopyRectTransformPositionRotationScale(GameObject objectToCopyFrom, GameObject objectToPasteTo,        bool copyPosition = true, bool copyRotation = true, bool copyScale = true)    {        var newTransform = objectToCopyFrom.GetComponent<RectTransform>();        var currentTransform = objectToPasteTo.GetComponent<RectTransform>();                if (copyPosition) currentTransform.localPosition = newTransform.localPosition;        if (copyRotation) currentTransform.localRotation = newTransform.localRotation;        if (copyScale) currentTransform.localScale = newTransform.localScale;    }    /// <summary>    /// Уничтожение объекта с уникальным названием    /// </summary>    /// <param name="objectName"> название объекта </param>    private void DestroyObjectWithName(string objectName)    {        DestroyImmediate(GameObject.Find(objectName)?.gameObject);    }    /// <summary>    /// Добавление компонента к объекту с уникальным названием    /// </summary>    /// <param name="objectName"> название объекта </param>    /// <typeparam name="T"> тип компонента </typeparam>    private void AddComponentToObject<T>(string objectName) where T : Component    {        GameObject.Find(objectName)?.gameObject.AddComponent<T>();    }    /// <summary>    /// Изменение слоя объекта по названию слоя    /// </summary>    /// <param name="gameObject"> объект </param>    /// <param name="layerName"> название слоя </param>    private void ChangeObjectLayer(GameObject gameObject, string layerName)    {        gameObject.layer = LayerMask.NameToLayer(layerName);    }    /// <summary>    /// Добавление префаба на сцену с возможностью определения родительского элемента и порядка в иерархии    /// </summary>    /// <param name="prefabPath"> путь к префабу </param>    /// <param name="parentGameObject"> родительский объект </param>    /// <param name="hierarchyIndex"> порядок в иерархии родительского элемента </param>    private void InstantiateNewGameObject(string prefabPath, GameObject parentGameObject, int hierarchyIndex = 0)    {        if (parentGameObject)        {            var newGameObject = Instantiate((GameObject) AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)), parentGameObject.transform);                        // изменение порядка в иерархии сцены внутри родительского элемента            newGameObject.transform.SetSiblingIndex(hierarchyIndex);        }        else            Instantiate((GameObject) AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)));    }}#endif

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

Волшебная кнопкаВолшебная кнопка

Заключение

Имея в своём распоряжении такой инструмент, вы сможете делать всё, что вам угодно за считанные клики: сериализовать поля, менять иерархию на сценах, настраивать Fuse/IClone/DAZ и других персонажей, а также менять Build Pipeline, но об этом как-нибудь в другой раз.

Главное, не забывайте использовать систему контроля версий и проверять запуск ваших модификаций сперва на одной сцене (т.е. без использования RunSceneUpdateCycle).

Запустить тестовый проект и получить полный код можно на моём GitHub.

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

Спасибо за внимание и до новых встреч!

Подробнее..

Автоматизация машинного обучения

18.06.2021 14:19:30 | Автор: admin

Datascience это не только fit-predict

Представим, что вы начали работать в компании, которая производит однообразные операции с бесконечными таблицами. Например, в крупном ретейлере или у ведущего оператора связи. Ежедневно перед вами ставят задачу выяснить, останется ли клиент с вами или хватит ли товара на полках до конца недели. Алгоритм выглядит просто. Вы берете выборку, изучаете бесконечные ряды признаков, удаляете мусор, генерируете новые признаки, собираете сводную таблицу. Подаете готовые данные в модель, настраиваете параметры и с нетерпением ждете заветных цифр итоговой метрики. Это повторяется день за днем. Затрачивая каждый день всего 60 минут на генерацию фич или подбор параметров, за месяц вы израсходуете минимум 20 часов. Это, без малого, целые сутки, за которые можно выполнить новую задачу, обучить нейросеть или прочесть несколько статей на arxive.

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

TL:DR

В статье мы рассмотрим применение трех решений оптимизации рабочего процесса: генератор признаков featuretools, подборщик гиперпараметров optuna и коробочное решение автоматического машинного обучения от H2O AutoML.

Сравним работу новых инструментов против классических pandas и sklearn. Не будем глубоко закапываться в код. Приведем основные моменты и оставим анализ полного функционала в качестве домашнего задания. Статья будет полезна DS-специалистам и аналитикам. Требуется понимание основных шагов процесса машинного обучения.

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

Нужно больше данных

Начнем с генерации признаков. Зачем вообще нужны новые фичи в машинном обучении? Они агрегируют данные, дают новую информацию для анализа и улучшают качество работы моделей. Например, методы groupby() и sum() это рядовые представители сгенерированных признаков. Классический подход инжиниринга данных изучить датасет вручную и написать код для каждого нового признака. Такой метод практичен, но отнимает много времени и велика вероятность ошибки. Особенно, когда однообразного кода становится много.

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

Инструмент предлагает два направления генерации фич:feature primitives(fp, англ. примитивные признаки) иdeep feature synthesis(dfs, англ. глубокий синтез признаков).Первый метод производит простые математические операции между определенными фичами. Второй, более сложный, позволяет соединять несколько примитивных операций над признаками. Каждая новая операция увеличивает глубину (depth) сложности алгоритма. Например, мы можем посчитать сначала все средние значения по определенному пользователю, а затем сразу суммировать их.

Переходим к практическому применению. Установка и импорт.

pip install featuretoolsimport featuretools as ft

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

ft.primitives.list_primitives()

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

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

es = ft.EntitySet(id=data)                                                                           # Новый пустой EntitySetes.entity_from_dataframe(entity_id = january,                                    # Добавляем в него информацию      dataframe=df_train.drop(target, axis=1),      index=1)feature_matrix, feature_defs = ft.dfs(entityset=es,                                # Запускаем генерацию признаков          target_entity=january,          trans_primitives=[add_number, multiply_numeric],          verbose=1)

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

Всю мощь featuretools раскрывает при обработке нескольких таблиц. Подробный туториал с примерами кода есть наофициальном сайте. Рекомендую к ежедневному использованию.

Искусство легких настроек

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

Самые известные инструменты поиска гиперпараметров из sklearn GridSearchCVиRandomizedSearchCV. GridSearchCV перебирает все возможные комбинации параметров на кросс-валидации. RandomizedSearchCV сначала создает словарь с несколькими случайно выбранными параметрами из всех переданных значений. Затем алгоритм тестирует отобранные значения на моделях и выбирает лучшие. Первый метод является крайне ресурсозатратным. Второй быстрее, но мало применим к сложным ансамблям градиентного бустинга. Для обучения серьезных моделей классические алгоритмы не подходят.

Бытует мнение, что истинные профи не запускают подбор, а с первого раза вбивают нужные параметры, просто смотря на входные данные. Для рядовых специалистов есть несколько коробочных решений: optuna, hyperopt, scikit-optimization.

Разберем применения самого быстрого и самого молодого из них optuna.

Кроме настройки классических моделей sklearn и ансамблей бустинга (xgboost, lightgbm, catboost), фреймворк позволяет настраивать нейросети, написанные на pytorch, tensorflow, chainer и mxnet. В отличие от классических sklearn методов, optuna, вместе с другими новыми фреймворками, может обрабатывать непрерывные значения гиперпараметров. Например, альфа- или лямбда-регуляризации будут принимать любые значения с плавающей точкой в заданном диапазоне. Все это делает его одним из самых гибких инструментов настройки моделей глубокого обучения.

В своей работе optuna использует байесовские алгоритмы подбора с возможностью удаления заведомо проигрышного пространства заданных гиперпараметров из анализа. Рассмотрим практическое применение фреймворка. Как библиотека устроена под капотом на английском языке можно прочесть наarxiv.orgНа русском языке есть подробная статья наХабре. В ней вы найдете математическое описание методов оптимизации практически на пальцах.

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

# установка (при необходимости)pip install optuna# импорт библиотеки и средств визуализацииimport optunafrom optuna.visualization import plot_optimization_history, plot_param_importances

Посмотрим на работу модели без подбора гиперпараметров. Базовая метрика RMSE на приватном списке победителей = 0.73562. Запустим optuna и посмотрим на улучшение метрики за 5 итераций. Имеющиеся данные достаточно простые и мы не ждем глобального скачка качества после подбора гиперпараметров. Нам интересен сам процесс и скорость его работы.

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

reg_alpha: trial.suggest_loguniform(reg_alpha, 1e-3, 10.0)  # передаем нижнюю и верхнюю границы непрерывного параметраnum_leaves: trial.suggest_int(num_leaves, 1, 300)# передаем нижнее и верхнее значение числового параметраmax_depth: trial.suggest_categorical(max_depth, [-1,10,20]) # передаем список определенных значений категориального признака, которые надо проверить

Сам процесс обучения прописывается в две строки:

study = optuna.create_study(direction=minimize)  # минимизируем ошибкуstudy.optimize(objective, n_trials=5)       # objective  задание для поиска, 5  количество      # итераций оптимизации  

В приложенном к статьеkaggleноутбуке рассматривается базовый вариант запуска optuna. За 5 итераций удалось улучшить метрику на отложенной выборке на 0.1 пункта метрики RMSE. Процесс длился 5 минут. Интересно, сколько по времени работал бы традиционный GridSearchCV с данным количеством параметров. Метрика на финальном сабмите с оптимизацией = 0.7221, что лучше ручной настройки модели на сырых данных.

MachineLearningза 7 строк кода

Такой заголовок подошел бы для кликбейтовой рекламы в интернете. Но мы действительно создадим полный пайплайн обучения за минимальное количество блоков кода. Поможет нам в этомh2оот Amazon. Это библиотека содержит в себе все популярные модули обработки данных и машинного обучения. Разберем модуль автоматического машинного обучения automl. Фреймворк позволяет подбирать модель и параметры без участия специалиста.

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

pip install f https://h2o-release.s3.amazonaws.com/h2o/latest_stable_Py.html h2o from h2o.automl import H2OAutoML# установим максимальный размер используемой оперативной памятиh2o.init(max_mem_size=16G)

Pandas больше не нужен! Можно загружать данные в рабочий ноутбук с помощью AutoML.

train = h20.import_file(../input/tabular-playground-series-jan-21/train.csv)test = h20.import_file(../input/tabular-playground-series-jan-21/test.csv)

Выберем признаки и таргет.

x = test.columns[1:]y = target

Запускаем машинное обучение. Библиотека будет последовательно обучать встроенные модели с различными гиперпараметрами. В нашем примере настроим количество моделей, random_state=47 и максимальное время обучения одной модели = 3100 секунд (3600 секунд по умолчанию)

aml = H20AutoML(max_models=2,  # Количество различных моделей для обучения                                 seed=SEED,                                  max_runtime_secs = 3100)   # Максимальное время обучения одной моделиaml.train(x=x, y=y, training_frame=train)# Ждем, когда заполнится строка обучения AutoML

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

preds = aml.predict(test) df_sub[target] = preds.as_data_frame().values_flatten() df_sub.to_csv(h2o_submission_5.csv, index=False)

Финальная метрика AutoMl на приватном списке победителей = 0.71487. Напомню, что метрика среднеквадратичной ошибки (root mean squared error, RMSE) отражает среднее отклонение предсказаний от реального значения. Простыми словами она должна быть минимальной и стремиться к нулю. RMSE у победителя соревнования = 0.69381. В пределах рутинных задач с которыми можно столкнуться в ежедневной работе разница так же невелика. Но время AutoML экономит значительно.

Заключение

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

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

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

Подробнее..

Powershell настоящий язык программирования. Скрипт оптимизации рутины в техподдержке

20.06.2021 14:08:21 | Автор: admin

Работая в компании IT-аутсорса в качестве руководителя 3 линии поддержки, задумался, как автоматизировать подключение сотрудников по RDP, через VPN к серверам десятков клиентов.

Таблички с адресами, паролями и прочими настройками серверов, конечно, хорошо, но поиск клиента и вбивание адресов с аккаунтами занимает довольно существенное время.
Держать все подключения к VPN в Windows не самая лучшая идея, да и при переустановке оного, создавать VPNы тоже не доставляет удовольствие.
Плюс к тому, в большинстве случаев, требуется установить VPN подключение к клиенту без использования шлюза. дабы не гонять весь интернет-трафик через клиента.
Задача, к тому же, осложняется тем, что у некоторых клиентов pptp, у кого-то l2tp, у некоторых несколько подсетей, туннели и т.п.

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

До написания этого скрипта-приложения программированием не занимался вообще, разве что лет 20 назад что-то пописывал на VBS в MS Excel и MS Access, поэтому не гарантирую красивость кода и принимаю критику от опытных программистов, как можно было бы сделать красивее.

В Powershell, начиная с Windows 8 и, конечно в Windows 10, появилась прекрасная возможность создавать VPN подключения командой Add-VpnConnection и указывать какие маршруты использовать с этими соединениями командой Add-VpnConnectionRoute, для использования VPN без шлюза.

На основании этих команд и создано данное приложение. Но, обо всем по порядку.

Для начала, создаем в Google Disk таблицу с именованными столбцами:
Number; Name; VPNname; ServerAddress; RemoteNetwork; VPNLogin; VPNPass; VPNType; l2tpPsk; RDPcomp; RDPuser; RDPpass; DefaultGateway; PortWinbox; WinboxLogin; WinboxPwd; Link; Inform

  • VPNname произвольное имя для VPN соединения

  • ServerAddress адрес VPN сервера

  • RemoteNetwork адреса подсети или подсетей клиента, разделенные ;

  • VPNLogin; VPNPass учетная запись VPN

  • VPNType -тип VPN (пока используется pptp или l2tp)

  • l2tpPsk PSK для l2tp, в случае pptp оставляем пустым

  • RDPcomp адрес сервера RPD

  • RDPuser; RDPpass учетная запись RPD

  • DefaultGateway принимает значение TRUE или FALSE и указывает на то, использовать ли Шлюз по умолчанию для этого соединения. В 90% случаев = FALSE

  • PortWinbox; WinboxLogin; WinboxPwd порт, логин и пароль для Winbox, поскольку у нас большинство клиентов использует Mikrotik)

  • Link ссылка на расширенную информацию о компании, например, на диске Google, или в любом другом месте, будет выводиться в информационном поле для быстрого доступа к нужной информации

Inform примечание

Пример таблицы доступен по ссылке

Number

Name

VPNname

ServerAddress

RemoteNetwork

VPNLogin

VPNPass

VPNType

l2tpPsk

RDPcomp

RDPuser

RDPpass

DefaultGateway

PortWinbox

WinboxLogin

WinboxPwd

Link

Inform

1

Тест1

Test1

a.b.c.d

192.168.10.0/24: 10.10.0.0/24

vpnuser

passWord

pptp

none

192.168.10.1

user

passWord

TRUE

8291

Admin

Admin

http://yandex.ru

тест

2

Тест2

Test2

e.f.j.k

192.168.2.0/24

vpnuser

passWord

l2tp

KdoSDtdP

192.168.2.1

user

passWord

FALSE

8291

Admin

Admin

Скриншот работающего приложения с затертыми данными:

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

function Get-Clients #Функция принимает строку адреса файла в Google Drive и возвращает в виде массива данных о клиентах{param([string]$google_url = "")[string]$xlsFile = $google_url$csvFile = "$env:temp\clients.csv"$Comma = ','Invoke-WebRequest $xlsFile -OutFile $csvFile$clients = Import-Csv -Delimiter $Comma -Path "$env:temp\clients.csv"Remove-Item -Path $csvFilereturn $clients}function Main {<#    Функция, срабатываемая при запуске скрипта#>Param ([String]$Commandline)#Иннициализируем переменные и присваиваем начальные значения. Здесь же, указываем путь к таблице с клиентами$Global:Clients = $null$Global:Current$Global:CurrentRDPcomp$Global:google_file = "https://docs.google.com/spreadsheets/d/1O-W1YCM4x3o5W1w6XahCJZpkTWs8cREXVF69gs1dD0U/export?format=csv" # Таблица скачивается сразу в виде csv-файла$Global:Clients = Get-Clients ($Global:google_file) # Присваиваем значения из таблицы массиву #Скачиваем Winbox64 во временную папку$download_url = "https://download.mikrotik.com/winbox/3.27/winbox64.exe"$Global:local_path = "$env:temp\winbox64.exe"If ((Test-Path $Global:local_path) -ne $true){$WebClient = New-Object System.Net.WebClient$WebClient.DownloadFile($download_url, $Global:local_path)}  #Разрываем все текущие VPN соединения (на всякий случай)foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" }){Rasdial $item.Name /disconnect}  #Удаляем все, ранее созданные программой временные соединения, если вдруг не удалились при некорректном закрытии приложенияget-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force#Запускаем приложениеShow-MainForm_psf}#Собственно, само приложениеfunction Show-MainForm_psf{[void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')[void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')#Создаем форму и объекты формы[System.Windows.Forms.Application]::EnableVisualStyles()$formКлиентыАльбус = New-Object 'System.Windows.Forms.Form'$statusbar1 = New-Object 'System.Windows.Forms.StatusBar'$groupboxTools = New-Object 'System.Windows.Forms.GroupBox'$buttonPing = New-Object 'System.Windows.Forms.Button'$buttonВыход = New-Object 'System.Windows.Forms.Button'$buttonWindox = New-Object 'System.Windows.Forms.Button'$buttonПеречитатьДанные = New-Object 'System.Windows.Forms.Button'$buttonPingAll = New-Object 'System.Windows.Forms.Button'$groupboxRDP = New-Object 'System.Windows.Forms.GroupBox'$comboboxRDP = New-Object 'System.Windows.Forms.ComboBox'$textboxRDPLogin = New-Object 'System.Windows.Forms.TextBox'$textboxRdpPwd = New-Object 'System.Windows.Forms.TextBox'$buttonПодключитьRDP = New-Object 'System.Windows.Forms.Button'$groupboxVPN = New-Object 'System.Windows.Forms.GroupBox'$buttonПодключитьVPN = New-Object 'System.Windows.Forms.Button'$buttonОтключитьVPN = New-Object 'System.Windows.Forms.Button'$checkboxШлюзПоумолчанию = New-Object 'System.Windows.Forms.CheckBox'$richtextboxinfo = New-Object 'System.Windows.Forms.RichTextBox'$listbox_clients = New-Object 'System.Windows.Forms.ListBox'$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'  #----------------------------------------------# Обработчики событий#----------------------------------------------$formКлиентыАльбус_Load = {#При загрузке формы очистить поле информации и заполнить поле с клиентами (их названиями) $richtextboxinfo.Clear()$Global:Clients | ForEach-Object {[void]$listbox_clients.Items.Add($_.Name)} # В листбокс добавляем всех наших клиентов по именам и массива при загрузке формы}$listbox_clients_SelectedIndexChanged = {#Прочитать из массива информацию о клиенте при выборе его в поле listbox_clients (массив, как мы помним считан из файла с диска Google)$statusbar1.Text = 'Выбран клиент: ' + $listbox_clients.SelectedItem.ToString() # Пишем клиента в статусбар$Global:Current = $Global:Clients.Where({ $_.Name -eq $listbox_clients.SelectedItem.ToString() })If ($Current.PortWinbox -ne 0) # Если порт Winbox указан, то у клиента Mikrotik, включаем соответствующую кнопку{$buttonWindox.Enabled = $true$buttonWindox.Text = "Winbox"}$VPNname = $Global:Current.VPNname + "-tmp" #Добавляем к имени VPN соединения "-tmp" для указания метки временного соединения, чтобы при выходе удалить только ихswitch ($Global:Current.VPNType) #В зависимости от типа VPN пишем на кнопке "Подключить pptp VPN" или "Подключить l2tp VPN", если у клиента нет VPN, то пишем "Здесь нет VPN"{"pptp" {$buttonПодключитьVPN.Enabled = $true$buttonПодключитьVPN.Text = "Подключить pptp VPN"}"l2tp" {$buttonПодключитьVPN.Enabled = $true$buttonПодключитьVPN.Text = "Подключить l2tp VPN"}DEFAULT{$buttonПодключитьVPN.Enabled = $false$buttonПодключитьVPN.Text = "Здесь нет VPN"}}switch ($Global:Current.DefaultGateway) #Смотрим в массиве, используется ли у клиента "Шлюз по-умолчанию" и заполняем соответствующий чекбокс{"FALSE"{ $checkboxШлюзПоумолчанию.Checked = $false }"Нет"{ $checkboxШлюзПоумолчанию.Checked = $false }"TRUE"{ $checkboxШлюзПоумолчанию.Checked = $true }"Да"{ $checkboxШлюзПоумолчанию.Checked = $true }DEFAULT{ $checkboxШлюзПоумолчанию.Checked = $false }}$VPNStatus = (ipconfig | Select-String $VPNname -Quiet) #Проверяем, не установлено ли уже это VPN соединение?If ($VPNStatus) #Если установлено, то разблокируем кнопку "Подключить RDP"{$buttonПодключитьRDP.Enabled = $true}else{$buttonПодключитьRDP.Enabled = $false}$richtextboxinfo.Clear() #Очищаем информационное поле # И заполняем информацией о клиенте из массива$richtextboxinfo.SelectionColor = 'Black'$richtextboxinfo.Text = "Клиент: " + $Global:Current.Name + [System.Environment]::NewLine + `"Имя VPN: " + $Global:Current.VPNname + [System.Environment]::NewLine + `"Тип VPN: " + $Global:Current.VPNType + [System.Environment]::NewLine + `"Адрес сервера: " + $Global:Current.ServerAddress + [System.Environment]::NewLine + `"Подсеть клиента: " + $Global:Current.RemoteNetwork + [System.Environment]::NewLine + `"Адрес сервера RDP: " + $Global:Current.RDPcomp + [System.Environment]::NewLine + [System.Environment]::NewLine + `"DefaultGateway: " + $Global:Current.DefaultGateway + [System.Environment]::NewLine + [System.Environment]::NewLine + `"Примечание: " + [System.Environment]::NewLine + $Global:Current.Inform + [System.Environment]::NewLine + `"Connection '" + $VPNname + "' status is " + $buttonПодключитьRDP.Enabled + [System.Environment]::NewLine$richtextboxinfo.AppendText($Global:Current.Link)$RDPServers = $Global:Current.RDPcomp.Split(';') -replace '\s', '' #Считываем и разбираем RDP серверы клиента из строки с разделителем в массив#Добавляем из в выпадающее поле выбора сервера$comboboxRDP.Items.Clear()$comboboxRDP.Text = $RDPServers[0]foreach ($RDPServer in $RDPServers){$comboboxRDP.Items.Add($RDPServer)}#Заполняем поля имени и пароля RDP по умолчанию из таблицы о клиенте (при желании, их можно поменять в окне программы)$textboxRdpPwd.Text = $Global:Current.RDPpass$textboxRdpLogin.Text = $Global:Current.RDPuser} # Форма заполнена, при смене выбранного клиента произойдет перезаполнение полей в соответствии с выбранным клиентом$buttonWindox_Click = {#Обработка нажатия кнопки WinboxIf ($Global:Current.PortWinbox -ne 0) #Если порт Winbox заполнен, то открываем скачанный ранее Winbox, подставляем туда имя и пароль к нему и запускаем{$runwinbox = "$env:temp\winbox64.exe"$ServerPort = $Global:Current.ServerAddress + ":" + $Global:Current.PortWinbox$ServerLogin = " """ + $Global:Current.WinboxLogin + """"$ServerPass = " """ + $Global:Current.WinboxPwd + """"$Arg = "$ServerPort $ServerLogin $ServerPass "Start-Process -filePath $runwinbox -ArgumentList $Arg}}$buttonПодключитьVPN_Click = {#Обработка нажатия кнопки ПодключитьVPN$VPNname = $Global:Current.VPNname + "-tmp" #Добавляем к имени VPN соединения "-tmp" для указания метки временного соединения, чтобы при выходе удалить только их$richtextboxinfo.Clear() #Очищаем информационное поля для вывода туда информации о процессе подключения$richtextboxinfo.Text = "Клиент: " + $Global:Current.Name + [System.Environment]::NewLineforeach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" }) #Разрываем все установленные соединения{$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLineRasdial $item.Name /disconnect}Remove-VpnConnection $VPNname -Force #Удаляем соединение, если ранее оно было создано$RemoteNetworks = $Global:Current.RemoteNetwork.Split(';') -replace '\s', '' #Считываем и разбираем по строкам в массив список подсетей клиента разделенный ;switch ($Global:Current.VPNType) #В зависимости от типа VPNа создаем pptp или l2tp соединение{"pptp" {$richtextboxinfo.Text = $richtextboxinfo.Text + "Создаем pptp подключение " + $VPNname + [System.Environment]::NewLineIf ($checkboxШлюзПоумолчанию.Checked -eq $false) #Если не используется "Шлюз по-умолчанию", то создаем VPN соединение без него и прописываем маршруты{$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -SplitTunneling -Force -RememberCredential -PassThru) #Здесь происходит создание VPNforeach ($RemoteNetwork in $RemoteNetworks) #Добавляем все подсети клиента к этому VPN{$richtextboxinfo.AppendText('Добавляем маршрут к ' + $RemoteNetwork + [System.Environment]::NewLine)Add-VpnConnectionRoute -ConnectionName $VPNname -DestinationPrefix $RemoteNetwork -PassThru}}else #Если используется "Шлюз по-умолчанию", то создаем VPN соединение с ним и маршруты к клиенту не нужны{$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -Force -RememberCredential -PassThru)}}"l2tp" {$richtextboxinfo.Text = $richtextboxinfo.Text + "Создаем l2tp подключение " + $Global:Current.VPNname + [System.Environment]::NewLineIf ($checkboxШлюзПоумолчанию.Checked -eq $false) #Если не используется "Шлюз по-умолчанию", то создаем VPN соединение без него и прописываем маршруты{$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -L2tpPsk $Global:Current.l2tpPsk -SplitTunneling -Force -RememberCredential -PassThru) #Здесь происходит создание VPNforeach ($RemoteNetwork in $RemoteNetworks) #Добавляем все подсети клиента к этому VPN{$richtextboxinfo.AppendText('Добавляем маршрут к ' + $RemoteNetwork + [System.Environment]::NewLine)Add-VpnConnectionRoute -ConnectionName $VPNname -DestinationPrefix $RemoteNetwork -PassThru}}else #Если используется "Шлюз по-умолчанию", то создаем VPN соединение с ним и маршруты к клиенту не нужны{$Errcon = (Add-VpnConnection -Name $VPNname -ServerAddress $Global:Current.ServerAddress -TunnelType $Global:Current.VPNType -L2tpPsk $Global:Current.l2tpPsk -Force -RememberCredential -PassThru)}}}$richtextboxinfo.AppendText("Устанавливаем " + $Global:Current.VPNType + " подключение к " + $VPNname + [System.Environment]::NewLine)$Errcon = Rasdial $VPNname $Global:Current.VPNLogin $Global:Current.VPNPass #Устанавливаем созданное VPN подключение и выводим информацию в поле$richtextboxinfo.Text = $richtextboxinfo.Text + [System.Environment]::NewLine + $Errcon + [System.Environment]::NewLineIf ((ipconfig | Select-String $VPNname -Quiet)) #Проверяем успешность соединения и, если все удачно, разблокируем кнопку RDP  и кнопку "Отключить VPN"{$buttonПодключитьRDP.Enabled = $true$buttonОтключитьVPN.Visible = $true$buttonОтключитьVPN.Enabled = $true$statusbar1.Text = $Global:Current.Name + ' подключен'}}$formКлиентыАльбус_FormClosing = [System.Windows.Forms.FormClosingEventHandler]{#При закрытии формы подчищаем за собой. Разрываем и удаляем все созданные соединения. foreach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" }){$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLineRasdial $item.Name /disconnect}$richtextboxinfo.Text = $richtextboxinfo.Text + "Удаляем все временные соединения" + [System.Environment]::NewLineget-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force#Удаляем информацию о RPD-серверах из реестра$Global:Clients | ForEach-Object {$term = "TERMSRV/" + $_.RDPcompcmdkey /delete:$term}}$buttonПодключитьRDP_Click = {#Обработка кнопки ПодключитьRDP$RDPcomp = $comboboxRDP.Text$RDPuser = $textboxRDPLogin.Text$RDPpass = $textboxRdpPwd.Textcmdkey /generic:"TERMSRV/$RDPcomp" /user:"$RDPuser" /pass:"$RDPpass"mstsc /v:$RDPcomp}$buttonОтключитьVPN_Click = {#При отключении VPN подчищаем за собой и оповещаем о процессе в поле информацииforeach ($item in get-vpnconnection | where { $_.ConnectionStatus -eq "Connected" }){$richtextboxinfo.Text = $richtextboxinfo.Text + "Обнаружено активное соединение " + $item.Name + " разрываем его" + [System.Environment]::NewLineRasdial $item.Name /disconnect}$richtextboxinfo.Text = $richtextboxinfo.Text + "Удаляем все временные соединения" + [System.Environment]::NewLineget-vpnconnection | where { $_.Name -match "tmp" } | Remove-VpnConnection -Force$buttonОтключитьVPN.Visible = $false$buttonПодключитьRDP.Enabled = $false$statusbar1.Text = $Global:Current.Name + ' отключен'}$buttonPingAll_Click={#Пингуем всех клиентов и оповещаем о результатах$I=0$richtextboxinfo.Clear()$richtextboxinfo.SelectionColor = 'Black'$clientscount = $Global:Clients.count$Global:Clients | ForEach-Object {if ((test-connection -Count 1 -computer $_.ServerAddress -quiet) -eq $True){$richtextboxinfo.SelectionColor = 'Green'$richtextboxinfo.AppendText($_.Name +' ('+ $_.ServerAddress +') доступен' + [System.Environment]::NewLine)}else{$richtextboxinfo.SelectionColor = 'Red'$richtextboxinfo.AppendText($_.Name + ' (' + $_.ServerAddress + ')  недоступен (или закрыт ICMP)' + [System.Environment]::NewLine)}$richtextboxinfo.ScrollToCaret()$I = $I + 1Write-Progress -Activity "Ping in Progress" -Status "$i clients of $clientscount pinged" -PercentComplete ($i/$clientscount*100)}$richtextboxinfo.SelectionColor = 'Black'Write-Progress -Activity "Ping in Progress" -Status "Ready" -Completed}$buttonПеречитатьДанные_Click={#Перечитываем данные из таблицы Google$Global:Clients = Get-Clients ($Global:google_file)$listbox_clients.Items.Clear()$Global:Clients | ForEach-Object {[void]$listbox_clients.Items.Add($_.Name)}}$buttonВыход_Click = {#Выход$formКлиентыАльбус.Close()}$richtextboxinfo_LinkClicked=[System.Windows.Forms.LinkClickedEventHandler]{#Обработка нажатия на ссылку в окне информацииStart-Process $_.LinkText.ToString()}$buttonPing_Click={#Пингуем ip текущего клиента и выводим результат в поле информацииif ((test-connection -Count 1 -computer $Global:Current.ServerAddress -quiet) -eq $True){$richtextboxinfo.AppendText([System.Environment]::NewLine)$richtextboxinfo.SelectionColor = 'Green'$richtextboxinfo.AppendText($Global:Current.Name + ' (' + $Global:Current.ServerAddress + ') доступен' + [System.Environment]::NewLine)}else{$richtextboxinfo.AppendText([System.Environment]::NewLine)$richtextboxinfo.SelectionColor = 'Red'$richtextboxinfo.AppendText($Global:Current.Name + ' (' + $Global:Current.ServerAddress + ')  недоступен (или закрыт ICMP)' + [System.Environment]::NewLine)}}#----------------------------------------------#Описание объектов формы#----------------------------------------------## formКлиентыАльбус#$formКлиентыАльбус.Controls.Add($statusbar1)$formКлиентыАльбус.Controls.Add($groupboxTools)$formКлиентыАльбус.Controls.Add($groupboxRDP)$formКлиентыАльбус.Controls.Add($groupboxVPN)$formКлиентыАльбус.Controls.Add($richtextboxinfo)$formКлиентыАльбус.Controls.Add($listbox_clients)$formКлиентыАльбус.AutoScaleDimensions = '6, 13'$formКлиентыАльбус.AutoScaleMode = 'Font'$formКлиентыАльбус.AutoSize = $True$formКлиентыАльбус.ClientSize = '763, 446'$formКлиентыАльбус.FormBorderStyle = 'FixedSingle'$formКлиентыАльбус.MaximizeBox = $False$formКлиентыАльбус.Name = 'formКлиентыАльбус'$formКлиентыАльбус.SizeGripStyle = 'Hide'$formКлиентыАльбус.StartPosition = 'CenterScreen'$formКлиентыАльбус.Text = 'Клиенты Альбус'$formКлиентыАльбус.add_FormClosing($formКлиентыАльбус_FormClosing)$formКлиентыАльбус.add_Load($formКлиентыАльбус_Load)## statusbar1#$statusbar1.Location = '0, 424'$statusbar1.Name = 'statusbar1'$statusbar1.Size = '763, 22'$statusbar1.TabIndex = 17## groupboxTools#$groupboxTools.Controls.Add($buttonPing)$groupboxTools.Controls.Add($buttonВыход)$groupboxTools.Controls.Add($buttonWindox)$groupboxTools.Controls.Add($buttonПеречитатьДанные)$groupboxTools.Controls.Add($buttonPingAll)$groupboxTools.Location = '308, 258'$groupboxTools.Name = 'groupboxTools'$groupboxTools.Size = '147, 163'$groupboxTools.TabIndex = 10$groupboxTools.TabStop = $False$groupboxTools.Text = 'Tools'$groupboxTools.UseCompatibleTextRendering = $True## buttonPing#$buttonPing.Location = '7, 44'$buttonPing.Name = 'buttonPing'$buttonPing.Size = '133, 23'$buttonPing.TabIndex = 12$buttonPing.Text = 'Ping'$buttonPing.UseCompatibleTextRendering = $True$buttonPing.UseVisualStyleBackColor = $True$buttonPing.add_Click($buttonPing_Click)## buttonВыход#$buttonВыход.Location = '7, 125'$buttonВыход.Name = 'buttonВыход'$buttonВыход.Size = '133, 23'$buttonВыход.TabIndex = 15$buttonВыход.Text = 'Выход'$buttonВыход.UseCompatibleTextRendering = $True$buttonВыход.UseVisualStyleBackColor = $True$buttonВыход.add_Click($buttonВыход_Click)## buttonWindox#$buttonWindox.Enabled = $False$buttonWindox.Location = '7, 17'$buttonWindox.Name = 'buttonWindox'$buttonWindox.Size = '133, 23'$buttonWindox.TabIndex = 11$buttonWindox.Text = 'Windox'$buttonWindox.UseCompatibleTextRendering = $True$buttonWindox.UseVisualStyleBackColor = $True$buttonWindox.add_Click($buttonWindox_Click)## buttonПеречитатьДанные#$buttonПеречитатьДанные.Location = '7, 98'$buttonПеречитатьДанные.Name = 'buttonПеречитатьДанные'$buttonПеречитатьДанные.Size = '133, 23'$buttonПеречитатьДанные.TabIndex = 14$buttonПеречитатьДанные.Text = 'Перечитать данные'$buttonПеречитатьДанные.UseCompatibleTextRendering = $True$buttonПеречитатьДанные.UseVisualStyleBackColor = $True$buttonПеречитатьДанные.add_Click($buttonПеречитатьДанные_Click)## buttonPingAll#$buttonPingAll.Location = '7, 71'$buttonPingAll.Name = 'buttonPingAll'$buttonPingAll.Size = '133, 23'$buttonPingAll.TabIndex = 13$buttonPingAll.Text = 'Ping All'$buttonPingAll.UseCompatibleTextRendering = $True$buttonPingAll.UseVisualStyleBackColor = $True$buttonPingAll.add_Click($buttonPingAll_Click)## groupboxRDP#$groupboxRDP.Controls.Add($comboboxRDP)$groupboxRDP.Controls.Add($textboxRDPLogin)$groupboxRDP.Controls.Add($textboxRdpPwd)$groupboxRDP.Controls.Add($buttonПодключитьRDP)$groupboxRDP.Location = '308, 128'$groupboxRDP.Name = 'groupboxRDP'$groupboxRDP.Size = '147, 126'$groupboxRDP.TabIndex = 5$groupboxRDP.TabStop = $False$groupboxRDP.Text = 'RDP'$groupboxRDP.UseCompatibleTextRendering = $True## comboboxRDP#$comboboxRDP.FormattingEnabled = $True$comboboxRDP.Location = '7, 17'$comboboxRDP.Name = 'comboboxRDP'$comboboxRDP.Size = '133, 21'$comboboxRDP.TabIndex = 6$comboboxRDP.Text = 'IP RDP сервера'## textboxRDPLogin#$textboxRDPLogin.Location = '7, 44'$textboxRDPLogin.Name = 'textboxRDPLogin'$textboxRDPLogin.Size = '133, 20'$textboxRDPLogin.TabIndex = 7$textboxRDPLogin.Text = 'RDP-login'## textboxRdpPwd#$textboxRdpPwd.Location = '7, 69'$textboxRdpPwd.Name = 'textboxRdpPwd'$textboxRdpPwd.PasswordChar = '*'$textboxRdpPwd.Size = '133, 20'$textboxRdpPwd.TabIndex = 8$textboxRdpPwd.Text = 'RDP-Password'## buttonПодключитьRDP#$buttonПодключитьRDP.Enabled = $False$buttonПодключитьRDP.Location = '7, 94'$buttonПодключитьRDP.Name = 'buttonПодключитьRDP'$buttonПодключитьRDP.Size = '133, 20'$buttonПодключитьRDP.TabIndex = 9$buttonПодключитьRDP.Text = 'Подключить RDP'$buttonПодключитьRDP.UseCompatibleTextRendering = $True$buttonПодключитьRDP.UseVisualStyleBackColor = $True$buttonПодключитьRDP.add_Click($buttonПодключитьRDP_Click)## groupboxVPN#$groupboxVPN.Controls.Add($buttonПодключитьVPN)$groupboxVPN.Controls.Add($buttonОтключитьVPN)$groupboxVPN.Controls.Add($checkboxШлюзПоумолчанию)$groupboxVPN.Location = '308, 27'$groupboxVPN.Name = 'groupboxVPN'$groupboxVPN.Size = '147, 98'$groupboxVPN.TabIndex = 1$groupboxVPN.TabStop = $False$groupboxVPN.Text = 'VPN'$groupboxVPN.UseCompatibleTextRendering = $True## buttonПодключитьVPN#$buttonПодключитьVPN.Enabled = $False$buttonПодключитьVPN.Location = '7, 45'$buttonПодключитьVPN.Name = 'buttonПодключитьVPN'$buttonПодключитьVPN.Size = '133, 20'$buttonПодключитьVPN.TabIndex = 3$buttonПодключитьVPN.Text = 'Подключить VPN'$buttonПодключитьVPN.UseCompatibleTextRendering = $True$buttonПодключитьVPN.UseVisualStyleBackColor = $True$buttonПодключитьVPN.add_Click($buttonПодключитьVPN_Click)## buttonОтключитьVPN#$buttonОтключитьVPN.Enabled = $False$buttonОтключитьVPN.Location = '7, 67'$buttonОтключитьVPN.Name = 'buttonОтключитьVPN'$buttonОтключитьVPN.Size = '133, 20'$buttonОтключитьVPN.TabIndex = 4$buttonОтключитьVPN.Text = 'Отключить VPN'$buttonОтключитьVPN.UseCompatibleTextRendering = $True$buttonОтключитьVPN.UseVisualStyleBackColor = $True$buttonОтключитьVPN.Visible = $False$buttonОтключитьVPN.add_Click($buttonОтключитьVPN_Click)## checkboxШлюзПоумолчанию#$checkboxШлюзПоумолчанию.Location = '7, 19'$checkboxШлюзПоумолчанию.Name = 'checkboxШлюзПоумолчанию'$checkboxШлюзПоумолчанию.Size = '133, 24'$checkboxШлюзПоумолчанию.TabIndex = 2$checkboxШлюзПоумолчанию.Text = 'Шлюз по-умолчанию'$checkboxШлюзПоумолчанию.TextAlign = 'MiddleRight'$checkboxШлюзПоумолчанию.UseCompatibleTextRendering = $True$checkboxШлюзПоумолчанию.UseVisualStyleBackColor = $True## richtextboxinfo#$richtextboxinfo.Cursor = 'Default'$richtextboxinfo.ForeColor = 'WindowText'$richtextboxinfo.HideSelection = $False$richtextboxinfo.Location = '461, 27'$richtextboxinfo.Name = 'richtextboxinfo'$richtextboxinfo.ReadOnly = $True$richtextboxinfo.ScrollBars = 'ForcedVertical'$richtextboxinfo.ShowSelectionMargin = $True$richtextboxinfo.Size = '290, 394'$richtextboxinfo.TabIndex = 16$richtextboxinfo.Text = ''$richtextboxinfo.add_LinkClicked($richtextboxinfo_LinkClicked)## listbox_clients#$listbox_clients.FormattingEnabled = $True$listbox_clients.Location = '12, 27'$listbox_clients.Name = 'listbox_clients'$listbox_clients.Size = '290, 394'$listbox_clients.TabIndex = 0$listbox_clients.add_SelectedIndexChanged($listbox_clients_SelectedIndexChanged)#Save the initial state of the form$InitialFormWindowState = $formКлиентыАльбус.WindowState#Init the OnLoad event to correct the initial state of the form$formКлиентыАльбус.add_Load($Form_StateCorrection_Load)#Clean up the control events$formКлиентыАльбус.add_FormClosed($Form_Cleanup_FormClosed)#Store the control values when form is closing$formКлиентыАльбус.add_Closing($Form_StoreValues_Closing)#Show the Formreturn $formКлиентыАльбус.ShowDialog()}#Запуск приложения!Main ($CommandLine) 

Скрипт можно запускать как скрипт ps1 или скомпилировать в exe через ps2exe и использовать как полноценное приложение

Подробнее..

Автозаказ как сделать так, чтобы нужные продукты сами попадали на полки 17000 магазинов по всей стране

17.06.2021 18:06:38 | Автор: admin

В школе все мы решали задачки вида едет из пункта А в пункт Б. Речь преимущественно шла о скорости и времени как быстро доберётся транспортное средство? Реальность, однако, подбрасывает задачки значительно интереснее: Существует масштабная ритейл-сеть по продаже товаров, которой необходимо, чтобы огромное количество номенклатурных позиций доезжало в каждый из 17000 магазинов, расположенных на половине площади самой большой страны в мире, вовремя и в нужном количестве. Для решения такой задачи в X5 Group существует ряд реализованных решений, и одним из самых важных является процесс автозаказа товаров.

Техническую поддержку этого направления в X5 Group обеспечивает команда 2-SAP Логистики.

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

Автозаказ это комплекс процессов управления запасами и заказами

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

Планирование заказа.

Формирование заказа.

Отправка заказа.

Экономическое обоснование поддерживаемого уровня.

Контроль за состоянием запасов.

Управление ассортиментом.

Для этого используются разные механизмы прогнозирования в зависимости от формата магазина и гибких настроек в системе.

Прогноз продаж, построенный SAP это прогноз спроса на период 42 дня, который строится на исторических данных по продажам. Период анализа продаж и модель прогноза определяется из настройки Профиля прогноза.

В периоде анализа продажи могут быть Плановые регулярные продажи и Внеплановые промо продажи.

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

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

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

Около 04:30 эти данные по потребности из JDA поступают в SAP, где обрабатываются фоновым заданием с интервалом запуска каждые 15 минут, в результате чего создаются Автозаказы.

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

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

Через автозаказ пополняется до 80% основного ассортимента магазинов, а ручное пополнение выполняется для товаров in-out.

Схема реализации товара:

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

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

- в 00:00 стартует задание по АЗ, в период его работы по нашему товару рассчиталась потребность, сформировался автозаказ, и он бы отправлен из SAP в программу магазина GK(Пятёрочка), там у сотрудников магазина есть время отредактировать рассчитанное кол-во (в большую или меньшую сторону) до наступления времени автосогласования (например, до 11 утра, время зависит от тайминга поставщика, т.е. до какого момента он должен получить заказ, чтобы собрать и вовремя привезти заказ в магазин);

- после внесения корректировок эти изменения отправляются в SAP, и дальше происходит процесс автосогласования заказ согласовывается и автоматически отправляется поставщику по почте или его провайдеру (в зависимости от того, по какой схеме поставщик работает). Например, если поставщик работает по EDI (через электронный документооборот), то после доставки заказа в его систему, нам провайдер передает информацию о размещении заказа в своем ПО и в системе ER2 заказ переходит в статус Размещен;

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

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

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

  • Продажи за период в прошлом

  • Прогноз на период в будущее (на основании продаж строится прогноз SAP)

  • Остатки на объекте получателе (остаток в SAP на момент расчёта)

  • Открытые заказы (заказы на поставку и возвраты)

Продажи приходят из магазинов каждый вечер до расчёта фонового задания Автозаказа.

Факт получения продаж запускает расчет Автозаказа. Если до 03:00 продажи не получены SAP ERP, то происходит безусловный запуск задания Автозаказа.

Автозаказ формируется строго по графику заказа/поставки согласованному с поставщиком, так категории FRESH и ULTRA FRESH, как правило, заказываются и поставляются в магазины ежедневно.

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

Для того, чтобы заказать оптимальное кол-во товара, учитывается прогноз, который строится в SAP в момент расчета Автозаказа.

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

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

Запускается задание ежедневно в 00:00. Первыми выполняются расчеты по регионам Сибирь и Урал, далее по регионам Волга, ЮГ, Москва, Центральные регионы и Северо-запад. Последовательность выполнения крайне важна, т.к. наши магазины расположены в разных часовых поясах, и пока в Москве все сладко спят, в Сибири уже в разгаре рабочий день.

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

Для этого мы реализовали различные системы мониторинга, инструменты для анализа, а в случае необходимости и возможность отправки в магазины резервных шаблонов заказа.<o:p>

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

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

Подробнее..

Аналитик в автоматизации кто он и чем занимается

19.05.2021 22:12:58 | Автор: admin

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

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

Какие бывают аналитики

Наглядный профит от использования аналитикиНаглядный профит от использования аналитики

Если поискать в интернете, то найдётся куча видов таких млекопитающих:

  • Аналитик (вот так, просто аналитик)

  • Аналитик БД

  • Аналитик бизнес-процессов

  • Бизнес-аналитик

  • Системный аналитик

  • UX-аналитик

  • Продуктовый аналитик

  • Аналитик данных

  • BI-аналитик

  • Веб-аналитик

  • Технический писатель

И это не полный список. Плюс на должности навешиваются грейды, типа старший, ведущий, главный, незаменимый и т.д.

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

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

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

Специалисты широкого спектра

Сюда отношу тех, кто как правило задействован в автоматизациях, например, в интеграторах или в разработке продукта в in house команде. Такие люди чаще решают следующие задачи:

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

  • Формирование предложений по развитию продукта

  • Проектирование бизнес-процессов

  • Постановка задач дизайнерам

  • Подготовка спецификаций для разработчиков

  • Сопровождение разработки и тестирования

  • Верификация разработки, то есть проверка того, что разработка сделала всё так, как и задумывал аналитик

  • Взаимодействие со специалистами узкого спектра, о них позже

  • Участие в presale и предпроектных активностях

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

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

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

Что пригодится такому аналитику

Простая формула идеального аналитикаПростая формула идеального аналитика

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

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

  • Бизнес-анализ 30%

  • Системный анализ 35%

  • Продуктовый анализ 15%

  • UX-аналитика и дизайн 10%

  • Менеджмент 5%

  • Прочее последние 5%

Если детальнее, то из бизнес-анализа стоит знать:

  • Бизнес-процессы --- зачем они нужны, зачем их формализуют

  • Нотации моделирования бизнес-процессов, базовые знания BPMN необходимы

  • Логику и правила проектирования логической модели данных

  • Правила сбора требований и ограничений

  • Отдельно отмечу правила сбора требований к пользовательским интерфейсам

Для системного анализа пригодятся:

  • Типовые архитектурные паттерны

  • Навыки программирования алгоритмы, типы данных, наследование

  • Знания БД, понимание концепций нормализации и денормализации, реляционных и нереляционных СУБД

  • Представление о нотациях UML, умение работать с базовыми диаграммами (диаграмма последовательности, вариантов использования)

  • Понимание концепции декомпозиции задач на конкретные работы, хотя бы банально на back и front

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

  • Понимание работы тестирования и умение самому представлять возможные не-happy path варианты работы решения

Для решения задач продуктового анализа потребуется:

  • Знание предметной области или живой ум, чтобы быстро разбираться на уровне, достаточном для решения задач

  • Базовое понимание метрик и их ценностей для продукта

  • Умение расставлять приоритеты между задачами и понимание концепции MVP

Для корректного взаимодействия с дизайнерами необходимы базовые знания UX/UI:

  • Сложно формализуемое чувство прекрасного для интерфейса и умение поставить себя на место пользователя

  • Понимание компонентного подхода при проектировании интерфейсов

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

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

Про узких специалистов

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

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

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

  • Нравится копаться в системных штуках системный аналитик, и там до архитектора

  • Любишь работать с данными аналитик БД, BI или в сторону data science

  • Интересно продумывать пользовательский путь и управлять им UX-аналитик, UX/UI-дизайнер

  • Горишь в том, чтобы принимать решения и развивать продукт продуктовый аналитик, и далее до руководителя продукта

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

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

Итоги

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

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

Подробнее..

Перевод Автоматизация тестирования на Python Шесть способов тестировать эффективно

03.06.2021 18:21:18 | Автор: admin

Мы уже говорили об автоматизации тестирования, теперь пришло время познакомиться с шестью лучшими инструментами автоматизации тестирования на Python.

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

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

Итак, начнем.

PyUnit и Nose2

PyUnit это фреймворк юнит-тестирования на Python. Его добавили в стандартную библиотеку Python еще в версии 2.1, он совместим со всеми последующими версиями языка. PyUnit это реализация JUnit на Python, стандартного фреймворка юнит-тестирования Java. Именно поэтому разработчики, которые переходят с Java на Python найдут его очень простым в использовании. Оба фреймворка обязаны своим существованием фреймворку для тестирования на Smalltalk от Кента Бека.

PyUnit содержит все необходимые инструменты для создания автоматизированных тестов.

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

  • Методы для выполнения тестов.

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

  • Раннеры для выполнения тестов.

Вот пример базового юнит-теста:

import unittest     class SimpleWidgetTestCase(unittest.TestCase):        def setUp(self):            self.widget = Widget("The widget")     class DefaultWidgetSizeTestCase(SimpleWidgetTestCase):        def runTest(self):            assert self.widget.size() == (50,50), 'incorrect default size'

SimpleWidgetTestCase использует фикстуру setUp, чтобы создать Widget для тестирования. DefaultWidgetSizeTestCase это класс-наследник SimpleWidgetTestCase, который проверяет размер Widget.

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

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

Nose2 также содержит Such DSL для написания функциональных тестов.

Если вы поместите код в файл с именем test_widgets.py, тест-раннер Nose2 найдет тест и запустит его. Все, что вам нужно сделать это добавить в ваши файлы префикс tests_.

PyTest

PyTest (https://pytest.org/en/latest/) нативная библиотека тестирования на Python, она содержит расширенный набор функций PyUnit. По сравнению с моделированием архитектуры JUnit, она определенно написана в стиле Python. Она активно использует декораторы и ассерты Python.

PyTest также поддерживает параметризированное тестирование (без плагинов по типу Nose), что упрощает переиспользование кода и его покрытие тестами.

Если вы перепишете под Pytest тот тест, который мы написали выше, он будет выглядеть более декларативным.

@pytest.fixturedef widget():    return Widget("The widget") def test_widget_size(widget):    assert widget.size() == (50,50), 'incorrect default size'

PyTest использует тестовые фикстуры для передачи Widget методу тестирования.

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

PyTest упрощает создание отчетов в виде обычного текста, XML или HTML. Также вы можете добавить информацию о покрытии кода в отчеты PyTest.

Несмотря на то, что PyTest можно использовать самостоятельно, вы можете интегрировать его с другими фреймворками тестирования и тест-раннерами, такими как PyUnit и Nose2. Благодаря такой совместимости PyTest станет отличным выбором для растущих проектов, которым нужно хорошее покрытие тестами. Для PyTest нужен Python 3.6 или более поздние версии.

Behave

PyUnit и PyTest мощные традиционные фреймворки для юнит-тестирования, но что, если вам нужны behavior-driven тесты?

Behave это behavior-driven (BDD) фреймворк для тестирования. Он критически отличается от PyUnit и PyTest. В нем вы пишете тесты на Gherkin вместо Python. Несмотря на то, что здесь не оригинальный Gherkin от Cucumber, в Behave есть полная поддержка Gherkin, поэтому он является одним из самых популярных BDD-фреймворков для Python.

Behave настолько распространен, что даже у Jetbrains есть для него плагин в PyCharm Professional Edition. Также существует множество онлайн-руководств и документации для работы с Behave.

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

Если вы интересуетесь или даже уже используете behavior-driven разработку (BDD), Behave один из лучший вариантов для этого. Он поставляется с интеграциями как для Django, так и для Flask, так что вы можете использовать его в full-stack проектах.

Тест из предыдущих примеров можно реализовать на Behave, как представлено ниже.

Вот грамматика естественного языка:

Feature: widget size  Scenario: verify a widget's size     Given we have a widget     When the widget is valid     Then the size is correct

А вот код на Python. У Given, When и Then есть соответствующие аннотации.

from behave import *@given('we have a widget')def step_given_a_widget(context):    context.widget = Widget('The widget')   @when('the widget is valid')def step_widget_is_valid(context)    assert context.widget is not None@then('the size is correct')def step_impl(context):    assert context.widget.size() == (50,50)

Lettuce

Lettuce это behavior-driven инструмент автоматизации для Selenium и Python. Подобно Behave, он использует синтаксис Gherkin для описания тестовых сценариев, но у него не такая совместимость, как у Behave. Lettuce не так распространен, как Behave, однако он хорошо работает с небольшими проектами.

Его также легко интегрировать с другими фреймворками, такими как Selenium и Nose.

Тесты на Lettuce чем-то напоминают тесты на Behave. Вот как это выглядит на естественном языке:

Feature: widget size Scenario: verify a widget's size     Given we have a widget     When the widget is valid     Then the size is correct

А вот код. Вместо отдельной аннотации для каждого шага теста, Lettuce аннотирует сам step.

from lettuce import stepfrom lettuce import world @step('we have a widget') def step_given_a_widget(step):    world.widget = Widget('The widget') @step('the widget is valid')def step_widget_is_valid(step)     assert world.widget is not None @step('the size is corrrect') def step_impl(context):     assert world.widget.size() == (50,50)

Когда вы интегрируете Lettuce с Selenium, у вас получается надежный фреймворк для тестирования приложений на Django. Так что, если вам не нравится синтаксис Jasmineс JavaScript, этот вариант может оказаться наилучшим.

Однако Lettuce не обновлялся с 2016 года. Вы все еще можете скачать его и использовать в коде, но больше он не поддерживается.

Jasmine для автоматизации тестирования на Python

BDD не просто популярная парадигма разработки на Python, также она широко распространена в веб-разработке. Jasmine популярный фреймворк для тестирования веб-приложений в стиле BDD. Скорее всего вы думаете о Jasmine, как об инструменте тестирования приложений на JavaScript, но вы вполне можете использовать его для автоматизации тестирования на Python.

Благодаря Jasmine-Py вы можете добавить Jasmine в свои проекты на Django. Так вы сможете запускать Jasmine из вашей среды Python и с вашего сервера CI/CD.

Тестирование веб-приложений на основе поведения, а не DOM, делает ваши тесты более устойчивыми к изменениям. Это становится огромным преимуществом в тот момент, когда вы тестируете как код на Django создает страницы. Вместо Gherkin вы будете писать тесты в грамматике Jasmine.

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

Фреймворк Robot

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

Вместо того, чтобы ориентироваться на поведение, как в Jasmine, Robot ориентируется на ключевые слова.

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

Можно расширить возможности Robot с помощью библиотек для тестирования, написанных на Python или Java. Таким образом, в дополнение к использованию этого фреймворка для тестирования кода на Python, вы можете расширить Robot с помощью Python. Также у вас есть доступ к обширной библиотеке плагинов для Robot.

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

Вам определенно нужна автоматизация тестирования на Python

В последние десять лет популярность Python неуклонно росла. Ее рост вы можете увидеть в индексе TIOBE. Велика вероятность, что вы уже пишете на Python или планируете добавить его в свой инструментарий в ближайшее время.

Расширение области применения Python привело к распространению фреймворков, инструментов тестирования и других утилит. Вне зависимости от того, создаете ли вы REST-сервис на бэкенде или любое другое приложение, для вас найдется подходящий фреймворк для автоматизированного тестирования.

Какой из них будет лучше отвечать вашим потребностям? У Testim есть руководство, которое поможет вам принять взвешенное решение. Обратитесь к нему и начните тестировать на Python уже сегодня.


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

Подробнее..

Кто такие citizen developers и как они двигают вперед цифровую трансформацию туториал по созданию робота

28.05.2021 18:18:56 | Автор: admin

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

При написании этой статьи мы пообщались со специалистом финансового отдела одного из Российских провайдеров цифровых услуг и решений, использующей в своей работе Studio X. Светлана не имела опыта разработки до знакомства с сервисами UiPath. С помощью Studio X она менее чем за полгода научилась роботизировать RPA-процессы.

Светлана рассказала нам немного о своём опыте:

Сложности конечно были. По указанной ссылке я установила программу (UiPath Studio X), больше 4 месяцев смотрела презентации, видео, читала материалы на сайте, но ничего не получалось повторить. Потом я прошла видеообучение со специалистом UiPath, Ильей и на практике освоила азы роботизации.

Правда не всё получилось сразу. Я долго мучалась, но у меня не выделялись нужные области в документах, как на обучении. Тем не менее, я создала своего первого робота через картинки, как объяснил позже Илья.

Комментарий от автора: Светлана автоматизировала удалённый рабочий стол через картинки это значит, что использовались не классические селекторы, а технология Computer Vision (компьютерное зрение), о которой мы рассказали здесь. Нужные области это селекторы, на примере ниже станет более понятно о выделении каких областей идёт речь.

Далее состоялась встреча с Ильёй, на которой я смогла показать своего первого робота. Оказалось, что для Oracle нужно было установить еще донастройку Java.

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

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

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

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

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

Темпы оцифровывания бизнеса все время растут, а после пандемии COVID-19 они совершили качественный скачок. В исследовании компании KMDA говорится, что в 2020 году на 20% (по сравнению с 2018 годом) выросло количество российских компаний, приступивших к реализации цифровой трансформации. Соответственно, увеличилась и потребность в компетенциях IT-специалистов, компаниям стало значительно не хватать разработчиков и айтишников других профилей.

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

Раньше citizen developers обычно создавали простые однопользовательские решения на основе Microsoft Excel и Access. Сегодня low-code инструменты не требуют от пользователя особых технических знаний и навыков, и они могут без труда разрабатывать ведомственные, корпоративные и даже общедоступные приложения.

Зачем нужны citizen developers?

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

Для наглядности можно посмотреть на график, на котором по оси ординат мы измеряем выгоду от роботизации процесса, а по оси абсцисс количество задач. Самую большую выгоду (на один процесс) мы получаем при роботизации процессов с большим числом транзакций, например, акты сверок. Роботизировали один раз автоматизировали сразу сотни тысяч транзакций. Число таких процессов у компании конечно. Количество задач, которые могут автоматизировать citizen developers, наоборот, неограниченно. Но каждая такая задача приносит несоразмерную с затратами на разработку в центре компетенций или силами подрядчика прибыль. Однако, если посчитать площадь под графиком, то мы увидим, что выгода от citizen development сопоставима с роботизацией крупных процессов с большим числом транзакций. Именно поэтому имеет смысл подключать бизнес-пользователей к разработке RPA-процессов для себя. Минимальными затратами можно получить серьезную прибыль в масштабах компании.

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

Citizen developers помогают снизить нагрузку на IТ-отдел, освобождая место для более приоритетной работы, требующей экспертного взгляда. Штатные программисты получают дополнительное время для решения стратегических задач, за счет того, что автоматизацией на местах занимаются citizen developers.

Как становятся citizen developers

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

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

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

В отличие от RPA- разработчиков и IТ-команд citizen developers создают новые решения только с использованием предварительно одобренных технологий. Их продукты чаще всего используются на индивидуальном и командном уровне, хотя не исключено, что они могут найти более широкое применение в масштабе всей организации.

Решения UiPath для citizen developers

Одним из решений UiPath для citizen developers является Studio X это среда разработки, которую используют бизнес-пользователи для создания своих проектов по автоматизации. Интерфейс студии очень простой и user friendly:

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

Рассмотрим простой пример, как можно в Studio X сделать простейшую автоматизацию создать робота, забирающего значение текущего времени из поисковика.

Шаг 1

Заходим в браузер на Google.com и гуглим текущее время в Москве. Для этого выбираем ресурс User Application Browser. Studio X говорит, что необходимо указать приложение для автоматизации:

Шаг 2

Для этого достаточно кликнуть по окну браузера, в котором вы работаете. Далее возвращаемся в студию и видим, что там указано, что мы используем Google Chrome. Сюда же можно ввести нужный URL:

Шаг 3

Далее выбираем активность Type Into, чтобы задать роботу текстовое поле для ввода:

Шаг 4

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

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

Шаг 5

И там же указываем объект это кнопка поиска Google:

Шаг 6

После этого роботу нужно получить данные со значением текущего времени используем для этого активность Get Text и выделяем строчку со временем:

Шаг 7

Полученное значение времени сохраним в переменную для дальнейшего использования. Назовем ее Время Москва:

Шаг 8

И в завершении используем активность вывода сообщения Message Box.

Пишем Точное время в городе Москва и дальше добавляем сохраненную переменную:

Вот как выглядит результат работы робота:

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

Такие простые автоматизации без труда могут быть освоены citizen developers и использоваться ими для роботизации рутинных процессов на местах.

У UiPath есть не только продукты, специально разработанные для citizen developers, но и специализированные курсы по автоматизации для бизнес-пользователей.

Как компании используют citizen development

Тренд citizen development набирает обороты в последние полтора года среди клиентов UiPath в Азиатско-Тихоокеанском регионе (APAC).

Австралийский Heritage Bank приступил к автоматизации в начале 2017 года. Теперь у него есть полноценная команда по автоматизации, управляющая 12 unattended и 60 attended роботами.

Для масштабной автоматизации банк привлек сотрудников, у которых была сильная мотивация к обучению навыкам автоматизации. Они стали главными кандидатами на роль citizen developers. Далее планируется отобрать 10-20 человек из их числа, чтобы управлять автоматизацией всей компании. Руководство хочет, чтобы сотрудники могли видеть, как автоматизация, запущенная с их помощью, помогает коллегам высвобождать время на выполнение более стратегических или творческих задач. Это должно мотивировать людей заниматься citizen development на местах.

Еще один пример успешного внедрения citizen development компания VITAL. В конце 2017 года VITAL начала работать с RPA для автоматизации повторяющейся работы с данными, чтобы в итоге стать цифровым сервисным центром.

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

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

Citizen development: факторы успеха

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

  • поддержка руководства компании,

  • грамотные коммуникации с коллективом,

  • поддержание уверенности сотрудников в полезности RPA.

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

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

Подробнее..

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

21.06.2021 14:12:41 | Автор: admin

Отыщи всему начало, и ты многое поймёшь (Козьма Прутков).

Меня зовут Руслан, я релиз-инженер в Badoo и Bumble. Недавно я столкнулся с необходимостью оптимизировать механизм автомерджа в мобильных проектах. Задача оказалась интересной, поэтому я решил поделиться её решением с вами. В статье я расскажу, как у нас раньше было реализовано автоматическое слияние веток Git и как потом мы увеличили пропускную способность автомерджа и сохранили надёжность процессов на прежнем высоком уровне.

Свой автомердж

Многие программисты ежедневно запускают git merge, разрешают конфликты и проверяют свои действия тестами. Кто-то автоматизирует сборки, чтобы они запускались автоматически на отдельном сервере. Но решать, какие ветки сливать, всё равно приходится человеку. Кто-то идёт дальше и добавляет автоматическое слияние изменений, получая систему непрерывной интеграции (Continuous Integration, или CI).

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

Такой автоматизации может быть достаточно для небольших проектов. Но с увеличением количества разработчиков и веток, ограничения, накладываемые сервисами, могут существенно повлиять на производительность CI. Например, раньше у нас была система мерджа, при которой основная ветка всегда находилась в стабильном состоянии благодаря последовательной стратегии слияний. Обязательным условием слияния была успешная сборка при наличии всех коммитов основной ветки в ветке разработчика. Работает эта стратегия надёжно, но у неё есть предел, определяемый временем сборки. И этого предела оказалось недостаточно. При времени сборки в 30 минут на обработку 100 слияний в день потребовалось бы более двух суток. Чтобы исключить ограничения подобного рода и получить максимальную свободу выбора стратегий мерджа и моделей ветвления, мы создали собственный автомердж.

Итак, у нас есть свой автомердж, который мы адаптируем под нужды каждой команды. Давайте рассмотрим реализацию одной из наиболее интересных схем, которую используют наши команды Android и iOS.

Термины

Main. Так я буду ссылаться на основную ветку репозитория Git. И коротко, и безопасно. =)

Сборка. Под этим будем иметь в виду сборку в TeamCity, ассоциированную с веткой Git и тикетом в трекере Jira. В ней выполняются как минимум статический анализ, компиляция и тестирование. Удачная сборка на последней ревизии ветки в сочетании со статусом тикета To Merge это однo из необходимых условий автомерджа.

Пример модели ветвления

Испробовав разные модели ветвления в мобильных проектах, мы пришли к следующему упрощённому варианту:

На основе ветки main разработчик создаёт ветку с названием, включающим идентификатор тикета в трекере, например PRJ-k. По завершении работы над тикетом разработчик переводит его в статус Resolved. При помощи хуков, встроенных в трекер, мы запускаем для ветки тикета сборку. В определённый момент, когда изменения прошли ревью и необходимые проверки автотестами на разных уровнях, тикет получает статус To Merge, его забирает автоматика и отправляет в main.

Раз в неделю на основе main мы создаём ветку релиза release_x.y.z, запускаем на ней финальные сборки, при необходимости исправляем ошибки и наконец выкладываем результат сборки релиза в App Store или Google Play. Все фазы веток отражаются в статусах и дополнительных полях тикетов Jira. В общении с Jira помогает наш клиент REST API.

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

Первая версия: жадная стратегия

Сначала мы шли от простого и очевидного. Брали все тикеты, находящиеся в статусе To Merge, выбирали из них те, для которых есть успешные сборки, и отправляли их в main командой git merge, по одной.

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

Наличие в TeamCity актуальной успешной сборки мы проверяли при помощи метода REST API getAllBuilds примерно следующим образом (псевдокод):

haveFailed = False # Есть ли неудачные сборкиhaveActive = False # Есть ли активные сборки# Получаем сборки типа buildType для коммита commit ветки branchbuilds = teamCity.getAllBuilds(buildType, branch, commit)# Проверяем каждую сборкуfor build in builds:  # Проверяем каждую ревизию в сборке  for revision in build.revisions:    if revision.branch is branch and revision.commit is commit:      # Сборка актуальна      if build.isSuccessful:        # Сборка актуальна и успешна        return True      else if build.isRunning or build.isQueued        haveActive = True      else if build.isFailed:        haveFailed = Trueif haveFailed:  # Исключаем тикет из очереди, переоткрывая его  ticket = Jira.getTicket(branch.ticketKey)  ticket.reopen("Build Failed")  return Falseif not haveActiveBuilds:  # Нет ни активных, ни упавших, ни удачных сборок. Запускаем новую  TriggerBuild(buildType, branch)

Ревизии это коммиты, на основе которых TeamCity выполняет сборку. Они отображаются в виде 16-ричных последовательностей на вкладке Changes (Изменения) страницы сборки в веб-интерфейсе TeamCity. Благодаря ревизиям мы можем легко определить, требуется ли пересборка ветки тикета или тикет готов к слиянию.

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

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

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

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

Конфликты слияния

Если изменить одну и ту же строку кода в разных ветках и попытаться соединить их в main, то Git попросит разрешить конфликты слияния. Из двух вариантов нужно выбрать один и закоммитить изменения.

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

Если команда git merge завершилась с ошибкой и для всех файлов в списке git ls-files --unmerged заданы обработчики конфликтов, то для каждого такого файла мы выполняем парсинг содержимого по маркерам конфликтов <<<<<<<, ======= и >>>>>>>. Если конфликты вызваны только изменением версии приложения, то, например, выбираем последнюю версию между локальной и удалённой частями конфликта.

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

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

Логические конфликты

А может ли случиться так, что, несмотря на успешность сборок пары веток в отдельности, после слияния их с main сборка на основной ветке упадёт? Практика показывает, что может. Например, если сумма a и b в каждой из двух веток не превышает 5, то это не гарантирует того, что совокупные изменения a и b в этих ветках не приведут к большей сумме.

Попробуем воспроизвести это на примере Bash-скрипта test.sh:

#!/bin/bashget_a() {    printf '%d\n' 1}get_b() {    printf '%d\n' 2}check_limit() {    local -i value="$1"    local -i limit="$2"    if (( value > limit )); then        printf >&2 '%d > %d%s\n' "$value" "$limit"        exit 1    fi}limit=5a=$(get_a)b=$(get_b)sum=$(( a + b ))check_limit "$a" "$limit"check_limit "$b" "$limit"check_limit "$sum" "$limit"printf 'OK\n'

Закоммитим его и создадим пару веток: a и b.
Пусть в первой ветке функция get_a() вернёт 3, а во второй get_b() вернёт 4:

diff --git a/test.sh b/test.shindex f118d07..39d3b53 100644--- a/test.sh+++ b/test.sh@@ -1,7 +1,7 @@ #!/bin/bash get_a() {-    printf '%d\n' 1+    printf '%d\n' 3 } get_b() {git diff main bdiff --git a/test.sh b/test.shindex f118d07..0bd80bb 100644--- a/test.sh+++ b/test.sh@@ -5,7 +5,7 @@ get_a() { }  get_b() {-    printf '%d\n' 2+    printf '%d\n' 4 }  check_limit() {

В обоих случаях сумма не превышает 5 и наш тест проходит успешно:

git checkout a && bash test.shSwitched to branch 'a'OKgit checkout b && bash test.shSwitched to branch 'b'OK

Но после слияния main с ветками тесты перестают проходить, несмотря на отсутствие явных конфликтов:

git merge a bFast-forwarding to: aTrying simple merge with bSimple merge did not work, trying automatic merge.Auto-merging test.shMerge made by the 'octopus' strategy. test.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)bash test.sh7 > 5

Было бы проще, если бы вместо get_a() и get_b() использовались присваивания: a=1; b=2, заметит внимательный читатель и будет прав. Да, так было бы проще. Но, вероятно, именно поэтому встроенный алгоритм автомерджа Git успешно обнаружил бы конфликтную ситуацию (что не позволило бы продемонстрировать проблему логического конфликта):

git merge a Updating 4d4f90e..8b55df0Fast-forward test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)git merge b Auto-merging test.shCONFLICT (content): Merge conflict in test.shRecorded preimage for 'test.sh'Automatic merge failed; fix conflicts and then commit the result.

Разумеется, на практике конфликты бывают менее явными. Например, разные ветки могут полагаться на API разных версий какой-нибудь библиотеки зависимости, притом что более новая версия не поддерживает обратной совместимости. Без глубоких знаний кодовой базы (читай: без разработчиков проекта) обойтись вряд ли получится. Но ведь CI как раз и нужен для решения таких проблем.

Конечно, от разрешения конфликта мы никуда не уйдём кто-то должен внести правки. Но чем раньше нам удастся обнаружить проблему, тем меньше людей будет привлечено к её решению. В идеале потребуется озадачить лишь разработчика одной из конфликтующих веток. Если таких веток две, то одна из них вполне может быть соединена с main.

Превентивные меры

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

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

Вторая версия: последовательная стратегия

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

Git, по идее, как раз и является средством синхронизации. Но порядок попадания веток в main и, наоборот, main в ветки определяем мы сами. Чтобы определить точно, какие из веток вызывают проблемы в main, можно попробовать отправлять их туда по одной. Тогда можно выстроить их в очередь, а порядок организовать на основе времени попадания тикета в статус To Merge в стиле первый пришёл первым обслужен.

С порядком определились. А как дальше соединять ветки? Допустим, мы сольём в main первый тикет из очереди. Так как main изменилась, она может конфликтовать с остальными тикетами в очереди. Поэтому перед тем как сливать следующий тикет, нужно удостовериться, что обновлённая main по-прежнему совместима с ним. Для этого достаточно слить main в тикет. Но так как после соединения main с веткой её состояние отличается от того, которое было в сборке, необходимо перезапустить сборку. Для сохранения порядка все остальные тикеты в очереди должны ждать завершения сборки и обработки впередистоящих тикетов. Примерно такие рассуждения привели нас к последовательной стратегии автомерджа.

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

Но есть у этой схемы существенный недостаток: пропускная способность автомерджа линейно зависит от времени сборки. При среднем времени сборки iOS-приложения в 25 минут мы можем рассчитывать на прохождение максимум 57 тикетов в сутки. В случае же с Android-приложением требуется примерно 45 минут, что ограничивает автомердж 32 тикетами в сутки, а это даже меньше количества Android-разработчиков в нашей компании.

На практике время ожидания тикета в статусе To Merge составляло в среднем 2 часа 40 минут со всплесками, доходящими до 10 часов! Необходимость оптимизации стала очевидной. Нужно было увеличить скорость слияний, сохранив при этом стабильность последовательной стратегии.

Финальная версия: сочетание последовательной и жадной стратегий

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

Давайте вспомним идею жадной стратегии: мы сливали все ветки готовых тикетов в main. Основной проблемой было отсутствие синхронизации между ветками. Решив её, мы получим быстрый и надёжный автомердж!

Раз нужно оценить общий вклад всех тикетов в статусе To Merge в main, то почему бы не слить все ветки в некоторую промежуточную ветку Main Candidate (MC) и не запустить сборку на ней? Если сборка окажется успешной, то можно смело сливать MC в main. В противном случае придётся исключать часть тикетов из MC и запускать сборку заново.

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

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

Следуя этому алгоритму, для k проблемных тикетов в худшем случае нам придётся выполнить O(k*log2(n)) сборок, прежде чем мы обработаем все проблемные тикеты и получим удачную сборку на оставшихся.

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

Итак, у нас есть две автономные модели автомерджа: последовательная (назовём её Sequential Merge, или SM) и жадная (назовём её Greedy Merge, или GM). Чтобы получить пользу от обеих, нужно дать им возможность работать параллельно. А параллельные процессы требуют синхронизации, которой можно добиться либо средствами межпроцессного взаимодействия, либо неблокирующей синхронизацией, либо сочетанием этих двух методов. Во всяком случае, мне другие методы неизвестны.

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

Остаётся предотвратить все возможные случаи состояний гонки. Их много, но для понимания сути приведу несколько самых важных:

  1. SM-SM и GM-GM: между командами одного типа.

  2. SM-GM: между SM и GM в рамках одного репозитория.

Первая проблема легко решается при помощи мьютекса по токену, включающему в себя имя команды и название репозитория. Пример: lock_${command}_${repository}.

Поясню, в чём заключается сложность второго случая. Если SM и GM будут действовать несогласованно, то может случиться так, что SM соединит main с первым тикетом из очереди, а GM этого тикета не заметит, то есть соберёт все остальные тикеты без учёта первого. Например, если SM переведёт тикет в статус In Master, а GM будет всегда выбирать тикеты по статусу To Merge, то GM может никогда не обработать тикета, соединённого SM. При этом тот самый первый тикет может конфликтовать как минимум с одним из других.

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

Таким образом, мы получили своего рода неблокирующую синхронизацию.

Немного о TeamCity

В процессе реализации GM нам предстояло обработать много нюансов, которыми я не хочу перегружать статью. Но один из них заслуживает внимания. В ходе разработки я столкнулся с проблемой зацикливания команды GM: процесс постоянно пересобирал ветку MC и создавал новую сборку в TeamCity. Проблема оказалась в том, что TeamCity не успел скачать обновления репозитория, в которых была ветка MC, созданная процессом GM несколько секунд назад. К слову, интервал обновления репозитория в TeamCity у нас составляет примерно 30 секунд.

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

Кто-то посчитает решение очевидным, но я нашёл его не сразу. Оказывается, прикрепить ревизию к сборке при её добавлении в очередь можно при помощи параметра lastChanges метода addBuildToQueue:

<lastChanges>  <change    locator="version:{{revision}},buildType:(id:{{build_type}})"/></lastChanges>

В этом примере {{revision}} заменяется на 16-ричную последовательность коммита, а {{build_type}} на идентификатор конфигурации сборки. Но этого недостаточно, так как TeamCity, не имея информации о новом коммите, может отказать нам в запросе.

Для того чтобы новый коммит дошёл до TeamCity, нужно либо подождать примерно столько, сколько указано в настройках конфигурации корня VCS, либо попросить TeamCity проверить наличие изменений в репозитории (Pending Changes) при помощи метода requestPendingChangesCheck, а затем подождать, пока TeamCity скачает изменения, содержащие наш коммит. Проверка такого рода выполняется посредством метода getChange, где в changeLocator нужно передать как минимум сам коммит в качестве параметра локатора version. Кстати, на момент написания статьи (и кода) на странице ChangeLocator в официальной документации описание параметра version отсутствовало. Быть может, поэтому я не сразу узнал о его существовании и о том, что это 40-символьный 16-ричный хеш коммита.

Псевдокод:

teamCity.requestPendingChanges(buildType)attempt = 1while attempt <= 20:  response = teamCity.getChange(commit, buildType)  if response.commit == commit:    return True # Дождались  sleep(10)return False

О предельно высокой скорости слияний

У жадной стратегии есть недостаток на поиск ветки с ошибкой может потребоваться много времени. Например, 6 сборок для 20 тикетов у нас может занять около трёх часов. Можно ли устранить этот недостаток?

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

Согласно жадной стратегии, мы пробуем собрать сразу все 10 тикетов, что приводит к падению сборки. Далее собираем левую половину (с 1 по 5) успешно, так как тикет с ошибкой остался в правой половине.

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

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

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

Примерно такой же алгоритм реализован в премиум-функции GitLab под названием Merge Trains. Перевода этого названия на русский язык я не нашёл, поэтому назову его Поезда слияний. Поезд представляет собой очередь запросов на слияние с основной веткой (merge requests). Для каждого такого запроса выполняется слияние изменений ветки самого запроса с изменениями всех запросов, расположенных перед ним (то есть запросов, добавленных в поезд ранее). Например, для трёх запросов на слияние A, B и С GitLab создаёт следующие сборки:

  1. Изменения из А, соединённые с основной веткой.

  2. Изменения из A и B, соединённые с основной веткой.

  3. Изменения из A, B и C, соединённые с основной веткой.

Если сборка падает, то соответствующий запрос из очереди удаляется, а сборки всех предыдущих запросов перезапускаются (без учёта удалённого запроса).

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

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

Но если преград человеческой мысли нет, то пределы аппаратных ресурсов видны достаточно отчётливо:

  1. Каждой сборке нужен свой агент в TeamCity.

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

  3. Сборки автомерджа мобильных приложений в main составляют лишь малую часть от общего количества сборок в TeamCity.

Взвесив все плюсы и минусы, мы решили пока остановиться на алгоритме SM + GM. При текущей скорости роста очереди тикетов алгоритм показывает хорошие результаты. Если в будущем заметим возможные проблемы с пропускной способностью, то, вероятно, пойдём в сторону Merge Trains и добавим пару параллельных сборок GM:

  1. Вся очередь.

  2. Левая половина очереди.

  3. Левая четверть очереди.

Что в итоге получилось

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

  • уменьшение среднего размера очереди в 2-3 раза;

  • уменьшение среднего времени ожидания в 4-5 раз;

  • мердж порядка 50 веток в день в каждом из упомянутых проектов;

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

Примеры графиков слияний за несколько дней:

Количество тикетов в очереди до и после внедрения нового алгоритма:

Среднее количество тикетов в очереди (AVG) уменьшилось в 2,5 раза (3,95/1,55).

Время ожидания тикетов в минутах:

Среднее время ожидания (AVG) уменьшилось в 4,4 раза (155,5/35,07).

Подробнее..

Тренды тестирования 2020-2021 правда и мифы

15.06.2021 16:19:06 | Автор: admin

Всем привет! Недавно я наткнулся на World Quality Report (ссылку поставил в конце, чтобы не пугать вас сразу отчетом на 50 страниц) большой обзор трендов в тестировании 2020-2021 годов. А поскольку мы в Qameta Software сами постоянно сталкиваемся с командами тестирования, которые стараются как-то поправить свои процессы и наладить работу тестирования, я решил оценить, насколько они актуальны в России.

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

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

1. DevOps и Agile приходит в тестирование

Правда.

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

Именно поэтому в последнее время инструменты для автоматизации тестирования находятся на подъеме: Cypress, Playwright, Pupperteer и многие другие. Тестировщикам тоже приходится меняться и учиться подходам разработчиков: автоматизации, масштабированию и работе по спринтам.

2. Искусственный интеллект и машинное обучение

Пока нет.

Мы ждем прихода ИИ уже давно. Что же, все еще ждем теперь в управлении качеством. Появляются стартапы, фокус которых лежит в области подготовки, генерации и анализа тестовых данных или тестирования сервисов с помощью ИИ. Не верите? Посмотрите на Applitools, Functionize, Synthesized, TestIM или ReportPortal.

Под удар в первую очередь попадает ручное тестирование 90% участников опроса ищут ИИ-решения, ориентированные именно на замену ручного труда в тестировании. Пока мы такого тренда не наблюдаем, вполне вероятно, он придет к нам позже.

3. Оптимизация бюджетов на тестирование

Похоже на правду.

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

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

4. Фокус на автоматизацию тестирования

Правда.

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

Этот тренд подтверждается нашими наблюдениями все больше пользователей Allure Report двигаются в сторону автоматизации и инструментов, которые позволяют эту автоматизацию аккуратно вплести в процессы (это мы видим по спросу на TestOps).

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

5. Управление тестовыми окружениями (ТО) и тестовыми данными (ТД)

Похоже на правду.

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

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

6. COVID-19 помог компаниям трансформироваться

Неправда.

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

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

Вместо итогов

В общем, World Quality Report получился скорее интересный, чем нет. Да, где-то есть призывы в духе "нормально делай нормально будет", но в целом рекомендую к ознакомлению. В отчете несколько десятков страниц, из которых я постарался сделать выжимку, так что каждый при внимательном изучении сможет найти интересные детали.

Остается только спросить, какие тренды ощущаете вы? Что происходит в вашем тестировании, и насколько все это совпадает с наблюдениями из отчета.

Подробнее..

Программируемое реле easyE4

18.06.2021 20:15:41 | Автор: admin

Предисловие

В свое время наткнулся на очень хорошую статью о програмируемых реле EASY компании Eaton (Moeller). Если кратко, то можно выделить 3 больших серии реле:

  • easy500 - реле 8 входов, 4 выхода, без возможности расширения;

  • easy700 - реле 12 входов, 6/8 выходов, с возможностью подключения одного модуля расширения или коммуникации. Максимальное число точек - 40 (базовый модуль 12+8, модуль расширения 12+8).

  • easy800 - более продвинутая версия 700-й серии, с увеличенным размером памяти, со встроенным протоколом коммуникации easyNet, позволяющим подключить до 8 устройств в одну сеть.

Программируемые реле серий EASY800, EASY500 и EASY700Программируемые реле серий EASY800, EASY500 и EASY700

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


Новинка easyE4

В 2019 году у Eaton вышла обновленная линейка программируемых реле - easyE4. Компания в новой линейке изменила философию продукта. Теперь есть базовый модуль, по размерам как реле серии easy500, имеющий 8 входов и 4 выхода, к которому можно подключить до 11 модулей расширения. При этом функционал базового модуля сопоставим как минимум со "старой" серией easy800.

Новая серия easyE4 и EASY512, подключенные к питающему напряжениюНовая серия easyE4 и EASY512, подключенные к питающему напряжению

По питающему напряжению можно выделить 3 вида:

  • DC - 24VDC

  • UC - 12VDC, 24VDC, 24VAC

  • AC - 100_240VAC, 100_240VDC

Есть еще версии без экрана, итого максимальное количество различных базовых модулей всего 6. В каталогах встречаются модели с push-in клеммами, но они еще только планируются к производству. С данными моделями количество базовых модулей вырастет до 12.

Разберем структуру наименования базовых модулей. Маска наименования следующая:
EASY-E4-[VV]-12[W]C(X)1(P), где:

  • [VV] - тип питающего напряжения - DC, UC, AC;

  • [W] - тип выходов - релейные (R) или транзисторные (T);

  • (Х) - отсутствие дисплея (если дисплей есть, символ не ставится);

  • (P) - push-in клеммы (модель еще не выпущена); если символа нет - клеммы винтовые.

    Примеры:
    EASY-E4-DC-12TC1 - напряжение питания 24 Вольт постоянного тока, транзисторные выходы, с дисплеем (артикул производителя 197213).
    EASY-E4-UC-12RCX1P - напряжение питания 12/24 Вольта, релейные выходы, без дисплея, push-in клеммы (артикул производителя 197505).

Структура наименования моделей расширения. Маска наименования: EASY-E4-[VV]-[AA][B]E1(P), где:

  • [VV] - напряжение питания модуля;

  • [AA] - общее количество точек (входов-выходов);

  • [B] - тип точек (R - релейные, T - транзисторные, A - аналоговые, P- температурные);

  • (P) - push-in клеммы.

    Примеры:
    EASY-E4-AC-16RE1 - напряжение питания 100-240 Вольт, общее количество входов и выходов 16, тип выходов - релейные (артикул производителя 197222).
    EASY-E4-DC-4PE1 - напряжение питания 24 Вольт постоянного тока, 4 температурных входа (артикул производителя 197224).


"Железо"

Рассмотрим базовый модуль на примере EASY-E4-UC-12RC1 (артикул 197211).
Он содержит 8 цифровых (дискретных) входов, (в версиях DC и UC входы I5-I8 могут быть аналоговыми по напряжению - функционал выбирается в ПО для программирования). Есть 4 выхода - релейные (релейные для версий AC, UC, транзисторные для DC). Шестистрочный экран с подсветкой, которая может быть белой, красной, зеленой. Слева от дисплея находится слот под карту microSD, с помощью которой можно обновить прошивку устройства, организовать логгирование, или же сохранить свой загрузочный логотип.

easyE4easyE4

В правой части находится порт для подключения модулей расширения (закрыт заглушкой).
Для коммуникации имеется порт RJ-45 с интерфейсом Ethernet. Доступные протоколы - Modbus TCP и EasyNet. Используя данный порт, можно зайти на встроенный web-сервер (с собственным API), отправиль сообщения на e-mail, также реле может синхронизировать часы реального времени с серверами в интернете - удобная вещь в случае использования функционала, завязанного на астрономические таймеры.

Web-сервер easyE4 с отображением текущего состояния входовWeb-сервер easyE4 с отображением текущего состояния входов

Модули расширения подключаются при помощи комплектного соединителя, который идет с каждым модулем.

Модули расширения с комплектными соединителямиМодули расширения с комплектными соединителями

Всего к одному базовому устройству можно подключить до 11 модулей расширения. Т.к. каждый модуль имеет свои клеммы питания, в одной системе можно использовать различные типы модулей расширения - например, базовое устройство EASY-E4-UC-12RC1, модуль расширения аналоговых входов EASY-E4-DC-6AE1 и модуль 4DI+4RO EASY-E4-AC-8RE1 прекрасно будут работать в одной сборке, позволяя подключить разнообразные устройства ко входам и выходам.

Артикулы 197211+197222+197223Артикулы 197211+197222+197223

Программное обеспечение

Если вы работали со "старыми" easy500/700/800 (в коллективе называем эти реле "изиками"), то средой программирования было ПО EasySoft 6. А для подключения реле к компьютеру использовался специальный кабель, который стоит немало.

EASY800 и EASY700 с кабелями программированияEASY800 и EASY700 с кабелями программирования

Можно, конечно, попытаться вбить программу руками через кнопки, но если проект сложнее чего-то базового "по сигналу I1 включить выход Q1", то ручной ввод программы будет настоящим мучением.

Для новых программируемых реле easyE4 используется ПО EasySoft 7. Приемы работы в нем схожи с работой в 6 версии, поэтому переход на новое ПО будет легким.

EasySoft 7EasySoft 7

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

Конвертация "старого" проекта *.e60 Конвертация "старого" проекта *.e60

Демонстрационную версию можно скачать с сайта Eaton. Демо-версия позволяет создать и отдадить проект, но без возможности записать проект в само реле. Для этого необходима лицензия. Сама лицензия стоит денег, но можно схитрить - есть специальные стартовые наборы, состоящие из самого реле, шнурка для программирования (обычный патч-корд, подойдет абслютно любой RJ-45 прямого обжима) и флаера с кодом, который нужно зарегистрировать и получить ключ активации ПО. Набор такой стоит немного дороже самого базового модуля. Так что если нужно несколько базовых модулей - вместо одного покупаете набор, и получаете лицензионное ПО со скидкой:) Особенностью ПО является то, что код с флаера можно зарегистрировать несколько раз, тем самым получив коды активации для нескольких компьютеров. Артикулы для поиска - 197227/197228/197229.

Стартовый набор 197227Стартовый набор 197227

К программному обеспечению хорошо изучить руководство по эксплуатации. Его код MN050009 (англоязычная версия). Загуглив код "MN050009_RU", находится русскоязычное руководство, которого, почему-то, нету на сайте производителя.
Дополнительно посоветую обучающий курс по программированию для старых изиков "EASY Это просто" от Одесского политеха (авторы О.А. Андрюшенко, В.А. Водичев) - там основы основ.


Пример применения

Один из практически реализованных мной простых проектов - управление освещением во дворе. Есть 2 группы опор освещения - основное (КМ2) и дежурное (КМ1). В автоматическом режиме после заката (по астрономическому таймеру) включается всё освещение, после 22.00 остается только дежурное, которое работает до полуночи в будние дни и чуть позже в выходные. Данные временнные отрезки установлены в недельных таймерах.

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

Электрическая схема подключенияЭлектрическая схема подключения

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

Настройки астрономического таймераНастройки астрономического таймера
Подробнее..

Категории

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

  • Имя: Макс
    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