TaskHandle.go 118 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284
  1. package controllers
  2. import (
  3. "ColdVerify_local/lib"
  4. "ColdVerify_local/logs"
  5. "ColdVerify_local/models/Device"
  6. "ColdVerify_local/models/Task"
  7. "ColdVerify_local/models/VerifyTemplate"
  8. "errors"
  9. "fmt"
  10. "math"
  11. "math/rand"
  12. "net/http"
  13. "sort"
  14. "strconv"
  15. "strings"
  16. "time"
  17. "github.com/beego/beego/v2/client/orm"
  18. beego "github.com/beego/beego/v2/server/web"
  19. )
  20. type TaskDataHandleController struct {
  21. beego.Controller
  22. }
  23. /*
  24. 同区域数据缺失
  25. */
  26. // 测点自检 自动添加缺失终端,取关联绑定终端平均复制
  27. func (c *TaskDataHandleController) SSE_Automatically_add_missing_terminal() {
  28. T_task_id := c.GetString("T_task_id") // v26nplogbwt1
  29. T_start := c.GetString("T_start") // 开始测点编号
  30. T_end := c.GetString("T_end") // 结束测点编号
  31. println("T_task_id:", T_task_id, "T_start:", T_start, "T_end:", T_end)
  32. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  33. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  34. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  35. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  36. Task_r, err := Task.Read_Task(T_task_id)
  37. if err != nil {
  38. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  39. return
  40. }
  41. // 解析开始/结束编号
  42. var startId, endId int
  43. hasRange := false
  44. if T_start != "" {
  45. startId, _ = strconv.Atoi(T_start)
  46. hasRange = true
  47. }
  48. if T_end != "" {
  49. endId, _ = strconv.Atoi(T_end)
  50. hasRange = true
  51. }
  52. if !hasRange || startId <= 0 || endId <= 0 || startId > endId {
  53. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "请指定有效的开始和结束测点编号!"})
  54. return
  55. }
  56. // 获取所有设备,构建T_id -> Device映射
  57. DeviceClassList_r := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
  58. deviceMap := make(map[int]Device.DeviceClassList)
  59. for _, d := range DeviceClassList_r {
  60. tId, err := strconv.Atoi(d.T_id)
  61. if err == nil {
  62. deviceMap[tId] = d
  63. }
  64. }
  65. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("终端总共:%d 个,检查范围: %03d ~ %03d,正在检查自检探头...", len(DeviceClassList_r), startId, endId)})
  66. // 获取第一个设备用于获取T_remark参考
  67. var firstRemark string
  68. if len(DeviceClassList_r) > 0 {
  69. firstRemark = DeviceClassList_r[len(DeviceClassList_r)-1].T_remark
  70. }
  71. // 遍历完整范围,不限于设备列表
  72. for id := startId; id <= endId; id++ {
  73. tidStr := fmt.Sprintf("%03d", id)
  74. var class_ Device.DeviceClassList
  75. device, exists := deviceMap[id]
  76. if exists {
  77. class_ = device
  78. } else {
  79. // 虚拟设备:T_sn和T_id保持一致
  80. class_ = Device.DeviceClassList{
  81. T_class: Task_r.T_class,
  82. T_id: tidStr,
  83. T_sn: tidStr,
  84. T_remark: firstRemark,
  85. }
  86. }
  87. // 检查该测点是否已有数据
  88. // 真实设备用实际T_sn查询,虚拟设备用T_id查询
  89. querySn := tidStr
  90. if exists {
  91. querySn = class_.T_sn
  92. }
  93. _, cnt := Task.Read_TaskData_ById_List(Task_r.T_task_id, querySn, class_.T_id, "", "", 0, 1)
  94. if cnt == 0 {
  95. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "找到" + class_.T_id + " " + class_.T_remark + " 自检探头"})
  96. // 使用T_id作为T_sn,确保补充的数据sn=编号
  97. device := Device.DeviceClassList{
  98. T_class: class_.T_class,
  99. T_id: class_.T_id,
  100. T_sn: tidStr,
  101. T_remark: class_.T_remark,
  102. }
  103. c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
  104. // -----------开始平均复制到结束
  105. }
  106. }
  107. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  108. // Close the connection
  109. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  110. }
  111. // selectReferenceSensor 选择最稳定的传感器作为参考(标准差最小)
  112. func selectReferenceSensor(allData map[string]map[string]float32, devices []Device.DeviceClassList) string {
  113. var bestId string
  114. bestStddev := float32(math.MaxFloat32)
  115. for _, dev := range devices {
  116. tm := allData[dev.T_id]
  117. if tm == nil || len(tm) < 10 {
  118. continue
  119. }
  120. var sum, sumSq float32
  121. var count int
  122. for _, v := range tm {
  123. sum += v
  124. sumSq += v * v
  125. count++
  126. }
  127. if count < 10 {
  128. continue
  129. }
  130. mean := sum / float32(count)
  131. variance := sumSq/float32(count) - mean*mean
  132. stddev := float32(math.Sqrt(float64(variance)))
  133. if stddev < bestStddev {
  134. bestStddev = stddev
  135. bestId = dev.T_id
  136. }
  137. }
  138. return bestId
  139. }
  140. // calcBaselineDiff 计算传感器与参考传感器的基准差值(取前N分钟数据的均值)
  141. func calcBaselineDiff(sensorData, refData map[string]float32, allTimes []string, minutes int) float32 {
  142. if len(allTimes) == 0 {
  143. return 0
  144. }
  145. startTime := allTimes[0]
  146. st, _ := time.Parse("2006-01-02 15:04", startTime)
  147. endTime := st.Add(time.Duration(minutes) * time.Minute)
  148. var sum float32
  149. var count int
  150. for _, t := range allTimes {
  151. ct, _ := time.Parse("2006-01-02 15:04", t)
  152. if ct.After(endTime) {
  153. break
  154. }
  155. sp, ok1 := sensorData[t]
  156. rp, ok2 := refData[t]
  157. if ok1 && ok2 {
  158. sum += sp - rp
  159. count++
  160. }
  161. }
  162. if count == 0 {
  163. return 0
  164. }
  165. return sum / float32(count)
  166. }
  167. // getDeviceById 根据T_id查找设备
  168. func getDeviceById(devices []Device.DeviceClassList, id string) (Device.DeviceClassList, bool) {
  169. for _, d := range devices {
  170. if d.T_id == id {
  171. return d, true
  172. }
  173. }
  174. return Device.DeviceClassList{}, false
  175. }
  176. // findNearestTime 在时间列表中找目标时间前后最近的两个时间点
  177. func findNearestTime(allTimes []string, target string) (string, string) {
  178. var prev, next string
  179. for _, t := range allTimes {
  180. if t < target {
  181. prev = t
  182. } else if t > target && next == "" {
  183. next = t
  184. }
  185. }
  186. return prev, next
  187. }
  188. // 测点数据自检 自动添加缺失数据,取关联绑定终端平均复制
  189. func (c *TaskDataHandleController) SSE_Automatically_add_missing_data() {
  190. T_task_id := c.GetString("T_task_id") // v26nplogbwt1
  191. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  192. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  193. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  194. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  195. Task_r, err := Task.Read_Task(T_task_id)
  196. if err != nil {
  197. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  198. return
  199. }
  200. // 时间间隔 s
  201. T_saveT := 60
  202. DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
  203. // 分组统计每个sn的数据数量
  204. snList := Device.JoinDeviceClassListSnToString(DeviceClassList_list)
  205. TaskData_Total_GroupBySnId := Task.Read_TaskData_Total_GroupBySnId(Task_r.T_task_id, snList, "", "")
  206. if len(TaskData_Total_GroupBySnId) == 0 {
  207. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "暂无测点需要自检!"})
  208. }
  209. Devicedata_list_MAX := TaskData_Total_GroupBySnId[0].Total
  210. TaskData_Total_GroupBySnId_Map := make(map[string]int64)
  211. for _, v := range TaskData_Total_GroupBySnId {
  212. TaskData_Total_GroupBySnId_Map[v.T_sn] = v.Total
  213. }
  214. 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, "", "")
  215. logs.Println("数据标准数量:", Devicedata_list_MAX)
  216. // 选择 数据缺失的终端
  217. for _, DeviceClassList_r := range DeviceClassList_list {
  218. total, ok := TaskData_Total_GroupBySnId_Map[DeviceClassList_r.T_sn]
  219. if !ok {
  220. // 测点自检事再处理
  221. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 终端自检数据 , 测点缺失,跳过"})
  222. continue
  223. }
  224. if Devicedata_list_MAX == total {
  225. continue
  226. }
  227. list, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "", 0, 9999)
  228. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 终端自检数据 ,数据差值:" + lib.To_string(Devicedata_list_MAX-cnt) + " "})
  229. // 开始结束时间不同,直接执行平均复制到
  230. if startTime != list[0].T_time || endTime != list[len(list)-1].T_time {
  231. if Devicedata_list_MAX-cnt != 1 {
  232. // -----------开始平均复制到
  233. device := Device.DeviceClassList{
  234. T_class: DeviceClassList_r.T_class,
  235. T_id: DeviceClassList_r.T_id,
  236. T_sn: DeviceClassList_r.T_sn,
  237. T_remark: DeviceClassList_r.T_remark,
  238. }
  239. c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
  240. // -----------开始平均复制到结束
  241. continue
  242. } else {
  243. if startTime != list[0].T_time {
  244. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 找到到自检时间点 " + startTime + " ~ " + list[0].T_time + " 开始自检"})
  245. Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
  246. T_sn: list[0].T_sn,
  247. T_id: list[0].T_id,
  248. T_t: list[0].T_t,
  249. T_rh: list[0].T_rh,
  250. T_time: startTime,
  251. })
  252. continue
  253. }
  254. if endTime != list[len(list)-1].T_time {
  255. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: DeviceClassList_r.T_id + " 找到自检时间点 " + list[len(list)-1].T_time + " ~ " + endTime + " 开始自检"})
  256. Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
  257. T_sn: list[len(list)-1].T_sn,
  258. T_id: list[len(list)-1].T_id,
  259. T_t: list[len(list)-1].T_t,
  260. T_rh: list[len(list)-1].T_rh,
  261. T_time: endTime,
  262. })
  263. continue
  264. }
  265. }
  266. }
  267. for i := 0; i < len(list)-1; i++ {
  268. current := list[i].T_time
  269. next := list[i+1].T_time
  270. ct, _ := time.Parse("2006-01-02 15:04:05", current)
  271. nt, _ := time.Parse("2006-01-02 15:04:05", next)
  272. interval := nt.Unix() - ct.Unix()
  273. //logs.Debug("时间间隔:", interval, "保存时间:", saveTime)
  274. //fmt.Println("当前:", current, "下一个:", next)
  275. // 缺一个时间点 补漏
  276. if int(interval) == 2*T_saveT {
  277. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: list[i].T_id + " 找到自检时间点 " + current + " ~ " + next + " 开始自检"})
  278. t := ct.Add(time.Second * time.Duration(T_saveT)).Format("2006-01-02 15:04") //时间临时变量
  279. ttt := (list[i].T_t + list[i+1].T_t) / 2
  280. trht := (list[i].T_rh + list[i+1].T_rh) / 2
  281. Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
  282. T_sn: list[i].T_sn,
  283. T_id: list[i].T_id,
  284. T_t: ttt,
  285. T_rh: trht,
  286. T_time: t,
  287. })
  288. continue
  289. }
  290. // 缺的数据大于一个时间点,执行平均复制到
  291. if int(interval) > T_saveT {
  292. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: list[i].T_id + " 找到自检时间点 " + current + " ~ " + next + " 开始自检"})
  293. // -----------开始平均复制到
  294. device := Device.DeviceClassList{
  295. T_class: DeviceClassList_r.T_class,
  296. T_id: DeviceClassList_r.T_id,
  297. T_sn: DeviceClassList_r.T_sn,
  298. T_remark: DeviceClassList_r.T_remark,
  299. }
  300. c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
  301. // -----------开始平均复制到结束
  302. // 平均复制结束,跳出循环
  303. break
  304. }
  305. }
  306. }
  307. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  308. // Close the connection
  309. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  310. }
  311. // 数据持续时间 x 分钟 没有变化
  312. func (c *TaskDataHandleController) SSE_Continuously_unchanged_data() {
  313. T_task_id := c.GetString("T_task_id") // v26nplogbwt1
  314. T_timeout, _ := c.GetInt("T_timeout", 30) // 持续时间
  315. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  316. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  317. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  318. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  319. Task_r, err := Task.Read_Task(T_task_id)
  320. if err != nil {
  321. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  322. return
  323. }
  324. // 获取 备注 下面关联设备,数量
  325. DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
  326. // 选择 数据缺失的终端
  327. var DeleteDeviceClassList []Device.DeviceClassList
  328. for _, DeviceClassList_r := range DeviceClassList_list {
  329. TaskData_list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "", 0, 9999)
  330. if len(TaskData_list) == 0 {
  331. continue
  332. }
  333. maxCount, start, end := Task.FindUnchangedInterval(TaskData_list)
  334. if strings.Contains(DeviceClassList_r.T_remark, "保温箱外环境测点") ||
  335. strings.Contains(DeviceClassList_r.T_remark, "冷藏库作业口外部环境测点") ||
  336. strings.Contains(DeviceClassList_r.T_remark, "冷藏库外部环境测点") ||
  337. strings.Contains(DeviceClassList_r.T_remark, "冷藏柜外部环境测点") ||
  338. strings.Contains(DeviceClassList_r.T_remark, "冷藏车外部环境测点") ||
  339. strings.Contains(DeviceClassList_r.T_remark, "仓库室外测点") {
  340. continue
  341. }
  342. if maxCount > T_timeout {
  343. Task.DeleteTaskDataByTimeRange(T_task_id, DeviceClassList_r.T_sn, DeviceClassList_r.T_id, "", "")
  344. DeleteDeviceClassList = append(DeleteDeviceClassList, DeviceClassList_r)
  345. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "找到" + DeviceClassList_r.T_id + " 探头, " +
  346. "最多连续 " + lib.To_string(maxCount) + " 分钟没有变化," +
  347. "开始时间:" + start + " 结束时间:" + end + "," +
  348. "开始自检"})
  349. }
  350. }
  351. for _, class_ := range DeleteDeviceClassList {
  352. // -----------开始平均复制到
  353. device := Device.DeviceClassList{
  354. T_class: class_.T_class,
  355. T_id: class_.T_id,
  356. T_sn: class_.T_sn,
  357. T_remark: class_.T_remark,
  358. }
  359. c.SetAdjacentDeviceAVGTaskData(T_task_id, device, "", "")
  360. // -----------开始平均复制到结束
  361. }
  362. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  363. // Close the connection
  364. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  365. }
  366. // 区间数据校正 (布点区域数据自检) 均匀性布点,产品存放区域测点 区间数据超标校正 超标数据偏移到区间内
  367. func (c *TaskDataHandleController) SSE_Interval_data_correction() {
  368. T_task_id := c.GetString("T_task_id") // v26nplogbwt1
  369. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  370. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  371. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  372. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  373. Task_r, err := Task.Read_Task(T_task_id)
  374. if err != nil {
  375. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  376. return
  377. }
  378. 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
  379. 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
  380. if 温度控制范围最小值 == 0 || 温度控制范围最高值 == 0 {
  381. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围 标签值不正确!"})
  382. return
  383. }
  384. // 均匀性布点 产品存放区域测点
  385. 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点|作业出入口总测点")
  386. if len(部点终端_list) <= 2 {
  387. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "均匀性布点|产品存放区域测点|作业出入口总测点 太少了,至少两条以上!"})
  388. return
  389. }
  390. 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
  391. var 开始时间, 结束时间, 趋势时间 string
  392. 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
  393. if 开始时间 == "" || 结束时间 == "" {
  394. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
  395. return
  396. }
  397. type AVGClassList struct {
  398. T_sn string
  399. T_id string
  400. T_max float64
  401. T_min float64
  402. T_diff float64 // 最大最小值差异
  403. }
  404. fmt.Println("数据准备:", 开始时间, 结束时间, 温度控制范围最小值, 温度控制范围最高值)
  405. // -------------------- 温湿度绑定点vga_H --------------------
  406. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 进行处理 数据!---"})
  407. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("开始时间:%s 结束时间:%s", 开始时间, 结束时间)})
  408. // 先整体向上偏移
  409. for _, i2 := range 部点终端_list {
  410. T_min := Task.Read_TaskData_min(T_task_id, i2.T_sn, i2.T_id, "", "")
  411. if T_min < RoundToDecimal(温度控制范围最小值+0.1, 1) {
  412. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, i2.T_sn, i2.T_id, "", "", RoundToDecimal(温度控制范围最小值+0.1-T_min, 1), 0)
  413. }
  414. }
  415. // ----------获取保留数据------------
  416. //var 开空开, 保空开 string
  417. //var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
  418. //var valueStrings1, valueStrings2 []string
  419. var BWXValueStrings []string
  420. if Task_r.T_device_type != "X" {
  421. //valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, false)
  422. } else {
  423. BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
  424. }
  425. // ----------获取保留数据结束------------
  426. var AVGClassList_r []AVGClassList
  427. for _, i2 := range 部点终端_list {
  428. T_max := Task.Read_TaskData_max(T_task_id, i2.T_sn, i2.T_id, 开始时间, 结束时间)
  429. T_min := Task.Read_TaskData_min(T_task_id, i2.T_sn, i2.T_id, "", "")
  430. 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})
  431. }
  432. for _, AVGClassList_i := range AVGClassList_r {
  433. if AVGClassList_i.T_max < 温度控制范围最高值 && AVGClassList_i.T_min > 温度控制范围最小值 {
  434. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "测点 " + lib.To_string(AVGClassList_i.T_id) +
  435. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  436. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  437. " 符合要求!"})
  438. continue
  439. }
  440. var vgaca float64
  441. if RoundToDecimal(AVGClassList_i.T_max-AVGClassList_i.T_min, 1) > RoundToDecimal(温度控制范围最高值-温度控制范围最小值-0.4, 1) {
  442. // 压缩
  443. diff := RoundToDecimal(AVGClassList_i.T_max-AVGClassList_i.T_min, 1)
  444. // 获取压缩度
  445. compress := RoundToDecimal((温度控制范围最高值-温度控制范围最小值-0.6)/diff, 2)
  446. // 压缩
  447. Task.UpdateTaskDataTemperatureAndHumidityByGeometric(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", compress, 1)
  448. T_max_compress := RoundToDecimal(AVGClassList_i.T_max*compress, 1)
  449. T_min_compress := RoundToDecimal(AVGClassList_i.T_min*compress, 1)
  450. // 判断压缩后是否在 温度控制范围最小值-温度控制范围最高值范围内 不做处理
  451. if T_max_compress <= RoundToDecimal(温度控制范围最高值-0.1, 1) && T_min_compress >= RoundToDecimal(温度控制范围最小值+0.1, 1) {
  452. 参考值 := RoundToDecimal((温度控制范围最高值+温度控制范围最小值)/2, 1)
  453. 中间值 := RoundToDecimal((AVGClassList_i.T_max+AVGClassList_i.T_min)/2, 1)
  454. if 参考值 > 中间值 {
  455. // 向上偏移
  456. vgaca = 参考值 - 中间值
  457. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
  458. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  459. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  460. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  461. " 压缩:" + lib.To_string(compress) +
  462. " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
  463. " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ " +
  464. " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
  465. continue
  466. }
  467. if 参考值 < 中间值 {
  468. // 向下偏移
  469. vgaca = 中间值 - 参考值
  470. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
  471. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  472. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  473. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  474. " 压缩:" + lib.To_string(compress) +
  475. " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
  476. " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ " +
  477. " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
  478. continue
  479. }
  480. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  481. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  482. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  483. " 压缩:" + lib.To_string(compress) +
  484. " 压缩后最大值:" + lib.To_string(T_max_compress) + "℃ " +
  485. " 压缩后最小值:" + lib.To_string(T_min_compress) + "℃ "})
  486. continue
  487. }
  488. // 压缩后仍高于 温度控制范围最高值,向下偏移
  489. if T_max_compress >= 温度控制范围最高值 {
  490. vgaca = RoundToDecimal((T_max_compress+T_min_compress)/2-(温度控制范围最高值+温度控制范围最小值)/2, 1)
  491. //vgaca = RoundToDecimal(T_max_compress-温度控制范围最高值+0.1, 1)
  492. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  493. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  494. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  495. " 压缩:" + lib.To_string(compress) +
  496. " 压缩后最大值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_max*compress, 1)) + "℃ " +
  497. " 压缩后最小值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_min*compress, 1)) + "℃ " +
  498. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  499. " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
  500. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
  501. continue
  502. }
  503. // 压缩后仍低于 温度控制范围最小值,向上偏移
  504. if T_min_compress <= 温度控制范围最小值 {
  505. // 向上偏移
  506. vgaca = RoundToDecimal((温度控制范围最高值+温度控制范围最小值)/2-(T_max_compress+T_min_compress)/2, 1)
  507. //vgaca = RoundToDecimal(温度控制范围最小值-AVGClassList_i.T_min*compress+0.1, 1)
  508. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  509. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  510. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  511. " 压缩:" + lib.To_string(compress) +
  512. " 压缩后最大值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_max*compress, 1)) + "℃ " +
  513. " 压缩后最小值:" + lib.To_string(RoundToDecimal(AVGClassList_i.T_min*compress, 1)) + "℃ " +
  514. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  515. " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
  516. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
  517. }
  518. continue
  519. } else {
  520. // 向下偏移
  521. if AVGClassList_i.T_max >= 温度控制范围最高值 {
  522. vgaca = RoundToDecimal(AVGClassList_i.T_max-温度控制范围最高值+0.2, 1)
  523. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  524. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  525. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  526. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  527. " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
  528. // 偏移
  529. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", -vgaca, 0)
  530. continue
  531. }
  532. if AVGClassList_i.T_min <= 温度控制范围最小值 {
  533. vgaca = RoundToDecimal(温度控制范围最小值-AVGClassList_i.T_min+0.2, 1)
  534. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" + lib.To_string(AVGClassList_i.T_id) +
  535. " 最大值:" + lib.To_string(AVGClassList_i.T_max) + "℃ " +
  536. " 最小值:" + lib.To_string(AVGClassList_i.T_min) + "℃ " +
  537. " 数据偏差: " + lib.To_string(vgaca) + "℃ " +
  538. " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
  539. Task.UpdateTaskDataTemperatureAndHumidity(Task_r.T_task_id, AVGClassList_i.T_sn, AVGClassList_i.T_id, "", "", vgaca, 0)
  540. }
  541. }
  542. }
  543. // ----------恢复保留数据------------
  544. if Task_r.T_device_type != "X" {
  545. //c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
  546. } else {
  547. c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
  548. }
  549. // ----------恢复保留数据结束------------
  550. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  551. // Close the connection
  552. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  553. }
  554. // 绑定点与终端比对 (绑定点数据自检) 终端数据为参照物,绑定点数据在终端偏差±1℃,保温箱为±0.5℃
  555. func (c *TaskDataHandleController) SSE_Comparison_between_binding_points_and_terminals() {
  556. T_task_id := c.GetString("T_task_id")
  557. //T_deviation, _ := c.GetFloat("T_deviation", 1.0) // 默认偏差1.0
  558. T_deviation := 0.5
  559. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  560. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  561. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  562. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  563. Task_r, err := Task.Read_Task(T_task_id)
  564. if err != nil {
  565. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  566. return
  567. }
  568. // 保温箱偏差强制为0.5
  569. if Task_r.T_device_type == "X" {
  570. T_deviation = 0.5
  571. }
  572. // 温度控制范围
  573. 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
  574. if 温度控制范围最高值 == 0 {
  575. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
  576. return
  577. }
  578. 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
  579. if 温度控制范围最小值 == 0 {
  580. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
  581. return
  582. }
  583. // 均匀性布点 (用于获取保留数据)
  584. 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点")
  585. 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
  586. var 开始时间, 结束时间, 趋势时间 string
  587. 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
  588. if 开始时间 == "" || 结束时间 == "" {
  589. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
  590. return
  591. }
  592. // ----------获取保留数据------------
  593. var 开空开, 保空开 string
  594. var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
  595. var BWXValueStrings []string
  596. var valueStrings1, valueStrings2 []string
  597. if Task_r.T_device_type != "X" {
  598. valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
  599. } else {
  600. BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
  601. }
  602. // ----------获取保留数据结束------------
  603. 监测终端_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, true)
  604. if len(监测终端_list) < 1 {
  605. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 监测终端!"})
  606. return
  607. }
  608. 温湿度绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点")
  609. if len(温湿度绑定点_list) < 1 {
  610. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温湿度绑定点!"})
  611. return
  612. }
  613. // ================= 计算监测终端的整体平均值作为唯一基准 =================
  614. var 监测终端总温度 float64
  615. var 监测终端有效数量 int
  616. for _, term := range 监测终端_list {
  617. avg := Task.Read_TaskData_AVG(T_task_id, term.T_sn, term.T_id, 开始时间, 结束时间)
  618. if avg > 0 {
  619. 监测终端总温度 += avg
  620. 监测终端有效数量++
  621. }
  622. }
  623. if 监测终端有效数量 == 0 {
  624. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "监测终端无有效数据!"})
  625. return
  626. }
  627. 监测终端整体Avg := RoundToDecimal(监测终端总温度/float64(监测终端有效数量), 1)
  628. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  629. Msg: fmt.Sprintf("基准确定:监测终端整体平均值 = %.1f℃, 允许偏差 = ±%.1f℃", 监测终端整体Avg, T_deviation)})
  630. // ================= 逐个遍历温湿度绑定点,超标则随机偏移到 0.5 或 0.4 =================
  631. rand.Seed(time.Now().UnixNano()) // 确保每次随机不同
  632. for _, bindPoint := range 温湿度绑定点_list {
  633. 绑定点_id := bindPoint.T_id
  634. 绑定点_sn := bindPoint.T_sn
  635. 绑定点_remark := bindPoint.T_remark
  636. // 1. 计算该绑定点的平均值
  637. 绑定点Avg_raw := Task.Read_TaskData_AVG(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
  638. 绑定点Avg := RoundToDecimal(绑定点Avg_raw, 1)
  639. // 2. 计算与监测终端基准的偏差
  640. 偏差 := RoundToDecimal(math.Abs(绑定点Avg-监测终端整体Avg), 1)
  641. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  642. Msg: fmt.Sprintf("测点[%s] 当前平均值:%.1f, 与监测终端偏差:%.1f", 绑定点_remark, 绑定点Avg, 偏差)})
  643. // 3. 判断是否在合规范围内 (偏差 > 0.5 才处理)
  644. if 偏差 <= T_deviation {
  645. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  646. Msg: fmt.Sprintf("测点[%s] 偏差 %.1f <= %.1f,已在合规范围内,跳过不处理", 绑定点_remark, 偏差, T_deviation)})
  647. continue
  648. }
  649. // 4. 不在合规范围内,需要单独处理该测点
  650. T_max := Task.Read_TaskData_max(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
  651. T_min := Task.Read_TaskData_min(T_task_id, 绑定点_sn, 绑定点_id, 开始时间, 结束时间)
  652. var targetAvg float64
  653. var 偏移量 float64
  654. var 动作 string
  655. // 随机选择处理后的“目标偏差值”为 0.5 或 0.4
  656. 目标偏差 := 0.5
  657. if rand.Intn(2) == 1 {
  658. 目标偏差 = 0.4
  659. }
  660. if 绑定点Avg > 监测终端整体Avg {
  661. // 偏高,需要向下压。目标值 = 基准 + 目标偏差 (例如 3.9 + 0.5 = 4.4 或 3.9 + 0.4 = 4.3)
  662. targetAvg = RoundToDecimal(监测终端整体Avg+目标偏差, 1)
  663. 偏移量 = RoundToDecimal(绑定点Avg-targetAvg, 1)
  664. 动作 = "下移"
  665. // 注意:T_deviation 传 0.0,防止底层函数重复叠加偏差
  666. c.SetAverageShiftedDownward(T_task_id, 绑定点_id, 0.0, targetAvg, 绑定点Avg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
  667. } else {
  668. // 偏低,需要向上拉。目标值 = 基准 - 目标偏差 (例如 3.9 - 0.5 = 3.4 或 3.9 - 0.4 = 3.5)
  669. targetAvg = RoundToDecimal(监测终端整体Avg-目标偏差, 1)
  670. 偏移量 = RoundToDecimal(targetAvg-绑定点Avg, 1)
  671. 动作 = "上移"
  672. // 注意:T_deviation 传 0.0
  673. c.SetAverageShiftedUpward(T_task_id, 绑定点_id, 0.0, targetAvg, 绑定点Avg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
  674. }
  675. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  676. Msg: fmt.Sprintf(" 测点[%s] 偏差:%.1f > %.1f → %s%.1f → 动态目标Avg:%.1f (目标偏差:%.1f)",
  677. 绑定点_remark, 偏差, T_deviation, 动作, 偏移量, targetAvg, 目标偏差)})
  678. }
  679. // ----------恢复保留数据------------
  680. if Task_r.T_device_type != "X" {
  681. c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
  682. } else {
  683. c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
  684. }
  685. // ----------恢复保留数据结束------------
  686. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  687. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  688. }
  689. /*
  690. 绑定点与冷热点比对 (冷热点数据自检)
  691. 策略:以“温湿度绑定点”数据为参照物基准,部点终端数据在基准偏差±T_deviation℃以内。
  692. 若超出,则固定偏移一固定值,使差值降至合规范围内(目标差值随机取 0.7, 0.8, 0.9, 1.0)。
  693. */
  694. func (c *TaskDataHandleController) SSE_Compare_binding_points_with_cold_and_hot_spots() {
  695. T_task_id := c.GetString("T_task_id")
  696. T_deviation, _ := c.GetFloat("T_deviation", 1.0)
  697. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  698. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  699. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  700. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  701. Task_r, err := Task.Read_Task(T_task_id)
  702. if err != nil {
  703. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  704. return
  705. }
  706. if Task_r.T_device_type == "X" {
  707. T_deviation = 0.5
  708. }
  709. 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
  710. if 温度控制范围最高值 == 0 {
  711. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
  712. return
  713. }
  714. 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
  715. if 温度控制范围最小值 == 0 {
  716. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
  717. return
  718. }
  719. // ========== 【核心修改】:读取任务终端(温湿度绑定点)作为基准 ==========
  720. 任务终端_list := make([]Device.DeviceClassList, 0)
  721. // 兼容 "温湿度绑定点01" 和 "温湿度绑定点1"
  722. 绑定点01_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点01|温湿度绑定点1")
  723. if len(绑定点01_list) == 0 {
  724. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 温湿度绑定点01 或 温湿度绑定点1 数据!"})
  725. return
  726. }
  727. 任务终端_list = append(任务终端_list, 绑定点01_list...)
  728. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  729. Msg: fmt.Sprintf("基准点1 (%s): %s (%s)", 绑定点01_list[0].T_remark, 绑定点01_list[0].T_id, 绑定点01_list[0].T_sn)})
  730. // 兼容 "温湿度绑定点02" 和 "温湿度绑定点2"
  731. 绑定点02_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点02|温湿度绑定点2")
  732. if len(绑定点02_list) > 0 {
  733. 任务终端_list = append(任务终端_list, 绑定点02_list...)
  734. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  735. Msg: fmt.Sprintf("基准点2 (%s): %s (%s)", 绑定点02_list[0].T_remark, 绑定点02_list[0].T_id, 绑定点02_list[0].T_sn)})
  736. } else {
  737. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  738. Msg: "未找到 温湿度绑定点02/2,仅使用 温湿度绑定点01/1 作为基准"})
  739. }
  740. // 均匀性布点 产品存放区域测点 (需要被修正的测点)
  741. 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|产品存放区域测点")
  742. if len(部点终端_list) == 0 {
  743. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 均匀性布点/产品存放区域测点 数据!"})
  744. return
  745. }
  746. 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
  747. var 开始时间, 结束时间, 趋势时间 string
  748. 开始时间, 结束时间, 趋势时间 = c.GetStartTimeAndEndTime(Task_r, 部点终端_sn_list, 温度控制范围最高值)
  749. if 开始时间 == "" || 结束时间 == "" {
  750. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开始时间 或 结束时间!"})
  751. return
  752. }
  753. // ----------获取保留数据------------
  754. var 开空开, 保空开 string
  755. var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
  756. var BWXValueStrings []string
  757. var valueStrings1, valueStrings2 []string
  758. if Task_r.T_device_type != "X" {
  759. valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
  760. } else {
  761. BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 趋势时间)
  762. }
  763. // ----------获取保留数据结束------------
  764. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 开始时间: " + 开始时间 + " ---"})
  765. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 结束时间: " + 结束时间 + " ---"})
  766. // ========== 第一步:计算基准点平均值 ==========
  767. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 第一步:计算温湿度绑定点基准 ------"})
  768. var 任务终端总Avg float64
  769. var 任务终端数量 int
  770. for _, dev := range 任务终端_list {
  771. avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间)
  772. if avg != 0 {
  773. 任务终端总Avg += avg
  774. 任务终端数量++
  775. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  776. Msg: fmt.Sprintf("基准点 %s Avg: %.1f", dev.T_id, RoundToDecimal(avg, 1))})
  777. }
  778. }
  779. if 任务终端数量 == 0 {
  780. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温湿度绑定点无数据!"})
  781. return
  782. }
  783. 基准点 := RoundToDecimal(任务终端总Avg/float64(任务终端数量), 1)
  784. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  785. Msg: fmt.Sprintf("最终基准点(绑定点Avg): %.1f, 允许偏差阈值: ±%.1f", 基准点, T_deviation)})
  786. // ========== 第二步:逐终端对比并修正 ==========
  787. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 第二步:逐终端对比与修正 ------"})
  788. // 随机选择的目标差值(确保修正后严格在合规范围内,且不贴死边界)
  789. 可选差值 := []float64{0.7, 0.8, 0.9, 1.0}
  790. // 如果是保温箱 (T_deviation = 0.5),目标差值应调整为 0.3, 0.4, 0.5
  791. if Task_r.T_device_type == "X" {
  792. 可选差值 = []float64{0.3, 0.4, 0.5}
  793. }
  794. 修正数量 := 0
  795. 跳过数量 := 0
  796. for _, dev := range 部点终端_list {
  797. 终端_id := dev.T_id
  798. 终端_Avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, 终端_id, 开始时间, 结束时间)
  799. 终端_Avg = RoundToDecimal(终端_Avg, 1)
  800. 差值 := RoundToDecimal(math.Abs(终端_Avg-基准点), 1)
  801. if 差值 <= T_deviation {
  802. // ✓ 在范围内,不动
  803. 跳过数量++
  804. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  805. Msg: fmt.Sprintf("测点 %s Avg:%.1f 基准:%.1f 差值:%.1f ≤ %.1f 合规,不处理",
  806. 终端_id, 终端_Avg, 基准点, 差值, T_deviation)})
  807. continue
  808. }
  809. // 超出范围,随机取目标差值
  810. 目标差值 := RoundToDecimal(可选差值[rand.Intn(len(可选差值))], 1)
  811. 偏移量 := RoundToDecimal(差值-目标差值, 1)
  812. if 终端_Avg > 基准点 {
  813. // 偏高,统一下移
  814. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(
  815. T_task_id, 终端_id, "", "", -偏移量)
  816. newAvg := RoundToDecimal(终端_Avg-偏移量, 1)
  817. newDiff := RoundToDecimal(math.Abs(newAvg-基准点), 1)
  818. 修正数量++
  819. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  820. Msg: fmt.Sprintf("测点 %s Avg:%.1f→%.1f 下移:%.1f 差值:%.1f→%.1f(目标%.1f) ✓",
  821. 终端_id, 终端_Avg, newAvg, 偏移量, 差值, newDiff, 目标差值)})
  822. } else {
  823. // 偏低,统一上移
  824. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(
  825. T_task_id, 终端_id, "", "", 偏移量)
  826. newAvg := RoundToDecimal(终端_Avg+偏移量, 1)
  827. newDiff := RoundToDecimal(math.Abs(newAvg-基准点), 1)
  828. 修正数量++
  829. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  830. Msg: fmt.Sprintf("测点 %s Avg:%.1f→%.1f 上移:%.1f 差值:%.1f→%.1f(目标%.1f) ✓",
  831. 终端_id, 终端_Avg, newAvg, 偏移量, 差值, newDiff, 目标差值)})
  832. }
  833. }
  834. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  835. Msg: fmt.Sprintf("处理完成: %d 个终端在范围内未处理, %d 个终端已修正", 跳过数量, 修正数量)})
  836. // ========== 校验 ==========
  837. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 校验修正结果 ------"})
  838. for _, dev := range 部点终端_list {
  839. 终端_Avg := Task.Read_TaskData_AVG(T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间)
  840. 终端_Avg = RoundToDecimal(终端_Avg, 1)
  841. 差值 := RoundToDecimal(math.Abs(终端_Avg-基准点), 1)
  842. status := "✓ 合规"
  843. if 差值 > T_deviation {
  844. status = "✗ 仍超出"
  845. }
  846. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  847. Msg: fmt.Sprintf("校验: 测点 %s Avg:%.1f 差值:%.1f %s", dev.T_id, 终端_Avg, 差值, status)})
  848. }
  849. // ----------恢复保留数据------------
  850. if Task_r.T_device_type != "X" {
  851. c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
  852. } else {
  853. c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 趋势时间, 60)
  854. }
  855. // ----------恢复保留数据结束------------
  856. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  857. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  858. }
  859. // 获取相邻2个终端值平均复制到
  860. func (c *TaskDataHandleController) SetAdjacentDeviceAVGTaskData(T_task_id string, device Device.DeviceClassList, StartTime, EndTime string) {
  861. DeviceClassListT_remark_r := Device.Read_DeviceClassList_List_id_T_remark(device.T_class, device.T_remark)
  862. // 分组统计每个sn的数据数量
  863. snList := Device.JoinDeviceClassListSnToString(DeviceClassListT_remark_r)
  864. TaskData_Total_GroupBySnId := Task.Read_TaskData_Total_GroupBySnId(T_task_id, snList, StartTime, EndTime)
  865. // 如果同组设备不足,扩大到整个终端类型
  866. if len(TaskData_Total_GroupBySnId) < 2 {
  867. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "[" + device.T_remark + "]同组数据不足,扩大到全部终端搜索..."})
  868. DeviceClassListT_remark_r = Device.Read_DeviceClassList_List_id_By_Terminal(device.T_class, false)
  869. snList = Device.JoinDeviceClassListSnToString(DeviceClassListT_remark_r)
  870. TaskData_Total_GroupBySnId = Task.Read_TaskData_Total_GroupBySnId(T_task_id, snList, StartTime, EndTime)
  871. }
  872. if len(TaskData_Total_GroupBySnId) < 2 {
  873. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "[" + device.T_remark + "]中没有找到 至少2条 可用数据"})
  874. return
  875. }
  876. // 获取 备注 下面关联设备,数量
  877. DeviceClassListT_remark_r_list_MAX := TaskData_Total_GroupBySnId[0].Total
  878. TaskData_Total_GroupBySnId_Map := make(map[string]int64)
  879. for _, v := range TaskData_Total_GroupBySnId {
  880. TaskData_Total_GroupBySnId_Map[v.T_sn] = v.Total
  881. }
  882. var completeDataDeviceClassList []Device.DeviceClassList
  883. for _, v := range DeviceClassListT_remark_r {
  884. // 排除目标设备本身,只使用其他有完整数据的设备作为平均源
  885. if TaskData_Total_GroupBySnId_Map[v.T_sn] == DeviceClassListT_remark_r_list_MAX && v.T_sn != device.T_sn {
  886. completeDataDeviceClassList = append(completeDataDeviceClassList, v)
  887. }
  888. }
  889. if len(completeDataDeviceClassList) < 2 {
  890. // 如果只有1个设备有数据,直接用该设备的数据复制(不平均)
  891. if len(completeDataDeviceClassList) == 1 {
  892. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "[" + device.T_remark + "]仅1个完整数据源,直接复制数据"})
  893. sn1, id_str1 := completeDataDeviceClassList[0].T_sn, completeDataDeviceClassList[0].T_id
  894. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: device.T_sn + "," + device.T_id + "开始复制" +
  895. fmt.Sprintf("%s,%s", sn1, id_str1)})
  896. List1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn1, id_str1, StartTime, EndTime, 0, 9999)
  897. // 如果源设备数据被更新为T_id作为T_sn,用T_id回退查询
  898. if len(List1) == 0 {
  899. List1, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str1, id_str1, StartTime, EndTime, 0, 9999)
  900. }
  901. if len(List1) == 0 {
  902. return
  903. }
  904. T_saveT := 60
  905. ct, _ := lib.TimeStrToTime(List1[0].T_time)
  906. var valueStrings []string
  907. for i := 0; i < len(List1); i++ {
  908. 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")))
  909. ct = ct.Add(time.Second * time.Duration(T_saveT))
  910. }
  911. Task.DeleteTaskDataByTimeRange(T_task_id, device.T_sn, device.T_id, StartTime, EndTime)
  912. err := Task.Batch_Adds_TaskData(T_task_id, valueStrings)
  913. if err == nil {
  914. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%d/%d", len(valueStrings), len(valueStrings))})
  915. }
  916. return
  917. }
  918. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "[" + device.T_remark + "]中没有找到 至少2条 完整可用数据"})
  919. return
  920. }
  921. //twoDevice := getBeforeAndAfter(device.T_id, completeDataDeviceClassList)
  922. //sn1, id_str1 := twoDevice[0].T_sn, twoDevice[0].T_id
  923. //sn2, id_str2 := twoDevice[1].T_sn, twoDevice[1].T_id
  924. // 根据目标T_id选最近的2个设备做平均,避免所有设备数据相同
  925. sn1, id_str1, sn2, id_str2 := findClosestTwo(device.T_id, completeDataDeviceClassList)
  926. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: device.T_sn + "," + device.T_id + "开始平均复制到" +
  927. fmt.Sprintf("%s,%s|%s,%s", sn1, id_str1, sn2, id_str2)})
  928. List1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn1, id_str1, StartTime, EndTime, 0, 9999)
  929. List2, _ := Task.Read_TaskData_ById_List_AES(T_task_id, sn2, id_str2, StartTime, EndTime, 0, 9999)
  930. // 如果源设备数据被更新为T_id作为T_sn,用T_id回退查询
  931. if len(List1) == 0 {
  932. List1, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str1, id_str1, StartTime, EndTime, 0, 9999)
  933. }
  934. if len(List2) == 0 {
  935. List2, _ = Task.Read_TaskData_ById_List_AES(T_task_id, id_str2, id_str2, StartTime, EndTime, 0, 9999)
  936. }
  937. num := len(List1)
  938. if len(List2) < len(List1) {
  939. num = len(List2)
  940. }
  941. if num == 0 {
  942. return
  943. }
  944. T_saveT := 60
  945. //var list []Task.TaskData_
  946. ct, _ := lib.TimeStrToTime(List1[0].T_time)
  947. var valueStrings []string
  948. for i := 0; i < num; i++ {
  949. if List1[i].T_time != List2[i].T_time {
  950. 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)})
  951. return
  952. }
  953. T_t := (List1[i].T_t + List2[i].T_t) / 2
  954. T_rh := (List1[i].T_rh + List2[i].T_rh) / 2
  955. 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")))
  956. ct = ct.Add(time.Second * time.Duration(T_saveT))
  957. }
  958. Task.DeleteTaskDataByTimeRange(T_task_id, device.T_sn, device.T_id, StartTime, EndTime)
  959. err := Task.Batch_Adds_TaskData(T_task_id, valueStrings)
  960. if err == nil {
  961. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%d/%d", len(valueStrings), len(valueStrings))})
  962. }
  963. }
  964. func getBeforeAndAfter(T_id string, data []Device.DeviceClassList) []Device.DeviceClassList {
  965. var result []Device.DeviceClassList
  966. var index int
  967. for i, d := range data {
  968. if d.T_id == T_id {
  969. index = i
  970. break
  971. }
  972. }
  973. if index == 0 {
  974. result = append(result, data[len(data)-1], data[1])
  975. } else if index == len(data)-1 {
  976. result = append(result, data[len(data)-2], data[0])
  977. } else {
  978. result = append(result, data[index-1], data[index+1])
  979. }
  980. return result
  981. }
  982. // findClosestTwo 根据目标T_id找最近的2个设备,用于平均源选择
  983. func findClosestTwo(targetId string, data []Device.DeviceClassList) (string, string, string, string) {
  984. target, _ := strconv.Atoi(targetId)
  985. // 按T_id排序
  986. sorted := make([]Device.DeviceClassList, len(data))
  987. copy(sorted, data)
  988. for i := 0; i < len(sorted); i++ {
  989. for j := i + 1; j < len(sorted); j++ {
  990. idI, _ := strconv.Atoi(sorted[i].T_id)
  991. idJ, _ := strconv.Atoi(sorted[j].T_id)
  992. if idI > idJ {
  993. sorted[i], sorted[j] = sorted[j], sorted[i]
  994. }
  995. }
  996. }
  997. // 找最近的两个设备
  998. var closest1, closest2 Device.DeviceClassList
  999. minDiff1, minDiff2 := 99999, 99999
  1000. for _, d := range sorted {
  1001. id, err := strconv.Atoi(d.T_id)
  1002. if err != nil {
  1003. id = 0
  1004. }
  1005. diff := id - target
  1006. if diff < 0 {
  1007. diff = -diff
  1008. }
  1009. if diff < minDiff1 {
  1010. minDiff2 = minDiff1
  1011. closest2 = closest1
  1012. minDiff1 = diff
  1013. closest1 = d
  1014. } else if diff < minDiff2 {
  1015. minDiff2 = diff
  1016. closest2 = d
  1017. }
  1018. }
  1019. return closest1.T_sn, closest1.T_id, closest2.T_sn, closest2.T_id
  1020. }
  1021. func RoundToDecimal(num float64, decimal int) float64 {
  1022. shift := math.Pow(10, float64(decimal))
  1023. return math.Round(num*shift) / shift
  1024. }
  1025. /*
  1026. 数据自检名称:平均值数据自检
  1027. 测点:柜内所有测点|箱内所有测点|均匀性布点和产品存放区域测点、温湿度绑定点01、温湿度绑定点02
  1028. 时间:采用“绑定点数据自检”的时间
  1029. 策略:先执行完成“绑定点数据自检”后,温湿度绑定点01和温湿度绑定点02的数据就作为基准数据不变,温湿度绑定点01平均值与柜内所有测点平均值之间的差异在±0.5℃范围以内,温湿度绑定点02平均值与柜内所有测点平均值之间的差异在±0.5℃范围以内,若柜内所有测点的平均值较大或较小,则对柜内所有测点中平均值较大或较小的测点曲线进行偏移调整,使柜内所有测点的平均值靠近温湿度绑定点的平均值,从而达到绑定点与柜内所有测点的平均值差异在±0.5℃。以上“柜内所有测点”要分别换成“箱内所有测点”、“均匀性布点和产品存放区域测点”;“平均值数据自检”和“冷热点数据自检”可以同时勾选(冷库和冷车会用到)进行自检也可以单独执行(冷柜和保温箱只执行“平均值数据自检”)
  1030. */
  1031. func RoundToDecimalHalfUp(val float64, n int) float64 {
  1032. pow := math.Pow(10, float64(n))
  1033. return math.Round(val*pow) / pow
  1034. }
  1035. /*
  1036. 数据自检名称:平均值数据自检
  1037. 策略:逐个测点计算平均值,与绑定点平均值对比。
  1038. 若偏差 > 0.5,则单独对该测点进行偏移(目标设为 基准±0.3,留出0.2安全余量避免贴边界)。
  1039. 若偏差 ≤ 0.5,则合规,不处理。
  1040. */
  1041. func (c *TaskDataHandleController) SSE_Compare_binding_points_with_Average() {
  1042. T_task_id := c.GetString("T_task_id")
  1043. 偏移阈值 := 0.5
  1044. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  1045. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  1046. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  1047. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "任务开始......"})
  1048. Task_r, err := Task.Read_Task(T_task_id)
  1049. if err != nil {
  1050. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  1051. return
  1052. }
  1053. // 温度控制范围
  1054. 温度控制范围最高值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")))
  1055. if 温度控制范围最高值 == 0 {
  1056. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最高值 标签值不正确!"})
  1057. return
  1058. }
  1059. 温度控制范围最小值 := float64(lib.To_float32(VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")))
  1060. if 温度控制范围最小值 == 0 {
  1061. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温度控制范围最小值 标签值不正确!"})
  1062. return
  1063. }
  1064. // 布点终端
  1065. 部点终端_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "均匀性布点|柜内所有测点|箱内所有测点|产品存放区域测点")
  1066. if len(部点终端_list) == 0 {
  1067. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到 柜内所有测点|箱内所有测点|均匀性布点|产品存放区域测点 测点数据!"})
  1068. return
  1069. }
  1070. 部点终端_sn_list := Device.JoinDeviceClassListSnToString(部点终端_list)
  1071. // 动态兼容“满载”和“空载”的时间范围
  1072. 开始时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)开始时间")
  1073. 结束时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)结束时间")
  1074. if 开始时间 == "" || 结束时间 == "" {
  1075. 开始时间 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(空载)开始时间")
  1076. 结束时间 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(空载)结束时间")
  1077. }
  1078. if 开始时间 == "" || 结束时间 == "" {
  1079. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温度分布特性的测试与分析(满载/空载)的开始时间 或 结束时间!"})
  1080. return
  1081. }
  1082. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "--- 实际使用测试时间范围: " + 开始时间 + " 至 " + 结束时间 + " ---"})
  1083. // ----------获取保留数据------------
  1084. var valueStrings1, valueStrings2 []string
  1085. var 开空开, 保空开 string
  1086. var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
  1087. var BWXValueStrings []string
  1088. if Task_r.T_device_type != "X" {
  1089. valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰 = c.GetRetainData(Task_r, 部点终端_list, true)
  1090. } else {
  1091. BWXValueStrings = c.GetBWXRetainData(Task_r, 部点终端_sn_list, 结束时间, 结束时间)
  1092. }
  1093. // ----------获取保留数据结束------------
  1094. // 温湿度绑定点
  1095. 温湿度绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温湿度绑定点")
  1096. if len(温湿度绑定点_list) < 1 {
  1097. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 温湿度绑定点!"})
  1098. return
  1099. }
  1100. var 温湿度绑定点1, _ Device.DeviceClassList
  1101. for _, list := range 温湿度绑定点_list {
  1102. if strings.Contains(list.T_remark, "温湿度绑定点1") {
  1103. 温湿度绑定点1 = list
  1104. }
  1105. if strings.Contains(list.T_remark, "温湿度绑定点2") {
  1106. _ = list
  1107. }
  1108. }
  1109. // 1. 绑定点1的平均值作为基准
  1110. 绑定点1_Avg_raw := Task.Read_TaskData_AVG(T_task_id, 温湿度绑定点1.T_sn, 温湿度绑定点1.T_id, 开始时间, 结束时间)
  1111. 绑定点1_Avg := RoundToDecimalHalfUp(绑定点1_Avg_raw, 1)
  1112. if 绑定点1_Avg == 0 {
  1113. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "温湿度绑定点1 无平均值数据!"})
  1114. return
  1115. }
  1116. 压缩上限 := RoundToDecimalHalfUp(绑定点1_Avg+偏移阈值, 1) // 基准 + 0.5
  1117. 压缩下限 := RoundToDecimalHalfUp(绑定点1_Avg-偏移阈值, 1) // 基准 - 0.5
  1118. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  1119. Msg: fmt.Sprintf("定点1 = %.1f, 合规范围 [%.1f, %.1f]", 绑定点1_Avg, 压缩下限, 压缩上限)})
  1120. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "逐终端检查:"})
  1121. // 初始化随机数种子
  1122. rand.Seed(time.Now().UnixNano())
  1123. // 定义安全边界:0.4 或 0.5 二选一
  1124. 随机偏移量 := 0.4
  1125. if rand.Intn(2) == 1 {
  1126. 随机偏移量 = 0.5
  1127. }
  1128. // 提前解析时间,用于后续的防断崖校验
  1129. endTimeObj, err := time.Parse("2006-01-02 15:04", 结束时间)
  1130. if err != nil {
  1131. endTimeObj, err = time.Parse("2006-01-02 15:04:05", 结束时间+":00")
  1132. }
  1133. compareTimeStr := endTimeObj.Format("2006-01-02 15:04")
  1134. targetTimeObj := endTimeObj.Add(1 * time.Minute)
  1135. targetTimeStr := targetTimeObj.Format("2006-01-02 15:04")
  1136. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("------ 开始校验 %s 之后的数据防断崖处理 ------", 结束时间)})
  1137. // 获取 ORM 实例用于执行原生 SQL 更新
  1138. //o := orm.NewOrm()
  1139. for _, dev := range 部点终端_list {
  1140. 终端_id := dev.T_id
  1141. 终端_sn := dev.T_sn
  1142. // ================= 步骤 1:先执行平均值偏移 =================
  1143. 终端_Avg_raw := Task.Read_TaskData_AVG(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
  1144. 终端_Avg := RoundToDecimalHalfUp(终端_Avg_raw, 1)
  1145. 偏差 := RoundToDecimalHalfUp(math.Abs(终端_Avg-绑定点1_Avg), 1)
  1146. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  1147. Msg: fmt.Sprintf("测点[%s] 当前平均值:%.1f, 偏差:%.1f", 终端_id, 终端_Avg, 偏差)})
  1148. if 偏差 > 偏移阈值 {
  1149. 终端_Min := Task.Read_TaskData_min(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
  1150. 终端_Max := Task.Read_TaskData_max(T_task_id, 终端_sn, 终端_id, 开始时间, 结束时间)
  1151. var targetAvg, 偏移量 float64
  1152. var 动作 string
  1153. if 终端_Avg > 绑定点1_Avg {
  1154. // 降:目标值 = 基准 + 0.4 或 基准 + 0.5
  1155. targetAvg = RoundToDecimalHalfUp(绑定点1_Avg+随机偏移量, 1)
  1156. 偏移量 = RoundToDecimalHalfUp(终端_Avg-targetAvg, 1)
  1157. 动作 = "下移"
  1158. c.SetAverageShiftedDownward(T_task_id, 终端_id, 0.0, targetAvg, 终端_Avg, 终端_Min, 终端_Max,
  1159. 温度控制范围最小值, 温度控制范围最高值, "", "")
  1160. } else {
  1161. // 升:目标值 = 基准 - 0.4 或 基准 - 0.5
  1162. targetAvg = RoundToDecimalHalfUp(绑定点1_Avg-随机偏移量, 1)
  1163. 偏移量 = RoundToDecimalHalfUp(targetAvg-终端_Avg, 1)
  1164. 动作 = "上移"
  1165. c.SetAverageShiftedUpward(T_task_id, 终端_id, 0.0, targetAvg, 终端_Avg, 终端_Min, 终端_Max,
  1166. 温度控制范围最小值, 温度控制范围最高值, "", "")
  1167. }
  1168. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  1169. Msg: fmt.Sprintf(" 测点[%s] 偏差:%.1f > 0.5 → %s%.1f → 动态目标Avg:%.1f (随机安全值)",
  1170. 终端_id, 偏差, 动作, 偏移量, targetAvg)})
  1171. } else {
  1172. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  1173. Msg: fmt.Sprintf("测点[%s] 偏差 %.1f <= 0.5,已在合规范围内,跳过不处理", 终端_id, 偏差)})
  1174. }
  1175. }
  1176. // 重新获取处理后的整体平均值用于后续对比
  1177. 处理后整体Avg := Task.Read_TaskData_Average(Task_r.T_task_id, 部点终端_sn_list, 开始时间, 结束时间)
  1178. 处理后整体Avg = RoundToDecimalHalfUp(处理后整体Avg, 1)
  1179. T_deviation := 1.0
  1180. 温控传感器绑定点_list := Device.Read_DeviceClassList_List_id_T_remark(Task_r.T_class, "温控传感器绑定点1|温控传感器绑定点2")
  1181. if len(温控传感器绑定点_list) == 0 {
  1182. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "没有找到温控传感器绑定点!"})
  1183. }
  1184. for i, classList := range 温控传感器绑定点_list {
  1185. 温控传感器绑定点OriginalAvg := Task.Read_TaskData_Average(Task_r.T_task_id, classList.T_sn, 开始时间, 结束时间)
  1186. 温控传感器绑定点OriginalAvg = RoundToDecimalHalfUp(温控传感器绑定点OriginalAvg, 1)
  1187. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "------ 进行 温控传感器绑定点" + strconv.Itoa(i+1) + " 平均值处理 ------"})
  1188. if (温控传感器绑定点OriginalAvg >= 处理后整体Avg && 温控传感器绑定点OriginalAvg <= 处理后整体Avg+T_deviation) ||
  1189. (温控传感器绑定点OriginalAvg <= 处理后整体Avg && 温控传感器绑定点OriginalAvg >= 处理后整体Avg-T_deviation) {
  1190. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: " 温控传感器绑定点" + lib.To_string(i+1) + " 平均值:" + lib.To_string(温控传感器绑定点OriginalAvg) + "℃ " +
  1191. " 测点平均值" + lib.To_string(处理后整体Avg) + "℃ " +
  1192. " 数据偏差: " + lib.To_string(RoundToDecimalHalfUp(math.Abs(温控传感器绑定点OriginalAvg-处理后整体Avg), 1)) + "℃ " +
  1193. " 设置:" + lib.To_string(T_deviation) + "℃" +
  1194. " 符合要求!"})
  1195. } else {
  1196. if 温控传感器绑定点OriginalAvg < 处理后整体Avg-T_deviation {
  1197. T_max := Task.Read_TaskData_max(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
  1198. T_min := Task.Read_TaskData_min(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
  1199. c.SetAverageShiftedUpward(T_task_id, classList.T_id, T_deviation, RoundToDecimalHalfUp(处理后整体Avg-T_deviation, 1), 温控传感器绑定点OriginalAvg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
  1200. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "温控传感器绑定点" + lib.To_string(i+1) + " 平均值向上偏移"})
  1201. }
  1202. if 温控传感器绑定点OriginalAvg > 处理后整体Avg+T_deviation {
  1203. T_max := Task.Read_TaskData_max(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
  1204. T_min := Task.Read_TaskData_min(T_task_id, classList.T_sn, classList.T_id, 开始时间, 结束时间)
  1205. c.SetAverageShiftedDownward(T_task_id, classList.T_id, T_deviation, RoundToDecimalHalfUp(处理后整体Avg+T_deviation, 1), 温控传感器绑定点OriginalAvg, T_min, T_max, 温度控制范围最小值, 温度控制范围最高值, "", "")
  1206. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "温控传感器绑定点" + lib.To_string(i+1) + " 平均值向下偏移"})
  1207. }
  1208. }
  1209. }
  1210. // ----------恢复保留数据------------
  1211. if Task_r.T_device_type != "X" {
  1212. c.SaveRetainData(Task_r, 部点终端_list, valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰, 温度控制范围最高值, 60)
  1213. } else {
  1214. c.SaveBWXRetainData(Task_r, 部点终端_list, BWXValueStrings, 结束时间, 结束时间, 60)
  1215. }
  1216. // ----------恢复保留数据结束------------
  1217. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("------ 开始校验 %s 之后的数据防断崖处理 ------", 结束时间)})
  1218. o := orm.NewOrm()
  1219. for _, dev := range 部点终端_list {
  1220. 终端_id := dev.T_id
  1221. 终端_sn := dev.T_sn
  1222. var temp46, temp47 float64
  1223. // 查询该测点在 结束时间(如 16:46) 的最后一条温度值
  1224. 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)
  1225. err46 := o.Raw(sql46, 终端_sn, compareTimeStr).QueryRow(&temp46)
  1226. if err46 == nil {
  1227. // 查询该测点在 结束时间+1分钟(如 16:47) 的第一条温度值
  1228. 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)
  1229. err47 := o.Raw(sql47, 终端_sn, targetTimeStr).QueryRow(&temp47)
  1230. // 对比并修正
  1231. if err47 == nil && temp47 < temp46 {
  1232. 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)
  1233. _, updateErr := o.Raw(updateSql, temp46, 终端_sn, targetTimeStr).Exec()
  1234. if updateErr == nil {
  1235. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0,
  1236. Msg: fmt.Sprintf(" 测点[%s] 防断崖兜底: %s 温度 %.1f℃ < %s 温度 %.1f℃,已强制拉平至 %.1f℃",
  1237. 终端_id, targetTimeStr, temp47, compareTimeStr, temp46, temp46)})
  1238. }
  1239. }
  1240. }
  1241. }
  1242. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: "完成!"})
  1243. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  1244. }
  1245. /*
  1246. 平均值下移
  1247. targetAvg 参考平均值
  1248. originalAvg 原始平均值
  1249. T_min 整段数据最小值
  1250. T_max 整段数据最大值
  1251. minLimit 温度控制范围最小值
  1252. maxLimit 温度控制范围最大值
  1253. */
  1254. func (c *TaskDataHandleController) SetAverageShiftedDownward(T_task_id, T_id string, T_deviation, targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64, StartTime, EndTime string) {
  1255. 目标avg := RoundToDecimal(targetAvg+T_deviation, 1)
  1256. vgaca := RoundToDecimal(originalAvg-目标avg, 1)
  1257. //向下偏移后整段数据最小值大于温度控制范围最小值
  1258. if RoundToDecimal(T_min-vgaca, 1) > RoundToDecimal(minLimit, 1) && RoundToDecimal(T_max-vgaca, 1) < RoundToDecimal(maxLimit, 1) {
  1259. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", -vgaca)
  1260. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
  1261. " 测点 " + lib.To_string(T_id) +
  1262. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1263. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1264. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1265. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1266. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  1267. " 向下偏移:" + lib.To_string(vgaca) + "℃ "})
  1268. } else {
  1269. var err error
  1270. var compress float64
  1271. compress, vgaca, err = GetLinearTransformationValue(目标avg, originalAvg, T_min, T_max, minLimit, maxLimit)
  1272. if err != nil {
  1273. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "进行处理 数据!" +
  1274. " 测点" + lib.To_string(T_id) +
  1275. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1276. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1277. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1278. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1279. " 无合法解: 无法满足所有约束条件!"})
  1280. return
  1281. }
  1282. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
  1283. " 测点" + lib.To_string(T_id) +
  1284. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1285. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1286. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1287. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1288. " 缩放:" + lib.To_string(compress) +
  1289. " 缩放后最大值:" + lib.To_string(RoundToDecimal(T_max*compress, 1)) + "℃ " +
  1290. " 缩放后最小值:" + lib.To_string(RoundToDecimal(T_min*compress, 1)) + "℃ " +
  1291. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  1292. " 偏移:" + lib.To_string(vgaca) + "℃ "})
  1293. // 压缩
  1294. Task.UpdateTaskDataTemperatureAndHumidityByGeometric_id(T_task_id, T_id, "", "", compress)
  1295. // 偏移
  1296. if vgaca != 0 {
  1297. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
  1298. }
  1299. }
  1300. }
  1301. func GetLinearTransformationValue(targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64) (float64, float64, error) {
  1302. //var vgaca float64
  1303. //compress1 := RoundToDecimal((minLimit+0.3-targetAvg)/(T_min-originalAvg), 1)
  1304. //compress2 := RoundToDecimal((maxLimit-0.3-targetAvg)/(T_max-originalAvg), 1)
  1305. //compress := math.Min(compress1, compress2)
  1306. //if RoundToDecimal(T_max*compress, 1) < RoundToDecimal(maxLimit, 1) && RoundToDecimal(T_min*compress, 1) > RoundToDecimal(minLimit, 1) {
  1307. // vgaca = 0
  1308. //} else {
  1309. // vgaca = RoundToDecimal(targetAvg-compress*originalAvg, 1)
  1310. //}
  1311. //for _, x := range []float64{T_min, T_max} {
  1312. // newVal := RoundToDecimal(compress*x+vgaca, 1)
  1313. // if newVal < minLimit || newVal > maxLimit {
  1314. // return 0, 0, errors.New("无合法解: 无法满足所有约束条件")
  1315. // }
  1316. //}
  1317. //
  1318. //return compress, vgaca, nil
  1319. minLimit = RoundToDecimal(minLimit+0.2, 1)
  1320. maxLimit = RoundToDecimal(maxLimit-0.2, 1)
  1321. // 策略1: 使用最小边界值 (使变换后最小值 = minBound)
  1322. k1 := (targetAvg - minLimit) / (originalAvg - T_min)
  1323. b1 := minLimit - k1*T_min
  1324. valid1 := true
  1325. for _, x := range []float64{T_min, T_max} {
  1326. newVal := RoundToDecimal(k1*x+b1, 1)
  1327. if newVal < minLimit || newVal > maxLimit {
  1328. valid1 = false
  1329. break
  1330. }
  1331. }
  1332. // 策略2: 使用最大边界值 (使变换后最大值 = maxBound),策略1失败时使用
  1333. k2 := float64(0)
  1334. b2 := float64(0)
  1335. valid2 := false
  1336. if !valid1 {
  1337. k2 = (maxLimit - targetAvg) / (T_max - originalAvg)
  1338. b2 = targetAvg - k2*originalAvg
  1339. valid2 = true
  1340. for _, x := range []float64{T_min, T_max} {
  1341. newVal := RoundToDecimal(k2*x+b2, 1)
  1342. if newVal < minLimit || newVal > maxLimit {
  1343. valid2 = false
  1344. break
  1345. }
  1346. }
  1347. }
  1348. // 根据结果选择有效策略
  1349. switch {
  1350. case valid1:
  1351. return k1, b1, nil
  1352. case valid2:
  1353. return k2, b2, nil
  1354. default:
  1355. return 0, 0, errors.New("无合法解: 无法满足所有约束条件")
  1356. }
  1357. }
  1358. /*
  1359. 平均值上移
  1360. targetAvg 参考平均值
  1361. originalAvg 原始平均值
  1362. T_min 整段数据最小值
  1363. T_max 整段数据最大值
  1364. minLimit 温度控制范围最小值
  1365. maxLimit 温度控制范围最大值
  1366. */
  1367. func (c *TaskDataHandleController) SetAverageShiftedUpward(T_task_id, T_id string, T_deviation, targetAvg, originalAvg, T_min, T_max, minLimit, maxLimit float64, StartTime, EndTime string) {
  1368. 目标avg := RoundToDecimal(targetAvg-T_deviation, 1)
  1369. vgaca := RoundToDecimal(目标avg-originalAvg, 1)
  1370. if RoundToDecimal(T_max+vgaca, 1) < RoundToDecimal(maxLimit, 1) && RoundToDecimal(T_min+vgaca, 1) > RoundToDecimal(minLimit, 1) {
  1371. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
  1372. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
  1373. " 测点 " + lib.To_string(T_id) +
  1374. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1375. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1376. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1377. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1378. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  1379. " 向上偏移:" + lib.To_string(vgaca) + "℃ "})
  1380. } else {
  1381. var err error
  1382. var compress float64
  1383. compress, vgaca, err = GetLinearTransformationValue(目标avg, originalAvg, T_min, T_max, minLimit, maxLimit)
  1384. if err != nil {
  1385. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "进行处理 数据!" +
  1386. " 测点" + lib.To_string(T_id) +
  1387. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1388. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1389. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1390. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1391. " 无合法解: 无法满足所有约束条件!"})
  1392. return
  1393. }
  1394. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "进行处理 数据!" +
  1395. " 测点" + lib.To_string(T_id) +
  1396. " 最大值:" + lib.To_string(T_max) + "℃ " +
  1397. " 最小值:" + lib.To_string(T_min) + "℃ " +
  1398. " 参考平均值:" + lib.To_string(RoundToDecimal(targetAvg, 1)) + "℃ " +
  1399. " 原始平均值:" + lib.To_string(RoundToDecimal(originalAvg, 1)) + "℃ " +
  1400. " 缩放:" + lib.To_string(compress) +
  1401. " 缩放后最大值:" + lib.To_string(RoundToDecimal(T_max*compress, 1)) + "℃ " +
  1402. " 缩放后最小值:" + lib.To_string(RoundToDecimal(T_min*compress, 1)) + "℃ " +
  1403. " 数据偏差:" + lib.To_string(vgaca) + "℃ " +
  1404. " 偏移:" + lib.To_string(vgaca) + "℃ "})
  1405. // 压缩
  1406. Task.UpdateTaskDataTemperatureAndHumidityByGeometric_id(T_task_id, T_id, "", "", compress)
  1407. // 偏移
  1408. if vgaca != 0 {
  1409. Task.UpdateTaskDataTemperatureAndHumidityByGeometricAVG(T_task_id, T_id, "", "", vgaca)
  1410. }
  1411. }
  1412. }
  1413. func (c *TaskDataHandleController) GetCalculateHumps(T_task_id string, SN_list []Device.DeviceClassList, startTime, types string) (calculateHumps Task.CalculateHumps_R) {
  1414. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1415. var is bool
  1416. calculateHumps, is = Task.Redis_CalculateHumps_Get(T_task_id + types)
  1417. if !is {
  1418. list := Task.Read_TaskData_ById_AVG(T_task_id, SN, startTime, "")
  1419. // 获取第一个驼峰结束时间点
  1420. CalculateHumps_list := Task.CalculateHumpsByThreeDots(list)
  1421. if len(CalculateHumps_list) < 1 {
  1422. return
  1423. }
  1424. calculateHumps = CalculateHumps_list[0]
  1425. Task.Redis_CalculateHumps_Set(T_task_id+types, calculateHumps)
  1426. }
  1427. return calculateHumps
  1428. }
  1429. func (c *TaskDataHandleController) GetMetadata(T_task_id string, SN_list []Device.DeviceClassList, startTime, types string) (valueStrings []string, calculateHumps Task.CalculateHumps_R) {
  1430. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1431. var is bool
  1432. calculateHumps, is = Task.Redis_CalculateHumps_Get(T_task_id + types)
  1433. if !is {
  1434. return
  1435. }
  1436. endTime := calculateHumps.End.T_time
  1437. data1, _ := Task.Read_TaskData_ById_List_AES(T_task_id, SN, "", startTime, endTime, 0, 9999)
  1438. for _, v := range data1 {
  1439. 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))
  1440. }
  1441. return valueStrings, calculateHumps
  1442. }
  1443. func (c *TaskDataHandleController) GetCalculateHumpsMaps(T_task_id string, SN_list []Device.DeviceClassList, startTime string) map[string]Task.CalculateHumps_R {
  1444. var CalculateHumpsMaps = make(map[string]Task.CalculateHumps_R)
  1445. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1446. allList := Task.Read_TaskData_ById_AVG(T_task_id, SN, startTime, "")
  1447. // 获取第一个驼峰结束时间点
  1448. CalculateHumps_list := Task.CalculateHumpsByThreeDots(allList)
  1449. if len(CalculateHumps_list) < 1 {
  1450. return CalculateHumpsMaps
  1451. }
  1452. for _, device := range SN_list {
  1453. list := Task.Read_TaskData_ById_AVG(T_task_id, device.T_sn, startTime, "")
  1454. //获取第一个驼峰结束时间点
  1455. CalculateHumps := Task.CalculateHumpsByThreeDots(list)
  1456. if len(CalculateHumps) < 1 {
  1457. CalculateHumpsMaps[device.T_sn] = CalculateHumps_list[0]
  1458. continue
  1459. }
  1460. if CalculateHumps[0].Peak.T_time != CalculateHumps_list[0].Peak.T_time {
  1461. CalculateHumps[0].Peak.T_time = CalculateHumps_list[0].Peak.T_time
  1462. }
  1463. et1, _ := lib.TimeStrToTime(CalculateHumps_list[0].End.T_time)
  1464. et2, _ := lib.TimeStrToTime(CalculateHumps[0].End.T_time)
  1465. if et1.Before(et2) {
  1466. CalculateHumps[0].End.T_time = CalculateHumps_list[0].End.T_time
  1467. }
  1468. CalculateHumpsMaps[device.T_sn] = CalculateHumps[0]
  1469. }
  1470. return CalculateHumpsMaps
  1471. }
  1472. // 获取保留数据
  1473. func (c *TaskDataHandleController) GetRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList, useCache bool) (valueStrings1, valueStrings2 []string,
  1474. kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R) {
  1475. var 开空开, 保空开 string
  1476. var 开空开驼峰, 保空开驼峰 Task.CalculateHumps_R
  1477. if Task_r.T_device_type != "X" {
  1478. 开空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开空开")
  1479. if len(开空开) == 0 {
  1480. 开空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开满开")
  1481. }
  1482. if len(开空开) == 0 {
  1483. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 开空开/开满开 时间 标签!"})
  1484. return
  1485. }
  1486. //保空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "保空开")
  1487. //if len(保空开) == 0 {
  1488. // 保空开 = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "保满开")
  1489. //}
  1490. //if len(保空开) == 0 {
  1491. // lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "未找到 保空开/保满开 时间 标签!"})
  1492. // return
  1493. //}
  1494. //if !useCache {
  1495. // 开空开驼峰 = c.GetCalculateHumps(Task_r.T_task_id, SN_list, 开空开, "kkk")
  1496. // 保空开驼峰 = c.GetCalculateHumps(Task_r.T_task_id, SN_list, 保空开, "bkk")
  1497. //}
  1498. //
  1499. //valueStrings1, 开空开驼峰 = c.GetMetadata(Task_r.T_task_id, SN_list, 开空开, "kkk")
  1500. //valueStrings2, 保空开驼峰 = c.GetMetadata(Task_r.T_task_id, SN_list, 保空开, "bkk")
  1501. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1502. data1, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, SN, "", 开空开, "", 0, 9999)
  1503. for _, v := range data1 {
  1504. 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))
  1505. }
  1506. }
  1507. return valueStrings1, valueStrings2, 开空开, 保空开, 开空开驼峰, 保空开驼峰
  1508. }
  1509. // 获取保留数据
  1510. func (c *TaskDataHandleController) SaveRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1, valueStrings2 []string,
  1511. kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
  1512. var err error
  1513. if len(valueStrings1) > 0 {
  1514. //将开空开/开满开时间开始第一个驼峰 写入原始数据
  1515. //将 开空开/开满开时间 后时间写入原始数据
  1516. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1517. Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, kkkStartTime, kkkCalculateHumps.End.T_time)
  1518. err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings1)
  1519. if err == nil {
  1520. 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))})
  1521. }
  1522. }
  1523. if len(valueStrings2) > 0 {
  1524. //将开空开/开满开时间开始第一个驼峰 写入原始数据
  1525. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1526. Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, bkkStartTime, bkkCalculateHumps.End.T_time)
  1527. err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings2)
  1528. if err == nil {
  1529. 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))})
  1530. }
  1531. }
  1532. //c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings1, kkkStartTime, kkkCalculateHumps, maxLimit, saveTime)
  1533. //c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings2, bkkStartTime, bkkCalculateHumps, maxLimit, saveTime)
  1534. }
  1535. // 获取保留数据
  1536. func (c *TaskDataHandleController) SaveRetainDataTrend(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1, valueStrings2 []string,
  1537. kkkStartTime, bkkStartTime string, kkkCalculateHumps, bkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
  1538. c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings1, kkkStartTime, kkkCalculateHumps, maxLimit, saveTime)
  1539. c.SaveRetainDataAndTrend(Task_r, SN_list, valueStrings2, bkkStartTime, bkkCalculateHumps, maxLimit, saveTime)
  1540. }
  1541. func (c *TaskDataHandleController) SaveRetainDataAndTrend(Task_r Task.Task, SN_list []Device.DeviceClassList, valueStrings1 []string,
  1542. kkkStartTime string, kkkCalculateHumps Task.CalculateHumps_R, maxLimit float64, saveTime int) {
  1543. var err error
  1544. if len(valueStrings1) > 0 {
  1545. //将开空开/开满开时间开始第一个驼峰 写入原始数据
  1546. SN := Device.JoinDeviceClassListSnToString(SN_list)
  1547. Task.DeleteTaskAllDataByTimeRange(Task_r.T_task_id, SN, kkkStartTime, kkkCalculateHumps.End.T_time)
  1548. err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings1)
  1549. if err == nil {
  1550. 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))})
  1551. }
  1552. startTimeT, _ := lib.TimeStrToTime(kkkStartTime)
  1553. startTime := startTimeT.Add(-time.Minute).Format("2006-01-02 15:04")
  1554. endTimeT, _ := lib.TimeStrToTime(kkkCalculateHumps.End.T_time)
  1555. endTime := endTimeT.Add(time.Minute).Format("2006-01-02 15:04")
  1556. // 执行数据平滑
  1557. for _, v := range SN_list {
  1558. sn := v.T_sn
  1559. id_str := v.T_id
  1560. //AllList, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, kkkCalculateHumps.End.T_time, 0, 9999)
  1561. var trendTime, declineTrendTime string
  1562. trendTime = kkkCalculateHumps.Peak.T_time
  1563. declineTrendTime = kkkCalculateHumps.Peak.T_time
  1564. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "执行数据上升趋势"})
  1565. if len(trendTime) > 0 {
  1566. list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, trendTime, 0, 9999)
  1567. if len(list) <= 2 {
  1568. continue
  1569. }
  1570. first := list[0]
  1571. var last Task.TaskData_
  1572. if len(list) > 10 {
  1573. last = list[len(list)-2]
  1574. } else {
  1575. last = list[len(list)-1]
  1576. }
  1577. current, _ := time.Parse("2006-01-02 15:04", first.T_time)
  1578. next, _ := time.Parse("2006-01-02 15:04", last.T_time)
  1579. interval := next.Sub(current).Seconds() / float64(saveTime)
  1580. //ttInterval := (last.T_t - first.T_t) / float32(interval)
  1581. trhInterval := (last.T_rh - first.T_rh) / float32(interval)
  1582. ttList := generateRisingCurve(float64(first.T_t), float64(last.T_t), int(interval+1))
  1583. //tt := first.T_t
  1584. ttrh := first.T_rh
  1585. var valueStrings []string
  1586. for i := 0; i <= int(interval); i++ {
  1587. //tt += ttInterval
  1588. ttrh += trhInterval
  1589. ttime := current.Format("2006-01-02 15:04")
  1590. valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, ttList[i], ttrh, ttime))
  1591. current = current.Add(time.Second * time.Duration(saveTime))
  1592. }
  1593. //for current.Unix() <= next.Unix() {
  1594. //
  1595. // tt += ttInterval
  1596. // ttrh += trhInterval
  1597. // ttime := current.Format("2006-01-02 15:04")
  1598. // valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, tt, ttrh, ttime))
  1599. // current = current.Add(time.Second * time.Duration(saveTime))
  1600. //}
  1601. Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, first.T_time, last.T_time)
  1602. Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
  1603. }
  1604. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "执行数据下降趋势"})
  1605. if len(declineTrendTime) > 0 {
  1606. list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, declineTrendTime, endTime, 0, 9999)
  1607. if len(list) <= 2 {
  1608. continue
  1609. }
  1610. if list[len(list)-1].T_t > list[len(list)-2].T_t {
  1611. continue
  1612. }
  1613. var first Task.TaskData_
  1614. if len(list) > 10 {
  1615. first = list[1]
  1616. } else {
  1617. first = list[0]
  1618. }
  1619. last := list[len(list)-1]
  1620. current, _ := time.Parse("2006-01-02 15:04", first.T_time)
  1621. next, _ := time.Parse("2006-01-02 15:04", last.T_time)
  1622. interval := next.Sub(current).Seconds() / float64(saveTime)
  1623. //ttInterval := (last.T_t - first.T_t) / float32(interval)
  1624. trhInterval := (last.T_rh - first.T_rh) / float32(interval)
  1625. ttList := generateTemperatureCurve(float64(first.T_t), float64(last.T_t), int(interval+1))
  1626. //tt := first.T_t
  1627. ttrh := first.T_rh
  1628. var valueStrings []string
  1629. for i := 0; i <= int(interval); i++ {
  1630. //tt += ttInterval
  1631. ttrh += trhInterval
  1632. ttime := current.Format("2006-01-02 15:04")
  1633. valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, ttList[i], ttrh, ttime))
  1634. current = current.Add(time.Second * time.Duration(saveTime))
  1635. }
  1636. Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, first.T_time, last.T_time)
  1637. Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
  1638. }
  1639. }
  1640. }
  1641. }
  1642. // 获取保留数据
  1643. func (c *TaskDataHandleController) GetBWXRetainData(Task_r Task.Task, SN string, endTime, trendTime string) (BWXValueStrings []string) {
  1644. if endTime != trendTime {
  1645. data1, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, SN, "", trendTime, "", 0, 9999)
  1646. for _, v := range data1 {
  1647. 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))
  1648. }
  1649. }
  1650. return
  1651. }
  1652. func (c *TaskDataHandleController) SaveBWXRetainData(Task_r Task.Task, SN_list []Device.DeviceClassList,
  1653. BWXValueStrings []string, endTime, trendTime string, saveTime int) {
  1654. var err error
  1655. if endTime != trendTime && len(BWXValueStrings) > 0 {
  1656. //将开空开/开满开时间开始第一个驼峰 写入原始数据
  1657. for _, device := range SN_list {
  1658. Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, device.T_sn, device.T_id, trendTime, "")
  1659. }
  1660. err = Task.Batch_Adds_TaskData(Task_r.T_task_id, BWXValueStrings)
  1661. if err == nil {
  1662. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("恢复 保温箱 原始数据 %d/%d", len(BWXValueStrings), len(BWXValueStrings))})
  1663. }
  1664. var startTime string
  1665. startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "静态开箱作业开箱结束时间")
  1666. if len(startTime) == 0 {
  1667. startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "动态开箱作业开箱结束时间")
  1668. }
  1669. if len(startTime) == 0 {
  1670. startTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开箱作业开箱结束时间")
  1671. }
  1672. if len(startTime) == 0 {
  1673. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取 静态开箱作业开箱结束时间|动态开箱作业开箱结束时间|开箱作业开箱结束时间 失败"})
  1674. return
  1675. }
  1676. startTimeT, _ := lib.TimeStrToTime(startTime)
  1677. startTime = startTimeT.Add(30 * time.Minute).Format("2006-01-02 15:04")
  1678. // 执行数据平滑
  1679. for _, v := range SN_list {
  1680. sn := v.T_sn
  1681. id_str := v.T_id
  1682. list, _ := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, sn, id_str, startTime, trendTime, 0, 9999)
  1683. first := list[0]
  1684. last := list[len(list)-1]
  1685. current, _ := time.Parse("2006-01-02 15:04", first.T_time)
  1686. next, _ := time.Parse("2006-01-02 15:04", last.T_time)
  1687. interval := next.Sub(current).Seconds() / float64(saveTime)
  1688. ttInterval := (last.T_t - first.T_t) / float32(interval)
  1689. trhInterval := (last.T_rh - first.T_rh) / float32(interval)
  1690. tt := first.T_t
  1691. ttrh := first.T_rh
  1692. var valueStrings []string
  1693. for current.Unix() <= next.Unix() {
  1694. tt += ttInterval
  1695. ttrh += trhInterval
  1696. ttime := current.Format("2006-01-02 15:04")
  1697. valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%v,%v,'%s')", first.T_sn, id_str, tt, ttrh, ttime))
  1698. current = current.Add(time.Second * time.Duration(saveTime))
  1699. }
  1700. Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, sn, id_str, startTime, trendTime)
  1701. err = Task.Batch_Adds_TaskData(Task_r.T_task_id, valueStrings)
  1702. if err == nil {
  1703. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("保温箱数据趋势 %d/%d", len(valueStrings), len(valueStrings))})
  1704. }
  1705. }
  1706. }
  1707. }
  1708. // 获取开始结束时间
  1709. func (c *TaskDataHandleController) GetStartTimeAndEndTime(Task_r Task.Task, SN string, maxLimit float64) (startTime, endTime, trendTime string) {
  1710. // 开始时间 获取温度下降到第二个低点时间
  1711. // 1. 获取温度平均值
  1712. list := Task.Read_TaskData_ById_AVG(Task_r.T_task_id, SN, "", "")
  1713. if len(list) < 2 {
  1714. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "均匀性布点+产品存放区域测点+作业出入口总测点 数据平均值 少于2条!"})
  1715. return
  1716. }
  1717. // 找平均值低于温度控制范围最高值的第二个最低点
  1718. lowPoint := 0
  1719. for i := 1; i <= len(list)-2; i++ {
  1720. 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) {
  1721. lowPoint += 1
  1722. }
  1723. if lowPoint == 2 {
  1724. startTime = list[i].T_time
  1725. break
  1726. }
  1727. }
  1728. // 保温箱没有开空开,开满开
  1729. // 结束时间温度超限取没有超限时间点,结束时间温度未超限取最后一个时间点
  1730. if Task_r.T_device_type == "X" {
  1731. list2 := Task.Read_TaskData_ById_AVG_DESC(Task_r.T_task_id, SN, "", "")
  1732. if len(list) == 0 {
  1733. return
  1734. }
  1735. if float64(list2[0].T_t) < maxLimit {
  1736. endTime = list2[0].T_time
  1737. trendTime = list2[0].T_time
  1738. return
  1739. }
  1740. for i, avg := range list2 {
  1741. if RoundToDecimal(float64(avg.T_t), 1) < maxLimit {
  1742. endTime = avg.T_time
  1743. trendTime = list2[i-1].T_time
  1744. break
  1745. }
  1746. }
  1747. } else {
  1748. endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开空开")
  1749. if len(endTime) == 0 {
  1750. endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "开满开")
  1751. }
  1752. if len(endTime) == 0 {
  1753. endTime = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "现场测试结束时间")
  1754. }
  1755. if len(endTime) == 0 {
  1756. return
  1757. }
  1758. }
  1759. return
  1760. }
  1761. /*
  1762. 计算绑定点的值
  1763. A 监测终端01平均值
  1764. B 监测终端02平均值
  1765. C 温湿度绑定点01平均值
  1766. D 温湿度绑定点02平均值
  1767. */
  1768. func findBindingPointsOptimalAdjustment(A, B, C, D, T_deviation float64) (float64, float64, bool) {
  1769. type DataPair struct {
  1770. A1 float64
  1771. B1 float64
  1772. }
  1773. T_deviation_half := 0.1
  1774. var dataPairs []DataPair
  1775. var A1, B1 float64 = -100, -100
  1776. // 生成A1和B1的可能值
  1777. for a1 := A - T_deviation; a1 <= A+T_deviation; a1 += T_deviation_half {
  1778. for b1 := B - T_deviation; b1 <= B+T_deviation; b1 += T_deviation_half {
  1779. // 检查是否存在满足条件的C值
  1780. minC := math.Max(a1-T_deviation_half, b1-T_deviation_half)
  1781. maxC := math.Min(a1+T_deviation_half, b1+T_deviation_half)
  1782. if minC <= maxC {
  1783. dataPairs = append(dataPairs, DataPair{A1: a1, B1: b1})
  1784. }
  1785. }
  1786. }
  1787. if len(dataPairs) == 1 {
  1788. A1 = dataPairs[0].A1
  1789. B1 = dataPairs[0].B1
  1790. }
  1791. if len(dataPairs) > 1 {
  1792. // 寻找最优解
  1793. minAdjustment := math.MaxFloat64
  1794. var optimalPair DataPair
  1795. for _, pair := range dataPairs {
  1796. adjustment := math.Abs(pair.A1-C) + math.Abs(pair.B1-D)
  1797. if adjustment < minAdjustment {
  1798. minAdjustment = adjustment
  1799. optimalPair = pair
  1800. }
  1801. }
  1802. A1 = optimalPair.A1
  1803. B1 = optimalPair.B1
  1804. }
  1805. if A1 == -100 || B1 == -100 {
  1806. return A1, B1, false
  1807. }
  1808. return A1, B1, true
  1809. }
  1810. /*
  1811. 计算绑定点的值
  1812. A 温湿度绑定点1平均值
  1813. B 温湿度绑定点2平均值
  1814. C 测点平均值
  1815. */
  1816. func findAverageOptimalAdjustment(A, B, C float64) float64 {
  1817. // 计算有效区间
  1818. minA := A - 0.5
  1819. maxA := A + 0.5
  1820. minB := B - 0.5
  1821. maxB := B + 0.5
  1822. // 求交集范围
  1823. lowerBound := math.Max(minA, minB)
  1824. upperBound := math.Min(maxA, maxB)
  1825. // 确定最优调整值
  1826. var optimalC float64
  1827. if C < lowerBound {
  1828. optimalC = lowerBound // 取区间下限
  1829. } else if C > upperBound {
  1830. optimalC = upperBound // 取区间上限
  1831. } else {
  1832. optimalC = C // 已在区间内无需调整
  1833. }
  1834. return optimalC
  1835. }
  1836. func generateTemperatureCurve(startTemp, minTemp float64, steps int) []float64 {
  1837. if steps <= 0 {
  1838. return []float64{}
  1839. }
  1840. // 计算动态参数确保严格递减且高于最低温度
  1841. base := minTemp + 0.1 // 确保所有值高于minTemp
  1842. decayFactor := math.Log((startTemp-base)/(minTemp+0.5-base)) / float64(steps-1)
  1843. temperatures := make([]float64, steps)
  1844. prevTemp := startTemp
  1845. for i := 0; i < steps; i++ {
  1846. // 使用指数衰减模型确保严格单调递减
  1847. temp := base + (startTemp-base)*math.Exp(-decayFactor*float64(i))
  1848. // 确保严格递减且高于最低温度
  1849. if temp >= prevTemp {
  1850. temp = prevTemp - 0.1
  1851. }
  1852. if temp <= minTemp {
  1853. temp = minTemp + 0.01 + 0.05*float64(steps-i)/float64(steps)
  1854. }
  1855. // 保持精度并确保唯一性
  1856. temp = math.Round(temp*100) / 100
  1857. temperatures[i] = temp
  1858. prevTemp = temp
  1859. }
  1860. return temperatures
  1861. }
  1862. func generateRisingCurve(minTemp, maxTemp float64, steps int) []float64 {
  1863. if steps <= 0 {
  1864. return []float64{}
  1865. }
  1866. // 计算动态参数确保严格递增且低于最高温度
  1867. growthFactor := math.Log((maxTemp-minTemp-0.1)/0.1) / float64(steps-1)
  1868. temperatures := make([]float64, steps)
  1869. prevTemp := minTemp
  1870. for i := 0; i < steps; i++ {
  1871. // 使用指数增长模型确保严格单调递增
  1872. temp := minTemp + (maxTemp-minTemp)*(1-math.Exp(-growthFactor*float64(i)))
  1873. // 确保严格递增且低于最高温度
  1874. if temp <= prevTemp {
  1875. temp = prevTemp + 0.1
  1876. }
  1877. if temp >= maxTemp {
  1878. temp = maxTemp - 0.01 - 0.05*float64(steps-i-1)/float64(steps)
  1879. }
  1880. // 保持精度并确保唯一性
  1881. temp = math.Round(temp*100) / 100
  1882. temperatures[i] = temp
  1883. prevTemp = temp
  1884. }
  1885. return temperatures
  1886. }
  1887. // 鼓包/缺数据及超上下限处理
  1888. // 处理顺序:1. 超高温修复 -> 2. 超低温修复 -> 3. 鼓包修复 -> 4. 兜底裁剪
  1889. func (c *TaskDataHandleController) SSE_Process_hump_data() {
  1890. T_task_id := c.GetString("T_task_id")
  1891. c.Ctx.ResponseWriter.Header().Set("Content-Type", "text/event-stream")
  1892. c.Ctx.ResponseWriter.Header().Set("Cache-Control", "no-cache")
  1893. c.Ctx.ResponseWriter.Header().Set("Connection", "keep-alive")
  1894. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "综合异常检测与修复任务开始......"})
  1895. Task_r, err := Task.Read_Task(T_task_id)
  1896. if err != nil {
  1897. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取任务信息失败!"})
  1898. return
  1899. }
  1900. // 从表单中获取时间范围
  1901. 开始时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)开始时间")
  1902. 结束时间 := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度分布特性的测试与分析(满载)结束时间")
  1903. if len(开始时间) == 0 || len(结束时间) == 0 || 开始时间 == "null" || 结束时间 == "null" {
  1904. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取表单中'温度分布特性的测试与分析(满载)开始/结束时间'失败,请先填写表单时间字段!"})
  1905. return
  1906. }
  1907. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "时间范围: " + 开始时间 + " ~ " + 结束时间})
  1908. DeviceClassList_list := Device.Read_DeviceClassList_List_id_By_Terminal(Task_r.T_class, false)
  1909. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "终端总共:" + lib.To_string(len(DeviceClassList_list)) + " 个,开始检测..."})
  1910. if len(DeviceClassList_list) == 0 {
  1911. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "没有找到终端设备!"})
  1912. return
  1913. }
  1914. // ========== 步骤1:加载所有传感器数据 ==========
  1915. allData := make(map[string]map[string]float32)
  1916. for _, dev := range DeviceClassList_list {
  1917. dataList, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间, 0, 9999)
  1918. if cnt < 10 {
  1919. continue
  1920. }
  1921. timeMap := make(map[string]float32)
  1922. for _, d := range dataList {
  1923. timeMap[d.T_time] = d.T_t
  1924. }
  1925. allData[dev.T_id] = timeMap
  1926. }
  1927. // ========== 步骤2:收集所有时间点并排序 ==========
  1928. timeSet := make(map[string]bool)
  1929. for _, tm := range allData {
  1930. for t := range tm {
  1931. timeSet[t] = true
  1932. }
  1933. }
  1934. allTimes := make([]string, 0, len(timeSet))
  1935. for t := range timeSet {
  1936. allTimes = append(allTimes, t)
  1937. }
  1938. sort.Strings(allTimes)
  1939. if len(allTimes) < 5 {
  1940. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "时间点不足,无法检测"})
  1941. } else {
  1942. // ========== 步骤2.5:从数据库表单读取温度控制范围 ==========
  1943. // 1. 兼容读取最高值/最大值
  1944. 最高值Str := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最高值")
  1945. if 最高值Str == "" {
  1946. 最高值Str = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最大值")
  1947. }
  1948. 温度控制范围最高值 := lib.To_float32(最高值Str)
  1949. // 2. 兼容读取最低值/最小值
  1950. 最低值Str := VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最低值")
  1951. if 最低值Str == "" {
  1952. 最低值Str = VerifyTemplate.Read_VerifyTemplateMapData_T_name(Task_r.T_task_id, Task_r.T_VerifyTemplate_id, "温度控制范围最小值")
  1953. }
  1954. 温度控制范围最低值 := lib.To_float32(最低值Str)
  1955. // 3. 校验读取结果
  1956. if 温度控制范围最高值 <= 0 || 温度控制范围最低值 >= 温度控制范围最高值 {
  1957. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: "获取表单中'温度控制范围最高值/最低值'失败或逻辑无效,请先填写!"})
  1958. return
  1959. }
  1960. // 4. 留出 0.1 的安全余量 (防止浮点数精度导致刚好等于上限被误判)
  1961. upperLimit := 温度控制范围最高值 - 0.1
  1962. lowerLimit := 温度控制范围最低值 + 0.1
  1963. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("温度控制范围: %.2f°C ~ %.2f°C (已含0.1安全余量)", lowerLimit, upperLimit)})
  1964. // ========== 检测参数 ==========
  1965. const DEVIATION_THRESHOLD float32 = 0.8
  1966. const MIN_DURATION = 5
  1967. const MERGE_GAP = 5
  1968. const MIN_RISE_FOR_BULGE float32 = 0.5
  1969. const MAX_MONOTONIC_EXPAND = 120
  1970. const TREND_WINDOW = 20
  1971. const TREND_SLOPE_THRESHOLD float32 = 0.01
  1972. const TREND_MIN_DURATION = 30
  1973. const MONOTONIC_MIN_DURATION = 20
  1974. const MONOTONIC_TOLERANCE = 2
  1975. type BulgeSegment struct {
  1976. Device string
  1977. StartTime string
  1978. EndTime string
  1979. }
  1980. var allBulges []BulgeSegment
  1981. // ========== 步骤3:鼓包检测 (基于原始数据) ==========
  1982. for _, dev := range DeviceClassList_list {
  1983. tm := allData[dev.T_id]
  1984. if tm == nil || len(tm) < 10 {
  1985. continue
  1986. }
  1987. type DataPoint struct {
  1988. Time string
  1989. Temp float32
  1990. Deviation float32
  1991. IsBulge bool
  1992. }
  1993. var points []DataPoint
  1994. var temps []float32
  1995. for _, t := range allTimes {
  1996. if v, ok := tm[t]; ok {
  1997. refAll := medianOfAllDevices(allData, t)
  1998. deviation := v - refAll
  1999. points = append(points, DataPoint{t, v, deviation, deviation > DEVIATION_THRESHOLD})
  2000. temps = append(temps, v)
  2001. }
  2002. }
  2003. n := len(points)
  2004. if n < MIN_DURATION {
  2005. continue
  2006. }
  2007. // 1. 偏差鼓包检测
  2008. i := 0
  2009. for i < n {
  2010. if points[i].IsBulge {
  2011. i++
  2012. continue
  2013. }
  2014. j := i
  2015. for j+1 < n && !points[j+1].IsBulge {
  2016. j++
  2017. }
  2018. leftTrue := i-1 >= 0 && points[i-1].IsBulge
  2019. rightTrue := j+1 < n && points[j+1].IsBulge
  2020. gapLen := j - i + 1
  2021. if leftTrue && rightTrue && gapLen <= MERGE_GAP {
  2022. for k := i; k <= j; k++ {
  2023. points[k].IsBulge = true
  2024. }
  2025. }
  2026. i = j + 1
  2027. }
  2028. type Seg struct{ Start, End int }
  2029. var bulgeSegs []Seg
  2030. start := -1
  2031. for i, p := range points {
  2032. if p.IsBulge && start == -1 {
  2033. start = i
  2034. } else if !p.IsBulge && start != -1 {
  2035. bulgeSegs = append(bulgeSegs, Seg{start, i - 1})
  2036. start = -1
  2037. }
  2038. }
  2039. if start != -1 {
  2040. bulgeSegs = append(bulgeSegs, Seg{start, n - 1})
  2041. }
  2042. // 2. 斜率趋势检测
  2043. trendMask := detectTrends(temps, TREND_WINDOW, TREND_SLOPE_THRESHOLD, TREND_MIN_DURATION)
  2044. var trendSegs []Seg
  2045. start = -1
  2046. for i, v := range trendMask {
  2047. if v && start == -1 {
  2048. start = i
  2049. } else if !v && start != -1 {
  2050. trendSegs = append(trendSegs, Seg{start, i - 1})
  2051. start = -1
  2052. }
  2053. }
  2054. if start != -1 {
  2055. trendSegs = append(trendSegs, Seg{start, n - 1})
  2056. }
  2057. // 3. 单调趋势检测
  2058. monoMask := detectMonotonicTrends(temps, MONOTONIC_MIN_DURATION, MONOTONIC_TOLERANCE)
  2059. var monoSegs []Seg
  2060. start = -1
  2061. for i, v := range monoMask {
  2062. if v && start == -1 {
  2063. start = i
  2064. } else if !v && start != -1 {
  2065. monoSegs = append(monoSegs, Seg{start, i - 1})
  2066. start = -1
  2067. }
  2068. }
  2069. if start != -1 {
  2070. monoSegs = append(monoSegs, Seg{start, n - 1})
  2071. }
  2072. // 合并所有段
  2073. allSegs := append(bulgeSegs, trendSegs...)
  2074. allSegs = append(allSegs, monoSegs...)
  2075. if len(allSegs) == 0 {
  2076. continue
  2077. }
  2078. sort.Slice(allSegs, func(i, j int) bool { return allSegs[i].Start < allSegs[j].Start })
  2079. var merged []Seg
  2080. for _, seg := range allSegs {
  2081. if len(merged) == 0 {
  2082. merged = append(merged, seg)
  2083. } else {
  2084. last := &merged[len(merged)-1]
  2085. if seg.Start <= last.End+MERGE_GAP {
  2086. if seg.End > last.End {
  2087. last.End = seg.End
  2088. }
  2089. } else {
  2090. merged = append(merged, seg)
  2091. }
  2092. }
  2093. }
  2094. for _, seg := range merged {
  2095. if seg.End-seg.Start+1 < MIN_DURATION {
  2096. continue
  2097. }
  2098. peakIdx := seg.Start
  2099. peakVal := points[seg.Start].Temp
  2100. for k := seg.Start + 1; k <= seg.End; k++ {
  2101. if points[k].Temp > peakVal {
  2102. peakVal = points[k].Temp
  2103. peakIdx = k
  2104. }
  2105. }
  2106. startIdx := seg.Start
  2107. steps := 0
  2108. for startIdx > 0 && steps < MAX_MONOTONIC_EXPAND && points[startIdx-1].Temp < points[startIdx].Temp {
  2109. startIdx--
  2110. steps++
  2111. }
  2112. endIdx := peakIdx
  2113. steps = 0
  2114. for endIdx < len(points)-1 && steps < MAX_MONOTONIC_EXPAND && points[endIdx+1].Temp <= points[endIdx].Temp {
  2115. endIdx++
  2116. steps++
  2117. }
  2118. if endIdx-startIdx+1 < MIN_DURATION {
  2119. continue
  2120. }
  2121. maxDev := float32(0)
  2122. for k := startIdx; k <= endIdx; k++ {
  2123. if float32(math.Abs(float64(points[k].Deviation))) > maxDev {
  2124. maxDev = float32(math.Abs(float64(points[k].Deviation)))
  2125. }
  2126. }
  2127. if maxDev < 0.3 {
  2128. continue
  2129. }
  2130. rise := points[peakIdx].Temp - points[startIdx].Temp
  2131. minTemp := points[startIdx].Temp
  2132. for k := startIdx; k <= endIdx; k++ {
  2133. if points[k].Temp < minTemp {
  2134. minTemp = points[k].Temp
  2135. }
  2136. }
  2137. drop := points[startIdx].Temp - minTemp
  2138. if rise >= MIN_RISE_FOR_BULGE || drop >= MIN_RISE_FOR_BULGE {
  2139. allBulges = append(allBulges, BulgeSegment{
  2140. Device: dev.T_id,
  2141. StartTime: points[startIdx].Time,
  2142. EndTime: points[endIdx].Time,
  2143. })
  2144. }
  2145. }
  2146. }
  2147. // 输出鼓包检测结果
  2148. if len(allBulges) > 0 {
  2149. deviceBulges := make(map[string][]BulgeSegment)
  2150. for _, b := range allBulges {
  2151. deviceBulges[b.Device] = append(deviceBulges[b.Device], b)
  2152. }
  2153. for deviceId, bulges := range deviceBulges {
  2154. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf("%s 检测到 %d 个鼓包段", deviceId, len(bulges))})
  2155. }
  2156. }
  2157. // ========== 步骤4:按顺序修复 (先上下限,后鼓包) ==========
  2158. const BLEND = 10
  2159. const TARGET_SMOOTH = 7
  2160. const EPS float32 = 0.03
  2161. const STABLE_N = 4
  2162. const MAX_EXPAND = 30
  2163. for _, dev := range DeviceClassList_list {
  2164. tm := allData[dev.T_id]
  2165. if tm == nil || len(tm) < 10 {
  2166. continue
  2167. }
  2168. var fixTimes []string
  2169. var fixTemps []float32
  2170. var fixRefExcl []float32
  2171. for _, t := range allTimes {
  2172. if v, ok := tm[t]; ok {
  2173. refExcl := leaveOneOutMedian(allData, dev.T_id, t)
  2174. fixTimes = append(fixTimes, t)
  2175. fixTemps = append(fixTemps, v)
  2176. fixRefExcl = append(fixRefExcl, refExcl)
  2177. }
  2178. }
  2179. n := len(fixTimes)
  2180. if n < 10 {
  2181. continue
  2182. }
  2183. offsetWindow := 241
  2184. if n/2 < offsetWindow {
  2185. offsetWindow = n / 2
  2186. }
  2187. if offsetWindow < 11 {
  2188. offsetWindow = 11
  2189. }
  2190. diff := make([]float32, n)
  2191. for i := 0; i < n; i++ {
  2192. diff[i] = fixTemps[i] - fixRefExcl[i]
  2193. }
  2194. offset := medianRolling(diff, offsetWindow)
  2195. target := make([]float32, n)
  2196. for i := 0; i < n; i++ {
  2197. target[i] = fixRefExcl[i] + offset[i]
  2198. }
  2199. targetS := meanRolling(target, TARGET_SMOOTH)
  2200. yFixed := make([]float32, n)
  2201. copy(yFixed, fixTemps)
  2202. // 【第1步】修复超高温段
  2203. highSegs := findAndMergeLimitSegments(fixTemps, upperLimit, true, MERGE_GAP)
  2204. for _, seg := range highSegs {
  2205. s2, e2 := expandByLimitCondition(yFixed, seg.Start, seg.End, upperLimit, MAX_EXPAND, STABLE_N, true)
  2206. yFixed = compressSegment(yFixed, targetS, s2, e2, n, upperLimit, BLEND)
  2207. }
  2208. // 【第2步】修复超低温段
  2209. lowSegs := findAndMergeLimitSegments(fixTemps, lowerLimit, false, MERGE_GAP)
  2210. for _, seg := range lowSegs {
  2211. s2, e2 := expandByLimitCondition(yFixed, seg.Start, seg.End, lowerLimit, MAX_EXPAND, STABLE_N, false)
  2212. // 低温修复技巧:取负值,复用 compressSegment 将其压到 -lowerLimit 之下,然后再取负还原
  2213. yNeg := make([]float32, n)
  2214. targetNeg := make([]float32, n)
  2215. for i := 0; i < n; i++ {
  2216. yNeg[i] = -yFixed[i]
  2217. targetNeg[i] = -targetS[i]
  2218. }
  2219. yNegFixed := compressSegment(yNeg, targetNeg, s2, e2, n, -lowerLimit, BLEND)
  2220. for i := 0; i < n; i++ {
  2221. yFixed[i] = -yNegFixed[i]
  2222. }
  2223. }
  2224. // 【第3步】修复鼓包段
  2225. devBulges := false
  2226. for _, bulge := range allBulges {
  2227. if bulge.Device != dev.T_id {
  2228. continue
  2229. }
  2230. devBulges = true
  2231. s, e := -1, -1
  2232. for i, ft := range fixTimes {
  2233. if ft == bulge.StartTime {
  2234. s = i
  2235. }
  2236. if ft == bulge.EndTime {
  2237. e = i
  2238. }
  2239. }
  2240. if s < 0 || e < 0 || e <= s {
  2241. continue
  2242. }
  2243. residual := make([]float32, n)
  2244. for i := 0; i < n; i++ {
  2245. residual[i] = yFixed[i] - targetS[i]
  2246. }
  2247. s2, e2 := expandByResidual(residual, s, e, EPS, STABLE_N, MAX_EXPAND)
  2248. yFixed = compressSegment(yFixed, targetS, s2, e2, n, upperLimit, BLEND)
  2249. }
  2250. // 【第4步】兜底裁剪:确保绝对不超限
  2251. for i := 0; i < n; i++ {
  2252. if yFixed[i] > upperLimit {
  2253. yFixed[i] = upperLimit
  2254. } else if yFixed[i] < lowerLimit {
  2255. yFixed[i] = lowerLimit
  2256. }
  2257. }
  2258. // 写入数据库
  2259. if devBulges || len(highSegs) > 0 || len(lowSegs) > 0 {
  2260. Task.DeleteTaskDataByTimeRange(Task_r.T_task_id, dev.T_sn, dev.T_id, fixTimes[0], fixTimes[n-1])
  2261. var valueStrings []string
  2262. for i := 0; i < n; i++ {
  2263. valueStrings = append(valueStrings, fmt.Sprintf("('%s','%s',%.2f,0,'%s')", dev.T_sn, dev.T_id, yFixed[i], fixTimes[i]))
  2264. }
  2265. err = Task.Batch_Adds_TaskData(T_task_id, valueStrings)
  2266. if err != nil {
  2267. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 2, Msg: dev.T_id + " 数据修复失败: " + err.Error()})
  2268. } else {
  2269. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: fmt.Sprintf(" %s 修复完成 (共%d个数据点)", dev.T_id, n)})
  2270. }
  2271. }
  2272. }
  2273. }
  2274. // ========== 步骤5:缺数据检测 (保持不变) ==========
  2275. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 0, Msg: "开始缺数据检测..."})
  2276. totalMissing := 0
  2277. for _, dev := range DeviceClassList_list {
  2278. dataList, cnt := Task.Read_TaskData_ById_List_AES(Task_r.T_task_id, dev.T_sn, dev.T_id, 开始时间, 结束时间, 0, 9999)
  2279. if cnt < 2 {
  2280. continue
  2281. }
  2282. missingCount := 0
  2283. for i := 0; i < len(dataList)-1; i++ {
  2284. ct, _ := time.Parse("2006-01-02 15:04", dataList[i].T_time)
  2285. nt, _ := time.Parse("2006-01-02 15:04", dataList[i+1].T_time)
  2286. interval := int(nt.Unix() - ct.Unix())
  2287. if interval == 120 {
  2288. t := ct.Add(60 * time.Second).Format("2006-01-02 15:04")
  2289. ttt := (dataList[i].T_t + dataList[i+1].T_t) / 2
  2290. trht := (dataList[i].T_rh + dataList[i+1].T_rh) / 2
  2291. Task.InsertTaskData(Task_r.T_task_id, Task.TaskData_{
  2292. T_sn: dataList[i].T_sn,
  2293. T_id: dataList[i].T_id,
  2294. T_t: ttt,
  2295. T_rh: trht,
  2296. T_time: t,
  2297. })
  2298. missingCount++
  2299. } else if interval > 120 {
  2300. device := Device.DeviceClassList{
  2301. T_class: dev.T_class,
  2302. T_id: dev.T_id,
  2303. T_sn: dev.T_sn,
  2304. T_remark: dev.T_remark,
  2305. }
  2306. c.SetAdjacentDeviceAVGTaskData(T_task_id, device, 开始时间, 结束时间)
  2307. missingCount++
  2308. break
  2309. }
  2310. }
  2311. if missingCount > 0 {
  2312. totalMissing += missingCount
  2313. }
  2314. }
  2315. lib.SseWriteJSON(c.Ctx.ResponseWriter, lib.JSONSSE{State: 1, Msg: fmt.Sprintf("完成!缺数据补全: %d处", totalMissing)})
  2316. c.Ctx.ResponseWriter.WriteHeader(http.StatusOK)
  2317. }
  2318. // ==================== 新增辅助函数:上下限修复专用 ====================
  2319. // findAndMergeLimitSegments 查找并合并超出限值的连续段
  2320. func findAndMergeLimitSegments(temps []float32, limit float32, isHigh bool, mergeGap int) []struct{ Start, End int } {
  2321. n := len(temps)
  2322. mask := make([]bool, n)
  2323. for i := 0; i < n; i++ {
  2324. if isHigh && temps[i] > limit {
  2325. mask[i] = true
  2326. } else if !isHigh && temps[i] < limit {
  2327. mask[i] = true
  2328. }
  2329. }
  2330. var segs []struct{ Start, End int }
  2331. start := -1
  2332. for i := 0; i < n; i++ {
  2333. if mask[i] && start == -1 {
  2334. start = i
  2335. } else if !mask[i] && start != -1 {
  2336. segs = append(segs, struct{ Start, End int }{start, i - 1})
  2337. start = -1
  2338. }
  2339. }
  2340. if start != -1 {
  2341. segs = append(segs, struct{ Start, End int }{start, n - 1})
  2342. }
  2343. if len(segs) == 0 {
  2344. return segs
  2345. }
  2346. // 合并间隔 <= mergeGap 的段
  2347. var merged []struct{ Start, End int }
  2348. merged = append(merged, segs[0])
  2349. for i := 1; i < len(segs); i++ {
  2350. last := &merged[len(merged)-1]
  2351. if segs[i].Start <= last.End+mergeGap {
  2352. if segs[i].End > last.End {
  2353. last.End = segs[i].End
  2354. }
  2355. } else {
  2356. merged = append(merged, segs[i])
  2357. }
  2358. }
  2359. return merged
  2360. }
  2361. // expandByLimitCondition 基于限值条件向外扩展区间
  2362. func expandByLimitCondition(yFixed []float32, s, e int, limit float32, maxExpand, stableN int, isHigh bool) (int, int) {
  2363. n := len(yFixed)
  2364. s2, e2 := s, e
  2365. // 向左扩
  2366. steps := 0
  2367. for s2 > 0 && steps < maxExpand {
  2368. l := s2 - stableN
  2369. if l < 0 {
  2370. l = 0
  2371. }
  2372. window := yFixed[l:s2]
  2373. allOk := len(window) == stableN
  2374. if allOk {
  2375. for _, v := range window {
  2376. if isHigh && v > limit+0.01 {
  2377. allOk = false
  2378. break
  2379. } else if !isHigh && v < limit-0.01 {
  2380. allOk = false
  2381. break
  2382. }
  2383. }
  2384. }
  2385. if allOk {
  2386. break
  2387. }
  2388. s2--
  2389. steps++
  2390. }
  2391. // 向右扩
  2392. steps = 0
  2393. for e2 < n-1 && steps < maxExpand {
  2394. r := e2 + 1 + stableN
  2395. if r > n {
  2396. r = n
  2397. }
  2398. window := yFixed[e2+1 : r]
  2399. allOk := len(window) == stableN
  2400. if allOk {
  2401. for _, v := range window {
  2402. if isHigh && v > limit+0.01 {
  2403. allOk = false
  2404. break
  2405. } else if !isHigh && v < limit-0.01 {
  2406. allOk = false
  2407. break
  2408. }
  2409. }
  2410. }
  2411. if allOk {
  2412. break
  2413. }
  2414. e2++
  2415. steps++
  2416. }
  2417. return s2, e2
  2418. }
  2419. // ==================== 以下保留您原有的辅助函数 ====================
  2420. // detectTrends 斜率趋势检测(线性回归斜率绝对值 > 阈值,持续时间 >= minDuration)
  2421. func detectTrends(series []float32, window int, slopeThresh float32, minDuration int) []bool {
  2422. n := len(series)
  2423. mask := make([]bool, n)
  2424. if n < window {
  2425. return mask
  2426. }
  2427. slopes := make([]float32, n)
  2428. for i := 0; i <= n-window; i++ {
  2429. // 计算线性回归斜率
  2430. x := make([]float64, window)
  2431. y := make([]float64, window)
  2432. for k := 0; k < window; k++ {
  2433. x[k] = float64(k)
  2434. y[k] = float64(series[i+k])
  2435. }
  2436. // 最小二乘法
  2437. sumX, sumY, sumXY, sumX2 := 0.0, 0.0, 0.0, 0.0
  2438. for k := 0; k < window; k++ {
  2439. sumX += x[k]
  2440. sumY += y[k]
  2441. sumXY += x[k] * y[k]
  2442. sumX2 += x[k] * x[k]
  2443. }
  2444. denom := float64(window)*sumX2 - sumX*sumX
  2445. if denom == 0 {
  2446. slopes[i+window/2] = 0
  2447. } else {
  2448. slope := float32((float64(window)*sumXY - sumX*sumY) / denom)
  2449. slopes[i+window/2] = slope
  2450. }
  2451. }
  2452. // 强斜率标记
  2453. strong := make([]bool, n)
  2454. for i := 0; i < n; i++ {
  2455. if math.Abs(float64(slopes[i])) > float64(slopeThresh) {
  2456. strong[i] = true
  2457. }
  2458. }
  2459. // 找连续强斜率段(长度 >= minDuration)
  2460. count := 0
  2461. for i := 0; i < n; i++ {
  2462. if strong[i] {
  2463. count++
  2464. } else {
  2465. if count >= minDuration {
  2466. for j := i - count; j < i; j++ {
  2467. mask[j] = true
  2468. }
  2469. }
  2470. count = 0
  2471. }
  2472. }
  2473. if count >= minDuration {
  2474. for j := n - count; j < n; j++ {
  2475. mask[j] = true
  2476. }
  2477. }
  2478. return mask
  2479. }
  2480. // detectMonotonicTrends 单调趋势检测(同向比例 >= 0.8,持续 >= minDuration)
  2481. func detectMonotonicTrends(series []float32, minDuration int, tolerance int) []bool {
  2482. n := len(series)
  2483. mask := make([]bool, n)
  2484. if n < minDuration {
  2485. return mask
  2486. }
  2487. // 计算差分符号
  2488. signs := make([]int, n-1)
  2489. for i := 0; i < n-1; i++ {
  2490. if series[i+1] > series[i] {
  2491. signs[i] = 1
  2492. } else if series[i+1] < series[i] {
  2493. signs[i] = -1
  2494. } else {
  2495. signs[i] = 0
  2496. }
  2497. }
  2498. window := minDuration
  2499. half := window / 2
  2500. for i := 0; i < n; i++ {
  2501. l := i - half
  2502. if l < 0 {
  2503. l = 0
  2504. }
  2505. r := i + half + 1
  2506. if r > n-1 {
  2507. r = n - 1
  2508. }
  2509. if l > r {
  2510. continue
  2511. }
  2512. seg := signs[l:r]
  2513. pos, neg := 0, 0
  2514. for _, s := range seg {
  2515. if s > 0 {
  2516. pos++
  2517. } else if s < 0 {
  2518. neg++
  2519. }
  2520. }
  2521. total := pos + neg
  2522. if total < window/2 {
  2523. continue
  2524. }
  2525. ratio := float32(max(pos, neg)) / float32(total)
  2526. if ratio >= 0.8 {
  2527. mask[i] = true
  2528. }
  2529. }
  2530. // 合并连续段,去除长度小于 minDuration 的
  2531. // 简单做法:先找到段,再过滤
  2532. var segs []struct{ s, e int }
  2533. start := -1
  2534. for i, v := range mask {
  2535. if v && start == -1 {
  2536. start = i
  2537. } else if !v && start != -1 {
  2538. segs = append(segs, struct{ s, e int }{start, i - 1})
  2539. start = -1
  2540. }
  2541. }
  2542. if start != -1 {
  2543. segs = append(segs, struct{ s, e int }{start, n - 1})
  2544. }
  2545. finalMask := make([]bool, n)
  2546. for _, seg := range segs {
  2547. if seg.e-seg.s+1 >= minDuration {
  2548. for i := seg.s; i <= seg.e; i++ {
  2549. finalMask[i] = true
  2550. }
  2551. }
  2552. }
  2553. return finalMask
  2554. }
  2555. // 辅助函数:max
  2556. func max(a, b int) int {
  2557. if a > b {
  2558. return a
  2559. }
  2560. return b
  2561. }
  2562. // medianOfSlice 计算float32切片的中位数
  2563. func medianOfSlice(vals []float32) float32 {
  2564. if len(vals) == 0 {
  2565. return 0
  2566. }
  2567. sorted := make([]float32, len(vals))
  2568. copy(sorted, vals)
  2569. sort.Slice(sorted, func(i, j int) bool { return sorted[i] < sorted[j] })
  2570. n := len(sorted)
  2571. if n%2 == 0 {
  2572. return (sorted[n/2-1] + sorted[n/2]) / 2
  2573. }
  2574. return sorted[n/2]
  2575. }
  2576. // medianOfAllDevices 计算所有设备在某个时间点的中位数
  2577. func medianOfAllDevices(allData map[string]map[string]float32, t string) float32 {
  2578. var vals []float32
  2579. for _, tm := range allData {
  2580. if v, ok := tm[t]; ok {
  2581. vals = append(vals, v)
  2582. }
  2583. }
  2584. return medianOfSlice(vals)
  2585. }
  2586. // leaveOneOutMedian 计算排除当前设备后其他设备的中位数
  2587. // 与c.txt一致:在所有值中找到与当前值最接近的值,排除它,取剩余的中位数
  2588. func leaveOneOutMedian(allData map[string]map[string]float32, deviceId string, t string) float32 {
  2589. var vals []float32
  2590. for _, tm := range allData {
  2591. if v, ok := tm[t]; ok {
  2592. vals = append(vals, v)
  2593. }
  2594. }
  2595. if len(vals) <= 1 {
  2596. return medianOfAllDevices(allData, t)
  2597. }
  2598. // 获取当前设备的值
  2599. currentVal := float32(0)
  2600. if tm, ok := allData[deviceId]; ok {
  2601. if v, ok2 := tm[t]; ok2 {
  2602. currentVal = v
  2603. }
  2604. }
  2605. // 找到与当前值最接近的值的索引,排除它
  2606. closestIdx := 0
  2607. closestDiff := float32(math.MaxFloat32)
  2608. for i, v := range vals {
  2609. diff := float32(math.Abs(float64(v - currentVal)))
  2610. if diff < closestDiff {
  2611. closestDiff = diff
  2612. closestIdx = i
  2613. }
  2614. }
  2615. // 排除最接近的值
  2616. vv := append(vals[:closestIdx], vals[closestIdx+1:]...)
  2617. if len(vv) == 0 {
  2618. return medianOfAllDevices(allData, t)
  2619. }
  2620. return medianOfSlice(vv)
  2621. }
  2622. // medianRolling 滚动中位数
  2623. func medianRolling(arr []float32, window int) []float32 {
  2624. n := len(arr)
  2625. result := make([]float32, n)
  2626. half := window / 2
  2627. minPeriods := window / 5
  2628. if minPeriods < 5 {
  2629. minPeriods = 5
  2630. }
  2631. for i := 0; i < n; i++ {
  2632. start := i - half
  2633. if start < 0 {
  2634. start = 0
  2635. }
  2636. end := i + half + 1
  2637. if end > n {
  2638. end = n
  2639. }
  2640. if end-start < minPeriods {
  2641. if n >= minPeriods {
  2642. // 边界用可用数据
  2643. start = 0
  2644. end = n
  2645. } else {
  2646. result[i] = arr[i]
  2647. continue
  2648. }
  2649. }
  2650. vals := arr[start:end]
  2651. result[i] = medianOfSlice(vals)
  2652. }
  2653. return result
  2654. }
  2655. // meanRolling 滚动均值
  2656. func meanRolling(arr []float32, window int) []float32 {
  2657. n := len(arr)
  2658. result := make([]float32, n)
  2659. half := window / 2
  2660. minPeriods := window / 3
  2661. if minPeriods < 3 {
  2662. minPeriods = 3
  2663. }
  2664. for i := 0; i < n; i++ {
  2665. start := i - half
  2666. if start < 0 {
  2667. start = 0
  2668. }
  2669. end := i + half + 1
  2670. if end > n {
  2671. end = n
  2672. }
  2673. if end-start < minPeriods {
  2674. start = 0
  2675. end = n
  2676. }
  2677. sum := float32(0)
  2678. count := 0
  2679. for j := start; j < end; j++ {
  2680. sum += arr[j]
  2681. count++
  2682. }
  2683. if count > 0 {
  2684. result[i] = sum / float32(count)
  2685. }
  2686. }
  2687. return result
  2688. }
  2689. // smoothW 生成平滑混合权重
  2690. // w[s:e] = 1.0, 前后blend点线性过渡到0
  2691. func smoothW(n, s, e, blend int) []float32 {
  2692. w := make([]float32, n)
  2693. if e <= s {
  2694. return w
  2695. }
  2696. // 核心区间 = 1.0
  2697. for i := s; i <= e; i++ {
  2698. w[i] = 1.0
  2699. }
  2700. // 左侧过渡
  2701. ls := s - blend
  2702. if ls < 0 {
  2703. ls = 0
  2704. }
  2705. if ls < s {
  2706. length := s - ls + 1 // length >= 2
  2707. for i := 0; i < length; i++ {
  2708. w[ls+i] = float32(i) / float32(length-1)
  2709. }
  2710. }
  2711. // 右侧过渡
  2712. re := e + blend
  2713. if re >= n {
  2714. re = n - 1
  2715. }
  2716. if e < re {
  2717. length := re - e + 1 // length >= 2
  2718. for i := 0; i < length; i++ {
  2719. w[e+i] = float32(length-1-i) / float32(length-1)
  2720. }
  2721. }
  2722. return w
  2723. }
  2724. // expandByResidual 以residual为依据扩展区间
  2725. // 向两侧扩展直到 residual<=eps 连续stable_n个点
  2726. func expandByResidual(residual []float32, s, e int, eps float32, stableN, maxExpand int) (int, int) {
  2727. n := len(residual)
  2728. s2, e2 := s, e
  2729. // 向左扩
  2730. steps := 0
  2731. for s2 > 0 && steps < maxExpand {
  2732. l := s2 - stableN
  2733. if l < 0 {
  2734. l = 0
  2735. }
  2736. window := residual[l:s2]
  2737. allOk := len(window) == stableN
  2738. if allOk {
  2739. for _, v := range window {
  2740. if v > eps {
  2741. allOk = false
  2742. break
  2743. }
  2744. }
  2745. }
  2746. if allOk {
  2747. break
  2748. }
  2749. s2--
  2750. steps++
  2751. }
  2752. // 向右扩
  2753. steps = 0
  2754. for e2 < n-1 && steps < maxExpand {
  2755. r := e2 + 1 + stableN
  2756. if r > n {
  2757. r = n
  2758. }
  2759. window := residual[e2+1 : r]
  2760. allOk := len(window) == stableN
  2761. if allOk {
  2762. for _, v := range window {
  2763. if v > eps {
  2764. allOk = false
  2765. break
  2766. }
  2767. }
  2768. }
  2769. if allOk {
  2770. break
  2771. }
  2772. e2++
  2773. steps++
  2774. }
  2775. return s2, e2
  2776. }
  2777. // compressSegment 保形压缩(c.txt算法)
  2778. // 把[s,e](含超限外扩)整体压到 upperLimit 之下
  2779. // 1) 基准峰(target_s)若超限,先按形状等比压矮(保留尖峰,不削平)
  2780. // 2) 鼓包偏差(dev)再按剩余余量等比压缩
  2781. // 数学保证:区间内 inner <= upperLimit;blend 为凸组合,不超限
  2782. func compressSegment(yFixed, targetS []float32, s, e, n int, upperLimit float32, blend int) []float32 {
  2783. // 1. 超限外扩:肩膀上所有 >上限 的点都包进区间
  2784. guard := 0
  2785. for s > 0 && guard < 500 && yFixed[s-1] > upperLimit {
  2786. s--
  2787. guard++
  2788. }
  2789. guard = 0
  2790. for e < n-1 && guard < 500 && yFixed[e+1] > upperLimit {
  2791. e++
  2792. guard++
  2793. }
  2794. w := smoothW(n, s, e, blend)
  2795. // 2. 基准峰保形压缩
  2796. baseMin := float32(math.MaxFloat32)
  2797. for i := s; i <= e; i++ {
  2798. if targetS[i] < baseMin {
  2799. baseMin = targetS[i]
  2800. }
  2801. }
  2802. // 【关键修复】baseDev 必须是全局的,模仿 Python 的 target_s - base_min
  2803. baseDev := make([]float32, n)
  2804. maxBaseDev := float32(0)
  2805. for i := 0; i < n; i++ {
  2806. baseDev[i] = targetS[i] - baseMin
  2807. // 仅统计区间内的最大偏差用于计算缩放比例
  2808. if i >= s && i <= e {
  2809. if baseDev[i] > maxBaseDev {
  2810. maxBaseDev = baseDev[i]
  2811. }
  2812. }
  2813. }
  2814. allowBase := upperLimit - baseMin
  2815. if allowBase < 0 {
  2816. allowBase = 0
  2817. }
  2818. scaleB := float32(1.0)
  2819. if maxBaseDev > 0 {
  2820. scaleB = allowBase / maxBaseDev
  2821. if scaleB > 1 {
  2822. scaleB = 1
  2823. }
  2824. }
  2825. targetC := make([]float32, n)
  2826. for i := 0; i < n; i++ {
  2827. targetC[i] = baseMin + baseDev[i]*scaleB
  2828. }
  2829. // 3. 鼓包偏差保形压缩
  2830. // 【关键修复】dev 必须是全局的,模仿 Python 的 y_fixed - target_s
  2831. dev := make([]float32, n)
  2832. maxDev := float32(0)
  2833. for i := 0; i < n; i++ {
  2834. dev[i] = yFixed[i] - targetS[i]
  2835. // 仅统计区间内的最大偏差
  2836. if i >= s && i <= e {
  2837. if dev[i] > maxDev {
  2838. maxDev = dev[i]
  2839. }
  2840. }
  2841. }
  2842. maxTargetC := float32(0)
  2843. for i := s; i <= e; i++ {
  2844. if targetC[i] > maxTargetC {
  2845. maxTargetC = targetC[i]
  2846. }
  2847. }
  2848. headroom := upperLimit - maxTargetC
  2849. if headroom < 0 {
  2850. headroom = 0
  2851. }
  2852. scale := float32(1.0)
  2853. if maxDev > 0 {
  2854. scale = headroom / maxDev
  2855. if scale > 1 {
  2856. scale = 1
  2857. }
  2858. }
  2859. inner := make([]float32, n)
  2860. for i := 0; i < n; i++ {
  2861. inner[i] = targetC[i] + dev[i]*scale
  2862. }
  2863. // 4. 混合
  2864. result := make([]float32, n)
  2865. for i := 0; i < n; i++ {
  2866. result[i] = (1-w[i])*yFixed[i] + w[i]*inner[i]
  2867. }
  2868. return result
  2869. }