1use serde::{Deserialize, Serialize};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
27pub enum MosCode {
28 Alpha18A,
30 Alpha180A,
32 Zulu18Z,
34 Fox18F,
36 Bravo18B,
38 Echo18E,
40 Delta18D,
42 Charlie18C,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
48pub enum OdaSlot {
49 VentureCommander,
51 OperationsDeputy,
53 ProcessArchitect,
55 DeepResearcher,
57 GrowthEngineer1,
59 GrowthEngineer2,
61 Communications1,
63 Communications2,
65 HrPeopleOps1,
67 HrPeopleOps2,
69 PlatformEngineer1,
71 PlatformEngineer2,
73}
74
75impl OdaSlot {
76 pub const ALL: [OdaSlot; 12] = [
78 Self::VentureCommander,
79 Self::OperationsDeputy,
80 Self::ProcessArchitect,
81 Self::DeepResearcher,
82 Self::GrowthEngineer1,
83 Self::GrowthEngineer2,
84 Self::Communications1,
85 Self::Communications2,
86 Self::HrPeopleOps1,
87 Self::HrPeopleOps2,
88 Self::PlatformEngineer1,
89 Self::PlatformEngineer2,
90 ];
91
92 pub const FOUNDERS: [OdaSlot; 2] = [Self::HrPeopleOps1, Self::DeepResearcher];
94
95 #[must_use]
97 pub const fn mos_code(&self) -> MosCode {
98 match self {
99 Self::VentureCommander => MosCode::Alpha18A,
100 Self::OperationsDeputy => MosCode::Alpha180A,
101 Self::ProcessArchitect => MosCode::Zulu18Z,
102 Self::DeepResearcher => MosCode::Fox18F,
103 Self::GrowthEngineer1 | Self::GrowthEngineer2 => MosCode::Bravo18B,
104 Self::Communications1 | Self::Communications2 => MosCode::Echo18E,
105 Self::HrPeopleOps1 | Self::HrPeopleOps2 => MosCode::Delta18D,
106 Self::PlatformEngineer1 | Self::PlatformEngineer2 => MosCode::Charlie18C,
107 }
108 }
109
110 #[must_use]
112 pub const fn is_founding(&self) -> bool {
113 matches!(self, Self::HrPeopleOps1 | Self::DeepResearcher)
114 }
115
116 #[must_use]
118 pub const fn display_name(&self) -> &'static str {
119 match self {
120 Self::VentureCommander => "Venture Commander",
121 Self::OperationsDeputy => "Operations Deputy",
122 Self::ProcessArchitect => "Process Architect",
123 Self::DeepResearcher => "Deep Researcher",
124 Self::GrowthEngineer1 => "Growth Engineer 1",
125 Self::GrowthEngineer2 => "Growth Engineer 2",
126 Self::Communications1 => "Communications 1",
127 Self::Communications2 => "Communications 2",
128 Self::HrPeopleOps1 => "HR/People Ops 1",
129 Self::HrPeopleOps2 => "HR/People Ops 2",
130 Self::PlatformEngineer1 => "Platform Engineer 1",
131 Self::PlatformEngineer2 => "Platform Engineer 2",
132 }
133 }
134
135 #[must_use]
137 pub const fn slug(&self) -> &'static str {
138 match self {
139 Self::VentureCommander => "venturecommander",
140 Self::OperationsDeputy => "operationsdeputy",
141 Self::ProcessArchitect => "processarchitect",
142 Self::DeepResearcher => "deepresearcher",
143 Self::GrowthEngineer1 => "growthengineer1",
144 Self::GrowthEngineer2 => "growthengineer2",
145 Self::Communications1 => "communications1",
146 Self::Communications2 => "communications2",
147 Self::HrPeopleOps1 => "hrpeopleops1",
148 Self::HrPeopleOps2 => "hrpeopleops2",
149 Self::PlatformEngineer1 => "platformengineer1",
150 Self::PlatformEngineer2 => "platformengineer2",
151 }
152 }
153
154 #[must_use]
156 pub const fn authority_depth(&self) -> u32 {
157 match self {
158 Self::VentureCommander => 0,
159 Self::OperationsDeputy => 1,
160 Self::ProcessArchitect => 2,
161 Self::DeepResearcher => 2,
162 Self::GrowthEngineer1
163 | Self::GrowthEngineer2
164 | Self::Communications1
165 | Self::Communications2
166 | Self::HrPeopleOps1
167 | Self::HrPeopleOps2
168 | Self::PlatformEngineer1
169 | Self::PlatformEngineer2 => 3,
170 }
171 }
172}
173
174impl std::fmt::Display for OdaSlot {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.write_str(self.slug())
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn all_slots_count() {
186 assert_eq!(OdaSlot::ALL.len(), 12);
187 }
188
189 #[test]
190 fn founders() {
191 assert_eq!(OdaSlot::FOUNDERS.len(), 2);
192 for f in &OdaSlot::FOUNDERS {
193 assert!(f.is_founding());
194 }
195 assert!(!OdaSlot::VentureCommander.is_founding());
197 assert!(!OdaSlot::PlatformEngineer1.is_founding());
198 }
199
200 #[test]
201 fn mos_codes() {
202 assert_eq!(OdaSlot::VentureCommander.mos_code(), MosCode::Alpha18A);
203 assert_eq!(OdaSlot::DeepResearcher.mos_code(), MosCode::Fox18F);
204 assert_eq!(OdaSlot::HrPeopleOps1.mos_code(), MosCode::Delta18D);
205 assert_eq!(OdaSlot::HrPeopleOps2.mos_code(), MosCode::Delta18D);
206 assert_eq!(OdaSlot::GrowthEngineer1.mos_code(), MosCode::Bravo18B);
207 assert_eq!(OdaSlot::GrowthEngineer2.mos_code(), MosCode::Bravo18B);
208 }
209
210 #[test]
211 fn authority_depth_hierarchy() {
212 assert_eq!(OdaSlot::VentureCommander.authority_depth(), 0);
213 assert_eq!(OdaSlot::OperationsDeputy.authority_depth(), 1);
214 assert_eq!(OdaSlot::ProcessArchitect.authority_depth(), 2);
215 assert_eq!(OdaSlot::PlatformEngineer1.authority_depth(), 3);
216 }
217
218 #[test]
219 fn display_names() {
220 for slot in &OdaSlot::ALL {
221 assert!(!slot.display_name().is_empty());
222 }
223 }
224
225 #[test]
226 fn stable_slot_slugs() {
227 assert_eq!(OdaSlot::VentureCommander.slug(), "venturecommander");
228 assert_eq!(OdaSlot::HrPeopleOps1.slug(), "hrpeopleops1");
229 assert_eq!(OdaSlot::PlatformEngineer2.to_string(), "platformengineer2");
230 }
231
232 #[test]
233 fn slot_serde_roundtrip() {
234 for slot in &OdaSlot::ALL {
235 let j = serde_json::to_string(slot).unwrap();
236 let rt: OdaSlot = serde_json::from_str(&j).unwrap();
237 assert_eq!(&rt, slot);
238 }
239 }
240
241 #[test]
242 fn mos_serde_roundtrip() {
243 let codes = [
244 MosCode::Alpha18A,
245 MosCode::Alpha180A,
246 MosCode::Zulu18Z,
247 MosCode::Fox18F,
248 MosCode::Bravo18B,
249 MosCode::Echo18E,
250 MosCode::Delta18D,
251 MosCode::Charlie18C,
252 ];
253 for code in &codes {
254 let j = serde_json::to_string(code).unwrap();
255 let rt: MosCode = serde_json::from_str(&j).unwrap();
256 assert_eq!(&rt, code);
257 }
258 }
259}