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

Excel

Перевод Созданные с помощью библиотеки .NET документы Excel обходят проверки безопасности

15.04.2021 18:16:38 | Автор: admin

Pedro Tavares

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


Описание угрозы

Семейство вредоносного ПО "работает" с июня 2020 года и атакует организации из разных стран с применением фишинговых сообщений электронной почты, содержащих изменённый файл Excel. Чтобы фишинговые сообщения не попадали в папки со спамом и против них не срабатывали механизмы отсечения нежелательной почты, злоумышленники отправляют свои письма с официальных учётных записей организаций. Учётные данные таких организаций, как правило, попадают в руки злоумышленников в результате взлома. Злоумышленники с помощью сервиса проверки аккаунтов на утечки "Have I Been Pwned?" проверяют, были ли скомпрометированы учётные записи электронной почты, или просто взламывают такие записи до того, как приступить к вредоносной рассылке.

Рис. 1. Пример фишингового электронного письма, рассылаемого вредоносным ПО Epic Manchego.Рис. 1. Пример фишингового электронного письма, рассылаемого вредоносным ПО Epic Manchego.

Согласно данным NVISO, "через VirusTotal было пропущено около 200 вредоносных документов, и был составлен список из 27 стран, ранжированных по количеству отправленных документов. В списке не делалось различие, каким способом загружались такие файлы (возможно, через VPN)".

В ходе исследования выяснилось, что наибольшему риску рассылки вредоносных файлов подвергаются такие страны, как Соединённые Штаты Америки, Чешская Республика, Франция, Германия и Китай.

Рис. 2. Целевые регионы, выявленные в ходе исследования угроз с помощью VirusTotal.Рис. 2. Целевые регионы, выявленные в ходе исследования угроз с помощью VirusTotal.

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

Рис. 3. Другие шаблоны электронных писем, рассылаемых вредоносным ПО Epic Manchego.Рис. 3. Другие шаблоны электронных писем, рассылаемых вредоносным ПО Epic Manchego.

Как работает Epic Manchego

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

Рис. 4. Прямоугольник внутри файла Excel c вредоносной полезной нагрузкой.Рис. 4. Прямоугольник внутри файла Excel c вредоносной полезной нагрузкой.

Вредоносные документы Microsoft Office создаются не через Microsoft Office Excel, а с использованием .NET библиотеки EPPlus. Поскольку такие документы не являются стандартными документами Excel, они могут маскироваться и обходить защитные механизмы.

Документ на рисунке 4 содержит объект drawing1.xml (скруглённый прямоугольник) с именем name="VBASampleRect и создан с использованием исходного кода EPPLUS Wiki (справа), как это показано ниже.

Рис. 5. Код прямоугольника Excel и код прямоугольника EPPlus.Рис. 5. Код прямоугольника Excel и код прямоугольника EPPlus.

Если открыть окно макросов документа, в нём не будет ни одного макроса.

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

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

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

Рис. 7. Пароль необходим только для отображения кода VBA внутри вредоносного кода.Рис. 7. Пароль необходим только для отображения кода VBA внутри вредоносного кода.

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

Рис. 8. Строка DPB вредоносного файла .doc.Рис. 8. Строка DPB вредоносного файла .doc.

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

Согласно результатам исследования NVISO Labs, чтобы загрузить полезную нагрузку в коде VBA, используются либо объекты PowerShell, либо объекты ActiveX, в зависимости от разновидности исходного вредоносного ПО.

Анализ завершающего этапа работы вредоносного ПО

Через вредоносный код VBA на втором этапе с различных интернет-сайтов загружается полезная нагрузка. Каждый исполняемый файл, создаваемый соответствующим вредоносным документом и запускаемый на втором этапе, выступает для конечной полезной нагрузки как носитель вируса (дроппер). После этого на втором этапе также загружается вредоносный файл DLL. Этот компонент DLL формирует дополнительные параметры и полезную нагрузку для третьего этапа, после чего запускает на выполнение конечную полезную нагрузку, которая, как правило, крадёт информацию.

Рис. 9. Действия Epic Manchego на последнем этапе.Рис. 9. Действия Epic Manchego на последнем этапе.

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

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

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

Чаще всего (более чем в 50% случаев) на компьютер жертвы устанавливается вредоносная программа AZORult, похищающая личные данные пользователей, программы для кражи информации называются инфостилерами. В качестве других полезных нагрузок могут использоваться трояны AgentTesla, Formbook, Matiex и njRat, причем Azorult и njRAT имеют высокий уровень повторного использования.

Рис. 10. Классификация полезной нагрузки на основе словаря и (повторное) использование ПО с усечёнными хэшами.Рис. 10. Классификация полезной нагрузки на основе словаря и (повторное) использование ПО с усечёнными хэшами.

Обнаружение и действия

Для запуска вредоносных программ злоумышленники придумывают новые методы обхода систем обнаружения угроз и реакции на конечных точках (EDR) и антивирусных программ (AV). При использовании нового способа создания вредоносных документов Office механизмы обнаружения угроз не должны позволять вредоносному ПО переходить на следующий этап. Часто на этом этапе запускается скрипт PowerShell, который может выполняться в памяти без обращения к диску.

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

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

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

  • Использовать решения защиты конечных точек (Endpoint Protection) и обновлённое антивирусное ПО для предотвращения заражения вредоносными программами.

  • Использовать системы управления уязвимостями и мониторинга для выявления потенциальных неустранённых уязвимостей и инцидентов в режиме реального времени.

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

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

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

Другие профессии и курсы

Источники:

Подробнее..

Пиксели, Excel, Kotlin и немного ностальгии

13.02.2021 14:22:01 | Автор: admin

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

Предисловие

На дворе стоял 2000-й год. Кризис 98 года остался позади. Я учился в 8 классе местной школы, в небольшом городке. С началом учебного года всех ждало небольшое событие - ввели урок информатики. Многие отнеслись к этому, как к еще одному предмету который надо учить, но были и те, у кого загорелись глаза. В числе последних оказался и я.

Надо отметить, что информатику хоть и ввели, но "ввести новые компьютеры" забыли, потому что денег на эти цели не было. На службе у нашей школы тогда стояли машины made in USSR - "Электроника МС 0511" и несколько их чуть более современных аналогов. Работали они только по им самим ведомым законам, или после прихода некоего "Николая Владимировича" - местного мастера.

фото с сайта - red-innovations.suфото с сайта - red-innovations.su

Вести предмет как водится поставили молодого и "горячего" преподавателя - девушку 26 лет, которая кстати очень старалась. Мы учили системы счисления и переводили письменно числа из одной в другую. Читали про общее устройство ПК и конечно был Бейсик. У каждого тетрадка была в прочной прозрачной обложке, сзади которой была нарисована система координат. Это был своего рода холст для эскизов фигур, которые мы потом старательно переносили в код.

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

Рисуем первое изображение

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

fun drawPixel(    x:Int, y:Int, red:Int, green:Int, blue: Int,    image: BufferedImage) {    image.setRGB(x, y, Color(red,green,blue).rgb)}

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

fun drawRandImage(     image: BufferedImage, stepSize: Int = 1,      redRng: Int = 255, greenRng: Int = 255, blueRng: Int = 255) {     for(posX in 0 until image.width step stepSize){         for (posY in 0 until image.height step stepSize) {            val r = if (redRng <= 0) 0 else Random.nextInt(0, redRng)             val g = if (greenRng <= 0) 0 else Random.nextInt(0, greenRng)            val b = if (blueRng <= 0) 0 else Random.nextInt(0, blueRng)             drawPixel(posX, posY, r, g, b, image)         }      }}

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

рандомное изображение 1.) step 3, RGB (11, 238, 229) 2.) step 2, RGB (181, 19, 227)рандомное изображение 1.) step 3, RGB (11, 238, 229) 2.) step 2, RGB (181, 19, 227)

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

fun writeImage(img: BufferedImage, file: String) {    val imgthread = Thread(Runnable {        ImageIO.write(img, File(file).extension, File(file))    })    try {        imgthread.start()    } catch (ex: Exception) {        ex.printStackTrace()        imgthread.interrupt()    }}

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

Пиксельное сердце

Координаты для отрисовки решил сделать в виде двумерного списка ArrayList<List<Int>>. Получить "пиксельный" эффект мне помогла функция drawTitle, которая "дергает" в цикле drawPixel, рисуя "big pixel" в виде плитки.

fun drawTile(    startX: Int, startY: Int, size: Int,     red: Int, green: Int, blue: Int, image: BufferedImage) {    for (posX in startX until startX+size) {        for (posY in startY until startY+size) {            drawPixel(posX,posY,red,green,blue,image)        }    }}

Настала очередь обработать массив с числами. Сказано-сделано. Добавив с помощью оператора when обработку 4 цветов

fun drawImage(pixels: ArrayList<List<Int>>, image: BufferedImage) {    pixels.forEachIndexed { posY, row ->        row.forEachIndexed { posX, col ->            when(col) {                1 -> drawTile(posX*10,posY*10,10,255,2,0,image)                2 -> drawTile(posX*10,posY*10,10,156,25,31,image)                3 -> drawTile(posX*10,posY*10,10,255,255,255,image)                else -> drawTile(posX*10,posY*10,10,23,0,44,image)            }        }    }}

и создав список в виде двумерного массива, где каждая цифра соответствует своему цвету (1 = красный, 2 = темно-красный, 3 = белый, 4 = фиолетовый)

val map = arrayListOf(    listOf(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),    listOf(0,0,0,1,1,1,0,0,0,1,2,2,0,0,0),    listOf(0,0,1,3,3,1,1,0,1,1,1,2,2,0,0),    listOf(0,1,3,3,1,1,1,1,1,1,1,1,2,2,0),    listOf(0,1,3,1,1,1,1,1,1,1,1,1,2,2,0),    listOf(0,1,1,1,1,1,1,1,1,1,1,1,2,2,0),    listOf(0,1,1,1,1,1,1,1,1,1,1,1,2,2,0),    listOf(0,0,1,1,1,1,1,1,1,1,1,2,2,0,0),    listOf(0,0,0,1,1,1,1,1,1,1,2,2,0,0,0),    listOf(0,0,0,0,1,1,1,1,1,2,2,0,0,0,0),    listOf(0,0,0,0,0,1,1,1,2,2,0,0,0,0,0),    listOf(0,0,0,0,0,0,1,2,2,0,0,0,0,0,0),    listOf(0,0,0,0,0,0,0,2,0,0,0,0,0,0,0),    listOf(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0),)

...на выходе получил такую красоту. Мой внутренний "школьник" был очень доволен.

pixel heartpixel heart

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

Excel как холст

Следующим вечером я продолжил. Сперва подумал о JS (Resct JS), но тут нужно было переписывать все полностью на нем, да и JavaScript я пробовал слишком давно. Хотелось взять что-то простое

По работе часто приходится работать с таблицами, поэтому само собой выбор остановился на Excel. Привел строки столбцы к виду квадратной сетки и вуаля - наш холст готов к работе с цифровыми красками. Осталось лишь только получить данные из ячеек. "Цифровая бумага все стерпит" - подумал я, и взял Apache POI - библиотеку для работы файлами word, excel, pdf. Документация у нее написана хорошо, но некоторые примеры кода там явно требуют корректировки.

Для начала набросал простую лямбду для преобразования hex в rgba, которая отдает стандартный джавовский класс Color.

val toRGBA = { hex: String ->    val red = hex.toLong(16) and 0xff0000 shr 16    val green = hex.toLong(16) and 0xff00 shr 8    val blue = hex.toLong(16) and 0xff    val alpha = hex.toLong(16) and 0xff000000 shr 24    Color(red.toInt(),green.toInt(),blue.toInt(),alpha.toInt())}

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

fun getPixelColors(file: String, listName: String): ArrayList<List<String>> {    val table = FileInputStream(file)    val sheet = WorkbookFactory.create(table).getSheet(listName)    val rowIterator: Iterator<Row> = sheet.iterator()    val rowArray: ArrayList<Int> = ArrayList()    val cellArray: ArrayList<Int> = ArrayList()    while (rowIterator.hasNext()) {        val row: Row = rowIterator.next()        rowArray.add(row.rowNum)        val cellIterator = row.cellIterator()        while (cellIterator.hasNext()) {            val cell = cellIterator.next()            cellArray.add(cell.address.column)        }    }    val rowSize = rowArray.maxOf { el->el }    //...проходим по листу     //...и формируем массив    return pixelMatrix}

Функция немаленькая и всю ее приводить я не буду (ссылка на код в конце статьи). Конечно, ее можно сократить, но ради читаемости я оставил все как есть. И тут хотелось бы остановиться на одном моменте.

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

val rows = sheet.lastRowNumval cells = sheet.getRow(rows).lastCellNum // + rowsval pixArray = Array(rows+1) {Array(ccc+1) {""} }

...то Вы получите ошибку OutOfBounds. Количество строк (row) получается всегда правильным, но количество ячеек порой то меньше, то больше чем нужно. Я так и не понял, почему результат "скачет", причем проявляется это рандомно. Исправить это можно при помощи iterator.hasNext(), который реально возвращает последнюю ячейку.

Редактор пикселей в ExcelРедактор пикселей в Excel

Дело сталось за малым - преобразовать нашу "пиксельную матрицу" в картинку и вернуть в качестве результата BufferedImage. В отличии от начала статьи, тип картинки у нас изменился на - TYPE_INT_ARGB, чтобы не закрашенные ячейки таковыми и оставались.

fun renderImage(pixels: ArrayList<List<String>>): BufferedImage {    val resultImage = BufferedImage(        pixels[0].size*10,        pixels.size*10,        BufferedImage.TYPE_INT_ARGB    )    pixels.forEachIndexed { posY, row ->        row.forEachIndexed { posX, col ->            drawTile(                (posX)*10,(posY)*10, 10,                toRGBA(col).red, toRGBA(col).green,toRGBA(col).blue,                toRGBA(col).alpha, resultImage            )        }    }    return resultImage}

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

отрисованная картина в Excel. за основу взята работа Mockingjay1701отрисованная картина в Excel. за основу взята работа Mockingjay1701

Выводы

Весь код доступен по ссылке на github. Что дальше? В планах добавить поддержку svg, может добавить несколько фильтров (blur, glitch, glow, etc..), переписать все с индусского кода на человеческий, добавить поддержку xls (HSSF Color) и возможно набросать пару тестов. Чего-то больше добавлять не имеет смысла, так как это скорее интересная задача с легким налетом ностальгии, чем какой-то проект.

Послесловие

Конечно, можно было ограничиться лишь "Фотошопом и Экселем" (ctrl+c, ctrl+v), но цель была не просто получить пиксельный "шедевр" в пару кликов. Хотелось вспомнить школьные уроки информатики, ту теплую атмосферу: Бейсик, старые компьютеры, пиксельные рисунки на экране черно-белого монитора "Электроника МС". Да черт побери, в конечном счете это хоть и простая, но интересная задача, потратить на которую пару вечеров просто приятно.

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

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

А с чего начиналась информатика у Вас в школе?

Всем спасибо! Всем пока!

Подробнее..

Старт работы с Excel на C

28.10.2020 20:04:59 | Автор: admin

В современном мире разработки приложений нередко встает необходимость работы с Excel документами. Чаще всего это разного рода отчеты, но иногда xls/x файлы используются в качестве хранилища данных. Например, если пользователь должен иметь возможность загрузить данные в приложение или выгрузить, в человеко-читаемом виде, Excel де-факто является стандартом. Относительно дружелюбный интерфейс, прозрачная структура, в купе с его распространенностью... трудно навскидку назвать решение лучше.

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

Историческая справка

Времена, когда доминировал проприетарный формат .xls(Excel Binary File Format) давно прошли и сейчас мы имеем только .xlsx(Excel Workbook), в рамках Office Open XML. Последний представляет собой обычный .zip архив с XML файлами. Не будем углубляться в его структуру, я искренне надеюсь что вам это никогда не понадобится:)

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

Задача

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

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

Аналитик, в свою очередь, выдает задачу с феноменально точнымописанием - "Сгенерироватьexcel отчет на базе данных MarketReport". Что ж, для нашего примера, создадим заглушку генератор фейковых данных:

Первый запуск

Подключим EPPlus версии 4.5.3.3 и создадим базовую обвязку для будущего генератора.

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

В методе main создается генератор отчетов, а также генератор Excel файлов. Далее полученный файл просто записывается на диск.

При попытке запустить приложение, получаем exception:InvalidOperationException: The workbook must contain at least one worksheet

Все правильно, Excel документ не может существовать без страниц, должна быть хотя бы одна. Добавляем ее, все интуитивно понятно:

var sheet = package.Workbook.Worksheets    .Add("Market Report");

Запускаем снова и... вот оно! Теперь наше приложение генерирует документ и, хотя там еще ничего нет, он уже весит 2,5KB - значит мы работаем с Excel правильно и все идет как надо.

Вывод данных

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

sheet.Cells["B2"].Value = "Company:";sheet.Cells[2, 3].Value = report.Company.Name;
Полный код вывода шапки.
sheet.Cells["B2"].Value = "Company:";sheet.Cells[2, 3].Value = report.Company.Name;sheet.Cells["B3"].Value = "Location:";sheet.Cells["C3"].Value = $"{report.Company.Address}, " +  $"{report.Company.City}, " +                            $"{report.Company.Country}";sheet.Cells["B4"].Value = "Sector:";sheet.Cells["C4"].Value = report.Company.Sector;sheet.Cells["B5"].Value = report.Company.Description;

Для вывода исторических данных понадобится как минимум шапка таблицы и цикл по массиву History:

sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][]{ new []{"Capitalization", "SharePrice", "Date"} });var row = 9;var column = 2;foreach (var item in report.History){  sheet.Cells[row, column].Value = item.Capitalization;  sheet.Cells[row, column + 1].Value = item.SharePrice;  sheet.Cells[row, column + 2].Value = item.Date;      row++;}

Предлагаю обратить внимание на метод LoadFromArrays, который заполняет диапазон ячеек рваным(зубчатым) массивом. Здесь мы можем видеть, что типизация теряется и передавая массив object мы ожидаем что EPPlus в конечном итоге использует ToString, чтобы записать переданное в ячейки.

Стилизация

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

Как это выглядит

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

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

Размер ячеек

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

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

sheet.Cells[1, 1, row, column + 2].AutoFitColumns();sheet.Column(2).Width = 14;sheet.Column(3).Width = 12;

Формат данных

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

sheet.Cells[9, 4, 9 + report.History.Length, 4].Style.Numberformat.Format = "yyyy";sheet.Cells[9, 2, 9 + report.History.Length, 2].Style.Numberformat.Format =  "### ### ### ##0";

Выравнивание

Его можно задать как на ячейке, так и на диапазоне. На самом деле, для EPPlus, это одна и та же сущность некий ExcelRange, описывающий диапазон ячеек, в том числе и со всего 1 ячейкой.

sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;

Стиль текста

Также легко задается, используя Style.Font, кстати, здесь, на 2-й строчке, мы впервые указываем диапазон так, как привыкли его видеть пользователи Excel:

sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true;sheet.Cells["B2:C4"].Style.Font.Bold = true;

Границы

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

sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double);sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;

График

"Ну что за отчет без графиков, верно, Карл?" - ловко подметит специалист по тестированию, и не важно, что этого не было в ТЗ а на часах уже половина 9-го...

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

var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line);capitalizationChart.Title.Text = "Capitalization";capitalizationChart.SetPosition(7, 0, 5, 0);capitalizationChart.SetSize(800, 400);var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"]));capitalizationData.Header = report.Company.Currency;

Еще, может понадобиться защитить страницу от редактирования:

sheet.Protection.IsProtected = true;

Заключение

О чем говорит финальная версия метода Generate?
public byte[] Generate(MarketReport report){      var package = new ExcelPackage();        var sheet = package.Workbook.Worksheets            .Add("Market Report");          sheet.Cells["B2"].Value = "Company:";      sheet.Cells[2, 3].Value = report.Company.Name;      sheet.Cells["B3"].Value = "Location:";      sheet.Cells["C3"].Value = $"{report.Company.Address}, " +    $"{report.Company.City}, " +                                 $"{report.Company.Country}";      sheet.Cells["B4"].Value = "Sector:";      sheet.Cells["C4"].Value = report.Company.Sector;      sheet.Cells["B5"].Value = report.Company.Description;        sheet.Cells[8, 2, 8, 4].LoadFromArrays(new object[][]{ new []{"Capitalization", "SharePrice", "Date"} });      var row = 9;      var column = 2;      foreach (var item in report.History)      {            sheet.Cells[row, column].Value = item.Capitalization;           sheet.Cells[row, column + 1].Value = item.SharePrice;           sheet.Cells[row, column + 2].Value = item.Date;            row++;      }        sheet.Cells[1, 1, row, column + 2].AutoFitColumns();      sheet.Column(2).Width = 14;      sheet.Column(3).Width = 12;            sheet.Cells[9, 4, 9+ report.History.Length, 4].Style.Numberformat.Format = "yyyy";      sheet.Cells[9, 2, 9+ report.History.Length, 2].Style.Numberformat.Format =  "### ### ### ##0";        sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;      sheet.Cells[8, 3, 8 + report.History.Length, 3].Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;      sheet.Column(4).Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;        sheet.Cells[8, 2, 8, 4].Style.Font.Bold = true;      sheet.Cells["B2:C4"].Style.Font.Bold = true;    sheet.Cells[8, 2, 8 + report.History.Length, 4].Style.Border.BorderAround(ExcelBorderStyle.Double);      sheet.Cells[8, 2, 8, 4].Style.Border.Bottom.Style = ExcelBorderStyle.Thin;         var capitalizationChart = sheet.Drawings.AddChart("FindingsChart", OfficeOpenXml.Drawing.Chart.eChartType.Line);      capitalizationChart.Title.Text = "Capitalization";      capitalizationChart.SetPosition(7, 0, 5, 0);      capitalizationChart.SetSize(800, 400);      var capitalizationData = (ExcelChartSerie)(capitalizationChart.Series.Add(sheet.Cells["B9:B28"], sheet.Cells["D9:D28"]));      capitalizationData.Header = report.Company.Currency;           sheet.Protection.IsProtected = true;        return package.GetAsByteArray();}

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

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

