Files
dougal-software/etc/qc/default/definitions.yaml

279 lines
13 KiB
YAML

# QC definition file
-
name: "Missing shots"
iterate: "sequences"
labels: [ "QC" ]
id: missing_shots
check: |
const sequence = currentItem;
let results;
if (sequence.missing_shots) {
results = {
shots: {}
}
const missing_shots = missingShotpoints.filter(i => !i.ntba);
for (const shot of missing_shots) {
results.shots[shot.point] = { remarks: "Missed shot", labels: [ "QC", "QCAcq" ] };
}
} else {
results = true;
}
results;
-
name: "Gun QC"
disabled: false
labels: [ "QC", "QCGuns" ]
children:
-
name: "Sequences without gun data"
iterate: "sequences"
id: seq_no_gun_data
check: |
shotpoints.some(i => i.meta?.raw?.smsrc) || "Sequence has no gun data"
-
name: "Missing gun data"
id: missing_gun_data
ignoreAllFailed: true
check: |
!!currentItem._("raw_meta.smsrc.guns")
? true
: "Missing gun data"
-
name: "No fire"
id: no_fire
check: |
// const currentShot = currentItem;
// const gunData = currentItem._("raw_meta.smsrc");
// (gunData && gunData.guns && gunData.guns.length != gunData.num_active)
// ? `Source ${gunData.src_number}: No fire (${gunData.guns.length - gunData.num_active} guns)`
// : true;
// Disabled due to changes in Smartsource software. It now returns all guns on every shot, not just active ones.
true
-
name: "Pressure errors"
id: pressure_errors
check: |
const pressure=11;
const gunData = currentItem._("raw_meta.smsrc");
const results = gunData &&
gunData
.guns
.filter(gun => ((gun[2] == gunData.src_number) && (gun[pressure]/parameters.gunPressureNominal - 1) > parameters.gunPressureToleranceRatio))
.map(gun =>
`source ${gun[2]}, string ${gun[0]}, gun ${gun[1]}, pressure: ${gun[pressure]} / ${parameters.gunPressureNominal} = ${(Math.abs(gun[pressure]/parameters.gunPressureNominal - 1)*100).toFixed(2)}% > ${(parameters.gunPressureToleranceRatio*100).toFixed(2)}%`
).join(" \n");
results && results.length
? results
: true
-
name: "Single gun / cluster"
children:
-
name: "Source depth"
id: source_depth
check: |
const currentShot = currentItem;
let _result_;
_depth=10;
const gunData = currentShot._("raw_meta.smsrc.guns");
if (!gunData) {
// We check for missing data elsewhere, so don't fail this test
_result_ = true
} else if (gunData.every(gun => Math.abs(gun[_depth]-parameters.gunDepth) <= parameters.gunDepthTolerance)) {
_result_ = true;
} else {
const bad_guns = gunData.filter(gun => Math.abs(gun[_depth]-parameters.gunDepth) > parameters.gunDepthTolerance).map(gun => {
return `source ${gun[2]}, string ${gun[0]}, gun ${gun[1]}, depth: ${gun[10]}`;
});
_result_ = `Depth error: ${bad_guns.join("; ")}`;
}
_result_
-
name: "Synchronisation (error)"
id: sync_error
check: |
const currentShot = currentItem;
const gunData = currentShot._("raw_meta.smsrc");
let result = [];
if (gunData && gunData.num_nofire == 0) {
// These are the indices into the gun array for the different
// values of interest.
const subarray = 0;
const aimpoint = 7;
const firetime = 8;
// We only care about the source which actually fired (or was supposed to)
const sourceFired = gunData.guns.filter(g => g[2] == gunData.src_number);
// Let us check if the average delta for each string is within spec
let subarrayAverages = [];
sourceFired.forEach(g => {
const idx = g[subarray]-1;
const delta = g[firetime]-g[aimpoint];
if (!subarrayAverages[idx]) {
subarrayAverages[idx] = [];
}
subarrayAverages[idx].push(delta);
});
subarrayAverages = subarrayAverages.map(s => s.reduce( (a, b) => a+b, 0 ) / s.length);
subarrayAverages.forEach((value, idx) => {
if (value > parameters.gunTimingSubarrayAverage) {
result.push(`Average delta error: string ${idx+1}: ${value.toFixed(2)} > ${parameters.gunTimingSubarrayAverage}`);
}
});
// Let us see about individual guns
sourceFired
.filter(gun => Math.abs(gun[firetime]-gun[aimpoint]) > parameters.gunTiming)
.forEach(gun => {
const value = Math.abs(gun[firetime]-gun[aimpoint]);
result.push(`Delta error: source ${gun[2]}, string ${gun[0]}, gun ${gun[1]}: ${value.toFixed(2)} > ${parameters.gunTiming}`);
});
}
if (result.length) {
result.join("; ");
} else {
// Either there were no error or gun data was missing, which we take care of elsewhere
true;
}
-
name: "Synchronisation (warning)"
id: sync_warn
check: |
const currentShot = currentItem;
const gunData = currentShot._("raw_meta.smsrc");
let result = [];
if (gunData && gunData.num_nofire == 0) {
// These are the indices into the gun array for the different
// values of interest.
const subarray = 0;
const aimpoint = 7;
const firetime = 8;
// We only care about the source which actually fired (or was supposed to)
const sourceFired = gunData.guns.filter(g => g[2] == gunData.src_number);
sourceFired
.filter(gun => Math.abs(gun[firetime]-gun[aimpoint]) >= parameters.gunTimingWarning && Math.abs(gun[firetime]-gun[aimpoint]) <= parameters.gunTiming)
.forEach(gun => {
const value = Math.abs(gun[firetime]-gun[aimpoint]);
result.push(`Delta warning: source ${gun[2]}, string ${gun[0]}, gun ${gun[1]}: ${parameters.gunTimingWarning} ≤ ${value.toFixed(2)} ≤ ${parameters.gunTiming}`);
});
}
if (result.length) {
result.join("; ");
} else {
// Either there were no error or gun data was missing, which we take care of elsewhere
true;
}
-
name: "Autofire"
id: autofire
check: |
const currentShot = currentItem;
let _result_;
_autofire=5;
const gunData = currentShot._("raw_meta.smsrc.guns");
if (!gunData) {
// We check for missing data elsewhere, so don't fail this test
_result_ = true;
} else if (gunData.every(gun => gun[_autofire] == false)) {
_result_ = true;
} else {
const bad_guns = gunData.filter(gun => gun[_autofire]).map(gun => {
return `source ${gun[2]}, string ${gun[0]}, gun ${gun[1]}, depth: ${gun[10]}`;
});
_result_ = `Depth error: ${bad_guns.join(";\n")}`;
}
_result_
-
name: "Centre of source preplot deviation (single shots)"
labels: [ "QC", "QCNav" ]
disabled: false
children:
-
name: "Crossline"
id: crossline
check: |
const currentShot = currentItem;
Math.abs(currentShot.error_i) <= parameters.crosslineError
|| `Crossline error (${currentShot.type}): ${currentShot.error_i.toFixed(2)} > ${parameters.crosslineError}`
-
name: "Inline"
id: inline
check: |
const currentShot = currentItem;
Math.abs(currentShot.error_j) <= parameters.inlineError
|| `Inline error (${currentShot.type}): ${currentShot.error_j.toFixed(2)} > ${parameters.inlineError}`
-
name: "Centre of source preplot deviation (moving average)"
labels: [ "QC", "QCNav" ]
children:
-
name: "Crossline"
iterate: "sequences"
parameters: [ "crosslineErrorAverage" ]
id: crossline_average
check: |
const currentSequence = currentItem;
//const i_err = shotpoints.filter(s => s.error_i != null).map(a => a.error_i);
const i_err = shotpoints.map(i =>
(i.errorfinal?.coordinates ?? i.errorraw?.coordinates)[0]
)
.filter(i => !isNaN(i));
if (i_err.length) {
const avg = i_err.reduce( (a, b) => a+b)/i_err.length;
avg <= parameters.crosslineErrorAverage ||
`Average crossline error: ${avg.toFixed(2)} > ${parameters.crosslineErrorAverage}`
} else {
`Sequence ${currentSequence.sequence} has no shots within preplot`
}
-
name: "Inline"
iterate: "sequences"
parameters: [ "inlineErrorRunningAverageShots" ]
id: inline_average
check: |
const currentSequence = currentItem;
const n = parameters.inlineErrorRunningAverageShots; // For brevity
const results = shotpoints.slice(n/2, -n/2).map( (shot, index) => {
const shots = shotpoints.slice(index, index+n).map(i =>
(i.errorfinal?.coordinates ?? i.errorraw?.coordinates)[1]
).filter(i => i !== null);
if (!shots.length) {
// We are outside the preplot
// Nothing to see here, move along
return true;
}
const mean = shots.reduce( (a, b) => a+b ) / shots.length;
return Math.abs(mean) <= parameters.inlineErrorRunningAverageValue || [
shot.point,
{
remarks: `Running average inline error: ${mean.toFixed(2)} > ${parameters.inlineErrorRunningAverageValue}`,
labels: [ "QC", "QCNav" ]
}
]
}).filter(i => i !== true);
results.length == 0 || results.join("\n");
results.length == 0 || {
remarks: "Sequence exceeds inline error running average limit",
shots: Object.fromEntries(results)
}