Alfa Battle — как это было, запись конференции и список задач

@

В начале июня мы запустили Alfa Battle для Java-разработчиков. Пришло время рассказать о том, как все прошло, поделиться полезными видео от спикеров стрим-конференции «Кодинг будущего» (Альфа-Банк, Билайн, X5 Retail Group) и показать наши задачи.

К слову, о задачах. То ли мы перестарались и переоценили возможных участников, то ли погода была так себе, в общем, из 1498 участников из 50 городов все задачи (5 штук) целиком не решил никто. Поэтому под катом вы найдете все задачи с описанием и, если захочется их порешать, сможете это сделать без каких-то дедлайнов. Тех, кто осилит, с радостью пригласим к нам. В конце поста — о новой стратегии найма специалистов. Было время, когда IT Альфа-Банка по больше части сосредотачивалось в трёх городах — Москва, Санкт-Петербург, Екатеринбург. Теперь же мы можем рассматривать людей со всей страны. И не только.

Спасибо всем, кто откликнулся — таких было более 1500 человек, из которых до финала дошли 198 ребят, показавших лучшие результаты. Да, было непросто: задачи оказались сложнее, чем ожидали многие участники, да и сам формат онлайн-чемпионата со стримом конференции, прямо скажем, был напряжённым. Всё же, 5 часов онлайн-программирования — это не ретро в команде провести.
Итого — три победителя, которые получили денежный приз, а также приглашения на работу к нам и к партнёрам чемпионата. Вот они:
1 место: Михаил Бурштейн, Москва (250 000 рублей)
2 место: Александр Илюшечкин, Москва (150 000 рублей)
3 место: Павел Любинский, Санкт-Петербург (100 000 рублей)

Задачи

#1 Где же банкомат

Подробности задачи

Условие

Требуется реализовать сервис для получения данных о банкоматах Альфа-Банка.
Сервис должен предоставлять REST API на IP:8080.
Файл с описанием API – api.json.
Для получения базовых данных необходима интеграция решения с Альфа-Банк API Сервис банкоматов.
Документация: api.alfabank.ru/node/238
Для доступа к API необходима регистрация и несложная настройка.
Детали: api.alfabank.ru/start
* IP — внешний IP виртуальной машины.
* Все ресурсы лежат тут: github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task1

Задачи

1. Получение данных банкомата по deviceId

Запрос: GET IP:8080/atms/{deviceId}
Ответ:
— 200 AtmResponse
— 404 ErrorResponse (если банкомат не найден)
Пример:

GET http://IP:8080/atms/153463 200 { "deviceId": 153463, "latitude": "55.6610213", "longitude": "37.6309405", "city": "Москва", "location": "Старокаширское ш., 4, корп. 10", "payments": false }

Пример:

GET http://IP:8080/atms/1 404 { “status”: “atm not found” }

Следующая задача доступна после получения 18 баллов.

2. Получение ближайшего к указанным координатам банкомата (по-глупому)

По-глупому — потому что считаем Землю плоской.
А в Задаче 3 Свободная касса! надо будет по-умному.
Если передан параметр payments=true, ищутся только банкоматы с поддержкой оплаты товаров и услуг (см. Альфа-Банк API Сервис банкоматов ATMServices).
Запрос: GET IP:8080/atms/nearest?latitude=string&longitude=string&payments=boolean
Ответ: 200 AtmResponse
Пример:

GET http://IP:8080/atms/nearest?latitude=55.66&longitude=37.63 200 { "deviceId": 153463, "latitude": "55.6610213", "longitude": "37.6309405", "city": "Москва", "location": "Старокаширское ш., 4, корп. 10", "payments": false }

Пример:

GET http://IP:8080/atms/nearest?latitude=55.66&longitude=37.63&payments=true 200 { "deviceId": 210612, "latitude": "55.66442", "longitude": "37.628051", "city": "Москва", "location": "Каширское шоссе, д. 14", "payments": true }

Следующая задача доступна после получения 36 баллов.

3. Получение списка ближайших банкоматов для снятия Альфиков