Подробнее..
Категории: Программирование , C , Net , Excel , Epplus

Функция количества високосных дней в периоде

25.01.2021 12:19:59 | Автор: admin

Предыстория

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

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

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

Причина 1

Большинство стран живут по Григорианскому календарю, правила високосных лет для которого были определены ещё 1582 году римским папой Григорием XIII:

1. Год, номер которого кратен 400, - високосный;

2. Остальные годы, номер которых кратен 100, - невисокосные (например, годы 1700, 1800, 1900, 2100, 2200,2300)

3. Остальные годы, номер которых кратен 4, - високосные.

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

Причина 2

Пользовательская функция в Excel создаётся на языке VBA (Visual Basic for Applications). Несмотря на то, что интерпретатор данного языка встроен в MS Office, я обнаружил некоторые отличия в работе с датами.

Excel поддерживает две системы дат, так называемые системы 1900 и 1904. По умолчанию используется система 1900. Это означает, что число 1 введённое в ячейку соответствует 01 января 1900 года, 2 2 января и так далее.

В VBA есть функция CDate(expression), которая приводит к типу Date введённое значение. И если этой функции передать число 1, то она вернёт переменную типа Date с датой 31 декабря 1899 года. А вот для числа 60 функция CDate вернёт 28.02.1900, а то же значение введённое в ячейку будет отображать 29.02.1900 (хотя, конечно, 1900 год високосным не является). Далее, начиная с 01 марта 1900 года значения дат выравниваются.

Такое поведение Excel, давно известно компании Microsoft и было принято решение оставить как есть, нужно просто учитывать его. Поэтому и появилось второе ограничение минимальной даты периода 01 марта 1900 года.

Алгоритм решения

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

Так как все делители, с помощью которых мы можем определить високосность года кратны 4, то я решил разбить все годы на блоки по 4 (квартеты) начиная с 1 года. То есть 1-й блок начинается с 1 года и заканчивается 4, 2-й блок с 5 по 8 и так далее.

В каждом блоке год будет иметь свой индекс от 1 до 4 (например, 2021 год это 506-й блок, индекс в блоке 1)

Теперь мы можем разделить вычисление на 3 блока:

Високосных^{всего}_{дней} = В^{от начальной даты}_{дней до конца квартета} + В^{в промеж. квартетах}_{дней} + В^{с начала последнего квартера}_{до дня окончания}

В зависимости от года параметров и индексов квартетов формула расчёта количества дней високосного года будет следующей:

Если год начальной и конечной даты равны и год високосный:

В^{от начальной даты}_{до конца квартета}=Дата_{кон} - Дата_{нач}

Если год начальной даты невисокосный, а конечной високосный и индексы квартета начальной даты и конечной равны, то:

В^{от начальной даты}_{до конца квартета} = Дата_{кон} - 31 дек (Год_{дата кон} -1)

Если год начальной даты високосный, а конечной нет, то:

В^{от начальной даты}_{до конца квартета} = 31 дек Года_{дата нач}- Дата_{нач}

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

Выше указанная логика расчёта количества дней високосных лет для первого квартета реализована следующей функцией на VBA:

Функция високосных дней для первого квартета
Private Function first_quartet_leap_year_days(ByVal d_begin As Date, ByVal d_end As Date) As Long    Dim result As Long    result = 0        Dim year_diff As Long    Dim quartet_index_diff As Long        year_diff = year(d_end) - year(d_begin)    quartet_index_diff = quartet_index(year(d_end)) - quartet_index(year(d_begin))        If year_diff = 0 And is_year_leap(d_begin) Then        result = DateDiff("d", d_begin, d_end)        first_quartet_leap_year_days = result        Exit Function    End If        If quartet_index_diff = 0 Then            If is_year_leap(d_begin) Then            result = DateDiff("d", d_begin, CDate(DateSerial(year(d_begin), 12, 31)))            first_quartet_leap_year_days = result            Exit Function                End If                If is_year_leap(d_end) Then            result = DateDiff("d", CDate(DateSerial(year(d_end) - 1, 12, 31)), d_end)            first_quartet_leap_year_days = result            Exit Function        End If            Else            If is_year_leap(d_begin) Then            result = DateDiff("d", d_begin, CDate(DateSerial(year(d_begin), 12, 31)))            first_quartet_leap_year_days = result            Exit Function        Else                    If Not is_quartet_noleap(quartet_index(year(d_begin))) Then                result = 366                first_quartet_leap_year_days = result                Exit Function            End If                    End If            End If    first_quartet_leap_year_days = result    End Function

Если разница индексов квартетов начальной и конечной даты >0, то рассчитывается 3-й блок формулы "Количество високосных дней в последнем квартете".

Здесь формула только одна, где при условии, что год конечной даты високосный:

В^{с начала квартета}_{до даты окончания} = Дата_{кон} - 31 дек (Год_{даты окончания}-1)Функция високосных дней для последнего квартета
Private Function last_quartet_leap_year_days(ByVal d_begin As Date, ByVal d_end As Date) As Long        Dim result As Long    result = 0         Dim quartet_index_diff As Long           quartet_index_diff = quartet_index(year(d_end)) - quartet_index(year(d_begin))        If quartet_index_diff > 0 Then            If is_year_leap(d_end) Then            result = DateDiff("d", CDate(DateSerial(year(d_end) - 1, 12, 31)), d_end)        End If            End If             last_quartet_leap_year_days = result    End Function

Если разница индексов квартетов начальной и конечной даты >1, то рассчитывается 2-й блок формулы "Количество високосных дней в промежуточных квартетах".

В^{в промеж. квартетах}_{дней} = 366*K_{квартетов} - К_{полных 100 летий} + K_{полных 400 летий}

При этом К полных столетий означает разность индексов столетий между датами. Например 1999 индекс столетия 19, а 2001 20, таким образом разность столетий 1.

Аналогично и 400-летий.

Функция для расчёта високосных дней в промежуточных квартетах
Private Function middle_quartets_leap_year_days(ByVal d_begin As Date, ByVal d_end As Date) As Long        Dim quartet_count As Long        quartet_count = middle_quartets_count(d_begin, d_end)        If quartet_count = 0 Then            middle_quartets_leap_year_days = 0        Exit Function            End If        Dim q_begin, q_end As Long        q_begin = quartet_index(year(d_begin))    q_end = quartet_index(year(d_end)) - 1        Dim quot_25, quot_100 As Integer        quot_25 = WorksheetFunction.Quotient(q_end, 25) - WorksheetFunction.Quotient(q_begin, 25)    quot_100 = WorksheetFunction.Quotient(q_end, 100) - WorksheetFunction.Quotient(q_begin, 100)        Dim result As Long        result = (quartet_count - quot_25 + quot_100) * 366        middle_quartets_leap_year_days = result        End Function

Реализация функций

Функция вычисления високосных дней для периода:

Public Function LEAP_DAYS(ByVal val_begin As Long, ByVal val_end As Long, Optional count_first_day = 0, Optional count_last_day = 1) As Long        Dim d_begin, d_end As Date        count_first_day = IIf(count_first_day <> 0, 1, 0)    count_last_day = IIf(count_last_day <> 0, 1, 0)            d_begin = CDate(val_begin)    d_end = CDate(val_end)        Dim check_error As Variant    check_error = check_constrains(d_begin, d_end)        If IsError(check_error) Then        LEAP_DAYS = check_error        Exit Function    End If        Dim result As Long    result = 0        If is_year_leap(d_begin) And count_first_day = 1 Then result = result + 1    If is_year_leap(d_end) And count_last_day = 0 Then result = result - 1        result = result + first_quartet_leap_year_days(d_begin, d_end) _            + middle_quartets_leap_year_days(d_begin, d_end) _            + last_quartet_leap_year_days(d_begin, d_end)        LEAP_DAYS = result    End Function

В приведённом выше коде мы сначала приводим значения параметров count_first_day и count_last_day к значению 1 или 0. Затем мы объявляем переменные типа Date для даты начала и окончания периода и задаём значения. Далее следует проверка параметров на ограничения.

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

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

Заключение

Таким образом мы получили формулы расчёта количества дней високосных и невисокосных лет в заданном периоде, скорость которой не зависит от количества дней в периоде. Единственный минус это то, что формула способна корректно работать только в рамках одной тысячи лет (2900 - 1900). Думаю, что до 2900 года у нас есть ещё время усовершенствовать такую функцию.

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

Гитхаб

Источники и дополнительные ссылки

  1. Статья из журнала "Главная книга" "Считаем проценты по займу: день первый, день последний"

  2. Excel неправильно предполагает, что 1900 год является високосным годом.

  3. Статья из Википедии "Григорианский календарь"


Подробнее..

А я говорю, возьми Excel и позвони

01.04.2021 14:11:52 | Автор: admin

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

Но в современном мире иметь API недостаточно мало кто хочет формировать HTTP-запросы, передавать параметры, думать про правильную авторизацию. Поэтому мы предлагаем SDK для разных языков программирования: Python, PHP, C# и многих других. И кажется, что этого достаточно, чтобы сделать нашу платформу лёгкой в использовании для очень большой аудитории. Или всё-таки недостаточно?

Обратимся к статистике. По разным данным сейчас в мире насчитывается где-то 15-30 миллионов разработчиков цифра несомненно впечатляющая. Но, например, пользователей MS Excel в мире не менее 100 миллионов. Почему же они должны страдать? Ведь, будем честны, почти каждый из тех, кто хоть раз открывал Excel, явно ощущал недостаток возможностей по управлению коммуникационными платформами в этом без сомнения очень гибком программном продукте. Практически каждый день мы получаем на наш email сотни запросов, которые сводятся к очень простой просьбе: Я хочу звонить из Excel!. Однажды у окон нашего офиса даже выстроились люди с такими требованиями (видели фото выше?) Мы просто не могли оставаться в стороне.

Однако звонки это всё-таки слишком революционно, а главное, потребует установки дополнительных ActiveX-компонентов, что, безусловно, противоречит всем существующим и несуществующим политикам информационной безопасности, поэтому давайте начнём с более простой вещи SDK для работы с нашим API. Из средств разработки в Экселе доступен VBA, для него мы и создадим SDK.

Для того, чтобы выполнить API-запрос, необходимо:

  1. Сформировать URL и тело POST-запроса.

  2. Добавить аутентификационные параметры.

  3. Непосредственно выполнить запрос.

  4. Распарсить результат (в нашем случае это JSON).

Формируем URL и тело POST-запроса

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

Public Function URL_Encode(ByRef txt As String) As String    Dim buffer As String, i As Long, c As Long, n As Long    buffer = String$(Len(txt) * 12, "%")     For i = 1 To Len(txt)        c = AscW(Mid$(txt, i, 1)) And 65535         Select Case c            Case 48 To 57, 65 To 90, 97 To 122, 45, 46, 95  ' Unescaped 0-9A-Za-z-._ '                n = n + 1                Mid$(buffer, n) = ChrW(c)            Case Is <= 127            ' Escaped UTF-8 1 bytes U+0000 to U+007F '                n = n + 3                Mid$(buffer, n - 1) = Right$(Hex$(256 + c), 2)            Case Is <= 2047           ' Escaped UTF-8 2 bytes U+0080 to U+07FF '                n = n + 6                Mid$(buffer, n - 4) = Hex$(192 + (c \ 64))                Mid$(buffer, n - 1) = Hex$(128 + (c Mod 64))            Case 55296 To 57343       ' Escaped UTF-8 4 bytes U+010000 to U+10FFFF '                i = i + 1                c = 65536 + (c Mod 1024) * 1024 + (AscW(Mid$(txt, i, 1)) And 1023)                n = n + 12                Mid$(buffer, n - 10) = Hex$(240 + (c \ 262144))                Mid$(buffer, n - 7) = Hex$(128 + ((c \ 4096) Mod 64))                Mid$(buffer, n - 4) = Hex$(128 + ((c \ 64) Mod 64))                Mid$(buffer, n - 1) = Hex$(128 + (c Mod 64))            Case Else                 ' Escaped UTF-8 3 bytes U+0800 to U+FFFF '                n = n + 9                Mid$(buffer, n - 7) = Hex$(224 + (c \ 4096))                Mid$(buffer, n - 4) = Hex$(128 + ((c \ 64) Mod 64))                Mid$(buffer, n - 1) = Hex$(128 + (c Mod 64))        End Select    Next    URL_Encode = Left$(buffer, n)End Function

Следующий нюанс передача даты и времени. В API Voximplant временные метки принимаются в UTC в формате YYYY-MM-DD hh:mm:ss. В Excel же дата и время хранятся без учёта часового пояса (на самом деле, в самой таблице они вообще хранятся как число с плавающей точкой). Поэтому нам придётся принимать дату/время из таблицы тоже UTC. Мы думаем, что все 100+ миллионов пользователей Excel знают, что такое UTC, и это не вызовет у них никаких вопросов.

Кстати, в VBA есть функция форматирования даты, и она даже работает, но весьма необычным образом. Интересующий нас формат даты описывается так: yyyy-mm-dd hh:mm:ss. То есть mm это либо месяц, либо минуты в зависимости от того, за чем оно следует: за hhили за yyyy (это не шутка, это даже в MSDN описано). В общем, если кто-то захочет вывести время без часов, придётся импровизировать.

Переходим к аутентификации

Здесь нас ожидает самое большое разочарование. Мы в Voximplant предлагаем нашим клиентам использовать JWT, что, конечно, весьма мудрёно, если выполнять запросы из консоли или браузера, но при использовании наших SDK это совершенно никак не усложняет жизнь разработчику. В то же время JWT обеспечивает крайне высокий уровень безопасности.

А что же VBA? К сожалению, разумно простого способа сформировать JWT-подпись просто не существует. Причина в том, что в VBA доступен фреймворк .NET версии 4.x, а функция RSA.ImportPkcs8PrivateKey, необходимая для загрузки приватного ключа из PKCS8, появилась только в .NET 5. Да и вообще, все .NET-разработчики используют для таких задач сторонние библиотеки.

Поэтому нам придётся ограничиться авторизацией с помощью статического API-ключа. Ведь, как известно, один из способов обработки риска информационной безопасности - принятие этого риска. Так и поступим. Ведь нам ну очень-очень нужен этот SDK.

Кадр из кинофильма Большой Лебовски (The Big Lebowski (1998), Polygram Filmed Entertainment, Working Title Films)Кадр из кинофильма Большой Лебовски (The Big Lebowski (1998), Polygram Filmed Entertainment, Working Title Films)

Выполняем запрос

Переходим к третьей части к выполнению самого запроса. Встроенных средств работы с HTTP в VBA нет (теперь понятно, почему нет и функции URL-кодирования, а зачем?).

Но, тем не менее, это достаточно тривиальная манипуляция подключаем необходимый фреймворк MSXML 6.0 и Microsoft Scripting Runtime и выполняем запрос, подключая через COM сам MSXML. Просто!

Function makeRequest(name As String, params As Dictionary, accountId As Integer, apiKey As String) As Object    Dim objHTTP As New MSXML2.XMLHTTP60    Dim jsonData As String    Dim parsedJson As Object    Dim postString As String    postString = ""        Dim iterKey As Variant        For Each iterKey In params.Keys        postString = postString & "&" & iterKey & "=" & URL_Encode(params(iterKey))    Next    Url = "https://api.voximplant.com/platform_api/" + name    objHTTP.Open "POST", Url, False    objHTTP.send "account_id=" & accountId & "&api_key=" & apiKey & postString    jsonData = objHTTP.responseText    Set parsedJson = JsonConverter.ParseJson(jsonData)    Set makeRequest = parsedJsonEnd Function

Парсим JSON

Ну и, наконец, JSON. Как и всё остальное, парсер JSON надо искать где-то вовне экосистемы VBA. К счастью, на дворе 2021 год, есть GitHub, и кто-то уже озадачился созданием JSON-парсера для VBA. Мы взяли вот такой.

Он подключается как отдельный модуль и превращает JSON-строку в Dictionary. То, что нужно!

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

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

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

Для этого пишем вот такую функцию:

Function getTotalCallCost(FromDate, ToDate, Username) As Double    Dim totalCost As Double    Dim lastCount As Integer    Dim offset As Integer    Dim res As Dictionary    Dim RecordsPerRequest As Integer    Dim api As New VoximplantAPI    totalCost = 0    lastCount = 1    offset = 0    RecordsPerRequest = 100        'Pass Voximplant account id and API key    api.SetCredentials 100, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"        Do While lastCount > 0        Set res = api.GetCallHistory(FromDate, ToDate, remote_number:=Username, with_calls:=True, with_records:=True, with_other_resources:=True, offset:=offset, count:=RecordsPerRequest)                Dim session As Variant        Dim item As Variant                For Each session In res("result")            For Each item In session("calls")                totalCost = totalCost + item("cost")            Next            For Each item In session("records")                totalCost = totalCost + item("cost")            Next            For Each item In session("other_resource_usage")                totalCost = totalCost + item("cost")            Next        Next                lastCount = res("count")        offset = offset + RecordsPerRequest    Loop        getTotalCallCost = totalCostEnd Function

И вызываем её следующим образом:

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


Резюме:

При желании можно и для VBA сделать какое-то подобие SDK. При его создании не пострадал ни один разработчик. Ах да, с 1 апреля! :D

Подробнее..

Перевод Бесшовная интеграция Microsoft Excel и Word с помощью Python

22.04.2021 16:18:43 | Автор: admin

Хотя в среднем для каждодневных задач автоматизация не требуется, бывают случаи, когда она может быть необходима.Создание множества диаграмм, рисунков, таблиц и отчётов может утомить, если вы работаете вручную. Так быть не должно.Можно построить конвейер на Python, с помощью которого Excel и Word легко интегрировать: нужно создать таблицы в Excel, а затем перенести результаты в Word, чтобы практически мгновенно получить отчёт.


Openpyxl

Встречайте Openpyxl возможно, одну из самых универсальных связок [биндингов] с Python, которая сделает взаимодействие с Excel очень простым. Вооружившись этой библиотекой, вы сможете читать и записывать все нынешние и устаревшие форматы Excel, то есть xlsx и xls.

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

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

Python-docx

Затем идёт Python-docx, этот пакет для Word то же самое, что Openpyxl для Excel. Если вы ещё не изучили его документацию, вам, вероятно, стоит взглянуть на неё. Python-docx без преувеличения один из самых простых и понятных мне наборов инструментов, с которыми я работал с тех пор, как начал работать с самим Python.

Python-docx позволяет автоматизировать создание документов путём автоматической вставки текста, заполнения таблиц и рендеринга изображений в отчёт без каких-либо накладных расходов. Без лишних слов давайте создадим наш собственный автоматизированный конвейер. Запустите Anaconda (или любую другую IDE по вашему выбору) и установите эти пакеты:

pip install openpyxlpip install python-docx

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

Сначала загрузим уже созданный лист Excel, вот так:

workbook = xl.load_workbook('Book1.xlsx')sheet_1 = workbook['Sheet1']

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

for row in range(2, sheet_1.max_row + 1):    current = sheet_1.cell(row, 2)    voltage = sheet_1.cell(row, 3)    power = float(current.value) * float(voltage.value)    power_cell = sheet_1.cell(row, 1)    power_cell.value = power

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

values = Reference(sheet_1, min_row = 2, max_row = sheet_1.max_row, min_col = 1, max_col = 1)chart = LineChart()chart.y_axis.title = 'Power'chart.x_axis.title = 'Index'chart.add_data(values)sheet_1.add_chart(chart, 'e2') workbook.save('Book1.xlsx')
Автоматически созданная таблица ExcelАвтоматически созданная таблица Excel

Извлечение диаграммы

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

input_file = "C:/Users/.../Book1.xlsx"output_image = "C:/Users/.../chart.png"

Затем откройте электронную таблицу, используя следующий метод:

operation = win32com.client.Dispatch("Excel.Application")operation.Visible = 0operation.DisplayAlerts = 0workbook_2 = operation.Workbooks.Open(input_file)sheet_2 = operation.Sheets(1)

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

for x, chart in enumerate(sheet_2.Shapes):    chart.Copy()    image = ImageGrab.grabclipboard()    image.save(output_image, 'png')    passworkbook_2.Close(True)operation.Quit()

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

Теперь, когда у нас есть сгенерированное изображение диаграммы, мы должны создать шаблон документа, который в принципе является обычным документом Microsoft Word (.docx), сформированным именно так, как мы хотим: отчёт содержит шрифты, размеры шрифтов, структуру и форматирование страниц.

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

Шаблон документа Microsoft WordШаблон документа Microsoft Word

Любой сгенерированный контент, включая текст и изображения, может быть объявлен в двойных фигурных скобках {{ variable_name }}. В случае таблиц вам нужно создать таблицу со строкой шаблона со всеми включёнными столбцами, затем нужно добавить одну строку вверху и одну строку ниже со следующей нотацией:

Первая строка:

{%tr for item in variable_name %}

Последняя строка:

{%tr for item in variable_name %}

