diff --git a/lib/www/server/events/handlers/report-line-change-time.js b/lib/www/server/events/handlers/report-line-change-time.js index b0d6bfd..e186258 100644 --- a/lib/www/server/events/handlers/report-line-change-time.js +++ b/lib/www/server/events/handlers/report-line-change-time.js @@ -1,6 +1,7 @@ const { schema2pid } = require('../../lib/db/connection'); const { event, project } = require('../../lib/db'); const { withinValidity } = require('../../lib/utils/ranges'); +const unique = require('../../lib/utils/unique'); const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename); class ReportLineChangeTime { @@ -41,6 +42,7 @@ class ReportLineChangeTime { const cur = this.queue.shift(); const next = this.queue[0]; const projectId = cur.pid; + const forward = (cur.old?.labels?.includes("LGSP") || cur.new?.labels?.includes("LGSP")); if (!projectId) { WARNING("No projectID found in event", cur); @@ -52,16 +54,29 @@ class ReportLineChangeTime { } - async function getLineChangeTime (data) { - const lspEvents = await event.list(projectId, {label: "LSP"}); - const lsp = lspEvents.filter(i => i.tstamp < data.tstamp).shift(); - // DEBUG("lspEvents", lspEvents); - DEBUG("lsp", lsp); - // DEBUG("data", data); + async function getLineChangeTime (data, forward = false) { + if (forward) { + const ospEvents = await event.list(projectId, {label: "FGSP"}); + // DEBUG("ospEvents", ospEvents); + const osp = ospEvents.filter(i => i.tstamp > data.tstamp).pop(); + DEBUG("fsp", osp); + // DEBUG("data", data); - if (lsp) { - DEBUG("Δt", data.tstamp - lsp.tstamp); - return data.tstamp - lsp.tstamp; + if (osp) { + DEBUG("lineChangeTime", osp.tstamp - data.tstamp); + return { lineChangeTime: osp.tstamp - data.tstamp, osp }; + } + } else { + const ospEvents = await event.list(projectId, {label: "LGSP"}); + // DEBUG("ospEvents", ospEvents); + const osp = ospEvents.filter(i => i.tstamp < data.tstamp).shift(); + DEBUG("lsp", osp); + // DEBUG("data", data); + + if (osp) { + DEBUG("lineChangeTime", data.tstamp - osp.tstamp); + return { lineChangeTime: data.tstamp - osp.tstamp, osp }; + } } } @@ -84,10 +99,123 @@ class ReportLineChangeTime { return str.trim(); } + // const deleteStaleEvents = async (id) => { + // if (id) { + // DEBUG("Will delete lct events related to record(s)", id); + // + // const jpq = Array.isArray(id) + // ? `$."${this.author}".parents ? (${[ ...new Set(id)].map(i => "@ == "+i).join(" || ")})` + // : `$."${this.author}".parents ? (@ == ${id})`; + // DEBUG("jpq", jpq); + // const staleEvents = await event.list(projectId, {jpq}); + // DEBUG(staleEvents.length ?? 0, "events to delete"); + // for (let staleEvent of staleEvents) { + // DEBUG("Deleting event", staleEvent.id); + // await event.del(projectId, staleEvent.id); + // } + // } + // } + + const deleteStaleEvents = async (seq) => { + if (seq) { + DEBUG("Will delete lct events related to sequence(s)", seq); + + const jpq = `$."${this.author}"`; + + const opts = {jpq}; + + if (Array.isArray(seq)) { + opts.sequences = unique(seq).filter(i => !!i); + } else { + opts.sequence = seq; + } + + const staleEvents = await event.list(projectId, opts); + DEBUG(staleEvents.length ?? 0, "events to delete"); + for (let staleEvent of staleEvents) { + DEBUG(`Deleting event id ${staleEvent.id} (seq = ${staleEvent.sequence}, point = ${staleEvent.point})`); + await event.del(projectId, staleEvent.id); + } + } + } + + const createLineChangeTimeEvents = async (lineChangeTime, data, osp) => { + + const events = []; + const cfg = (await project.configuration.get(projectId)); + const nlcd = cfg?.production?.nominalLineChangeDuration * 60*1000; // m → ms + DEBUG("nlcd", nlcd); + if (nlcd && lineChangeTime > nlcd) { + const excess = lineChangeTime-nlcd; + const excessString = formatInterval(parseInterval(excess)); + DEBUG("excess", excess, excessString); + + // ref: The later of the two events + const ref = forward ? osp : data; + const payload = { + // tstamp: new Date(ref.tstamp-1), + sequence: ref.sequence, + point: ref.point, + remarks: `_Nominal line change duration exceeded by ${excessString}_`, + labels: [ "Nav", "Prod" ], + meta: { + auto: true, + author: this.author, + [this.author]: { + parents: [ + data.id, + osp.id + ], + type: "excess", + value: excess + } + } + } + + events.push(payload); + DEBUG("Created line change duration exceeded event", projectId, payload); + } + + + const lctString = formatInterval(parseInterval(lineChangeTime)); + + // ref: The later of the two events + const ref = forward ? osp : data; + const payload = { + // tstamp: new Date(ref.tstamp-1), + sequence: ref.sequence, + point: ref.point, + remarks: `Line change time: ${lctString}`, + labels: [ "Nav", "Prod" ], + meta: { + auto: true, + author: this.author, + [this.author]: { + parents: [ + data.id, + osp.id + ], + type: "lineChangeTime", + value: lineChangeTime + } + } + }; + + events.push(payload); + DEBUG("Created line change duration event", projectId, payload); + + return events; + } + + const maybePostEvent = async (projectId, payload) => { + DEBUG("Posting event", projectId, payload); + await event.post(projectId, payload); + } try { // DEBUG("Previous", prev); DEBUG("Current", cur); + DEBUG("Forward search", forward); // We have these scenarios to consider: // INSERT: @@ -107,85 +235,35 @@ class ReportLineChangeTime { // `new` will be NULL // Delete previous event from event_log (not event_log_full) - if (cur.operation == "UPDATE" || cur.operation == "DELETE") { - const data = cur.old; + await deleteStaleEvents([cur.old?.sequence, cur.new?.sequence]); - const jpq = `$."${this.author}".parents ? (@ == ${data.id})`; - const staleEvents = await event.list(projectId, {jpq}); - for (let staleEvent of staleEvents) { - DEBUG("Deleting event", staleEvent.id); - await event.del(projectId, staleEvent.id); - } - } - - if (cur.operation == "INSERT" || cur.operation == "UPDATE") { + if (cur.operation == "INSERT") { + // NOTE: UPDATE on the event_log view translates to one UPDATE plus one INSERT + // on event_log_full, so we don't need to worry about UPDATE here. const data = cur.new; + DEBUG("INSERT seen: will add lct events related to ", data.id); if (withinValidity(data.validity)) { + DEBUG("Event within validity period", data.validity, new Date()); data.tstamp = new Date(data.tstamp); - const lineChangeTime = await getLineChangeTime(data); + const { lineChangeTime, osp } = await getLineChangeTime(data, forward); if (lineChangeTime) { - DEBUG("lineChangeTime", lineChangeTime); - const cfg = (await project.configuration.get(projectId)); - const nlcd = cfg?.production?.nominalLineChangeDuration * 60*1000; // m → ms - DEBUG("nlcd", nlcd); - if (nlcd && lineChangeTime > nlcd) { - const excess = lineChangeTime-nlcd; - const excessString = formatInterval(parseInterval(excess)); - DEBUG("excess", excess, excessString); - - const payload = { - tstamp: new Date(data.tstamp-1), - // sequence: data.sequence, - // point: data.point, - remarks: `_Nominal line change duration exceeded by ${excessString}_`, - labels: [ "Nav", "Prod" ], - meta: { - auto: true, - author: this.author, - [this.author]: { - parents: [ - data.id, - // FIXME And also the corresponding LSP event id - ], - value: excess - } - } - } - - DEBUG("Posting event (duration exceeded)", projectId, payload); - await event.post(projectId, payload); + const events = await createLineChangeTimeEvents(lineChangeTime, data, osp); + if (events?.length) { + DEBUG("Deleting other events for sequence", events[0].sequence); + await deleteStaleEvents(events[0].sequence); } - - const lctString = formatInterval(parseInterval(lineChangeTime)); - - const payload = { - tstamp: new Date(data.tstamp-1), - // sequence: data.sequence, - // point: data.point, - remarks: `Line change time: ${lctString}`, - labels: [ "Nav", "Prod" ], - meta: { - auto: true, - author: this.author, - [this.author]: { - parents: [ - data.id, - // FIXME And also the corresponding LSP event id - ], - value: lineChangeTime - } - } - }; - - DEBUG("Posting event", projectId, payload); - await event.post(projectId, payload); + for (let payload of events) { + await maybePostEvent(projectId, payload); + } } + } else { + DEBUG("Event outside of validity range", data.validity, "lct events not inserted"); } } @@ -216,7 +294,8 @@ class ReportLineChangeTime { const n = data.payload.new; const o = data.payload.old; - if (!n?.labels?.includes("FSP") && !o?.labels?.includes("FSP")) { + if (!n?.labels?.includes("FGSP") && !o?.labels?.includes("FGSP") && + !n?.labels?.includes("LGSP") && !o?.labels?.includes("LGSP")) { return; } @@ -231,7 +310,7 @@ class ReportLineChangeTime { ALERT("ReportLineChangeTime queue full at", this.queue.length); } - this.processQueue(); + await this.processQueue(); } }