stt

package
v0.15.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 26, 2026 License: GPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package stt provides a local algorithmic speech-to-command parser for ATC transcripts, replacing the LLM-based approach with fast fuzzy matching.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CleanWord

func CleanWord(w string) string

CleanWord removes non-alphanumeric characters from a word.

func CommandCategory added in v0.15.0

func CommandCategory(cmd string) string

CommandCategory returns the category of an output command ("heading", "altitude", "speed", ...), or "" if it has none. Exported for analysis tooling (cmd/stteval).

func CommandsEquivalent

func CommandsEquivalent(expected, actual string, aircraft map[string]Aircraft) bool

CommandsEquivalent checks if two command strings are equivalent, considering altitude-aware flexibility for A/D/C commands. For example, "A40" and "D40" are equivalent if the aircraft is above 4000 ft.

func ConvertNATOLetter

func ConvertNATOLetter(word string) (string, bool)

ConvertNATOLetter converts a NATO phonetic word to its letter. Returns the letter and true if found, empty string and false otherwise.

func DoubleMetaphone

func DoubleMetaphone(word string) (primary, alternate string)

DoubleMetaphone generates phonetic encodings for a word. Returns primary and alternate encodings. The alternate may be empty. This is a simplified implementation covering common ATC vocabulary.

func EntryHash added in v0.15.0

func EntryHash(e TestFile) string

EntryHash computes the identity hash of a record: transcripts plus the stored output. This must remain stable — it is the key of the persisted Seen set.

func ExtractNATOSpelling

func ExtractNATOSpelling(words []string) (string, int)

ExtractNATOSpelling extracts consecutive NATO phonetic letters from words. Returns the spelled-out string (uppercase) and number of words consumed. Stops at the first non-NATO word.

func FuzzyMatch

func FuzzyMatch(word, target string, threshold float64) bool

FuzzyMatch reports whether word matches target at the given threshold.

func Init added in v0.14.1

func Init()

func IsCommandKeyword

func IsCommandKeyword(w string) bool

IsCommandKeyword returns true if the word is a command keyword that indicates the start of a new command context. Used by the slack mechanism to avoid searching past command boundaries.

func IsDigit

func IsDigit(s string) bool

IsDigit returns true if the string is a single digit (0-9).

func IsFillerWord

func IsFillerWord(w string) bool

IsFillerWord returns true if the word should be ignored during parsing.

func IsNumber

func IsNumber(s string) bool

IsNumber returns true if the string is a sequence of digits.

func IsSingleDigit19

func IsSingleDigit19(s string) bool

IsSingleDigit19 returns true if the string is a single digit 1-9.

func IsSpellingTrigger

func IsSpellingTrigger(word string) bool

IsSpellingTrigger returns true if the word introduces a spelling correction.

func JaroWinkler

func JaroWinkler(s1, s2 string) float64

JaroWinkler computes the Jaro-Winkler similarity between two strings. Returns a value between 0.0 (no similarity) and 1.0 (identical). Jaro-Winkler gives higher weight to strings that match from the beginning.

func Levenshtein added in v0.15.0

func Levenshtein(a, b string) int

Levenshtein returns the edit distance (insertions, deletions, substitutions) between two strings.

func MatchCallsign

func MatchCallsign(tokens []Token, aircraft map[string]Aircraft) (CallsignMatch, []Token)

MatchCallsign attempts to match tokens to an aircraft callsign. Tries starting at different positions to handle garbage words at the beginning. Returns the best match and remaining tokens after the callsign.

func NormalizeTranscript

func NormalizeTranscript(transcript string) []string

func ParseCommands

func ParseCommands(tokens []Token, ac Aircraft) ([]string, float64)

ParseCommands parses tokens into a sequence of commands using the registered command templates.

func ParseDigit

func ParseDigit(s string) int

ParseDigit converts a digit string to int. Returns -1 on error.

func ParseNumber

func ParseNumber(s string) int

ParseNumber converts a digit sequence to int. Returns -1 on error.

func PhoneticMatch

func PhoneticMatch(w1, w2 string) bool

PhoneticMatch reports whether two words are phonetically similar enough to treat as the same word in isolation.

func RegisterCallsignPattern

func RegisterCallsignPattern(template string, opts ...CallsignPatternOption)

RegisterCallsignPattern registers a callsign matching pattern.

func SanitizeTestFilename added in v0.15.0

func SanitizeTestFilename(transcript string) string

