... At the end of the post - about a new strategy for hiring specialists. There was a time when Alfa-Bank's IT was mostly concentrated in three cities - Moscow, St. Petersburg, Yekaterinburg. Now we can look at people from all over the country. And not only.
Thanks to everyone who responded - there were more than 1,500 such people, of which 198 guys reached the finals who showed the best results. Yes, it was not easy: the tasks turned out to be more difficult than many participants expected, and the format of the online championship with a conference stream, frankly, was tense. Still, 5 hours of online programming is not a retro experience in a team.
In total - three winners who received a cash prize, as well as invitations to work with us and with partners of the championship. Here they are:
1st place: Mikhail Burshtein, Moscow (250,000 rubles)
2nd place: Alexander Ilyushechkin, Moscow (150,000 rubles)
3rd place: Pavel Lyubinsky, St. Petersburg (100,000 rubles)
Tasks
# 1 Where is the ATM
Task details
-.
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
: GET IP:8080/atms/{deviceId}
:
— 200 AtmResponse
— 404 ErrorResponse ( )
:
:
18 .
- — .
3 ! -.
payments=true, (. - API ATMServices).
: GET IP:8080/atms/nearest?latitude=string&longitude=string&payments=boolean
: 200 AtmResponse
:
:
36 .
- .
, .
: GET IP:8080/atms/nearest-with-alfik?latitude=string&longitude=string&alfik=int
: 200 [AtmResponse]
, WebSocket STOMP .
:
( task1):
: ws://IP:8100
: { “deviceId”: 0 }
/topic/alfik: { “deviceId”: 0, “alfik”: 0 }
:
(.. 153463 — 383026 )
:
( , )
Condition
-.
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 Analyze it
Task details
Kafka RAW_PAYMENTS .
docker IP:29092 .
Kafka.
:
.
( task2):
* IP — IP .
* : github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task2
:
, key1 — .
.
:
RAW_PAYMENTS. .
, .
, Kafka .
.
, , . , ( , - ).
:
start.sh, . ,
Swagger- api-swagger.json .
:
( categoryId).
, .
.
200
:
:
analyticInfo Id .
— 200
— 404 + {«status»:”user not found"} ( )
:
:
40 .
. :
— 200
— 404 + {«status»:”user not found"} ( )
.
:
60
.
:
, .
— 200
— 404 + {«status»:”user not found"} ( )
, .
:
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 .
.
, , . , ( , - ).
:
- consumer-group-id. .
- 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"}
<h4>GET http://IP:8081/analytic</h4>
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 Free cash desk
Task details
- .
REST API IP:8082.
API – api.json.
, , , , .
PostgreSQL.
PostgreSQL:
( task3):
: IP:5432
DB: alfa_battle
Auth: alfa_battle / qwe123
* IP — IP .
* : github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task3
: GET IP:8082/branches/{id}
:
— 200 Branches
— 404 ErrorResponse ( )
:
:
8 .
-, , 1.
.
, Latitude Longitude.
: GET IP:8082/branches/lat=string&lon=string
: 200 Branches
:
Distance — , , .
28 .
, — , , , , .
.
: GET IP:8082/branches/{id}/predict?dayOfWeek=int&hourOfDay=int
:
— 200 BranchesWithPredicting
— 404 ErrorResponse ( )
:
dayOfWeek — , 1, 7
hourOfDay — 0 23
Predicting — , , .
:
.
, , apache-commons-math3
- .
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 Elastic loans
Task details
1 2 JSON:
person.json — , :
, :
ID — ,
DocId — ( 9 )
FIO —
Birthday — MM/dd/yyyy
Salary — , , .. 201.02 20102 .
Gender —
loans.json — , :
, :
Loan — ,
PersonId —
Amount — , , .. 201.02 20 102
StartDate — MM/dd/yyyy
Period —
, ElasticSearch .
REST- ElasticSearch, . IP:8083
ElasticSearch:
ElasticSearch IP:9200
( task4):
* IP — IP .
* : github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task4
ElasticSearch :
persons
loans
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»}
400 ( )
{
«status»: «person not found»
}
400 ( )
, — Document.
, :
countLoan —
sumAmountLoans —
loans —
, ( ElasticSearch).
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 On the promo needle
Task details
.
- .
.
REST API IP:8084 :
: /promo, /receipt.
* IP — IP .
* : github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task5
- , -, .
-, - .
- , -, .
2 RoundingMode.HALF_EVEN.
!!! , code review !!!
:
: 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 .
/promo - “ ” (LoyaltyCardRule). , .
- , /receipt loyaltyCard=true - , id .
:
: POST IP:8084/promo
: 200
: POST IP:8084/receipt
16 .
/promo - LoyaltyCardRule ItemCountRule. , .
N+k , N ID k . N=3, k=2:
.
- .
.
REST API IP:8084 :
- /promo – POST - ( , ). .
- /receipt – , POST id , (, ), , , .
: /promo, /receipt.
* IP — IP .
* : github.com/evgenyshiryaev/alfa-battle-resources/tree/master/task5
- CSV :
- 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:
Video recordings of reports
All 16 reports from our guys and partners from Beeline and X5 Retail Group you can see on the event website .
So what about hiring?
Damir Battulin, our director of the online channels development department, spoke about hiring, the bank's strategic initiatives, the principles of teamwork on projects and the criteria for choosing new specialists for the development of digitalization.
: , - . , , , , . . , .
. , . , , . , . .
You can view all our current vacancies (IT, marketing, HR, etc.) on this page.
For lovers of details - the entire interview
- 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. , . – , , . , . , - , , , .
— , . ?
— , , , . , , , . .
. , , .
— ?
— – . . . , , , .
– . , -, . , , . , .
, , , , , . — , , , . .
— -, 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. , . – , , . , . , - , , , .
— , . ?
— , , , . , , , . .
. , , .
— ?
— – . . . , , , .
– . , -, . , , . , .
, , , , , . — , , , . .