Commit 964c5ce3 authored by Andrey S. Petrov's avatar Andrey S. Petrov
Browse files

Initial commit

parents
{
"presets": ["es2015-loose", "stage-0", "react"]
}
db
static
media
.git
sql
nash_region/config
PROJECT_NAME=pos
DOCKER_COMPOSE_PROJECTS_PATH=D:\PycharmProjects
\ No newline at end of file
elasticsearch/data/nodes
elasticsearch/data/elasticsearch
webpack-stats.json
*.swp
.idea/*
*.pyc
*.bak
__pycache__
debug.log
easy_thumbnails.log
*.pid
nash_region.sock
*.sock
.pytest_cache
chromedriver.exe
chromedriver
.cache
FROM python:3.7-buster
RUN apt-get update && \
apt-get install -y \
apt-utils \
git \
gcc \
libjpeg62-turbo-dev \
zlib1g-dev \
openssh-server \
default-libmysqlclient-dev \
gettext \
libxml2-dev \
libxslt-dev \
libssl-dev \
swig \
jpegoptim \
optipng \
redis-tools \
supervisor \
nano
# Install Nodejs for style editor
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && \
apt install -y nodejs && \
npm install -g less less-plugin-clean-css
RUN mkdir /app \
/django-edw \
/var/log/app
RUN touch /var/log/app/debug.log && \
chmod 777 /var/log/app/debug.log
# Avoid cache purge by adding requirements first
ADD ./requirements/common.txt /app/common.txt
ADD ./requirements/production.txt /app/production.txt
WORKDIR /app/
RUN pip install -r production.txt
COPY ./build/supervisor.conf /etc/supervisor/conf.d/nash_region.conf
COPY ./build/translations /app/translations
# Общее описание решения
Проект является цифровым решением в рамках конкурса World AI&DATA Challenge, соответствующиим задаче "Интеллектуальный алгоритм автоматизированной обработки обращений граждан" (https://git.asi.ru/tasks/world-ai-and-data-challenge/intelligent-algorithm-for-automated-processing-of-citizenss-requests)
# Общее описание логики работы решения
Система состоит из:
1. Единое цифровое окно обратной связи
2. Единая интеллектуальная система автоматизированной обработки обращений граждан
3. API для интеграции систем региона
![Схема взаимодействия](https://git.asi.ru/infolabs/pos/raw/master/images/общая_схема_АСИ.jpg)
**1. Единое цифровое окно обратной связи**
[Прототип окна обратной связи](https://www.figma.com/file/jd1iXWbqSmzUOQTjQeVPet/%D0%9E%D0%A1%D0%9A?node-id=0%3A1)
Виджет позволяет гражданам России обращаться с сообщениями с любого сайта (Администрации муниципального образования, регионального СМИ, официальных сайтов энергетики, библиотек и тд). В виджете предусмотрена авторизация пользователя через соц. сети и ЕСИА. Для точного определения категории, тематики и ответственного исполнителя необходимо указать на карте адрес проблемы и описать проблему. В режиме реального времени определяются 5 категорий проблемы в порядке убывания вероятности соответствия. Если ни одна категория не подошла, пользователь сможет указать категорию вручную из классификатора тематик. Далее вся информация о сообщении передается на соответствующий портал региона, который занимается обработкой сообщений.
**2. Единая интеллектуальная система автоматизированной обработки обращений граждан**
**Справочники:**
* Категории сообщений
* Ответственные исполнители
* Структура регионального деления (городские округа, районы, сельские поселения и т.д.)
**Входные данные:**
* Текст сообщения
* Адрес проблемы и/или гео координаты
* Фотоматериалы
* Контактная информация пользователя
**Выходные данные:**
* Категория сообщения
* Ответственный исполнитель
* Регион
* Шаблон ответа
* Текст сообщения
* Адрес проблемы и гео координаты
* Фотоматериалы
* Контактная информация пользователя
**Аналитическая отчетность по:**
* Временной интервал
* Одному или нескольким исполнителям
* Регионам
* Категориям
**3. API для интеграции различных систем**
Интерфейс REST API использует универсальный формат JSON, который в сочетании с функционалом витрин данных (упрощенно, динамически настраиваемых наборов правил, обеспечивающих фильтрацию данных по множеству параметров) предоставляет широкие возможности для интеграции. Так, возможна передача обращений только конкретной тематики или с привязкой к определенной территории.
![Пример представления данных](https://git.asi.ru/infolabs/pos/raw/master/images/20200219_093876.jpg)
**Шаги решения/подходы**
*Классификаторы*
Существует несколько видов общероссийских классификаторов, которые подходят для классификации обращения граждан:
* Общероссийский классификатор;
* Классификатор МинЖКХ;
* Классификатор ПОС.
Наше решение позволяет классифицировать сообщения по нескольким видам классификаторов, в том числе можно добавить аналитику, которая будет отвечать за внутренний региональный классификатор. Таким образом будет поддерживаться дальнейшая сквозная интеграция между различными системами.
*Шаблоны ответов*
На основе собранных данных:
* Категория сообщения
* Ответственный исполнитель
* Текст сообщения
* Адрес проблемы и гео координаты
* Контактная информация пользователя
* Дата сообщения
* Текущий статус сообщения
Возможно формирование шаблона ответа для ответственных исполнителей.
Мы предполагаем, что данный функционал востребован в ситуации, когда в течение некоторого промежутка времени сообщения идут по одному адресу и на одну и ту же проблему, которая находится в работе и не закрыта.
В ситуации, когда проблема находится в статусе "Закрыто", а на нее повторно поступают жалобы, применение шаблонных ответов нецелесообразно.
Анализ предоставленных данных не позволяет полноценно обучить алгоритм подбору готовых шаблонов ответов, так как не все регионы предоставили адреса проблем, а варианты ответов есть только у 20 сообщений по Ханты-Мансийскому АО.
Наиболее полные данные предоставлены по республике Татарстан. Анализ данных показал, что по одному адресу возникает не более 2-3 проблем в одной категории. При этом дата подачи сообщений отличается на 2-6 недель. Так как нет истории изменения статусов по сообщениям, поэтому невозможно провести анализ наложения событий.
Для решения задачи автоматического формирования шаблонов ответов, мы предлагаем следующее решение - после занесения сообщения в систему автоматически формируется
подборка сообщений по этой же тематике и адресу. Среди них можно выбрать уже готовые ответы ответственных исполнителей. Функционал доступен как в интерфейсе пользователя,
так и возможно настроить, чтобы варианты ответов передавались по API. Так же этот функционал позволяет определять критические тематики и места.
**Структура данных предметно-ориентированного хранилища**
![Структура данных предметно-ориентированного хранилища](https://git.asi.ru/infolabs/pos/raw/master/images/photo_2020-06-30_16-45-28.jpg)
[Исходный файл со схемой](https://git.asi.ru/infolabs/pos/raw/master/images/Структура_данных_предметно_ориентированного_хранилища_EDW__1_.xmind.zip)
**Общая схема функционирования решения**
![Общая схема](https://git.asi.ru/infolabs/pos/raw/master/images/схема_определения_тематики.jpg)
# Требования к окружению для запуска продукта
**Платформа:** кроссплатформенное решение.
Docker 2.3.0.3
Используемый язык программирования с указанием версии, если существенно.
**Backend:** Python 3.7, Django Framework 1.11.28, Redis 3.4.1., ElasticSearch >=5,<6
# Сценарий сборки и запуска проекта
**Разворачивание и настройка проекта**
1.1 Установить Docker, провести первичную настройку программы, разрешить доступ к диску в пункте настроек Shared Drives. Убедиться, что процессор локальной машины позволяет использовать режим виртуализации
1.2 Скачать содержимое данного репозитория, открыть файл .env проекта pos и заменить в нём значение переменной окружения DOCKER_COMPOSE_PROJECTS_PATH на путь до директории, в которой размещен проект pos
1.3 В консоли перейти в директорию проекта, запустить команду сборки без кэша `docker-compose build --no-cache`
1.4 Запустить контейнеры проекта: `docker-compose up -d`
1.5 Подключиться к контейнеру базы данных: `docker exec -it pos_db_1 bash`
1.6 Подключиться к mysql: `mysql -u root -p`, пароль не задан, ввод пропустить, нажав enter
1.7 Создать новую базу данных: `CREATE DATABASE nash_region_db CHARACTER SET utf8 COLLATE utf8_general_ci;`, вернуться в контейнер: `exit`
1.8 Восстановить дамп базы данных: `mysql -u root -p nash_region_db < /tmp/pos.sql`, вернуться в контейнер: `exit`
1.9 Зайти в контейнер приложения: `docker exec -it pos_app_1 bash`
1.10 Запустить сборку статического контента: `python manage.py collectstatic`, подтвердить `Y` - enter
1.11 Восстановить недостающие файлы: `python manage.py create_lost_filer_files_from_default_file`
1.12 Восстановить недостающие изображения: `python manage.py create_lost_filer_images_from_default_image`
1.13 Запустить обновление поисковых индексов: `python manage.py rebuild_index`, подтвердить `Y` - enter
1.14 Запустить систему по фону: `supervisorctl start all` , выйти из контейнера: `exit`
1.15 Открыть портал в браузере, проект будет доступен по адресу `127.0.0.1`
**Тестирование массового определения тематики сообщений**
2.1 Открыть директорию проекта, добавить в каталог `/app/media/uploads` файл с тестовыми данными в формате .csv следующей структуры:
* порядок столбцов: 'Геопозиция', 'Адрес', 'Оригинальная категория', 'Текст обращения'
* первая строка является заголовками столбцов
* тексты обращений заключены в двойные кавычки, внутри обращения эти символы должны быть исключены
* разделителем полей является точка с запятой, её использование в содержимом полей также недопустимо
2.2 В консоли перейти в директорию проекта pos и подключиться к контейнеру приложения `docker exec -it pos_app_1 bash`
2.3 Запустить команду обработки данных, указав имя требуемого файла: `python manage.py parse_and_set_typical_problem --file /media/uploads/<имя файла для тестирования>`
2.4 После завершения работы скрипта выгрузить файл с результатами `/app/media/uploads/result.csv`, имеющий следующую структуру:
* порядок столбцов: 'Геопозиция', 'Адрес', 'Оригинальная категория', 'Текст обращения', 'Вхождение 1', 'Вхождение 2', 'Вхождение 3','Найденная категория 1', 'Найденная категория 2', 'Найденная категория 3', 'id вхождения 1', 'id вхождения 2', 'id вхождения 3'
* поля 'Вхождение 1', 'Вхождение 2', 'Вхождение 3' означают установление точного соответствия с одной из трёх найденных в системе подходящих тематик, записанных в полях 'Найденная категория 1', 'Найденная категория 2', 'Найденная категория 3'.
* поля 'id вхождения 1', 'id вхождения 2', 'id вхождения 3' содержат внутренние идентификаторы тематик в системе, так как классификатор системы является расширенным и приближенным к требованиям Минстроя, что позволяет в том числе более точно устанавливать тематику для отдельных обращений
* после повторного выполнения команды с новыми тестовыми данными, файл `/app/media/uploads/result.csv` перезаписывается полностью
Для примера в репозитории размещены файл с тестовыми данными и файл с результатами тестирования
[https://git.asi.ru/infolabs/pos/blob/master/media/uploads/test_data.csv](https://git.asi.ru/infolabs/pos/blob/master/media/uploads/test_data.csv)
[https://git.asi.ru/infolabs/pos/blob/master/media/uploads/result.csv](https://git.asi.ru/infolabs/pos/blob/master/media/uploads/result.csv)
# Примеры использования
[Тестовый стенд: http://pos.infolabs.ru/](http://pos.infolabs.ru/)
Логин: `nikifor41@gmail.com`
Пароль: `psswrdpsswrd`
[Видео с примером использования: https://youtu.be/-1nRLHhN6Mg/](https://youtu.be/-1nRLHhN6Mg)
[Презентация проекта: https://git.asi.ru/infolabs/pos/blob/master/images/Презентация_для_оформления_итогов_решения_AI_and_Data_2_ЭТАП_INFOLABS.pdf](https://git.asi.ru/infolabs/pos/blob/master/images/Презентация_для_оформления_итогов_решения_AI_and_Data_2_ЭТАП_INFOLABS.pdf)
**Шаг 1:**
![Пример определения тематики сообщения: шаг 1](https://git.asi.ru/infolabs/pos/raw/master/images/шаг1.png)
**Шаг 2:**
![Пример определения тематики сообщения: шаг 2](https://git.asi.ru/infolabs/pos/raw/master/images/шаг2.png)
**Шаг 3:**
![Пример определения тематики сообщения: шаг 3](https://git.asi.ru/infolabs/pos/raw/master/images/шаг3.png)
**Результат отправки обращения:**
![Пример встраивания на сайт: результат](https://git.asi.ru/infolabs/pos/raw/master/images/шаг4.png)
**Такие же проблемы по выбранному адресу:**
![Пример встраивания на сайт: результат](https://git.asi.ru/infolabs/pos/raw/master/images/Снимок_экрана_2020-08-31_в_21.49.11.png)
# Используемые наборы данных
Для обучения алгоритмов использовались данные:
* предоставленные в постановке задачи конкурса;
* открытые данные портала Белгородской области ["Народная экспертиза" https://narod-expert.ru/edw/api/data-marts/5/entities/](https://narod-expert.ru/edw/api/data-marts/5/entities/) .
# Дополнительный инструментарий
Для разворачивания решения требуются только установка Docker и любое средство для работы с репозиториями.
\ No newline at end of file
[program:nash_region]
command = /usr/local/bin/gunicorn 'nash_region.wsgi' -b 0.0.0.0:8000 --log-level=error -w 4 -k gevent
directory = /app
user = root
autostart = false
autorestart = false
redirect_stderr = True
stdout_logfile = /var/log/app/nash_region.log
stdout_logfile_maxbytes = 10MB
debug = False
[program:celery]
command = /usr/local/bin/celery -A nash_region -Q nash_region -n nash_region worker -l debug -E
directory = /app
environment = PATH="usr/local/bin"
user = root
autostart = false
autorestart = false
redirect_stderr = True
stdout_logfile = /var/log/app/celery.log
stdout_logfile_maxbytes = 10MB
debug = False
[program:celery_beat]
command = /usr/local/bin/celery -A nash_region beat -l info -S django -s /celerybeat-schedule
directory = /app/
environment = PATH="/usr/local/bin"
user = root
autostart = false
autorestart = false
redirect_stderr = True
stdout_logfile = /var/log/app/celery_beat.log
stdout_logfile_maxbytes = 10MB
debug = False
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django-constance\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 08:26+0000\n"
"PO-Revision-Date: 2020-05-08 11:35+0300\n"
"Last-Translator: Infolabs <team@infolabs.ru>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/django-constance/"
"language/ru/)\n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
"X-Generator: Poedit 2.3\n"
#: admin.py:109
#, python-format
msgid ""
"Default value type must be equal to declared config parameter type. Please "
"fix the default value of '%(name)s'."
msgstr ""
"Значение по умолчанию должно соответствовать типу параметра. Пожалуйста, "
"исправьте значение по умолчанию переменной '%(name)s'."
#: admin.py:119
#, python-format
msgid ""
"Constance doesn't support config values of the type %(config_type)s. Please "
"fix the value of '%(name)s'."
msgstr ""
"Не поддерживаются значения типа %(config_type)s. Пожалуйста, исправьте "
"значение переменной '%(name)s'."
#: admin.py:153
msgid ""
"The settings have been modified by someone else. Please reload the form and "
"resubmit your changes."
msgstr ""
"Настройки были уже кем-то изменены. Пожалуйста, перезагрузите страницу и "
"повторно сохраните ваши изменения."
#: admin.py:165 checks.py:19
msgid ""
"CONSTANCE_CONFIG_FIELDSETS is missing field(s) that exists in "
"CONSTANCE_CONFIG."
msgstr ""
"В CONSTANCE_CONFIG_FIELDSETS отсутствуют поля присутствующие в "
"CONSTANCE_CONFIG."
#: admin.py:233
msgid "Live settings updated successfully."
msgstr "Настройки успешно сохранены."
#: admin.py:298
msgid "config"
msgstr "настройки"
#: backends/database/models.py:19
msgid "constance"
msgstr "настройки"
#: backends/database/models.py:20
msgid "constances"
msgstr "настройки"
#: management/commands/constance.py:30
msgid "Get/Set In-database config settings handled by Constance"
msgstr "Get/Set In-database config settings handled by Constance"
#: templates/admin/constance/change_list.html:60
msgid "Save"
msgstr "Сохранить"
#: templates/admin/constance/change_list.html:69
msgid "Home"
msgstr "Главная"
#: templates/admin/constance/includes/results_list.html:6
msgid "Name"
msgstr "Название"
#: templates/admin/constance/includes/results_list.html:7
msgid "Default"
msgstr "По умолчанию"
#: templates/admin/constance/includes/results_list.html:8
msgid "Value"
msgstr "Текущее значение"
#: templates/admin/constance/includes/results_list.html:9
msgid "Is modified"
msgstr "Было изменено"
#: templates/admin/constance/includes/results_list.html:22
msgid "Current file"
msgstr "Текущий файл"
#: templates/admin/constance/includes/results_list.html:39
msgid "Reset to default"
msgstr "Сбросить настройки по умолчанию"
#~ msgid "Constance config"
#~ msgstr "Настройки"
#~ msgid "Constance"
#~ msgstr "Управление параметрами системы"
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 08:30+0000\n"
"PO-Revision-Date: 2020-05-08 11:35+0300\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: ru\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
"X-Generator: Poedit 2.3\n"
#: admin.py:71
msgid "Task (registered)"
msgstr "Зарегистрированное задание"
#: admin.py:75
msgid "Task (custom)"
msgstr "Пользовательское задание"
#: admin.py:92
msgid "Need name of task"
msgstr "Требуется указать имя задания"
#: admin.py:98 models.py:563
msgid "Only one can be set, in expires and expire_seconds"
msgstr "Возможно задать только один параметр expires или expire_seconds"
#: admin.py:108
#, python-format
msgid "Unable to parse JSON: %s"
msgstr "Не удалось распарсить JSON: %s"
#: admin.py:174
#, python-brace-format
msgid "{0} task{1} {2} successfully {3}"
msgstr "{0} задание{1} {2} успешно {3}"
#: admin.py:177 admin.py:239
msgid "was,were"
msgstr "было,были"
#: admin.py:186
msgid "Enable selected tasks"
msgstr "Включить выбранные задания"
#: admin.py:192
msgid "Disable selected tasks"
msgstr "Отключить выбранные задания"
#: admin.py:204
msgid "Toggle activity of selected tasks"
msgstr "Включить выбранные задания"
#: admin.py:224
#, python-brace-format
msgid "task \"{0}\" not found"
msgstr "задание \"{0}\" не найдено"
#: admin.py:236
#, python-brace-format
msgid "{0} task{1} {2} successfully run"
msgstr "{0} задание{1} {2} успешно выполнено"
#: admin.py:242
msgid "Run selected tasks"
msgstr "Запустить выбранные задания"
#: apps.py:15
msgid "Periodic Tasks"
msgstr "Периодические задания"
#: models.py:29
msgid "Days"
msgstr "Дни"
#: models.py:30
msgid "Hours"
msgstr "Часы"
#: models.py:31
msgid "Minutes"
msgstr "Минуты"
#: models.py:32
msgid "Seconds"
msgstr "Секунды"
#: models.py:33
msgid "Microseconds"
msgstr "Микросекунды"
#: models.py:54
msgid "Solar Event"
msgstr "Солнечное событие"
#: models.py:55
msgid "The type of solar event when the job should run"
msgstr "Укажите солнечное событие для запуска задания"
#: models.py:59
msgid "Latitude"
msgstr "Широта"
#: models.py:60
msgid "Run the task when the event happens at this latitude"
msgstr "Запускать задание, когда солнечное событие случится на данной широте"
#: models.py:65
msgid "Longitude"
msgstr "Долгота"
#: models.py:66
msgid "Run the task when the event happens at this longitude"
msgstr "Запускать задание, когда солнечное событие случится на данной долготе"
#: models.py:73
msgid "solar event"
msgstr "солнечное событие"
#: models.py:74
msgid "solar events"
msgstr "солнечные события"
#: models.py:124
msgid "Number of Periods"
msgstr "Количество периодов"
#: models.py:125
msgid "Number of interval periods to wait before running the task again"
msgstr "Количество периодов через которое будет выполнятся задание"
#: models.py:131
msgid "Interval Period"
msgstr "Интервал"
#: models.py:132
msgid "The type of period between task runs (Example: days)"
msgstr "Укажите интервал для запуска (например: дни)"
#: models.py:138
msgid "interval"
msgstr "интервал"
#: models.py:139
msgid "intervals"
msgstr "интервалы"
#: models.py:162
#, python-brace-format
msgid "every {0.period_singular}"
msgstr "каждый(ые) {0.period_singular}"
#: models.py:163
#, python-brace-format
msgid "every {0.every} {0.period}"
msgstr "каждый(ые) {0.every} {0.period}"
#: models.py:175
msgid "Clock Time"
msgstr "Время запуска"
#: models.py:176
msgid "Run the task at clocked time"
msgstr "Запустить задание в определенное время"
#: models.py:181 models.py:483
msgid "Enabled"
msgstr "Включено"