gov_codes 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 96c91603e5956e6918476045972a183754d150c8bfef8a8f7d33de5159ca4c60
4
+ data.tar.gz: 72ade76ae58f72cfcc07ff2f301bd3a7067fc5e1fb836ca91438b767ff1198ae
5
+ SHA512:
6
+ metadata.gz: cbf18e6e8e9099d19c023e12ed688374335213dc04ec9c95217a2b4f3df085fa29be735d4de828ddea254306357ff7463d4e2346e344ccde9281ac0c593ca258
7
+ data.tar.gz: 82a5a80e5d3880e65711f7ee09c0d9b3c50b866faf879360997bfec5b98830bd2fe4c49f467e3545800cd80f6e9acd6f3cc41c7735cbdf06c592ff7fdc0ee172
data/.simplecov ADDED
@@ -0,0 +1,15 @@
1
+ SimpleCov.start do
2
+ # enable_coverage :branch
3
+
4
+ # Add any files or directories you want to exclude from coverage
5
+ add_filter "/test/", "lib/gov_codes/version.rb"
6
+
7
+ # Set minimum coverage requirements
8
+ # minimum_coverage 80
9
+
10
+ # Track all files in the lib directory
11
+ track_files "lib/**/*.rb"
12
+
13
+ # Group files by module
14
+ add_group "AFSC", "lib/gov_codes/afsc"
15
+ end
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/standardrb/standard
3
+ ruby_version: 3.1
data/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ ruby 3.4.3
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
+ and this project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## [0.1.0] - 2025-05-02
9
+
10
+ ### Added
11
+
12
+ - AFSC codes
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Gov Codes
2
+
3
+ Understand and process information used by the US government.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'gov_codes'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install gov_codes
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Basic Usage
28
+
29
+ ```ruby
30
+ require 'gov_codes/afsc'
31
+
32
+ # Find an enlisted AFSC code
33
+ code = GovCodes::AFSC.find("1A1X2")
34
+ puts code.name # => "Aircrew operations"
35
+ puts code.career_field # => "1A"
36
+ puts code.career_field_subdivision # => "1A1"
37
+ puts code.skill_level # => "X"
38
+ puts code.specific_afsc # => "1A1X2"
39
+ puts code.shredout # => nil
40
+
41
+ # Find an officer AFSC code
42
+ code = GovCodes::AFSC.find("11M4")
43
+ puts code.name # => "Pilot"
44
+ puts code.career_group # => "11"
45
+ puts code.functional_area # => "M"
46
+ puts code.qualification_level # => "4"
47
+ puts code.shredout # => nil
48
+ ```
49
+
50
+ ### Extending with Custom AFSC Codes
51
+
52
+ You can extend the default AFSC codes with your own custom codes by placing a YAML file in your application's load path:
53
+
54
+ ```ruby
55
+ # In your application's lib/gov_codes/afsc/enlisted.yml
56
+ 9Z:
57
+ name: Custom AFSC
58
+ subcategories:
59
+ 0X1:
60
+ name: Custom Subcategory
61
+ subcategories:
62
+ A:
63
+ name: Custom Shredout
64
+ ```
65
+
66
+ The gem will automatically merge your custom codes with the default codes, overriding any existing codes.
67
+
68
+ ## Development
69
+
70
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
71
+
72
+ To install this gem onto your local machine, run `bundle exec rake install`.
73
+
74
+ This project is managed with [Reissue](https://github.com/SOFware/reissue).
75
+
76
+ To release a new version, make your changes and be sure to update the CHANGELOG.md.
77
+
78
+ To release a new version:
79
+
80
+ 1. `bundle exec rake build:checksum`
81
+ 2. `bundle exec rake release`
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on GitHub at https://github.com/SOFware/gov_codes.
86
+
87
+ ## License
88
+
89
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[test standard]
11
+
12
+ require "reissue/gem"
13
+
14
+ Reissue::Task.create :reissue do |task|
15
+ task.version_file = "lib/gov_codes/version.rb"
16
+ end
@@ -0,0 +1,103 @@
1
+ require "strscan"
2
+ require "yaml"
3
+ require_relative "../data_loader"
4
+
5
+ puts "Loading enlisted.rb"
6
+
7
+ module GovCodes
8
+ module AFSC
9
+ module Enlisted
10
+ class Parser
11
+ def initialize(code)
12
+ @code = code
13
+ end
14
+
15
+ def parse
16
+ scanner = StringScanner.new(@code.to_s)
17
+ result = {
18
+ prefix: nil,
19
+ career_group: nil,
20
+ career_field: nil,
21
+ career_field_subdivision: nil,
22
+ skill_level: nil,
23
+ specific_afsc: nil,
24
+ subcategory: nil,
25
+ shredout: nil
26
+ }
27
+
28
+ # Scan for prefix (optional)
29
+ result[:prefix] = scanner.scan(/[A-Z]/)
30
+
31
+ # Scan for career group (single digit)
32
+ career_group = scanner.scan(/\d/)
33
+ return result unless career_group
34
+ result[:career_group] = career_group.to_sym
35
+
36
+ # Scan for career field letter and combine with career group
37
+ career_field_letter = scanner.scan(/[A-Z]/)
38
+ return result unless career_field_letter
39
+ result[:career_field] = :"#{career_group}#{career_field_letter}"
40
+
41
+ # Scan for subdivision digit and combine with career field
42
+ subdivision_digit = scanner.scan(/\d/)
43
+ return result unless subdivision_digit
44
+ result[:career_field_subdivision] = :"#{result[:career_field]}#{subdivision_digit}"
45
+
46
+ # Scan for skill level
47
+ skill_level = scanner.scan(/[\dX]/)
48
+ return result unless skill_level
49
+ result[:skill_level] = skill_level.to_sym
50
+
51
+ # Scan for specific AFSC digit and combine with previous components
52
+ specific_digit = scanner.scan(/\d/)
53
+ return result unless specific_digit
54
+ result[:specific_afsc] = :"#{result[:career_field_subdivision]}#{result[:skill_level]}#{specific_digit}"
55
+
56
+ result[:subcategory] = result[:specific_afsc][2..4].to_sym
57
+
58
+ # Scan for shredout (optional)
59
+ result[:shredout] = scanner.scan(/[A-Z]/)&.to_sym
60
+
61
+ # Check if we've reached the end of the string
62
+ return result unless scanner.eos?
63
+
64
+ result
65
+ end
66
+ end
67
+
68
+ extend GovCodes::DataLoader
69
+ DATA = data
70
+
71
+ Code = Data.define(
72
+ :prefix,
73
+ :career_group,
74
+ :career_field,
75
+ :career_field_subdivision,
76
+ :skill_level,
77
+ :specific_afsc,
78
+ :subcategory,
79
+ :shredout,
80
+ :name
81
+ )
82
+
83
+ def self.find(code)
84
+ code = code.to_s
85
+ CODES[code] ||= begin
86
+ parser = Parser.new(code)
87
+ result = parser.parse
88
+
89
+ return nil if result.reject { |_, v| v.nil? }.empty?
90
+
91
+ # Find the name by recursively searching the codes hash
92
+ name = find_name_recursive(result)
93
+
94
+ # Add the name to the result
95
+ result[:name] = name
96
+
97
+ # Create a new Code object with the result
98
+ Code.new(**result)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,725 @@
1
+ 1A:
2
+ name: Aircrew operations
3
+ subcategories:
4
+ 1X2:
5
+ name: Mobility force aviator
6
+ subcategories:
7
+ A:
8
+ name: C-5 flight engineer
9
+ B:
10
+ name: C-5 loadmaster
11
+ C:
12
+ name: C-17 loadmaster
13
+ D:
14
+ name: C-130J loadmaster
15
+ E:
16
+ name: WC-130J loadmaster
17
+ F:
18
+ name: E-3 flight engineer
19
+ G:
20
+ name: KC-46 boom operator
21
+ H:
22
+ name: KC-135 boom operator
23
+ I:
24
+ name: KC-10 boom operator
25
+ J:
26
+ name: KC-10 flight engineer
27
+ K:
28
+ name: E-8 flight engineer
29
+ L:
30
+ name: C-130H flight engineer
31
+ M:
32
+ name: CEM mobility force aviator
33
+ N:
34
+ name: C-130H loadmaster
35
+ O:
36
+ name: EC-130H flight engineer
37
+ Z:
38
+ name: Data mask mobility force aviator
39
+ 1X3:
40
+ name: Special mission aviator
41
+ subcategories:
42
+ A:
43
+ name: AC-130J gunner
44
+ B:
45
+ name: CV-22 flight engineer
46
+ C:
47
+ name: UH-1N flight engineer
48
+ D:
49
+ name: HC-130J loadmaster
50
+ E:
51
+ name: MC-130J loadmaster
52
+ F:
53
+ name: HH-60 flight engineer
54
+ G:
55
+ name: MH-139 flight engineer
56
+ H:
57
+ name: C-146 loadmaster
58
+ S:
59
+ name: CEM special mission aviator
60
+ Z:
61
+ name: SMA data masked
62
+ 1X8:
63
+ name: Executive mission aviator
64
+ subcategories:
65
+ A:
66
+ name: C-32/C-40 flight attendant
67
+ B:
68
+ name: C-32/C-40 communications systems operator
69
+ C:
70
+ name: C-37 flight attendant
71
+ D:
72
+ name: C-37 flight engineer
73
+ E:
74
+ name: CEM executive mission aviator
75
+ F:
76
+ name: C-37 communications systems operator
77
+ G:
78
+ name: E-4 flight engineer
79
+ H:
80
+ name: E-4 flight attendant
81
+ I:
82
+ name: E-4 communications systems operator/technician/controller
83
+ J:
84
+ name: Presidential Airlift Group, flight engineer
85
+ K:
86
+ name: Presidential Airlift Group, flight attendant
87
+ L:
88
+ name: Presidential Airlift Group, communication system operator
89
+ Z:
90
+ name: EMA data mask
91
+ 1B:
92
+ name: Cyber warfare
93
+ subcategories:
94
+ 4X1:
95
+ name: Cyber warfare operations
96
+ 1C:
97
+ name: Command and control systems operations
98
+ subcategories:
99
+ 0X2:
100
+ name: Aviation resource management
101
+ 1X1:
102
+ name: Air traffic control
103
+ 3X1:
104
+ name: All-domain command and control operations (C2 OPS)
105
+ 5X1:
106
+ name: Battle management ops
107
+ subcategories:
108
+ D:
109
+ name: Weapons director
110
+ 6X1:
111
+ name: Space systems operations
112
+ 7X1:
113
+ name: Airfield management
114
+ 8X3:
115
+ name: Radar, airfield & weather systems (RAWS)
116
+ 1D:
117
+ name: Cyber defense operations
118
+ subcategories:
119
+ 7X1:
120
+ name: Cyber defense operations (to become IT systems technician)
121
+ subcategories:
122
+ A:
123
+ name: Network operations (to be phased out)
124
+ B:
125
+ name: Systems operations (to be phased out)
126
+ D:
127
+ name: Security operations (to be phased out)
128
+ E:
129
+ name: Client systems operations (to be phased out)
130
+ K:
131
+ name: Knowledge operations (to be phased out)
132
+ M:
133
+ name: Mission defense activities (to become cybersecurity in November 2024)
134
+ P:
135
+ name: Data operations (to become 1D7X4 data engineering)
136
+ Q:
137
+ name: Enterprise operations
138
+ R:
139
+ name: RF operations (to become 1D7X2R RF transmissions operations)
140
+ W:
141
+ name: Expeditionary communications
142
+ Z:
143
+ name: Software development operations (to be phased out)
144
+ 7X2:
145
+ name: Spectrum defense operations (to become RF & electromagnetic activities)
146
+ subcategories:
147
+ F:
148
+ name: Spectrum operations (to become spectrum management)
149
+ 7X3:
150
+ name: Cable and antenna defense operations (to become cable & antenna operations)
151
+ subcategories:
152
+ C:
153
+ name: Cable and antenna operations (to be phased out)
154
+ 1H:
155
+ name: Aerospace physiology
156
+ subcategories:
157
+ 0X1:
158
+ name: Aerospace physiology
159
+ 1N:
160
+ name: Intelligence
161
+ subcategories:
162
+ 0X1:
163
+ name: All Source intelligence analyst
164
+ 092:
165
+ name: Intelligence superintendent
166
+ 1X1:
167
+ name: Geospatial intelligence
168
+ subcategories:
169
+ A:
170
+ name: Imagery analyst
171
+ 292:
172
+ name: Cryptologic intelligence superintendent
173
+ 2X1:
174
+ name: Signals intelligence
175
+ subcategories:
176
+ A:
177
+ name: Electronic non-communications analyst
178
+ C:
179
+ name: Communications analyst
180
+ 3X1:
181
+ name: Cryptologic language analyst
182
+ subcategories:
183
+ F:
184
+ name: Arabic
185
+ G:
186
+ name: Chinese
187
+ H:
188
+ name: Korean
189
+ I:
190
+ name: Russian
191
+ J:
192
+ name: Spanish
193
+ K:
194
+ name: Persian
195
+ M:
196
+ name: Pashto
197
+ N:
198
+ name: Urdu
199
+ Z:
200
+ name: Divested languages
201
+ 4X1:
202
+ name: Cyber intelligence
203
+ subcategories:
204
+ A:
205
+ name: Analyst
206
+ 4X2:
207
+ name: Cryptologic analyst & reporter
208
+ 7X1:
209
+ name: Human intelligence specialist
210
+ 8X1:
211
+ name: Targeting analyst
212
+ 1P:
213
+ name: Aircrew flight equipment
214
+ subcategories:
215
+ 0X1:
216
+ name: Aircrew flight equipment
217
+ subcategories:
218
+ A:
219
+ name: Ejection seat aircraft
220
+ B:
221
+ name: Non-ejection seat aircraft
222
+ 1S:
223
+ name: Safety
224
+ subcategories:
225
+ 0X1:
226
+ name: Safety
227
+ 1T:
228
+ name: Special warfare enabler
229
+ subcategories:
230
+ 0X1:
231
+ name: Survival, evasion, resistance, escape (SERE) specialist
232
+ 1U:
233
+ name: Aircrew operations (RPA)
234
+ subcategories:
235
+ 0X1:
236
+ name: Sensor operator
237
+ subcategories:
238
+ N:
239
+ name: RQ-4
240
+ Q:
241
+ name: MQ-1
242
+ R:
243
+ name: MQ-9
244
+ T:
245
+ name: MC-12
246
+ U:
247
+ name: AC-130U
248
+ V:
249
+ name: AC-130J
250
+ W:
251
+ name: AC-130W
252
+ Y:
253
+ name: CAA
254
+ 1X1:
255
+ name: Remotely piloted aircraft (RPA) pilot
256
+ subcategories:
257
+ O:
258
+ name: RQ-4
259
+ R:
260
+ name: MQ-9
261
+ 1W:
262
+ name: Weather
263
+ subcategories:
264
+ 0X1:
265
+ name: Weather
266
+ 2A:
267
+ name: Aerospace Maintenance
268
+ subcategories:
269
+ 0X1:
270
+ name: Avionics Test Station and Components
271
+ 3X3:
272
+ name: Tactical Aircraft Maintenance
273
+ subcategories:
274
+ A:
275
+ name: A-10/U-2
276
+ B:
277
+ name: F-15
278
+ C:
279
+ name: F-16
280
+ D:
281
+ name: F-22
282
+ E:
283
+ name: F-35
284
+ M:
285
+ name: Special Operations Forces/Personnel Recovery
286
+ 3X4:
287
+ name: Fighter Aircraft Integrated Avionics
288
+ subcategories:
289
+ A:
290
+ name: A-10/F-15
291
+ B:
292
+ name: F-16
293
+ C:
294
+ name: F-22
295
+ D:
296
+ name: F-35
297
+ 3X5:
298
+ name: Advanced Fighter Aircraft Integrated Avionics
299
+ 3X7:
300
+ name: Tactical Aircraft Maintenance (5th Generation)
301
+ subcategories:
302
+ A:
303
+ name: F-22
304
+ B:
305
+ name: F-35
306
+ 3X8:
307
+ name: Remotely Piloted Aircraft Maintenance
308
+ subcategories:
309
+ A:
310
+ name: MQ-1/MQ-9
311
+ B:
312
+ name: RQ-4
313
+ 5X1:
314
+ name: Aerospace Maintenance (CEM)
315
+ 5X2:
316
+ name: Helicopter/Tiltrotor Aircraft Maintenance
317
+ subcategories:
318
+ A:
319
+ name: UH-1
320
+ B:
321
+ name: HH-60
322
+ C:
323
+ name: CV-22
324
+ 5X4:
325
+ name: Refuel/Bomber Aircraft Maintenance
326
+ subcategories:
327
+ A:
328
+ name: KC-46
329
+ B:
330
+ name: KC-135
331
+ C:
332
+ name: B-1
333
+ D:
334
+ name: B-2
335
+ E:
336
+ name: B-52
337
+ 7X1:
338
+ name: Aircraft Metals Technology
339
+ 7X2:
340
+ name: Nondestructive Inspection
341
+ 7X3:
342
+ name: Aircraft Structural Maintenance
343
+ 7X4:
344
+ name: Survival Equipment
345
+ 8X1:
346
+ name: Mobility Air Forces Integrated Communication/Navigation/Mission Systems
347
+ 8X2:
348
+ name: Mobility Air Forces Integrated Instrument and Flight Control Systems
349
+ 2F:
350
+ name: Fuels
351
+ subcategories:
352
+ 0X1:
353
+ name: Fuels
354
+ 2G:
355
+ name: Logistics Plans
356
+ subcategories:
357
+ 0X1:
358
+ name: Logistics Plans
359
+ 2M:
360
+ name: Missile and Space Systems Maintenance
361
+ subcategories:
362
+ 0X1:
363
+ name: Missile and Space Systems Electronic Maintenance
364
+ 0X2:
365
+ name: Missile and Space Systems Maintenance
366
+ 0X3:
367
+ name: Missile and Space Facilities
368
+ 2P:
369
+ name: Precision Measurement Equipment Laboratory
370
+ subcategories:
371
+ 0X1:
372
+ name: Precision Measurement Equipment Laboratory
373
+ 2R:
374
+ name: Maintenance Management
375
+ subcategories:
376
+ 0X1:
377
+ name: Maintenance Management Analysis
378
+ 1X1:
379
+ name: Maintenance Management Production
380
+ 2S:
381
+ name: Materiel Management
382
+ subcategories:
383
+ 0X1:
384
+ name: Materiel Management
385
+ 2T:
386
+ name: Transportation and Vehicle Management
387
+ subcategories:
388
+ 0X1:
389
+ name: Traffic Management
390
+ 1X1:
391
+ name: Vehicle Operations
392
+ 2X1:
393
+ name: Vehicle Management
394
+ 3X1:
395
+ name: Mission Generation Vehicular Equipment Maintenance
396
+ 3X2:
397
+ name: Special Vehicle Maintenance
398
+ 3X7:
399
+ name: Vehicle Management and Analysis
400
+ 2W:
401
+ name: Munitions and Weapons
402
+ subcategories:
403
+ 0X1:
404
+ name: Munitions Systems
405
+ 1X1:
406
+ name: Aircraft Armament Systems
407
+ subcategories:
408
+ A:
409
+ name: A-10/F-15
410
+ B:
411
+ name: F-16
412
+ C:
413
+ name: F-22
414
+ D:
415
+ name: F-35
416
+ 3A:
417
+ name: Personnel
418
+ subcategories:
419
+ 0X1:
420
+ name: Personnel
421
+ 3D:
422
+ name: Information Technology
423
+ subcategories:
424
+ 0X1:
425
+ name: Knowledge Operations Management
426
+ 0X2:
427
+ name: Cyber Systems Operations
428
+ 0X3:
429
+ name: Cyber Surety
430
+ 0X4:
431
+ name: Computer Systems Programming
432
+ 1X1:
433
+ name: Client Systems
434
+ 1X2:
435
+ name: Cyber Systems Operations
436
+ 1X3:
437
+ name: Cyber Surety
438
+ 3E:
439
+ name: Engineering
440
+ subcategories:
441
+ 0X1:
442
+ name: Engineering
443
+ 3F:
444
+ name: Force Support
445
+ subcategories:
446
+ 0X1:
447
+ name: Services
448
+ 3G:
449
+ name: Ground Transportation
450
+ subcategories:
451
+ 0X1:
452
+ name: Ground Transportation
453
+ 3H:
454
+ name: Historian
455
+ subcategories:
456
+ 0X1:
457
+ name: Historian
458
+ 3M:
459
+ name: Services
460
+ subcategories:
461
+ 0X1:
462
+ name: Services
463
+ 3N:
464
+ name: Public Affairs
465
+ subcategories:
466
+ 0X1:
467
+ name: Public Affairs
468
+ 3P:
469
+ name: Security Forces
470
+ subcategories:
471
+ 0X1:
472
+ name: Security Forces
473
+ 3S:
474
+ name: Support
475
+ subcategories:
476
+ 0X1:
477
+ name: Support
478
+ 4A:
479
+ name: Health Services Management
480
+ subcategories:
481
+ 1X1:
482
+ name: Medical material
483
+ 4B:
484
+ name: Biomedical Equipment
485
+ subcategories:
486
+ 0X1:
487
+ name: Biomedical Equipment
488
+ 4C:
489
+ name: Mental Health Service
490
+ subcategories:
491
+ 0X1:
492
+ name: Mental Health Service
493
+ 4D:
494
+ name: Diagnostic Imaging
495
+ subcategories:
496
+ 0X1:
497
+ name: Diagnostic Imaging
498
+ 4E:
499
+ name: Aerospace Physiology
500
+ subcategories:
501
+ 0X1:
502
+ name: Aerospace Physiology
503
+ 4H:
504
+ name: Cardiopulmonary Laboratory
505
+ subcategories:
506
+ 0X1:
507
+ name: Cardiopulmonary Laboratory
508
+ 4J:
509
+ name: Physical Medicine
510
+ subcategories:
511
+ 0X1:
512
+ name: Physical Medicine
513
+ 4M:
514
+ name: Aerospace and Operational Physiology
515
+ subcategories:
516
+ 0X1:
517
+ name: Aerospace and Operational Physiology
518
+ 4N:
519
+ name: Aerospace Medical Service
520
+ subcategories:
521
+ 0X1:
522
+ name: Aerospace Medical Service
523
+ 4P:
524
+ name: Pharmacy
525
+ subcategories:
526
+ 0X1:
527
+ name: Pharmacy
528
+ 4R:
529
+ name: Diagnostic Molecular
530
+ subcategories:
531
+ 0X1:
532
+ name: Diagnostic Molecular
533
+ 4T:
534
+ name: Laboratory
535
+ subcategories:
536
+ 0X1:
537
+ name: Laboratory
538
+ 0X2:
539
+ name: Histopathology
540
+ 4V:
541
+ name: Ophthalmic
542
+ subcategories:
543
+ 0X1:
544
+ name: Ophthalmic
545
+ subcategories:
546
+ S:
547
+ name: Ophthalmic
548
+ 4Y:
549
+ name: Dental Assistant
550
+ subcategories:
551
+ 0X1:
552
+ name: Dental Assistant
553
+ 5J:
554
+ name: Paralegal
555
+ subcategories:
556
+ 0X1:
557
+ name: Paralegal
558
+ 5R:
559
+ name: Religious Affairs
560
+ subcategories:
561
+ 0X1:
562
+ name: Religious Affairs
563
+ 6C:
564
+ name: Contracting
565
+ subcategories:
566
+ 0X1:
567
+ name: Contracting
568
+ 6F:
569
+ name: Financial Management and Comptroller
570
+ subcategories:
571
+ 0X1:
572
+ name: Financial Management and Comptroller
573
+ 7S:
574
+ name: Special Investigations
575
+ subcategories:
576
+ 0X1:
577
+ name: Special Investigations
578
+ 8A:
579
+ name: Career Development
580
+ subcategories:
581
+ 100:
582
+ name: Career Assistance Advisor
583
+ 8B:
584
+ name: Military Training
585
+ subcategories:
586
+ 000:
587
+ name: Military Training Instructor
588
+ 100:
589
+ name: Military Training Leader
590
+ 200:
591
+ name: Academy Military Training NCO
592
+ 8C:
593
+ name: Airman & Family Readiness
594
+ subcategories:
595
+ 000:
596
+ name: Airman & Family Readiness Center NCO
597
+ 8D:
598
+ name: Linguist
599
+ subcategories:
600
+ 100:
601
+ name: Defense Attaché
602
+ 8F:
603
+ name: First Sergeant
604
+ subcategories:
605
+ 000:
606
+ name: First Sergeant
607
+ 8G:
608
+ name: USAF Honor Guard
609
+ subcategories:
610
+ 000:
611
+ name: USAF Honor Guard
612
+ 8H:
613
+ name: Nuclear Weapons
614
+ subcategories:
615
+ 000:
616
+ name: Nuclear Weapons Instructor
617
+ 8K:
618
+ name: Enlisted Professional Military Education
619
+ subcategories:
620
+ 000:
621
+ name: Enlisted Professional Military Education Instructor
622
+ 8L:
623
+ name: Recruiting
624
+ subcategories:
625
+ 000:
626
+ name: Recruiting Service
627
+ 100:
628
+ name: Recruiting Liaison NCO
629
+ 200:
630
+ name: In-Service Recruiter
631
+ 8P:
632
+ name: Security Forces
633
+ subcategories:
634
+ 100:
635
+ name: Military Working Dog Handler
636
+ 8R:
637
+ name: Enlisted Accessions
638
+ subcategories:
639
+ 000:
640
+ name: Enlisted Accessions Recruiter
641
+ 300:
642
+ name: Flight chief
643
+ subcategories:
644
+ A:
645
+ name: Flight chief
646
+ 8S:
647
+ name: Missile Facility
648
+ subcategories:
649
+ 000:
650
+ name: Missile Facility Manager
651
+ 8T:
652
+ name: Technical Training
653
+ subcategories:
654
+ 000:
655
+ name: Technical Training Instructor
656
+ 9A:
657
+ name: Awaiting Retraining-Reasons Beyond Control
658
+ subcategories:
659
+ 000:
660
+ name: Awaiting Retraining-Reasons Beyond Control
661
+ 9C:
662
+ name: CMSgt of the Air Force
663
+ subcategories:
664
+ 000:
665
+ name: CMSgt of the Air Force
666
+ 9D:
667
+ name: Dormitory Manager
668
+ subcategories:
669
+ 000:
670
+ name: Dormitory Manager
671
+ 9E:
672
+ name: Command Chief Master Sergeant
673
+ subcategories:
674
+ 000:
675
+ name: Command Chief Master Sergeant
676
+ 9F:
677
+ name: First Term Airmen
678
+ subcategories:
679
+ 000:
680
+ name: First Term Airmen
681
+ 9G:
682
+ name: Air Force Specialty Training
683
+ subcategories:
684
+ 000:
685
+ name: Air Force Specialty Training
686
+ 9J:
687
+ name: Prisoner
688
+ subcategories:
689
+ 000:
690
+ name: Prisoner
691
+ 9L:
692
+ name: Interpreter/Translator
693
+ subcategories:
694
+ 000:
695
+ name: Interpreter/Translator
696
+ 9P:
697
+ name: Patient
698
+ subcategories:
699
+ 000:
700
+ name: Patient
701
+ 9R:
702
+ name: Civil Air Patrol-USAF Reserve Assistance
703
+ subcategories:
704
+ 000:
705
+ name: Civil Air Patrol-USAF Reserve Assistance
706
+ 9S:
707
+ name: Technical Applications Specialist
708
+ subcategories:
709
+ 100:
710
+ name: Technical Applications Specialist
711
+ 9T:
712
+ name: Basic Enlisted Airman
713
+ subcategories:
714
+ 000:
715
+ name: Basic Enlisted Airman
716
+ 9U:
717
+ name: Basic Enlisted Helper
718
+ subcategories:
719
+ 000:
720
+ name: Basic Enlisted Helper
721
+ 9W:
722
+ name: Combat Wounded Warrior
723
+ subcategories:
724
+ 000:
725
+ name: Combat Wounded Warrior
@@ -0,0 +1,84 @@
1
+ require "strscan"
2
+ require "yaml"
3
+ require_relative "../data_loader"
4
+
5
+ module GovCodes
6
+ module AFSC
7
+ module Officer
8
+ class Parser
9
+ def initialize(code)
10
+ @code = code
11
+ end
12
+
13
+ def parse
14
+ scanner = StringScanner.new(@code.to_s)
15
+ result = {
16
+ prefix: nil,
17
+ career_group: nil,
18
+ functional_area: nil,
19
+ qualification_level: nil,
20
+ shredout: nil
21
+ }
22
+
23
+ # Scan for prefix (optional)
24
+ result[:prefix] = scanner.scan(/[A-Z]/)
25
+
26
+ # Scan for career group (two digits)
27
+ career_group = scanner.scan(/\d{2}/)
28
+ return result unless career_group
29
+ result[:career_group] = career_group
30
+
31
+ # Scan for functional area (uppercase letter)
32
+ functional_area = scanner.scan(/[A-Z]/)
33
+ return result unless functional_area
34
+ result[:functional_area] = functional_area
35
+
36
+ # Scan for qualification level (digit 0-4)
37
+ qualification_level = scanner.scan(/[0-4]/)
38
+ return result unless qualification_level
39
+ result[:qualification_level] = qualification_level
40
+
41
+ # Scan for shredout (optional)
42
+ result[:shredout] = scanner.scan(/[A-Z]/)
43
+
44
+ # Check if we've reached the end of the string
45
+ return result unless scanner.eos?
46
+
47
+ result
48
+ end
49
+ end
50
+
51
+ extend GovCodes::DataLoader
52
+ DATA = data
53
+
54
+ Code = Data.define(
55
+ :prefix,
56
+ :career_group,
57
+ :functional_area,
58
+ :qualification_level,
59
+ :shredout,
60
+ :name
61
+ )
62
+
63
+ def self.find(code)
64
+ code = code.to_s
65
+ parser = Parser.new(code)
66
+ result = parser.parse
67
+
68
+ return nil if result.reject { |_, v| v.nil? }.empty?
69
+
70
+ # Find the name from the codes data
71
+ career_group = result[:career_group]
72
+ functional_area = result[:functional_area]
73
+
74
+ # Look up the name in the codes hash
75
+ name = find_name(career_group, functional_area)
76
+
77
+ # Add the name to the result
78
+ result[:name] = name
79
+
80
+ Code.new(**result)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "afsc/enlisted"
2
+ require_relative "afsc/officer"
3
+
4
+ module GovCodes
5
+ module AFSC
6
+ def self.find(code)
7
+ AFSC::Enlisted.find(code) ||
8
+ AFSC::Officer.find(code)
9
+ end
10
+
11
+ def self.reset_data(lookup: $LOAD_PATH)
12
+ Enlisted.reset_data(lookup:)
13
+ Officer.reset_data(lookup:)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,75 @@
1
+ require "yaml"
2
+
3
+ module GovCodes
4
+ # DataLoader module provides shared functionality for loading YAML data
5
+ # from multiple paths in the lookup array
6
+ module DataLoader
7
+ def self.extended(base)
8
+ base.const_set(:CODES, {})
9
+ base.private_constant(:CODES)
10
+ end
11
+
12
+ def data(lookup: $LOAD_PATH)
13
+ data = {}
14
+
15
+ namespace_parts = name.split("::")
16
+ .map { |it| it.gsub(/([A-Z])([a-z])/, '_\1\2').downcase }
17
+ .map { |it| it.sub(/^_/, "") }
18
+
19
+ # Append .yml to the last item
20
+ namespace_parts[-1] = "#{namespace_parts[-1]}.yml"
21
+
22
+ # Iterate through each path in the lookup array
23
+ files = lookup.map do |dir|
24
+ yaml_path = File.join(dir, *namespace_parts)
25
+ yaml_path if File.exist?(yaml_path)
26
+ end
27
+ .compact
28
+ .uniq
29
+ files.each do |path|
30
+ data.merge!(YAML.load_file(path, symbolize_names: true))
31
+ end
32
+
33
+ data
34
+ end
35
+
36
+ def reset_data(lookup: $LOAD_PATH)
37
+ remove_const(:DATA)
38
+ const_set(:DATA, data(lookup:).freeze)
39
+ remove_const(:CODES)
40
+ const_set(:CODES, {})
41
+ end
42
+
43
+ def find_name_recursive(result)
44
+ # Start with the career field (e.g., "1N")
45
+ base_code = result[:career_field].to_sym
46
+
47
+ loaded_data = self::DATA
48
+ # Look up in the codes hash
49
+ if loaded_data[base_code]
50
+ # If we have a subcategory, try to find a more specific name
51
+ if result[:subcategory] &&
52
+ loaded_data.dig(base_code, :subcategories) &&
53
+ loaded_data.dig(base_code, :subcategories, result[:subcategory])
54
+
55
+ subdivision = loaded_data.dig(base_code, :subcategories, result[:subcategory])
56
+ # If we have a shredout, try to find an even more specific name
57
+ if result[:shredout] &&
58
+ subdivision.dig(:subcategories) &&
59
+ subdivision.dig(:subcategories, result[:shredout])
60
+ return subdivision.dig(:subcategories, result[:shredout], :name)
61
+ end
62
+
63
+ # Return the subdivision name if no shredout match
64
+ return subdivision.dig(:name)
65
+ end
66
+
67
+ # Return the base name if no subdivision match
68
+ return loaded_data.dig(base_code, :name)
69
+ end
70
+
71
+ # Return a default if no match found
72
+ "Unknown"
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GovCodes
4
+ VERSION = "0.1.0"
5
+ end
data/lib/gov_codes.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gov_codes/version"
4
+
5
+ module GovCodes
6
+ class Error < StandardError; end
7
+ end
data/sig/gov_codes.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module GovCodes
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gov_codes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jim Gay
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Process and understand codes used by the US government.
13
+ email:
14
+ - jim@saturnflyer.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".simplecov"
20
+ - ".standard.yml"
21
+ - ".tool-versions"
22
+ - CHANGELOG.md
23
+ - README.md
24
+ - Rakefile
25
+ - lib/gov_codes.rb
26
+ - lib/gov_codes/afsc.rb
27
+ - lib/gov_codes/afsc/enlisted.rb
28
+ - lib/gov_codes/afsc/enlisted.yml
29
+ - lib/gov_codes/afsc/officer.rb
30
+ - lib/gov_codes/data_loader.rb
31
+ - lib/gov_codes/version.rb
32
+ - sig/gov_codes.rbs
33
+ homepage: https://github.com/SOFware/gov_codes
34
+ licenses: []
35
+ metadata:
36
+ homepage_uri: https://github.com/SOFware/gov_codes
37
+ source_code_uri: https://github.com/SOFware/gov_codes
38
+ changelog_uri: https://github.com/SOFware/gov_codes/blob/main/CHANGELOG.md
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.1.0
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubygems_version: 3.6.7
54
+ specification_version: 4
55
+ summary: Handle codes used by the US government.
56
+ test_files: []