gov_codes 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.simplecov +15 -1
- data/.tool-versions +1 -1
- data/CHANGELOG.md +57 -2
- data/README.md +223 -22
- data/Rakefile +7 -1
- data/checksums/gov_codes-0.1.0.gem.sha512 +1 -0
- data/checksums/gov_codes-0.1.1.gem.sha512 +1 -0
- data/lib/gov_codes/afsc/enlisted.rb +134 -24
- data/lib/gov_codes/afsc/officer.rb +141 -25
- data/lib/gov_codes/afsc/releases/dafecd/2025-10-31/enlisted.yml +2726 -0
- data/lib/gov_codes/afsc/releases/dafecd/2025-10-31/ri.yml +369 -0
- data/lib/gov_codes/afsc/releases/dafocd/2025-10-31/officer.yml +2393 -0
- data/lib/gov_codes/afsc/releases/dafocd/2025-10-31/ri.yml +193 -0
- data/lib/gov_codes/afsc/releases.rb +219 -0
- data/lib/gov_codes/afsc/releases.yml +11 -0
- data/lib/gov_codes/afsc/ri.rb +207 -0
- data/lib/gov_codes/afsc.rb +35 -3
- data/lib/gov_codes/dafecd/index_builder.rb +416 -0
- data/lib/gov_codes/dafecd/patterns.rb +50 -0
- data/lib/gov_codes/dafecd/publication.rb +252 -0
- data/lib/gov_codes/dafecd/record_splitter.rb +77 -0
- data/lib/gov_codes/dafecd/ri_sdi/change_date.rb +63 -0
- data/lib/gov_codes/dafecd/ri_sdi/config.rb +137 -0
- data/lib/gov_codes/dafecd/ri_sdi/index_builder.rb +265 -0
- data/lib/gov_codes/dafecd/ri_sdi/ri_list_parser.rb +125 -0
- data/lib/gov_codes/dafecd/ri_sdi/sdi_card_parser.rb +151 -0
- data/lib/gov_codes/dafecd/ri_sdi/sdi_section_splitter.rb +77 -0
- data/lib/gov_codes/dafecd/ri_sdi/section_slicer.rb +45 -0
- data/lib/gov_codes/dafecd/ri_sdi/title.rb +76 -0
- data/lib/gov_codes/dafecd/ri_sdi/title_overrides/dafecd.yml +124 -0
- data/lib/gov_codes/dafecd/ri_sdi/title_overrides/dafocd.yml +78 -0
- data/lib/gov_codes/dafecd/shredout_acronyms.rb +47 -0
- data/lib/gov_codes/dafecd/shredout_degluer.rb +73 -0
- data/lib/gov_codes/dafecd/shredout_overrides/dafecd.yml +27 -0
- data/lib/gov_codes/dafecd/shredout_overrides/dafocd.yml +27 -0
- data/lib/gov_codes/dafecd/shredout_parser.rb +75 -0
- data/lib/gov_codes/dafecd/specialty_parser.rb +200 -0
- data/lib/gov_codes/dafecd/text.rb +48 -0
- data/lib/gov_codes/dafecd/title_degluer.rb +67 -0
- data/lib/gov_codes/dafecd/title_overrides/dafocd.yml +141 -0
- data/lib/gov_codes/dafecd/title_overrides.yml +142 -0
- data/lib/gov_codes/data_loader.rb +49 -36
- data/lib/gov_codes/version.rb +1 -1
- metadata +36 -4
- data/lib/gov_codes/afsc/enlisted.yml +0 -725
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "patterns"
|
|
4
|
+
require_relative "publication"
|
|
5
|
+
require_relative "text"
|
|
6
|
+
|
|
7
|
+
module GovCodes
|
|
8
|
+
module Dafecd
|
|
9
|
+
# Parses a single specialty record (as produced by RecordSplitter) into a
|
|
10
|
+
# structured header: X-form specialty, career field, CEM code (enlisted),
|
|
11
|
+
# per-specialty change date, skill/qualification ladder, and the
|
|
12
|
+
# (conservatively normalized) specialty title.
|
|
13
|
+
#
|
|
14
|
+
# Behavior is publication-specific (injected Publication); the default is the
|
|
15
|
+
# enlisted directory.
|
|
16
|
+
#
|
|
17
|
+
# Deliberately does NOT attempt to de-glue titles that pdf-reader ran
|
|
18
|
+
# together (e.g. "FORCEAVIATOR" / "SPECIALWARFARE"). Such titles are
|
|
19
|
+
# title-cased verbatim and flagged via :glued_title so the deferred LLM
|
|
20
|
+
# despacer can address them.
|
|
21
|
+
class SpecialtyParser
|
|
22
|
+
DECORATIVE = Patterns::DECORATIVE
|
|
23
|
+
UNICODE_DASHES = Patterns::UNICODE_DASHES
|
|
24
|
+
|
|
25
|
+
def initialize(record, publication: Publication.dafecd)
|
|
26
|
+
@publication = publication
|
|
27
|
+
@record = Text.split_glued_afsc(record, publication.glued_afsc)
|
|
28
|
+
@lines = @record.lines
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse
|
|
32
|
+
codes = ladder_codes
|
|
33
|
+
{
|
|
34
|
+
specialty: specialty(codes),
|
|
35
|
+
career_field: career_field(codes),
|
|
36
|
+
cem_code: cem_code,
|
|
37
|
+
bare_code: bare_code,
|
|
38
|
+
changed_date: changed_date,
|
|
39
|
+
name: name,
|
|
40
|
+
raw_title: raw_title,
|
|
41
|
+
glued_title: glued_title?
|
|
42
|
+
}.merge(@publication.levels_key => levels)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def ladder
|
|
48
|
+
@publication.ladder
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Array<String>] concrete ladder AFSCs in document order
|
|
52
|
+
def ladder_codes
|
|
53
|
+
@lines.filter_map { |line| line[ladder, 1] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The specialty key: the ladder-family X-form, or (for a bare single-code
|
|
57
|
+
# record with no ladder) the literal code.
|
|
58
|
+
def specialty(codes)
|
|
59
|
+
return @publication.specialty_key(codes) if codes.any?
|
|
60
|
+
bare = bare_code
|
|
61
|
+
bare&.to_sym
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def career_field(codes)
|
|
65
|
+
return @publication.career_field(codes) if codes.any?
|
|
66
|
+
bare = bare_code
|
|
67
|
+
bare && :"#{bare[0, 2]}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The bare standalone code of a single-code record (officer), or nil.
|
|
71
|
+
def bare_code
|
|
72
|
+
return @bare_code if defined?(@bare_code)
|
|
73
|
+
pattern = @publication.bare_code
|
|
74
|
+
@bare_code = pattern && @record[pattern, 1]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cem_code
|
|
78
|
+
pattern = @publication.cem
|
|
79
|
+
pattern && @record[pattern, 1]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def changed_date
|
|
83
|
+
raw = @record[@publication.change_date, 1]
|
|
84
|
+
return nil unless raw
|
|
85
|
+
Text.parse_date(raw)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# @return [Hash{Integer=>Hash}] level digit => {code:, title:}
|
|
89
|
+
def levels
|
|
90
|
+
result = {}
|
|
91
|
+
@lines.each do |line|
|
|
92
|
+
next unless line =~ ladder
|
|
93
|
+
code = $1
|
|
94
|
+
title = normalize_level($2)
|
|
95
|
+
digit = code[3].to_i
|
|
96
|
+
result[digit] = {code: code, title: title}
|
|
97
|
+
end
|
|
98
|
+
result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def normalize_level(word)
|
|
102
|
+
collapsed = word.gsub(/\s+/, " ").strip
|
|
103
|
+
(collapsed == "Senior Enlisted") ? "Senior Enlisted Leader" : collapsed
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def name
|
|
107
|
+
raw = raw_title
|
|
108
|
+
return nil if raw.nil? || raw.empty?
|
|
109
|
+
titleize(raw)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def glued_title?
|
|
113
|
+
title = name
|
|
114
|
+
return false if title.nil?
|
|
115
|
+
# Heuristic proxy for pdf-reader glue: an unusually long single token
|
|
116
|
+
# (excluding preserved acronyms). Reported, not relied upon, for the
|
|
117
|
+
# deferred despacer. False positives are possible for genuinely long
|
|
118
|
+
# words; the coverage report lists them for human review.
|
|
119
|
+
title.split(/\s+/).any? do |token|
|
|
120
|
+
bare = token.delete("()/-")
|
|
121
|
+
bare.length >= 12 && bare.match?(/\A[A-Za-z]+\z/)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# The title line(s) between the last ladder/bare-code line and the change
|
|
126
|
+
# date / first numbered section, joined and sanitized (pre-titlecase).
|
|
127
|
+
# Exposed verbatim so the de-gluer can diff against the source.
|
|
128
|
+
def raw_title
|
|
129
|
+
return @raw_title if defined?(@raw_title)
|
|
130
|
+
@raw_title = compute_raw_title
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def compute_raw_title
|
|
134
|
+
last_anchor = last_anchor_index
|
|
135
|
+
return nil if last_anchor.nil?
|
|
136
|
+
|
|
137
|
+
collected = []
|
|
138
|
+
@lines[(last_anchor + 1)..].each do |line|
|
|
139
|
+
stripped = sanitize(line)
|
|
140
|
+
next if stripped.empty?
|
|
141
|
+
next if stripped.match?(/\A\d+\z/) # bare page number
|
|
142
|
+
next if stripped.match?(/\A[A-Z][a-z]+\z/) # wrapped ladder word (e.g. "Leader")
|
|
143
|
+
break if stripped.match?(/\A\(?(?:Changed|Established|Effective)\b/) # change date
|
|
144
|
+
break if stripped.match?(/\A\d+\./) # numbered section (1. / 1.Specialty / 3.4.1)
|
|
145
|
+
break if stripped.match?(/\b(?:Specialty Summary|Special Duty Summary|Duties and Responsibilities)\b/)
|
|
146
|
+
|
|
147
|
+
if title_line?(stripped)
|
|
148
|
+
collected << stripped
|
|
149
|
+
else
|
|
150
|
+
break
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
return nil if collected.empty?
|
|
155
|
+
collected.join(" ")
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# The last line that anchors a record: a ladder line, or (for a bare
|
|
159
|
+
# single-code officer record) the standalone code line.
|
|
160
|
+
def last_anchor_index
|
|
161
|
+
@lines.each_index.reverse_each.find do |i|
|
|
162
|
+
@lines[i] =~ ladder || (@publication.bare_code && @lines[i] =~ @publication.bare_code)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Remove decorative symbol glyphs, normalize unicode dashes, and collapse
|
|
167
|
+
# runs of whitespace (pdf-reader pads some titles with long space runs) so
|
|
168
|
+
# a title line reduces to its plain-text content.
|
|
169
|
+
def sanitize(line)
|
|
170
|
+
line.gsub(DECORATIVE, "").gsub(UNICODE_DASHES, "-").gsub(/\s+/, " ").strip
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# A title line is short, starts with a capital / paren / digit, and is not
|
|
174
|
+
# a prose sentence. Accepts ALL-CAPS and Title-Case forms; the boundary
|
|
175
|
+
# breaks in compute_raw_title stop collection before the summary prose.
|
|
176
|
+
def title_line?(stripped)
|
|
177
|
+
return false if stripped.length > 70
|
|
178
|
+
return false unless stripped.match?(/\A[A-Z0-9(]/)
|
|
179
|
+
return false if stripped.split(/\s+/).size > 10
|
|
180
|
+
return false if stripped.match?(/[a-z]\.\s+[A-Z]/) # mid-line sentence break = prose
|
|
181
|
+
stripped.match?(/[A-Za-z]/)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def titleize(raw)
|
|
185
|
+
raw.split(/\s+/).map { |word| titleize_word(word) }.join(" ")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def titleize_word(word)
|
|
189
|
+
# Preserve parenthesized acronyms, e.g. "(RPA)", "(ISR)", "(C2)".
|
|
190
|
+
return word if word.match?(/\A\([A-Z0-9&\/.-]{2,}\)\z/)
|
|
191
|
+
# Preserve all-caps tokens containing a digit, e.g. "C2", "F16".
|
|
192
|
+
return word if word.match?(/\A[A-Z]*\d[A-Z0-9]*\z/) && word.match?(/[A-Z0-9]/)
|
|
193
|
+
|
|
194
|
+
word.split(/([\/-])/).map { |seg|
|
|
195
|
+
seg.match?(/[A-Za-z]/) ? seg.capitalize : seg
|
|
196
|
+
}.join
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GovCodes
|
|
4
|
+
module Dafecd
|
|
5
|
+
# Shared text utilities for the DAFECD extractor: normalization of
|
|
6
|
+
# pdf-reader glue artifacts and date parsing. Used by RecordSplitter,
|
|
7
|
+
# SpecialtyParser, and the extractor CLI so the behavior stays consistent.
|
|
8
|
+
module Text
|
|
9
|
+
MONTHS = %w[jan feb mar apr may jun jul aug sep oct nov dec]
|
|
10
|
+
.each_with_index.to_h { |m, i| [m, i + 1] }.freeze
|
|
11
|
+
|
|
12
|
+
# Default (enlisted) glue boundary: a letter immediately followed by an
|
|
13
|
+
# "AFSC <5-char enlisted code>".
|
|
14
|
+
DEFAULT_GLUED_AFSC = /(?<=[A-Za-z])(?=AFSC \d[A-Z]\d\d\d)/
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
# pdf-reader frequently glues the word "AFSC" to a preceding word when a
|
|
19
|
+
# ladder line wraps, e.g. "LeaderAFSC 1A178*, Craftsman". Insert a newline
|
|
20
|
+
# before an "AFSC <code>" that is glued to a letter so the ladder line is
|
|
21
|
+
# detectable at line start. This is safe for prose mentions such as
|
|
22
|
+
# "possession ofAFSC 1A132" because ladder detection additionally requires
|
|
23
|
+
# a skill-level word at end of line, which prose lines do not satisfy.
|
|
24
|
+
#
|
|
25
|
+
# The glue boundary is publication-specific (the enlisted and officer AFSC
|
|
26
|
+
# code shapes differ); pass the publication's pattern to override the
|
|
27
|
+
# enlisted default.
|
|
28
|
+
def split_glued_afsc(text, pattern = DEFAULT_GLUED_AFSC)
|
|
29
|
+
text.gsub(pattern, "\n")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Normalize a directory date such as "31 Oct 25", "31 Oct 2024", or the
|
|
33
|
+
# officer directory's glued "30Apr 25" to ISO 8601 ("2025-10-31" /
|
|
34
|
+
# "2024-10-31"). The day/month gap is optional (\s*) to absorb pdf-reader's
|
|
35
|
+
# glued day/month; the enlisted (always-spaced) dates are unaffected.
|
|
36
|
+
# Returns nil when unparseable.
|
|
37
|
+
def parse_date(raw)
|
|
38
|
+
return nil unless raw =~ /(\d{1,2})\s*(\w{3,9})\s+(\d{2,4})/
|
|
39
|
+
day = $1.to_i
|
|
40
|
+
month = MONTHS[$2[0, 3].downcase]
|
|
41
|
+
year = $3.to_i
|
|
42
|
+
year += 2000 if year < 100
|
|
43
|
+
return nil unless month
|
|
44
|
+
format("%04d-%02d-%02d", year, month, day)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module GovCodes
|
|
6
|
+
module Dafecd
|
|
7
|
+
# Applies verified de-glued specialty titles.
|
|
8
|
+
#
|
|
9
|
+
# pdf-reader drops spaces inside DAFECD titles ("MOBILITY FORCEAVIATOR").
|
|
10
|
+
# The deterministic extractor title-cases those verbatim ("Mobility
|
|
11
|
+
# Forceaviator") and cannot safely re-insert the missing spaces. The clean
|
|
12
|
+
# titles are produced out of band and shipped in title_overrides.yml.
|
|
13
|
+
#
|
|
14
|
+
# The de-gluing invariant — enforced at build time by IndexBuilder — is that
|
|
15
|
+
# an override may only change SPACING and CASE relative to the raw source
|
|
16
|
+
# title; no letter, digit, or punctuation may change. Any drift (e.g. a
|
|
17
|
+
# future release renamed the specialty) fails the build loudly rather than
|
|
18
|
+
# silently applying a stale title.
|
|
19
|
+
class TitleDegluer
|
|
20
|
+
DEFAULT_PATH = File.expand_path("title_overrides.yml", __dir__)
|
|
21
|
+
|
|
22
|
+
# @return [TitleDegluer] loaded from the shipped overrides file
|
|
23
|
+
def self.load(path = DEFAULT_PATH)
|
|
24
|
+
overrides = YAML.safe_load_file(path, permitted_classes: [Symbol]) || {}
|
|
25
|
+
new(overrides)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [TitleDegluer] loaded from +publication+'s overrides file, or an
|
|
29
|
+
# empty de-gluer when that file does not yet exist (officer titles are
|
|
30
|
+
# supplied out of band in a later phase).
|
|
31
|
+
def self.for(publication)
|
|
32
|
+
path = publication.title_overrides_path
|
|
33
|
+
File.exist?(path) ? load(path) : empty
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# An empty de-gluer applies no overrides (keeps the auto-titlecased names).
|
|
37
|
+
def self.empty
|
|
38
|
+
new({})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Whitespace-and-case-insensitive normalization used for the invariant.
|
|
42
|
+
def self.norm(str)
|
|
43
|
+
str.to_s.gsub(/\s+/, "").downcase
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Does +override+ differ from +raw_title+ only in spacing/case?
|
|
47
|
+
def self.matches_source?(override, raw_title)
|
|
48
|
+
return false if raw_title.nil?
|
|
49
|
+
norm(override) == norm(raw_title)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param overrides [Hash{Symbol=>String}] specialty => clean title
|
|
53
|
+
def initialize(overrides = {})
|
|
54
|
+
@overrides = overrides
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @return [String, nil] the clean title for +specialty+, or nil
|
|
58
|
+
def override_for(specialty)
|
|
59
|
+
@overrides[specialty]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def any?
|
|
63
|
+
!@overrides.empty?
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# De-glued specialty titles for DAFOCD, effective 2025-10-31.
|
|
2
|
+
# Each value is the source PDF title with ONLY spacing/case adjusted --
|
|
3
|
+
# no letters, digits, or punctuation changed. Enforced at build time by
|
|
4
|
+
# the IndexBuilder verification gate (whitespace/case-insensitive
|
|
5
|
+
# equality against the raw source title); drift fails the build.
|
|
6
|
+
:10C0: Operations Commander
|
|
7
|
+
:11BX: Bomber Pilot
|
|
8
|
+
:11EX: Experimental Test Pilot
|
|
9
|
+
:11FX: Fighter Pilot
|
|
10
|
+
:11GX: Generalist Pilot
|
|
11
|
+
:11HX: Rescue Pilot
|
|
12
|
+
:11KX: Trainer Pilot
|
|
13
|
+
:11MX: Mobility Pilot
|
|
14
|
+
:11RX: Reconnaissance/Surveillance/Electronic Warfare Pilot
|
|
15
|
+
:11SX: Special Operations Pilot
|
|
16
|
+
:11TX: Student Pilot
|
|
17
|
+
:11UX: Remotely Piloted Aircraft (RPA) Pilot
|
|
18
|
+
:12BX: Bomber Combat Systems Officer
|
|
19
|
+
:12EX: Experimental Test Combat Systems Officer
|
|
20
|
+
:12FX: Fighter Combat Systems Officer
|
|
21
|
+
:12GX: Generalist Combat Systems Officer
|
|
22
|
+
:12HX: Rescue Combat Systems Officer
|
|
23
|
+
:12KX: Trainer Combat Systems Officer
|
|
24
|
+
:12MX: Mobility Combat Systems Officer
|
|
25
|
+
:12RX: Reconnaissance/Surveillance/Electronic Warfare Combat Systems Officer
|
|
26
|
+
:12SX: Special Operations Combat Systems Officer
|
|
27
|
+
:12UX: Remotely Piloted Aircraft (RPA) Combat Systems Officer
|
|
28
|
+
:13AX: Astronaut
|
|
29
|
+
:13BX: Air Battle Manager
|
|
30
|
+
:13HX: Aerospace Physiologist
|
|
31
|
+
:13MX: Airfield Operations
|
|
32
|
+
:13NX: Nuclear and Missile Operations
|
|
33
|
+
:13OX: Multi-Domain Warfare Officer
|
|
34
|
+
:13SX: Space Operations
|
|
35
|
+
:13ZX: Rated Multi-Domain Warfare Officer
|
|
36
|
+
:14FX: Information Operations
|
|
37
|
+
:14NX: Intelligence
|
|
38
|
+
:15AX: Operations Analysis Officer
|
|
39
|
+
:15WX: Weather and Environmental Sciences
|
|
40
|
+
:16FX: Foreign Area Officer (FAO)
|
|
41
|
+
:16GX: Air Force Operations Staff Officer
|
|
42
|
+
:16KX: Software Development Officer (SDO)
|
|
43
|
+
:16PX: Political-Military Affairs Strategist (PAS)
|
|
44
|
+
:16RX: Planning and Programming
|
|
45
|
+
:16ZX: Rated Foreign Area Officer (FAO)
|
|
46
|
+
:17DX: Warfighter Communications
|
|
47
|
+
:17SX: Cyberspace Effects Operations
|
|
48
|
+
:17WX: Warfighter Communications & IT Systems
|
|
49
|
+
:17YX: Cyber Effects & Warfare Operations
|
|
50
|
+
:18AX: Attack Remotely Piloted Aircraft Pilot
|
|
51
|
+
:18EX: Experimental Test Remotely Piloted Aircraft Pilot
|
|
52
|
+
:18GX: Generalist Remotely Piloted Aircraft Pilot
|
|
53
|
+
:18RX: Reconnaissance Remotely Piloted Aircraft Pilot
|
|
54
|
+
:18SX: Special Operations Remotely Piloted Aircraft Pilot
|
|
55
|
+
:19ZX: Special Warfare
|
|
56
|
+
:20C0: Logistics Commander
|
|
57
|
+
:21AX: Aircraft Maintenance
|
|
58
|
+
:21MX: Munitions and Missile Maintenance
|
|
59
|
+
:21RX: Logistics Readiness
|
|
60
|
+
:30C0: Support Commander
|
|
61
|
+
:31PX: Security Forces
|
|
62
|
+
:32EX: Civil Engineer
|
|
63
|
+
:35BX: Band
|
|
64
|
+
:35PX: Public Affairs
|
|
65
|
+
:38FX: Force Support
|
|
66
|
+
:40C0: Medical Commander
|
|
67
|
+
:41AX: Healthcare Administrator
|
|
68
|
+
:42BX: Physical Therapist
|
|
69
|
+
:42EX: Optometrist
|
|
70
|
+
:42FX: Podiatric Surgeon
|
|
71
|
+
:42GX: Physician Assistant
|
|
72
|
+
:42NX: Audiologist
|
|
73
|
+
:42PX: Clinical Psychologist
|
|
74
|
+
:42SX: Clinical Social Worker
|
|
75
|
+
:42TX: Occupational Therapist
|
|
76
|
+
:43BX: Biomedical Scientist
|
|
77
|
+
:43DX: Dietitian
|
|
78
|
+
:43EX: Bioenvironmental Engineer
|
|
79
|
+
:43HX: Public Health Officer
|
|
80
|
+
:43PX: Pharmacist
|
|
81
|
+
:43TX: Biomedical Laboratory
|
|
82
|
+
:44AX: Chief of Medical Staff
|
|
83
|
+
:44BX: Preventive Medicine Physician
|
|
84
|
+
:44DX: Pathologist
|
|
85
|
+
:44EX: Emergency Medicine Physician
|
|
86
|
+
:44FX: Family Physician
|
|
87
|
+
:44GX: General Practice Physician
|
|
88
|
+
:44HX: Nuclear Medicine Physician
|
|
89
|
+
:44JX: Medical Geneticist and Genomicist
|
|
90
|
+
:44KX: Pediatrician
|
|
91
|
+
:44MX: Internal Medicine Physician
|
|
92
|
+
:44NX: Neurologist
|
|
93
|
+
:44OX: Physician
|
|
94
|
+
:44PX: Psychiatrist
|
|
95
|
+
:44RX: Radiologist
|
|
96
|
+
:44SX: Dermatologist
|
|
97
|
+
:44TX: Radiation Oncologist
|
|
98
|
+
:44UX: Occupational Medicine Physician
|
|
99
|
+
:44YX: Critical Care Medicine Physician
|
|
100
|
+
:44ZX: Allergist and Immunologist
|
|
101
|
+
:45AX: Anesthesiologist
|
|
102
|
+
:45BX: Orthopaedic Surgeon
|
|
103
|
+
:45EX: Ophthalmologist
|
|
104
|
+
:45GX: Gynecologic Surgeon and Obstetrician
|
|
105
|
+
:45NX: Otolaryngologist - Head and Neck Surgeon
|
|
106
|
+
:45PX: Physical Medicine Physician
|
|
107
|
+
:45SX: Surgeon
|
|
108
|
+
:45UX: Urologist
|
|
109
|
+
:46AX: Nursing Administrator
|
|
110
|
+
:46FX: Flight Nurse
|
|
111
|
+
:46NX: Clinical Nurse
|
|
112
|
+
:46PX: Mental Health Nurse
|
|
113
|
+
:46SX: Operating Room Nurse
|
|
114
|
+
:46YX: Advanced Practice Registered Nurse (APRN)
|
|
115
|
+
:47BX: Orthodontist
|
|
116
|
+
:47DX: Oral and Maxillofacial Pathologist
|
|
117
|
+
:47EX: Endodontist
|
|
118
|
+
:47GX: Dentist
|
|
119
|
+
:47HX: Periodontist
|
|
120
|
+
:47KX: Pediatric Dentist
|
|
121
|
+
:47PX: Prosthodontist
|
|
122
|
+
:47SX: Oral and Maxillofacial Surgeon
|
|
123
|
+
:48AX: Aerospace Medicine Physician Specialist
|
|
124
|
+
:48GX: General Medical Officer (GMO), Flight Surgeon
|
|
125
|
+
:48OX: Aeromedical Physician
|
|
126
|
+
:48RX: Residency Trained Flight Surgeon
|
|
127
|
+
:48VX: Pilot-Physician
|
|
128
|
+
:51JX: Judge Advocate
|
|
129
|
+
:52RX: Chaplain
|
|
130
|
+
:60C0: Senior Materiel Leader-Upper Echelon
|
|
131
|
+
:61CX: Chemist/Nuclear Chemist
|
|
132
|
+
:61DX: Physicist/Nuclear Engineer
|
|
133
|
+
:62EX: Developmental Engineer
|
|
134
|
+
:62S0: Materiel Leader
|
|
135
|
+
:63AX: Acquisition Manager
|
|
136
|
+
:63G0: Senior Materiel Leader-Lower Echelon
|
|
137
|
+
:63S0: Materiel Leader
|
|
138
|
+
:64PX: Contracting
|
|
139
|
+
:65FX: Financial Management
|
|
140
|
+
:65WX: Cost Analysis
|
|
141
|
+
:71SX: Special Investigations
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# De-glued specialty titles for DAFECD, effective 2025-10-31.
|
|
2
|
+
# Each value is the source PDF title with ONLY spacing/case adjusted --
|
|
3
|
+
# no letters, digits, or punctuation changed. Enforced at build time:
|
|
4
|
+
# the IndexBuilder verifies each override against the raw source title
|
|
5
|
+
# (whitespace-and-case-insensitive equality) and fails the build on drift.
|
|
6
|
+
# Produced as the governed de-gluing step for pdf-reader space loss.
|
|
7
|
+
:1A1X2: Mobility Force Aviator
|
|
8
|
+
:1A1X3: Special Mission Aviator
|
|
9
|
+
:1A1X4: Multi-Domain Operations Aviator
|
|
10
|
+
:1A1X8: Executive Mission Aviator
|
|
11
|
+
:1A8X0: SEL Airborne Intelligence, Surveillance and Reconnaissance (ISR)
|
|
12
|
+
:1A8X1: Airborne Cryptologic Language Analyst (ACLA)
|
|
13
|
+
:1A8X2: Airborne Intelligence, Surveillance, and Reconnaissance (ISR) Operator
|
|
14
|
+
:1B4X1: Cyber Warfare Operations
|
|
15
|
+
:1C0X2: Aviation Resource Management
|
|
16
|
+
:1C1X1: Air Traffic Control
|
|
17
|
+
:1C3X1: All-Domain Command and Control Operations
|
|
18
|
+
:1C5X1: Battle Management Operations
|
|
19
|
+
:1C6X1: Space Systems Operations
|
|
20
|
+
:1C7X1: Airfield Management
|
|
21
|
+
:1C8X3: Radar, Airfield & Weather Systems (RAWS)
|
|
22
|
+
:1D7X1: Warfighter Communications
|
|
23
|
+
:1D7X2: Radio Frequency Transmissions and Electromagnetic Activities (EMA)
|
|
24
|
+
:1D7X3: Cable and Antenna
|
|
25
|
+
:1D7X4: Data Engineering
|
|
26
|
+
:1D7X5: Cybersecurity
|
|
27
|
+
:1H0X1: Aerospace Physiology
|
|
28
|
+
:1N0X1: All Source Intelligence Analyst
|
|
29
|
+
:1N0X2: Intelligence Superintendent
|
|
30
|
+
:1N1X1: Geospatial Intelligence (GEOINT)
|
|
31
|
+
:1N2X1: Signals Intelligence
|
|
32
|
+
:1N2X2: Cryptologic Intelligence Superintendent
|
|
33
|
+
:1N3X1: Cryptologic Language Analyst
|
|
34
|
+
:1N4X1: Cyber Intelligence
|
|
35
|
+
:1N4X2: Cryptologic Analyst & Reporter
|
|
36
|
+
:1N7X1: Human Intelligence Specialist
|
|
37
|
+
:1N8X1: Targeting Analyst
|
|
38
|
+
:1P0X1: Aircrew Flight Equipment
|
|
39
|
+
:1S0X1: Safety
|
|
40
|
+
:1T0X1: Survival, Evasion, Resistance, Escape (SERE) Specialist
|
|
41
|
+
:1U1X1: Remotely Piloted Aircraft (RPA) Pilot
|
|
42
|
+
:1W0X1: Weather
|
|
43
|
+
:1Z1X1: Pararescue
|
|
44
|
+
:1Z2X1: Combat Control
|
|
45
|
+
:1Z3X1: Tactical Air Control Party (TACP)
|
|
46
|
+
:1Z4X1: Special Reconnaissance
|
|
47
|
+
:2A0X0: Avionics
|
|
48
|
+
:2A0X1: Avionics Test Station, Components, and Electronic Warfare Systems
|
|
49
|
+
:2A2X1: MHU-139 Electrical, Environmental and Avionics Technician
|
|
50
|
+
:2A3X0: Fighter Aircraft Maintenance
|
|
51
|
+
:2A3X3: Tactical Aircraft Maintenance
|
|
52
|
+
:2A3X4: Fighter Aircraft Integrated Avionics
|
|
53
|
+
:2A3X5: Advanced Fighter Aircraft Integrated Avionics
|
|
54
|
+
:2A3X7: Tactical Aircraft Maintenance (5th Generation)
|
|
55
|
+
:2A5X0: Airlift/Special Mission Aircraft Maintenance Superintendent
|
|
56
|
+
:2A5X1: Airlift/Special Mission Aircraft Maintenance
|
|
57
|
+
:2A5X2: Helicopter/Tiltrotor Aircraft Maintenance
|
|
58
|
+
:2A5X4: Refuel/Bomber Aircraft Maintenance
|
|
59
|
+
:2A6X0: Aircraft Accessories
|
|
60
|
+
:2A6X1: Aerospace Propulsion
|
|
61
|
+
:2A6X2: Aerospace Ground Equipment
|
|
62
|
+
:2A6X3: Aircrew Egress Systems
|
|
63
|
+
:2A6X4: Aircraft Fuel Systems
|
|
64
|
+
:2A6X5: Aircraft Hydraulic Systems
|
|
65
|
+
:2A6X6: Aircraft Electrical and Environmental Systems
|
|
66
|
+
:2A7X0: Aircraft Fabrication
|
|
67
|
+
:2A7X1: Aircraft Metals Technology
|
|
68
|
+
:2A7X2: Nondestructive Inspection
|
|
69
|
+
:2A7X3: Aircraft Structural Maintenance
|
|
70
|
+
:2A9X4: Heavy Aircraft Integrated Avionics
|
|
71
|
+
:2F0X1: Fuels
|
|
72
|
+
:2G0X1: Logistics Plans
|
|
73
|
+
:2M0X0: Missile and Space Systems Maintenance
|
|
74
|
+
:2M0X1: Missile and Space Systems Electronic Maintenance
|
|
75
|
+
:2M0X2: Missile and Space Systems Maintenance
|
|
76
|
+
:2M0X3: Missile and Space Facilities
|
|
77
|
+
:2P0X1: Precision Measurement Equipment Laboratory
|
|
78
|
+
:2R2X1: Maintenance Management
|
|
79
|
+
:2S0X1: Materiel Management
|
|
80
|
+
:2T0X1: Traffic Management Operations
|
|
81
|
+
:2T1X1: Ground Transportation
|
|
82
|
+
:2T2X1: Air Transportation
|
|
83
|
+
:2T3X0: Vehicle Management
|
|
84
|
+
:2T3X1: Mission Generation Vehicular Equipment Maintenance
|
|
85
|
+
:2T3X7: Fleet Management and Analysis
|
|
86
|
+
:2W0X1: Munitions Systems
|
|
87
|
+
:2W1X1: Aircraft Armament Systems
|
|
88
|
+
:2W2X1: Nuclear Weapons
|
|
89
|
+
:3E0X0: Facility Systems
|
|
90
|
+
:3E0X1: Electrical Systems
|
|
91
|
+
:3E0X2: Electrical Power Production
|
|
92
|
+
:3E1X1: Heating, Ventilation, Air Conditioning, and Refrigeration
|
|
93
|
+
:3E2X0: Heavy Repair
|
|
94
|
+
:3E2X1: Pavements and Construction Equipment
|
|
95
|
+
:3E3X1: Structural
|
|
96
|
+
:3E4X0: Infrastructure Systems
|
|
97
|
+
:3E4X1: Water and Fuel Systems Maintenance
|
|
98
|
+
:3E4X3: Pest Management
|
|
99
|
+
:3E5X1: Engineering
|
|
100
|
+
:3E6X1: Operations Management
|
|
101
|
+
:3E7X1: Fire Protection
|
|
102
|
+
:3E8X1: Explosive Ordnance Disposal
|
|
103
|
+
:3E9X1: Emergency Management
|
|
104
|
+
:3F0X1: Human Resources and Administration
|
|
105
|
+
:3F1X1: Services
|
|
106
|
+
:3F2X1: Education and Training
|
|
107
|
+
:3F3X1: Manpower
|
|
108
|
+
:3F4X1: Equal Opportunity
|
|
109
|
+
:3G0X1: Talent Acquisition
|
|
110
|
+
:3H0X1: Historian
|
|
111
|
+
:3N0X0: Public Affairs
|
|
112
|
+
:3N0X6: Public Affairs
|
|
113
|
+
:3N1X1: Regional Band
|
|
114
|
+
:3N2X1: Premier Band - The USAF Band
|
|
115
|
+
:3N3X1: Premier Band - The USAF Academy Band
|
|
116
|
+
:3P0X1: Security Forces
|
|
117
|
+
:4A0X1: Health Services Management
|
|
118
|
+
:4A1X1: Medical Materiel
|
|
119
|
+
:4A2X1: Biomedical Equipment
|
|
120
|
+
:4B0X1: Bioenvironmental Engineering (BE)
|
|
121
|
+
:4C0X1: Mental Health Service
|
|
122
|
+
:4D0X1: Diet Therapy
|
|
123
|
+
:4E0X1: Public Health
|
|
124
|
+
:4H0X1: Respiratory Care Practitioner
|
|
125
|
+
:4J0X2: Physical Medicine
|
|
126
|
+
:4N0X1: Aerospace Medical Service
|
|
127
|
+
:4N1X1: Surgical Technologist
|
|
128
|
+
:4P0X1: Pharmacy
|
|
129
|
+
:4R0X1: Diagnostic Imaging
|
|
130
|
+
:4T0X0: Medical Laboratory
|
|
131
|
+
:4T0X1: Medical Laboratory
|
|
132
|
+
:4T0X2: Histopathology
|
|
133
|
+
:4V0X1: Ophthalmic
|
|
134
|
+
:4Y0X0: Dental
|
|
135
|
+
:4Y0X1: Dental Assistant
|
|
136
|
+
:4Y0X2: Dental Laboratory
|
|
137
|
+
:5J0X1: Paralegal
|
|
138
|
+
:5R0X1: Religious Affairs
|
|
139
|
+
:6C0X1: Contracting
|
|
140
|
+
:6F0X1: Financial Management and Comptroller
|
|
141
|
+
:7S0X1: Special Investigations
|
|
142
|
+
:8G0X1: Premier Honor Guard
|