[TOC]
# Описание скрипта решения начальной стадии задачи
# Основные источники информации
Основным источником информации, по которой строятся и данные профайлов машин и персон и должна строится таблица соответствия между машинами и персонами, являются события, регистрируемые в пунктах наблюдения и предварительно распознаваемые с использованием обученных нейросетей. Эта информация записана в рабочей базе данных в виде двух таблиц cars_events и customer_events. Алгоритм решения реализован в виде скрипта на языке javascript. Чтение данных в скрипте осуществляется из файлов, в которые выполняется экспорт из таблиц cars_events и customer_events
## SQL код для экспорта данных для скрипта
### Экспорт событий машин:
Экспорт событий машин из таблицы cars_events выполняется с помощью следующего SQL запроса:
<details>
<summary>Раскрыть</summary>
```sql=
/****** Script for Select N Rows of car events for subsequent interval interval detection******/
DECLARE @startDate as DateTime
SET @startDate = CAST('2021-03-09' as date)
SELECT TOP (1000000)
[car_id],
[place_id],
[event_type_id],
DATEDIFF(MILLISECOND,@startDate,event_begin)/1000.0
[t],
DATEDIFF(MILLISECOND,event_begin,event_end)/1000.0
[dt]
FROM cars_events
group by car_id,event_begin,event_end,place_id,event_type_id
order by car_id,event_begin
```
Результат экспорта событий машин
```cpp=
car_id p_id e_type t dt
16289 7 2 41237.896000 0.000000
16289 13 6 41444.473000 283.640000
16289 5 4 41709.313000 0.000000
16290 5 4 41254.526000 0.000000
16291 7 2 41259.686000 0.000000
16291 7 2 138807.900000 0.000000
16291 7 2 138871.210000 0.000000
16291 7 2 138919.190000 0.000000
16291 7 2 299098.353000 0.000000
16291 7 2 299136.466000 0.000000
16291 7 2 299172.373000 0.000000
16291 7 2 299203.056000 0.000000
16291 7 2 299231.240000 0.000000
16291 7 2 299255.866000 0.000000
16291 7 2 299283.246000 0.000000
16292 7 2 41264.860000 0.000000
16292 5 4 42957.183000 0.000000
16293 5 4 41272.776000 0.000000
16294 16 6 41281.440000 414.140000
16294 5 4 41715.470000 0.000000
16294 23 6 1327696.476000 727.987000
16294 5 4 1328491.446000 0.000000
16295 18 6 41284.326000 38.854000
16295 5 4 41351.570000 0.000000
```
</details>
### Экспорт событий людей
Экспорт из таблицы customer_events выполняется с помощью следующего SQL запроса:
<details>
<summary>Раскрыть</summary>
```sql=
/****** Script for Select N Rows of customer events for subsequent interval interval detection******/
DECLARE @startDate as DateTime
SET @startDate = CAST('2021-03-09' as date)
SELECT TOP (1000000)
[customer_id]
,[place_id]
,[event_type_id]
-- ,[event_begin]
-- ,[event_end]
, DATEDIFF(MILLISECOND,@startDate,event_begin)/1000.0
[t],
DATEDIFF(MILLISECOND,event_begin,event_end)/1000.0
[dt]
FROM customer_events
group by customer_id,event_begin,event_end,place_id,event_type_id
order by customer_id,event_begin
```
Результат экспорта
```cpp=
cust_id p_id e_type t dt
28316 3 3 41137.250000 1.700000
28317 3 3 41163.250000 1.566000
28318 3 3 41197.250000 6.700000
28318 3 3 42766.583000 1.933000
28318 3 3 44335.353000 0.633000
28318 3 3 46255.150000 0.500000
28318 3 3 1075574.626000 1.300000
28318 3 3 1077769.726000 1.200000
28318 3 3 1244268.510000 0.366000
28318 3 3 1249386.156000 5.100000
28318 3 3 1702511.180000 1.300000
28319 4 5 41173.426000 49.790000
28319 4 5 41238.210000 20.516000
28319 4 5 41274.200000 4.680000
28319 4 5 41283.796000 23.634000
```
</details>
> *t* - это время начала очередного события для машин (для моего удобства я его представляю в секундах относительно заданной стартовой даты)
>
>
> d*t* - это длительность этого события - для въезда и выезда - это 0, а вот для события Prk это время очень важно для адекватной оценки принадлежности интервала пребывания клиента на заправке интервалу пребывания машины
# Код скрипта
Ниже представлен текущий вариант кода скрипта решения задачи соответствия
<details>
<summary>Раскрыть</summary>
<iframe src= https://hackmd.io/@ArthMax/Hk1u8CRL_ width=1200 height=1200></iframe>
</details>
# Описание скрипта
В коде решаются следующие подзадачи
## 1. Получение полных списков событий
### 1.1 Чтение данных событий для машин
Чтение данных событий для машин из файла car_event_from_sql2.txt (строки 43..61) и заполнение массива *carsEvents[]*, каждый элемент которого - массив событий конкретного профиля машины:
```javascript=43
var filetxt = fs.readFileSync('./car_event_from_sql2.txt')+'';
events = filetxt.split(/\r?\n/)
var ic=0;
events.forEach( line => {
...
...
```
### 1.2 Чтение данных событий для людей
Чтение данных событий для людей из файла customer_events_from_sql2.txt (строки 502..522) и заполнение массива *customersEvents[]*, каждый элемент которого - массив событий конкретного профиля человека:
```javascript=502
var filetxt = fs.readFileSync('./customer_events_from_sql2.txt')+'';
events = filetxt.split(/\r?\n/)
var ip=0;
events.forEach( line => {
...
...
```
## 2. Временная привязка каждого события следующему
Выполняется дополнение каждого события внутри одного профиля смещением *diff* относительно текущего события до следующего.
### 2.1 Дополнительный атрибут событий машин
Вычисление и добавление дополнительного атрибута *diff* событий для машин (строки 64..78):
```javascript=64
for(var i=1; i< ic; i++) // i - internal car number for this script
{
let events = carsEvents[i]
let el=events.length
for(let e=0;e<el;e++)
...
...
```
### 2.2 Дополнительный атрибут событий людей
Вычисление и добавление дополнительного атрибута *diff* событий для людей (строки 525..539)
```javascript=525
for(var i=1; i<= ip; i++) // i - internal customer number for this script
{
let events = customersEvents[i]
let el=events.length
for(let e=0;e<el;e++)
{
...
...
```
> Это дополнение (*diff*) необходимо для определения границ визита пребывания человека и интервала машины на заправке. В принципе, можно было бы усложнить SQL код экспорта из базы, например, с использованием оконных фунций, для того, чтобы не выполнять в скрипте эту подзадачу.
## 3. Определение визитов пребывания людей и интервалов машин на заправке
### 3.1 Определение интервалов пребывания машин на заправке
В строках (83..127):
```javascript=525
var I=[]
var car
for(var i=1; i< ic; i++) // i - car number
{
let events = carsEvents[i] // events only for car i
let el=events.length
car=carsEvents[i][0]
...
...
```
кроме обнаружения интервалов, для каждого интервала выполняется коррекциия и построение данных для функции принадлежности конкретному интервалу (вызов функции *Irecovery()* в строке 118)
```javascript=118
let intervalFunctionData = Irecovery()
```
В результате получаем массивы *carsIntervals[]* и *sortedCarsIntervalAndWeights[]*.
> Массив *carsIntervals[]* содержит для каждой машины данные об интервалах и их функциях принадлежности. Эти данные для кажой машины отсортированы в порядке возрастания времени начала интервала.
> Массив *sortedCarsIntervalAndWeights[]* содержит все интервалы, глобально отсортированные по времени начала, для последующей оптимизации выполнения процедуры пересечений множества визитов и множества интервалов и оценок этих пересечений и реализации варианта демпфирования оценок для возможности проявления смены владельца машины
#### 3.1.1 Коррекциия и построение данных для функции принадлежности интервалу
Коррекциия и построение данных для функции принадлежности интервалу выполняется в функции *function Irecovery()* (строки 170...498).
```javascript=170
function Irecovery(){
let Imin=Number.MAX_VALUE;
let Imax=0
let ets=[]
let t1=0;
let dt=0;
let t2=0;
for(let i=0; i<I.length;i++)
{
...
...
```
Эта коррекция необходима из-за большой доли неполных интервалов, нарушения порядка событий в них, дублирования событий одного типа в интервале.
Входящей информацией для этой функции является массив *I[]*, содержащий последовательность определенных интервалов для каждой конкретной машины.
> массив *I[]* является глобальным, что не очень хорошо с точки зрения красивого кодирования, но это легко исправить при переписывании кода. Значения этого массива заново формируются для каждой машины c индексом *i* в цикле "for(var i=1; i< ic; i++) // i - car number ..."(строки 85...125) и очередная такая последовательность передается для коррекции функции *Irecovery()* в строке 118
В процессе проверки и коррекции каждого интервала формируется список особых временных точек интервала с их весами для вычисления оценки принадлежности к интервалу (строки 193...491):
```javascript=193
if(etS.size==1)
{
let pt=etss[0];
if(pt==2) //if only Begins points
{
let w=0;
for(let i=0; i<I.length;i++)
{
...
...
```
Для этого обрабатывается сформированные списки элементарных событий интервала и определяются границы и важные внутренние точки как полностью, так и частично определенных интервалов. Также принимаются решения, что делать с событиями, нарушающими естественную последовательность событий интервала.
### 3.2 Определение визитов людей на заправке
В строках (544..579) выполняется обработка входных расширенных событий людей и в результате получается массив визитов людей на заправку *customersIntervals[]*:
```javascript=544
var I=[]
var customer
for(var i=1; i<= ip; i++) // i - customer number
{
let events = customersEvents[i]
let el=events.length
customer=customersEvents[i][0]
...
...
```
*customersIntervals[p]* - множество визитов на заправку человека c номером (индексом) *p* . Это множество для каждого человека также отсортировано по возрастанию времени начала визита.
> Объект (человек или машина) считается находящимся в пределах интервала (визита), если разница между событиями не превышает $\tau$, сейчас имеющего значение 3600 секунд = 1 час (строка 22). В данном коде параметр $\tau$ один и тот же, как для машин. так и для людей. Можно подправить код, так чтобы для людей и для машин этот параметр имел бы разные значения (например, для людей меньше или, может быть, больше)
## 4. Оценка принадлежности визита интервалам
### 4.1 Начальная оценка принадлежности визита интервалам
Начальная оценка *Wm* принадлежности визита человека интервалам машин вычисляется при однократном попадании визита в интервал.
> Для четко определенных интервалов эта проверка корректна. К сожалению, входные данные для машин порождают очень много неполных интервалов. В функции Irecovery() (строки 168…496) выполняется коррекция и дополнение незаданных событий, исходя из статистики для полных. Однако, эти значения коррекции по умолчанию можно настраивать (строки 26...28), в частности, если есть подозрение, что алгоритм пропускает множество соответствий:
```javascript=26
defaultShiftFromStartToPrk=1.5*60 // default start parameter for missed events Begin or Prk
defaultPrkDuration=3*60 // default Prk duration parameter for missed event Prk
defaultShiftFromPrkToExit=30 // default time shift from Prk to Exit for missed event Prk or Exit
```
Перед вычислением начальной оценки принадлежности, необходимо гарантировать, что границы визита лежат строго внутри интервала. Когда для одного человека имеется несколько его событий на одном и том же интервале машины, вычисляются значения *w<sub>l</sub>* оценок принадлежности для всех событий данного визита человека на этом интервале. Из этих оценок событий визита человека для одного интервала выбирается максимальное значение *Wm* (строки 630...660).
```javascript=630
Wm=0; // future max weight for car number k
Tm=0;
for(let l = 0; l < pin; l++) //l - index of event list of one interval of customer
{
let pers_t = customersVisits[i][j][l][2] // time of event in current customer interval
for(let m = 0; m < endIndex; m++) //m - index of timepoints of one interval of car k (current car)
{
// search subinterval [t0,t1] for customer time pers_t
...
...
```
Значения оценки вычисляются путем линейной интерполяци весов по времени попадания события человека между точками интервала машины и уменьшаются в заданном масштабе *downScalingFactor=10* для разделения масштабов оценок принадлежности при однократном и многократном попадании визитов конкретного человека в интервалы заданной машины (строка 648):
```javascript=648
w = (w0* _x + w1*x)/h/downScalingFactor
```
### 4.2 Изменение оценки принадлежности при повторных совпадениях визитов и интервалов
Начальная максимизированная оценка $w_0=W_m$ запоминается и при повторных попаданиях сильно модифицируется (увеличивается) согласно формуле :
$$ w'= \max \left(
1 -(1-w)^M, 1 -(1-W_m)^M
\right),$$
где $1\lt M$ - параметр алгоритма (в коде он имеет имя *mParameter* ).
> В предлагаемом варианте скрипта $M=8$, что позволяет достаточно плавно поднимать веса при повторных совпадениях визитов человека и интервалов машины, приближая оценку к 1, начиная с 4-х совпадений. Если необходимо максимально жестко поднимать веса уже при двух совпадениях, то необходимо повысить значение этого параметра, например, до $M=16$
>
```javascript=661
let carW = carsWeightsMap.get(car_id)
if(!carW)carsWeightsMap.set(car_id,Wm)
else carsWeightsMap.set(car_id,Math.max(1-(1-carW)**mParameter, 1-(1-Wm)**mParameter) )
```
### 4.3 Демпфирование ранних оценок принадлежности более поздними
В описываемой версии кода выполняется демпфирование новыми соответствиями старых - это сделано для плавного выделения во времени текущего "лидера" соответствий. Демпфирование выполняется параметром weigthDempherFactor=0.95.
```javascript=26
const weigthDempherFactor=0.95
```
> Если есть подозрения, что часть совпадений слишком задавливается, то его нужно сделать равным 1.
Сама операция демпфирования выполняется в строках (679..692)
```javascript=679
for (var [c, w] of carsWeightsMap) {
let cRC = carsRepeatCountMap.get(c)
if(newWeight <= 0.9)
{
if(carPersCrossCount <= cRC) continue // for low weight compare it's count with current carPersCrossCount
if( newWeight < w ) continue // do not dempfer weights if car number of repeats greater or equal carPersCrossCount
}
// newWeight > 0.9
if( newWeight >=w ) continue ; // do not dempfer high weight if it is not enough high
w *= weigthDempherFactor
carsWeightsMap.set(c,w)
...
}
```
При выполнении этой операции для всех машин, интервалы которых охватывали визиты данной персоны, учитывается, что интервалы машин упорядочены в порядке возраствния времени начала интервала.
При демпфировании применялись следующие правила:
а) Для неведущих весов ($\le 0.9$)
1) Если текущее количество совпадений оказывается меньше, чем у машин из ранее полученного списка, то к таким машинам из списка демпфирование не применяется (учет повторов)
2) Если текущий вес меньше весов машин из ранее полученного списка, то к таким машинам из списка демпфирование также не применяется (учет весов).
б) Для ведущих весов ($\gt 0.9$)
1) Если текущий вес не меньше весов машин ("тяжеловесов") из ранее полученного списка, то к таким машинам из списка демпфирование также не применяется (демпфировать следует только самых тяжелых "лидеров").