SanitizeTestFilename derives a test-file basename (without extension) from a transcript: lowercased, spaces to underscores, other punctuation dropped, truncated to 50 characters.

func StopCapture

func StopCapture() []string

StopCapture stops capturing and returns the captured lines.

func WordScore added in v0.15.0

func WordScore(word, target string) float64

WordScore returns a graded similarity in [0,1] between a transcript word and a vocabulary target: 1.0 for an exact (case-insensitive) match, otherwise the best of letter similarity (Jaro-Winkler), phonetic similarity, and known whisper confusions, capped at scoreFuzzyCap.

Types

type Aircraft

type Aircraft struct {
	Callsign                  string
	AircraftType              string                       `json:",omitempty"` // Aircraft type code (e.g., "C172", "BE36")
	Fixes                     map[string]string            `json:",omitempty"` // spoken name -> fix ID
	CandidateApproaches       map[string]string            `json:",omitempty"` // canonical name ("RNAV Z Runway 28R") -> approach ID
	CandidateVisualApproaches map[string]string            `json:",omitempty"` // spoken name -> runway ID for active plain visual approaches
	ApproachFixes             map[string]map[string]string `json:",omitempty"` // approach ID -> (spoken name -> fix ID)
	AssignedApproach          string                       `json:",omitempty"`
	ExpectedDirectFix         string                       `json:",omitempty"` // Fix the controller said to "expect direct" (if any)
	SID                       string                       `json:",omitempty"`
	STAR                      string                       `json:",omitempty"`
	Route                     []string                     `json:",omitempty"` // Ordered route waypoint fix names (full route, no truncation)
	Altitude                  int                          `json:",omitempty"` // Current altitude in feet
	Heading                   int                          `json:",omitempty"` // Current magnetic heading in degrees (0 if unknown)
	Speed                     int                          `json:",omitempty"` // Current groundspeed in knots (0 if unknown)
	AssignedAltitude          int                          `json:",omitempty"` // Controller-assigned altitude in feet (0 if none)
	AssignedHeading           int                          `json:",omitempty"` // Controller-assigned heading in degrees (0 if none)
	AssignedSpeed             int                          `json:",omitempty"` // Controller-assigned speed in knots (0 if none, or if assigned in mach)
	AssignedMach              int                          `json:",omitempty"` // Controller-assigned mach in hundredths, e.g. 78 for M0.78 (0 if none, or if assigned in knots)
	State                     string                       `json:",omitempty"` // "departure", "arrival", "cleared approach", "overflight", "vfr flight following"
	ControllerFrequency       string                       `json:",omitempty"` // Current controller position the aircraft is tuned to
	TrackingController        string                       `json:",omitempty"` // Controller tracking this aircraft (from flight plan)
	AddressingForm            sim.CallsignAddressingForm   `json:",omitempty"` // How this aircraft was addressed (based on which key matched)
	LAHSORunways              []string                     `json:",omitempty"` // Runways that intersect the approach runway (for LAHSO matching)
}

Aircraft holds context for a single aircraft for STT processing. Zero means "unknown"/"none" throughout, so the JSON tags omit empty values: this struct is logged verbatim with every transmission and stored in the test corpus, where zeroes are pure noise.

type CallsignMatch

type CallsignMatch struct {
	Callsign       string                     // The matched ICAO callsign (e.g., "AAL5936")
	SpokenKey      string                     // The key in the aircraft context map
	Confidence     float64                    // Match confidence (0.0-1.0)
	Consumed       int                        // Number of tokens consumed for the callsign
	AddressingForm sim.CallsignAddressingForm // How the callsign was addressed (full vs type+trailing3)
}

CallsignMatch represents a matched callsign with confidence score.

func MatchCallsignCandidates added in v0.15.0

func MatchCallsignCandidates(tokens []Token, aircraft map[string]Aircraft) []CallsignMatch

MatchCallsignCandidates returns the distinct callsign interpretations of the leading tokens, best first.

Matching proceeds using a declarative pattern-based approach:

  1. Weight class filtering - if "heavy"/"super" found, filter aircraft first
  2. Pattern-based matching - uses DSL patterns in priority order

type CallsignPattern

type CallsignPattern struct {
	Name            string                                    // Human-readable name for debugging
	Template        string                                    // Original template string
	Priority        int                                       // Higher priority patterns are tried first
	Matchers        []callsignMatcher                         // Parsed from template
	MinScore        float64                                   // Minimum score to accept a match
	FixedConfidence float64                                   // If > 0, use this confidence instead of computed
	ScoringFunc     func(result *callsignMatchResult) float64 // Custom scoring function
	Condition       func(Aircraft) bool                       // Pre-filter aircraft (returns true to include)
	RequireUnique   bool                                      // Only accept if exactly one aircraft matches
}

