Type
Task
Scope
Multi-theme or Platform
Skillset
engineering
Description
Companion to #695, which adopts the policy that Overture schema fields carry no non-null default. That issue is the why; this one removes the ones that exist.
Eleven fields carry a non-null default. They resolve to four declaration sites in four files -- the rest are inherited and re-reported by subclasses.
#692 already did this once, removing the connectors = [] default and making the field required. These four are what remain.
Verified on origin/vnext at b893ab09, which carries #692; origin/main does not yet.
| Declaration |
Default |
Fields it produces |
Becomes |
common/level.py:23 Stacked.level |
0 |
6 |
Level | None = None |
transportation/segment/_common.py:318 SpeedLimitRule.is_max_speed_variable |
False |
1 |
bool | None with no default |
base/land.py:142,145 Land.class_, Land.subtype |
LandClass.LAND, LandSubtype.LAND |
2 |
required |
base/water.py:141,148 Water.class_, Water.subtype |
WaterClass.WATER, WaterSubtype.WATER |
2 |
required |
Land and Water class and subtype
Published data already satisfies the tightened constraint, the same justification #692 used for connectors. In release 2026-08-19.0, one partition per type:
| Type |
Rows |
subtype null |
class null |
| land |
1,174,629 |
0 |
0 |
| water |
1,985,644 |
0 |
0 |
No published feature relies on the default.
subtype = 'water' reads as the identity of the model already chosen, which is what makes the default look harmless. It is not an identity: in the water partition it is one of 8 values, covering 1,200,960 of 1,985,644 rows, and in land, class = 'land' covers 68,679 of 1,174,629. They are catch-all values that happen to be common. Required is the right shape, not defaulted.
SpeedLimitRule.is_max_speed_variable
In the segment partition measured, false appears in 0 of 601,764 speed limit rules; 601,497 are null and 267 are true. Absent already means "not a variable speed corridor" to every reader of the release, and the description should say so rather than leaving a default to imply it.
With the default in place, absent, null, and false are three encodings of two meanings, and which pair collapses depends on whether you came through Pydantic or through Parquet.
After removing a default, fix the description
A default often carries meaning that nothing else states. Remove it without updating the field description and that meaning is gone silently. level and is_max_speed_variable both need a description of what absence means; class_ and subtype need nothing, since becoming required leaves no absence to explain.
The level sentence belongs on the Stacked.level field annotation, not on the Level NewType. Level is also the type of LevelRule.value (transportation/segment/_common.py:135), which is required -- absence is impossible there, so a NewType-level sentence about absence renders as nonsense into the segment JSON Schema and Markdown reference.
The description is what resolves this, and that is the whole point of the step. An absence with nothing said about it is itself a default -- the reader supplies a meaning, and different readers supply different ones. Removing a default without describing absence relocates the ambiguity instead of removing it.
is_max_speed_variable is where that bites. Absence could mean false, or could mean unknown, and the two make different claims about roads nobody surveyed. The published data bears on it without settling it: 267 rules say true, 601,497 say nothing, and none say false, so the publisher records only positives -- and that is not an artefact of the Pydantic default, since the data pipeline never consults it. Writing the description is the act of deciding, so it needs whoever knows what the flag asserts rather than an inference from the counts.
Breaking change classification
No consumer sees a difference: the release already carries what the models will require, and nobody reads Overture data through the Python models today.
The model contract still changes, so this is a breaking package change and lands in the 2.0.0 window.
| Change |
Effect on the release |
Change to the model contract |
level default dropped |
none; already null on all but a handful of rows |
input that omits level parses to None instead of 0 |
class_ / subtype required |
none; always written |
constructing Land() / Water() without those arguments raises |
is_max_speed_variable default dropped |
none; false never written |
input that omits it parses to None instead of False |
Nothing changes for anyone querying Parquet. Requiredness never reaches the emitted schema -- schema_builder.py:140 renders every field as StructField(name, type, True), nullable regardless of is_required -- so a required field is a validation rule, not a physical constraint. The columns and their nullability are identical before and after. The only difference is that validation now reports a null where it previously accepted one, and no published row has one.
Downstream effects
- Markdown reference.
class_ and subtype move from Optional to Required for Land and Water. Expected; confirm the rendered tables read correctly.
- Generated PySpark. Required-field checks appear for the four newly-required fields. Regenerate and read the diff rather than accepting it.
- JSON Schema output. The
default annotations disappear, and class/subtype join the required arrays. Confirm the annotations are gone rather than relocated.
Acceptance criteria
Related
Type
Task
Scope
Multi-theme or Platform
Skillset
engineering
Description
Companion to #695, which adopts the policy that Overture schema fields carry no non-null default. That issue is the why; this one removes the ones that exist.
Eleven fields carry a non-null default. They resolve to four declaration sites in four files -- the rest are inherited and re-reported by subclasses.
#692 already did this once, removing the
connectors = []default and making the field required. These four are what remain.Verified on
origin/vnextatb893ab09, which carries #692;origin/maindoes not yet.common/level.py:23Stacked.level0Level | None = Nonetransportation/segment/_common.py:318SpeedLimitRule.is_max_speed_variableFalsebool | Nonewith no defaultbase/land.py:142,145Land.class_,Land.subtypeLandClass.LAND,LandSubtype.LANDbase/water.py:141,148Water.class_,Water.subtypeWaterClass.WATER,WaterSubtype.WATERLandandWaterclass and subtypePublished data already satisfies the tightened constraint, the same justification #692 used for connectors. In release
2026-08-19.0, one partition per type:subtypenullclassnullNo published feature relies on the default.
subtype = 'water'reads as the identity of the model already chosen, which is what makes the default look harmless. It is not an identity: in the water partition it is one of 8 values, covering 1,200,960 of 1,985,644 rows, and in land,class = 'land'covers 68,679 of 1,174,629. They are catch-all values that happen to be common. Required is the right shape, not defaulted.SpeedLimitRule.is_max_speed_variableIn the segment partition measured,
falseappears in 0 of 601,764 speed limit rules; 601,497 are null and 267 aretrue. Absent already means "not a variable speed corridor" to every reader of the release, and the description should say so rather than leaving a default to imply it.With the default in place, absent,
null, andfalseare three encodings of two meanings, and which pair collapses depends on whether you came through Pydantic or through Parquet.After removing a default, fix the description
A default often carries meaning that nothing else states. Remove it without updating the field description and that meaning is gone silently.
levelandis_max_speed_variableboth need a description of what absence means;class_andsubtypeneed nothing, since becoming required leaves no absence to explain.The
levelsentence belongs on theStacked.levelfield annotation, not on theLevelNewType.Levelis also the type ofLevelRule.value(transportation/segment/_common.py:135), which is required -- absence is impossible there, so a NewType-level sentence about absence renders as nonsense into the segment JSON Schema and Markdown reference.The description is what resolves this, and that is the whole point of the step. An absence with nothing said about it is itself a default -- the reader supplies a meaning, and different readers supply different ones. Removing a default without describing absence relocates the ambiguity instead of removing it.
is_max_speed_variableis where that bites. Absence could meanfalse, or could mean unknown, and the two make different claims about roads nobody surveyed. The published data bears on it without settling it: 267 rules saytrue, 601,497 say nothing, and none sayfalse, so the publisher records only positives -- and that is not an artefact of the Pydantic default, since the data pipeline never consults it. Writing the description is the act of deciding, so it needs whoever knows what the flag asserts rather than an inference from the counts.Breaking change classification
No consumer sees a difference: the release already carries what the models will require, and nobody reads Overture data through the Python models today.
The model contract still changes, so this is a breaking package change and lands in the 2.0.0 window.
leveldefault droppedlevelparses toNoneinstead of0class_/subtyperequiredLand()/Water()without those arguments raisesis_max_speed_variabledefault droppedfalsenever writtenNoneinstead ofFalseNothing changes for anyone querying Parquet. Requiredness never reaches the emitted schema --
schema_builder.py:140renders every field asStructField(name, type, True), nullable regardless ofis_required-- so a required field is a validation rule, not a physical constraint. The columns and their nullability are identical before and after. The only difference is that validation now reports a null where it previously accepted one, and no published row has one.Downstream effects
class_andsubtypemove from Optional to Required for Land and Water. Expected; confirm the rendered tables read correctly.defaultannotations disappear, andclass/subtypejoin therequiredarrays. Confirm the annotations are gone rather than relocated.Acceptance criteria
2026-08-19.0per touched type validates clean against the changed models, withoverture-validate --skip-schema-checkto clear the unreleased-field gate.make checkgreen; regenerated PySpark and Markdown diffs reviewed, not just regenerated.Related
connectors = []default and made the field required; merged tovnext, and the precedent for these four.connectors = []correctness bug that motivated [ENHANCEMENT](transportation) Make segment connectors required #692.defaultis theField()kwarg decision this pair of issues resolves.operating_statusprecedent: default removed, replacement never landed.