В каждом банкомате доступно для снятия определенное количество чудо-валюты Альфик.
Нужно получить список ближайших банкоматов, в которых можно суммарно снять указанное число Альфиков.
Запрос: GET IP:8080/atms/nearest-with-alfik?latitude=string&longitude=string&alfik=int
Ответ: 200 [AtmResponse]
Узнать число доступных Альфиков для указанного банкомата можно через внешний сервис, предоставляющий WebSocket STOMP интерфейс.
Для запуска:
mkdir task1 ; cd task1
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task1/docker-compose.yml
docker-compose up -d

Для остановки (в папке task1):
docker-compose down
Адрес: ws://IP:8100
Запрос: { “deviceId”: 0 }
Ответ на /topic/alfik: { “deviceId”: 0, “alfik”: 0 }
Пример:

GET http://IP:8080/atms/nearest-with-alfik?latitude=55.66&longitude=37.63&alfik=300000 200 [{ "deviceId": 153463, "latitude": "55.6610213", "longitude": "37.6309405", "city": "Москва", "location": "Старокаширское ш., 4, корп. 10", "payments": false }]

(т.к. в банкомате 153463 — 383026 Альфиков)
Пример:

GET http://IP:8080/atms/nearest-with-alfik?latitude=55.66&longitude=37.63&alfik=400000 200 [{ "deviceId": 153463, "latitude": "55.6610213", "longitude": "37.6309405", "city": "Москва", "location": "Старокаширское ш., 4, корп. 10", "payments": false }, { “deviceId": 153465, "latitude": "55.6602801", "longitude": "37.633823", "city": "Москва", "location":" Каширское ш., 18", "payments": false }]

(Альфиков уже не хватает, нужен следующий банкомат)

#2 Анализируй это

Подробности задачи

Условие

В топике Kafka RAW_PAYMENTS находятся данные по платежам пользователей.
Брокер развернут на виртуальной машине в docker контейнере и доступен по IP:29092 снаружи.
Перед началом выполнения задания необходимо поднять контейнер с Kafka.
Для запуска:
mkdir task2 ; cd task2
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task2/data.txt
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task2/docker-compose.yml
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task2/start.sh
bash start.sh

Скрипт развернет необходимую инфраструктуру и зальет данные в топик.
Для остановки (в папке task2):
docker-compose down
* IP — внешний IP виртуальной машины.
* Все ресурсы лежат тут: github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task2
Формат одной записи представлен ниже:

key1:{"ref":"U030306190000188", "categoryId":1, "userId":"XAABAA", "recipientId":"XA3SZV", "desc":"Платеж за услуги", "amount":10.0}

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

  • Вычитать все данные из топика Kafka.
  • Выполнить агрегацию данных для возможности отображения аналитики по платежам пользователей.
  • Реализовать REST API для доступа к аналитике по пользователям. Приложение должно быть доступно по порту 8081.

Уточнения

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

Дополнительная информация

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

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

docker exec -i broker kafka-consumer-groups --bootstrap-server broker:9092 --all-groups --all-topics --reset-offsets --to-earliest --execute
Рестартовать контейнеры с инфраструктурой кафки скриптом start.sh, как было описано вначале. Желательно при этом остановить все консьюмеры, чтобы не возникало неожиданных эффектов в дальнейшем при работе

Пример Swagger-описания ожидаемого интерфейса

Swagger-описание ожидаемого интерфейса приведено в файле api-swagger.json в папке ресурсов задания.

Задачи

Пример данных в топике:

key1:{"ref":"ref1", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_1", "amount":10.0} key2:{"ref":"ref2", "categoryId":2, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_2", "amount":350.56} key3:{"ref":"ref3", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_3", "amount":700.0} key4:{"ref":"ref4", "categoryId":3, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_4", "amount":5.99} key5:{"ref":"ref5", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_5", "amount":10.0} key6:{"ref":"ref6", "categoryId":2, "userId":"User_2", "recipientId":"User_3", "desc":"Тестовый платеж_6", "amount":350.56} key7:{"ref":"ref7", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_7", "amount":890.0} key8:{"ref":"ref8", "categoryId":3, "userId":"User_3", "recipientId":"User_2", "desc":"Тестовый платеж_8", "amount":35.99} key9:{"ref":"ref9", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_9", "amount":890.0} key10:{"ref":"ref10", "categoryId":3, "userId":"User_3", "recipientId":"User_2", "desc":"Тестовый платеж_10", "amount":35.9910} key11:{"ref":"ref11", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_11", "amount":10.0} key12:{"ref":"ref12", "categoryId":2, "userId":"User_2", "recipientId":"User_3", "desc":"Тестовый платеж_12", "amount":350.56} key13:{"ref":"ref13", "categoryId":1, "userId":"User_1", "recipientId":"User_2", "desc":"Тестовый платеж_13", "amount":10.0} key14:{"ref":"ref14", "categoryId":2, "userId":"User_2", "recipientId":"User_3", "desc":"Тестовый платеж_14", "amount":350.56} key15:{"ref":"ref15", "categoryId":4, "userId":"User_1", "recipientId":"User_4", "desc":"Тестовый платеж_15", "amount":15.00}

