package models
|
|
import (
|
"errors"
|
"fmt"
|
"strconv"
|
"strings"
|
"time"
|
|
"model-engine/db"
|
"model-engine/pkg/logger"
|
"model-engine/service"
|
)
|
|
type DisappearModel struct {
|
AreaIds map[string]struct{}
|
Building string // 楼栋
|
Floor string // 楼层
|
AlarmType db.AlarmType // 预警方式
|
KeyPersonType string // 人员类型
|
PersonLabel string
|
DisappearTime int // 消失时间, 单位小时
|
AlarmInterval int // 报警时间间隔, 单位天
|
LastDirection string // 最后一次抓拍
|
MaxAge int // 年龄
|
MinAge int // 年龄
|
StartTime int64 // 起始时间
|
Task *db.ModelTask
|
}
|
|
func (m *DisappearModel) Init(task *db.ModelTask) error {
|
m.AreaIds = make(map[string]struct{})
|
for _, a := range task.DomainUnitIds {
|
m.AreaIds[a] = struct{}{}
|
}
|
|
m.Task = task
|
m.Building = task.Building
|
m.Floor = task.Floor
|
m.AlarmType = task.AlarmType
|
m.KeyPersonType = task.PersonType
|
m.PersonLabel = task.PersonLabel
|
m.StartTime = task.BeginTime.Unix()
|
|
for _, v := range task.Rules {
|
if v.Alias == "disappearTime" {
|
if val, ok := v.Value.(float64); ok {
|
m.DisappearTime = int(val)
|
}
|
}
|
|
if v.Alias == "alarmInterval" {
|
if val, ok := v.Value.(float64); ok {
|
m.AlarmInterval = int(val)
|
}
|
}
|
|
if v.Alias == "age" {
|
if val, ok := v.Value.(string); ok {
|
ages := strings.Split(val, ",")
|
m.MinAge, _ = strconv.Atoi(ages[0])
|
m.MaxAge, _ = strconv.Atoi(ages[1])
|
}
|
}
|
|
if v.Alias == "lastDirection" {
|
if val, ok := v.Value.(string); ok {
|
m.LastDirection = val
|
}
|
}
|
}
|
|
logger.Debugf("DisappearModel init finish ...task id:%s, name:%s, rule:%+v", task.ID, task.Name, m)
|
|
if m.DisappearTime == 0 {
|
logger.Warnf("invalid parameters. task id:%s, name:%s", task.ID, task.Name)
|
return errors.New("invalid parameters")
|
}
|
|
return nil
|
}
|
|
type PersonInfo struct {
|
DocumentNumber string `json:"document_number"`
|
CommunityId string `json:"community_id"`
|
OrgId string `json:"org_id"`
|
PersonName string `json:"person_name"`
|
IdCard string `json:"id_card"`
|
LastAppearanceTime int64 `json:"last_appearance_time"`
|
LastDirection string `json:"last_direction"`
|
LastLocation string `json:"last_location"`
|
}
|
|
func (m *DisappearModel) Run() error {
|
results := make([]*db.ModelTaskResults, 0)
|
var ageFilter, labelFilter, keyFilter, lastFilter []PersonInfo
|
|
if m.MinAge > 0 {
|
err := db.GetDB().Raw(`
|
SELECT
|
s.document_number,
|
s.community_id,
|
s.org_id,
|
p.person_name,
|
p.id_card,
|
s.last_appearance_time,
|
s.last_direction,
|
s.last_location
|
FROM
|
snapshot_count_summary AS s
|
JOIN person AS p ON p.id = s.document_number
|
WHERE
|
s.last_appearance_time > ?
|
AND p.id_card != ""
|
AND TIMESTAMPDIFF(
|
YEAR,
|
STR_TO_DATE( CASE WHEN LENGTH( id_card ) = 18 THEN SUBSTRING( id_card, 7, 8 ) ELSE NULL END, '%Y%m%d' ),
|
CURDATE( )
|
) >= ?
|
AND TIMESTAMPDIFF(
|
YEAR,
|
STR_TO_DATE( CASE WHEN LENGTH( id_card ) = 18 THEN SUBSTRING( id_card, 7, 8 ) ELSE NULL END, '%Y%m%d' ),
|
CURDATE( )
|
) <= ?
|
`, m.StartTime, m.MinAge, m.MaxAge).Scan(&ageFilter).Error
|
if err != nil {
|
logger.Warnf(err.Error())
|
}
|
|
if len(ageFilter) == 0 {
|
return fmt.Errorf("no results found that match the age condition %s - %s", m.MinAge, m.MaxAge)
|
}
|
|
logger.Debugf("task %s match age result %d", m.Task.Name, len(ageFilter))
|
}
|
|
if m.PersonLabel != "" {
|
labels := strings.Split(m.PersonLabel, ",")
|
err := db.GetDB().Raw(`
|
SELECT
|
s.document_number,
|
s.community_id,
|
s.org_id,
|
p.person_name,
|
p.id_card,
|
s.last_appearance_time,
|
s.last_direction,
|
s.last_location
|
FROM
|
snapshot_count_summary AS s
|
JOIN person AS p ON p.id = s.document_number
|
JOIN person_label AS l ON p.id = l.person_id
|
WHERE
|
s.last_appearance_time > ?
|
AND l.label_id IN ?
|
`, m.StartTime, labels).Scan(&labelFilter).Error
|
if err != nil {
|
logger.Warnf(err.Error())
|
}
|
|
if len(labelFilter) == 0 {
|
return fmt.Errorf("no results found that match the label condition %s", m.PersonLabel)
|
}
|
|
logger.Debugf("task %s match label result %d", m.Task.Name, len(labelFilter))
|
}
|
|
// 合并一下条件
|
if m.MinAge > 0 && m.PersonLabel != "" {
|
lastFilter = intersectPersonInfo(ageFilter, labelFilter)
|
} else if m.MinAge > 0 {
|
lastFilter = ageFilter
|
} else if m.PersonLabel != "" {
|
lastFilter = labelFilter
|
}
|
|
if m.KeyPersonType != "" {
|
keyTypes := strings.Split(m.KeyPersonType, ",")
|
err := db.GetDB().Raw(`
|
SELECT
|
s.document_number,
|
s.community_id,
|
s.org_id,
|
p.person_name,
|
p.id_card,
|
s.last_appearance_time,
|
s.last_direction,
|
s.last_location
|
FROM
|
snapshot_count_summary AS s
|
JOIN person AS p ON p.id = s.document_number
|
JOIN key_person AS k ON k.id_card = p.id_card
|
WHERE
|
s.last_appearance_time > ?
|
AND s.key_status = 1
|
AND p.id_card != ""
|
AND k.person_type IN ?
|
`, m.StartTime, keyTypes).Scan(&keyFilter).Error
|
if err != nil {
|
logger.Warnf(err.Error())
|
}
|
|
logger.Debugf("task %s match key person result %d", m.Task.Name, len(keyFilter))
|
|
if len(lastFilter) > 0 {
|
lastFilter = intersectPersonInfo(lastFilter, keyFilter)
|
} else {
|
lastFilter = keyFilter
|
}
|
}
|
|
logger.Debugf("task %s last result %d", m.Task.Name, len(lastFilter))
|
|
var personIds = make(map[string]struct{}, 0)
|
for _, p := range lastFilter {
|
if len(m.AreaIds) > 0 {
|
_, o1 := m.AreaIds[p.CommunityId]
|
_, o2 := m.AreaIds[p.OrgId]
|
if !o1 && !o2 {
|
continue
|
}
|
}
|
|
if m.LastDirection != "" {
|
if p.LastDirection != m.LastDirection {
|
continue
|
}
|
}
|
|
if isOlderThanGivenHours(p.LastAppearanceTime, m.DisappearTime) {
|
result := &db.ModelTaskResults{
|
Title: m.Task.Name,
|
Event: m.eventFormat(p.LastAppearanceTime, p.LastDirection),
|
ModelID: m.Task.ModelID,
|
ModelTaskID: m.Task.ID,
|
CommunityId: p.CommunityId,
|
OrgID: p.OrgId,
|
ObjectIds: p.DocumentNumber,
|
Location: p.LastLocation,
|
PicDate: time.Unix(p.LastAppearanceTime, 0).Format("2006-01-02 15:04:05"),
|
FirstPersonID: p.DocumentNumber,
|
}
|
|
// 同一个人报警一次
|
if _, ok := personIds[p.DocumentNumber]; ok {
|
continue
|
} else {
|
personIds[p.DocumentNumber] = struct{}{}
|
}
|
|
results = append(results, result)
|
}
|
}
|
|
logger.Debugf("task %s last filter result %d", m.Task.Name, len(results))
|
return service.SaveTaskResults(results)
|
}
|
|
func (m *DisappearModel) KeepAlive() error {
|
db.GetDB().Model(m.Task).Where("id = ?", m.Task.ID).Update("last_run_time", time.Now())
|
return nil
|
}
|
|
func (m *DisappearModel) Shutdown() error {
|
// 清理资源
|
fmt.Println("Shutting down Disappear Model")
|
return nil
|
}
|
|
func intersectPersonInfo(arr1, arr2 []PersonInfo) []PersonInfo {
|
// 使用 map 来存储 arr1 中的 DocumentNumber
|
resultMap := make(map[string]PersonInfo)
|
var result []PersonInfo
|
|
// 将第一个数组的 DocumentNumber 添加到 map 中
|
for _, person := range arr1 {
|
resultMap[person.DocumentNumber] = person
|
}
|
|
// 遍历第二个数组,检查 DocumentNumber 是否在 map 中
|
for _, person := range arr2 {
|
if _, exists := resultMap[person.DocumentNumber]; exists {
|
// 如果存在交集,将该元素加入结果数组
|
result = append(result, person)
|
// 可选:如果希望每个交集元素只出现一次,可以删除 map 中的元素
|
delete(resultMap, person.DocumentNumber)
|
}
|
}
|
|
return result
|
}
|
|
func isOlderThanGivenHours(timestamp int64, hours int) bool {
|
// 将时间戳转换为时间对象
|
timestampTime := time.Unix(timestamp, 0)
|
|
// 获取当前时间
|
currentTime := time.Now()
|
|
// 计算当前时间减去指定小时数的时间
|
timeThreshold := currentTime.Add(-time.Duration(hours) * time.Hour)
|
|
// 判断时间戳是否早于该时间
|
return timestampTime.Before(timeThreshold)
|
}
|
|
func (m *DisappearModel) eventFormat(lastAppearTime int64, lastDirection string) string {
|
lastTime := time.Unix(lastAppearTime, 0)
|
currentTime := time.Now()
|
|
duration := currentTime.Sub(lastTime)
|
|
// 输出时间差的小时数
|
var durationStr = "小时"
|
hours := duration.Hours()
|
if hours > 24 {
|
hours = hours / 24
|
durationStr = "天"
|
}
|
|
var direction string
|
var reverse = "未出现"
|
if lastDirection == "in" {
|
direction = "进"
|
reverse = "未出"
|
} else if lastDirection == "out" {
|
direction = "出"
|
reverse = "未归"
|
}
|
|
return fmt.Sprintf("%s%s,持续%.1f%s%s", lastTime.Format("2006-01-02 15:04:05"), direction, hours, durationStr, reverse)
|
}
|