На рисунке выше имена переменных:

  • table_contents для словаря Python, в котором будут храниться наши табличные данные;

  • Index для ключей словаря (первый столбец);

  • Power, Current и Voltage для значений словаря (второй, третий и четвёртый столбцы).

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

template = DocxTemplate('template.docx')table_contents = []for i in range(2, sheet_1.max_row + 1):    table_contents.append({        'Index': i-1,        'Power': sheet_1.cell(i, 1).value,        'Current': sheet_1.cell(i, 2).value,        'Voltage': sheet_1.cell(i, 3).value        })

Далее импортируем ранее созданное в Excel изображение диаграммы и создадим другой словарь для создания экземпляров всех объявленных в документе шаблона переменных-заполнителей:

image = InlineImage(template,'chart.png',Cm(10))context = {    'title': 'Automated Report',    'day': datetime.datetime.now().strftime('%d'),    'month': datetime.datetime.now().strftime('%b'),    'year': datetime.datetime.now().strftime('%Y'),    'table_contents': table_contents,    'image': image    }

И, наконец, визуализируем отчёт с нашей таблицей значений и изображением диаграммы:

template.render(context)template.save('Automated_report.docx')

Результаты

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

Автоматически сгенерированный отчётАвтоматически сгенерированный отчёт

Исходный код

import openpyxl as xlfrom openpyxl.chart import LineChart, Referenceimport win32com.clientimport PILfrom PIL import ImageGrab, Imageimport osimport sysfrom docx.shared import Cmfrom docxtpl import DocxTemplate, InlineImagefrom docx.shared import Cm, Inches, Mm, Emuimport randomimport datetimeimport matplotlib.pyplot as plt######## Generate automated excel workbook ########workbook = xl.load_workbook('Book1.xlsx')sheet_1 = workbook['Sheet1']  for row in range(2, sheet_1.max_row + 1):    current = sheet_1.cell(row, 2)    voltage = sheet_1.cell(row, 3)    power = float(current.value) * float(voltage.value)    power_cell = sheet_1.cell(row, 1)    power_cell.value = power  values = Reference(sheet_1, min_row = 2, max_row = sheet_1.max_row, min_col = 1, max_col = 1)chart = LineChart()chart.y_axis.title = 'Power'chart.x_axis.title = 'Index'chart.add_data(values)sheet_1.add_chart(chart, 'e2')  workbook.save('Book1.xlsx')######## Extract chart image from Excel workbook ########input_file = "C:/Users/.../Book1.xlsx"output_image = "C:/Users/.../chart.png"operation = win32com.client.Dispatch("Excel.Application")operation.Visible = 0operation.DisplayAlerts = 0    workbook_2 = operation.Workbooks.Open(input_file)sheet_2 = operation.Sheets(1)    for x, chart in enumerate(sheet_2.Shapes):    chart.Copy()    image = ImageGrab.grabclipboard()    image.save(output_image, 'png')    passworkbook_2.Close(True)operation.Quit()######## Generating automated word document ########template = DocxTemplate('template.docx')#Generate list of random valuestable_contents = []for i in range(2, sheet_1.max_row + 1):        table_contents.append({        'Index': i-1,        'Power': sheet_1.cell(i, 1).value,        'Current': sheet_1.cell(i, 2).value,        'Voltage': sheet_1.cell(i, 3).value        })#Import saved figureimage = InlineImage(template,'chart.png',Cm(10))#Declare template variablescontext = {    'title': 'Automated Report',    'day': datetime.datetime.now().strftime('%d'),    'month': datetime.datetime.now().strftime('%b'),    'year': datetime.datetime.now().strftime('%Y'),    'table_contents': table_contents,    'image': image    }#Render automated reporttemplate.render(context)template.save('Automated_report.docx')

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

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

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

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

27.04.2021 00:04:49 | Автор: admin

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

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

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

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

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

познакомимся с интерфейсом программы:

Он минималистичен и тем хорош. Создадим свой первый скрипт и попробуем написать какой-нибудь простой скрипт, своего рода Hello world от мира автокликинга.

Нажимаем на кнопку создания скрипта (1) и кнопку редактора (3).

Интерфейс редактора


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

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

https://cs14.pikabu.ru/video/2021/04/11/1618089701287548721_1920x1080.webm

Мы используем несколько команд:

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

LCLICK- Кликает левой кнопкой мыши по указанным в скобках координатам, необходимости прописывать их в ручную нет, просто наводите мышь туда, куда хотите кликнуть и нажимаете комбинациюALT + Q. Команда с координатами вставляется в скрипт автоматически. Либо если хотите вручную их поправить, опять же наводим мышь на желаемое расположение и смотрим сюда:

RCLICK- Соответственно всё тоже самое, только правой кнопкой мыши, вызывается тем же набором клавиш, просто нужно заменить в скрипте L на R.

Шаблоныкопирования и вставки (CTRL + C, CTRL +V) они уже есть в разделе шаблоны, нет необходимости их прописывать самому, просто выбираем подходящий.

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

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

Подробнее..

АнтиBIMing

22.05.2021 20:11:20 | Автор: admin
image
Сама по себе автоматизация лишь инструмент и как каждый инструмент у нее есть своя область применения, своя техника безопасности внедрения и применения, а так же свои преимущества и негатив. Традиционно бизнес стремится внедряться IT-разработки там, где существуют достаточно высокая маржа, а значит проще получить прибыль и уменьшать издержки, однако существуют области в которых давно назрела необходимость что-нибудь внедрить с тем что бы упростить и тогда все сформируется. Речь о личном опыте решения таких задач при составлении исполнительной документации в строительстве.

Программа, в которой описываются основные понятия и определения встречающиеся в тексте
Состав ПСД. Приемо-сдаточная документация#1 делится на:
1. Разрешительная документация, включая ППР;
2. Исполнительная документация.

Вся структура приемо-сдаточной документации субподрядной организации по спецмонтажным работам будет выглядеть так:
Разрешительная документация#2 термин, используемый для обозначения документации, оформляемой в соответствии со статьями 45 51 Градостроительного кодекса РФ вплоть до получения разрешения на строительство (ст. 51 ГрКРФ), а также получение разрешения на ввод объекта в эксплуатацию (ст. 55 ГрКРФ).
Исполнительная документация (ИД)#2 представляет собой текстовые и графические материалы, отражающие фактическое исполнение проектных решений и фактическое положение объектов капитального строительства и их элементов в процессе строительства, реконструкции, капитального ремонта объектов капитального строительства по мере завершения определенных в проектной документации работ.

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

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

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

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

Действие Первое. В котором рассказывается про предпосылки для автоматизации и проблемные рутины в Строительстве

Ты что делаешь?
Анекдоты читаю.
А отчет?
Час назад уже у тебя на столе лежит.
Погоди, тогда почему твой предшественник на его подготовку тратил три часа?
Послушай, я тоже могу тратить три часа на его подготовку. Если хочешь, я могу читать анекдоты в столовой. Но результат будет тот же.
Структура договорных отношений между участниками строительства #1
Обычно Инвестор нанимает организацию, занимающуюся управлением строительства, она может называться заказчиком. Этот заказчик нанимает проектный институт (лицо, осуществляющее подготовку проектной документации), чтобы тот ему нарисовал проект, бывает, так же нанимает генпроектировщика, а тот нанимает субчиков. Потом играют в тендер (кстати, то же самое может быть и с институтом) и выбирают генподрядчика это ответственный за строительную площадку (лицо, осуществляющее строительство) и заключает с ним договор. Для заказчика существует только генподрядчик (подрядчик) так как им так легче и удобней работать. Генподрядчик уже без тендера выбирает себе субподрядчиков (лицо, выполняющее работы), обычно по видам работ и заключает с ними договора. Субподрядчик или даже сам генподрядчик так же часто себе набирает субчиков, но уже не официально как бы под своим флагом. Заказчик нанимает технический надзор или сам может выполнять данную функцию (представитель заказчика или технический надзор заказчика). Если объект подпадает под государственный строительный надзор (ГСН), то и следит за всем этим он в виде инспекторов, их уведомляет заказчик о начале строительства, те приезжают со своей инспекцией, пишут замечания и уезжают. Все отношения регулируются договорами и действующим законодательством.

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

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

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

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


Согласно Википедии
BIM (англ. Building Information Model или Modeling) информационная модель (или моделирование) зданий и сооружений, под которыми в широком смысле понимают любые объекты инфраструктуры, например инженерные сети (водные, газовые, электрические, канализационные, коммуникационные), дороги, железные дороги, мосты, порты и тоннели и т. д.

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

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

Что касается документации и информационной модели на стройке и откуда она там берется. Как правило Заказчик передает Подрядчику проект со штампом В производство работ на бумажном носителе (очень редко в формате pdf и почти никогда в dwg) для того что бы последний в соответствии с контрактом за оговоренную сумму произвел некоторые работы. Прораб/мастер/нач.участка заказывает через снабженцев (привет бухгалтерия и 1С) материалы согласно потребностям, проекту и графику производства работ, нанимается техника, она же ремонтируется, под нее покупается ГСМ и прочие расходы связанные с объектом, часть из которых связана с машинами и механизмами, часть с материалами которые будут монтироваться. Затем на объекте ведутся на бумажном носителе: журнал входного контроля, общий журнал работ и прочие специализированные журналы которые зависят от видов выполняемых работ. К концу каждого операционного цикла подготавливается исполнительная документация, которая представляет из себя акты и схемы выполненных работ (схемы по сути копируют проект, ибо отступление от проекта, без согласования с Заказчиком и контролирующими органами, недопустимо и будет означать лишь проблемы для Подрядчика). Такие акты и схемы на бумажном носителе подписываются представителями Подрядчика, Заказчика, контролирующих органов и организаций и только после того как пройдет успешная защита составляются финансовые акты (обычно по форме КС-2 и КС-3, но это не обязательно, достаточно к договору приложить свой шаблон), на основании которых в особо упоротых случаях бухгалтерия Заказчика может позволить списать материалы бухгалтерии Подрядчика (помимо актов выполненных работ составляются так же акты входного контроля и все вместе это передается Заказчику) в соответствии со сметными расценками.

Сегодня, в отличие от СССР, прораб/мастер/нач.участка не составляют исполнительную документацию. Это не означает что они не заполняют и не составляют бумаг, просто они другие, больше связаны с непосредственной организации управленческих процессов (открытие и закрытие нарядов, журналы инструктажа, выдачи заданий, заявки, письма и т.п.) объем бумаг достаточно большой и это нормальная (в том плане, что распространенная практика) брать в штат сотрудников с высшим (!) инженерным образованием инженеров ПТО, которые будут заниматься всей остальной документацией, а проще говоря исполнять работу технического секретаря. (На самом деле порог вхождения в процессию очень низок, т.к. базового школьного курса Черчения достаточно, что бы читать строительные чертежи и даже перерисовывать схемы, конечно потребуется навык работы с Word/Excel/Paint/AutoCAD/Компас, но это не так сложно как может показаться и потому такая специальность утилизирует людей как с профильным образованием, так и с гуманитарным менеджеров/юристов/учителей и т.д. и т.п.)

Как правило рабочее место, которое может быть и удалено (вагончик в поле), оборудовано МФУ, Wi-Fi точкой, раздающей 3G интернет, ноутбуком. В отсутствие сис.админа все это работает в меру сил и понимания инженера ПТО, который за все это отвечает, который выполняет не только прямые обязанности, но и те от которых не удалось отбрыкаться по разным причинам. Надо ли говорить, что общая техническая грамотность страдает. Обычно на ноутбуки установлен, хорошо если заботливо, Windows, MS Office, редактор для векторной графики, GIMP, программа оптического распознавания текстов. Такой скудный выбор связан с тем, что з/п и оснащение такого инженера находится в составе Накладных Расходов, а не в статьях Общей Заработной Платы, как в случае, например, рабочих, т.е. разные статьи расценок сметы.

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

Исаак Ньютон:
От флюенции возьму флюксию и обратно.
Лейбниц:
Могу делать то же самое!

Идея создания Программы родилась спонтанно, после 3-х закрытых объектов в 2016году ПАО Транснефть. Помимо сбора и компоновки информации большой блок времени отнимали задачи, связанные с банальным заполнением документов по шаблону, среди которых преобладали Акты входного контроля и Акты освидетельствования скрытых работ. Особенно много времени уходило на проверки в случае описок или различного рода неточностей. Т.к. если они выявлялись, то приходилось заново открывать и проверять такие акты. Иногда, как в ситуации в 2018году, когда Ростехнадзор поменял форму актов скрытых работ, их счет шел на десятки. Но так родилась идея: А что, если я соберу все данные, необходимые для заполнения актов, в таблицу, а уже Программа будет прописывать их в шаблоны за меня?.

Самой простой и пригодной из доступных для этого является MS Office с макросами VBA. Учитывая тот факт, что в 90-е годы я в школе ударно изучал QBasic 4.5 и Borland Pascal 7.0, то выбор платформы оказался более чем очевиден. Пробелы в синтаксисе помог закрыть Гугл. Сама Идея не нова, но в 2016-м году в открытом доступе, так сказать в open source, я нашел только один вариант через Слияние, который тогда, в далеком 2016-м году меня не устроил. И вот я начал разрабатывать свой велосипед:

1. Самое главное и без чего не имело все дальнейшее смысл это без наглядности и удобства в работе. Поэтому для варианта с экспортом в Excel был выбран путь перебора текста в ячейках с целью поиска комбинаций текстовых маркеров, которые априори не встречаются в русскоязычных регламентных формах актов, с последующей авто подстановкой значений из таблицы. (Например, f1, d3, b8 и т.д. и т.п.) Для того что бы не пришлось перебирать всю матрицу я ввел упрощение, при создании шаблона если в первой ячейке за областью печати располагается символ арабской единицы, то только в этом случае макрос ищет текст во всех ячейках этой строки. Позднее я решил вопрос как получить в макрос диапазон ячеек, отправляемых на печать.

В случае с экспортом в World тут все гораздо проще Закладки и ссылки на содержимое закладок, при повторном упоминании содержимого в тексте.

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

Следующий шаг это формирование логики заполнения данных, т.е.:

А) Данные организаций и участников строительства, а также общие характеристики объекта;

Б) Данные для формирования Актов входного контроля, т.е. в первую очередь определяемся не с работами, а с материалами

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

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

И вот таким образом ПК/ноутбук превращаются из печатной машинки в помощника, который берет часть обязанностей на себя. Т.е. то, что часто многими упускается из виду при создании программ.

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

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

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

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

3. Зачастую люди, которым нужна автоматизация, не могут за нее заплатить, т.к. их оклад не такой уж и большой, а в их опыте даже нет рабочих примеров, когда софт облегчал им рабочую рутины, да еще и уменьшал ИХ КОЛИЧЕСТВО ОШИБОК. В то время как цены на такие программы сравнимы со стоимостью Сметных-комплексов. Но без сметных комплексов очень трудно обойтись, а вот без автоматизации Исполнительной документации элементарно.

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

Действие третье. В котором рассказывается о том как кристаллизовалась программа


На стройке самое важное что? График производства работ и ключевые даты на нем (врезка, подключение, начало работ и окончание работ и некоторые другие). На участке ведется ОЖР, в котором записывается вручную что было выполнено за каждый конкретный рабочий день. Но если взять график (Месячно-суточный график) и заполнять его, то мы получим графическое представление, который и легче воспринимается и, затем, легче автоматизируется, служа исходными данными для актов и аналитики.

Рис.1 Пример Месячно-суточного графика

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

Следующий Важный момент в строительстве это Материалы. Они должны соответствовать проекту и при их приемке осуществляется Входной контроль комиссией с оформлением бумаг (Акт входного контроля и Журнал Входного контроля). Это держим в уме. А дальше большая часть ИД в строительстве составляют Акты скрытых работ или полностью, Акт освидетельствования скрытых работ. Работы, их очередность и данные подтягиваем с Месячно-суточного графика, а материалы из перечня актов Входного контроля теперь еще и наглядно мониторить можно какие материалы в каком количестве списываются по актам АОСР.

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

Действие четвертое. В котором речь пойдет о макросах VBA

Далее пойдут спойлеры с кодом, призванные решить те или иные вопросы/проблемы.
Немного ускоряем MS Excel при работе с макросами
'Ускоряем Excel путём отключения всего "тормозящего" Public Sub AccelerateExcel()   'Больше не обновляем страницы после каждого действия  Application.ScreenUpdating = False   'Расчёты переводим в ручной режим  Application.Calculation = xlCalculationManual   'Отключаем события  Application.EnableEvents = False   'Не отображаем границы ячеек  If Workbooks.Count Then      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = False  End If   'Отключаем статусную строку  Application.DisplayStatusBar = False   'Отключаем сообщения Excel  Application.DisplayAlerts = False  End Sub

а теперь возвращаем настройки обратно
'Включаем всё то что выключили процедурой AccelerateExcelPublic Sub disAccelerateExcel()   'Включаем обновление экрана после каждого события  Application.ScreenUpdating = True   'Расчёты формул - снова в автоматическом режиме  Application.Calculation = xlCalculationAutomatic   'Включаем события  Application.EnableEvents = True   'Показываем границы ячеек  If Workbooks.Count Then      ActiveWorkbook.ActiveSheet.DisplayPageBreaks = True  End If   'Возвращаем статусную строку  Application.DisplayStatusBar = True   'Разрешаем сообшения Excel  Application.DisplayAlerts = True End Sub

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


Рис.2 Пример файла шаблона в формате MS Excel

Здесь в ячейке А1 содержится формула:
=СЦЕПИТЬ(АДРЕС(СТРОКА(Область_печати);СТОЛБЕЦ(Область_печати);1;1);":";АДРЕС(СТРОКА(Область_печати)+ЧСТРОК(Область_печати)-1;СТОЛБЕЦ(Область_печати)+ЧИСЛСТОЛБ(Область_печати)-1;1;1))
Т.е. мы можем получить область печати, обратившись к переменной, фигурирующей в диспетчере имен. Полученные абсолютные границы печати, которые будут автоматически меняться, если нам придется увеличить или уменьшить область печати. Зачем? Здесь следует сделать отступление.


Рис.3 Пример листа с хранящимися данными для автоматического заполнения актов.

