# 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) }