CallsignPattern represents a declarative callsign matching rule.

type CallsignPatternOption

type CallsignPatternOption func(*CallsignPattern)

CallsignPatternOption configures a callsign pattern registration.

func WithCallsignConfidence

func WithCallsignConfidence(conf float64) CallsignPatternOption

WithCallsignConfidence sets a fixed confidence value for matches from this pattern.

func WithCallsignMinScore

func WithCallsignMinScore(score float64) CallsignPatternOption

WithCallsignMinScore sets the minimum score threshold for accepting a match.

func WithCallsignName

func WithCallsignName(name string) CallsignPatternOption

WithCallsignName sets a human-readable name for the pattern.

func WithCallsignPriority

func WithCallsignPriority(priority int) CallsignPatternOption

WithCallsignPriority sets the pattern priority (higher = tried first).

func WithCallsignRequire

func WithCallsignRequire(fn func(Aircraft) bool) CallsignPatternOption

WithCallsignRequire sets a condition function to pre-filter aircraft.

func WithCallsignScoring

func WithCallsignScoring(fn func(*callsignMatchResult) float64) CallsignPatternOption

WithCallsignScoring sets a custom scoring function.

type CandidateApproach added in v0.15.0

type CandidateApproach struct {
	Id       string
	FullName string
	Spoken   string
}

CandidateApproach is one approach an aircraft could be cleared for, expanded from the aircraft's canonical-name-to-code map. Id is the scenario's approach code and is what emitted commands carry; it is facility-authored and has no fixed layout ("RZ8R" at one field, "Z8R" or even "R4L" at another), so nothing may be read from its characters. The approach's type, variant letter, and runway all come from FullName, the canonical name ("RNAV Z Runway 28R"); Spoken is its telephony, and is what transcript spans are aligned against.

type CommandMatch

type CommandMatch struct {
	Command    string      // The output command string
	Name       string      // Matched template's name, for logging
	Confidence float64     // Per-token match score
	Score      float64     // Coverage-adjusted score used to rank competing matches
	Kind       commandKind // What kind of segment this template matched
	Consumed   int         // Tokens consumed
	IsThen     bool        // Whether this is a "then" sequenced command
	IsSayAgain bool        // True if this is a partial match that needs say-again
}

CommandMatch represents a matched command.

type CommandOption

type CommandOption func(*sttCommand)

CommandOption configures a command registration.

func WithKind added in v0.15.0

func WithKind(k commandKind) CommandOption

WithKind marks a template as informational rather than command-issuing.

func WithName

func WithName(name string) CommandOption

WithName sets a human-readable name for debugging.

func WithPriority

func WithPriority(p int) CommandOption

WithPriority sets the command priority.

func WithSayAgainMinTokens added in v0.14.2

func WithSayAgainMinTokens(n int) CommandOption

WithSayAgainMinTokens sets the minimum number of consumed tokens before SAYAGAIN triggers. Use this for commands starting with common words like "at" where a single keyword match is insufficient context. For example, "at {fix} cleared {approach}" should only trigger SAYAGAIN if the fix matched (2+ tokens consumed), not when just "at" matched.

func WithSayAgainOnFail

func WithSayAgainOnFail() CommandOption

WithSayAgainOnFail enables emitting SAYAGAIN when a type parser fails. Use this for commands where the controller clearly requested something specific (like "expect approach") and we should ask for clarification.

func WithThenVariant

func WithThenVariant(format string) CommandOption

WithThenVariant sets the format for "then" sequenced commands.

type LogBuffer

type LogBuffer struct {
	// contains filtered or unexported fields
}

LogBuffer captures STT processing logs for bug reports.

func StartCapture

func StartCapture() *LogBuffer

StartCapture begins capturing log lines to a buffer. Returns the buffer that will collect the logs.

type NumberCandidate added in v0.15.0

type NumberCandidate struct {
	Value    int
	Consumed int // tokens consumed starting at the decode position
	Score    float64
}

NumberCandidate is one scored interpretation of the tokens at a position.

func DecodeNumber added in v0.15.0

func DecodeNumber(tokens []Token, pos int, ctx NumberContext) []NumberCandidate