Дело в том, что мною был выбран способ-маркеров в тексте, т.е. при составлении шаблона маркеры (a1, b0, c7, d8 и т.д. и т.п.) однозначно характеризуют с одной стороны строку, из которой будут браться данные (порядковый номер элемента массива, который автоматически завязан на номер строки), с другой стороны в русскоязычных шаблонах в строительстве абсурдное сочетание букв латиницы с цифрой не используется. А значит это наглядно. После чего обычный перебор текста решает, НО (!) чем больше ячеек в области печати, тем медленнее будет работать алгоритм. Значит ему надо помочь и подсветить только те строки, в которых априори что-то есть.
Код макроса VBA осуществляющий экспорт в шаблон в формате MS Excel
          With Workbooks.Open(ThisWorkbook.Path + "\Шаблоны аддонов\" + NameShablonPrimer, ReadOnly:=True)               .Sheets(NameShablonPrimerList).Visible = -1               .Sheets(NameShablonPrimerList).Copy after:=wb.Worksheets(Worksheets.Count)                              Let НачальныйНомерСтрокиВФайле = .Sheets(NameShablonPrimerList).Cells(1, 2)       ' Начальный номер строки в файле шаблона               Let НачальныйНомерСтолбцаВФайле = .Sheets(NameShablonPrimerList).Cells(1, 3)      ' Начальный номер столбца в файле шаблона               Let КонечныйНомерСтрокиВФайле = .Sheets(NameShablonPrimerList).Cells(1, 4)        ' Конечный номер строки в файле шаблона               Let КонечныйНомерСтолбцаВФайле = .Sheets(NameShablonPrimerList).Cells(1, 5)       ' Конечный номер столбца в файле шаблона                              .Close True          End With       End If    End If    Do       ИмяФайла = BDList + " "                                                                  ' Префикс имени файла       wb.Worksheets(NameShablonPrimerList).Copy after:=Worksheets(Worksheets.Count)       Set новыйЛист = wb.Worksheets(NameShablonPrimerList + " (2)")              For X = НачальныйНомерСтолбцаВФайле To КонечныйНомерСтолбцаВФайле Step 1                  ' Перебираем столбцы в листе Примера формы-шаблона           For Y = НачальныйНомерСтрокиВФайле To КонечныйНомерСтрокиВФайле Step 1                ' Перебираем строк в листе Примера формы-шаблона               If Sheets(новыйЛист.Name).Cells(Y, КонечныйНомерСтолбцаВФайле + 1) = 1 Then       ' При наличии спец символа проверяем на совпадении строку                  Let k = CStr(Sheets(новыйЛист.Name).Cells(Y, X))                               ' Ищем только если в ячейке что-то есть                  If k <> "" Then                     For i = 1 To Кол_воЭл_овМассиваДанных Step 1                         ContentString = CStr(Worksheets(BDList + " (2)").Cells(i + 1, НомерСтолбца))                         If Len(arrСсылкиДанных(i)) > 1 Then                            If ContentString = "-" Or ContentString = "0" Then ContentString = ""                            Let k = Replace(k, arrСсылкиДанных(i), ContentString)                         End If                     Next i                     новыйЛист.Cells(Y, X) = k                  End If               End If           Next Y       Next X                     For Y = НачальныйНомерСтрокиВФайле To КонечныйНомерСтрокиВФайле Step 1           Sheets(новыйЛист.Name).Cells(Y, КонечныйНомерСтолбцаВФайле + 1) = ""       Next Y            

Заполнение шаблонного файла в формате MS Word
вывода в шаблон формата Word, и здесь на самом деле есть 2 способа вывода текста:

1. Это через функционал закладок,
            Rem -= Открываем файл скопированного шаблона по новому пути и заполняем его=-            Set Wapp = CreateObject("word.Application"): Wapp.Visible = False            Set Wd = Wapp.Documents.Open(ИмяФайла)                        NameOfBookmark = arrСсылкиДанных(1)            ContentOfBookmark = Worksheets("Данные для проекта").Cells(3, 3)            On Error Resume Next            UpdateBookmarks Wd, NameOfBookmark, ContentOfBookmark            Dim ContentString As String            For i = 4 To Кол_воЭл_овМассиваДанных Step 1                If Len(arrСсылкиДанных(i)) > 1 Then                   NameOfBookmark = arrСсылкиДанных(i)                   ContentString = CStr(Worksheets("БД для АОСР (2)").Cells(i, НомерСтолбца))                   If ContentString = "-" Or ContentString = "0" Then ContentString = ""                   ContentOfBookmark = ContentString                   On Error Resume Next                   UpdateBookmarks Wd, NameOfBookmark, ContentOfBookmark                End If            Next i                         Rem -= Обновляем поля, что бы ссылки в документе Word так же обновились и приняли значение закладок, на которые ссылаются =-            Wd.Fields.Update                         Rem -= Сохраняем и закрываем файл =-            Wd.SaveAs Filename:=ИмяФайла, FileFormat:=wdFormatXMLDocument            Wd.Close False: Set Wd = Nothing

Sub UpdateBookmarks(ByRef Wd, ByVal NameOfBookmark As String, ByVal ContentOfBookmark As Variant)    On Error Resume Next    Dim oRng As Variant    Dim oBm    Set oBm = Wd.Bookmarks    Set oRng = oBm(NameOfBookmark).Range    oRng.Text = ContentOfBookmark    oBm.Add NameOfBookmark, oRngEnd Sub


2. Если рисовать таблицы средствами Word, то к ним можно обращаться с адресацией в ячейку
 Rem -= Заполняем данными таблицы ЖВК =-       Dim y, k As Integer       Let k = 1       For y = Worksheets("Титул").Cells(4, 4) To Worksheets("Титул").Cells(4, 5)           Wd.Tables(3).cell(k, 1).Range.Text = Worksheets("БД для входного контроля (2)").Cells(6, 4 + y)           Let k = k + 1       Next y       End With       


Между выводами в файлы форматов Word и Excel есть огромная пропасть, которая заключается в следующем:

Шаблон Excel требует перед использованием настроить отображение под конкретный принтер, т.к. фактическая область печати разнится от модели к модели. Так же перенос строки текста возможен, но только в пределах ячейки/объединенных ячеек. В последнем случае не будте автораздвигания строки, в случае переноса текста. Т.е. Вам вручную придется заранее определит границы области, которые будут содержать текст, который в свою очередь в них еще должен убраться. Зато Вы точно задали границы печати и выводимого текста и уверены, что не съедет информация (но не содержание) с одного листа на другой.

Шаблон Word при настройке автоматически переносит текст на последующую строку, если он не убрался по ширине ячейки/строки, однако этим самым он вызывает непрогнозируемый сдвиг текста по вертикали. Учитывая тот факт, что по требованиям к Исполнительной документации в строительстве ЗАПРЕЩЕНО один акт печатать на 2х и более листах, то это в свою очередь так же рождает проблемы.


С проектом можно ознакомиться тут:
vk.com/softpto
Все программы распространяются абсолютно бесплатно. Всем Добра! ;)
Подробнее..

Как Microsoft Analysis Services финансовым аналитикам жизнь упростил

07.04.2021 16:22:59 | Автор: admin
Как мало пройдено дорог как много сделано отчетов

Введение


Василий, мы установили новый BI продукт, наш САМЙ ГЛАВНЙ от него просто в восторге!
Да, но я не знаю, как выгрузить данные для анализа из этой системы?! Он, похоже, только в html может что-то показывать.
Ничего, я думаю ты справишься, сам понимаешь, чем шире улыбка шефа, тем выше премия.
Но, Иван Васильевич, этот продукт в качестве источника данных использует только PDF файлы.
Зато он показывает шикарные разноцветные графики, у него анимация как в Звездных войнах, а руководство просто в восторге от его интерактивных возможностей. Там ещё и пасхалочка есть. Если три раза кликнуть в правом нижнем углу, появится Дарт Вейдер и споёт Марсельезу. Да и в целом, Вася, будь оптимистом! Хочешь анекдот в тему?

Что у вас запланировано на 1 января?
Катание на санках
А если снег не выпадет?
Это нас огорчит, но не остановит.

Не грусти Вася, принимайся за работу, а мне пора спешить утренняя планерка, эээ Daily Standup Meeting точнее, всё никак не могу запомнить.

Вася садится за свой рабочий стол и с грустью смотрит в монитор. Да уж, красивые графики, только толку от них? В Excel не выгрузить, с формулами не сверить, хоть бери тетрадку с ручкой и делай всё на бумаге. Плюс ещё как-то KPI на основе этого надо посчитать. Зато в ИТ отдел, говорят, художника взяли, чтобы он красивые отчеты для руководства оформлял. Глядя на новый продукт, Вася загрустил. В голове у него крутились пару строк из стихотворения C.А. Есенина Мне грустно на тебя смотреть:
Так мало пройдено дорог,
Так много сделано ошибок.

Ну что ж, оставим Васю на едине со своей болью и посмотрим на проблему шире. Видя переделку строк C.А. Есенина, которая вынесена в цитату к этой статье, мне кажется, что он не одинок в своих мыслях. Сложно понять, как работают современные BI системы и для кого их пишут то ли для аналитиков, то ли для руководителей. Очень много теории и информации, причём, в зависимости от источника, эта информация может противоречить самой себе. К этому стоит добавить обилие научных терминов и трудный для понимания язык описания. Сложно угадать с выбором, а цена ошибки велика, так как системы дорогие и работа с ними часто требует определенной квалификации. Понимая всё это, я решил поделиться своим опытом в BI сфере. Попытаюсь написать об этом простым языком и не вдаваться глубоко в теорию. Речь пойдет о Microsoft Analysis Services и о том, как он может решить часть проблем связанных с аналитической отчетностью. Другую часть этих проблем, я решил, написав специальную программу, которая позволяла формировать отчеты непосредственно в Excel, минуя HTML формы и минимизируя нагрузку на Web сервер, но о ней я уже писал тут http://personeltest.ru/aways/habr.com/ru/post/281703/, а тут даже видео снял: https://youtu.be/_csGSw-xyzQ. Приятного вам чтения.
Если лень читать, то есть кортокое видео (11 минут)
Создание OLAP-куба в Microsoft Analysis Services: https://youtu.be/f5DgG51KMf8
Но в этом видео далеко не всё то, о чём пойдёт речь далее!!!


Отчетность и её проблемы


Все началось с задачи, поставленной финансовым отделом крупного банка. Надо было создать систему отчетности, которая бы позволяла быстро и оперативно оценивать текущую ситуацию в организации. Для решения этой задачи мы взяли базу данных. Организовали в ней Хранилище (Data Warehouse), настроили процессы загрузки данных и установили систему отчетности. В качестве которой мы взяли SQL Server Reporting Services, так как этот продукт входил в MS Sharepoint, использовавшийся в тот момент в банке. В принципе всё работало, но у заказчика были претензии:

  • Претензия 1. HTML -> MS Excel: отчеты изначально формируются в HTML, а аналитики работают с MS Excel. Надо постоянно делать экспорт из одного формата в другой. При этом часто сбивается разметка и в Excel часто подгружается множество дополнительной информации, большой объём которой, в некоторых случаях, существенно влияет на производительность.
  • Претензия 2. Параметры для отчета: данные в отчетах зависят от параметров, причём при их изменении формируется новый отчет, который надо опять выгружать в Excel, что не всегда удобно.
  • Претензия 3. Добавление изменений в отчет: для того, чтобы что-то изменить в отчете, добавить новую колонку или создать группировку, надо обращаться к специалисту, который может поправить этот отчет на сервере.
  • Претензия 4. Анализ данных: отчеты получаются статическими и когда нужно посмотреть различные разрезы, поменять строки с колонками, отфильтровать или добавить, либо удалить какие-то значения, надо делать все эти манипуляции в Excel, что не всегда удобно, а порой и сложно, из-за проблем с производительностью компьютеров, на которых работают аналитики.

Стоит отметить, что сотрудники банка не рассматривали для себя никакого другого инструмента в качестве замены MS Excel. И на то были веские основания. Весь его функционал сложно чем-то заменить. К примеру, аналитики очень часто:

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

В общем использовали его на все 100%. Хотя были те, кто предлагал им что-то другое, точнее не столько предлагал, сколько заставлял. Как итог таких предложений, у нас в системе появились SAP BO, Oracle Reports Services и ряд других BI инструментов. Возможно, они в чем-то превосходили SQL Server Reporting Services, но суть работы с ними кардинально не изменилась:

  1. формируем отчет в HTML,
  2. экспортируем его в Excel,
  3. начинаем заниматься бесконечными танцами вокруг данных.

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

Выход из ситуации


К найденному решению подтолкнули PivotTable в Excel



и PivotGrid от фирмы DevExpress ( https://demos.devexpress.com/blazor/PivotGrid).

Детально изучив эти решения вышли на MS Analysis Services и решили попробовать. Его можно использовать в Excel, и он может работать с Oracle, как с источником данных, что нас на тот момент устраивало. С точки зрения архитектуры, источником данных для него может служить что угодно, был бы нужный провайдер. Суть его в том, что он способен хранить в себе большие объемы данных со всеми их агрегациями и выдавать их клиенту максимально быстро. К Excel его можно легко подключить и манипулировать данными в Pivot Table.



В MS Analysis Services есть возможность партиционирования данных (хранение их в виде множества отдельных частей) и так же инкрементальное обновление данных. Это даёт ему возможность загружать данные из внешних систем небольшими кусочками и хранить их во множестве партиций. С точки зрения максимальных объемов, у него есть ограничения, но они довольно большие https://docs.microsoft.com/en-us/analysis-services/multidimensional-models/olap-physical/maximum-capacity-specifications-analysis-services?view=asallproducts-allversions.

MS Analysis Services является OLAP системой, которая использует отдельный сервер для хранения данных, либо части данных. Его плюсом является то, что он способен довольно быстро работать с миллионами записей, будучи установленным на обычный, современный компьютер. Так же он позволяет анализировать данные непосредственно в Excel и может заменить собой десятки отчетов на MS Reporting Services или ему подобных. Причем при работе с ним не надо писать и править различные запросы типа SQL, хотя при желании можно, только вместо SQL он использует MDX.

Правда есть тут и ложка дегтя. В Excel можно запросить разом очень большой объём данных и OLAP их вернет, но отобразить такой объем Excel не сможет, либо сможет, но работать при этом будет очень медленно. На первых порах это раздражало аналитиков, но поняв причину и настроив фильтры в Pivot Table эту проблему решили.

Секрет быстродействия MS Analysis Services, как и любой другой OLAP системы, кроется в архитектуре хранения данных. В нем все храниться в максимально подготовленном и оптимизированном для запросов виде. Такая подготовка требует времени и запись вновь пришедших данных в OLAP происходит не быстро, но, с другой стороны, чтение данных получается очень быстрым. Выходит долго пишем быстро читаем.

Немного теории


Чаще всего, когда анализируют данные их объединяют в группы, а сами группы так же объединяют в иерархии. Для примера возьмём торговую точку. С точки зрения бизнеса, интерес представляют продажи. То есть сколько товара было продано за день (1-группа), за месяц (2-ая) и за год (3-я). Где день месяц и год это разные уровни одной иерархии. Получается, что продажи за месяц это сумма всех продаж за все дни в месяце, а продажи за год это сумма продаж за все месяцы в этом году. Отсюда получается, что для получения максимального быстродействия, можно заранее собрать данные в группы и рассчитать агрегаты (в нашем примере суммы продаж) для каждого уровня иерархи. Вот на этом принципе и работают MS Analysis Services. Им достаточно сказать что надо считать, по какой формуле и на какие группы это можно разбить. Остальную работу они сделают сами. Тут немного о том как они это делают: http://citforum.ru/consulting/BI/molap_overview/node7.shtml. Стоит отметить, что в современных OLAP системах все агрегаты, чаще всего, не рассчитываются заранее. Это всё делается на лету, в момент запроса.

Теперь о терминах:


MS Analysis Services это одна из OLAP систем, где OLAP это аббревиатура online analytical processing. Дословно это означает интерактивная (online) аналитическая обработка данных. Со временем данная формулировка утратила свой первоначальный смысл, так как появились системы, способные обрабатывать данные с большой скоростью и передавать их пользователю без использования подходов, декларируемых в OLAP. Поэтому, сейчас есть более полное описание требований к системам, которые могут называться OLAP, это:


По своему опыту, могу сказать, что чем больше ваш OLAP куб удовлетворяет описанию Е.Ф. Кодда, тем лучше, как с точки зрения работы с ним, так и с точки зрения его создания.

Вкратце, OLAP это система хранения, организованная таким образом, чтобы данные в ней:

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

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

При построении OLAP выделяют Факты и Измерения. Факты это цифровые значения измеряемых величин. Измерения это сами измеряемые величины. Совокупность всех связанных между собой измерений, фактов и функций для их агрегации называют OLAP-кубом. Факты и Измерения связанны между собой. По типу связи выделяют 2 схемы организации хранения данных Звезда и Снежинка. Звезда это когда все измерения напрямую связаны с фактом, снежинка это когда есть измерения, которые связанны с фактом через другие измерения. Эти схемы можно создавать и просматривать в разделе Data Source Views в SSAS.







Создание OLAP-куба в Microsoft Analysis Services


Построение OLAP кубов делается через проект в Visual Studio. По большей части там реализована технология визуального программирования перетащить, кликнуть мышкой, настроить. Отсюда это проще показать, чем описать. Что я и сделал в моем видео: https://youtu.be/f5DgG51KMf8. Так же стоит отметить то, что Microsoft, в ознакомительных целях, предоставляет свои продукты бесплатно. Отсюда, посмотреть, как он работает можно на любом компьютере с ОС Windows 10, удовлетворяющем следующим требованиям: https://docs.microsoft.com/en-us/sql/sql-server/install/hardware-and-software-requirements-for-installing-sql-server-ver15?view=sql-server-ver15. Требования по ссылке к MS SQL Server, так как MS Analysis Services являются его частью.

Заключение


OLAP это относительно простой способ повысить скорость и удобство работы с данными. В данный момент существует множество решений, основанных на этой технологии. Я работал с MS Analysis Services (SSAS) и вот что мне в нём понравилось:

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

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

Инвестирование в отраслевые фонды в Экселе

03.02.2021 12:13:25 | Автор: admin

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

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


Почему фонды

Хотя вся необходимая информация и доступна и бесплатна, докопаться до реально работающих схем, приносящих доход, каждому приходится самому. Та пена, которую все ежедневно видят на баннерах типа: Покупай акции такие-то и продавай такие-то не имеют ничего общего с реальной жизнью. В частности, реклама фондов, рассылаемая банками (Вложись в супер-ETF и обрети целых 4.5% годовых!!!), оказала на автора противоположное действие и отложило инвестиции в фонды на три года. Прежде чем перейти к фондам я остановлюсь на минусах акций и облигаций.

Акции:

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

  • Анализ всяких технических вкусностей вроде ROE (см. finance.yahoo.com, раздел Statistics и Financials), а также прибылей за определённые промежутки времени, по аналогии с выложенной таблицей для оценки фондов, даёт прибыльность меньшую, нежели фонды.

  • Высокая волатильность.

  • Абсолютно непонятна логика/психология рынка: Почему если в новостях говорят о том, что у фирмы был хороший или плохой год, курс может как упасть, так и подняться. Где логика?

  • С акциями такое чувство, будто все фирмы мухлюют с отчётностью (привет, Wirecard!)

Облигации:

  • Низкая доходность

  • Можно по недосмотру нарваться на что-то экзотическое, например на облигацию, которая погашается по мере приближения к моменту погашения, зато

Куча плюсов облигаций:

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

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

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

Поэтому я выбрал

Фонды

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

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

Таблица

Для тех, кто никогда не работал с Selenium в Экселе, существует библиотека SeleniumBasic. Коротенькое описание установки для работы с ней приведено здесь. По нажатии на Update, таблица загружает данные о прибыли ценной бумаги (ЦБ) с сайта ariva.de.

Внутренности таблицы:

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

Табличка подробнее
  • WKN (Колонка C): Немецкий аналог Международного идентификационного кода ценной бумаги (ISIN), только короче. ISIN настолько международный, что finance.yahoo.com им не пользуется, предпочитая свои, мериканьские, обозначения. Дикари-с. WKN-единственный обязательный входной параметр таблицы.

  • Favorites (Колонка D): Если ячейка пуста, и чек-бокс Update only favorites отмечен, то для ускорения данная ЦБ будет пропущена. Раз в неделю я обновляю данные по всем ЦБ, но чаще всего только по тем, которые меня интересуют.

  • Страна (Колонка E): Страна не всегда обновляется корректно. В таких случаях ячейка со страной остаётся пустой.

  • Отрасль (Колонка F): Довольно грубое деление по отраслям. На немецком, так что гугл транслейт в помощь. Используется чтобы равномерно распределить ЦБ по разным секторам экономики.

  • Benchmark (Колонка G): Из какого индекса набраны акции.

  • Валюта (Колонка H): Тут всё понятно. Сайт видимо переводит всё в евро, а потому иные валюты я пока не встречал.

  • URL (Колонка I): В случае отсутствия автоматически обновляется по WKN.

  • Прибыль ЦБ за определённый период (Колонки J - N): Прибыль за 3/6 месяцев, 1/3/5 лет. Зелёным цветом помечены ЦБ из верхнего 85% перцентиля, красным из нижнего 15% перцентиля.

  • Время обновления (Колонка O): Если ЦБ уже была обновлена сегодня, то при повторном обновлении (тоже сегодня) она будет пропущена.

  • Остальные колонки говорят сами за себя: Цена в евро (Колонка P), Альфа (Колонка Q), Бета (Колонка R): Отношение Шарпа (Колонка S).

  • Сортировка осуществляется автоматически, выбором соответствующего критерия лист-боксом. Для сортировки важны данные после ключевого слова Sorting в колонке A. Категории перечислены в ячейках A59:A67, после которых (колонка B) идут формулы выбора соответствующей колонки для сортировки и вид сортировки, по возрастанию (TRUE) или убыванию (FALSE).

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

  • 3m/6m/1yr/3yrs/5yrs: Доходность ЦБ за данный период в процентах. В принципе меня обычно интересует только доходность за последние 3 месяца.

  • Положение (место) в каждой категории (3m/6m) вычисляется в колонках AB:AI.

  • Ну и в колонке AJ находится сумма всех категорий. Теоретически, чем меньше число, тем больший доход принесёт фонд. Стоит также отменить, что в сумму входят так же значения альфа и бета и отношение Шарпа, хотя они не являются линейнозависимыми. В общем можно и альфа и бета выкинуть.

В качестве простеньких бонусов:

Часто задают вопрос: А откуда следует, что если фонд давал прибыль в прошлом, то он будет давать прибыль и в будущем? Для ответа нарисуем график прибыли между 6 месяцами и 3 месяцами (условное будущее) и между годом и 6 месяцами (прошлое). Как видно из графика, чем больше прибыль в прошлом, тем больше прибыль и в будущем. Если убрать сильно выпадающие точки в верхней части графика, то коэффициент корреляции будет ещё выше: 0.2. Для автора этот результат был откровением, так как автор ожидал более хаотичного распределения точек. И, не забываем, что посредине, между 6 месяцами и годом был кризис в виде ковида. Выпадающие две верхних точки относятся к зелёной энергетике, т. Е. к хайпу в настоящий момент. Конечно, хотелось бы, чтобы наклон был больше, но ведь растёт!

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

Как работает табличка

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

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

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

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

Что я стараюсь делать:

  • Продаю ЦБ только не ранее 3 месяцев после покупки. Даю каждой ЦБ шанс показать на что она способна. А могу и продать и раньше хозяин счёта всё-таки я, а не табличка.

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

  • Когда наступает время очередного зуда и хочется продать или купить или и то и другое, я залезаю на finanzen.net/ariva.de в раздел Fonds/ETFs в разделы Fonds-Suche и ETF-Suche (поиск по фондам и ETF), ищу фонды с максимальной прибыльностью за год и вношу их в табличку. Таким образом проблема попасть на фонд вроде индексного фонда японского рынка и зависнуть на десятилетия в минусах практически невозможна.

Что я стараюсь не делать:

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

  • Вкладываться в хайп: Руки, конечно, чешутся, но Тесла/Биткоин не для меня. Если хотите, чтобы Тесла/Биткоин обрушилась, напишите мне и я их куплю. Убытки поделим пополам :-)

  • Меня смущают фонды, не торгующиеся на бирже (пример: green benefit Global Impact Fund). Да, он есть в моей копилке, но его покупка растянулась на три дня, так что быстро избавиться от него в течение 10 минут и по нужной цене скорее всего не удастся.

  • Не связываюсь с акциями и облигациями европейских фирм: К ним у меня никакого доверия после банкротства AirBerlin и Sympatex, чьи дефолтные облигации до сих пор висят мёртвым грузом на моём счету. Только моя медлительность спасла меня от покупки Wirecard прямо перед их дефолтом. Да и Баффета недавно немцы обидели.

  • Я не вкладываю в ЦБ дополнительных денег и вот почему (это лишь моё видение, оно не критично, у каждого свой стиль инвестирования):

  • Фонд должен приносить деньги мне, а не я ему. Поэтому 10% прибыли я вывожу и вкладываю в покупку золота, наличной валюты (можно и в тушенку/патроны, зависит от фантазии), в НЗ, на случай если всё обрушится.

  • Не получается откладывать 10% с зарплаты получается наверное намазываем на хлеб слишком много масла и моемся горячей водой.

