Skip to content

Commit 7469cdd

Browse files
committed
Fix semantics models and test workflow
1 parent 4b5e705 commit 7469cdd

6 files changed

Lines changed: 252 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ name: CI
33
on:
44
push:
55
branches:
6-
- main
6+
- "**"
7+
tags-ignore:
8+
- "v*"
79
pull_request:
810

911
jobs:
@@ -32,7 +34,7 @@ jobs:
3234
run: uv sync --python ${{ matrix.python-version }}
3335

3436
- name: Run tests
35-
run: uv run python -m unittest discover -s tests -p 'test_*.py'
37+
run: make test
3638

3739
build:
3840
runs-on: ubuntu-latest

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.PHONY: test
2+
3+
test:
4+
uv run python -m unittest discover -s tests -p 'test_*.py'

notamify_sdk/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
from .config import ConfigStore, SDKConfig
66
from .models import (
77
ActiveNotamsQuery,
8+
AffectedElementChangeDTO,
9+
AffectedElementClauseDTO,
10+
AffectedElementDTO,
11+
AffectedElementReferenceDTO,
12+
AffectedElementSemanticsDTO,
813
AircraftDetails,
914
BriefingJobCreated,
1015
BriefingJobStatusDTO,
@@ -13,7 +18,10 @@
1318
CriticalOperationalRestrictionGroup,
1419
GenerateFlightBriefingRequest,
1520
GenerateFlightBriefingResponse,
21+
GroupedMeasurementValueDTO,
22+
FractionalMeasurementValueDTO,
1623
HistoricalNotamsQuery,
24+
IntegerMeasurementValueDTO,
1725
Listener,
1826
ListenerAffectedElementFilter,
1927
ListenerFilters,
@@ -27,6 +35,7 @@
2735
ListenerTimeWindowFilter,
2836
LocationType,
2937
LocationWithType,
38+
MeasurementComponentValueDTO,
3039
NearbyNotamsQuery,
3140
NotamDTO,
3241
NotamInterpretationDTO,
@@ -37,7 +46,10 @@
3746
NotamPriority,
3847
NotamScheduleInterpretationDTO,
3948
PrioritizedNotamDTO,
49+
ProcedureCapabilityDTO,
4050
SandboxDeliveryResult,
51+
ValueComponentDTO,
52+
ValueDTO,
4153
WatcherWebhookEvent,
4254
WebhookContext,
4355
WebhookLifecycleChange,
@@ -53,6 +65,11 @@
5365
__all__ = [
5466
"APIError",
5567
"ActiveNotamsQuery",
68+
"AffectedElementChangeDTO",
69+
"AffectedElementClauseDTO",
70+
"AffectedElementDTO",
71+
"AffectedElementReferenceDTO",
72+
"AffectedElementSemanticsDTO",
5673
"AircraftDetails",
5774
"BriefingJobCreated",
5875
"BriefingJobStatusDTO",
@@ -64,7 +81,10 @@
6481
"CriticalOperationalRestrictionGroup",
6582
"GenerateFlightBriefingRequest",
6683
"GenerateFlightBriefingResponse",
84+
"GroupedMeasurementValueDTO",
85+
"FractionalMeasurementValueDTO",
6786
"HistoricalNotamsQuery",
87+
"IntegerMeasurementValueDTO",
6888
"Listener",
6989
"ListenerAffectedElementFilter",
7090
"ListenerFilters",
@@ -78,6 +98,7 @@
7898
"ListenerTimeWindowFilter",
7999
"LocationType",
80100
"LocationWithType",
101+
"MeasurementComponentValueDTO",
81102
"NearbyNotamsQuery",
82103
"NotamDTO",
83104
"NotamInterpretationDTO",
@@ -91,11 +112,14 @@
91112
"NotamifyClient",
92113
"NotamsResource",
93114
"PrioritizedNotamDTO",
115+
"ProcedureCapabilityDTO",
94116
"ReceiverConfig",
95117
"ReceivedEvent",
96118
"SandboxDeliveryResult",
97119
"SDKConfig",
98120
"SignatureVerificationError",
121+
"ValueComponentDTO",
122+
"ValueDTO",
99123
"WatcherWebhookEvent",
100124
"WebhookContext",
101125
"WebhookLifecycleChange",

notamify_sdk/models.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,11 +235,95 @@ class NotamPriority(str, Enum):
235235
low = "LOW"
236236

237237

238+
class ValueComponentDTO(NotamifyModel):
239+
type: str | None = None
240+
value: int | str
241+
unit: str | None = None
242+
243+
244+
class ValueDTO(NotamifyModel):
245+
kind: str | None = None
246+
raw_string: str
247+
values: list[ValueComponentDTO] = Field(default_factory=list)
248+
249+
250+
class MeasurementComponentValueDTO(NotamifyModel):
251+
type: str
252+
value: int
253+
unit: str
254+
255+
256+
class IntegerMeasurementValueDTO(NotamifyModel):
257+
kind: str | None = None
258+
raw_string: str
259+
value: int
260+
unit: str
261+
262+
263+
class FractionalMeasurementValueDTO(NotamifyModel):
264+
kind: str | None = None
265+
raw_string: str
266+
numerator: int
267+
denominator: int
268+
unit: str
269+
270+
271+
class GroupedMeasurementValueDTO(NotamifyModel):
272+
kind: str | None = None
273+
raw_string: str
274+
values: list[MeasurementComponentValueDTO] = Field(default_factory=list)
275+
276+
277+
class ProcedureCapabilityDTO(NotamifyModel):
278+
scheme: str
279+
category: str | None = None
280+
level: str | None = None
281+
classification: str | None = None
282+
source_label: str | None = None
283+
284+
285+
class AffectedElementReferenceDTO(NotamifyModel):
286+
relation: str
287+
type: str | None = None
288+
identifier: str | None = None
289+
290+
291+
class AffectedElementChangeDTO(NotamifyModel):
292+
subject: str
293+
from_: list[ProcedureCapabilityDTO | ValueDTO] = Field(default_factory=list, alias="from")
294+
to: list[ProcedureCapabilityDTO | ValueDTO] = Field(default_factory=list)
295+
details: str | None = None
296+
297+
298+
class AffectedElementClauseDTO(NotamifyModel):
299+
dimension: str
300+
operator: str
301+
value: (
302+
IntegerMeasurementValueDTO
303+
| FractionalMeasurementValueDTO
304+
| GroupedMeasurementValueDTO
305+
| ProcedureCapabilityDTO
306+
| list[str]
307+
)
308+
unit: str | None = None
309+
details: str | None = None
310+
311+
312+
class AffectedElementSemanticsDTO(NotamifyModel):
313+
scope: list[AffectedElementClauseDTO] = Field(default_factory=list)
314+
conditions: list[AffectedElementClauseDTO] = Field(default_factory=list)
315+
exceptions: list[AffectedElementClauseDTO] = Field(default_factory=list)
316+
changes: list[AffectedElementChangeDTO] = Field(default_factory=list)
317+
references: list[AffectedElementReferenceDTO] = Field(default_factory=list)
318+
319+
238320
class AffectedElementDTO(NotamifyModel):
239321
type: str
240322
identifier: str
241323
effect: str
242324
details: str | None = None
325+
subtype: str | None = None
326+
semantics: AffectedElementSemanticsDTO | None = None
243327

244328

245329
class NotamScheduleInterpretationDTO(NotamifyModel):

tests/test_models.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,123 @@ def test_notam_list_result_validation(self):
3939
result = NotamListResult.model_validate(payload)
4040
self.assertEqual(result.notams[0].id, "n1")
4141

42+
def test_notam_list_result_accepts_affected_element_semantics(self):
43+
payload = {
44+
"notams": [
45+
{
46+
"id": "11111111-1111-1111-1111-111111111111",
47+
"notam_number": "A1234/26",
48+
"location": "KJFK",
49+
"starts_at": "2026-02-25T10:00:00Z",
50+
"ends_at": "2026-02-26T10:00:00Z",
51+
"issued_at": "2026-02-25T09:00:00Z",
52+
"is_estimated": False,
53+
"is_permanent": False,
54+
"message": "RWY 11 restrictions",
55+
"interpretation": {
56+
"description": "Runway operations restricted.",
57+
"excerpt": "Runway 11 restrictions in effect.",
58+
"category": "AERODROME",
59+
"subcategory": "RUNWAY_OPERATIONS",
60+
"affected_elements": [
61+
{
62+
"type": "RUNWAY",
63+
"identifier": "11",
64+
"effect": "RESTRICTED",
65+
"details": "Runway 11 restricted to lighter traffic.",
66+
"subtype": "DEPARTURE_RUNWAY",
67+
"semantics": {
68+
"scope": [],
69+
"conditions": [
70+
{
71+
"dimension": "operation_phase",
72+
"operator": "IN",
73+
"value": ["TAKEOFF", "LANDING"],
74+
"unit": None,
75+
"details": None,
76+
},
77+
{
78+
"dimension": "weight",
79+
"operator": "LTE",
80+
"value": {
81+
"kind": "MEASUREMENT",
82+
"raw_string": "5700KG",
83+
"value": 5700,
84+
"unit": "KG",
85+
},
86+
"unit": "KG",
87+
"details": None,
88+
},
89+
{
90+
"dimension": "procedure_capability",
91+
"operator": "EQ",
92+
"value": {
93+
"scheme": "ILS_CATEGORY",
94+
"category": "CAT_I",
95+
"level": "LVL_1",
96+
},
97+
"unit": None,
98+
"details": None,
99+
},
100+
],
101+
"exceptions": [
102+
{
103+
"dimension": "aircraft_type",
104+
"operator": "IN",
105+
"value": ["HELICOPTER"],
106+
"unit": None,
107+
"details": None,
108+
}
109+
],
110+
"changes": [
111+
{
112+
"subject": "PROCEDURE_CAPABILITY",
113+
"from": [
114+
{
115+
"scheme": "ILS_CATEGORY",
116+
"category": "CAT_II",
117+
"level": "LVL_2",
118+
}
119+
],
120+
"to": [
121+
{
122+
"scheme": "ILS_CATEGORY",
123+
"category": "CAT_I",
124+
"level": "LVL_1",
125+
}
126+
],
127+
"details": "Downgraded due to maintenance.",
128+
}
129+
],
130+
"references": [
131+
{
132+
"relation": "DEPENDS_ON",
133+
"type": "NOTAM",
134+
"identifier": "A1234/26",
135+
}
136+
],
137+
},
138+
}
139+
],
140+
"schedules": [],
141+
},
142+
}
143+
],
144+
"total_count": 1,
145+
"page": 1,
146+
"per_page": 30,
147+
}
148+
149+
result = NotamListResult.model_validate(payload)
150+
affected = result.notams[0].interpretation.affected_elements[0]
151+
152+
self.assertEqual(affected.subtype, "DEPARTURE_RUNWAY")
153+
self.assertEqual(affected.semantics.conditions[0].value, ["TAKEOFF", "LANDING"])
154+
self.assertEqual(affected.semantics.conditions[1].value.value, 5700)
155+
self.assertEqual(affected.semantics.conditions[2].value.scheme, "ILS_CATEGORY")
156+
self.assertEqual(affected.semantics.changes[0].from_[0].category, "CAT_II")
157+
self.assertEqual(affected.semantics.references[0].relation, "DEPENDS_ON")
158+
42159
def test_required_fields_enforced(self):
43160
with self.assertRaises(ValidationError):
44161
NotamListResult.model_validate({"notams": []})

0 commit comments

Comments
 (0)