1. Разбиение данных по категориям

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

GET IP:8081/admin/health

200
Пример ответа:

{"status":"UP"}

GET http://IP:8081/analytic

200

Возвращает аналитику платежей по всем пользователям
Пример ответа:

[ { "userId": "User_3", "totalSum": 71.981, "analyticInfo": { "3": { "min": 35.99, "max": 35.991, "sum": 71.981 } } }, { "userId": "User_2", "totalSum": 1051.68, "analyticInfo": { "2": { "min": 350.56, "max": 350.56, "sum": 1051.68 } } }, { "userId": "User_1", "totalSum": 2891.55, "analyticInfo": { "1": { "min": 10, "max": 890, "sum": 2520 }, "2": { "min": 350.56, "max": 350.56, "sum": 350.56 }, "3": { "min": 5.99, "max": 5.99, "sum": 5.99 }, "4": { "min": 15, "max": 15, "sum": 15 } } } ]

Ключи структуры analyticInfo являются Id категорий платежей.

GET IP:8081/analytic/{userId}

— 200
— 404 + тело {«status»:”user not found"} (если пользователь не найден)
Получение аналитики по конкретному пользователю
Пример запроса:

GET http://IP:8081/analytic/User_1

Пример ответа:

{ "userId": "User_1", "totalSum": 2891.55, "analyticInfo": { "1": { "min": 10, "max": 890, "sum": 2520 }, "2": { "min": 350.56, "max": 350.56, "sum": 350.56 }, "3": { "min": 5.99, "max": 5.99, "sum": 5.99 }, "4": { "min": 15, "max": 15, "sum": 15 } } }

Следующая задача доступна после получения 40 баллов.

2. Статистика

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

  • самая частая категория трат
  • самая редкая категория трат
  • категория с наибольшей суммой
  • категория с наименьшей суммой

GET IP:8081/analytic/{userId}/stats

— 200
— 404 + тело {«status»:”user not found"} (если пользователь не найден)
Получение статистики категорий по пользователю.
Пример запроса:

GET http://IP:8081/analytic/User_1/stats Пример ответа: { "oftenCategoryId": 1, "rareCategoryId": 2, "maxAmountCategoryId": 1, "minAmountCategoryId": 3 }

Следующая задача открывается после набора 60 баллов

3. Шаблоны платежей

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

  • У платежей совпадают величина, категория, от кого и кому платеж был проведен (recipientId и userId соответственно)
  • Такие платежи повторяются три и более раза

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

GET IP:8081/analytic/{userId}/templates

— 200
— 404 + тело {«status»:”user not found"} (если пользователь не найден)
Получение информации по платежам, которые были выделены как шаблонные для данного пользователя.
Пример запроса:

GET http://IP:8081/analytic/User_1/templates Пример ответа: [ { "recipientId": "User_2", "categoryId": 1, "amount": 10 } ]

#3 Свободная касса

Подробности задачи

Условие

Требуется реализовать сервис для получения данных об офисах Альфа-Банка и их загруженности.
Сервис должен предоставлять REST API на IP:8082.
Файл с описанием API – api.json.
Так как мы не сомневаемся, что вы умеете гуглить, дали вам немного подсказок для задачи и ссылок на статьи, чтобы экономить бесценное время, которого и так немного.
Данные лежат в PostgreSQL.
Для запуска PostgreSQL:
mkdir task3 ; cd task3
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task3/docker-compose.yml
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task3/Dockerfile
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task3/init_db.sql
docker-compose up -d

Для остановки (в папке task3):
docker-compose down
Адрес: IP:5432
DB: alfa_battle
Auth: alfa_battle / qwe123
* IP — внешний IP виртуальной машины.
* Все ресурсы лежат тут: github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task3

Задачи

1. Получение данных офиса по id

Запрос: GET IP:8082/branches/{id}
Ответ:
— 200 Branches
— 404 ErrorResponse (если офис не найден)
Пример:

GET http://IP:8082/branches/612 200 { "id": 612, "title": "Мясницкий", "lon": 37.6329, "lat": 55.7621, "address": "Мясницкая ул., 13, стр. 1" }

Пример:

GET http://IP:8082/branches/1 404 { “status”: “branch not found” }

Следующая задача доступна после получения 8 баллов.

2. Получение ближайшего к указанным координатам офиса (по-умному)

По-умному, потому что Земля не плоская, как в Задаче 1.
Это не так сложно.
Необходимо найти самый ближайший офис к точке, основываясь на координатах пользователя Latitude Longitude.
Запрос: GET IP:8082/branches/lat=string&lon=string
Ответ: 200 Branches
Пример:

GET http://IP:8082/branches/lat=55.773284&lon=37.624125 200 { "id": 631, "title": "Цветной Бульвар", "lon": 37.6227, "lat": 55.7695, "address": "Цветной бул., 16/1", "distance": 430 }

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

3. Предсказание загруженности офиса

Необходимо на основе данных предсказать загруженность любого из офисов в определенный час, а именно — необходимо предсказать, сколько клиент потратит времени, находясь в очереди, ожидая, пока его обслужат.
При расчете предсказания использовать медиану.
Запрос: GET IP:8082/branches/{id}/predict?dayOfWeek=int&hourOfDay=int
Ответ:
— 200 BranchesWithPredicting
— 404 ErrorResponse (если офис не найден)
Пример:

GET http://IP:8082/branches/612/predict?dayOfWeek=1&hourOfDay=14 200 { "id": 612, "title": "Мясницкий", "lon": 37.6329, "lat": 55.7621, "address": "Мясницкая ул., 13, стр. 1", "dayOfWeek": 1, "hourOfDay": 14, "predicting": 117 }

dayOfWeek — число, где понедельник это 1, а воскресенье 7
hourOfDay — число от 0 до 23
Predicting — время ожидания, которое надо считать в секундах, а результат округлять.
Пример:

GET http://IP:8082/branches/1/predict?dayOfWeek=1&hourOfDay=14 404 { “status”: “branch not found” }

Предсказание загруженности в данном случае предлагаем считать через медиану.
Библиотека, которая вам может в этом помочь, apache-commons-math3

#4 Эластичные кредиты

Подробности задачи

Условие

Есть 1 стул и 2 входных файла JSON:
person.json — данные о клиентах, формат одной записи представлен ниже:

{ "ID":"29", "DocId":"702821510", "FIO":"Phoebe Whitehouse", "Birthday":"7/12/1971", "Salary":"201.02", "Gender":"F" }

, где:
ID — уникальный номер клиента,
DocId — документ (формат 9 цифр)
FIO — ФИО
Birthday — дата рождения в формате MM/dd/yyyy
Salary — средний заработок клиента, который записан в сотых, т.е. 201.02 означает 20102 руб.
Gender — пол
loans.json — данные о клиентах, формат одной записи представлен ниже:

{ "Loan":"631553", "PersonId":"68", "Amount":"201.02", "StartDate":"6/1/2019", "Period":"1" }

, где:
Loan — номер договора,
PersonId — номер клиента
Amount — сумма кредита, которая записана в сотых, т.е. 201.02 означает 20 102
StartDate — дата начала кредита в формате MM/dd/yyyy
Period — срок кредита в годах

Задачи

Из указанных выше файлов прочитать информацию, преобразовать ее и положить в ElasticSearch в разные индексы.
Реализовать REST-интерфейс для загрузки данных в ElasticSearch, а также для извлечения данных. Приложение должно быть доступно на IP:8083
Для запуска ElasticSearch:
mkdir task4 ; cd task4
wget https://raw.githubusercontent.com/evgenyshiryaev/alfa-battle-resources/master/task4/docker-compose.yml
docker-compose up -d

ElasticSearch доступен на IP:9200
Для остановки (в папке task4):
docker-compose down
* IP — внешний IP виртуальной машины.
* Все ресурсы лежат тут: github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task4

Уточнения

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

  • Birthday — должна храниться в виде 1945-05-03
  • Salary — должна храниться в рублях, а не в сотых

loans

  • PersonId — вместо это поля, должно появиться поле Document, которое будет хранить DocId из Persons.
  • Amount — сумма кредита должна быть в рублях, а не в сотых.
  • StartDate — дата начала кредита, должна храниться в виде 1945-05-03
  • Period — срок кредита, должен быть в месяцах

Ожидаемый интерфейс

Хелсчек

GET IP:8083/admin/health
200
{«status»:«UP»}

Загрузка клиентов

Загрузка преобразованных данных клиентов в ElasicSearch.
POST IP:8083/loans/loadPersons
200
{«status»:«OK»}

Загрузка договоров

Загрузка преобразованных данных клиентов в ElasicSearch.
При этом, для того чтобы на договоре проставить Document, необходимо сделать запрос в ElasticSearch для получения номер документа клиента.
POST IP:8083/loans/loadLoans
200
{«status»:«OK»}

Вывести информацию о клиенте

GET http://IP:8083/loans/getPerson/855406656/ 200 { "docid": "855406656", "fio": "Celina Jackson", "birthday": "1961-05-22", "salary": 69106.0, "gender": "F" }

400 (в случае отсутствия клиента)
{
«status»: «person not found»
}

Вывести информацию о договоре

GET http://IP:8083/loans/getLoan/692826/ 200 { "loan": "692826", "amount": 448900, "document": "027665876", "startdate": "2017-01-16", "period": 48 }

400 (в случае отсутствия договора)

{ "status": "loan not found" }

Запрос на кредитную историю клиента

Вывод информации о всех кредитных договорах клиента, с указанным номером документа — Document.

GET http://IP:8083/loans/creditHistory/737767072/ 200 { "countLoan": 4, "sumAmountLoans": 1058400.0, "loans": [ { "loan": "434224", "amount": 7100, "document": "737767072", "startdate": "2019-09-18", "period": 12 }, { "loan": "917105", "amount": 283600, "document": "737767072", "startdate": "2019-12-22", "period": 12 }, { "loan": "692147", "amount": 300800, "document": "737767072", "startdate": "2016-08-01", "period": 24 }, { "loan": "145020", "amount": 466900, "document": "737767072", "startdate": "2017-01-16", "period": 36 } ] }

, где:
countLoan — количество договоров
sumAmountLoans — сумма всех договоров
loans — массив договоров

Получение списка кредитных договоров, которые закрыты на первое число текущего месяца

GET http://IP:8083/loans/creditClosed 200 [ { "loan": "222398", "amount": 265400, "document": "074658188", "startdate": "2017-09-22", "period": 12 }, "loan": "826942", "amount": 329400, "document": "788117788", "startdate": "2016-01-29", "period": 48 }, ... ]

Получение информации по клиентам и договорам

Ответ должен быть отсортирован по дате рождения, по убыванию (сортировку производить при запросе из ElasticSearch).

GET http://IP:8083/loans/loansSortByPersonBirthday 200 [ { "id": null, "docid": "840704451", "fio": "John Isaac", "birthday": "19.08.1989", "salary": 58295.0, "gender": "M", "loans": [ { "loan": "771916", "amount": 337600, "document": "840704451", "startdate": "2019-11-09", "period": 48 }, { "loan": "504544", "amount": 358900, "document": "840704451", "startdate": "2018-06-10", "period": 36 }, { "loan": "699247", "amount": 464400, "document": "840704451", "startdate": "2018-10-30", "period": 36 }, { "loan": "783101", "amount": 139300, "document": "840704451", "startdate": "2017-02-19", "period": 36 } ] }, { "id": null, "docid": "023665566", "fio": "Denny Tanner", "birthday": "25.03.1989", "salary": 80713.0, "gender": "M", "loans": [ { "loan": "631553", "amount": 403000, "document": "023665566", "startdate": "2019-06-01", "period": 12 }, { "loan": "598452", "amount": 198500, "document": "023665566", "startdate": "2015-09-28", "period": 36 }, { "loan": "151915", "amount": 13600, "document": "023665566", "startdate": "2019-06-15", "period": 12 }, { "loan": "368342", "amount": 350500, "document": "023665566", "startdate": "2017-02-06", "period": 48 }, { "loan": "633056", "amount": 482900, "document": "023665566", "startdate": "2016-07-01", "period": 12 } ] }, ... ]

#5 На промоигле

Подробности задачи

Условие

Необходимо реализовать сервис актуализации стоимости корзины покупателя.
Суть актуализации стоимости корзины в расчете цен на товары с учетом различных промо-акций и получении итоговой суммы чека и размера скидки.
Сервис предполагается использовать сразу многими магазинами.
Сервис должен иметь REST API на IP:8084 с двумя методами:

  • /promo – в теле POST запроса приходит список дескрипторов актуальных промо-акции (содержит как акции Сети, так и акции для магазинов). Этот метод вызывается каждый раз перед запуском тестов.
  • /receipt – метод для актуализации цен, на вход в теле POST запроса приходит id магазина, список покупаемых товаров и список предъявленных документов на скидки (например, карта лояльности), на выход ожидается список товаров с корректными ценами и названиями, размером скидки, сумма чека и суммарный размер скидки.

При тестировании система каждый раз последовательно вызывает эти два метода: сначала /promo, потом /receipt.
* IP — внешний IP виртуальной машины.
* Все ресурсы лежат тут: github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task5

Входные данные

  1. CSV файлы с неизменяемыми данными:
  2. Файл с описанием API – api.yml

Общие замечания

В каждом магазине действуют единые промо-акции сети, а также локальные промо-акции, приоритет которых выше.
Если для позиции в чеке может применяться сразу несколько промо-акций, то выбирается промо-акция с наиболее высоким приоритетом.
Если для позиции чека подходят несколько промо-акций с одинаковым приоритетом, то нужно применять ту промо-акцию, которая даёт покупателю наибольшую скидку.
Округление следует проводить до 2 знаков с RoundingMode.HALF_EVEN.
!!! Автоматически будет проверяться только итоговая сумма чека и суммарная скидка, наличие и корректность позиций будет учитываться только на code review жюри!!!

Задачи

1. Без промо-акций

Пример:
Запрос: POST IP:8084/promo
{}
Ответ: 200
Запрос: POST IP:8084/receipt
{
«shopId»: 1,
«loyaltyCard»: false,
«positions»:
[
{
«itemId»: «3432166»,
«quantity»: 1
}
]
}
Ответ: 200
{
«total»: 141.99,
«discount»: 0.00,
«positions»:
[
{
«id»: «3432166»,
«name»: «ЛУК.Жид.АНТИФ.G11 Green нез.1кг»,
«price»: 141.99,
«regularPrice»: 141.99
}
]
}
Следующая задача доступна после получения 8 баллов.

2. Промо-акция Карта Лояльности

В /promo может приходить список промо-акций типа “скидка при предъявлении карты лояльности” (LoyaltyCardRule). Акции могут быть как глобальные, так и для конкретных магазинов.
Скидка при предъявлении промо-акции должна применяться, если в запросе на подсчет корзины /receipt будет loyaltyCard=true и промо-акция может быть применена для магазина, id которого приходит в запросе подсчёта.
Пример:
Запрос: POST IP:8084/promo

{ "loyaltyCardRules": [ { "shopId": -1, "discount": 0.03 }, { "shopId": 2, "discount": 0.05 } ] }

Ответ: 200
Запрос: POST IP:8084/receipt

{ "shopId": 1, "loyaltyCard": false, "positions": [ { "itemId": "3432166", "quantity": 1 } ] } Ответ: 200 { "total": 137.73, "discount": 4.26, "positions": [ { "id": "3432166", "name": "ЛУК.Жид.АНТИФ.G11 Green нез.1кг", "price": 137.73, "regularPrice": 141.99 } ] }

Следующая задача доступна после получения 16 баллов.

3. Промо-акция N+k

В /promo может приходить список промо-акций с типами LoyaltyCardRule и ItemCountRule. Акции могут быть как глобальные, так и для конкретных магазинов.
Суть скидки N+k в том, что при оплате N товаров заданного ID покупатель может получить k товаров бесплатно. Несколько примеров приведено ниже в таблице для Акции с N=3, k=2:

Видеозаписи докладов

Все 16 докладов от наших ребят и партнёров из Билайн и X5 Retail Group вы можете посмотреть на сайте мероприятия.

Так что там про найм?

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

Для любителей подробностей — интервью целиком

В Альфа-Банке более 100 команд работают над перспективными цифровыми проектами. И это не предел. Банк развивается и продолжает искать новых сотрудников, все больше обращая свое внимание на кандидатов, работавших в стартапах. Дамир Баттулин, директор департамента развития онлайн-каналов Альфа-Банка, рассказывает о стратегических инициативах банка, принципах командной работы над проектами и о критериях выбора новых специалистов для развития цифровизации.
— Два года назад мы начали основательно работать над цифровизацией Альфа-Банка, была принята стратегия до 2021 года. Мы стали активно набирать цифровые команды под самые различные проекты. И речь идет не столько о таких задачах, как перевод денег или получение кредитов, а сколько о самых разных проектах — от биометрии и блокчейна до безопасности или геймификации. В составе каждой команды от 7 до 12 человек. По сути, это маленькие самостоятельные единицы, по скорости разработки близкие к стартапам.
Благодаря появлению подобных команд, Альфа-Банк стал довольно быстро меняться, будучи огромной корпорацией. В основу этих преобразований легли процессы, которые, как правило, свойственны небольшим компаниям, но мы их перевернули под один из крупнейших частных банков страны. Изменения, в первую очередь, отражаются на клиентском опыте (как физических, так и юридических лиц): создаются и обновляются каналы коммуникаций — офисы, мобильные приложения, интернет-банк. Большинство позиций из принятой стратегии уже реализовано, но впереди еще достаточно много новых проектов.
— В каких направлениях развивается цифровизация банка?
— Я могу выделить три основные стратегические инициативы, которые направлены на развитие Альфа-Банка.
Одна из целей — создать лучшее мобильное приложение, в том числе, для корпоративных клиентов.
Вторая инициатива — безбумажный банк. Необходимо достигнуть такого уровня, когда бумага внутри организации будет полностью отсутствовать. Речь идет не только об обычном бумажном обороте между сотрудниками, но и о таких вроде бы незаменимых вещах, как кассовый ордер, который выдается клиенту.
И третья инициатива, недавно воплотившаяся в реальность, — smart branch. Мы хотим внедрить эту модель во всех офисах обслуживания банка – более 400 отделениях по всей стране. Первые два уже работают в Москве, третий будет открыт в сентябре, а в октябре мы выйдем с этим проектом в регионы.
Как это работает? Если клиент собрался в отделение банка, то в мобильном приложении он может узнать о его загруженности. На входе через систему распознавания лиц информация о посетителе будет доступна сотруднику банка, что сэкономит время обслуживания. Если клиент забыл паспорт, то его могут принять и без него. Подписывать документы станет необязательно, как и вести любой другой бумажный оборот. При необходимости ожидания своей очереди клиенту на смартфон будет направлена полезная информация, которую он может изучить, чтобы опять же сэкономить время.
И для эффективного внедрения и обеспечения работы этих процессов у нас есть команды, о которых я уже упоминал. Например, отдельная команда по биометрии, команды по безопасности и безбумажному обслуживанию, команда по электронной очереди и так далее.
Помимо этих инициатив, не стоит забывать и о внутренних процессах в банке, о работе бэк-офиса. Тут мы тоже решаем конкретные задачи. Например, сделали отдельное приложение для курьеров, разработали сервисы для сотрудников отделений, чтобы те оперативно получали информацию о клиенте. Приятно ведь, когда в банке могут знать о том, что у вас сегодня день рождения, не заглядывая в паспорт.
— Сильно ли влияет географический фактор на поиск новых сотрудников в команды?
— Исторически продуктовые команды у нас располагались в трех основных локациях: Москва, Санкт-Петербург и Екатеринбург. Но в связи с последними событиями, вызванными пандемией, мы поняли, что ограничения по географии только в нашей голове, и стали искать талантливых и интересных специалистов по всей стране. И даже Россией не ограничились. Например, у нас уже работает целая команда из Белоруссии.
Мы максимально открыты к различным вариантам сотрудничества. Можем как принять сотрудника в штат банка, так и взаимодействовать с ним через вендора. Нанимаем не только отдельно, но и целыми командами под конкретный проект или сервис, у которого уже определена перспектива развития. Конечно, получить идеальную команду под проект сложно. Пока стопроцентного попадания в этой части еще не было.
— Можете ли вы отдать разработку проекта сторонней компании?
— Основной принцип – time to market. Мы стараемся следовать здравому смыслу. Если лучше разработать проект самостоятельно, то беремся за него внутри банка. Но сейчас все больше нацелены на партнерство. Это сложный процесс, так как у многих до сих пор осталось представление о банках как о денежных хранилищах, а не о высокоскоростных цифровых корпорациях.
В любом случае мы готовы руководствоваться методом проб и ошибок, всегда тестируем абсолютно разные гипотезы, что даже роднит нас со стартапами. Проекты smart branch мы вообще начинали с создания прототипов в виде картонных офисов, запускали туда людей и изучали их потребности.
— Кто и как управляет командами, откуда появляются задачи и возникают идеи новых проектов?
— Задачи появляются по-разному. Часто после изучения исследований, анализа действий конкурентов. Есть стримы, где различные подразделения банка могут заявлять о необходимости того или иного проекта или сервиса. В дальнейшем они помогают командам.
Самими проектами занимаются непосредственно команды. Я работаю с ними, но не управляю и не руковожу. Это ключевой принцип — мы развиваем бизнес, не руководя. Внутри команды есть собственные метрики, существуют мотивационные показатели. Если кто-то из участников команды проседает, то его могут удалить из проекта. Также распространены переходы из одной команды в другую. Есть много случаев, когда специалисты поднимались по карьерной лестнице: от iOS-разработчика до Lead product manager в направлении iOS, от дизайнера до Chief product owner и так далее.
Количество команд постоянно растёт. Еще полтора года назад их было 56, а сейчас в два раза больше. Тогда они были укомплектованы на 60%, а уже сегодня этот показатель близок к 100%.
— Какие у вас требования к кандидатам?
— У нас есть несколько сотрудников, которые занимаются отбором кандидатов. Помимо меня, это могут быть руководители дирекций или руководители центров компетенций.
Если говорить про меня, то я больше сосредоточен на позициях chief product owner. И то, далеко не всех. Мои требования к ним – быть внутренним предпринимателем, исследователем, профессионалом и хорошим человеком. Обязательно изучаю те проекты, которые были реализованы кандидатом. Интересны претенденты, кто что-то делал, пробовал, необязательно при этом, чтобы у них получилось.
— Это очень похожие качества людей, которые работают в стартапах. Обращаете на них особое внимание?
— Да, причем на тех, кто работал внутри стартапов не только продактами, но и разработчиками. Нам нужны люди, которые обладают качествами, позволяющими реализовать ту или иную гипотезу, независимо от существующих правил. Важно быть гибкими и быстрыми.
В зависимости от проекта и бюджета нам может потребоваться специалист с большим опытом. И тогда мы можем смотреть, в том числе, и на основателей стартапов.
— Какие новые проекты у вас могут появиться в ближайшее время?
— Одна из наиболее актуальных тем для нашего банка – это направление инвестиций. Под эту задачу скоро начнем набирать команды. Развиваются проекты геймификации. В этом направлении мы поняли, что надо искать специалистов из тех, кто был связан именно с этой спецификой, а не просто с приложениями.
Еще одна интересная тема – улучшение мобильного приложения. После недавно проведенного исследования мы поняли, что нам не нужно строить супер-приложение, как это делают многие банки. Мы даже с нынешним, заточенным исключительно под банковское обслуживание, не до конца удовлетворяем своего клиента. После того как мы увидели эту проблему под другим углом, сразу возникли новые идеи.
Получается, что новые проекты, под которые мы ищем специалистов, появляются как в уже действующих направлениях развития банка, так и в новых, которые нам только предстоит развивать. В этом и наше преимущество — мы хотим пробовать, совершенствоваться, ошибаться, исправляться. Только так можно создать уникальный банк для российского рынка.

Данные о правообладателе фото и видеоматериалов взяты с сайта «Хабрахабр», подробнее в Правилах сервиса
Анализ
×
Дамир Рашитович Баттулин
Последняя должность: Руководитель департамента развития онлайн-каналов (АО "АЛЬФА-БАНК")
Бурштейн Михаил
Илюшечкин Александр
АО "АЛЬФА-БАНК"
Сфера деятельности:Финансы
287
X5 Group
Сфера деятельности:Розничная торговля
145