package models
|
|
import (
|
"errors"
|
"fmt"
|
"strconv"
|
"strings"
|
"time"
|
|
"model-engine/db"
|
"model-engine/pkg/logger"
|
"model-engine/service"
|
)
|
|
type RegularityModel struct {
|
OrgIds []interface{} `json:"-"`
|
AreaIds []interface{} `json:"-"`
|
IdentityType []string // 人员身份类型 陌生人, 访客, 住户
|
KeyPersonType string // 重点人员类型
|
PersonLabel string // 人员身份标签
|
Task *db.ModelTask
|
AlarmType db.AlarmType // 预警方式
|
|
Appearances int // 出现次数
|
Duration int // 时间范围, 单位天
|
LastDirection string // 最后一次出行的方向
|
StartHour int // 开始计算的时间 配置为小时 23 - 02 表示第一天23点 - 第二天的2点
|
EndHour int // 结束时间
|
}
|
|
func (m *RegularityModel) Init(task *db.ModelTask) error {
|
if len(task.DomainUnitIds) == 0 {
|
return errors.New("empty domain set")
|
}
|
orgIds, areaIds, err := service.GetOrgIdsAndAreaIdsByDomainUnitIds(task.DomainUnitIds)
|
if err != nil {
|
return err
|
}
|
|
m.Task = task
|
m.OrgIds = orgIds
|
m.AreaIds = areaIds
|
m.AlarmType = task.AlarmType
|
m.KeyPersonType = task.PersonType
|
m.PersonLabel = task.PersonLabel
|
m.LastDirection = "out"
|
if task.IdentityType != "" {
|
for _, t := range strings.Split(task.IdentityType, ",") {
|
if t == "all" {
|
m.IdentityType = []string{"stranger", "visitor", "resident"}
|
break
|
} else {
|
m.IdentityType = append(m.IdentityType, t)
|
}
|
}
|
} else {
|
m.IdentityType = []string{"stranger", "visitor", "resident"}
|
}
|
|
for _, v := range task.Rules {
|
if v.Alias == "timeRange" {
|
if val, ok := v.Value.(string); ok {
|
ages := strings.Split(val, ",")
|
m.StartHour, _ = strconv.Atoi(ages[0])
|
m.EndHour, _ = strconv.Atoi(ages[1])
|
}
|
}
|
|
if v.Alias == "appearances" {
|
if val, ok := v.Value.(float64); ok {
|
m.Appearances = int(val)
|
}
|
}
|
|
if v.Alias == "duration" {
|
if val, ok := v.Value.(float64); ok {
|
m.Duration = int(val)
|
}
|
}
|
|
if v.Alias == "direction" {
|
if val, ok := v.Value.(string); ok {
|
m.LastDirection = val
|
}
|
}
|
}
|
|
// 默认计算30天的数据
|
if m.Duration == 0 {
|
m.Duration = 30
|
}
|
|
if m.StartHour == 0 || m.EndHour == 0 {
|
return fmt.Errorf("task id:%s, %s timeRange Time range setting error. %+v", task.ID, task.Name, task.Rules)
|
}
|
|
logger.Debugf("LocationModel init finish ...task id:%s, name:%s, rule:%+v", task.ID, task.Name, m)
|
|
return nil
|
}
|
|
func (m *RegularityModel) Run() error {
|
results := make([]*db.ModelTaskResults, 0)
|
baseFilter := make([]PersonInfo, 0)
|
|
// 查找指定时间范围内出行过的档案编号
|
now := time.Now()
|
startDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()).AddDate(0, 0, -m.Duration)
|
err := db.GetDB().Raw(`
|
SELECT
|
document_number,
|
frequent_address,
|
community_id,
|
FROM
|
snapshot_count_summary
|
WHERE
|
last_appearance_time > ?
|
AND (community_id IN ?
|
OR org_id IN ?)
|
AND status IN ?
|
`, startDate.Unix(), m.AreaIds, m.OrgIds, m.IdentityType).Scan(&baseFilter).Error
|
if err != nil {
|
logger.Warnf(err.Error())
|
}
|
|
esCli := db.GetEsClient()
|
for _, p := range baseFilter {
|
// 调用es分析此人的出行规律是否符合条件, 返回符合条件的次数和最后一次符合条件的时间
|
sTime := startDate.Format(time.DateTime)
|
eTime := time.Now().Format(time.DateTime)
|
captures, err := service.QueryEsRecord(esCli, sTime, eTime, nil, []interface{}{p.CommunityId}, []string{p.DocumentNumber})
|
if len(captures) == 0 || err != nil {
|
continue
|
}
|
|
// 根据抓拍时间和出入方向,计算符合规则内的出入次数
|
hitCount := countValidDays(captures, m.StartHour, m.EndHour, m.LastDirection)
|
|
if hitCount > m.Appearances {
|
// 写数据库
|
result := &db.ModelTaskResults{
|
Title: m.Task.Name,
|
Event: fmt.Sprintf("%s %d次", m.Task.Name, hitCount),
|
ModelID: m.Task.ModelID,
|
ModelTaskID: m.Task.ID,
|
CommunityId: p.CommunityId,
|
OrgID: p.OrgId,
|
ObjectIds: p.DocumentNumber,
|
Location: p.FrequentAddress,
|
PicDate: time.Unix(p.LastAppearanceTime, 0).Format(time.DateTime),
|
FirstPersonID: p.DocumentNumber,
|
}
|
|
results = append(results, result)
|
}
|
}
|
|
logger.Debugf("task %s last filter result %d", m.Task.Name, len(results))
|
return service.SaveTaskResults(results)
|
}
|
|
func (m *RegularityModel) KeepAlive() error {
|
db.GetDB().Model(m.Task).Where("id = ?", m.Task.ID).Update("last_run_time", time.Now())
|
return nil
|
}
|
|
func (m *RegularityModel) Shutdown() error {
|
// 清理资源
|
fmt.Println("Shutting down LocationModel Model")
|
return nil
|
}
|
|
func (m *RegularityModel) eventFormat() string {
|
return ""
|
}
|
|
func countValidDays(records []*service.ESRecordInfo, startHour, endHour int, direction string) int {
|
layout := "2006-01-02 15:04:05" // 时间格式
|
lastDirectionMap := make(map[string]string) // 记录最后一条 Direction
|
lastTimeMap := make(map[string]time.Time) // 记录最后一条时间
|
|
// 判断是否跨天
|
var isCrossDay bool
|
if endHour < startHour {
|
isCrossDay = true
|
}
|
|
for _, record := range records {
|
// 解析时间
|
t, err := time.Parse(layout, record.PicDate)
|
if err != nil {
|
fmt.Println("解析时间失败:", err)
|
continue
|
}
|
|
// 获取小时
|
hour := t.Hour()
|
var key string
|
|
if !isCrossDay {
|
// 判断时间范围,并归属到某一天
|
if hour >= startHour && hour < endHour {
|
key = t.Format("2006-01-02")
|
} else {
|
continue // 不在统计范围内
|
}
|
} else {
|
// 判断时间范围,并归属到某一天
|
if hour >= startHour { // 21:00-23:59 归属当天
|
key = t.Format("2006-01-02")
|
} else if hour < endHour { // 00:00-02:59 归属前一天
|
key = t.AddDate(0, 0, -1).Format("2006-01-02")
|
} else {
|
continue // 不在统计范围内
|
}
|
}
|
|
// 记录该时间段内的最后一条数据
|
if lastTime, exists := lastTimeMap[key]; !exists || t.After(lastTime) {
|
lastTimeMap[key] = t
|
lastDirectionMap[key] = record.CameraLocation.Direction
|
}
|
}
|
|
// 统计符合条件
|
count := 0
|
for _, dir := range lastDirectionMap {
|
if dir == direction || direction == "" {
|
count++
|
}
|
}
|
|
return count
|
}
|