| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284 |
- package controllers
- import (
- "ColdVerify_local/lib"
- "ColdVerify_local/logs"
- "ColdVerify_local/models/Device"
- "ColdVerify_local/models/Task"
- "ColdVerify_local/models/VerifyTemplate"
- "errors"
- "fmt"
- "math"
- "math/rand"
- "net/http"
- "sort"
- "strconv"
- "strings"
- "time"
- "github.com/beego/beego/v2/client/orm"
- beego "github.com/beego/beego/v2/server/web"
- )
- type TaskDataHandleController struct {
- beego.Controller
- }
- /*
- 同区域数据缺失
- */
- // 测点自检 自动添加缺失终端,取关联绑定终端平均复制
- func (c *TaskDataHandleController) SSE_Automatically_add_missing_terminal() {
- T_task_id := c.GetString("T_task_id") // v26nplogbwt1
- T_start := c.GetString("T_start") // 开始测点编号
- T_end := c.GetString("T_end") // 结束测点编号
- println("T_task_id:", T_task_id, "T_start:", T_start, "T_end:", T_end)
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 解析开始/结束编号
- var startId, endId int
- hasRange := false
- if T_start != "" {
- startId, _ = strconv.Atoi(T_start)
- hasRange = true
- }
- if T_end != "" {
- endId, _ = strconv.Atoi(T_end)
- hasRange = true
- }
- if !hasRange || startId <= 0 || endId <= 0 || startId > endId {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "请指定有效的开始和结束测点编号!"})
- return
- }
- // 获取所有设备,构建T_id -> Device映射
- DeviceClassList_r := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
- deviceMap := make(map[int]Device.DeviceClassList)
- for _, d := range DeviceClassList_r {
- tId, err := strconv.Atoi(d.T_id)
- if err == nil {
- deviceMap[tId] = d
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("终端总共:%d 个,检查范围: %03d ~ %03d,正在检查自检探头...", len(DeviceClassList_r), startId, endId)})
- // 获取第一个设备用于获取T_remark参考
- var firstRemark string
- if len(DeviceClassList_r) > 0 {
- firstRemark = DeviceClassList_r[len(DeviceClassList_r)-1].T_remark
- }
- // 遍历完整范围,不限于设备列表
- for id := startId; id <= endId; id++ {
- tidStr := fmt.Sprintf("%03d", id)
- var class_ Device.DeviceClassList
- device, exists := deviceMap[id]
- if exists {
- class_ = device
- } else {
- // 虚拟设备:T_sn和T_id保持一致
- class_ = Device.DeviceClassList{
- T_class: Task_r.T_class,
- T_id: tidStr,
- T_sn: tidStr,
- T_remark: firstRemark,
- }
- }
- // 检查该测点是否已有数据
- // 真实设备用实际T_sn查询,虚拟设备用T_id查询
- querySn := tidStr
- if exists {
- querySn = class_.T_sn
- }
- _, cnt := Task.Read_TaskData_ById_List(Task_r.T_task_id, querySn, class_.T_id, "", "", 0, 1)
- if cnt == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "找到" + class_.T_id + " " + class_.T_remark + " 自检探头"})
- // 使用T_id作为T_sn,确保补充的数据sn=编号
- device := Device.DeviceClassList{
- T_class: class_.T_class,
- T_id: class_.T_id,
- T_sn: tidStr,
- T_remark: class_.T_remark,
- }
- c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
- // -----------开始平均复制到结束
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- // Close the connection
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // selectReferenceSensor 选择最稳定的传感器作为参考(标准差最小)
- func selectReferenceSensor(allData map[string]map[string]float32, devices []Device.DeviceClassList) string {
- var bestId string
- bestStddev := float32(math.MaxFloat32)
- for _, dev := range devices {
- tm := allData[dev.T_id]
- if tm == nil || len(tm) < 10 {
- continue
- }
- var sum, sumSq float32
- var count int
- for _, v := range tm {
- sum += v
- sumSq += v * v
- count++
- }
- if count < 10 {
- continue
- }
- mean := sum / float32(count)
- variance := sumSq/float32(count) - mean*mean
- stddev := float32(math.Sqrt(float64(variance)))
- if stddev < bestStddev {
- bestStddev = stddev
- bestId = dev.T_id
- }
- }
- return bestId
- }
- // calcBaselineDiff 计算传感器与参考传感器的基准差值(取前N分钟数据的均值)
- func calcBaselineDiff(sensorData, refData map[string]float32, allTimes []string, minutes int) float32 {
- if len(allTimes) == 0 {
- return 0
- }
- startTime := allTimes[0]
- st, _ := time.Parse("2006-01-02 15:04", startTime)
- endTime := st.Add(time.Duration(minutes) * time.Minute)
- var sum float32
- var count int
- for _, t := range allTimes {
- ct, _ := time.Parse("2006-01-02 15:04", t)
- if ct.After(endTime) {
- break
- }
- sp, ok1 := sensorData[t]
- rp, ok2 := refData[t]
- if ok1 && ok2 {
- sum += sp - rp
- count++
- }
- }
- if count == 0 {
- return 0
- }
- return sum / float32(count)
- }
- // getDeviceById 根据T_id查找设备
- func getDeviceById(devices []Device.DeviceClassList, id string) (Device.DeviceClassList, bool) {
- for _, d := range devices {
- if d.T_id == id {
- return d, true
- }
- }
- return Device.DeviceClassList{}, false
- }
- // findNearestTime 在时间列表中找目标时间前后最近的两个时间点
- func findNearestTime(allTimes []string, target string) (string, string) {
- var prev, next string
- for _, t := range allTimes {
- if t < target {
- prev = t
- } else if t > target && next == "" {
- next = t
- }
- }
- return prev, next
- }
- // 测点数据自检 自动添加缺失数据,取关联绑定终端平均复制
- func (c *TaskDataHandleController) SSE_Automatically_add_missing_data() {
- T_task_id := c.GetString("T_task_id") // v26nplogbwt1
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 时间间隔 s
- T_saveT := 60
- DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
- // 分组统计每个sn的数据数量
- snList := Device.JoinDeviceClassListSnToString(DeviceClassList_list)
- TaskData_Total_GroupBySnId := Task.Read_TaskData_Total_GroupBySnId(Task_r.T_task_id, snList, "", "")
- if len(TaskData_Total_GroupBySnId) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "暂无测点需要自检!"})
- }
- Devicedata_list_MAX := TaskData_Total_GroupBySnId[0].Total
- TaskData_Total_GroupBySnId_Map := make(map[string]int64)
- for _, v := range TaskData_Total_GroupBySnId {
- TaskData_Total_GroupBySnId_Map[v.T_sn] = v.Total
- }
- startTime, endTime := Task.Read_TaskData_T_time_T_Min_Max(Task_r.T_task_id, TaskData_Total_GroupBySnId[0].T_sn, TaskData_Total_GroupBySnId[0].T_id, "", "")
- logs.Println("数据标准数量:", Devicedata_list_MAX)
- // 选择 数据缺失的终端
- for _, DeviceClassList_r := range DeviceClassList_list {
- total, ok := TaskData_Total_GroupBySnId_Map[DeviceClassList_r.T_sn]
- if !ok {
- // 测点自检事再处理
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 终端自检数据 , 测点缺失,跳过"})
- continue
- }
- if Devicedata_list_MAX == total {
- continue
- }
- list, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "", 0, 9999)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 终端自检数据 ,数据差值:" + lib.To_string(Devicedata_list_MAX-cnt) + " "})
- // 开始结束时间不同,直接执行平均复制到
- if startTime != list[0].T_time || endTime != list[len(list)-1].T_time {
- if Devicedata_list_MAX-cnt != 1 {
- // -----------开始平均复制到
- device := Device.DeviceClassList{
- T_class: DeviceClassList_r.T_class,
- T_id: DeviceClassList_r.T_id,
- T_sn: DeviceClassList_r.T_sn,
- T_remark: DeviceClassList_r.T_remark,
- }
- c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
- // -----------开始平均复制到结束
- continue
- } else {
- if startTime != list[0].T_time {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 找到到自检时间点 " + startTime + " ~ " + list[0].T_time + " 开始自检"})
- Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
- T_sn: list[0].T_sn,
- T_id: list[0].T_id,
- T_t: list[0].T_t,
- T_rh: list[0].T_rh,
- T_time: startTime,
- })
- continue
- }
- if endTime != list[len(list)-1].T_time {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 找到自检时间点 " + list[len(list)-1].T_time + " ~ " + endTime + " 开始自检"})
- Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
- T_sn: list[len(list)-1].T_sn,
- T_id: list[len(list)-1].T_id,
- T_t: list[len(list)-1].T_t,
- T_rh: list[len(list)-1].T_rh,
- T_time: endTime,
- })
- continue
- }
- }
- }
- for i := 0; i < len(list)-1; i++ {
- current := list[i].T_time
- next := list[i+1].T_time
- ct, _ := time.Parse("2006-01-02 15:04:05", current)
- nt, _ := time.Parse("2006-01-02 15:04:05", next)
- interval := nt.Unix() - ct.Unix()
- //logs.Debug("时间间隔:", interval, "保存时间:", saveTime)
- //fmt.Println("当前:", current, "下一个:", next)
- // 缺一个时间点 补漏
- if int(interval) == 2*T_saveT {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: list[i].T_id + " 找到自检时间点 " + current + " ~ " + next + " 开始自检"})
- t := ct.Add(time.Second * time.Duration(T_saveT)).Format("2006-01-02 15:04") //时间临时变量
- ttt := (list[i].T_t + list[i+1].T_t) / 2
- trht := (list[i].T_rh + list[i+1].T_rh) / 2
- Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
- T_sn: list[i].T_sn,
- T_id: list[i].T_id,
- T_t: ttt,
- T_rh: trht,
- T_time: t,
- })
- continue
- }
- // 缺的数据大于一个时间点,执行平均复制到
- if int(interval) > T_saveT {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: list[i].T_id + " 找到自检时间点 " + current + " ~ " + next + " 开始自检"})
- // -----------开始平均复制到
- device := Device.DeviceClassList{
- T_class: DeviceClassList_r.T_class,
- T_id: DeviceClassList_r.T_id,
- T_sn: DeviceClassList_r.T_sn,
- T_remark: DeviceClassList_r.T_remark,
- }
- c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
- // -----------开始平均复制到结束
- // 平均复制结束,跳出循环
- break
- }
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- // Close the connection
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // 数据持续时间 x 分钟 没有变化
- func (c *TaskDataHandleController) SSE_Continuously_unchanged_data() {
- T_task_id := c.GetString("T_task_id") // v26nplogbwt1
- T_timeout, _ := c.GetInt("T_timeout", 30) // 持续时间
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 获取 备注 下面关联设备,数量
- DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
- // 选择 数据缺失的终端
- var DeleteDeviceClassList []Device.DeviceClassList
- for _, DeviceClassList_r := range DeviceClassList_list {
- TaskData_list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "", 0, 9999)
- if len(TaskData_list) == 0 {
- continue
- }
- maxCount, start, end := Task.FindUnchangedInterval(TaskData_list)
- if strings.Contains(DeviceClassList_r.T_remark, "保温箱外环境测点") ||
- strings.Contains(DeviceClassList_r.T_remark, "冷藏库作业口外部环境测点") ||
- strings.Contains(DeviceClassList_r.T_remark, "冷藏库外部环境测点") ||
- strings.Contains(DeviceClassList_r.T_remark, "冷藏柜外部环境测点") ||
- strings.Contains(DeviceClassList_r.T_remark, "冷藏车外部环境测点") ||
- strings.Contains(DeviceClassList_r.T_remark, "仓库室外测点") {
- continue
- }
- if maxCount > T_timeout {
- Task.DeleteTaskDataByTimeRange(T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "")
- DeleteDeviceClassList = append(DeleteDeviceClassList, DeviceClassList_r)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "找到" + DeviceClassList_r.T_id + " 探头, " +
- "最多连续 " + lib.To_string(maxCount) + " 分钟没有变化," +
- "开始时间:" + start + " 结束时间:" + end + "," +
- "开始自检"})
- }
- }
- for _, class_ := range DeleteDeviceClassList {
- // -----------开始平均复制到
- device := Device.DeviceClassList{
- T_class: class_.T_class,
- T_id: class_.T_id,
- T_sn: class_.T_sn,
- T_remark: class_.T_remark,
- }
- c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
- // -----------开始平均复制到结束
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- // Close the connection
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // 区间数据校正 (布点区域数据自检) 均匀性布点,产品存放区域测点 区间数据超标校正 超标数据偏移到区间内
- func (c *TaskDataHandleController) SSE_Interval_data_correction() {
- T_task_id := c.GetString("T_task_id") // v26nplogbwt1
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
- 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
- if 温度控制范围最小值 == 0 || 温度控制范围最高值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围 标签值不正确!"})
- return
- }
- // 均匀性布点 产品存放区域测点
- 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点|作业出入口总测点")
- if len(部点终端_list) <= 2 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "均匀性布点|产品存放区域测点|作业出入口总测点 太少了,至少两条以上!"})
- return
- }
- 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
- var 开始时间, 结束时间, 趋势时间 string
- 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
- if 开始时间 == "" || 结束时间 == "" {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
- return
- }
- type AVGClassList struct {
- T_sn string
- T_id string
- T_max float64
- T_min float64
- T_diff float64 // 最大最小值差异
- }
- fmt.Println("数据准备:", 开始时间, 结束时间, 温度控制范围最小值, 温度控制范围最高值)
- // -------------------- 温湿度绑定点vga_H --------------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 进行处理 数据!---"})
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("开始时间:%s 结束时间:%s", 开始时间, 结束时间)})
- // 先整体向上偏移
- for _, i2 := range 部点终端_list {
- T_min := Task.Read_TaskData_min(T_task_id, i2.T_sn, i2.T_id, "", "")
- if T_min < RoundToDecimal(温度控制范围最小值+0.1, 1) {
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, i2.T_sn, i2.T_id, "", "", RoundToDecimal(温度控制范围最小值+0.1-T_min, 1), 0)
- }
- }
- // ----------获取保留数据------------
- //var 开空开, 保空开 string
- //var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
- //var valueStrings1, valueStrings2 []string
- var BWXValueStrings []string
- if Task_r.T_device_type != "X" {
- //valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, false)
- } else {
- BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
- }
- // ----------获取保留数据结束------------
- var AVGClassList_r []AVGClassList
- for _, i2 := range 部点终端_list {
- T_max := Task.Read_TaskData_max(T_task_id, i2.T_sn, i2.T_id, 开始时间, 结束时间)
- T_min := Task.Read_TaskData_min(T_task_id, i2.T_sn, i2.T_id, "", "")
- AVGClassList_r = append(AVGClassList_r, AVGClassList{T_sn: i2.T_sn, T_id: i2.T_id, T_max: T_max, T_min: T_min, T_diff: T_max - T_min})
- }
- for _, AVGClassList_i := range AVGClassList_r {
- if AVGClassList_i.T_max < 温度控制范围最高值 && AVGClassList_i.T_min > 温度控制范围最小值 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "测点 " + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 符合要求!"})
- continue
- }
- var vgaca float64
- if RoundToDecimal(AVGClassList_i.T_max-AVGClassList_i.T_min, 1) > RoundToDecimal(温度控制范围最高值-温度控制范围最小值-0.4, 1) {
- // 压缩
- diff := RoundToDecimal(AVGClassList_i.T_max-AVGClassList_i.T_min, 1)
- // 获取压缩度
- compress := RoundToDecimal((温度控制范围最高值-温度控制范围最小值-0.6)/diff, 2)
- // 压缩
- Task.UpdateTaskDataTemperatureAndHumidityByGeometric(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", compress, 1)
- T_max_compress := RoundToDecimal(AVGClassList_i.T_max*compress, 1)
- T_min_compress := RoundToDecimal(AVGClassList_i.T_min*compress, 1)
- // 判断压缩后是否在 温度控制范围最小值-温度控制范围最高值范围内 不做处理
- if T_max_compress <= RoundToDecimal(温度控制范围最高值-0.1, 1) && T_min_compress >= RoundToDecimal(温度控制范围最小值+0.1, 1) {
- 参考值 := RoundToDecimal((温度控制范围最高值+温度控制范围最小值)/2, 1)
- 中间值 := RoundToDecimal((AVGClassList_i.T_max+AVGClassList_i.T_min)/2, 1)
- if 参考值 > 中间值 {
- // 向上偏移
- vgaca = 参考值 - 中间值
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 压缩:" + lib.To_string(compress) +
- " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
- " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ " +
- " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
- continue
- }
- if 参考值 < 中间值 {
- // 向下偏移
- vgaca = 中间值 - 参考值
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 压缩:" + lib.To_string(compress) +
- " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
- " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ " +
- " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
- continue
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 压缩:" + lib.To_string(compress) +
- " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
- " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ "})
- continue
- }
- // 压缩后仍高于 温度控制范围最高值,向下偏移
- if T_max_compress >= 温度控制范围最高值 {
- vgaca = RoundToDecimal((T_max_compress+T_min_compress)/2-(温度控制范围最高值+温度控制范围最小值)/2, 1)
- //vgaca = RoundToDecimal(T_max_compress-温度控制范围最高值+0.1, 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 压缩:" + lib.To_string(compress) +
- " 压缩后最大值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_max*compress, 1)) + "℃ " +
- " 压缩后最小值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_min*compress, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
- continue
- }
- // 压缩后仍低于 温度控制范围最小值,向上偏移
- if T_min_compress <= 温度控制范围最小值 {
- // 向上偏移
- vgaca = RoundToDecimal((温度控制范围最高值+温度控制范围最小值)/2-(T_max_compress+T_min_compress)/2, 1)
- //vgaca = RoundToDecimal(温度控制范围最小值-AVGClassList_i.T_min*compress+0.1, 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 压缩:" + lib.To_string(compress) +
- " 压缩后最大值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_max*compress, 1)) + "℃ " +
- " 压缩后最小值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_min*compress, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
- }
- continue
- } else {
- // 向下偏移
- if AVGClassList_i.T_max >= 温度控制范围最高值 {
- vgaca = RoundToDecimal(AVGClassList_i.T_max-温度控制范围最高值+0.2, 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
- // 偏移
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
- continue
- }
- if AVGClassList_i.T_min <= 温度控制范围最小值 {
- vgaca = RoundToDecimal(温度控制范围最小值-AVGClassList_i.T_min+0.2, 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
- " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
- " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
- " 数据偏差: " + lib.To_string(vgaca) + "℃ " +
- " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
- Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
- }
- }
- }
- // ----------恢复保留数据------------
- if Task_r.T_device_type != "X" {
- //c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
- } else {
- c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
- }
- // ----------恢复保留数据结束------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- // Close the connection
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // 绑定点与终端比对 (绑定点数据自检) 终端数据为参照物,绑定点数据在终端偏差±1℃,保温箱为±0.5℃
- func (c *TaskDataHandleController) SSE_Comparison_between_binding_points_and_terminals() {
- T_task_id := c.GetString("T_task_id")
- //T_deviation, _ := c.GetFloat("T_deviation", 1.0) // 默认偏差1.0
- T_deviation := 0.5
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 保温箱偏差强制为0.5
- if Task_r.T_device_type == "X" {
- T_deviation = 0.5
- }
- // 温度控制范围
- 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
- if 温度控制范围最高值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
- return
- }
- 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
- if 温度控制范围最小值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
- return
- }
- // 均匀性布点 (用于获取保留数据)
- 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点")
- 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
- var 开始时间, 结束时间, 趋势时间 string
- 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
- if 开始时间 == "" || 结束时间 == "" {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
- return
- }
- // ----------获取保留数据------------
- var 开空开, 保空开 string
- var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
- var BWXValueStrings []string
- var valueStrings1, valueStrings2 []string
- if Task_r.T_device_type != "X" {
- valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
- } else {
- BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
- }
- // ----------获取保留数据结束------------
- 监测终端_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, true)
- if len(监测终端_list) < 1 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 监测终端!"})
- return
- }
- 温湿度绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点")
- if len(温湿度绑定点_list) < 1 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温湿度绑定点!"})
- return
- }
- // ================= 计算监测终端的整体平均值作为唯一基准 =================
- var 监测终端总温度 float64
- var 监测终端有效数量 int
- for _, term := range 监测终端_list {
- avg := Task.Read_TaskData_AVG(T_task_id, term.T_sn, term.T_id, 开始时间, 结束时间)
- if avg > 0 {
- 监测终端总温度 += avg
- 监测终端有效数量++
- }
- }
- if 监测终端有效数量 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "监测终端无有效数据!"})
- return
- }
- 监测终端整体Avg := RoundToDecimal(监测终端总温度/float64(监测终端有效数量), 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("基准确定:监测终端整体平均值 = %.1f℃, 允许偏差 = ±%.1f℃", 监测终端整体Avg, T_deviation)})
- // ================= 逐个遍历温湿度绑定点,超标则随机偏移到 0.5 或 0.4 =================
- rand.Seed(time.Now().UnixNano()) // 确保每次随机不同
- for _, bindPoint := range 温湿度绑定点_list {
- 绑定点_id := bindPoint.T_id
- 绑定点_sn := bindPoint.T_sn
- 绑定点_remark := bindPoint.T_remark
- // 1. 计算该绑定点的平均值
- 绑定点Avg_raw := Task.Read_TaskData_AVG(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
- 绑定点Avg := RoundToDecimal(绑定点Avg_raw, 1)
- // 2. 计算与监测终端基准的偏差
- 偏差 := RoundToDecimal(math.Abs(绑定点Avg-监测终端整体Avg), 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点[%s] 当前平均值:%.1f, 与监测终端偏差:%.1f", 绑定点_remark, 绑定点Avg, 偏差)})
- // 3. 判断是否在合规范围内 (偏差 > 0.5 才处理)
- if 偏差 <= T_deviation {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点[%s] 偏差 %.1f <= %.1f,已在合规范围内,跳过不处理", 绑定点_remark, 偏差, T_deviation)})
- continue
- }
- // 4. 不在合规范围内,需要单独处理该测点
- T_max := Task.Read_TaskData_max(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
- T_min := Task.Read_TaskData_min(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
- var targetAvg float64
- var 偏移量 float64
- var 动作 string
- // 随机选择处理后的“目标偏差值”为 0.5 或 0.4
- 目标偏差 := 0.5
- if rand.Intn(2) == 1 {
- 目标偏差 = 0.4
- }
- if 绑定点Avg > 监测终端整体Avg {
- // 偏高,需要向下压。目标值 = 基准 + 目标偏差 (例如 3.9 + 0.5 = 4.4 或 3.9 + 0.4 = 4.3)
- targetAvg = RoundToDecimal(监测终端整体Avg+目标偏差, 1)
- 偏移量 = RoundToDecimal(绑定点Avg-targetAvg, 1)
- 动作 = "下移"
- // 注意:T_deviation 传 0.0,防止底层函数重复叠加偏差
- c.SetAverageShiftedDownward(T_task_id, 绑定点_id, 0.0, targetAvg, 绑定点Avg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
- } else {
- // 偏低,需要向上拉。目标值 = 基准 - 目标偏差 (例如 3.9 - 0.5 = 3.4 或 3.9 - 0.4 = 3.5)
- targetAvg = RoundToDecimal(监测终端整体Avg-目标偏差, 1)
- 偏移量 = RoundToDecimal(targetAvg-绑定点Avg, 1)
- 动作 = "上移"
- // 注意:T_deviation 传 0.0
- c.SetAverageShiftedUpward(T_task_id, 绑定点_id, 0.0, targetAvg, 绑定点Avg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf(" 测点[%s] 偏差:%.1f > %.1f → %s%.1f → 动态目标Avg:%.1f (目标偏差:%.1f)",
- 绑定点_remark, 偏差, T_deviation, 动作, 偏移量, targetAvg, 目标偏差)})
- }
- // ----------恢复保留数据------------
- if Task_r.T_device_type != "X" {
- c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
- } else {
- c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
- }
- // ----------恢复保留数据结束------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- /*
- 绑定点与冷热点比对 (冷热点数据自检)
- 策略:以“温湿度绑定点”数据为参照物基准,部点终端数据在基准偏差±T_deviation℃以内。
- 若超出,则固定偏移一固定值,使差值降至合规范围内(目标差值随机取 0.7, 0.8, 0.9, 1.0)。
- */
- func (c *TaskDataHandleController) SSE_Compare_binding_points_with_cold_and_hot_spots() {
- T_task_id := c.GetString("T_task_id")
- T_deviation, _ := c.GetFloat("T_deviation", 1.0)
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- if Task_r.T_device_type == "X" {
- T_deviation = 0.5
- }
- 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
- if 温度控制范围最高值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
- return
- }
- 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
- if 温度控制范围最小值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
- return
- }
- // ========== 【核心修改】:读取任务终端(温湿度绑定点)作为基准 ==========
- 任务终端_list := make([]Device.DeviceClassList, 0)
- // 兼容 "温湿度绑定点01" 和 "温湿度绑定点1"
- 绑定点01_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点01|温湿度绑定点1")
- if len(绑定点01_list) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 温湿度绑定点01 或 温湿度绑定点1 数据!"})
- return
- }
- 任务终端_list = append(任务终端_list, 绑定点01_list...)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("基准点1 (%s): %s (%s)", 绑定点01_list[0].T_remark, 绑定点01_list[0].T_id, 绑定点01_list[0].T_sn)})
- // 兼容 "温湿度绑定点02" 和 "温湿度绑定点2"
- 绑定点02_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点02|温湿度绑定点2")
- if len(绑定点02_list) > 0 {
- 任务终端_list = append(任务终端_list, 绑定点02_list...)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("基准点2 (%s): %s (%s)", 绑定点02_list[0].T_remark, 绑定点02_list[0].T_id, 绑定点02_list[0].T_sn)})
- } else {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: "未找到 温湿度绑定点02/2,仅使用 温湿度绑定点01/1 作为基准"})
- }
- // 均匀性布点 产品存放区域测点 (需要被修正的测点)
- 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点")
- if len(部点终端_list) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 均匀性布点/产品存放区域测点 数据!"})
- return
- }
- 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
- var 开始时间, 结束时间, 趋势时间 string
- 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
- if 开始时间 == "" || 结束时间 == "" {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
- return
- }
- // ----------获取保留数据------------
- var 开空开, 保空开 string
- var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
- var BWXValueStrings []string
- var valueStrings1, valueStrings2 []string
- if Task_r.T_device_type != "X" {
- valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
- } else {
- BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
- }
- // ----------获取保留数据结束------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 开始时间: " + 开始时间 + " ---"})
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 结束时间: " + 结束时间 + " ---"})
- // ========== 第一步:计算基准点平均值 ==========
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 第一步:计算温湿度绑定点基准 ------"})
- var 任务终端总Avg float64
- var 任务终端数量 int
- for _, dev := range 任务终端_list {
- avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间)
- if avg != 0 {
- 任务终端总Avg += avg
- 任务终端数量++
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("基准点 %s Avg: %.1f", dev.T_id, RoundToDecimal(avg, 1))})
- }
- }
- if 任务终端数量 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温湿度绑定点无数据!"})
- return
- }
- 基准点 := RoundToDecimal(任务终端总Avg/float64(任务终端数量), 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("最终基准点(绑定点Avg): %.1f, 允许偏差阈值: ±%.1f", 基准点, T_deviation)})
- // ========== 第二步:逐终端对比并修正 ==========
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 第二步:逐终端对比与修正 ------"})
- // 随机选择的目标差值(确保修正后严格在合规范围内,且不贴死边界)
- 可选差值 := []float64{0.7, 0.8, 0.9, 1.0}
- // 如果是保温箱 (T_deviation = 0.5),目标差值应调整为 0.3, 0.4, 0.5
- if Task_r.T_device_type == "X" {
- 可选差值 = []float64{0.3, 0.4, 0.5}
- }
- 修正数量 := 0
- 跳过数量 := 0
- for _, dev := range 部点终端_list {
- 终端_id := dev.T_id
- 终端_Avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, 终端_id, 开始时间, 结束时间)
- 终端_Avg = RoundToDecimal(终端_Avg, 1)
- 差值 := RoundToDecimal(math.Abs(终端_Avg-基准点), 1)
- if 差值 <= T_deviation {
- // ✓ 在范围内,不动
- 跳过数量++
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点 %s Avg:%.1f 基准:%.1f 差值:%.1f ≤ %.1f 合规,不处理",
- 终端_id, 终端_Avg, 基准点, 差值, T_deviation)})
- continue
- }
- // 超出范围,随机取目标差值
- 目标差值 := RoundToDecimal(可选差值[rand.Intn(len(可选差值))], 1)
- 偏移量 := RoundToDecimal(差值-目标差值, 1)
- if 终端_Avg > 基准点 {
- // 偏高,统一下移
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(
- T_task_id, 终端_id, "", "", -偏移量)
- newAvg := RoundToDecimal(终端_Avg-偏移量, 1)
- newDiff := RoundToDecimal(math.Abs(newAvg-基准点), 1)
- 修正数量++
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点 %s Avg:%.1f→%.1f 下移:%.1f 差值:%.1f→%.1f(目标%.1f) ✓",
- 终端_id, 终端_Avg, newAvg, 偏移量, 差值, newDiff, 目标差值)})
- } else {
- // 偏低,统一上移
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(
- T_task_id, 终端_id, "", "", 偏移量)
- newAvg := RoundToDecimal(终端_Avg+偏移量, 1)
- newDiff := RoundToDecimal(math.Abs(newAvg-基准点), 1)
- 修正数量++
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点 %s Avg:%.1f→%.1f 上移:%.1f 差值:%.1f→%.1f(目标%.1f) ✓",
- 终端_id, 终端_Avg, newAvg, 偏移量, 差值, newDiff, 目标差值)})
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("处理完成: %d 个终端在范围内未处理, %d 个终端已修正", 跳过数量, 修正数量)})
- // ========== 校验 ==========
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 校验修正结果 ------"})
- for _, dev := range 部点终端_list {
- 终端_Avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间)
- 终端_Avg = RoundToDecimal(终端_Avg, 1)
- 差值 := RoundToDecimal(math.Abs(终端_Avg-基准点), 1)
- status := "✓ 合规"
- if 差值 > T_deviation {
- status = "✗ 仍超出"
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("校验: 测点 %s Avg:%.1f 差值:%.1f %s", dev.T_id, 终端_Avg, 差值, status)})
- }
- // ----------恢复保留数据------------
- if Task_r.T_device_type != "X" {
- c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
- } else {
- c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
- }
- // ----------恢复保留数据结束------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // 获取相邻2个终端值平均复制到
- func (c *TaskDataHandleController) SetAdjacentDeviceAVGTaskData(T_task_id string, device Device.DeviceClassList, StartTime, EndTime string) {
- DeviceClassListT_remark_r := Device.Read_DeviceClassList_List_id_T_remark(device.T_class, device.T_remark)
- // 分组统计每个sn的数据数量
- snList := Device.JoinDeviceClassListSnToString(DeviceClassListT_remark_r)
- TaskData_Total_GroupBySnId := Task.Read_TaskData_Total_GroupBySnId(T_task_id, snList, StartTime, EndTime)
- // 如果同组设备不足,扩大到整个终端类型
- if len(TaskData_Total_GroupBySnId) < 2 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "[" + device.T_remark + "]同组数据不足,扩大到全部终端搜索..."})
- DeviceClassListT_remark_r = Device.Read_DeviceClassList_List_id_By_Terminal(device.T_class, false)
- snList = Device.JoinDeviceClassListSnToString(DeviceClassListT_remark_r)
- TaskData_Total_GroupBySnId = Task.Read_TaskData_Total_GroupBySnId(T_task_id, snList, StartTime, EndTime)
- }
- if len(TaskData_Total_GroupBySnId) < 2 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "[" + device.T_remark + "]中没有找到 至少2条 可用数据"})
- return
- }
- // 获取 备注 下面关联设备,数量
- DeviceClassListT_remark_r_list_MAX := TaskData_Total_GroupBySnId[0].Total
- TaskData_Total_GroupBySnId_Map := make(map[string]int64)
- for _, v := range TaskData_Total_GroupBySnId {
- TaskData_Total_GroupBySnId_Map[v.T_sn] = v.Total
- }
- var completeDataDeviceClassList []Device.DeviceClassList
- for _, v := range DeviceClassListT_remark_r {
- // 排除目标设备本身,只使用其他有完整数据的设备作为平均源
- if TaskData_Total_GroupBySnId_Map[v.T_sn] == DeviceClassListT_remark_r_list_MAX && v.T_sn != device.T_sn {
- completeDataDeviceClassList = append(completeDataDeviceClassList, v)
- }
- }
- if len(completeDataDeviceClassList) < 2 {
- // 如果只有1个设备有数据,直接用该设备的数据复制(不平均)
- if len(completeDataDeviceClassList) == 1 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "[" + device.T_remark + "]仅1个完整数据源,直接复制数据"})
- sn1, id_str1 := completeDataDeviceClassList[0].T_sn, completeDataDeviceClassList[0].T_id
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: device.T_sn + "," + device.T_id + "开始复制" +
- fmt.Sprintf("%s,%s", sn1, id_str1)})
- List1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn1, id_str1, StartTime, EndTime, 0, 9999)
- // 如果源设备数据被更新为T_id作为T_sn,用T_id回退查询
- if len(List1) == 0 {
- List1, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str1, id_str1, StartTime, EndTime, 0, 9999)
- }
- if len(List1) == 0 {
- return
- }
- T_saveT := 60
- ct, _ := lib.TimeStrToTime(List1[0].T_time)
- var valueStrings []string
- for i := 0; i < len(List1); i++ {
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", device.T_sn, device.T_id, List1[i].T_t, List1[i].T_rh, ct.Format("2006-01-02 15:04:05")))
- ct = ct.Add(time.Second * time.Duration(T_saveT))
- }
- Task.DeleteTaskDataByTimeRange(T_task_id, device.T_sn, device.T_id, StartTime, EndTime)
- err := Task.Batch_Adds_TaskData(T_task_id, valueStrings)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%d/%d", len(valueStrings), len(valueStrings))})
- }
- return
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "[" + device.T_remark + "]中没有找到 至少2条 完整可用数据"})
- return
- }
- //twoDevice := getBeforeAndAfter(device.T_id, completeDataDeviceClassList)
- //sn1, id_str1 := twoDevice[0].T_sn, twoDevice[0].T_id
- //sn2, id_str2 := twoDevice[1].T_sn, twoDevice[1].T_id
- // 根据目标T_id选最近的2个设备做平均,避免所有设备数据相同
- sn1, id_str1, sn2, id_str2 := findClosestTwo(device.T_id, completeDataDeviceClassList)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: device.T_sn + "," + device.T_id + "开始平均复制到" +
- fmt.Sprintf("%s,%s|%s,%s", sn1, id_str1, sn2, id_str2)})
- List1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn1, id_str1, StartTime, EndTime, 0, 9999)
- List2, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn2, id_str2, StartTime, EndTime, 0, 9999)
- // 如果源设备数据被更新为T_id作为T_sn,用T_id回退查询
- if len(List1) == 0 {
- List1, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str1, id_str1, StartTime, EndTime, 0, 9999)
- }
- if len(List2) == 0 {
- List2, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str2, id_str2, StartTime, EndTime, 0, 9999)
- }
- num := len(List1)
- if len(List2) < len(List1) {
- num = len(List2)
- }
- if num == 0 {
- return
- }
- T_saveT := 60
- //var list []Task.TaskData_
- ct, _ := lib.TimeStrToTime(List1[0].T_time)
- var valueStrings []string
- for i := 0; i < num; i++ {
- if List1[i].T_time != List2[i].T_time {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: fmt.Sprintf("%s【%s】、%s【%s】时间不一致", List1[i].T_id, List1[i].T_time, List2[i].T_id, List2[i].T_time)})
- return
- }
- T_t := (List1[i].T_t + List2[i].T_t) / 2
- T_rh := (List1[i].T_rh + List2[i].T_rh) / 2
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", device.T_sn, device.T_id, T_t, T_rh, ct.Format("2006-01-02 15:04:05")))
- ct = ct.Add(time.Second * time.Duration(T_saveT))
- }
- Task.DeleteTaskDataByTimeRange(T_task_id, device.T_sn, device.T_id, StartTime, EndTime)
- err := Task.Batch_Adds_TaskData(T_task_id, valueStrings)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%d/%d", len(valueStrings), len(valueStrings))})
- }
- }
- func getBeforeAndAfter(T_id string, data []Device.DeviceClassList) []Device.DeviceClassList {
- var result []Device.DeviceClassList
- var index int
- for i, d := range data {
- if d.T_id == T_id {
- index = i
- break
- }
- }
- if index == 0 {
- result = append(result, data[len(data)-1], data[1])
- } else if index == len(data)-1 {
- result = append(result, data[len(data)-2], data[0])
- } else {
- result = append(result, data[index-1], data[index+1])
- }
- return result
- }
- // findClosestTwo 根据目标T_id找最近的2个设备,用于平均源选择
- func findClosestTwo(targetId string, data []Device.DeviceClassList) (string, string, string, string) {
- target, _ := strconv.Atoi(targetId)
- // 按T_id排序
- sorted := make([]Device.DeviceClassList, len(data))
- copy(sorted, data)
- for i := 0; i < len(sorted); i++ {
- for j := i + 1; j < len(sorted); j++ {
- idI, _ := strconv.Atoi(sorted[i].T_id)
- idJ, _ := strconv.Atoi(sorted[j].T_id)
- if idI > idJ {
- sorted[i], sorted[j] = sorted[j], sorted[i]
- }
- }
- }
- // 找最近的两个设备
- var closest1, closest2 Device.DeviceClassList
- minDiff1, minDiff2 := 99999, 99999
- for _, d := range sorted {
- id, err := strconv.Atoi(d.T_id)
- if err != nil {
- id = 0
- }
- diff := id - target
- if diff < 0 {
- diff = -diff
- }
- if diff < minDiff1 {
- minDiff2 = minDiff1
- closest2 = closest1
- minDiff1 = diff
- closest1 = d
- } else if diff < minDiff2 {
- minDiff2 = diff
- closest2 = d
- }
- }
- return closest1.T_sn, closest1.T_id, closest2.T_sn, closest2.T_id
- }
- func RoundToDecimal(num float64, decimal int) float64 {
- shift := math.Pow(10, float64(decimal))
- return math.Round(num*shift) / shift
- }
- /*
- 数据自检名称:平均值数据自检
- 测点:柜内所有测点|箱内所有测点|均匀性布点和产品存放区域测点、温湿度绑定点01、温湿度绑定点02
- 时间:采用“绑定点数据自检”的时间
- 策略:先执行完成“绑定点数据自检”后,温湿度绑定点01和温湿度绑定点02的数据就作为基准数据不变,温湿度绑定点01平均值与柜内所有测点平均值之间的差异在±0.5℃范围以内,温湿度绑定点02平均值与柜内所有测点平均值之间的差异在±0.5℃范围以内,若柜内所有测点的平均值较大或较小,则对柜内所有测点中平均值较大或较小的测点曲线进行偏移调整,使柜内所有测点的平均值靠近温湿度绑定点的平均值,从而达到绑定点与柜内所有测点的平均值差异在±0.5℃。以上“柜内所有测点”要分别换成“箱内所有测点”、“均匀性布点和产品存放区域测点”;“平均值数据自检”和“冷热点数据自检”可以同时勾选(冷库和冷车会用到)进行自检也可以单独执行(冷柜和保温箱只执行“平均值数据自检”)
- */
- func RoundToDecimalHalfUp(val float64, n int) float64 {
- pow := math.Pow(10, float64(n))
- return math.Round(val*pow) / pow
- }
- /*
- 数据自检名称:平均值数据自检
- 策略:逐个测点计算平均值,与绑定点平均值对比。
- 若偏差 > 0.5,则单独对该测点进行偏移(目标设为 基准±0.3,留出0.2安全余量避免贴边界)。
- 若偏差 ≤ 0.5,则合规,不处理。
- */
- func (c *TaskDataHandleController) SSE_Compare_binding_points_with_Average() {
- T_task_id := c.GetString("T_task_id")
- 偏移阈值 := 0.5
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 温度控制范围
- 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
- if 温度控制范围最高值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
- return
- }
- 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
- if 温度控制范围最小值 == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
- return
- }
- // 布点终端
- 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|柜内所有测点|箱内所有测点|产品存放区域测点")
- if len(部点终端_list) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 柜内所有测点|箱内所有测点|均匀性布点|产品存放区域测点 测点数据!"})
- return
- }
- 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
- // 动态兼容“满载”和“空载”的时间范围
- 开始时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)开始时间")
- 结束时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)结束时间")
- if 开始时间 == "" || 结束时间 == "" {
- 开始时间 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(空载)开始时间")
- 结束时间 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(空载)结束时间")
- }
- if 开始时间 == "" || 结束时间 == "" {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温度分布特性的测试与分析(满载/空载)的开始时间 或 结束时间!"})
- return
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 实际使用测试时间范围: " + 开始时间 + " 至 " + 结束时间 + " ---"})
- // ----------获取保留数据------------
- var valueStrings1, valueStrings2 []string
- var 开空开, 保空开 string
- var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
- var BWXValueStrings []string
- if Task_r.T_device_type != "X" {
- valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
- } else {
- BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 结束时间)
- }
- // ----------获取保留数据结束------------
- // 温湿度绑定点
- 温湿度绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点")
- if len(温湿度绑定点_list) < 1 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温湿度绑定点!"})
- return
- }
- var 温湿度绑定点1, _ Device.DeviceClassList
- for _, list := range 温湿度绑定点_list {
- if strings.Contains(list.T_remark, "温湿度绑定点1") {
- 温湿度绑定点1 = list
- }
- if strings.Contains(list.T_remark, "温湿度绑定点2") {
- _ = list
- }
- }
- // 1. 绑定点1的平均值作为基准
- 绑定点1_Avg_raw := Task.Read_TaskData_AVG(T_task_id, 温湿度绑定点1.T_sn, 温湿度绑定点1.T_id, 开始时间, 结束时间)
- 绑定点1_Avg := RoundToDecimalHalfUp(绑定点1_Avg_raw, 1)
- if 绑定点1_Avg == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温湿度绑定点1 无平均值数据!"})
- return
- }
- 压缩上限 := RoundToDecimalHalfUp(绑定点1_Avg+偏移阈值, 1) // 基准 + 0.5
- 压缩下限 := RoundToDecimalHalfUp(绑定点1_Avg-偏移阈值, 1) // 基准 - 0.5
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("定点1 = %.1f, 合规范围 [%.1f, %.1f]", 绑定点1_Avg, 压缩下限, 压缩上限)})
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "逐终端检查:"})
- // 初始化随机数种子
- rand.Seed(time.Now().UnixNano())
- // 定义安全边界:0.4 或 0.5 二选一
- 随机偏移量 := 0.4
- if rand.Intn(2) == 1 {
- 随机偏移量 = 0.5
- }
- // 提前解析时间,用于后续的防断崖校验
- endTimeObj, err := time.Parse("2006-01-02 15:04", 结束时间)
- if err != nil {
- endTimeObj, err = time.Parse("2006-01-02 15:04:05", 结束时间+":00")
- }
- compareTimeStr := endTimeObj.Format("2006-01-02 15:04")
- targetTimeObj := endTimeObj.Add(1 * time.Minute)
- targetTimeStr := targetTimeObj.Format("2006-01-02 15:04")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("------ 开始校验 %s 之后的数据防断崖处理 ------", 结束时间)})
- // 获取 ORM 实例用于执行原生 SQL 更新
- //o := orm.NewOrm()
- for _, dev := range 部点终端_list {
- 终端_id := dev.T_id
- 终端_sn := dev.T_sn
- // ================= 步骤 1:先执行平均值偏移 =================
- 终端_Avg_raw := Task.Read_TaskData_AVG(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
- 终端_Avg := RoundToDecimalHalfUp(终端_Avg_raw, 1)
- 偏差 := RoundToDecimalHalfUp(math.Abs(终端_Avg-绑定点1_Avg), 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点[%s] 当前平均值:%.1f, 偏差:%.1f", 终端_id, 终端_Avg, 偏差)})
- if 偏差 > 偏移阈值 {
- 终端_Min := Task.Read_TaskData_min(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
- 终端_Max := Task.Read_TaskData_max(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
- var targetAvg, 偏移量 float64
- var 动作 string
- if 终端_Avg > 绑定点1_Avg {
- // 降:目标值 = 基准 + 0.4 或 基准 + 0.5
- targetAvg = RoundToDecimalHalfUp(绑定点1_Avg+随机偏移量, 1)
- 偏移量 = RoundToDecimalHalfUp(终端_Avg-targetAvg, 1)
- 动作 = "下移"
- c.SetAverageShiftedDownward(T_task_id, 终端_id, 0.0, targetAvg, 终端_Avg, 终端_Min, 终端_Max,
- 温度控制范围最小值, 温度控制范围最高值, "", "")
- } else {
- // 升:目标值 = 基准 - 0.4 或 基准 - 0.5
- targetAvg = RoundToDecimalHalfUp(绑定点1_Avg-随机偏移量, 1)
- 偏移量 = RoundToDecimalHalfUp(targetAvg-终端_Avg, 1)
- 动作 = "上移"
- c.SetAverageShiftedUpward(T_task_id, 终端_id, 0.0, targetAvg, 终端_Avg, 终端_Min, 终端_Max,
- 温度控制范围最小值, 温度控制范围最高值, "", "")
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf(" 测点[%s] 偏差:%.1f > 0.5 → %s%.1f → 动态目标Avg:%.1f (随机安全值)",
- 终端_id, 偏差, 动作, 偏移量, targetAvg)})
- } else {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf("测点[%s] 偏差 %.1f <= 0.5,已在合规范围内,跳过不处理", 终端_id, 偏差)})
- }
- }
- // 重新获取处理后的整体平均值用于后续对比
- 处理后整体Avg := Task.Read_TaskData_Average(Task_r.T_task_id, 部点终端_sn_list, 开始时间, 结束时间)
- 处理后整体Avg = RoundToDecimalHalfUp(处理后整体Avg, 1)
- T_deviation := 1.0
- 温控传感器绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温控传感器绑定点1|温控传感器绑定点2")
- if len(温控传感器绑定点_list) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "没有找到温控传感器绑定点!"})
- }
- for i, classList := range 温控传感器绑定点_list {
- 温控传感器绑定点OriginalAvg := Task.Read_TaskData_Average(Task_r.T_task_id, classList.T_sn, 开始时间, 结束时间)
- 温控传感器绑定点OriginalAvg = RoundToDecimalHalfUp(温控传感器绑定点OriginalAvg, 1)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 进行 温控传感器绑定点" + strconv.Itoa(i+1) + " 平均值处理 ------"})
- if (温控传感器绑定点OriginalAvg >= 处理后整体Avg && 温控传感器绑定点OriginalAvg <= 处理后整体Avg+T_deviation) ||
- (温控传感器绑定点OriginalAvg <= 处理后整体Avg && 温控传感器绑定点OriginalAvg >= 处理后整体Avg-T_deviation) {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: " 温控传感器绑定点" + lib.To_string(i+1) + " 平均值:" + lib.To_string(温控传感器绑定点OriginalAvg) + "℃ " +
- " 测点平均值" + lib.To_string(处理后整体Avg) + "℃ " +
- " 数据偏差: " + lib.To_string(RoundToDecimalHalfUp(math.Abs(温控传感器绑定点OriginalAvg-处理后整体Avg), 1)) + "℃ " +
- " 设置:" + lib.To_string(T_deviation) + "℃" +
- " 符合要求!"})
- } else {
- if 温控传感器绑定点OriginalAvg < 处理后整体Avg-T_deviation {
- T_max := Task.Read_TaskData_max(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
- T_min := Task.Read_TaskData_min(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
- c.SetAverageShiftedUpward(T_task_id, classList.T_id, T_deviation, RoundToDecimalHalfUp(处理后整体Avg-T_deviation, 1), 温控传感器绑定点OriginalAvg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "温控传感器绑定点" + lib.To_string(i+1) + " 平均值向上偏移"})
- }
- if 温控传感器绑定点OriginalAvg > 处理后整体Avg+T_deviation {
- T_max := Task.Read_TaskData_max(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
- T_min := Task.Read_TaskData_min(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
- c.SetAverageShiftedDownward(T_task_id, classList.T_id, T_deviation, RoundToDecimalHalfUp(处理后整体Avg+T_deviation, 1), 温控传感器绑定点OriginalAvg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "温控传感器绑定点" + lib.To_string(i+1) + " 平均值向下偏移"})
- }
- }
- }
- // ----------恢复保留数据------------
- if Task_r.T_device_type != "X" {
- c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
- } else {
- c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 结束时间, 60)
- }
- // ----------恢复保留数据结束------------
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("------ 开始校验 %s 之后的数据防断崖处理 ------", 结束时间)})
- o := orm.NewOrm()
- for _, dev := range 部点终端_list {
- 终端_id := dev.T_id
- 终端_sn := dev.T_sn
- var temp46, temp47 float64
- // 查询该测点在 结束时间(如 16:46) 的最后一条温度值
- sql46 := fmt.Sprintf("SELECT t_t FROM z_task_data_%s WHERE t_sn = ? AND DATE_FORMAT(t_time, '%%Y-%%m-%%d %%H:%%i') = ? ORDER BY t_time DESC LIMIT 1", Task_r.T_task_id)
- err46 := o.Raw(sql46, 终端_sn, compareTimeStr).QueryRow(&temp46)
- if err46 == nil {
- // 查询该测点在 结束时间+1分钟(如 16:47) 的第一条温度值
- sql47 := fmt.Sprintf("SELECT t_t FROM z_task_data_%s WHERE t_sn = ? AND DATE_FORMAT(t_time, '%%Y-%%m-%%d %%H:%%i') = ? ORDER BY t_time ASC LIMIT 1", Task_r.T_task_id)
- err47 := o.Raw(sql47, 终端_sn, targetTimeStr).QueryRow(&temp47)
- // 对比并修正
- if err47 == nil && temp47 < temp46 {
- updateSql := fmt.Sprintf("UPDATE z_task_data_%s SET t_t = ? WHERE t_sn = ? AND DATE_FORMAT(t_time, '%%Y-%%m-%%d %%H:%%i') = ?", Task_r.T_task_id)
- _, updateErr := o.Raw(updateSql, temp46, 终端_sn, targetTimeStr).Exec()
- if updateErr == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
- Msg: fmt.Sprintf(" 测点[%s] 防断崖兜底: %s 温度 %.1f℃ < %s 温度 %.1f℃,已强制拉平至 %.1f℃",
- 终端_id, targetTimeStr, temp47, compareTimeStr, temp46, temp46)})
- }
- }
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- /*
- 平均值下移
- targetAvg 参考平均值
- originalAvg 原始平均值
- T_min 整段数据最小值
- T_max 整段数据最大值
- minLimit 温度控制范围最小值
- maxLimit 温度控制范围最大值
- */
- func (c *TaskDataHandleController) SetAverageShiftedDownward(T_task_id, T_id string, T_deviation, targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64, StartTime, EndTime string) {
- 目标avg := RoundToDecimal(targetAvg+T_deviation, 1)
- vgaca := RoundToDecimal(originalAvg-目标avg, 1)
- //向下偏移后整段数据最小值大于温度控制范围最小值
- if RoundToDecimal(T_min-vgaca, 1) > RoundToDecimal(minLimit, 1) && RoundToDecimal(T_max-vgaca, 1) < RoundToDecimal(maxLimit, 1) {
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", -vgaca)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
- " 测点 " + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
- } else {
- var err error
- var compress float64
- compress, vgaca, err = GetLinearTransformationValue(目标avg, originalAvg, T_min, T_max, minLimit, maxLimit)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "进行处理 数据!" +
- " 测点" + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 无合法解: 无法满足所有约束条件!"})
- return
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
- " 测点" + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 缩放:" + lib.To_string(compress) +
- " 缩放后最大值:" + lib.To_string(RoundToDecimal(T_max*compress, 1)) + "℃ " +
- " 缩放后最小值:" + lib.To_string(RoundToDecimal(T_min*compress, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 偏移:" + lib.To_string(vgaca) + "℃ "})
- // 压缩
- Task.UpdateTaskDataTemperatureAndHumidityByGeometric_id(T_task_id, T_id, "", "", compress)
- // 偏移
- if vgaca != 0 {
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
- }
- }
- }
- func GetLinearTransformationValue(targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64) (float64, float64, error) {
- //var vgaca float64
- //compress1 := RoundToDecimal((minLimit+0.3-targetAvg)/(T_min-originalAvg), 1)
- //compress2 := RoundToDecimal((maxLimit-0.3-targetAvg)/(T_max-originalAvg), 1)
- //compress := math.Min(compress1, compress2)
- //if RoundToDecimal(T_max*compress, 1) < RoundToDecimal(maxLimit, 1) && RoundToDecimal(T_min*compress, 1) > RoundToDecimal(minLimit, 1) {
- // vgaca = 0
- //} else {
- // vgaca = RoundToDecimal(targetAvg-compress*originalAvg, 1)
- //}
- //for _, x := range []float64{T_min, T_max} {
- // newVal := RoundToDecimal(compress*x+vgaca, 1)
- // if newVal < minLimit || newVal > maxLimit {
- // return 0, 0, errors.New("无合法解: 无法满足所有约束条件")
- // }
- //}
- //
- //return compress, vgaca, nil
- minLimit = RoundToDecimal(minLimit+0.2, 1)
- maxLimit = RoundToDecimal(maxLimit-0.2, 1)
- // 策略1: 使用最小边界值 (使变换后最小值 = minBound)
- k1 := (targetAvg - minLimit) / (originalAvg - T_min)
- b1 := minLimit - k1*T_min
- valid1 := true
- for _, x := range []float64{T_min, T_max} {
- newVal := RoundToDecimal(k1*x+b1, 1)
- if newVal < minLimit || newVal > maxLimit {
- valid1 = false
- break
- }
- }
- // 策略2: 使用最大边界值 (使变换后最大值 = maxBound),策略1失败时使用
- k2 := float64(0)
- b2 := float64(0)
- valid2 := false
- if !valid1 {
- k2 = (maxLimit - targetAvg) / (T_max - originalAvg)
- b2 = targetAvg - k2*originalAvg
- valid2 = true
- for _, x := range []float64{T_min, T_max} {
- newVal := RoundToDecimal(k2*x+b2, 1)
- if newVal < minLimit || newVal > maxLimit {
- valid2 = false
- break
- }
- }
- }
- // 根据结果选择有效策略
- switch {
- case valid1:
- return k1, b1, nil
- case valid2:
- return k2, b2, nil
- default:
- return 0, 0, errors.New("无合法解: 无法满足所有约束条件")
- }
- }
- /*
- 平均值上移
- targetAvg 参考平均值
- originalAvg 原始平均值
- T_min 整段数据最小值
- T_max 整段数据最大值
- minLimit 温度控制范围最小值
- maxLimit 温度控制范围最大值
- */
- func (c *TaskDataHandleController) SetAverageShiftedUpward(T_task_id, T_id string, T_deviation, targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64, StartTime, EndTime string) {
- 目标avg := RoundToDecimal(targetAvg-T_deviation, 1)
- vgaca := RoundToDecimal(目标avg-originalAvg, 1)
- if RoundToDecimal(T_max+vgaca, 1) < RoundToDecimal(maxLimit, 1) && RoundToDecimal(T_min+vgaca, 1) > RoundToDecimal(minLimit, 1) {
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
- " 测点 " + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
- } else {
- var err error
- var compress float64
- compress, vgaca, err = GetLinearTransformationValue(目标avg, originalAvg, T_min, T_max, minLimit, maxLimit)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "进行处理 数据!" +
- " 测点" + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 无合法解: 无法满足所有约束条件!"})
- return
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
- " 测点" + lib.To_string(T_id) +
- " 最大值:" + lib.To_string(T_max) + "℃ " +
- " 最小值:" + lib.To_string(T_min) + "℃ " +
- " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
- " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
- " 缩放:" + lib.To_string(compress) +
- " 缩放后最大值:" + lib.To_string(RoundToDecimal(T_max*compress, 1)) + "℃ " +
- " 缩放后最小值:" + lib.To_string(RoundToDecimal(T_min*compress, 1)) + "℃ " +
- " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
- " 偏移:" + lib.To_string(vgaca) + "℃ "})
- // 压缩
- Task.UpdateTaskDataTemperatureAndHumidityByGeometric_id(T_task_id, T_id, "", "", compress)
- // 偏移
- if vgaca != 0 {
- Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
- }
- }
- }
- func (c *TaskDataHandleController) GetCalculateHumps(T_task_id string, SN_list []Device.DeviceClassList, startTime, types string) (calculateHumps Task.CalculateHumps_R) {
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- var is bool
- calculateHumps, is = Task.Redis_CalculateHumps_Get(T_task_id + types)
- if !is {
- list := Task.Read_TaskData_ById_AVG(T_task_id, SN, startTime, "")
- // 获取第一个驼峰结束时间点
- CalculateHumps_list := Task.CalculateHumpsByThreeDots(list)
- if len(CalculateHumps_list) < 1 {
- return
- }
- calculateHumps = CalculateHumps_list[0]
- Task.Redis_CalculateHumps_Set(T_task_id+types, calculateHumps)
- }
- return calculateHumps
- }
- func (c *TaskDataHandleController) GetMetadata(T_task_id string, SN_list []Device.DeviceClassList, startTime, types string) (valueStrings []string, calculateHumps Task.CalculateHumps_R) {
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- var is bool
- calculateHumps, is = Task.Redis_CalculateHumps_Get(T_task_id + types)
- if !is {
- return
- }
- endTime := calculateHumps.End.T_time
- data1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, SN, "", startTime, endTime, 0, 9999)
- for _, v := range data1 {
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", v.T_sn, v.T_id, v.T_t, v.T_rh, v.T_time))
- }
- return valueStrings, calculateHumps
- }
- func (c *TaskDataHandleController) GetCalculateHumpsMaps(T_task_id string, SN_list []Device.DeviceClassList, startTime string) map[string]Task.CalculateHumps_R {
- var CalculateHumpsMaps = make(map[string]Task.CalculateHumps_R)
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- allList := Task.Read_TaskData_ById_AVG(T_task_id, SN, startTime, "")
- // 获取第一个驼峰结束时间点
- CalculateHumps_list := Task.CalculateHumpsByThreeDots(allList)
- if len(CalculateHumps_list) < 1 {
- return CalculateHumpsMaps
- }
- for _, device := range SN_list {
- list := Task.Read_TaskData_ById_AVG(T_task_id, device.T_sn, startTime, "")
- //获取第一个驼峰结束时间点
- CalculateHumps := Task.CalculateHumpsByThreeDots(list)
- if len(CalculateHumps) < 1 {
- CalculateHumpsMaps[device.T_sn] = CalculateHumps_list[0]
- continue
- }
- if CalculateHumps[0].Peak.T_time != CalculateHumps_list[0].Peak.T_time {
- CalculateHumps[0].Peak.T_time = CalculateHumps_list[0].Peak.T_time
- }
- et1, _ := lib.TimeStrToTime(CalculateHumps_list[0].End.T_time)
- et2, _ := lib.TimeStrToTime(CalculateHumps[0].End.T_time)
- if et1.Before(et2) {
- CalculateHumps[0].End.T_time = CalculateHumps_list[0].End.T_time
- }
- CalculateHumpsMaps[device.T_sn] = CalculateHumps[0]
- }
- return CalculateHumpsMaps
- }
- // 获取保留数据
- func (c *TaskDataHandleController) GetRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList, useCache bool) (valueStrings1, valueStrings2 []string,
- kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R) {
- var 开空开, 保空开 string
- var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
- if Task_r.T_device_type != "X" {
- 开空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开空开")
- if len(开空开) == 0 {
- 开空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开满开")
- }
- if len(开空开) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开空开/开满开 时间 标签!"})
- return
- }
- //保空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "保空开")
- //if len(保空开) == 0 {
- // 保空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "保满开")
- //}
- //if len(保空开) == 0 {
- // lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 保空开/保满开 时间 标签!"})
- // return
- //}
- //if !useCache {
- // 开空开驼峰 = c.GetCalculateHumps(Task_r.T_task_id, SN_list, 开空开, "kkk")
- // 保空开驼峰 = c.GetCalculateHumps(Task_r.T_task_id, SN_list, 保空开, "bkk")
- //}
- //
- //valueStrings1, 开空开驼峰 = c.GetMetadata(Task_r.T_task_id, SN_list, 开空开, "kkk")
- //valueStrings2, 保空开驼峰 = c.GetMetadata(Task_r.T_task_id, SN_list, 保空开, "bkk")
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- data1, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, SN, "", 开空开, "", 0, 9999)
- for _, v := range data1 {
- valueStrings1 = append(valueStrings1, fmt.Sprintf("('%s','%s',%v,%v,'%s')", v.T_sn, v.T_id, v.T_t, v.T_rh, v.T_time))
- }
- }
- return valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰
- }
- // 获取保留数据
- func (c *TaskDataHandleController) SaveRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1, valueStrings2 []string,
- kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
- var err error
- if len(valueStrings1) > 0 {
- //将开空开/开满开时间开始第一个驼峰 写入原始数据
- //将 开空开/开满开时间 后时间写入原始数据
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, kkkStartTime, kkkCalculateHumps.End.T_time)
- err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings1)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("恢复 开空开/开满开 时间点后驼峰 %s ~ %s 数据 %d/%d", kkkStartTime, kkkCalculateHumps.End.T_time, len(valueStrings1), len(valueStrings1))})
- }
- }
- if len(valueStrings2) > 0 {
- //将开空开/开满开时间开始第一个驼峰 写入原始数据
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, bkkStartTime, bkkCalculateHumps.End.T_time)
- err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings2)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("恢复 保空开/保满开 时间点后驼峰 %s ~ %s 数据 %d/%d", kkkStartTime, kkkCalculateHumps.End.T_time, len(valueStrings1), len(valueStrings1))})
- }
- }
- //c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings1, kkkStartTime, kkkCalculateHumps, maxLimit, saveTime)
- //c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings2, bkkStartTime, bkkCalculateHumps, maxLimit, saveTime)
- }
- // 获取保留数据
- func (c *TaskDataHandleController) SaveRetainDataTrend(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1, valueStrings2 []string,
- kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
- c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings1, kkkStartTime, kkkCalculateHumps, maxLimit, saveTime)
- c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings2, bkkStartTime, bkkCalculateHumps, maxLimit, saveTime)
- }
- func (c *TaskDataHandleController) SaveRetainDataAndTrend(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1 []string,
- kkkStartTime string, kkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
- var err error
- if len(valueStrings1) > 0 {
- //将开空开/开满开时间开始第一个驼峰 写入原始数据
- SN := Device.JoinDeviceClassListSnToString(SN_list)
- Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, kkkStartTime, kkkCalculateHumps.End.T_time)
- err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings1)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("恢复 开空开/开满开 保空开/保满开 时间点后驼峰 %s ~ %s 数据 %d/%d", kkkStartTime, kkkCalculateHumps.End.T_time, len(valueStrings1), len(valueStrings1))})
- }
- startTimeT, _ := lib.TimeStrToTime(kkkStartTime)
- startTime := startTimeT.Add(-time.Minute).Format("2006-01-02 15:04")
- endTimeT, _ := lib.TimeStrToTime(kkkCalculateHumps.End.T_time)
- endTime := endTimeT.Add(time.Minute).Format("2006-01-02 15:04")
- // 执行数据平滑
- for _, v := range SN_list {
- sn := v.T_sn
- id_str := v.T_id
- //AllList, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, kkkCalculateHumps.End.T_time, 0, 9999)
- var trendTime, declineTrendTime string
- trendTime = kkkCalculateHumps.Peak.T_time
- declineTrendTime = kkkCalculateHumps.Peak.T_time
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "执行数据上升趋势"})
- if len(trendTime) > 0 {
- list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, trendTime, 0, 9999)
- if len(list) <= 2 {
- continue
- }
- first := list[0]
- var last Task.TaskData_
- if len(list) > 10 {
- last = list[len(list)-2]
- } else {
- last = list[len(list)-1]
- }
- current, _ := time.Parse("2006-01-02 15:04", first.T_time)
- next, _ := time.Parse("2006-01-02 15:04", last.T_time)
- interval := next.Sub(current).Seconds() / float64(saveTime)
- //ttInterval := (last.T_t - first.T_t) / float32(interval)
- trhInterval := (last.T_rh - first.T_rh) / float32(interval)
- ttList := generateRisingCurve(float64(first.T_t), float64(last.T_t), int(interval+1))
- //tt := first.T_t
- ttrh := first.T_rh
- var valueStrings []string
- for i := 0; i <= int(interval); i++ {
- //tt += ttInterval
- ttrh += trhInterval
- ttime := current.Format("2006-01-02 15:04")
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, ttList[i], ttrh, ttime))
- current = current.Add(time.Second * time.Duration(saveTime))
- }
- //for current.Unix() <= next.Unix() {
- //
- // tt += ttInterval
- // ttrh += trhInterval
- // ttime := current.Format("2006-01-02 15:04")
- // valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, tt, ttrh, ttime))
- // current = current.Add(time.Second * time.Duration(saveTime))
- //}
- Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, first.T_time, last.T_time)
- Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "执行数据下降趋势"})
- if len(declineTrendTime) > 0 {
- list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, declineTrendTime, endTime, 0, 9999)
- if len(list) <= 2 {
- continue
- }
- if list[len(list)-1].T_t > list[len(list)-2].T_t {
- continue
- }
- var first Task.TaskData_
- if len(list) > 10 {
- first = list[1]
- } else {
- first = list[0]
- }
- last := list[len(list)-1]
- current, _ := time.Parse("2006-01-02 15:04", first.T_time)
- next, _ := time.Parse("2006-01-02 15:04", last.T_time)
- interval := next.Sub(current).Seconds() / float64(saveTime)
- //ttInterval := (last.T_t - first.T_t) / float32(interval)
- trhInterval := (last.T_rh - first.T_rh) / float32(interval)
- ttList := generateTemperatureCurve(float64(first.T_t), float64(last.T_t), int(interval+1))
- //tt := first.T_t
- ttrh := first.T_rh
- var valueStrings []string
- for i := 0; i <= int(interval); i++ {
- //tt += ttInterval
- ttrh += trhInterval
- ttime := current.Format("2006-01-02 15:04")
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, ttList[i], ttrh, ttime))
- current = current.Add(time.Second * time.Duration(saveTime))
- }
- Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, first.T_time, last.T_time)
- Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
- }
- }
- }
- }
- // 获取保留数据
- func (c *TaskDataHandleController) GetBWXRetainData(Task_r Task.Task, SN string, endTime, trendTime string) (BWXValueStrings []string) {
- if endTime != trendTime {
- data1, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, SN, "", trendTime, "", 0, 9999)
- for _, v := range data1 {
- BWXValueStrings = append(BWXValueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", v.T_sn, v.T_id, v.T_t, v.T_rh, v.T_time))
- }
- }
- return
- }
- func (c *TaskDataHandleController) SaveBWXRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList,
- BWXValueStrings []string, endTime, trendTime string, saveTime int) {
- var err error
- if endTime != trendTime && len(BWXValueStrings) > 0 {
- //将开空开/开满开时间开始第一个驼峰 写入原始数据
- for _, device := range SN_list {
- Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, device.T_sn, device.T_id, trendTime, "")
- }
- err = Task.Batch_Adds_TaskData(Task_r.T_task_id, BWXValueStrings)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("恢复 保温箱 原始数据 %d/%d", len(BWXValueStrings), len(BWXValueStrings))})
- }
- var startTime string
- startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "静态开箱作业开箱结束时间")
- if len(startTime) == 0 {
- startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "动态开箱作业开箱结束时间")
- }
- if len(startTime) == 0 {
- startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开箱作业开箱结束时间")
- }
- if len(startTime) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取 静态开箱作业开箱结束时间|动态开箱作业开箱结束时间|开箱作业开箱结束时间 失败"})
- return
- }
- startTimeT, _ := lib.TimeStrToTime(startTime)
- startTime = startTimeT.Add(30 * time.Minute).Format("2006-01-02 15:04")
- // 执行数据平滑
- for _, v := range SN_list {
- sn := v.T_sn
- id_str := v.T_id
- list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, trendTime, 0, 9999)
- first := list[0]
- last := list[len(list)-1]
- current, _ := time.Parse("2006-01-02 15:04", first.T_time)
- next, _ := time.Parse("2006-01-02 15:04", last.T_time)
- interval := next.Sub(current).Seconds() / float64(saveTime)
- ttInterval := (last.T_t - first.T_t) / float32(interval)
- trhInterval := (last.T_rh - first.T_rh) / float32(interval)
- tt := first.T_t
- ttrh := first.T_rh
- var valueStrings []string
- for current.Unix() <= next.Unix() {
- tt += ttInterval
- ttrh += trhInterval
- ttime := current.Format("2006-01-02 15:04")
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, tt, ttrh, ttime))
- current = current.Add(time.Second * time.Duration(saveTime))
- }
- Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, startTime, trendTime)
- err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
- if err == nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("保温箱数据趋势 %d/%d", len(valueStrings), len(valueStrings))})
- }
- }
- }
- }
- // 获取开始结束时间
- func (c *TaskDataHandleController) GetStartTimeAndEndTime(Task_r Task.Task, SN string, maxLimit float64) (startTime, endTime, trendTime string) {
- // 开始时间 获取温度下降到第二个低点时间
- // 1. 获取温度平均值
- list := Task.Read_TaskData_ById_AVG(Task_r.T_task_id, SN, "", "")
- if len(list) < 2 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "均匀性布点+产品存放区域测点+作业出入口总测点 数据平均值 少于2条!"})
- return
- }
- // 找平均值低于温度控制范围最高值的第二个最低点
- lowPoint := 0
- for i := 1; i <= len(list)-2; i++ {
- if list[i].T_t < list[i-1].T_t && list[i].T_t < list[i+1].T_t && list[i].T_t < float32(maxLimit) {
- lowPoint += 1
- }
- if lowPoint == 2 {
- startTime = list[i].T_time
- break
- }
- }
- // 保温箱没有开空开,开满开
- // 结束时间温度超限取没有超限时间点,结束时间温度未超限取最后一个时间点
- if Task_r.T_device_type == "X" {
- list2 := Task.Read_TaskData_ById_AVG_DESC(Task_r.T_task_id, SN, "", "")
- if len(list) == 0 {
- return
- }
- if float64(list2[0].T_t) < maxLimit {
- endTime = list2[0].T_time
- trendTime = list2[0].T_time
- return
- }
- for i, avg := range list2 {
- if RoundToDecimal(float64(avg.T_t), 1) < maxLimit {
- endTime = avg.T_time
- trendTime = list2[i-1].T_time
- break
- }
- }
- } else {
- endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开空开")
- if len(endTime) == 0 {
- endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开满开")
- }
- if len(endTime) == 0 {
- endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "现场测试结束时间")
- }
- if len(endTime) == 0 {
- return
- }
- }
- return
- }
- /*
- 计算绑定点的值
- A 监测终端01平均值
- B 监测终端02平均值
- C 温湿度绑定点01平均值
- D 温湿度绑定点02平均值
- */
- func findBindingPointsOptimalAdjustment(A, B, C, D, T_deviation float64) (float64, float64, bool) {
- type DataPair struct {
- A1 float64
- B1 float64
- }
- T_deviation_half := 0.1
- var dataPairs []DataPair
- var A1, B1 float64 = -100, -100
- // 生成A1和B1的可能值
- for a1 := A - T_deviation; a1 <= A+T_deviation; a1 += T_deviation_half {
- for b1 := B - T_deviation; b1 <= B+T_deviation; b1 += T_deviation_half {
- // 检查是否存在满足条件的C值
- minC := math.Max(a1-T_deviation_half, b1-T_deviation_half)
- maxC := math.Min(a1+T_deviation_half, b1+T_deviation_half)
- if minC <= maxC {
- dataPairs = append(dataPairs, DataPair{A1: a1, B1: b1})
- }
- }
- }
- if len(dataPairs) == 1 {
- A1 = dataPairs[0].A1
- B1 = dataPairs[0].B1
- }
- if len(dataPairs) > 1 {
- // 寻找最优解
- minAdjustment := math.MaxFloat64
- var optimalPair DataPair
- for _, pair := range dataPairs {
- adjustment := math.Abs(pair.A1-C) + math.Abs(pair.B1-D)
- if adjustment < minAdjustment {
- minAdjustment = adjustment
- optimalPair = pair
- }
- }
- A1 = optimalPair.A1
- B1 = optimalPair.B1
- }
- if A1 == -100 || B1 == -100 {
- return A1, B1, false
- }
- return A1, B1, true
- }
- /*
- 计算绑定点的值
- A 温湿度绑定点1平均值
- B 温湿度绑定点2平均值
- C 测点平均值
- */
- func findAverageOptimalAdjustment(A, B, C float64) float64 {
- // 计算有效区间
- minA := A - 0.5
- maxA := A + 0.5
- minB := B - 0.5
- maxB := B + 0.5
- // 求交集范围
- lowerBound := math.Max(minA, minB)
- upperBound := math.Min(maxA, maxB)
- // 确定最优调整值
- var optimalC float64
- if C < lowerBound {
- optimalC = lowerBound // 取区间下限
- } else if C > upperBound {
- optimalC = upperBound // 取区间上限
- } else {
- optimalC = C // 已在区间内无需调整
- }
- return optimalC
- }
- func generateTemperatureCurve(startTemp, minTemp float64, steps int) []float64 {
- if steps <= 0 {
- return []float64{}
- }
- // 计算动态参数确保严格递减且高于最低温度
- base := minTemp + 0.1 // 确保所有值高于minTemp
- decayFactor := math.Log((startTemp-base)/(minTemp+0.5-base)) / float64(steps-1)
- temperatures := make([]float64, steps)
- prevTemp := startTemp
- for i := 0; i < steps; i++ {
- // 使用指数衰减模型确保严格单调递减
- temp := base + (startTemp-base)*math.Exp(-decayFactor*float64(i))
- // 确保严格递减且高于最低温度
- if temp >= prevTemp {
- temp = prevTemp - 0.1
- }
- if temp <= minTemp {
- temp = minTemp + 0.01 + 0.05*float64(steps-i)/float64(steps)
- }
- // 保持精度并确保唯一性
- temp = math.Round(temp*100) / 100
- temperatures[i] = temp
- prevTemp = temp
- }
- return temperatures
- }
- func generateRisingCurve(minTemp, maxTemp float64, steps int) []float64 {
- if steps <= 0 {
- return []float64{}
- }
- // 计算动态参数确保严格递增且低于最高温度
- growthFactor := math.Log((maxTemp-minTemp-0.1)/0.1) / float64(steps-1)
- temperatures := make([]float64, steps)
- prevTemp := minTemp
- for i := 0; i < steps; i++ {
- // 使用指数增长模型确保严格单调递增
- temp := minTemp + (maxTemp-minTemp)*(1-math.Exp(-growthFactor*float64(i)))
- // 确保严格递增且低于最高温度
- if temp <= prevTemp {
- temp = prevTemp + 0.1
- }
- if temp >= maxTemp {
- temp = maxTemp - 0.01 - 0.05*float64(steps-i-1)/float64(steps)
- }
- // 保持精度并确保唯一性
- temp = math.Round(temp*100) / 100
- temperatures[i] = temp
- prevTemp = temp
- }
- return temperatures
- }
- // 鼓包/缺数据及超上下限处理
- // 处理顺序:1. 超高温修复 -> 2. 超低温修复 -> 3. 鼓包修复 -> 4. 兜底裁剪
- func (c *TaskDataHandleController) SSE_Process_hump_data() {
- T_task_id := c.GetString("T_task_id")
- c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
- c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
- c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "综合异常检测与修复任务开始......"})
- Task_r, err := Task.Read_Task(T_task_id)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
- return
- }
- // 从表单中获取时间范围
- 开始时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)开始时间")
- 结束时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)结束时间")
- if len(开始时间) == 0 || len(结束时间) == 0 || 开始时间 == "null" || 结束时间 == "null" {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取表单中'温度分布特性的测试与分析(满载)开始/结束时间'失败,请先填写表单时间字段!"})
- return
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "时间范围: " + 开始时间 + " ~ " + 结束时间})
- DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "终端总共:" + lib.To_string(len(DeviceClassList_list)) + " 个,开始检测..."})
- if len(DeviceClassList_list) == 0 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到终端设备!"})
- return
- }
- // ========== 步骤1:加载所有传感器数据 ==========
- allData := make(map[string]map[string]float32)
- for _, dev := range DeviceClassList_list {
- dataList, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间, 0, 9999)
- if cnt < 10 {
- continue
- }
- timeMap := make(map[string]float32)
- for _, d := range dataList {
- timeMap[d.T_time] = d.T_t
- }
- allData[dev.T_id] = timeMap
- }
- // ========== 步骤2:收集所有时间点并排序 ==========
- timeSet := make(map[string]bool)
- for _, tm := range allData {
- for t := range tm {
- timeSet[t] = true
- }
- }
- allTimes := make([]string, 0, len(timeSet))
- for t := range timeSet {
- allTimes = append(allTimes, t)
- }
- sort.Strings(allTimes)
- if len(allTimes) < 5 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "时间点不足,无法检测"})
- } else {
- // ========== 步骤2.5:从数据库表单读取温度控制范围 ==========
- // 1. 兼容读取最高值/最大值
- 最高值Str := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")
- if 最高值Str == "" {
- 最高值Str = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最大值")
- }
- 温度控制范围最高值 := lib.To_float32(最高值Str)
- // 2. 兼容读取最低值/最小值
- 最低值Str := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最低值")
- if 最低值Str == "" {
- 最低值Str = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")
- }
- 温度控制范围最低值 := lib.To_float32(最低值Str)
- // 3. 校验读取结果
- if 温度控制范围最高值 <= 0 || 温度控制范围最低值 >= 温度控制范围最高值 {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取表单中'温度控制范围最高值/最低值'失败或逻辑无效,请先填写!"})
- return
- }
- // 4. 留出 0.1 的安全余量 (防止浮点数精度导致刚好等于上限被误判)
- upperLimit := 温度控制范围最高值 - 0.1
- lowerLimit := 温度控制范围最低值 + 0.1
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("温度控制范围: %.2f°C ~ %.2f°C (已含0.1安全余量)", lowerLimit, upperLimit)})
- // ========== 检测参数 ==========
- const DEVIATION_THRESHOLD float32 = 0.8
- const MIN_DURATION = 5
- const MERGE_GAP = 5
- const MIN_RISE_FOR_BULGE float32 = 0.5
- const MAX_MONOTONIC_EXPAND = 120
- const TREND_WINDOW = 20
- const TREND_SLOPE_THRESHOLD float32 = 0.01
- const TREND_MIN_DURATION = 30
- const MONOTONIC_MIN_DURATION = 20
- const MONOTONIC_TOLERANCE = 2
- type BulgeSegment struct {
- Device string
- StartTime string
- EndTime string
- }
- var allBulges []BulgeSegment
- // ========== 步骤3:鼓包检测 (基于原始数据) ==========
- for _, dev := range DeviceClassList_list {
- tm := allData[dev.T_id]
- if tm == nil || len(tm) < 10 {
- continue
- }
- type DataPoint struct {
- Time string
- Temp float32
- Deviation float32
- IsBulge bool
- }
- var points []DataPoint
- var temps []float32
- for _, t := range allTimes {
- if v, ok := tm[t]; ok {
- refAll := medianOfAllDevices(allData, t)
- deviation := v - refAll
- points = append(points, DataPoint{t, v, deviation, deviation > DEVIATION_THRESHOLD})
- temps = append(temps, v)
- }
- }
- n := len(points)
- if n < MIN_DURATION {
- continue
- }
- // 1. 偏差鼓包检测
- i := 0
- for i < n {
- if points[i].IsBulge {
- i++
- continue
- }
- j := i
- for j+1 < n && !points[j+1].IsBulge {
- j++
- }
- leftTrue := i-1 >= 0 && points[i-1].IsBulge
- rightTrue := j+1 < n && points[j+1].IsBulge
- gapLen := j - i + 1
- if leftTrue && rightTrue && gapLen <= MERGE_GAP {
- for k := i; k <= j; k++ {
- points[k].IsBulge = true
- }
- }
- i = j + 1
- }
- type Seg struct{ Start, End int }
- var bulgeSegs []Seg
- start := -1
- for i, p := range points {
- if p.IsBulge && start == -1 {
- start = i
- } else if !p.IsBulge && start != -1 {
- bulgeSegs = append(bulgeSegs, Seg{start, i - 1})
- start = -1
- }
- }
- if start != -1 {
- bulgeSegs = append(bulgeSegs, Seg{start, n - 1})
- }
- // 2. 斜率趋势检测
- trendMask := detectTrends(temps, TREND_WINDOW, TREND_SLOPE_THRESHOLD, TREND_MIN_DURATION)
- var trendSegs []Seg
- start = -1
- for i, v := range trendMask {
- if v && start == -1 {
- start = i
- } else if !v && start != -1 {
- trendSegs = append(trendSegs, Seg{start, i - 1})
- start = -1
- }
- }
- if start != -1 {
- trendSegs = append(trendSegs, Seg{start, n - 1})
- }
- // 3. 单调趋势检测
- monoMask := detectMonotonicTrends(temps, MONOTONIC_MIN_DURATION, MONOTONIC_TOLERANCE)
- var monoSegs []Seg
- start = -1
- for i, v := range monoMask {
- if v && start == -1 {
- start = i
- } else if !v && start != -1 {
- monoSegs = append(monoSegs, Seg{start, i - 1})
- start = -1
- }
- }
- if start != -1 {
- monoSegs = append(monoSegs, Seg{start, n - 1})
- }
- // 合并所有段
- allSegs := append(bulgeSegs, trendSegs...)
- allSegs = append(allSegs, monoSegs...)
- if len(allSegs) == 0 {
- continue
- }
- sort.Slice(allSegs, func(i, j int) bool { return allSegs[i].Start < allSegs[j].Start })
- var merged []Seg
- for _, seg := range allSegs {
- if len(merged) == 0 {
- merged = append(merged, seg)
- } else {
- last := &merged[len(merged)-1]
- if seg.Start <= last.End+MERGE_GAP {
- if seg.End > last.End {
- last.End = seg.End
- }
- } else {
- merged = append(merged, seg)
- }
- }
- }
- for _, seg := range merged {
- if seg.End-seg.Start+1 < MIN_DURATION {
- continue
- }
- peakIdx := seg.Start
- peakVal := points[seg.Start].Temp
- for k := seg.Start + 1; k <= seg.End; k++ {
- if points[k].Temp > peakVal {
- peakVal = points[k].Temp
- peakIdx = k
- }
- }
- startIdx := seg.Start
- steps := 0
- for startIdx > 0 && steps < MAX_MONOTONIC_EXPAND && points[startIdx-1].Temp < points[startIdx].Temp {
- startIdx--
- steps++
- }
- endIdx := peakIdx
- steps = 0
- for endIdx < len(points)-1 && steps < MAX_MONOTONIC_EXPAND && points[endIdx+1].Temp <= points[endIdx].Temp {
- endIdx++
- steps++
- }
- if endIdx-startIdx+1 < MIN_DURATION {
- continue
- }
- maxDev := float32(0)
- for k := startIdx; k <= endIdx; k++ {
- if float32(math.Abs(float64(points[k].Deviation))) > maxDev {
- maxDev = float32(math.Abs(float64(points[k].Deviation)))
- }
- }
- if maxDev < 0.3 {
- continue
- }
- rise := points[peakIdx].Temp - points[startIdx].Temp
- minTemp := points[startIdx].Temp
- for k := startIdx; k <= endIdx; k++ {
- if points[k].Temp < minTemp {
- minTemp = points[k].Temp
- }
- }
- drop := points[startIdx].Temp - minTemp
- if rise >= MIN_RISE_FOR_BULGE || drop >= MIN_RISE_FOR_BULGE {
- allBulges = append(allBulges, BulgeSegment{
- Device: dev.T_id,
- StartTime: points[startIdx].Time,
- EndTime: points[endIdx].Time,
- })
- }
- }
- }
- // 输出鼓包检测结果
- if len(allBulges) > 0 {
- deviceBulges := make(map[string][]BulgeSegment)
- for _, b := range allBulges {
- deviceBulges[b.Device] = append(deviceBulges[b.Device], b)
- }
- for deviceId, bulges := range deviceBulges {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%s 检测到 %d 个鼓包段", deviceId, len(bulges))})
- }
- }
- // ========== 步骤4:按顺序修复 (先上下限,后鼓包) ==========
- const BLEND = 10
- const TARGET_SMOOTH = 7
- const EPS float32 = 0.03
- const STABLE_N = 4
- const MAX_EXPAND = 30
- for _, dev := range DeviceClassList_list {
- tm := allData[dev.T_id]
- if tm == nil || len(tm) < 10 {
- continue
- }
- var fixTimes []string
- var fixTemps []float32
- var fixRefExcl []float32
- for _, t := range allTimes {
- if v, ok := tm[t]; ok {
- refExcl := leaveOneOutMedian(allData, dev.T_id, t)
- fixTimes = append(fixTimes, t)
- fixTemps = append(fixTemps, v)
- fixRefExcl = append(fixRefExcl, refExcl)
- }
- }
- n := len(fixTimes)
- if n < 10 {
- continue
- }
- offsetWindow := 241
- if n/2 < offsetWindow {
- offsetWindow = n / 2
- }
- if offsetWindow < 11 {
- offsetWindow = 11
- }
- diff := make([]float32, n)
- for i := 0; i < n; i++ {
- diff[i] = fixTemps[i] - fixRefExcl[i]
- }
- offset := medianRolling(diff, offsetWindow)
- target := make([]float32, n)
- for i := 0; i < n; i++ {
- target[i] = fixRefExcl[i] + offset[i]
- }
- targetS := meanRolling(target, TARGET_SMOOTH)
- yFixed := make([]float32, n)
- copy(yFixed, fixTemps)
- // 【第1步】修复超高温段
- highSegs := findAndMergeLimitSegments(fixTemps, upperLimit, true, MERGE_GAP)
- for _, seg := range highSegs {
- s2, e2 := expandByLimitCondition(yFixed, seg.Start, seg.End, upperLimit, MAX_EXPAND, STABLE_N, true)
- yFixed = compressSegment(yFixed, targetS, s2, e2, n, upperLimit, BLEND)
- }
- // 【第2步】修复超低温段
- lowSegs := findAndMergeLimitSegments(fixTemps, lowerLimit, false, MERGE_GAP)
- for _, seg := range lowSegs {
- s2, e2 := expandByLimitCondition(yFixed, seg.Start, seg.End, lowerLimit, MAX_EXPAND, STABLE_N, false)
- // 低温修复技巧:取负值,复用 compressSegment 将其压到 -lowerLimit 之下,然后再取负还原
- yNeg := make([]float32, n)
- targetNeg := make([]float32, n)
- for i := 0; i < n; i++ {
- yNeg[i] = -yFixed[i]
- targetNeg[i] = -targetS[i]
- }
- yNegFixed := compressSegment(yNeg, targetNeg, s2, e2, n, -lowerLimit, BLEND)
- for i := 0; i < n; i++ {
- yFixed[i] = -yNegFixed[i]
- }
- }
- // 【第3步】修复鼓包段
- devBulges := false
- for _, bulge := range allBulges {
- if bulge.Device != dev.T_id {
- continue
- }
- devBulges = true
- s, e := -1, -1
- for i, ft := range fixTimes {
- if ft == bulge.StartTime {
- s = i
- }
- if ft == bulge.EndTime {
- e = i
- }
- }
- if s < 0 || e < 0 || e <= s {
- continue
- }
- residual := make([]float32, n)
- for i := 0; i < n; i++ {
- residual[i] = yFixed[i] - targetS[i]
- }
- s2, e2 := expandByResidual(residual, s, e, EPS, STABLE_N, MAX_EXPAND)
- yFixed = compressSegment(yFixed, targetS, s2, e2, n, upperLimit, BLEND)
- }
- // 【第4步】兜底裁剪:确保绝对不超限
- for i := 0; i < n; i++ {
- if yFixed[i] > upperLimit {
- yFixed[i] = upperLimit
- } else if yFixed[i] < lowerLimit {
- yFixed[i] = lowerLimit
- }
- }
- // 写入数据库
- if devBulges || len(highSegs) > 0 || len(lowSegs) > 0 {
- Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, dev.T_sn, dev.T_id, fixTimes[0], fixTimes[n-1])
- var valueStrings []string
- for i := 0; i < n; i++ {
- valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%.2f,0,'%s')", dev.T_sn, dev.T_id, yFixed[i], fixTimes[i]))
- }
- err = Task.Batch_Adds_TaskData(T_task_id, valueStrings)
- if err != nil {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: dev.T_id + " 数据修复失败: " + err.Error()})
- } else {
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf(" %s 修复完成 (共%d个数据点)", dev.T_id, n)})
- }
- }
- }
- }
- // ========== 步骤5:缺数据检测 (保持不变) ==========
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "开始缺数据检测..."})
- totalMissing := 0
- for _, dev := range DeviceClassList_list {
- dataList, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间, 0, 9999)
- if cnt < 2 {
- continue
- }
- missingCount := 0
- for i := 0; i < len(dataList)-1; i++ {
- ct, _ := time.Parse("2006-01-02 15:04", dataList[i].T_time)
- nt, _ := time.Parse("2006-01-02 15:04", dataList[i+1].T_time)
- interval := int(nt.Unix() - ct.Unix())
- if interval == 120 {
- t := ct.Add(60 * time.Second).Format("2006-01-02 15:04")
- ttt := (dataList[i].T_t + dataList[i+1].T_t) / 2
- trht := (dataList[i].T_rh + dataList[i+1].T_rh) / 2
- Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
- T_sn: dataList[i].T_sn,
- T_id: dataList[i].T_id,
- T_t: ttt,
- T_rh: trht,
- T_time: t,
- })
- missingCount++
- } else if interval > 120 {
- device := Device.DeviceClassList{
- T_class: dev.T_class,
- T_id: dev.T_id,
- T_sn: dev.T_sn,
- T_remark: dev.T_remark,
- }
- c.SetAdjacentDeviceAVGTaskData(T_task_id, device, 开始时间, 结束时间)
- missingCount++
- break
- }
- }
- if missingCount > 0 {
- totalMissing += missingCount
- }
- }
- lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: fmt.Sprintf("完成!缺数据补全: %d处", totalMissing)})
- c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
- }
- // ==================== 新增辅助函数:上下限修复专用 ====================
- // findAndMergeLimitSegments 查找并合并超出限值的连续段
- func findAndMergeLimitSegments(temps []float32, limit float32, isHigh bool, mergeGap int) []struct{ Start, End int } {
- n := len(temps)
- mask := make([]bool, n)
- for i := 0; i < n; i++ {
- if isHigh && temps[i] > limit {
- mask[i] = true
- } else if !isHigh && temps[i] < limit {
- mask[i] = true
- }
- }
- var segs []struct{ Start, End int }
- start := -1
- for i := 0; i < n; i++ {
- if mask[i] && start == -1 {
- start = i
- } else if !mask[i] && start != -1 {
- segs = append(segs, struct{ Start, End int }{start, i - 1})
- start = -1
- }
- }
- if start != -1 {
- segs = append(segs, struct{ Start, End int }{start, n - 1})
- }
- if len(segs) == 0 {
- return segs
- }
- // 合并间隔 <= mergeGap 的段
- var merged []struct{ Start, End int }
- merged = append(merged, segs[0])
- for i := 1; i < len(segs); i++ {
- last := &merged[len(merged)-1]
- if segs[i].Start <= last.End+mergeGap {
- if segs[i].End > last.End {
- last.End = segs[i].End
- }
- } else {
- merged = append(merged, segs[i])
- }
- }
- return merged
- }
- // expandByLimitCondition 基于限值条件向外扩展区间
- func expandByLimitCondition(yFixed []float32, s, e int, limit float32, maxExpand, stableN int, isHigh bool) (int, int) {
- n := len(yFixed)
- s2, e2 := s, e
- // 向左扩
- steps := 0
- for s2 > 0 && steps < maxExpand {
- l := s2 - stableN
- if l < 0 {
- l = 0
- }
- window := yFixed[l:s2]
- allOk := len(window) == stableN
- if allOk {
- for _, v := range window {
- if isHigh && v > limit+0.01 {
- allOk = false
- break
- } else if !isHigh && v < limit-0.01 {
- allOk = false
- break
- }
- }
- }
- if allOk {
- break
- }
- s2--
- steps++
- }
- // 向右扩
- steps = 0
- for e2 < n-1 && steps < maxExpand {
- r := e2 + 1 + stableN
- if r > n {
- r = n
- }
- window := yFixed[e2+1 : r]
- allOk := len(window) == stableN
- if allOk {
- for _, v := range window {
- if isHigh && v > limit+0.01 {
- allOk = false
- break
- } else if !isHigh && v < limit-0.01 {
- allOk = false
- break
- }
- }
- }
- if allOk {
- break
- }
- e2++
- steps++
- }
- return s2, e2
- }
- // ==================== 以下保留您原有的辅助函数 ====================
- // detectTrends 斜率趋势检测(线性回归斜率绝对值 > 阈值,持续时间 >= minDuration)
- func detectTrends(series []float32, window int, slopeThresh float32, minDuration int) []bool {
- n := len(series)
- mask := make([]bool, n)
- if n < window {
- return mask
- }
- slopes := make([]float32, n)
- for i := 0; i <= n-window; i++ {
- // 计算线性回归斜率
- x := make([]float64, window)
- y := make([]float64, window)
- for k := 0; k < window; k++ {
- x[k] = float64(k)
- y[k] = float64(series[i+k])
- }
- // 最小二乘法
- sumX, sumY, sumXY, sumX2 := 0.0, 0.0, 0.0, 0.0
- for k := 0; k < window; k++ {
- sumX += x[k]
- sumY += y[k]
- sumXY += x[k] * y[k]
- sumX2 += x[k] * x[k]
- }
- denom := float64(window)*sumX2 - sumX*sumX
- if denom == 0 {
- slopes[i+window/2] = 0
- } else {
- slope := float32((float64(window)*sumXY - sumX*sumY) / denom)
- slopes[i+window/2] = slope
- }
- }
- // 强斜率标记
- strong := make([]bool, n)
- for i := 0; i < n; i++ {
- if math.Abs(float64(slopes[i])) > float64(slopeThresh) {
- strong[i] = true
- }
- }
- // 找连续强斜率段(长度 >= minDuration)
- count := 0
- for i := 0; i < n; i++ {
- if strong[i] {
- count++
- } else {
- if count >= minDuration {
- for j := i - count; j < i; j++ {
- mask[j] = true
- }
- }
- count = 0
- }
- }
- if count >= minDuration {
- for j := n - count; j < n; j++ {
- mask[j] = true
- }
- }
- return mask
- }
- // detectMonotonicTrends 单调趋势检测(同向比例 >= 0.8,持续 >= minDuration)
- func detectMonotonicTrends(series []float32, minDuration int, tolerance int) []bool {
- n := len(series)
- mask := make([]bool, n)
- if n < minDuration {
- return mask
- }
- // 计算差分符号
- signs := make([]int, n-1)
- for i := 0; i < n-1; i++ {
- if series[i+1] > series[i] {
- signs[i] = 1
- } else if series[i+1] < series[i] {
- signs[i] = -1
- } else {
- signs[i] = 0
- }
- }
- window := minDuration
- half := window / 2
- for i := 0; i < n; i++ {
- l := i - half
- if l < 0 {
- l = 0
- }
- r := i + half + 1
- if r > n-1 {
- r = n - 1
- }
- if l > r {
- continue
- }
- seg := signs[l:r]
- pos, neg := 0, 0
- for _, s := range seg {
- if s > 0 {
- pos++
- } else if s < 0 {
- neg++
- }
- }
- total := pos + neg
- if total < window/2 {
- continue
- }
- ratio := float32(max(pos, neg)) / float32(total)
- if ratio >= 0.8 {
- mask[i] = true
- }
- }
- // 合并连续段,去除长度小于 minDuration 的
- // 简单做法:先找到段,再过滤
- var segs []struct{ s, e int }
- start := -1
- for i, v := range mask {
- if v && start == -1 {
- start = i
- } else if !v && start != -1 {
- segs = append(segs, struct{ s, e int }{start, i - 1})
- start = -1
- }
- }
- if start != -1 {
- segs = append(segs, struct{ s, e int }{start, n - 1})
- }
- finalMask := make([]bool, n)
- for _, seg := range segs {
- if seg.e-seg.s+1 >= minDuration {
- for i := seg.s; i <= seg.e; i++ {
- finalMask[i] = true
- }
- }
- }
- return finalMask
- }
- // 辅助函数:max
- func max(a, b int) int {
- if a > b {
- return a
- }
- return b
- }
- // medianOfSlice 计算float32切片的中位数
- func medianOfSlice(vals []float32) float32 {
- if len(vals) == 0 {
- return 0
- }
- sorted := make([]float32, len(vals))
- copy(sorted, vals)
- sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
- n := len(sorted)
- if n%2 == 0 {
- return (sorted[n/2-1] + sorted[n/2]) / 2
- }
- return sorted[n/2]
- }
- // medianOfAllDevices 计算所有设备在某个时间点的中位数
- func medianOfAllDevices(allData map[string]map[string]float32, t string) float32 {
- var vals []float32
- for _, tm := range allData {
- if v, ok := tm[t]; ok {
- vals = append(vals, v)
- }
- }
- return medianOfSlice(vals)
- }
- // leaveOneOutMedian 计算排除当前设备后其他设备的中位数
- // 与c.txt一致:在所有值中找到与当前值最接近的值,排除它,取剩余的中位数
- func leaveOneOutMedian(allData map[string]map[string]float32, deviceId string, t string) float32 {
- var vals []float32
- for _, tm := range allData {
- if v, ok := tm[t]; ok {
- vals = append(vals, v)
- }
- }
- if len(vals) <= 1 {
- return medianOfAllDevices(allData, t)
- }
- // 获取当前设备的值
- currentVal := float32(0)
- if tm, ok := allData[deviceId]; ok {
- if v, ok2 := tm[t]; ok2 {
- currentVal = v
- }
- }
- // 找到与当前值最接近的值的索引,排除它
- closestIdx := 0
- closestDiff := float32(math.MaxFloat32)
- for i, v := range vals {
- diff := float32(math.Abs(float64(v - currentVal)))
- if diff < closestDiff {
- closestDiff = diff
- closestIdx = i
- }
- }
- // 排除最接近的值
- vv := append(vals[:closestIdx], vals[closestIdx+1:]...)
- if len(vv) == 0 {
- return medianOfAllDevices(allData, t)
- }
- return medianOfSlice(vv)
- }
- // medianRolling 滚动中位数
- func medianRolling(arr []float32, window int) []float32 {
- n := len(arr)
- result := make([]float32, n)
- half := window / 2
- minPeriods := window / 5
- if minPeriods < 5 {
- minPeriods = 5
- }
- for i := 0; i < n; i++ {
- start := i - half
- if start < 0 {
- start = 0
- }
- end := i + half + 1
- if end > n {
- end = n
- }
- if end-start < minPeriods {
- if n >= minPeriods {
- // 边界用可用数据
- start = 0
- end = n
- } else {
- result[i] = arr[i]
- continue
- }
- }
- vals := arr[start:end]
- result[i] = medianOfSlice(vals)
- }
- return result
- }
- // meanRolling 滚动均值
- func meanRolling(arr []float32, window int) []float32 {
- n := len(arr)
- result := make([]float32, n)
- half := window / 2
- minPeriods := window / 3
- if minPeriods < 3 {
- minPeriods = 3
- }
- for i := 0; i < n; i++ {
- start := i - half
- if start < 0 {
- start = 0
- }
- end := i + half + 1
- if end > n {
- end = n
- }
- if end-start < minPeriods {
- start = 0
- end = n
- }
- sum := float32(0)
- count := 0
- for j := start; j < end; j++ {
- sum += arr[j]
- count++
- }
- if count > 0 {
- result[i] = sum / float32(count)
- }
- }
- return result
- }
- // smoothW 生成平滑混合权重
- // w[s:e] = 1.0, 前后blend点线性过渡到0
- func smoothW(n, s, e, blend int) []float32 {
- w := make([]float32, n)
- if e <= s {
- return w
- }
- // 核心区间 = 1.0
- for i := s; i <= e; i++ {
- w[i] = 1.0
- }
- // 左侧过渡
- ls := s - blend
- if ls < 0 {
- ls = 0
- }
- if ls < s {
- length := s - ls + 1 // length >= 2
- for i := 0; i < length; i++ {
- w[ls+i] = float32(i) / float32(length-1)
- }
- }
- // 右侧过渡
- re := e + blend
- if re >= n {
- re = n - 1
- }
- if e < re {
- length := re - e + 1 // length >= 2
- for i := 0; i < length; i++ {
- w[e+i] = float32(length-1-i) / float32(length-1)
- }
- }
- return w
- }
- // expandByResidual 以residual为依据扩展区间
- // 向两侧扩展直到 residual<=eps 连续stable_n个点
- func expandByResidual(residual []float32, s, e int, eps float32, stableN, maxExpand int) (int, int) {
- n := len(residual)
- s2, e2 := s, e
- // 向左扩
- steps := 0
- for s2 > 0 && steps < maxExpand {
- l := s2 - stableN
- if l < 0 {
- l = 0
- }
- window := residual[l:s2]
- allOk := len(window) == stableN
- if allOk {
- for _, v := range window {
- if v > eps {
- allOk = false
- break
- }
- }
- }
- if allOk {
- break
- }
- s2--
- steps++
- }
- // 向右扩
- steps = 0
- for e2 < n-1 && steps < maxExpand {
- r := e2 + 1 + stableN
- if r > n {
- r = n
- }
- window := residual[e2+1 : r]
- allOk := len(window) == stableN
- if allOk {
- for _, v := range window {
- if v > eps {
- allOk = false
- break
- }
- }
- }
- if allOk {
- break
- }
- e2++
- steps++
- }
- return s2, e2
- }
- // compressSegment 保形压缩(c.txt算法)
- // 把[s,e](含超限外扩)整体压到 upperLimit 之下
- // 1) 基准峰(target_s)若超限,先按形状等比压矮(保留尖峰,不削平)
- // 2) 鼓包偏差(dev)再按剩余余量等比压缩
- // 数学保证:区间内 inner <= upperLimit;blend 为凸组合,不超限
- func compressSegment(yFixed, targetS []float32, s, e, n int, upperLimit float32, blend int) []float32 {
- // 1. 超限外扩:肩膀上所有 >上限 的点都包进区间
- guard := 0
- for s > 0 && guard < 500 && yFixed[s-1] > upperLimit {
- s--
- guard++
- }
- guard = 0
- for e < n-1 && guard < 500 && yFixed[e+1] > upperLimit {
- e++
- guard++
- }
- w := smoothW(n, s, e, blend)
- // 2. 基准峰保形压缩
- baseMin := float32(math.MaxFloat32)
- for i := s; i <= e; i++ {
- if targetS[i] < baseMin {
- baseMin = targetS[i]
- }
- }
- // 【关键修复】baseDev 必须是全局的,模仿 Python 的 target_s - base_min
- baseDev := make([]float32, n)
- maxBaseDev := float32(0)
- for i := 0; i < n; i++ {
- baseDev[i] = targetS[i] - baseMin
- // 仅统计区间内的最大偏差用于计算缩放比例
- if i >= s && i <= e {
- if baseDev[i] > maxBaseDev {
- maxBaseDev = baseDev[i]
- }
- }
- }
- allowBase := upperLimit - baseMin
- if allowBase < 0 {
- allowBase = 0
- }
- scaleB := float32(1.0)
- if maxBaseDev > 0 {
- scaleB = allowBase / maxBaseDev
- if scaleB > 1 {
- scaleB = 1
- }
- }
- targetC := make([]float32, n)
- for i := 0; i < n; i++ {
- targetC[i] = baseMin + baseDev[i]*scaleB
- }
- // 3. 鼓包偏差保形压缩
- // 【关键修复】dev 必须是全局的,模仿 Python 的 y_fixed - target_s
- dev := make([]float32, n)
- maxDev := float32(0)
- for i := 0; i < n; i++ {
- dev[i] = yFixed[i] - targetS[i]
- // 仅统计区间内的最大偏差
- if i >= s && i <= e {
- if dev[i] > maxDev {
- maxDev = dev[i]
- }
- }
- }
- maxTargetC := float32(0)
- for i := s; i <= e; i++ {
- if targetC[i] > maxTargetC {
- maxTargetC = targetC[i]
- }
- }
- headroom := upperLimit - maxTargetC
- if headroom < 0 {
- headroom = 0
- }
- scale := float32(1.0)
- if maxDev > 0 {
- scale = headroom / maxDev
- if scale > 1 {
- scale = 1
- }
- }
- inner := make([]float32, n)
- for i := 0; i < n; i++ {
- inner[i] = targetC[i] + dev[i]*scale
- }
- // 4. 混合
- result := make([]float32, n)
- for i := 0; i < n; i++ {
- result[i] = (1-w[i])*yFixed[i] + w[i]*inner[i]
- }
- return result
- }
|