А это ни тепло, ни холодно:

  • Комиссия инвестиционной компании (ИК): Не особо обращаю внимания на комиссию. В некоторых случаях она достигает 5%, но и отдача фонда в таком случае может быть неплохой.

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

  • Продавать всё во время кризиса. Да, в этот раз (март 2020) стоп-лоссы сработали как часы и я всё быстро распродал, в основном с прибылью. Но потом рынки достаточно быстро восстановились и я еле успел вложиться. Так что вышло то на то.

  • Можно застраховать фонды через Put-опционы. Тут у меня двойственная позиция:

  • Покупать: Автор предпочитает спать спокойно и видеть, что счёт в ИК не зависит от падения рынка.

  • Не покупать: Автор предпочитает спать спокойно, а не подгадывать момент, когда рынок упал с целью продать опцион.

  • В общем, и так и эдак плохо. Реально, последние две мои покупки опционов закончились потерей денег, ну а в сумме: плюс/минус ноль.

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

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

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

Риски

  • Доллар обрушится, обрушится: Честно говоря, не верю по одной причине:

  • Штаты собирают налоги со всего мира: Вот с какого бодуна мне приходится регулярно заполнять форму W-8BEN и посылать её в ИК, хотя я ни разу не гражданин США, а лишь купил их акции или АДР (американские депозитарные расписки)?

  • А вдруг будет новый кризис?

  • Кризис будет, но, экономика сейчас достаточно устойчива. Проверено корона-вирусом.

  • А вдруг завтра война?

  • Очень возможно, учитывая события 2014 года. В этом случае все накопления превратятся в ничто и именно на этот случай 10% прибыли и откладываются в НЗ (золото/тушенка/патроны). Автор до сих пор с содроганием вспоминает прочитанные в школе рассказы очевидцев о блокадном Ленинграде, когда за золото выменивали килограмм требухи типа лёгких.

  • А как на счёт искусственного раскачивания лодки, вроде того что сделали с GameStop?

  • Вот это действительно рискованно. Надеюсь у них хватит такта остановиться. А вообще, вся ситуация с GameStop сильно напоминает Россию в февральскую революцию, когда кучке большевиков удалось расшатать за пару месяцев огромную империю. Есть в Reddite что-то анархо-большевицкое.

Заключение

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

Ссылка на табличку

Будьте здоровы!

Живите богато!

Подробнее..

Перевод Microsoft Excel революционный игровой 3D-движок?

25.01.2021 12:19:59 | Автор: admin
image

Введение


В самых передовых компьютерных играх используются различные графические подсистемы так называемые трёхмерные графические движки. Играм и специалистам этой отрасли хорошо известны Source (Half Life 2), Unreal Engine (Unreal Tournament), idTech 4 (Doom 3), CryENGINE2 (Crysis) и Paradox [прим. пер.: оригинал статьи написан в 2008 году].

Настало время узнать имя нового 3D-движка: Microsoft Excel.

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

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

В разделах статьи даже представлены демо-программы и фильмы, созданные при помощи 3D-движка Excel.

Предупреждение: только для уверенных в себе специалистов!

Арифметические возможности


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

Компактность


Немногие игровые движки способны решать всю 3D-арифметику в рамках половины экрана. В верхней части Рисунка 1 находится пространственный сдвиг, поворот вокруг осей X, Y, Z и перспективная проекция. Как мы увидим в демо-приложениях, вычисления видимости, Z-буферизации и отражений занимают на экране почти столько же места.


Рисунок 1: фундамент 3D-движка

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

  1. Параметры перспективной проекции
  2. 3D-координаты точек объектов (относительно их центра)
  3. Матрица сдвига и поворота (подробнее об этом почитать можно, например, здесь)
  4. Параметры поворота
  5. Абсолютные 3D-координаты точек после сдвига и поворота
  6. 2D-координаты точек после вычисления перспективной проекции
  7. Экранные координаты точек
  8. Конечные точки граней объектов
  9. Формула элемента в матрице сдвига и поворота. Здесь чётко видны простота и компактность.

Изящество


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

Рендеринг


Разработчики могут выбрать одну из двух подсистем рендеринга:

  1. Нативную Excel Cell Graphics (ECG)
  2. Office-level Graphics Abstraction Layer (OGAL)

Нативная Excel Cell Graphics


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

Лист = экран движка

Ячейки = пиксели

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

Отбросим неприязнь и рассмотрим особенности ECG:

Размер пикселей можно менять, поэтому если кто-то захочет вернуться в прошлое и использовать мелкие квадратные пиксели традиционных 3D-движков, то это легко реализовать (см. Рисунки 2 и 3).


Рисунок 2: стандартный пиксель Excel


Рисунок 3: раскрашенные пиксели с изменённым размером

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


Рисунок 4: размер пикселей можно задавать для каждой строки

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

Большинство пользователей даже не осознаёт что это пиксели, и видит только поля для ввода данных!


Рисунок 5: рендеринг без линий сетки


Рисунок 6: рендеринг с линиями сетки

  • При необходимости можно использовать линии сетки субпиксельного размера. Это очень полезная функция, если строки и столбцы имеют разные размеры. На Рисунках 5 и 6 показаны разные режимы линий сетки.
  • Цвет пикселей можно задавать 24 битами.
  • Разрешение экрана 255x65535, что даёт уникально высокое разрешение в 16,7 мегапикселей, которого не найти в других 3D-движках.
  • Потрясающее соотношение сторон 1:256, которое при помощи функций Скрыть/Показать можно изменить на 4:4, 16:9 или любое другое произвольное соотношение.
  • 255 экранов в приложении, то есть вместе можно использовать не обычные два, а гораздо больше экранных буферов.
  • Встроенная функция зума, позволяющая по желанию увеличивать и уменьшать пиксели.

Можно сказать, что нативная Excel Cell Graphics обгоняет своё время и предоставляет множество уникальных и несравненных возможностей, недостижимых в других 3D-движках: пиксели переменного размера, произвольное изменяемое соотношение сторон, разрешение 16,7 мегапикселя, включаемый субпиксельный размер линий сетки.

На рисунке ниже (Рисунок 7) можно увидеть движок в действии (с отрендеренными линиями сетки). Запустить демо можно, скачав наши файлы примеров движка Excel (при запросе Excel включите макрос), нажав клавиши ALT+F8 и запустив ECG_Demo.


Рисунок 7: движок в действии (с подсистемой рендеринга ECG)

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



Office-level Graphics Abstraction Layer


Если вам не нужны продвинутые функции ECG типа пикселей переменного размера и изменяемого соотношения сторон, то можно выбрать ещё одну подсистему рендеринга Excel Office-level Graphics Abstraction Layer (слой абстракции графики офисного уровня).

OGAL предоставляет дополнительные возможности (отрисовка полигонов, заливка и т.д.), более высокую производительность и совместимость с другими приложениями из пакета MS Office. Эта совместимость может быть чрезвычайно полезной, если 3D-приложение нужно портировать, например, в Word.

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

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

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


Рисунок 8: движок в действии (с Office-level Graphics Abstraction Layer)

Типичным примером превосходства подсистемы OGAL является наличие полигонов: в то время как существующие 3D-движки работают только с треугольниками, OGAL поддерживает и другие виды многоугольников (четырёхугольники, пятиугольники и т.п.).

Отдельный фоновый буфер не требуется, потому что им занимается OGAL. Цвета можно задавать в обычном 24-битном формате, а подсистема также предоставляет дополнительный альфа-канал для прозрачности. Демонстрационный файл тоже можно найти в наших файлах примеров движка Excel. Видео для тех, кто боится выполнения в реальном времени:


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

Сдвиг парадигмы


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

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

Примечание: не стоит недооценивать силу привычек! Скорее всего, сейчас вы пользуйтесь клавиатурной раскладкой QWERTY, которая намеренно сделана наиболее неудобной!

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

Такова сила привычки.

(Дополнительную информацию можно прочитать здесь)

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

  • Нетрадиционный исходный код
  • Непоследовательная отладка
  • Мгновенная обратная связь (без последовательного процесса сборки)

Нетрадиционный исходный код


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


Рисунок 9: традиционный исходный код с интерпретацией сверху вниз

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

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

Это означает, что алгоритмы можно кодировать горизонтально (см. рисунок 1, где этапы 3D-сдвига интерпретируются слева направо), или снизу вверх, или даже в форме Г-образного хода коня. Excel не принуждает вас использовать традиционный последовательный кодинг, а потому обеспечивает программистам дополнительную степень свободы.

Можно сказать, что Excel открывает совершенно новые измерения для представления алгоритмов. Мы даже не можем увидеть границ этих новых возможностей! Скоро мы сможем не измерять не только длину исходного кода (см. SLOC), но и ширину (чем дополнятся значения SLOC?). Более того, это может открыть путь для n-мерного исходного кода, и мы сможем говорить ещё и о глубине или высоте исходного кода.

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

Ссылки на ячейки позволяют программисту проверить, какие ещё данные влияют на выбранную переменную (например, на рисунке 10 показано, что матрица 3D-сдвига определяется косинусом углов поворота X и Y), а также увидеть, на какие ещё переменные влияют выбранные данные (например, выбранная матрица сдвига влияет на все 3D-значения). При необходимости цепочку заданных в алгоритмах воздействий можно показать в обоих направлениях.


Рисунок 10: Ссылки на ячейки позволяют просматривать зависимости

Непоследовательная отладка


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

Они могут использовать полезные инструменты (контрольные точки, step in/step out, и т.п.) для ускорения процесса и повышения его удобства, но в парадигму программирования встроена последовательность, и она замедляет процесс отладки.

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

Мгновенная обратная связь


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

В обычной ситуации программисту приходится

  1. Сохранять изменения
  2. Компилировать сохранённые файлы
  3. Компоновать скомпилированные файлы
  4. Запускать исполняемый файл вводом в командную строку, нажатием на иконку или запуском браузера и вводом URL, и т.п.

и получать результат только после всех этих шагов.

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

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

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

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

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

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

Заключение


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

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

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

Перевод Как пересчитать электронную таблицу

17.01.2021 20:13:46 | Автор: admin
Предположим, я заказываю буррито для двоих друзей и хочу рассчитать общую стоимость заказа:



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



Округляем стоимость буррито El Farolito super vegi до 8 долларов, поэтому при доставке стоимостью 2 доллара общая сумма составит 20 долларов.

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



Эта простая стратегия пересчета всего документа может показаться расточительной, но на самом деле она уже лучше, чем было в VisiCalc, первых электронных таблицах в истории, и первом так называемом убойному приложению, благодаря которому стали популярными компьютеры Apple II. VisiCalc многократно пересчитывал ячейки слева направо и сверху вниз, сканируя их снова и снова, хотя ни одна из них не менялась. Несмотря на такой интересный алгоритм, VisiCalc оставался доминирующим программным обеспечением для электронных таблиц в течение четырёх лет. Его правление закончилось в 1983 году, когда Lotus 1-2-3 захватил рынок пересчётом в естественном порядке (natural-order recalculation). Вот как его описывал Трейси Робнетт Ликлайдер в журнале Byte:

Lotus 1-2-3 использовал пересчёт в естественном порядке, хотя также поддерживал построчный и поколончатый режимы VisiCalc. Пересчёт в естественном порядке поддерживал список зависимостей ячеек и пересчитывал ячейки с учётом зависимостей.

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

Но что насчёт цены буррито с доставкой


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



Обновляя ячейку только при изменении входных данных, мы можем избежать пересчёта цены буррито с доставкой. В этом случае мы сэкономим всего одно сложение, но в больших электронных таблицах экономия может оказаться гораздо более существенной! К сожалению, сейчас у нас другая проблема. Скажем, мои друзья теперь хотят мясные буррито, которые на доллар дороже, а закусочная El Farolito добавляет 2 доллара к заказу независимо количества буррито. Прежде чем что-то пересчитать, посмотрим на граф:



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

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

Первое решение включает в себя маркировку всех ячеек вниз по потоку от обновлённой ячейки. Они помечаются как грязные. Например, когда мы обновляем цену буррито, то помечаем нижестоящие ячейки Burrito Price w Ship и Total, прежде чем делать какой-либо пересчёт:



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

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



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

Это намного лучше первого решения: ни одна ячейка не маркируется грязной, если одна из входящих ячеек действительно не изменится. Однако это требует сохранять ячейки в отсортированном порядке в ожидании пересчёта. При использовании кучи это приводит к замедлению O(n log n), что в худшем случае асимптотически медленнее, чем стратегия Lotus 1-2-3 по пересчёту всего.

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

Ленивые вычисления


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

Кроме кэширования, один из интересных аспектов вычислительного графа в стиле электронных таблиц заключается в том, что мы можем вычислять только те ячейки, которые нас интересуют. Это иногда называют ленивыми вычислениями или demand-driven computation. В качестве более конкретного примера приведём немного расширенный график электронной таблицы с буррито. Пример такой же, как и раньше, но мы добавили то, что лучше всего описать как расчёт сальсы. Каждый буррито содержит 40 граммов сальсы, и мы выполняем быстрое умножение, чтобы узнать, сколько сальсы во всём заказе. Поскольку в нашем заказе три буррито, всего будет 120 граммов сальсы.



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

Именно здесь может помочь ленивый перерасчёт. Если каким-то образом указать, что нас интересует только результат Total, можно было бы пересчитать эту ячейку, её зависимости и не трогать сальсу. Давайте назовём Total наблюдаемой ячейкой, поскольку мы пытаемся посмотреть на её результат. Мы также можем назвать Total и три её зависимости необходимыми ячейками, поскольку они необходимы для вычисления некоторой наблюдаемой ячейки. Salsa In Order и Salsa Per Burrito назовём ненужными (unnecessary).

Для решения этой проблемы некоторые товарищи из команды Rust создали фреймворк Salsa, явно назвав его в честь ненужных вычислений сальсы, на которые тратились циклы их компьютеров. Наверняка они могут лучше меня объяснить, как он работает. Очень грубо, фреймворк использует номера ревизий для отслеживания того, нуждается ли ячейка в пересчёте. Любая мутация в формуле или входных данных увеличивает глобальный номер ревизии, и каждая ячейка отслеживает две ревизии: verified_at для ревизии, результат которой был обновлён, и changed_at для ревизии, результат которой был фактически изменён.



Когда пользователь указывает, что ему нужно новое значение Total, мы сначала рекурсивно пересчитываем все ячейки, необходимые для Total, пропуская ячейки, в которых ревизия last_updated равна глобальной ревизии. Как только зависимости Total обновлены, мы повторно запускаем фактическую формулу Total только в том случае, если либо у Burrito Price w Ship, либо у Num Burrito ревизия changed_at больше, чем проверенная ревизия Total. Это отлично подходит для Salsa в rust-analyzer, где важна простота, а вычисление каждой ячейки требует значительного времени. Однако в графике с буррито выше можно заметить и недостатки если Salsa Per Burrito постоянно меняется, то наш глобальный номер ревизии будет часто повышаться. В результате каждое наблюдение Total потребует прохода трёх ячеек, даже если ни одна из них не изменилась. Никакие формулы не будут пересчитаны, но если график большой, многократное прохождение всех зависимостей ячейки может оказаться дорогостоящим.

Более быстрые варианты ленивых вычислений


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

Сначала посмотрим на маркировку. Как и раньше, когда мы меняем формулу ячейки, то помечаем все нижестоящие ячейки как грязные. Поэтому при обновлении Salsa Per Burrito это будет выглядеть примерно так:



Но вместо того, чтобы немедленно пересчитать все грязные ячейки, мы ждём, пока пользователь не пронаблюдает ячейку. Затем запускаем на ней алгоритм Salsa, но вместо того, чтобы перепроверять зависимости с устаревшими номерами версий verified_at, мы перепроверяем только ячейки, помеченные как грязные. Такую технику использует Adapton. В такой ситуации наблюдение ячейки Total обнаруживает, что она не грязная, и поэтому мы можем пропустить проход по графу, который бы выполнила Salsa!

Если же пронаблюдать Salsa In Order, то она помечена как грязная, и поэтому мы перепроверим и пересчитаем и Salsa Per Burrito, и Salsa In Order. Даже здесь есть преимущества по сравнению с использованием только номеров ревизий, поскольку мы сможем пропустить рекурсивный проход по всё ещё чистой ячейке Num Burritos.

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

Кроме грязной маркировки, мы можем адаптировать для ленивых вычислений и топологическую сортировку. Это метод использует библиотека Incremental от Джейн Стрит, и для правильной работы требуется ряд серьёзных трюков. Прежде чем реализовать ленивые вычисления, наш алгоритм топологической сортировки использовал кучу, чтобы определить, какая ячейка будет пересчитана следующей. Но сейчас мы хотим пересчитывать только те ячейки, которые необходимы. Как? Мы не хотим ходить по всему дереву из наблюдаемых ячеек, как Adapton, поскольку полный проход по дереву разрушает всю цель топологической сортировки и даст нам характеристики производительности, аналогичные Adapton.

Вместо этого Incremental поддерживает набор ячеек, которые пользователь отметил наблюдаемыми, а также набор ячеек, необходимых для любой наблюдаемой ячейки. Всякий раз, когда ячейка помечена как наблюдаемая или ненаблюдаемая, Incremental ходит по зависимостям этой ячейки, чтобы убедиться, что необходимые метки применены правильно. Затем мы добавляем ячейки в кучу пересчёта только в том случае, если они помечены как необходимые. В нашем графе буррито, если только Total является частью наблюдаемого множества, изменение Salsa in Order не приведёт ни к какому проходу по графу, так как пересчитываются только необходимые ячейки:



Это решает нашу проблему без нетерпеливого хождения по графику, чтобы пометить ячейки как грязные! Мы всё ещё должны помнить, что ячейка Salsa per Burrito является грязной, чтобы пересчитать её позже, если это станет необходимым. Но в отличие от алгоритма Adapton, нам не нужно проталкивать эту единственную грязную метку вниз по всему графу.

Anchors, гибридное решение


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

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



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

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



Как и в предыдущем примере, мы на самом деле не пересчитываем много ячеек: 4+4 и 2*4 равно 8, поэтому в идеале мы пересчитываем только это арифметическое действие, когда пользователь делает это изменение. Но в отличие от предыдущего примера, Incremental теперь избегает хождения по дереву. С помощью Incremental мы кэшировали тот факт, что цена буррито является необходимой ячейкой, и поэтому, когда она меняется, мы можем пересчитать её, не проходя по графу. С помощью Adapton мы тратим время на маркировку Burrito Price w Ship и Total как грязной, даже если результат Burrito Price не изменяется.

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

Во многих случаях Anchors точно следуют алгоритму Incremental, что позволяет избежать вырожденного случая Adapton выше. Но когда ячейки помечены как ненаблюдаемые, их поведение немного расходится. Давайте посмотрим, что происходит. Начнём с пометки Total как наблюдаемой ячейки:



Если затем пометить Total как ненаблюдаемую ячейку, а Salsa in Order как наблюдаемую, традиционный алгоритм Incremental изменит граф, пройдя в процессе через все ячейки:



Anchors для этого изменения тоже обходит все ячейки, но создаёт другой граф:



Обратите внимание на флаги чистых ячеек! Когда ячейка больше не нужна, мы помечаем ее как чистую. Давайте посмотрим, что происходит, когда мы переходим от наблюдения Salsa in Order к Total:



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

Похоже, в закусочной El Farolito объявили скидку! Давайте снизим цену буррито на доллар. Как Anchors узнает, что нужно пересчитать сумму? До какого-либо пересчёта давайте посмотрим, как Anchors видит граф сразу после изменения цены буррито:



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



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



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

Синтаксис буррито


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



Поскольку традиционные электронные таблицы не являются ленивыми, мы можем вычислять B1, B2 и B3 в любом порядке, а потом вычислить ячейку IF. Однако в ленивом мире, если мы сначала можем вычислить значение B1, то можем посмотреть результат, чтобы узнать, какое значение нужно пересчитать B2 или B3. К сожалению, в традиционных электронных таблицах IF не может выразить это!

Проблема: B2 одновременно ссылается на ячейку B2 и извлекает её значение. Большинство упомянутых в статье ленивых библиотек вместо этого явно различают ссылку на ячейку и акт извлечения её фактического значения. В Anchors мы называем эту ссылку на ячейку якорем (anchor). Подобно тому, как в реальной жизни буррито обёртывает кучу ингредиентов вместе, тип Anchor<T> обёртывает T. Таким образом, я полагаю, наша ячейка с веганским буррито становится Anchor<Burrito>, своего рода нелепым буррито из буррито. Функции могут передавать наши Anchor<Burrito> столько, сколько им захочется. Но когда они на самом деле разворачивают буррито, чтобы получить доступ к Burrito внутри, мы создаём в нашем графе ребро зависимости, указывая алгоритму перерасчёта, что ячейка может быть необходима и нуждается в пересчёте.

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

let burrito_for_customer = cell!({    if get!(is_vegetarian) {        get!(vegi_burrito)    } else {        get!(meat_burrito)    }});

