Merge branch '127-sol-eol-events-not-being-inserted-in-the-log-automatically' into 'devel'

Resolve "SOL / EOL events not being inserted in the log automatically"

Closes #127

See merge request wgp/dougal/software!43
This commit is contained in:
D. Berge
2023-09-29 14:17:46 +00:00
2 changed files with 69 additions and 45 deletions

View File

@@ -1,23 +1,24 @@
const { schema2pid } = require('../../lib/db/connection'); const { schema2pid } = require('../../lib/db/connection');
const { event } = require('../../lib/db'); const { event } = require('../../lib/db');
const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);
class DetectSOLEOL { class DetectSOLEOL {
/* Data may come much faster than we can process it, so we put it /* Data may come much faster than we can process it, so we put it
* in a queue and process it at our own pace. * in a queue and process it at our own pace.
* *
* The run() method fills the queue with the necessary data and then * The run() method fills the queue with the necessary data and then
* calls processQueue(). * calls processQueue().
* *
* The processQueue() method looks takes the first two elements in * The processQueue() method looks takes the first two elements in
* the queue and processes them if they are not already being taken * the queue and processes them if they are not already being taken
* care of by a previous processQueue() call this will happen when * care of by a previous processQueue() call this will happen when
* data is coming in faster than it can be processed. * data is coming in faster than it can be processed.
* *
* If the processQueue() call is the first to see the two bottommost * If the processQueue() call is the first to see the two bottommost
* two elements, it will process them and, when finished, it will set * two elements, it will process them and, when finished, it will set
* the `isPending` flag of the bottommost element to `false`, thus * the `isPending` flag of the bottommost element to `false`, thus
* letting the next call know that it has work to do. * letting the next call know that it has work to do.
* *
* If the queue was empty, run() will set the `isPending` flag of its * If the queue was empty, run() will set the `isPending` flag of its
* first element to a falsy value, thus bootstrapping the process. * first element to a falsy value, thus bootstrapping the process.
*/ */
@@ -26,8 +27,10 @@ class DetectSOLEOL {
queue = []; queue = [];
async processQueue () { async processQueue () {
DEBUG("Queue length", this.queue.length)
while (this.queue.length > 1) { while (this.queue.length > 1) {
if (this.queue[0].isPending) { if (this.queue[0].isPending) {
DEBUG("Queue busy");
setImmediate(() => this.processQueue()); setImmediate(() => this.processQueue());
return; return;
} }
@@ -38,9 +41,15 @@ class DetectSOLEOL {
const sequence = Number(cur._sequence); const sequence = Number(cur._sequence);
try { try {
DEBUG("Sequence", sequence);
// DEBUG("Previous", prev);
// DEBUG("Current", cur);
if (prev.lineName == cur.lineName && prev._sequence == cur._sequence && if (prev.lineName == cur.lineName && prev._sequence == cur._sequence &&
prev.lineStatus != "online" && cur.lineStatus == "online" && sequence) { prev.lineStatus != "online" && cur.lineStatus == "online" && sequence) {
INFO("Transition to ONLINE detected");
// DEBUG(cur);
// DEBUG(prev);
// console.log("TRANSITION TO ONLINE", prev, cur); // console.log("TRANSITION TO ONLINE", prev, cur);
// Check if there are already FSP, FGSP events for this sequence // Check if there are already FSP, FGSP events for this sequence
@@ -63,12 +72,17 @@ class DetectSOLEOL {
} }
// console.log(projectId, payload); // console.log(projectId, payload);
INFO("Posting event", projectId, payload);
await event.post(projectId, payload); await event.post(projectId, payload);
} else { } else {
// A first shot point has been already entered in the log, // A first shot point has been already entered in the log,
// so we have nothing to do here. // so we have nothing to do here.
INFO("FSP already in the log. Doing nothing");
} }
} else if (prev.lineStatus == "online" && cur.lineStatus != "online") { } else if (prev.lineStatus == "online" && cur.lineStatus != "online") {
INFO("Transition to OFFLINE detected");
// DEBUG(cur);
// DEBUG(prev);
// console.log("TRANSITION TO OFFLINE", prev, cur); // console.log("TRANSITION TO OFFLINE", prev, cur);
// Check if there are already LSP, LGSP events for this sequence // Check if there are already LSP, LGSP events for this sequence
@@ -91,10 +105,12 @@ class DetectSOLEOL {
} }
// console.log(projectId, payload); // console.log(projectId, payload);
INFO("Posting event", projectId, payload);
await event.post(projectId, payload); await event.post(projectId, payload);
} else { } else {
// A first shot point has been already entered in the log, // A first shot point has been already entered in the log,
// so we have nothing to do here. // so we have nothing to do here.
INFO("LSP already in the log. Doing nothing");
} }
} }
// Processing of this shot has already been completed. // Processing of this shot has already been completed.

View File

@@ -1,6 +1,6 @@
// FIXME This code is in painful need of refactoring // FIXME This code is in painful need of refactoring
const { DEBUG } = require("DOUGAL_ROOT/debug")(__filename); const { ALERT, ERROR, WARNING, NOTICE, INFO, DEBUG } = require('DOUGAL_ROOT/debug')(__filename);
const { setSurvey, transaction, pool } = require('../connection'); const { setSurvey, transaction, pool } = require('../connection');
let last_tstamp = 0; let last_tstamp = 0;
@@ -70,9 +70,9 @@ async function getNearestOfflinePreplot (candidates) {
if ("latitude" in candidates[0] && "longitude" in candidates[0]) { if ("latitude" in candidates[0] && "longitude" in candidates[0]) {
text = ` text = `
SELECT SELECT
'${c._schema}' AS _schema, '${c.schema}' AS schema,
ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint($1, $2), 4326), ST_SRID(geometry)), geometry) AS distance ST_Distance(ST_Transform(ST_SetSRID(ST_MakePoint($1, $2), 4326), ST_SRID(geometry)), geometry) AS distance
FROM ${c._schema}.preplot_points FROM ${c.schema}.preplot_points
ORDER BY distance ASC ORDER BY distance ASC
LIMIT 1; LIMIT 1;
`; `;
@@ -80,9 +80,9 @@ async function getNearestOfflinePreplot (candidates) {
} else if ("easting" in candidates[0] && "northing" in candidates[0]) { } else if ("easting" in candidates[0] && "northing" in candidates[0]) {
text = ` text = `
SELECT SELECT
'${c._schema}' AS _schema, '${c.schema}' AS schema,
ST_Distance(ST_SetSRID(ST_MakePoint($1, $2), ST_SRID(geometry)), geometry) AS distance ST_Distance(ST_SetSRID(ST_MakePoint($1, $2), ST_SRID(geometry)), geometry) AS distance
FROM ${c._schema}.preplot_points FROM ${c.schema}.preplot_points
ORDER BY distance ASC ORDER BY distance ASC
LIMIT 1; LIMIT 1;
`; `;
@@ -98,13 +98,13 @@ async function getNearestOfflinePreplot (candidates) {
const results = []; const results = [];
for (const qry of queries) { for (const qry of queries) {
const res = await client.query(qry.text, qry.values); const res = await client.query(qry.text, qry.values);
if (res.rows[0] && res.rows[0]._schema) { if (res.rows[0] && res.rows[0].schema) {
results.push(res.rows[0]); results.push(res.rows[0]);
} }
} }
client.release(); client.release();
const _schema = results.sort( (a, b) => a.distance - b.distance).shift()?._schema; const schema = results.sort( (a, b) => a.distance - b.distance).shift()?.schema;
return candidates.find(c => c._schema == _schema); return candidates.find(c => c.schema == schema);
} }
async function saveOnline (dataset, opts = {}) { async function saveOnline (dataset, opts = {}) {
@@ -154,8 +154,8 @@ async function saveOnline (dataset, opts = {}) {
} }
await transaction.commit(client); await transaction.commit(client);
} catch (error) { } catch (error) {
console.error("ONLINE DATA INSERT ERROR"); ERROR("ONLINE DATA INSERT ERROR");
console.error(error); ERROR(error);
await transaction.rollback(client); await transaction.rollback(client);
} finally { } finally {
client.release(); client.release();
@@ -211,6 +211,37 @@ async function saveOffline (navData, opts = {}) {
client.release(); client.release();
} }
async function getCandidates (navData) {
const configs = await getAllProjectConfigs();
// We just get the bits of interest: pattern and schema
const candidates = configs.map(c => {
if (!c?.data?.online?.line || c?.archived === true) {
return null;
}
const p = c.data.online.line.pattern; // For short
const rx = new RegExp(p.regex, p.flags);
const matches = navData.lineName.match(rx);
if (!matches || ((matches.length+1) < p.captures.length)) {
return null;
}
matches.shift(); // Get rid of the full matched text
const obj = Object.assign({}, navData, {schema: c.schema});
p.captures.forEach( (k, i) => {
obj[k] = matches[i];
});
return obj;
}).filter(c => !!c);
DEBUG("Candidates: %j", candidates.map(c => c.schema));
return candidates;
}
async function save (navData, opts = {}) { async function save (navData, opts = {}) {
const hasLatLon = ("latitude" in navData && "longitude" in navData); const hasLatLon = ("latitude" in navData && "longitude" in navData);
@@ -218,44 +249,21 @@ async function save (navData, opts = {}) {
const hasLinePoint = ("lineName" in navData && "point" in navData); const hasLinePoint = ("lineName" in navData && "point" in navData);
if (!(hasLinePoint || hasLatLon || hasEastNorth)) { if (!(hasLinePoint || hasLatLon || hasEastNorth)) {
// This is of no interest to us // This is of no interest to us
console.warning("Ignoring data without useful values", navData); NOTICE("Ignoring data without useful values", navData);
return; return;
} }
// DEBUG("navData", navData);
if (navData.online === true) { if (navData.online === true) {
// So we have a lineName, see which projects match the line pattern. // So we have a lineName, see which projects match the line pattern.
// For this we need to get all the project configs // For this we need to get all the project configs
const configs = await getAllProjectConfigs(); const candidates = await getCandidates(navData);
// We just get the bits of interest: pattern and schema
const candidates = configs.map(c => {
if (!(c && c.online && c.online.line)) {
return null;
}
const p = c.online.line.pattern; // For short
const rx = new RegExp(p.regex, p.flags);
const matches = navData.lineName.match(rx);
if (!matches || ((matches.length+1) < p.captures.length)) {
return null;
}
matches.shift(); // Get rid of the full matched text
const obj = Object.assign({}, navData, {schema: c.schema});
p.captures.forEach( (k, i) => {
obj[k] = matches[i];
});
return obj;
}).filter(c => !!c);
DEBUG("Candidates: %j", candidates);
// console.log("CANDIDATES", candidates);
if (candidates.length == 0) { if (candidates.length == 0) {
// This is probably a test line, so we treat it as offline // This is probably a test line, so we treat it as offline
console.log("No match"); WARNING("No match");
} else { } else {
if (candidates.length == 1) { if (candidates.length == 1) {
// Only one candidate, associate with it // Only one candidate, associate with it
@@ -271,7 +279,7 @@ async function save (navData, opts = {}) {
await saveOnline(candidates.filter(c => c.schema == destinationSchema), opts); await saveOnline(candidates.filter(c => c.schema == destinationSchema), opts);
navData.payload._schema = destinationSchema; navData.payload._schema = destinationSchema;
} else { } else {
console.log("Nowhere to save to"); WARNING("Nowhere to save to");
} }
} }
@@ -289,10 +297,10 @@ async function save (navData, opts = {}) {
if (do_save) { if (do_save) {
const configs = await getAllProjectConfigs(); const configs = await getAllProjectConfigs();
const candidates = configs.map(c => Object.assign({}, navData, {_schema: c.schema})); const candidates = await getCandidates(navData);
const bestCandidate = await getNearestOfflinePreplot(candidates); const bestCandidate = await getNearestOfflinePreplot(candidates);
if (bestCandidate) { if (bestCandidate) {
navData.payload._schema = bestCandidate._schema; navData.payload._schema = bestCandidate.schema;
last_tstamp = now; last_tstamp = now;
} }
} }