[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) Если текущий вес не меньше весов машин ("тяжеловесов") из ранее полученного списка, то к таким машинам из списка демпфирование также не применяется (демпфировать следует только самых тяжелых "лидеров").