DecodeNumber decodes one number of the given kind at tokens[pos], returning all plausible interpretations ordered best-first. An empty result means no reading of the tokens at pos yields a valid value.

type NumberContext added in v0.15.0

type NumberContext struct {
	Kind NumberKind
	AC   Aircraft
	// AllowFlightLevel permits altitude values in 100-400 (which would
	// otherwise be reserved for speeds) when decoding in explicit
	// climb/descend context.
	AllowFlightLevel bool
}

NumberContext carries the decoding kind and the aircraft context used for plausibility priors (current altitude, performance envelope).

type NumberKind added in v0.15.0

type NumberKind int

NumberKind identifies what kind of value is being decoded, which determines the valid range and the plausibility priors.

const (
	NumAltitude        NumberKind = iota // encoded hundreds of feet (50 = 5,000 ft)
	NumHeading                           // 001-360 degrees, normally a multiple of 5
	NumSpeed                             // 100-400 knots, normally a multiple of 10
	NumMach                              // hundredths (75 = Mach 0.75), 60-99
	NumOClock                            // clock position 1-12 (traffic advisories)
	NumTrafficAltitude                   // encoded hundreds, as reported in traffic advisories
)

type ParseResult

type ParseResult struct {
	Callsign       string
	Commands       []string
	Confidence     float64
	CallsignConf   float64
	CommandConf    float64
	ValidationConf float64
	Errors         []string
}

ParseResult holds detailed parsing results for debugging/testing.

type ReviewState added in v0.15.0

type ReviewState struct {
	Queue []TestFile      `json:"queue"`
	Seen  map[string]bool `json:"seen"`
}

ReviewState is the persisted state of the transmission-review workflow (cmd/sttreview, cmd/stteval): a queue of records awaiting review and the set of already-processed record hashes.

func LoadReviewState added in v0.15.0

func LoadReviewState(path string) *ReviewState

LoadReviewState loads review state from path. A missing or unparseable file yields an empty state. Queue entries that are already Seen, and duplicates within the queue, are dropped.

func (*ReviewState) Ingest added in v0.15.0

func (s *ReviewState) Ingest(entries []TestFile) int

Ingest adds new entries to the queue, skipping already-seen ones and duplicates. Returns the number added.

func (*ReviewState) MarkDone added in v0.15.0

func (s *ReviewState) MarkDone(e TestFile)

MarkDone marks an entry as processed.

func (*ReviewState) Save added in v0.15.0

func (s *ReviewState) Save(path string) error

Save persists the state to path, creating parent directories as needed.

type TestFile added in v0.15.0

type TestFile struct {
	Time              string              `json:"time,omitempty"`
	Level             string              `json:"level,omitempty"`
	Msg               string              `json:"msg"`
	Callstack         []string            `json:"callstack,omitempty"`
	Transcript        string              `json:"transcript"`
	WhisperPrompt     string              `json:"whisper_prompt,omitempty"`
	WhisperDurationMs float64             `json:"whisper_duration_ms,omitempty"`
	Duration          int64               `json:"duration,omitempty"`
	AudioDurationMs   float64             `json:"audio_duration_ms,omitempty"`
	Processor         string              `json:"processor,omitempty"`
	WhisperModel      string              `json:"whisper_model,omitempty"`
	Callsign          string              `json:"callsign"`
	Command           string              `json:"command"`
	Suggested         string              `json:"suggested,omitempty"` // reviewer-prefill correction, if any
	Reason            string              `json:"reason,omitempty"`    // why the entry is a suspect / the suggestion (review note)
	STTAircraft       map[string]Aircraft `json:"stt_aircraft"`
	Logs              []string            `json:"logs,omitempty"`
}

TestFile is the on-disk record for one STT transmission: the shape of the "STT command" entries in the vice slog, of the corpus files in stt/tests/ and stt/failing_tests/, and of the entries in cmd/sttreview's review queue. Callsign and Command hold the expected decoder output (both empty means the expected output is silence).

func LoadTestFile added in v0.15.0

func LoadTestFile(path string) (TestFile, error)

LoadTestFile reads and parses one test-file JSON.

func (TestFile) BuildAircraftMap added in v0.15.0

func (tf TestFile) BuildAircraftMap() map[string]Aircraft

BuildAircraftMap converts the stored per-aircraft context into the map DecodeTranscript expects, mirroring the production context initialization in provider.go: type-addressed callsigns get a /T suffix, and the assigned approach's fixes are merged into Fixes.

func (TestFile) Expected added in v0.15.0

