# QC definition file - name: "Missing shots" iterate: "sequences" labels: [ "QC" ] id: missing_shots check: | const sequence = currentItem; const sp0 = Math.min(sequence.fsp, sequence.lsp); const sp1 = Math.max(sequence.fsp, sequence.lsp); const missing = preplots.filter(r => r.line == sequence.line && r.point >= sp0 && r.point <= sp1 && !sequence.shots.find(s => s.point == r.point) ); missing.length == 0 || missing.map(r => `Missing shot: ${r.point}`).join("\n") - name: "Gun QC" disabled: false labels: [ "QC", "QCGuns" ] children: - name: "Sequences without gun data" iterate: "sequences" id: seq_no_gun_data check: | const sequence = currentItem; currentItem.has_smsrc_data || "Sequence has no gun data" - name: "Missing gun data" id: missing_gun_data check: | sequences.some(s => s.sequence == currentItem.sequence && s.has_smsrc_data) ? (!!currentItem._("raw_meta.smsrc.guns") || "Missing gun data") : true - name: "No fire" id: no_fire check: | const currentShot = currentItem; const gunData = currentItem._("raw_meta.smsrc"); (gunData && gunData.num_nofire != 0) ? `Source ${gunData.src_number}: No fire (${gunData.num_nofire} guns)` : 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(gunData.manifold/parameters.gunPressureNominal - 1)*100).toFixed(1)}% > ${(parameters.gunPressureToleranceRatio*100).toFixed(1)}%` ); 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 error: 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(1)} > ${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(1)} > ${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 = currentSequence.shots.filter(s => s.error_i != null).map(a => a.error_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(1)} > ${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 = currentSequence.shots.slice(n/2, -n/2).map( (shot, index) => { const shots = currentSequence.shots.slice(index, index+n).map(i => i.error_j).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 || `Running average inline error: shot ${shot.point}, ${mean.toFixed(1)} > ${parameters.inlineErrorRunningAverageValue}` }).filter(i => i !== true); results.length == 0 || results.join("\n");