Различая ссылку на ячейку (здесь vegi_burrito) и акт развёртывания её значения (get!), Adapton может работать поверх операторов потока управления Rust, таких как if. Это отличное решение! Однако нужно немного магического глобального состояния для правильного подключения вызовов get! к ячейкам cell!, когда меняется is_vegetarian. Библиотека Anchors под влиянием Incremental использует менее магический подход. Подобно pre-async / await Future, Anchors позволяет запускать на Anchor<T> такие операции, как map и then. Так, приведённый выше пример будет выглядеть примерно так:

let burrito_for_customer: Anchor<Burrito> = is_vegetarian.then(|is_vegetarian: bool| -> Anchor<Burrito> {  if is_vegetarian {      vegi_burrito  } else {      meat_burrito  }});

Дальнейшее чтение


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


И конечно, вы всегда можете посмотреть мой фреймворк Anchors.
Подробнее..

Новые возможности Microsoft 365 для Mac

24.12.2020 10:15:09 | Автор: admin


Недавно мы анонсировали ряд улучшений для Mac, в частности новые версии приложений Microsoft 365 для компьютеров Mac на базе процессора M1. Благодаря этому повысится производительность офисных приложений Microsoft на последних моделях MacBook Air, MacBook Pro и Mac mini. Новые приложения Office являются универсальными, поэтому также будут работать на компьютерах Mac на базе процессоров Intel. Интерфейс новых версий приложений оптимизирован в соответствии со стилем операционной системы macOS Big Sur.

  • Обновленная версия интерфейсаOfficeStart. Новая версия приложений Word, Excel, PowerPoint и OneNote для Mac будет включать в себя элементы дизайна Fluent UI, а также операционной системы macOS Big Sur. Новый интерфейс Office Start станет доступен в следующем месяце.
  • Перевод данных таблиц из фотографий вExcel. Благодаря приложению Data from Picture пользователи смогут фотографировать таблицы на iPhone и превращать их в данные, которые можно редактировать в Excel для Mac.



  • Настройка представления листа вExcelдляMac. Функция позволит настраивать представление листа для других пользователей во время сортировки и фильтрации данных, чтобы не нарушать отображение информации для коллег во время совместной работы над файлами.
  • Обновленное окно поиска в офисных приложенияхпозволит быстро перейти к необходимым инструментам Office, просто введя нужный запрос в Word, Excel, PowerPoint или OneNote для Mac.
  • Поддержка учетных записейiCloudв новомOutlookдляMac. Благодаря этому пользователи смогут организовать работу с личными и рабочими электронными письмами, контактами и календарями в одном приложении. Новая функция появится в приложении в ближайшие недели.

Подробнее на английском языке
Подробнее..

Анализ банковских выписок в формате .xlsx с помощью Python и openpyxl

12.05.2021 20:07:31 | Автор: admin
Дисклеймер

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

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

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

  1. Открыть нужную excel-таблицу и провести построковый и полистовой поиск значения по 1-3 ключевым словам, не заморачиваясь с инструментами самого MO Excel по сортировке и фильтру. Построковый поиск предпочтительный, чтобы позволить выводить всю интересующую транзакцию и проводить поиск по всем колонкам.

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

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

import openpyxl from openpyxl import Workbookbankstatetment = input('Введите название файла для анализа ') #просим указать имя файла в рабочей директории#просим ввести ключевые для поиска словаobj1= input('Введите ключевое слово ') obj2= input('Введите ключевое слово ')obj3= input('Введите ключевое слово ')wb = openpyxl.load_workbook(bankstatetment) # загружаем выбранную выписку/файлresults_string_list = list() #создаем список, куда будет помещаться значение строк с ключевыми словамиresults_stringrow_list = list() #создаем список, куда будет помещаться номер строки с ключевыми словами

Как я понял, посредством openpyxl нельзя перебирать листы таблицы - в каждом случае приходится прописывать, какой лист таблицы должен стать активным и точно указать его наименование. Конечный пользователь, в результате, будет вынужден удостовериться, что в проверяемом файле все листы названы как в программе "Лист 1", "Лист 2", "Лист 3".

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

sheet = wb['Лист1'] #делаем активным первый лист таблицы.for row in sheet: #перебор строк в листе    string = ''    for cell in row:        string = string + str(cell.value) + ' ' #определить значение в ячейках строки        string_row = str(cell.row)+ ' '#определить номер строки    if obj1 in string:         results_string_list.append (string) #добавляем значение строки в отдельный список        results_stringrow_list.append (string_row) #добавляем номер строки в отдельный список    if obj2 in string:        results_string_list.append (string)        results_stringrow_list.append (string_row)    if obj3 in string:        results_string_list.append (string)        results_stringrow_list.append (string_row)

Аналогичный код идет после установки в качестве активного второго листа страницы и далее. После приступаем к передаче найденных данных в новую таблицу:

wb = Workbook() #создаем новую таблицуws = wb.active #делаем новую таблицу активнойa1 = ws['A1']a1.value = 'Содержание транзакции' #задаем значение подзаголовка колонки "А"b1 = ws['B1']b1.value = 'Номер строки с транзакцией' #задаем значение подзаголовка колонки "B"

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

a2 = ws['A2']a3 = ws['A3']a4 = ws['A4']a5 = ws['A5']...b2 = ws['B2']b3 = ws['B3']b4 = ws['B4']b5 = ws['B5']

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

for i in results_string_list[0:1]:    a2.value = ifor i in results_string_list[1:2]:    a3.value = ifor i in results_string_list[2:3]:    a4.value = ifor i in results_string_list[3:4]:    a5.value = i...for i in results_stringrow_list[0:1]:    b2.value = ifor i in results_stringrow_list[1:2]:    b3.value = ifor i in results_stringrow_list[2:3]:    b4.value = ifor i in results_stringrow_list[3:4]:    b5.value = i...wb.save('результаты анализа.xlsx') 

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

P.S. При применении openpyxl поиск ключевых слов не был чувствителен к регистру, так что эту проблему решать не пришлось.

Подробнее..
Категории: Python , Excel , Openpyxl

Из песочницы Как скрестить Excel c интерактивным веб-приложением

07.10.2020 16:09:34 | Автор: admin
Не секрет, что Excel довольно мощный инструмент для работы с числовыми табличными данными. Однако средства, которые предоставляет Microsoft для интеграции с ним, далеки от идеала. В частности, сложно интегрировать в Excel современные пользовательские интерфейсы. Нам нужно было дать пользователям Excel возможность работать с довольно насыщенным и функциональным интерфейсом. Мы пошли несколько другим путем, который в итоге показал хороший результат. В этой статье я расскажу, как можно организовать интерактивное взаимодействие Excel c веб-приложением на Angular и расшить Excel практически любым функционалом, который реализуем в современном веб-приложении.



Итак, меня зовут Михаил и я CTO в Exerica. Одна из проблем которые мы решаем облегчение работы финансовых аналитиков с числовыми данными. Обычно они работают как с исходными документами финансовой и статистической отчетности, так и каким-либо инструментом для создания и поддержания аналитических моделей. Так сложилось, что 99% аналитиков работают в Microsoft Excel и делают там довольно сложные вещи. Поэтому перевести их с Excel на другие решения не эффективно и практически невозможно. Объективно, облачные сервисы электронных таблиц до функционала Excel пока не дотягивают. Но в современном мире инструменты должны быть удобны и соответствовать ожиданиям пользователей: открываться по клику мышки, иметь удобный поиск. А реализация в виде разных несвязанных приложений будет довольно далека от ожданий пользователя.

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



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

Что у нас уже было


К моменту, когда мы начали реализацию интерактивного взаимодействия с Excel в виде, изложенном в этой статье, у нас уже была база данных на MongoDB, бэкэнд в виде REST API на .NET Core, фронтовое SPA на Angular и некоторые другие сервисы. Мы к этому моменту уже пробовали разные варианты интеграции в приложения электронных таблиц, в том числе и в Excel, и все они не пошли дальше MVP, но это тема отдельной статьи.



Связываем данные


В Excel существует два распространенных инструмента, с помощью которых можно решить задачу связывания данных в таблице с данными в системе: RTD (RealTimeData) и UDF (User-Defined Functions). Чистый RTD менее удобен для пользователя в плане синтаксиса и ограничивает гибкость решения. С помощью UDF можно создать кастомную функцию, которая будет работать привычным для Excel-пользователя образом. Ее можно использовать в других функциях, она понимает ссылки типа A1 или R1C1 и вообще ведет себя как надо. При этом никто не мешает использовать механизм RTD для обновления значения функции (что мы и сделали). UDF мы разрабатывали в виде Excel addin с использованием привычного нам C# и .NET Framework. Для ускорения разработки мы использовали библиотеку Excel DNA.

Кроме UDF наш addin реализует ribbon (панель инструментов) с настройками и некоторыми полезными функциями по работе с данными.

Добавляем интерактивность


Для передачи данных в Excel и налаживания интерактива мы разработали отдельный сервис, который предоставляет подключение по Websocket при помощи библиотеки SignalR и фактически является брокером для сообщений о событиях, которыми должны обмениваться фронтовые части системы в реальном времени. Он у нас называется Notification Service.



Вставляем данные в Excel


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

  • Перетаскивание (drag-and-drop)
  • Автоматическая вставка по клику в SPA
  • Копирование и вставка через клипборд

Когда пользователь инициирует dragndrop некоторого числа из SPA, для перетаскивания формируется ссылка с идентификатором этого числа из нашей системы (.../unifiedId/005F5549CDD04F8000010405FF06009EB57C0D985CD001). При вставке в Excel наш addin перехватывает событие вставки и парсит регэкспом вставляемый текст. При обнаружении валидной ссылки на лету подменяет ее на соответствующую формулу =ExrcP(...).

При клике на числе в SPA через Notification Service отправляется сообщение в addin, содержащее все необходимые данные для вставки формулы. Далее формула просто вставляется в текущую выделенную ячейку.

Эти способы хороши, когда пользователю нужно вставлять в свою модель по одному числу, но если надо перенести целую таблицу или ее часть, необходим другой механизм. Наиболее привычным для пользователей представляется копирование через клипборд. Однако этот способ оказался сложнее первых двух. Дело в том, что для удобства вставляемые данные должны быть представлены в нативном для Excel формате OpenXML Spreadsheet. Наиболее просто это реализуется используя объектную модель Excel, то есть из addinа. Поэтому процесс формирования клипборда у нас выглядит так:

  • Пользователь выделяет область с числами в SPA
  • Массив выделенных чисел передается на Notification Service
  • Notification Service передает его в addin
  • Addin формирует OpenXML и вставляет его в клипборд
  • Пользователь может вставить данные из клипборда в любое место любой Excel-таблицы.



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

Распространяем данные


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

А что это там за число?


Теперь у пользователя есть модель на несколько сотен строк и несколько десятков столбцов. И у него может возникнуть вопрос, что же там за число в ячейке L123? Чтобы получить ответ, у нас ему достаточно кликнуть на эту ячейку и в нашем SPA откроется тот самый отчет, на той самой странице, где записано кликнутое число, а число в отчете будет выделено. Вот так:



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

В качестве заключения


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

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

Делаем фильтры как в экселе на ASP.NET Core

18.02.2021 00:06:58 | Автор: admin

Сделайте нам фильтры как в экселе, довольно популярный запрос на разработку. К сожалению, реализация запроса "слегка" длинее, чем его лаконичная постановка. Если вдруг вы никогда не пользовались этими фильтрами, то вот пример. Основная фишка в том, что в строчке с названиям колонок появляются выпадающие списки со значениями из выбранного диапазона. Например в колонках А и B 4000 строк и 3999 значений (первую строчку занимают названия колонок). Таким образом, в соответсвтующих выпадающих списках будет по 3999 значений. В колонке C 220 строк и 219 значений в выпадающем списке соответственно.



ToDropdownOption


В .NET испокон веков существует прекрасный интерфейс IQuerable<T>, предоставляющий доступ к разнообразным источникам данных. Его и будем использовать. Определим метод-расширения ToDropdownOption поверх интерфейса.


public static IQueryable<DropdownOption<TValue>> ToDropdownOption<TQueryable, TValue, TDropdownOption>(   this IQueryable<TQueryable> q,   Expression<Func<TQueryable, string>> labelExpression,   Expression<Func<TQueryable, TValue>> valueExpression)   where TDropdownOption: DropdownOption<TValue>{   // Вызываем конструктор по умолчанию    // В Cache<TValue, TDropdownOption>.Constructor кешируется reflection   var newExpression = Expression.New(Cache<TValue, TDropdownOption>.Constructor);   // Подробнее об этой особой уличной магии здесь   // http://personeltest.ru/aways/habr.com/ru/company/jugru/blog/423891/#predicate-builder   var e2Rebind = Rebind(valueExpression, labelExpression);   var e1ExpressionBind = Expression.Bind(       Cache<TValue, TDropdownOption>.LabelPropertyInfo, labelExpression.Body);   var e2ExpressionBind = Expression.Bind(       Cache<TValue, TDropdownOption>.ValuePropertyInfo, e2Rebind.Body);   // Инициализируем значения Label и Value   var result = Expression.MemberInit(       newExpression, e1ExpressionBind, e2ExpressionBind);   var lambda = Expression.Lambda<Func<TQueryable, DropdownOption<TValue>>>(       result, labelExpression.Parameters);   /*   В итоге получим   return q.Select(x => new DropdownOption<TValue>   {     Label = labelExpression     Value = valueExpression   });   Но такой код не скомплируется,   поэтому пришлось написть с помощью API Expression Trees   */   return q.Select(lambda);}

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

Сами классы DropdownOption и DropdownOption<T> вылгядят следующим образом.


public class DropdownOption{   // Запрещаем программно создавать нетипизированные DropdownOption   // за пределами сборки   internal DropdownOption() {}   internal DropdownOption(string label, object value)   {       Value = value ?? throw new ArgumentNullException(nameof(value));       Label = label ?? throw new ArgumentNullException(nameof(label));   }   // Делаем свойства неизменяемыми за пределеами сборки   public string Label { get; internal set; }   public object Value { get; internal set; }}public class DropdownOption<T>: DropdownOption{    internal DropdownOption() {}    // Типизированные опции создавать за пределами сборки    public DropdownOption(string label, T value) : base(label, value)    {        _value = value;    }    private T _value;    // Перекрываем базовое свойство типизированным    public new virtual T Value    {        get => _value;       internal set       {           _value = value;           base.Value = value;       }    }}

Трюк с internal-конструктором позволяет привести любой DropdownOption<T> к DropdownOption без generic-параметра, одновременно, не позволяя создавать экземпляры класса без generic-параметра за пределами сборки.


Будет здорово когда/если ковариантные возвращаемые типы будут реализованы. С ними можно избавиться от перекрытия через new. Пока имеем, что имеем.

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


public IEnumerable GetDropdowns(IQueryable<SomeData> q) =>    q.ToDropdownOption(x => x.String, x => x.Id)

IDropdownProvider


Где вызывать этот метод расширения? Допустим, мы работаем с таким контроллером:


public IActionResult GetData(    [FromServices] IQueryable<SomeData> q    [FromQuery] SomeDataFilter filter) =>    Ok(q    .Filter(filter)    .ToList());

Классы SomeData и SomeDataFilter определены следующим образом:


public class SomeDataFilter{   public int[] Number { get; set; }   public DateTime[]? Date { get; set; }   public string[]? String { get; set; }}public class SomeData{   public int Number { get; set; }   public DateTime Date { get; set; }   public string String { get; set; }}

А метод Filter следующим образом:


public static IQueryable<SomeData> Filter(    this IQueryable<SomeData> q,    SomeDataFilter filter){    if (filter.Number != null)    {        q = q.Where(x => filter.Number.Contains(x.Number));    }    if (filter.Date != null)    {        q = q.Where(x => filter.Date.Contains(x.Date));    }    if (filter.String != null)    {        q = q.Where(x => filter.String.Contains(x.String));    }    return q;}

Для реальных проектов, этот метод можно сделать обобщенным Как именно описано здесь

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


public IActionResult GetSomeDataFilterDropdownOptions(   [FromServices] IQueryable<SomeData> q){   var number = q       .ToDropdownOption(x => x.Number.ToString(), x => x.Number)       .Distinct()       .ToList();   var date = q       .ToDropdownOption(x => x.Date.ToString("d"), x => x.Date)       .Distinct()       .ToList();   var @string = q       .ToDropdownOption(x => x.String, x => x.String)       .Distinct()       .ToList();   return Ok(new   {       number,       date,       @string   });}

Такой код может понадобится для любого типа фильтров, а не только SomeDataFilters, поэтому введем соответствующий интерфейс.


public interface IDropdownProvider<T>{  Dictionary<string, IEnumerable<DropdownOption>> GetDropdownOptions();}

И перенесем код получения опций в класс, реализующий интерфейс:


public class SomeDataFiltersDropdownProvider: IDropdownProvider<SomeDataFilter>{   private readonly IQueryable<SomeData> _q;   public SomeDataFiltersDropdownProvider(IQueryable<SomeData> q)   {       _q = q;   }   public Dictionary<string, IEnumerable<DropdownOption>> GetDropdownOptions()   {       return new Dictionary<string, IEnumerable<DropdownOption>>()       {           {               "name", _q               .ToDropdownOption(x => x.Number.ToString(), x => x.Number)               .Distinct()               .ToList();           },           {               "date", _q               .ToDropdownOption(x => x.Date.ToString("d"), x => x.Date)               .Distinct()               .ToList();                      },           {               "string", _q               .ToDropdownOption(x => x.String, x => x.String)               .Distinct()               .ToList();           }       };   }}

Осталось написать вот такой обобщенный метод контроллера, который будет по названию типа искать соответствующий DropdownProvider и вызывать его метод.


[HttpGet][Route("Dropdowns/{type}")]public async IActionResult Dropdowns(     string type,      [FromServices] IServiceProvider serviceProvider     [TypeResolver] ITypeResolver typeResolver){   var t = typeResolver(type);   if (t == null)   {       return NotFound();   }   // Преобразование к dynamic, чтобы не париться с приведением типов.   // T неизвестен, потому что метод контроллера не содержит дженерика.   dynamic service = serviceProvider       .GetService(typeof(IDropdownProvider<>)       .MakeGenericType(t));   if (service == null)   {       return NotFound();   }   var res = service.GetDropdownOptions();   return Ok(res);}

Одновременные запросы


На этом можно было бы и закончить, но, как говорится, есть нюанс. В примере сверху запросы к БД выполняются последовательно, хотя они не зависят друг от друга. Чем больше колонок с фильтрами, тем больший выигрыш можно получить за счет параллельного выполнения запросов. Реализации IQueryable чаще всего базируются на той или иной ORM, а реализации Unit Of Work ORM часто не потокобезопасны (иначе слишком сложно было бы реализовать change tracking). Поэтому будем использовать отдельные области видимости (scope) ServiceProvider и асинхронные версии методов.


public static async Task<TResult> InScopeAsync<TService, TResult>(    this IServiceProvider serviceProvider,    Func<TService, IServiceProvider, Task<TResult>> func){    using var scope = serviceProvider.CreateScope();     return await func(        scope.ServiceProvider.GetService<TService>(),        scope.ServiceProvider);}

В итоге код DropdownProvider можно переписать в следующем виде:


public async Task<Dictionary<string, IEnumerable<DropdownOption>>>   GetDropdownOptionsAsync(){    var dict = new Dictionary<string, IEnumerable<DropdownOption>>();    var name = sp.InScopeAsync<IQueryable<SomeData>>(q => q        .ToDropdownOption(x => x.Number.ToString(), x => x.Number)        .Distinct()        .ToListAsync());    var date = sp.InScopeAsync<IQueryable<SomeData>>(q => q        .ToDropdownOption(x => x.Date.ToString("d"), x => x.Date)        .Distinct()        .ToListAsync());       var @string = sp.InScopeAsync<IQueryable<SomeData>>(q => q        .ToDropdownOption(x => x.String, x => x.String)        .Distinct()        .ToListAsync());    // Теперь все запросы выполняются параллельно    await Task.WhenAll(new []{name, date, @string}});    dict["name"] = await name;    dict["date"] = await date;    dict["string"] = await @string;    return dict;}

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


public async Task<Dictionary<string, IEnumerable<DropdownOption>>>    GetDropdownOptionsAsync(){     return sp        .DropdownsFor<SomeDataFilters>        .With(x => x.Number)        .As<SomeData, int>(GetNumbers)        .With(x => x.Date)        .As<SomeData, DateTime>(GetDates)        .With(x => x.String)        .As<SomeData, string>(GetStrings)}
Подробнее..
Категории: C , Net , Excel , Serviceprovider , Epression trees

Как выбрать 1 млн. записей из бд, записать в Excel и не упасть с OutOfMemoryError

01.06.2021 20:15:10 | Автор: admin

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

1. Постановка задачи

В связи с тем, что мне нельзя разглашать подробности ТЗ, сущности, алгоритмы сбора данных и т. д. Пришлось придумать что-то аналогичное:

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

У нас есть 3 таблицы:

  1. User. Хранит имя пользователя и его некий рейтинг (не важно откуда он берется и как считается)

  2. Message. Хранит данные о сообщении Имя пользователя, ДатуВремя, Текст сообщения.

  3. Task. Задача на формирование отчета, которую создает заказчик. Хранит ID, Статус задачи (выполнено или нет), и два параметра: Дату сообщения начало, Дату сообщения конец.

Состав колонок будет следующим:

В Excel Заказчик хочет видеть 4 колонки 1) message_date. 2) name. 3) rating. 4) text. Ограничение по количеству строк 1 млн. Надо заполнить этими данными excel, а дальше заказчик уже будет работать с этими данными в екселе самостоятельно.