func (tf TestFile) Expected() string

Expected returns the expected decoder output: "CALLSIGN COMMANDS", or "" when both are empty (the transmission expects silence).

type Token

type Token struct {
	Original string    // Original text from STT (before normalization)
	Text     string    // Normalized text
	Type     TokenType // Token type
	Value    int       // Numeric value if applicable (-1 if not)
}

Token represents a normalized piece of the transcript.

func Tokenize

func Tokenize(words []string) []Token

Tokenize converts normalized words into structured tokens. Handles number merging, altitude patterns, and type detection.

type TokenType

type TokenType int

TokenType represents the type of a parsed token.

const (
	TokenWord TokenType = iota
	TokenNumber
	TokenAltitude // Specifically identified as an altitude (e.g., "8 thousand")
	TokenFix      // Matched against aircraft fixes
	TokenApproach // Matched against candidate approaches
	TokenCallsign // Part of callsign
	TokenICAO     // Airline ICAO code
)

type Transcriber

type Transcriber struct {
	// contains filtered or unexported fields
}

Transcriber converts speech transcripts to aircraft control commands using local algorithmic parsing with fast fuzzy matching.

func NewTranscriber

func NewTranscriber(lg *log.Logger) *Transcriber

NewTranscriber creates a new STT transcriber.

func (*Transcriber) BuildAircraftContext

func (p *Transcriber) BuildAircraftContext(
	state *sim.UserState,
	userTCW sim.TCW,
) map[string]Aircraft

BuildAircraftContext creates the STT aircraft context from simulation state.

func (*Transcriber) DecodeCommandsForCallsign

func (p *Transcriber) DecodeCommandsForCallsign(
	aircraft map[string]Aircraft,
	transcript string,
	callsign string,
) (string, error)

DecodeCommandsForCallsign parses commands from a transcript for a known callsign. This is used when the controller repeats a command without saying the callsign after an aircraft replied "AGAIN". It skips callsign matching and directly parses the entire transcript as commands for the specified aircraft. Returns one of:

  • "{commands}" for successfully parsed commands
  • "AGAIN" if no commands could be parsed
  • "" if transcript is empty

func (*Transcriber) DecodeFromState

func (p *Transcriber) DecodeFromState(
	state *sim.UserState,
	userTCW sim.TCW,
	transcript string,
) (string, error)

DecodeFromState decodes a transcript using the simulation state directly. It builds the aircraft context internally from the provided state. Only tracks on the user's frequency (ControllerFrequency) are included in the context.

func (*Transcriber) DecodeTranscript

func (p *Transcriber) DecodeTranscript(
	aircraft map[string]Aircraft,
	transcript string,
	controllerRadioName string,
) (string, error)

DecodeTranscript converts a speech transcript to aircraft control commands. It returns one of:

  • "{CALLSIGN} {CMD1} {CMD2} ..." for successful parsing
  • "{CALLSIGN} AGAIN" if callsign identified but commands unclear
  • "" if transcript is empty, only contains position identification, or no callsign could be matched

Commands may include SAYAGAIN/TYPE for partial parses where keywords were recognized but the associated value couldn't be extracted (e.g., "fly heading blark" would return "SAYAGAIN/HEADING"). Valid types are: HEADING, ALTITUDE, SPEED, APPROACH, TURN, SQUAWK, FIX. When combined with other commands, e.g., "{CALLSIGN} C50 SAYAGAIN/HEADING", the aircraft will execute the valid commands and ask for clarification on the missed part.

controllerRadioName is the user's controller radio name (e.g., "New York Departure") used to detect position identification phrases. Pass empty string if not available.

func (*Transcriber) GetUsageStats

func (p *Transcriber) GetUsageStats() string

GetUsageStats returns usage statistics for this provider.

func (*Transcriber) ParseTranscriptDetailed

func (p *Transcriber) ParseTranscriptDetailed(
	aircraft map[string]Aircraft,
	transcript string,
) ParseResult

ParseTranscriptDetailed provides detailed parsing results for testing.

type ValidationResult

type ValidationResult struct {
	ValidCommands []string // Commands that passed validation
	Confidence    float64  // Adjusted confidence based on validation
	Errors        []string // Validation error messages (for debugging)
}

ValidationResult holds the result of command validation.

func ValidateCommands

func ValidateCommands(commands []string, ac Aircraft) ValidationResult

ValidateCommands validates a list of commands against aircraft state. Returns filtered commands and adjusted confidence.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL