Files
OpenSquawk/shared/data/learnModules.ts

805 lines
29 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createBaseScenario, formatTemp } from '~~/shared/learn/scenario'
import type { ModuleDef } from '~~/shared/learn/types'
function gradientArt(colors: string[]): string {
const stops = colors
.map((color, idx) => `<stop offset="${Math.round((idx / Math.max(colors.length - 1, 1)) * 100)}%" stop-color="${color}"/>`)
.join('')
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 240"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">${stops}</linearGradient></defs><rect fill="url(#g)" width="400" height="240"/></svg>`
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
}
export const learnModules: ModuleDef[] = [
{
id: 'normalize',
title: 'Normalize · Fundamentals',
subtitle: 'Alphabet, ATIS, METAR & Radio Check',
art: 'https://images.unsplash.com/photo-1488085061387-422e29b40080?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'icao-alphabet',
title: 'ICAO Alphabet & Numbers',
desc: 'Spell letters and digits clearly',
keywords: ['Alphabet', 'Numbers', 'Normalize'],
hints: [
'Spell each letter with its ICAO name (e.g. Delta, Lima, Hotel).',
'Use ATC numbers: tree, fower, fife, niner.'
],
fields: [
{
key: 'letters',
label: 'Letters',
expected: scenario => scenario.callsignNato,
placeholder: 'Delta Lima Hotel',
width: 'xl',
threshold: 0.9
},
{
key: 'digits',
label: 'Numbers',
expected: scenario => scenario.flightNumberWords,
placeholder: 'one two three',
width: 'lg',
threshold: 0.88
}
],
readback: [
{ type: 'text', text: 'Call sign: ' },
{ type: 'field', key: 'letters', width: 'lg' },
{ type: 'text', text: ' · ' },
{ type: 'field', key: 'digits', width: 'md' }
],
defaultFrequency: 'DEL',
phrase: scenario => `${scenario.callsignNato} ${scenario.flightNumberWords}`,
info: scenario => [
`Callsign: ${scenario.callsign}`,
`Radio call: ${scenario.radioCall}`,
`ICAO spelling: ${scenario.callsignNato}`,
`Flight number spoken: ${scenario.flightNumberWords}`
],
generate: createBaseScenario
},
{
id: 'atis',
title: 'Understand the ATIS',
desc: 'Extract the identifier and key data from the ATIS',
keywords: ['ATIS', 'Weather'],
hints: [
'Remember the ATIS identifier as a single letter.',
'Order: runway wind visibility temperature dew point QNH.'
],
fields: [
{
key: 'atis-code',
label: 'ATIS',
expected: scenario => scenario.atisCode,
alternatives: scenario => [
scenario.atisCode,
scenario.atisCode.toLowerCase(),
`Information ${scenario.atisCode}`
],
placeholder: 'Letter',
width: 'xs',
threshold: 0.9
},
{
key: 'atis-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'atis-wind',
label: 'Wind',
expected: scenario => scenario.wind,
alternatives: scenario => [
scenario.wind,
`${scenario.wind}KT`,
scenario.windWords
],
width: 'md'
},
{
key: 'atis-visibility',
label: 'Visibility',
expected: scenario => scenario.atisSummary.visibility,
alternatives: scenario => [scenario.visibility, scenario.visibilityWords],
width: 'sm'
},
{
key: 'atis-temp',
label: 'Temperature',
expected: scenario => scenario.atisSummary.temperature,
alternatives: scenario => [scenario.temperatureWords],
width: 'sm'
},
{
key: 'atis-dew',
label: 'Dew point',
expected: scenario => scenario.atisSummary.dewpoint,
alternatives: scenario => [scenario.dewpointWords],
width: 'sm'
},
{
key: 'atis-qnh',
label: 'QNH',
expected: scenario => scenario.qnh.toString(),
alternatives: scenario => [`QNH ${scenario.qnh}`, scenario.qnhWords],
width: 'sm'
}
],
readback: [
{ type: 'text', text: 'Information ' },
{ type: 'field', key: 'atis-code', width: 'xs' },
{ type: 'text', text: ', runway ' },
{ type: 'field', key: 'atis-runway', width: 'sm' },
{ type: 'text', text: ', wind ' },
{ type: 'field', key: 'atis-wind', width: 'md' },
{ type: 'text', text: ', visibility ' },
{ type: 'field', key: 'atis-visibility', width: 'sm' },
{ type: 'text', text: ', temperature ' },
{ type: 'field', key: 'atis-temp', width: 'sm' },
{ type: 'text', text: ', dew point ' },
{ type: 'field', key: 'atis-dew', width: 'sm' },
{ type: 'text', text: ', QNH ' },
{ type: 'field', key: 'atis-qnh', width: 'sm' }
],
defaultFrequency: 'ATIS',
phrase: scenario => scenario.atisText,
info: () => [
'Note the identifier, runway, wind, visibility, temperature, dew point, and QNH.',
'Visibility: four digits or 9999 for ≥10 km; QNH as a four-digit value.'
],
generate: createBaseScenario
},
{
id: 'metar',
title: 'Decode the METAR',
desc: 'Extract the raw METAR values',
keywords: ['METAR', 'Weather'],
hints: [
'Read the METAR in blocks: wind visibility clouds temperature QNH.',
'The temperature block looks like 18/10; negative values start with M.'
],
fields: [
{
key: 'metar-wind',
label: 'Wind',
expected: scenario => scenario.metarSegments.wind,
alternatives: scenario => [
scenario.metarSegments.wind,
`${scenario.wind.replace('/', '')}KT`,
scenario.wind
],
width: 'md'
},
{
key: 'metar-vis',
label: 'Visibility',
expected: scenario => scenario.metarSegments.visibility,
alternatives: scenario => [scenario.visibility],
placeholder: '9999',
width: 'sm',
inputmode: 'numeric'
},
{
key: 'metar-temp',
label: 'Temp/Dew',
expected: scenario => scenario.metarSegments.temp,
alternatives: scenario => [
`${formatTemp(scenario.temperature)}/${formatTemp(scenario.dewpoint)}`
],
width: 'md'
},
{
key: 'metar-qnh',
label: 'QNH',
expected: scenario => scenario.metarSegments.qnh,
alternatives: scenario => [`Q${scenario.qnh}`, scenario.qnh.toString()],
width: 'sm'
}
],
readback: [
{ type: 'text', text: 'Wind ' },
{ type: 'field', key: 'metar-wind', width: 'md' },
{ type: 'text', text: ', visibility ' },
{ type: 'field', key: 'metar-vis', width: 'sm' },
{ type: 'text', text: ', temperature ' },
{ type: 'field', key: 'metar-temp', width: 'md' },
{ type: 'text', text: ', QNH ' },
{ type: 'field', key: 'metar-qnh', width: 'sm' }
],
defaultFrequency: 'ATIS',
phrase: scenario => scenario.metar,
info: scenario => [
`METAR: ${scenario.metar}`,
`Interpretation: Wind ${scenario.metarSegments.wind}, visibility ${scenario.visibilityWords}, temperature ${scenario.temperature}°C, QNH ${scenario.qnh}`
],
generate: createBaseScenario
},
{
id: 'radio-check',
title: 'Radio Check',
desc: 'Report readability',
keywords: ['Ground', 'Comms'],
hints: [
'Reply with "Readability" followed by the number.',
'Always finish the check with your call sign.'
],
fields: [
{
key: 'rc-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
placeholder: 'Lufthansa one two three',
width: 'lg'
},
{
key: 'rc-readability',
label: 'Readability',
expected: scenario => scenario.readabilityWord,
alternatives: scenario => [scenario.readability.toString(), scenario.readabilityWord],
placeholder: 'five',
width: 'sm'
}
],
readback: [
{ type: 'text', text: 'This is ' },
{ type: 'field', key: 'rc-callsign', width: 'lg' },
{ type: 'text', text: ', readability ' },
{ type: 'field', key: 'rc-readability', width: 'sm' }
],
defaultFrequency: 'GND',
phrase: scenario => `${scenario.airport.name} Ground, ${scenario.radioCall}, radio check on ${scenario.groundFreq}.`,
info: scenario => [
`Frequency: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`,
`Expected response: Readability ${scenario.readability} (${scenario.readabilityWord})`
],
generate: createBaseScenario
}
]
},
{
id: 'arc',
title: 'ARC Decision Tree',
subtitle: 'From clearance call to departure',
art: gradientArt(['#f97316', '#fb923c', '#0f172a']),
lessons: [
{
id: 'clearance-contact',
title: 'Delivery: Initial contact',
desc: 'Contact clearance delivery',
keywords: ['Delivery', 'Clearance'],
hints: [
'Order: unit, call sign, ATIS, destination, stand, request.',
'Say the ATIS as a single letter.'
],
fields: [
{
key: 'cd-atis',
label: 'ATIS',
expected: scenario => scenario.atisCode,
alternatives: scenario => [
scenario.atisCode,
scenario.atisCode.toLowerCase(),
`Information ${scenario.atisCode}`
],
width: 'xs',
threshold: 0.9
},
{
key: 'cd-dest',
label: 'Destination',
expected: scenario => scenario.destination.city,
alternatives: scenario => [scenario.destination.icao, scenario.destination.name],
width: 'md'
},
{
key: 'cd-stand',
label: 'Stand/Gate',
expected: scenario => scenario.stand,
width: 'sm'
}
],
readback: [
{
type: 'text',
text: scenario => `${scenario.airport.city} Delivery, ${scenario.radioCall}, information `
},
{ type: 'field', key: 'cd-atis', width: 'xs' },
{ type: 'text', text: ', IFR to ' },
{ type: 'field', key: 'cd-dest', width: 'md' },
{ type: 'text', text: ', stand ' },
{ type: 'field', key: 'cd-stand', width: 'sm' },
{ type: 'text', text: ', request clearance.' }
],
defaultFrequency: 'DEL',
phrase: scenario => `${scenario.airport.city} Delivery, ${scenario.radioCall}, information ${scenario.atisCode}, IFR to ${scenario.destination.city}, stand ${scenario.stand}, request clearance.`,
info: scenario => [
`ATIS: ${scenario.atisCode}`,
`Destination: ${scenario.destination.city} (${scenario.destination.icao})`,
`Stand/Gate: ${scenario.stand}`
],
generate: createBaseScenario
},
{
id: 'clearance-readback',
title: 'Clearance Readback',
desc: 'Read back the clearance in full',
keywords: ['Delivery', 'Readback'],
hints: [
'Remember the order: destination SID runway altitude squawk.',
'Speak altitude and squawk digits clearly.'
],
fields: [
{
key: 'clr-dest',
label: 'Destination',
expected: scenario => scenario.destination.city,
alternatives: scenario => [scenario.destination.icao, scenario.destination.name],
width: 'md'
},
{
key: 'clr-sid',
label: 'SID',
expected: scenario => scenario.sid,
width: 'lg'
},
{
key: 'clr-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'clr-alt',
label: 'Initial Altitude',
expected: scenario => scenario.altitudes.initialWords,
alternatives: scenario => [scenario.altitudes.initial.toString(), `${scenario.altitudes.initial} feet`],
width: 'md'
},
{
key: 'clr-squawk',
label: 'Squawk',
expected: scenario => scenario.squawkWords,
alternatives: scenario => [scenario.squawk, scenario.squawk.split('').join(' ')],
width: 'md'
}
],
readback: [
{ type: 'text', text: scenario => `${scenario.radioCall} cleared ` },
{ type: 'field', key: 'clr-dest', width: 'md' },
{ type: 'text', text: ' via ' },
{ type: 'field', key: 'clr-sid', width: 'lg' },
{ type: 'text', text: ', runway ' },
{ type: 'field', key: 'clr-runway', width: 'sm' },
{ type: 'text', text: ', climb ' },
{ type: 'field', key: 'clr-alt', width: 'md' },
{ type: 'text', text: ', squawk ' },
{ type: 'field', key: 'clr-squawk', width: 'md' }
],
defaultFrequency: 'DEL',
phrase: scenario => `${scenario.radioCall}, cleared to ${scenario.destination.city} via ${scenario.sid}, runway ${scenario.runway}, climb ${scenario.altitudes.initial} feet, squawk ${scenario.squawk}.`,
info: scenario => [
`SID: ${scenario.sid} (${scenario.transition})`,
`Initial Altitude: ${scenario.altitudes.initial} ft (${scenario.altitudes.initialWords})`,
`Squawk: ${scenario.squawk} (${scenario.squawkWords})`
],
generate: createBaseScenario
},
{
id: 'pushback',
title: 'Push & Start Readback',
desc: 'Acknowledge the pushback clearance',
keywords: ['Ground', 'Pushback'],
hints: [
'Repeat the runway direction and QNH, call sign at the end.',
'QNH may be just the number or "QNH xxxx".'
],
fields: [
{
key: 'push-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'push-qnh',
label: 'QNH',
expected: scenario => scenario.qnh.toString(),
alternatives: scenario => [`QNH ${scenario.qnh}`, scenario.qnhWords],
width: 'sm'
},
{
key: 'push-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Push and start approved, facing runway ' },
{ type: 'field', key: 'push-runway', width: 'sm' },
{ type: 'text', text: ', QNH ' },
{ type: 'field', key: 'push-qnh', width: 'sm' },
{ type: 'text', text: ', ' },
{ type: 'field', key: 'push-callsign', width: 'lg' }
],
defaultFrequency: 'GND',
phrase: scenario => `${scenario.radioCall}, push and start approved, facing runway ${scenario.runway}. QNH ${scenario.qnh}.`,
info: scenario => [
`Stand/Gate: ${scenario.stand}`,
`Ground: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`
],
generate: createBaseScenario
},
{
id: 'taxi',
title: 'Taxi Readback',
desc: 'Read back the taxi clearance with hold short',
keywords: ['Ground', 'Taxi'],
hints: [
'Repeat the route exactly as received, including the hold short.',
'Finish with the call sign.'
],
fields: [
{
key: 'taxi-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'taxi-route',
label: 'Route',
expected: scenario => scenario.taxiRoute,
alternatives: scenario => [scenario.taxiRoute, `via ${scenario.taxiRoute}`],
width: 'lg'
},
{
key: 'taxi-hold',
label: 'Hold Short',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'taxi-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Taxi to runway ' },
{ type: 'field', key: 'taxi-runway', width: 'sm' },
{ type: 'text', text: ' via ' },
{ type: 'field', key: 'taxi-route', width: 'lg' },
{ type: 'text', text: ', holding short runway ' },
{ type: 'field', key: 'taxi-hold', width: 'sm' },
{ type: 'text', text: ', ' },
{ type: 'field', key: 'taxi-callsign', width: 'lg' }
],
defaultFrequency: 'GND',
phrase: scenario => `${scenario.radioCall}, taxi to runway ${scenario.runway} via ${scenario.taxiRoute}, hold short runway ${scenario.runway}.`,
info: scenario => [
`Taxi route: ${scenario.taxiRoute}`,
`Hold short: ${scenario.runway}`
],
generate: createBaseScenario
},
{
id: 'lineup',
title: 'Line-up Clearance',
desc: 'Acknowledge line up and wait',
keywords: ['Tower', 'Line Up'],
hints: [
'Repeat the runway, then say "line up and wait".',
'Place the call sign at the end.'
],
fields: [
{
key: 'lineup-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'lineup-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Runway ' },
{ type: 'field', key: 'lineup-runway', width: 'sm' },
{ type: 'text', text: ', line up and wait, ' },
{ type: 'field', key: 'lineup-callsign', width: 'lg' }
],
defaultFrequency: 'TWR',
phrase: scenario => `${scenario.radioCall}, line up and wait runway ${scenario.runway}.`,
info: scenario => [
`Tower: ${scenario.towerFreq} (${scenario.frequencyWords.TWR})`,
`Line-up: runway ${scenario.runway}`
],
generate: createBaseScenario
},
{
id: 'takeoff',
title: 'Takeoff Clearance',
desc: 'Acknowledge the takeoff clearance',
keywords: ['Tower', 'Departure'],
hints: [
'Order: runway cleared for takeoff call sign.',
'Wind information can be omitted if it was not given.'
],
fields: [
{
key: 'tko-runway',
label: 'Runway',
expected: scenario => scenario.runway,
alternatives: scenario => [scenario.runway.replace(/^0/, ''), scenario.runwayWords],
width: 'sm'
},
{
key: 'tko-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Runway ' },
{ type: 'field', key: 'tko-runway', width: 'sm' },
{ type: 'text', text: ', cleared for takeoff, ' },
{ type: 'field', key: 'tko-callsign', width: 'lg' }
],
defaultFrequency: 'TWR',
phrase: scenario => `${scenario.radioCall}, wind ${scenario.windWords}, runway ${scenario.runway}, cleared for takeoff.`,
info: scenario => [
`Tower: ${scenario.towerFreq} (${scenario.frequencyWords.TWR})`,
`Wind: ${scenario.wind} (${scenario.windWords})`
],
generate: createBaseScenario
},
{
id: 'departure-handoff',
title: 'Departure Handoff',
desc: 'Switch to departure',
keywords: ['Departure', 'Handoff'],
hints: [
'Repeat the frequency exactly, either as digits or spoken form.',
'Append the call sign at the end.'
],
fields: [
{
key: 'dep-freq',
label: 'Frequenz',
expected: scenario => scenario.departureFreq,
alternatives: scenario => [
scenario.departureFreq,
scenario.departureFreq.replace('.', ''),
scenario.frequencyWords.DEP
],
width: 'md'
},
{
key: 'dep-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Contact departure ' },
{ type: 'field', key: 'dep-freq', width: 'md' },
{ type: 'text', text: ', ' },
{ type: 'field', key: 'dep-callsign', width: 'lg' }
],
defaultFrequency: 'DEP',
phrase: scenario => `${scenario.radioCall}, contact departure ${scenario.departureFreq}.`,
info: scenario => [
`Departure: ${scenario.departureFreq} (${scenario.frequencyWords.DEP})`,
`Tower handoff after departure.`
],
generate: createBaseScenario
},
{
id: 'departure-checkin',
title: 'Departure Check-in',
desc: 'Initial call to departure',
keywords: ['Departure', 'Check-in'],
hints: [
'Address the unit first, then state the call sign.',
'Repeat the altitude and SID exactly as received.'
],
fields: [
{
key: 'depcheck-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
},
{
key: 'depcheck-alt',
label: 'Altitude',
expected: scenario => scenario.altitudes.initialWords,
alternatives: scenario => [
scenario.altitudes.initialWords,
scenario.altitudes.initial.toString(),
`${scenario.altitudes.initial} feet`
],
width: 'md'
},
{
key: 'depcheck-sid',
label: 'SID',
expected: scenario => scenario.sid,
width: 'lg'
}
],
readback: [
{ type: 'text', text: scenario => `${scenario.airport.city} Departure, ` },
{ type: 'field', key: 'depcheck-callsign', width: 'lg' },
{ type: 'text', text: ', passing ' },
{ type: 'field', key: 'depcheck-alt', width: 'md' },
{ type: 'text', text: ', on SID ' },
{ type: 'field', key: 'depcheck-sid', width: 'lg' }
],
defaultFrequency: 'DEP',
phrase: scenario => `${scenario.airport.city} Departure, ${scenario.radioCall}, passing ${scenario.altitudes.initialWords}, on SID ${scenario.sid}.`,
info: scenario => [
`Initial Altitude: ${scenario.altitudes.initial} ft (${scenario.altitudes.initialWords})`,
`SID: ${scenario.sid}`
],
generate: createBaseScenario
},
{
id: 'climb-instruction',
title: 'Climb & Direct',
desc: 'Read back the climb instruction in full',
keywords: ['Departure', 'Climb'],
hints: [
'Start with "Climb", then the altitude and any direct clearance.',
'Repeat the call sign at the end.'
],
fields: [
{
key: 'climb-alt',
label: 'Altitude',
expected: scenario => scenario.altitudes.climbWords,
alternatives: scenario => [
scenario.altitudes.climbWords,
scenario.altitudes.climb.toString(),
`${scenario.altitudes.climb} feet`
],
width: 'md'
},
{
key: 'climb-direct',
label: 'Direct',
expected: scenario => scenario.transition,
width: 'md'
},
{
key: 'climb-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Climb ' },
{ type: 'field', key: 'climb-alt', width: 'md' },
{ type: 'text', text: ', direct ' },
{ type: 'field', key: 'climb-direct', width: 'md' },
{ type: 'text', text: ', ' },
{ type: 'field', key: 'climb-callsign', width: 'lg' }
],
defaultFrequency: 'DEP',
phrase: scenario => `${scenario.radioCall}, climb ${scenario.altitudes.climb} feet, proceed direct ${scenario.transition}.`,
info: scenario => [
`Climb: ${scenario.altitudes.climb} ft (${scenario.altitudes.climbWords})`,
`Direct to: ${scenario.transition}`
],
generate: createBaseScenario
},
{
id: 'center-handoff',
title: 'Center Handoff',
desc: 'Acknowledge the handoff to center',
keywords: ['Center', 'Handoff'],
hints: [
'Repeat the frequency exactly (with or without the decimal point).',
'Place the call sign at the end.'
],
fields: [
{
key: 'ctr-freq',
label: 'Frequenz',
expected: scenario => scenario.centerFreq,
alternatives: scenario => [
scenario.centerFreq,
scenario.centerFreq.replace('.', ''),
scenario.frequencyWords.CTR
],
width: 'md'
},
{
key: 'ctr-callsign',
label: 'Callsign',
expected: scenario => scenario.radioCall,
alternatives: scenario => [
scenario.radioCall,
`${scenario.airlineCall} ${scenario.flightNumber}`,
scenario.callsign
],
width: 'lg'
}
],
readback: [
{ type: 'text', text: 'Contact center ' },
{ type: 'field', key: 'ctr-freq', width: 'md' },
{ type: 'text', text: ', ' },
{ type: 'field', key: 'ctr-callsign', width: 'lg' }
],
defaultFrequency: 'CTR',
phrase: scenario => `${scenario.radioCall}, contact center ${scenario.centerFreq}.`,
info: scenario => [
`Center: ${scenario.centerFreq} (${scenario.frequencyWords.CTR})`,
`Next unit: Center`
],
generate: createBaseScenario
}
]
}
]
export default learnModules