2. Задача понятна, начнем поиск решения

Так как в компании все стараются придерживаться единого стиля в разработке приложений, то и мне пришлось начать с самого обычного подхода, который используется во всех остальных микросервисах это Spring + Hibernate для запуска приложения и работы с БД. В качестве БД используется Oracle, хотя использование любой другой СУБД будет плюс минус похожим.

Для старта приложения нам понадобится зависимость spring-boot-starter-data-jpa, которая объединяет в себе сразу Spring Data, Hibernate и JPA, все это нам понадобится для удобства работы с БД и нашими сущностями.

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-data-jpa</artifactId>            <version>2.4.5</version>        </dependency>

Для тестирования добавим spring-boot-starter-test

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>

И еще нам нужен сам драйвер для подключения к БД

        <dependency>            <groupId>com.oracle.database.jdbc</groupId>            <artifactId>ojdbc10</artifactId>            <version>19.10.0.0</version>        </dependency>

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

package com.report.generator.demo.config;import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.PropertySource;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.scheduling.TaskScheduler;import org.springframework.scheduling.annotation.EnableAsync;import org.springframework.scheduling.annotation.EnableScheduling;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configuration@EnableScheduling@EnableAsync@EnableJpaRepositories(basePackages = "com.report.generator.demo.repository")@PropertySource({"classpath:application.properties"})@ConditionalOnProperty(    value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)public class DemoConfig {    private static final int CORE_POOL_SIZE = 2;    @Bean(name = "taskScheduler")    public TaskScheduler getTaskScheduler() {        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();        scheduler.setPoolSize(CORE_POOL_SIZE);        scheduler.initialize();        return scheduler;    }}

Класс генерации отчетов содержит в себе @Scheduled метод, который раз в минуту ищет Task и, если находит, то запускает генерацию отчета с параметрами из этой таски.

    @Async("taskScheduler")    @Scheduled(fixedDelay = 60000)    public void scheduledTask() {        log.info("scheduledTask is started");        Task task = getTask();        if (Objects.isNull(task)) {            log.info("task not found");            return;        }        log.info("task found");        generate(task);    }

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

3. Выборка данных из БД

Т.к. в компании повсеместно используется Hibernate было решено использовать его. Добавлено entity MessageData с необходимым набором полей (id, name, rating, messageDate, test). Первой попыткой выбрать необходимые данные была попытка в лоб выгрузить все в List<Message> с помощью простого метода:

List<Message> findAllByMessageDateBetween(Instant dateFrom, Instant dateTo);

А дальше уже в цикле создавать объекты MessageData и обогащать их недостающими данными. Было очевидно, что данных подход в корне не верный и выгружать сразу миллион записей в List как минимум медленно. Но для эксперимента и замера скорости работы проверить хотелось, чтобы потом сравнить с другими вариантами. Но в результате данный набор записей выгружался около 30 минут после чего было получено OutOfMemoryError и на этом эксперимент завершился.

Даже если бы пользователь задал узкие рамки в параметрах и нам бы удалось выбрать все в один List, то дальше мы бы столкнулись со следующей проблемой для заполнения всех необходимых колонок нужно было бы собирать id пользователей, идти снова в базу, получать их имена и рейтинги, и заполнить уже с полными данными. Сложность такого алгоритма вырастала в разы. Было понятно, что выборку надо производить по частям и переложить все возможные действия с данными на сторону бд. Чтобы не выбирать все разом и, чтобы не городить велосипедов, было решено использовать ScrollableResults. Это позволяет нам получить ссылку на курсор и итерироваться по результатам с определенным шагом. Далее пришлось переписать запрос так, чтобы он возвращал сразу все необходимые данные уже после всех джойнов, объединений, группировок и т. д.

Следующий вопрос где хранить сам текст запроса. Это был не простая ситуация т.к. в действительности количество таблиц, которые участвовали в запросе было около десяти, количество джойнов и всяческих группировок было огромным, в результате чего текст запроса вышел на 200+ строк после ревью всевозможных коллег и утверждении самим тех лидом. Хранить такой запрос в java коде не хотелось, плюс в нем были захардкожены некоторые константы в условиях и светить ими в общем репозитории было бы неправильно. Для решения всех этих вопросов мне на помощь пришла идея использовать view. Весь текст запроса прекрасно туда вписывался, плюс на выходе мы получаем готовую сущность, с которой может работать hibernate как с обычной entity.

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

Оставался всего один вариант, который нам подходил это хранимая в бд функция. В нее можно передавать параметры, она может вернуть ссылку на курсор и ее результат можно удобно маппить на нашу entity. Таким образом была описана функция, которая принимала на вход несколько параметров, и возвращала sys_refcursor, весь скрипт занял около 300 строк в реальности, а в упрощенном варианте здесь она выглядит так:

create function message_ref(    date_from timestamp,    date_to timestamp) return sys_refcursor as    ret_cursor sys_refcursor;begin    open ret_cursor for        select m.id,               u.name,               u.rating,               m.message_date,               m.text        from message m                 left join users u on m.user_id = u.id        where m.message_date between date_from and date_to;    return ret_cursor;end message_ref;

Теперь как ее использовать? Для этого отлично подходит @NamedNativeQuery. Запрос для вызова функции выглядит следующим образом: "{ ? = call message_ref(?, ?) }", callable = true дает понять, что запрос представляет собой вызов функции, cacheMode = CacheModeType.IGNORE для указания не использовать кэш, т. к. скорость работы нам не так критична, как затрачиваемая память, ну и в конце resultClass = MessageData.class для маппинга результата на нашу entity. Класс MessageData выглядит следующим образом:

package com.report.generator.demo.repository.entity;import lombok.Data;import org.hibernate.annotations.CacheModeType;import org.hibernate.annotations.NamedNativeQuery;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Id;import java.io.Serializable;import java.time.Instant;import static com.report.generator.demo.repository.entity.MessageData.MESSAGE_REF_QUERY_NAME;@Data@Entity@NamedNativeQuery(    name = MESSAGE_REF_QUERY_NAME,    query = "{ ? = call message_ref(?, ?) }",    callable = true,    cacheMode = CacheModeType.IGNORE,    resultClass = MessageData.class)public class MessageData implements Serializable {    public static final String MESSAGE_REF_QUERY_NAME = "MessageData.callMessageRef";    private static final long serialVersionUID = -6780765638993961105L;    @Id    private long id;    @Column    private String name;    @Column    private int rating;    @Column(name = "MESSAGE_DATE")    private Instant messageDate;    @Column    private String text;}

Для того чтобы не использовать кэш было решено выполнять запрос в StatelessSession. Однако есть важная особенность: если попытаться вызвать namedQuery то hibernate при попытке установить CacheMode выдаст UnsupportedOperationException. Чтобы этого избежать необходимо установить два хинта:

            query.setHint(JPA_SHARED_CACHE_STORE_MODE, null);            query.setHint(JPA_SHARED_CACHE_RETRIEVE_MODE, null);

В итоге наш метод генерации имеет следующий вид:

 @Transactional    void generate(Task task) {        log.info("generating report is started");        try (            StatelessSession statelessSession = sessionFactory.openStatelessSession()        ) {            ReportExcelStreamWriter writer = new ReportExcelStreamWriter();            Query<MessageData> query = statelessSession.createNamedQuery(MESSAGE_REF_QUERY_NAME, MessageData.class);            query.setParameter(1, task.getDateFrom());            query.setParameter(2, task.getDateTo());            query.setHint(JPA_SHARED_CACHE_STORE_MODE, null);            query.setHint(JPA_SHARED_CACHE_RETRIEVE_MODE, null);            ScrollableResults results = query.scroll(ScrollMode.FORWARD_ONLY);            int index = 0;            while (results.next()) {                index++;                writer.createRow(index, (MessageData) results.get(0));                if (index % 100000 == 0) {                    log.info("progress {} rows", index);                }            }            writer.writeWorkbook();            task.setStatus(DONE.toString());            log.info("task {} complete", task);        } catch (Exception e) {            task.setStatus(FAIL.toString());            e.printStackTrace();            log.error("an error occurred with message {}. While executing the task {}", e.getMessage(), task);        } finally {            taskRepository.save(task);        }    }

4. Запись данных в Excel

На данном этапе вопрос с выборкой данных из БД был решен и возник следующий вопрос как теперь все это писать в excel так, чтобы это было быстро и не затратно по памяти. Первая попытка была самой очевидной это использование библиотеки org.apache.poi. Тут все просто: подключаем зависимость

        <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi-ooxml</artifactId>            <version>5.0.0</version>        </dependency>

Создаем XSSFWorkbook далее XSSFSheet, из него уже row и так далее. Ничего примечательного, примерный код ниже:

package com.report.generator.demo.service;import com.report.generator.demo.repository.entity.MessageData;import org.apache.poi.xssf.usermodel.XSSFCell;import org.apache.poi.xssf.usermodel.XSSFRow;import org.apache.poi.xssf.usermodel.XSSFSheet;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileOutputStream;import java.io.IOException;import java.time.Instant;public class ReportExcelWriter {    private final XSSFWorkbook wb;    private final XSSFSheet sheet;    public ReportExcelWriter() {        this.wb = new XSSFWorkbook();        this.sheet = wb.createSheet();        createTitle();    }    public void createRow(int index, MessageData data) {        XSSFRow row = sheet.createRow(index);        setCellValue(row.createCell(0), data.getMessageDate());        setCellValue(row.createCell(1), data.getName());        setCellValue(row.createCell(2), data.getRating());        setCellValue(row.createCell(3), data.getText());    }    public void writeWorkbook() throws IOException {        FileOutputStream fileOut = new FileOutputStream(Instant.now().getEpochSecond() + ".xlsx");        wb.write(fileOut);        fileOut.close();    }    private void createTitle() {        XSSFRow rowTitle = sheet.createRow(0);        setCellValue(rowTitle.createCell(0), "Date");        setCellValue(rowTitle.createCell(1), "Name");        setCellValue(rowTitle.createCell(2), "Rating");        setCellValue(rowTitle.createCell(3), "Text");    }    private void setCellValue(XSSFCell cell, String value) {        cell.setCellValue(value);    }    private void setCellValue(XSSFCell cell, long value) {        cell.setCellValue(value);    }    private void setCellValue(XSSFCell cell, Instant value) {        cell.setCellValue(value.toString());    }}

Но такой подход оказался не очень оптимальным. Примерно 3 минуты потребовалось на выборку 1 млн строк из бд и запись их в excel. И в итоге приводил к OutOfMemoryError. Вот пример:

А когда я выполнял его на терминалке с выделенной оперативной памятью в 2Gb, то падал он с OutOfMemoryError примерно на 30% прогресса.

Грузить весь миллион строк в память в excel было так же плохой идеей, как и выгружать весь запрос в List, очевидно, здесь надо было использовать некий stream, но хоть какой-то годный пример google тогда мне не дал. Была попытка написать свое подобие I/O Stream для работы с excel, но мысль о том, что я пишу велосипед не давала мне покоя. В результате я стал изучать библиотеку org.apache.poi пристальней и оказалось, что там уже есть пакет streaming. В этом пакете уже есть весь необходимый набор классов для работы с большим объемом данных в excel. Оставалось только заменить все ключевые классы на аналогичные из пакета streaming и все:

package com.report.generator.demo.service;import com.report.generator.demo.repository.entity.MessageData;import org.apache.poi.xssf.streaming.SXSSFCell;import org.apache.poi.xssf.streaming.SXSSFRow;import org.apache.poi.xssf.streaming.SXSSFSheet;import org.apache.poi.xssf.streaming.SXSSFWorkbook;import java.io.FileOutputStream;import java.io.IOException;import java.time.Instant;public class ReportExcelStreamWriter {    private final SXSSFWorkbook wb;    private final SXSSFSheet sheet;    public ReportExcelStreamWriter() {        this.wb = new SXSSFWorkbook();        this.sheet = wb.createSheet();        createTitle();    }    public void createRow(int index, MessageData data) {        SXSSFRow row = sheet.createRow(index);        setCellValue(row.createCell(0), data.getMessageDate());        setCellValue(row.createCell(1), data.getName());        setCellValue(row.createCell(2), data.getRating());        setCellValue(row.createCell(3), data.getText());    }    public void writeWorkbook() throws IOException {        FileOutputStream fileOut = new FileOutputStream(Instant.now().getEpochSecond() + ".xlsx");        wb.write(fileOut);        fileOut.close();    }    private void createTitle() {        SXSSFRow rowTitle = sheet.createRow(0);        setCellValue(rowTitle.createCell(0), "Date");        setCellValue(rowTitle.createCell(1), "Name");        setCellValue(rowTitle.createCell(2), "Rating");        setCellValue(rowTitle.createCell(3), "Text");    }    private void setCellValue(SXSSFCell cell, String value) {        cell.setCellValue(value);    }    private void setCellValue(SXSSFCell cell, long value) {        cell.setCellValue(value);    }    private void setCellValue(SXSSFCell cell, Instant value) {        cell.setCellValue(value.toString());    }}

Теперь сравним скорость обработки данных с этой библиотекой:

Вся обработка заняла пол минуты и, самое главное, никаких OutOfMemoryError.

5. Итог

В результате удалось добиться максимальной производительности за счет использования хранимой функции, StatelessSession, ScrollableResults и использования библиотеки org.apache.poi из пакета streaming. При большом желании можно улучшить производительность еще, если написать все на чистом jdbc, может быть есть еще варианты, как, что и где можно улучшить. Буду рад услышать комментарии от более опытных в этом экспертов. В данном примере не учтено ограничение на 1 млн. строк, т. к. это простая формальность и для примера не очень важна. Для наполнения БД тестовыми данными был добавлен тестовый класс DemoApplicationTests. Весь код можно посмотреть в репозитории на GitHub.

Подробнее..
Категории: Sql , Java , Excel

Ведение собственного бюджета в Excel проще чем два файла перебрать

06.02.2021 10:17:52 | Автор: admin
В первой части я описал основные принципы и подходы к своему бюджету. Можно вечно рассуждать много или мало я зарабатываю, на что именно трачу деньги, какие управленческие решения принимаю из таблицы учёта, и удобен ли Excel вообще для этого. Но это все-таки технический блог. Поэтому вторая часть будет посвящена технической стороне моего бюдж учёта финансов.
image

Вкратце напомню, как ведётся учёт сегодня, если уважаемый читатель не ознакомлен с первой частью. После нескольких месяцев проб и ошибок, было решено перейти к системе из трёх основных файлов:
  1. Файл DATA. Сюда вносятся все записи расходов-доходов. По сути, это самый важный файл всей системы, он регулярно бекапится и дублируется. Потому что формулы то я восстановлю, а вот чеки могут выгореть. В этом же файле хранятся категории расходов (ибо вносятся в таблицу расходов), типы счетов, виды расчётов и другая служебная часть. Здесь же считаются остатки по каждому счету, т.к. файл имеет минимум вычислений и загружается мгновенно.
  2. Файл BUDGET. Содержит в себе минимальные вычисления, показывает общий баланс и пару графиков. Используется для быстрой проверки гипотез, имеет небольшой вес, запускается быстро. Аналитики почти не включает, все выводы делаются самостоятельно.
  3. Файл BUDGET PRO. Вот здесь хранятся все вычисления, графики, таблички и прочая интересная математика. Прямо сейчас в книге 11 страниц, 9 сводных таблиц, 8 диаграмм, 17152 формул и много чего еще. VBA все еще не используется.
Каждый файл имеет свои тесты и зеркала. Но об этом чуть ниже.

DATA


Основой DATA является таблица с записями расход-доходов. Таблица состоит из
||Счёт||Категория||Сумма||Тип оплаты||Как оплачено||Дата||Что покупалось||Где покупалось||. Формул в таблице нет, но есть подсказки. Например, Счёт, Тип оплаты и Как оплачено можно выбрать из выпадающего списка. Счета подтягиваются из таблицы счетов, остальное из служебных таблиц. Сумма всегда указывается положительная. Категории не выбираются из списка (т.к. их слишком много) но там есть своя фича. В каждой новой строке категория проверяется на соответствие таблице Категорий и, если такой нет, ругается. На языке Excel это называется Проверка данных и позволяет исключить опечатки, ошибки склонений и другие сюрпризы. В принципе, можно настроить Проверку данных ещё и на Сумму или Дату покупки, но здесь проблем меньше.
Лайфхак
В прошлый раз спрашивали как вносить длинные чеки. Открыл для себя комбинацию клавиш CTRL+D копирует ячейку выше. Нажимаем несколько раз, и на новую запись мне нужно внести только Категорию, Сумму и Что покупалось. Нет ничего проще.

Служебные таблицы и таблицу Категорий расписывать не буду, тут вроде всё понятно. Давайте сразу к таблице счетов. ||Название||Валюта||Вид счёта||Начальный баланс||Текущий остаток|| ничего лишнего, все очень просто. Вид счёта тянется из служебной таблички (Наличные, Валюта, Эл. кошелёк и другие) и выбирается из списка, валюта пока используется как есть. Не исключаю таблицу с валютами при увеличении их количества, но пока не требуется. Было интересно сколько рублей живёт в валюте, начал искать возможность получать курсы автоматом с сайта ЦБ. Пока остановился на формуле
Формула курсов
=ФИЛЬТР.XML(ПОДСТАВИТЬ(ПОДСТАВИТЬ(ВЕБСЛУЖБА("http://www.cbr.ru/scripts/XML_dynamic.asp?date_req1="&ТЕКСТ(СЕГОДНЯ();"ДД.ММ.ГГГГ")&"&date_req2="&ТЕКСТ(СЕГОДНЯ();"ДД.ММ.ГГГГ")&"&VAL_NM_RQ=R01239");".";",");"=""1,0";"=""1.0");"//ValCurs//Record//Value")

Не самое изящное решение, но достаточно простое. Готов к другим вариантам, готов к обсуждению. Хочу простую формулу чтобы получать текущий курс от ЦБ и дальше уже работать с ним, памяти не надо, графиков и вот этого всего тоже. Только курс прямо сейчас.
Текущие остатки считаются тоже очень просто: Доход Расход + Перевод на Перевод с. Ну и плюс Начальный баланс, конечно же.
Формула остатков
=СУММЕСЛИМН(records[Сумма];records[Счет];[@Счёт];records[Тип];"Доход")-СУММЕСЛИМН(records[Сумма];records[Счет];[@Счёт];records[Тип];"Расход")+СУММЕСЛИМН(records[Сумма];records[Счет];[@Счёт];records[Тип];"Перевод на")-СУММЕСЛИМН(records[Сумма];records[Счет];[@Счёт];records[Тип];"Перевод с")+[@[Начальный баланс]]

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


Недостатки DATA


Самый большой сейчас недостаток курсы валют. По воскресеньям ЦБ не публикует курсы, поэтому формула отдаёт ошибку. Чтобы побороть её надо переписать формулу. В поисках вдохновения или подсказок.
Второе непонятно что делать с закрытыми счетами. Удалять их из таблицы счетов неправильно, тогда и таблица записей сломается. Болтаться им в общей таблице счетов тоже не к месту. Фильтр скрывает их, но не решает проблемы. Создавать отдельную таблицу с закрытыми счетами тоже не очень. В пассивном поиске решения.
Ну и самое странное для меня при операциях с финансами, Excel начинает считать суммы типа 0,999999999999999999999 вместо 1, E-12, -0 и другие. Форматирование как денежный тип данных решает отображение ячеек, но в формулах такие значения ломаются. Например, 0,00-(-2,27374E-13) не считается как равное нулю, хотя и является таковым. И таких ошибок все больше и больше в моих таблицах.
Лайфхак
Вместо тестовых счетов и записей очень эффективно оказалось тестирование пустыми данными. Служебные таблицы остаются не тронутыми, а таблица записей и таблица счетов вычищаются полностью. Такое простое решение позволило мне исправить несколько существенных ошибок.


BUDGET


Как я уже говорил, BUDGET содержит в себе минимальные вычисления. Если не нужна аналитика, но хочется посмотреть какие-то существенные вещи открывается этот файл. Прямо сейчас здесь всего 3 листа: таблица записей, общий баланс (формальный остаток) + распределение денег по счетам и график движения в периоде. Практически весь файл считается и обновляется автоматически, я его только просматриваю. Ну и дорабатываю, но это не относится к финансам.
Изначально, в файл BUDGET через PowerQuery подгружаются таблица Счетов и таблица Записей. При этом, таблица Счетов практически не обрабатывается: сортировкой по сумме остатка можно пренебречь, да и спорное это решение. Чуть ниже расскажу зачем я это сделал. Таблица Записей же обрабатывается сильнее. Для начала, все записи сортируются по дате операции. Потому что записи вносятся не всегда сразу, не в хронологическом порядке, сортировка записей в DATA выполняется от случая к случаю. Поэтому, для контроля, мне проще сортировку выполнять при первичной обработке. Вторым этапом меняется столбец Суммы операции с числового на валютный. Столбец Даты операции сам хорошо распознается как дата, остальные столбцы размечены как текстовые. В третий этап добавляется столбец с индексом записей и в таком виде таблица выгружается на лист. Справедливости ради, к таблице Записей в BUDGET я практически не обращался, в основном работаю с ней в DATA.
Лайфхак
В определённый момент было решено сортировку по индексу делать в обратном порядке. Таким образом, последние операции у меня всегда вверху таблицы, это оказалось банально удобнее.

График движения


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

Таблица счетов


Ну и пожалуй самый важный лист в этом файле Счета. Но и здесь не очень много анализа. Как я уже говорил, таблица Счетов практически без изменений подгружается на лист из DATA. Чтобы мне не мешали лишние данные, несколько столбцов сразу же скрываются, например Начальный остаток и Валюта. Казалось бы, зачем их сперва подгружать, а затем прятать? Дело в том, что эти столбцы участвуют в вычислениях. Поэтому, они все равно нужны, хоть я и не хочу их видеть.
Лайфхак
Пока таблица Счетов была статична обозначение валюты стояло приколоченным к каждой ячейке. Но после включения сортировки пришлось искать другой способ. И дело было даже не столько в самом символе валюты, скорее в общем формате. Победить обе проблемы получилось через условное форматирование по формуле. Если валюта в таблице указана как RUR, формат ячейки показывается как 0,00 . Аналогично и для других валют.

На основании Текущих остатков таблицы Счетов строится круговая диаграмма распределения денег. Таким образом, я сразу же вижу на каком счёте какая часть денег находится. Для этого и понадобилось сортировать таблицу Счетов по Текущему остатку части диаграммы тоже отсортированы по возрастанию, а не в хаотичном порядке.
Кроме того, в отдельных ячейках считается весь текущий остаток в разрезе валют. Это нужно для понимания сколько у меня денег вообще в данный момент. Формула максимально простая: =СУММЕСЛИ(accounts[Валюта];"RUR";accounts[Текущий Баланс])
Лайфхак
Когда я начал замечать что где-то расчёты не совпадают, решил сам себя перепроверить. Так, рядом с суммой текущего остатка по таблице Счетов появились суммы текущего остатка по таблице Записей. Сравнить глазами три пару чисел не так сложно. Ну или можно добавить ещё условного форматирования.

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

Использование Decision Table в JBoss Drools

18.12.2020 00:05:23 | Автор: admin

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

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

Что это вообще такое?

Довольно часто в процессе разработки коммерческого софта мы сталкиваемся с проблемой, когда некоторую логику расчета необходимо передать на сторону заказчика. Зачастую представитель этого заказчика не хочет (или не может) использовать какой-либо язык программирования для описания необходимой логики работы приложения. В таких случаях на помощь приходят BRMS или Business Rule Management System. Это информационная система для создания, управления и исполнения той самой
бизнес-логики приложения, её также называют бизнес-правилами. Обычно такие системы состоят из сервера, на котором происходит выполнение правил - это юрисдикция программистов, и средств ведения самих правил - это уже зона ответственности бизнеса.

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

BRMS фреймворков на рынке достаточно много. Свои решения предлагают многие крупные компании: IBM, Red Hat, Agiloft, SAS и даже Bosch. Все они либо платные, либо не подходили нам по разным критериям. Мы решили начать с уже зарекомендовавшей себя системы JBoss Drools. Она достаточно надежная, проверенная временем, используется в банковских решениях, ритейле и телекоме, а также предоставляет возможность ведения бизнес-правил как на специальном языке DRL, так и с помощью Excel-таблиц. Существует также и несколько UI-решений для ведения правил. Так как наши аналитики используют единую систему ведения справочников и нам удобнее хранить правила там, от использования UI мы отказались в пользу понятных бизнесу Excel-таблиц.

Что же такое бизнес-правило и как оно выглядит?

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

У нас есть респондент, который обладает характеристикойГендерная идентичность (gender). В зависимости от значения этой характеристики респонденту выставляетсяПол (SEX).То есть, еслиgender = male, то значению свойства Пол нужно будет поставить1. В противном случае это будет2. На языке DRL это правило будет выглядеть следующим образом:

rule "Rule 1 Example 1"      when         $s: Respondent($s.gender == "male")      then         $s.addResult("SEX", "1");end rule "Rule 2 Example 2"      when         $s: Respondent($s.gender == "female")      then         $s.addResult("SEX", "2"); end

Таким образом, мы имеем некий синтаксис, очень похожий на языкGherkin. В нашем случае у нас есть два правила, которые состоят из условия (указанного после ключевого словаwhen) и результата, которой будет получен в ходе выполнения этого условия (идет после словаthen).Условия в терминологииDroolsпринято называтьLeftHandSide(илиLHS), а действия -RightHandSide(илиRHS). Стоит упомянуть еще об одном объекте:Respondent.Этоfact,то есть объект в текущей памятиDrools, над которым будут производиться те или иные преобразования. В нашем случае у этого объекта присутствуют свойстваgenderиresult. Для того, чтобы было удобнее работать с объектами,frameworkпредусматривает возможность введения переменных. Обычно переменные начинаются с символа$.

Вот так это правило будет выглядеть на языкеExcel-таблиц:

Пример правила в ExcelПример правила в Excel

Как создать таблицу правил?

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

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

Область настройки правилОбласть настройки правил

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

  • RuleSet здесь указывается имя пакета для сгенерированного файлаDRL. Этот параметр обязательно должен стоять первым

  • Import через запятую указываются факты с указанием пакета, а также такиеJava-классы, которые можно использовать в расчетах, например,java.lang.Math

  • Functions здесь можно описывать функции, которые будут работать в рамках данногоRuleSetа. Функции должны соответствоватьDRL-синтаксису.

В области описания правил тоже есть свои зарезервированные слова, при этом главным словом являетсяRuleTable, которое указывает на то, что таблица ниже соответствует таблице правил, которую движокDroolsдолжен преобразовать вDRL-синтаксис. Опционально можно указать название для таблицы правил. В нашем случае этоnameforRuleTable.

Область описания правилОбласть описания правил

Начиная со следующей строки идут колонки:

  • NAME имя правила. Его можно не указывать.

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

  • CONDITION это тотLeftHandSideили указание условия, на основании которого будет выполненACTION. Этот параметр обязателен.

  • ACTION действие, которое необходимо применить к факту. В нашем случае это методaddResult, который добавляет вMapзначения результирующих переменных. Этот параметр обязателен. В блокеACTIONвыполняетсяJava-код, поэтому наличие точки с запятой здесь также обязательно. Также через точку с запятой можно указать сколько угодно методов.

    Следует отметить, что количествоCONDITIONиACTIONможет быть больше одного.

Как отмечалось ранее, строкой ниже идет присваивание переменной$sфактаRespondent. Отмечу, что его полное имя с названием пакета, в котором он находится, нужно обязательно указать в параметреImport. Если у нас несколькоCONDITIONотносятся к одному факту, тогда ячейки с фактом необходимо объединить в одну. Также мы можем работать с разными фактами в разныхCONDITION-колонках: для этого мы просто указываем новый факт и новую переменную, не забыв добавить его вImport.

Далее, на следующей строке, у нас идет описание самих правил в колонкеCONDITIONи действий в колонкеACTION, которые необходимо выполнить. Какие могут быть условия и действия к выполнению, я описал ниже. А пока перейдем к следующей строке. Это строка заголовков полей (Text-Parameter-Resultна картинке). Ее указывать обязательно. В противном случае те переменные/условия, которые будут указаны вместо этой строки,Droolsпроигнорирует. Далее у нас идет указание самих условий.

Какие могут быть условия?

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

Для начала стоит упомянуть еще раз, что все манипуляции мы будем производить над объектомRespondent. В нашей терминологии респондент это лицо, которое принимает участие в исследовании. У каждого респондента есть свой набор свойств (например, гендерная принадлежность, рассмотренная ранее). Для того, чтобы показать все многообразие условий, с которыми работаетDrools, я вынес каждое свойство в отдельное поле классаRespondent. Для простоты понимания, в качестве еще одного поля класса добавляемMap<String,String>result, в котором будем собирать все результирующие переменные. Таким образом, классRespondentбудет выглядеть так:

public class Respondent {    public int id;    public String gender;    public Boolean isActive;    public Integer age;    public List<String> pets;    public String city;    public List<String> devices;    public Map<String, String> properties;    public Car car;    public MobileBrand mobileBrand;    public Household household;    public Map<String, String> result = new HashMap<>();    public void addResult(String key, String value) {        result.put(key, value);    }//  getters, setters, constructor}

Самое простое правило, по определению гендерной принадлежности, мы уже рассмотрели. А что, если нам нужно определить половой признак только у тех респондентов, которые участвуют в исследовании? Для фильтрации таких респондентов мы будет использовать булеву переменнуюisActive.Значениеtrue респондент участвует в исследовании,falseнет.

Правила опредения пола респондентаПравила опредения пола респондента

На рисунке выше показано, как мы объединили два условия: активность респондента и его половой признак. Не трудно догадаться, что в таком случае происходит объединение правил, находящихся в одной строке, по логическомуИ. Два условия объединены одним фактомRespondent. Вот так выглядит описание правила на языкеDRL:

rule "name_for_RuleTable_20"when$s: Respondent(isActive == true, gender == "male")then$s.addResult("SEX", "M");endrule "name_for_RuleTable_21"when$s: Respondent(isActive == true, gender == "female")then$s.addResult("SEX", "F");end

Стоит отметить использование параметра$param-этот параметр работает в рамках одного столбца и во время компиляции правил он будет заменен на конкретное значение из ячейки. То есть условиеisActive== $paramбудет преобразовано вisActive==true. В случае работы с булевыми или целочисленными переменными экранировать его не надо. ДвижокDroolsпонимает, что это не строка. В случае работы со строками экранирование обязательно, как показано в примере со свойствомgender.

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

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

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

Еще один пример, где кавычки не требуются это перечисления. Допустим, каждый из респондентов обладает мобильным телефоном. В зависимости от того, какого бренда телефон, мы запишем в результирующую переменнуюPHONE_SALES_PERзначение процента продаж на мировом рынке мобильных устройств за 2019 год. Так как результирующий словарь в качестве значения принимает строку, то$paramнеобходимо экранировать. Также стоит отметить, чтоMobileBrandнеобходимо добавить в настройкуImportв области описания правил.

Правила определения процента продажПравила определения процента продаж

Иногда встречаются такие условия, когда одинаковое действие нужно применить к разным условиям. Такого поведения можно добиться, если в колонкеACTIONдублировать значения$param, то есть создавать отдельные правила с разными условиями и одинаковыми результатами. Но это будет загромождать нашу таблицу. Для таких случаев предусмотрена специальная конструкцияin:

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

В данном примере у нас будет два правила: если бренд мобильного телефона нашего респондента маркиSAMSUNGилиAPPLE, тогда в результирующую переменную мы запишем значениеPREMIUM. В случае, когда бренд телефонаHUAWEIилиXIAOMI, значение будетNOT_PREMIUM. (Прошу владельцев данных смартфонов меня извинить за то, что отнес ихкнепривилегированным :) ) Другими словами, мы выбираем значение из массива значений.

А если у нас обратная задача, когда мы имеем массив значений и при вхождении конкретного значения выполняется действие? В подобном случае мы используем конструкциюcontains. Эта конструкция работает со всеми объектами интерфейсаjava.util.Collection. В нашем примере у объектаRespondentесть коллекция домашних животныхpets.Мы сделали выборку тех респондентов, которые не достигли совершеннолетия и указали в своих ответах в качестве домашнего животного кошку или собаку. Третье правило сработает в том случае, если в семье есть и собака, и кошка. Так как домашнее животное это строковая переменная, то переменную$param мы заключаем в кавычки.

Правило определения домашнего животногоПравило определения домашнего животного

В следующих двух примерах мы рассмотрим еще один оператор работы с коллекциями:forall(<оператор>){<условие>}.В первом примере правило сработает в случае выполнения условия в фигурных скобках и хотя бы одного из перечисленных через запятую параметров в ячейке. Во втором примере должно выполниться условие в фигурных скобках со всеми параметрами, перечисленными через запятую в соответствующей ячейке. Другими словами, в первом примере объединение условий происходит через логическоеИЛИ, тогда как во втором черезИ. Это условие и указывается в фигурных скобках. Важно отметить, что переменная, которая будет обращаться к значению ячейки, в данном случае указывается без словаparam. Еще один интересный момент, который я хотел бы отметить в примерах ниже это отсутствие ссылки на факт ($s). Дело в том, что если мы работаем с одним фактом, тоDroolsпонимает, что мы обращаемся полям класса-факта (cityиdevices), поэтому указание переменной в описанииCONDITIONможно опустить.

Правила определения округа и интернет-активностиПравила определения округа и интернет-активности

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

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

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

Правила определения образованияПравила определения образования

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

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

Достаточно удобно объединять вложенные объекты и их наборы свойств, которые представлены в виде словаря. В примере, описанном ниже, у объектаCar,который является частью фактаRespondent,естьMap<String,String>properties с набором свойств. В данном наборе правил мы обращаемся к свойствуPOWER. Также в описании условий правил присутствует функцияDroolsgetValue. Ее реализацию необходимо вынести в блокFunctions в области настройки правил:

Описание функции getValueОписание функции getValue

И само правило:

Правила определения мощности автомобиляПравила определения мощности автомобиля

В заключение описания возможных реализаций условий хотелось бы показать комплексный пример использования словаря во вложенном объекте, где в качестве значения по ключу используется массив данных. Условие задачи звучит следующим образом: необходимо отобрать тех респондентов, у которых в домохозяйстве есть приставкаPlayStation. Другими словами, у дочернего объектаHouseholdфактаRespondentнеобходимо найти свойствоTVDevicesи проверить наличие приставкиPlayStation. Правило будет выглядеть следующим образом:

Правило определения наличия PlayStationПравило определения наличия PlayStation

Как все это применять?

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

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

  • setустанавливает значение свойства факта, но не уведомляет движокDroolsоб этом;

  • updateуведомляет движок о том, что факт был изменен посредством одного или несколькихset;

  • modifyэто некая сумма двух предыдущих действий, здесь происходит установка одного или нескольких свойств факта, а также уведомляется движок об изменении факта;

  • insertдобавление нового факта.

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

В первомRuleTableмы производим отбор тех активных респондентов, у которых по каким-то причинам возраст больше 99 лет и меньше 0. Если такие респонденты найдены, то выражение изменит факт$r, то естьRespondentaи установит ему значениеfalse.В следующемRuleTableу нас будет уже измененное свойствоisActiveи неактивным респондентам в свойствеREJECTEDзапишется значениеTRUE.

В заключение

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

Информацию по запускуDroolsи работе с ним можно с легкостью найти в интернете или же форкайте мой проект с примерами на гитлаб (https://github.com/sxexesx/drools-decision-table).

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

Подробнее..

Книга Бизнес-моделирование и анализ данных. Решение актуальных задач с помощью Microsoft Excel. 6-е издание

19.01.2021 12:23:47 | Автор: admin
image Привет, Хаброжители! Уэйн Винстон научит вас быстро анализировать данные, принимать решения, подводить итоги, составлять отчеты, обрабатывать данные и строить аналитические модели в Microsoft Excel 2019 и Office 365. В новом шестом издании вас ждут более 800 бизнес-задач, основанных на реальных ситуациях, а также обсуждение новых инструментов и функций. Где бы вы ни работали в крупной корпорации, небольшой компании, государственной или некоммерческой структуре, это поможет вам увеличить прибыль, снизить издержки или эффективно управлять производством. Прочитав эту книгу, вы сможете cпрогнозировать результаты выборов, научитесь определять точки безубыточности, рассчитывать вероятность выигрыша в кости или победы любимой команды в турнире. Хотите обогнать конкурентов? Решайте в Excel реальные задачи!

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


Обсуждаемые вопросы

  • Как загрузить актуальный курс биткойна и сделать так, чтобы эти данные обновлялись каждый день?
  • Как загрузить цифры текущего населения городов США?

Бизнес-аналитикам нередко нужен простой способ импортировать в Excel данные из интернета, текстового файла, базы данных или другого источника. Эти данные нужно упорядочить или обработать. Наконец, импортированные данные должны обновляться, не отставать от изменений в их источнике. В этой главе мы познакомим читателя с потрясающими возможностями инструмента Excel 2019 Получить и преобразовать данные (Get & Transform), который позволяет аналитикам эффективно импортировать, по-новому упорядочивать и преобразовывать данные. Как видно из рис. 40.1, начиная с Excel 2016 инструмент Получить и преобразовать данные находится непосредственно на вкладке Данные (Data).

image

Как показано на рис. 40.2 и 40.3, нажав на кнопку Получить данные (Get Data), вы увидите подробный список источников данных, поддерживаемых инструментом Получить и преобразовать данные.

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

image

image

Ответы на вопросы


Как загрузить актуальный курс биткойна и сделать так, чтобы эти данные обновлялись каждый день?

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

Для начала нам нужен веб-адрес, по которому находятся ежедневные курсы биткоийа. К счастью, эти сведения есть на Yahoo Finance. Нужный нам URL finance.yahoo.com/quote/BTC-USD/history. Если вам нужны сведения по акциям (например, Microsoft), просто замените текст после /quote условным биржевым обозначением (тикером). Например, цены на акции Microsoft можно импортировать с адреса finance.yahoo.com/quote/MSFT/history. В пустой книге Excel нажмите Получить данные (Get Data) на вкладке Данные (Data), в разделе Получить и преобразовать данные (Get & Transform) на ленте и выберите Из других источников (Other Sources). Выбрав Из Интернета (From Web), заполните диалоговое окно, как показано на рис. 40.4. Вот вы и создали поисковый запрос! Нажав OK, вы увидите список всех таблиц, содержащихся по указанному веб-адресу (рис. 40.5). Щелкнув по таблице 2 (Table 2), вы увидите предпросмотр того, что будет импортировано. В нашем случае таблица 2 содержит необходимую информацию о курсе биткойна.
image

image

Теперь, если хотите, можете выбрать Загрузить (Load) и сразу же загрузить данные в свою книгу. Мы же, однако, вместо этого решили упорядочить импортируемые данные иным образом, поэтому выбираем Преобразовать данные (Edit), что вызывает окно Редактор Power Query (Power Query Editor), представленное на рис. 40.6.

image

Допустим, вам нужно импортировать только столбцы Дата (Date) и Скорректированная цена закрытия (Adj Close). Тогда с помощью клавиши Control мы выделяем столбцы, которые хотим удалить. Щелкнув правой кнопкой мыши, выберите Удалить столбцы (Remove Columns), и у вас останутся только столбцы Дата (Date) и Скорректированная цена закрытия (Adj Close). Или, предположим, вам также надо импортировать неделю года. Для этого сначала выполните щелчок правой кнопкой мышки по столбцу Дата (Date) и выберите Создать дубликат столбца (Duplicate Column). Выполнив на нем щелчок правой кнопкой мышки, выбираем Переименовать (Rename) и переименовываем дубликат столбца с датами именем Неделя года (Week of Year).

Выделив столбец Неделя года (Week Of Year), выберите из контекстного меню Преобразование (Transform), затем Неделя (Week) и Неделя года (Week Of Year). Как видим на рис. 40.7, теперь у нас есть столбец Неделя года (Week Of Year).

Теперь мы готовы импортировать нужные нам данные в Excel. Просто выберите Закрыть и загрузить (Close And Load) на вкладке Главная (Home). Вы увидите курс биткойна за 100 последние дней, как показано на рис. 40.8 и в файле Bitcoinquery.xlsx.

image

Чтобы в любой момент обновить данные, просто поместите курсор в ячейку внутри импортированных данных, выполните щелчок правой кнопкой мышки и выберите Обновить (Refresh). Если вы хотите, чтобы информация обновлялась через заданные промежутки времени или при каждом открытии файла, то выберите Обновить все (Refresh All) на вкладке Данные (Data), в группе Запросы и подключения (Queries and Con nections), выберите Запросы и подключения и затем на появившейся панели в Запросах щелкните по Table 2 и из контекстного меню выберите Свойства. Теперь в диалоговом окне Свойства запроса (Query Properties) вы можете настроить параметры его обновления. Как показано на рис. 40.9, мы задали период обновления каждые 60 минут.

image

Как загрузить цифры текущего населения городов США?

Допустим, вы хотите импортировать в Excel население 100 крупнейших городов США. Необходимые сведения содержатся на сайте worldpopulationreview.com/us-cities. Применяя тот же метод, что мы использовали для загрузки курсов биткойна, мы выбрали Таблицу 0 (Table 0) и получили результаты, представленные на рис. 40.10.

image

Предположим, мы хотим, чтобы город и штат находились в одном столбце, а население каждого города в другом. Также предположим, что иные сведения нам не нужны, и мы не хотим их импортировать. Чтобы добиться этого, нажимаем Преобразование (Transform), удалим с помощью клавиши Control последние четыре столбца. Затем выбираем столбцы Город (City) и Штат (State). Выбираем Объединить столбцы (Merge Columns) из контекстного меню и выбираем запятую в качестве символа-разделителя между городом и штатом. Переименование получившегося столбца предложено выполнить здесь же называем его Город и штат (City and state). Теперь из меню Файл (File) мы сможем загрузить требующиеся нам данные и поисковый запрос в файл UScityquery.xlsx. Конечный результат представлен на рис. 40.11.

Если вы хотите увидеть, из каких шагов состояло выполнение нашего интернет-запроса, поместите курсор в ячейку внутри импортированных данных и выберите Запрос (Query) в правой части меню ленты. Щелкнув Редактировать (Edit) в правой части экрана, вы увидите шаги, которые потребовались для реализации вашего запроса (рис. 40.12). Панель Параметры запроса также, как правило, отображается сама где мы видим Примененные шаги. Разумеется, выбрав Закрыть и загрузить (Close And Load), вы вернетесь к книге Excel.
image


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

Для Хаброжителей скидка 25% по купону Microsoft Excel

По факту оплаты бумажной версии книги на e-mail высылается электронная книга.
Подробнее..

Категории

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

  • Имя: Макс
    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