Skip to content

Commit 76dcb27

Browse files
authored
Merge pull request #155 from AdaWorldAPI/claude/osm-emit-review-fixes
render_osm/render_python: merge colliding rail tiles + CLASS_ID as ClassVar (#154 review)
2 parents 518ee9c + c233222 commit 76dcb27

3 files changed

Lines changed: 56 additions & 44 deletions

File tree

.claude/harvest/osm-website-rs/python/osm/models.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"""
44
from __future__ import annotations
55
from dataclasses import dataclass, field
6-
from typing import Any, List, Optional
6+
from typing import Any, ClassVar, List, Optional
77

88
@dataclass
99
class Acl:
@@ -15,7 +15,7 @@ class ApplicationRecord:
1515

1616
@dataclass
1717
class Changeset:
18-
CLASS_ID: int = 0x0F04 # canonical concept `osm_changeset`
18+
CLASS_ID: ClassVar[int] = 0x0F04 # canonical concept `osm_changeset`
1919
user: Optional[int] = None
2020
changeset_tags: List[int] = field(default_factory=list)
2121
nodes: List[int] = field(default_factory=list)
@@ -104,7 +104,7 @@ class ModerationZone:
104104

105105
@dataclass
106106
class Node:
107-
CLASS_ID: int = 0x0F01 # canonical concept `osm_node`
107+
CLASS_ID: ClassVar[int] = 0x0F01 # canonical concept `osm_node`
108108
changeset: Optional[int] = None
109109
old_nodes: List[int] = field(default_factory=list)
110110
way_nodes: List[int] = field(default_factory=list)
@@ -117,12 +117,12 @@ class Node:
117117

118118
@dataclass
119119
class NodeTag:
120-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
120+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
121121
node: Optional[int] = None
122122

123123
@dataclass
124124
class Note:
125-
CLASS_ID: int = 0x0F08 # canonical concept `osm_note`
125+
CLASS_ID: ClassVar[int] = 0x0F08 # canonical concept `osm_note`
126126
author: Optional[int] = None
127127
comments: List[int] = field(default_factory=list)
128128
all_comments: List[int] = field(default_factory=list)
@@ -145,20 +145,20 @@ class Oauth2Application:
145145

146146
@dataclass
147147
class OldNode:
148-
CLASS_ID: int = 0x0F01 # canonical concept `osm_node`
148+
CLASS_ID: ClassVar[int] = 0x0F01 # canonical concept `osm_node`
149149
changeset: Optional[int] = None
150150
redaction: Optional[int] = None
151151
current_node: Optional[int] = None
152152
old_tags: List[int] = field(default_factory=list)
153153

154154
@dataclass
155155
class OldNodeTag:
156-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
156+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
157157
old_node: Optional[int] = None
158158

159159
@dataclass
160160
class OldRelation:
161-
CLASS_ID: int = 0x0F03 # canonical concept `osm_relation`
161+
CLASS_ID: ClassVar[int] = 0x0F03 # canonical concept `osm_relation`
162162
changeset: Optional[int] = None
163163
redaction: Optional[int] = None
164164
current_relation: Optional[int] = None
@@ -167,18 +167,18 @@ class OldRelation:
167167

168168
@dataclass
169169
class OldRelationMember:
170-
CLASS_ID: int = 0x0F06 # canonical concept `osm_relation_member`
170+
CLASS_ID: ClassVar[int] = 0x0F06 # canonical concept `osm_relation_member`
171171
old_relation: Optional[int] = None
172172
member: Optional[int] = None
173173

174174
@dataclass
175175
class OldRelationTag:
176-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
176+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
177177
old_relation: Optional[int] = None
178178

179179
@dataclass
180180
class OldWay:
181-
CLASS_ID: int = 0x0F02 # canonical concept `osm_way`
181+
CLASS_ID: ClassVar[int] = 0x0F02 # canonical concept `osm_way`
182182
changeset: Optional[int] = None
183183
redaction: Optional[int] = None
184184
current_way: Optional[int] = None
@@ -187,14 +187,14 @@ class OldWay:
187187

188188
@dataclass
189189
class OldWayNode:
190-
CLASS_ID: int = 0x0F07 # canonical concept `osm_way_node`
190+
CLASS_ID: ClassVar[int] = 0x0F07 # canonical concept `osm_way_node`
191191
old_way: Optional[int] = None
192192
node: Optional[int] = None
193193
way: Optional[int] = None
194194

195195
@dataclass
196196
class OldWayTag:
197-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
197+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
198198
old_way: Optional[int] = None
199199

200200
@dataclass
@@ -206,7 +206,7 @@ class Redaction:
206206

207207
@dataclass
208208
class Relation:
209-
CLASS_ID: int = 0x0F03 # canonical concept `osm_relation`
209+
CLASS_ID: ClassVar[int] = 0x0F03 # canonical concept `osm_relation`
210210
changeset: Optional[int] = None
211211
old_relations: List[int] = field(default_factory=list)
212212
relation_members: List[int] = field(default_factory=list)
@@ -216,13 +216,13 @@ class Relation:
216216

217217
@dataclass
218218
class RelationMember:
219-
CLASS_ID: int = 0x0F06 # canonical concept `osm_relation_member`
219+
CLASS_ID: ClassVar[int] = 0x0F06 # canonical concept `osm_relation_member`
220220
relation: Optional[int] = None
221221
member: Optional[int] = None
222222

223223
@dataclass
224224
class RelationTag:
225-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
225+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
226226
relation: Optional[int] = None
227227

228228
@dataclass
@@ -240,7 +240,7 @@ class SpammyPhrase:
240240

241241
@dataclass
242242
class Trace:
243-
CLASS_ID: int = 0x0F09 # canonical concept `osm_gpx_trace`
243+
CLASS_ID: ClassVar[int] = 0x0F09 # canonical concept `osm_gpx_trace`
244244
user: Optional[int] = None
245245
tags: List[int] = field(default_factory=list)
246246
points: List[int] = field(default_factory=list)
@@ -255,7 +255,7 @@ class Tracetag:
255255

256256
@dataclass
257257
class User:
258-
CLASS_ID: int = 0x0F0A # canonical concept `osm_user`
258+
CLASS_ID: ClassVar[int] = 0x0F0A # canonical concept `osm_user`
259259
traces: List[int] = field(default_factory=list)
260260
diary_entries: List[int] = field(default_factory=list)
261261
diary_comments: List[int] = field(default_factory=list)
@@ -315,7 +315,7 @@ class UserRole:
315315

316316
@dataclass
317317
class Way:
318-
CLASS_ID: int = 0x0F02 # canonical concept `osm_way`
318+
CLASS_ID: ClassVar[int] = 0x0F02 # canonical concept `osm_way`
319319
changeset: Optional[int] = None
320320
old_ways: List[int] = field(default_factory=list)
321321
way_nodes: List[int] = field(default_factory=list)
@@ -326,12 +326,12 @@ class Way:
326326

327327
@dataclass
328328
class WayNode:
329-
CLASS_ID: int = 0x0F07 # canonical concept `osm_way_node`
329+
CLASS_ID: ClassVar[int] = 0x0F07 # canonical concept `osm_way_node`
330330
way: Optional[int] = None
331331
node: Optional[int] = None
332332

333333
@dataclass
334334
class WayTag:
335-
CLASS_ID: int = 0x0F05 # canonical concept `osm_element_tag`
335+
CLASS_ID: ClassVar[int] = 0x0F05 # canonical concept `osm_element_tag`
336336
way: Optional[int] = None
337337

crates/ogar-render-askama/examples/render_osm.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -288,28 +288,34 @@ fn main() {
288288
"pub mod {} {{\n use super::{{Input, Output}};\n",
289289
rustify(part_of)
290290
));
291-
let mut seen = std::collections::HashSet::new();
291+
// Distinct is_a rail keys can normalise to the SAME Rust fn name (`foo?`
292+
// and `foo` both → `foo`). Group by the emitted name and MERGE their
293+
// sources + labels into one fn, so no rail tile silently vanishes
294+
// (a `continue` here dropped the collider — codex/Bugbot on the Python
295+
// sibling #154; the Rust emitter had the same latent drop).
296+
let mut by_fname: std::collections::BTreeMap<String, (Vec<String>, Vec<String>)> =
297+
std::collections::BTreeMap::new();
292298
for (is_a, sources) in isas {
293-
let fname = rustify(is_a);
294-
if !seen.insert(fname.clone()) {
295-
continue;
296-
}
297-
// All source controllers that collapse onto this canonical tile,
298-
// deduped + sorted, cited so no route is lost from the docs.
299-
let mut srcs: Vec<String> = sources
300-
.iter()
301-
.map(|(c, a)| format!("{c}#{a}"))
302-
.collect();
299+
let entry = by_fname.entry(rustify(is_a)).or_default();
300+
entry.0.push(is_a.clone());
301+
entry
302+
.1
303+
.extend(sources.iter().map(|(c, a)| format!("{c}#{a}")));
304+
}
305+
for (fname, (mut labels, mut srcs)) in by_fname {
306+
labels.sort();
307+
labels.dedup();
303308
srcs.sort();
304309
srcs.dedup();
305-
let primary = &srcs[0];
310+
let is_a_disp = labels.join(", ");
311+
let primary = srcs[0].clone();
306312
let source_line = if srcs.len() == 1 {
307313
format!("Source: `{primary}`.")
308314
} else {
309315
format!("Sources (canonical tile): `{}`.", srcs.join("`, `"))
310316
};
311317
acts.push_str(&format!(
312-
" /// `{part_of}:{is_a}` — DO arm. {source_line}\n \
318+
" /// `{part_of}:{is_a_disp}` — DO arm. {source_line}\n \
313319
pub fn {fname}(input: Input) -> Output {{\n \
314320
let _ = input;\n todo!(\"port {primary}\")\n }}\n"
315321
));

crates/ogar-render-askama/examples/render_python.rs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ fn main() {
127127
"\"\"\"@generated by ogar-render-askama render_python — ruff → OGAR (THINK arm).\n\
128128
OSM domain models as dataclasses; associations become typed id fields.\n\"\"\"\n\
129129
from __future__ import annotations\nfrom dataclasses import dataclass, field\n\
130-
from typing import Any, List, Optional\n\n",
130+
from typing import Any, ClassVar, List, Optional\n\n",
131131
);
132132
let mut class_ids: Vec<(String, u16)> = Vec::new();
133133
for c in &classes {
@@ -136,7 +136,7 @@ fn main() {
136136
if let Some(concept) = &c.canonical_concept {
137137
if let Some(id) = canonical_concept_id(concept) {
138138
models.push_str(&format!(
139-
" CLASS_ID: int = 0x{id:04X} # canonical concept `{concept}`\n"
139+
" CLASS_ID: ClassVar[int] = 0x{id:04X} # canonical concept `{concept}`\n"
140140
));
141141
class_ids.push((cls.clone(), id));
142142
}
@@ -207,24 +207,30 @@ fn main() {
207207
Input = dict # the Rails params bag; typed params are the next ruff brick\n\
208208
Output = Any\n\n",
209209
);
210-
let mut seen = std::collections::HashSet::new();
210+
// Distinct is_a rail keys can normalise to the SAME Python identifier
211+
// (`foo?` and `foo` both → `foo`). Group by the emitted name and MERGE
212+
// their sources + labels into one stub, so no rail tile silently
213+
// vanishes (Bugbot #154).
214+
let mut by_fname: BTreeMap<String, (Vec<String>, Vec<String>)> = BTreeMap::new();
211215
for (is_a, sources) in isas {
212-
let fname = pyify(is_a);
213-
if !seen.insert(fname.clone()) {
214-
continue;
215-
}
216-
let mut srcs: Vec<String> =
217-
sources.iter().map(|(c, a)| format!("{c}#{a}")).collect();
216+
let entry = by_fname.entry(pyify(is_a)).or_default();
217+
entry.0.push(is_a.clone());
218+
entry.1.extend(sources.iter().map(|(c, a)| format!("{c}#{a}")));
219+
}
220+
for (fname, (mut labels, mut srcs)) in by_fname {
221+
labels.sort();
222+
labels.dedup();
218223
srcs.sort();
219224
srcs.dedup();
225+
let is_a_disp = labels.join(", ");
220226
let cite = if srcs.len() == 1 {
221227
format!("Source: {}", srcs[0])
222228
} else {
223229
format!("Sources (canonical tile): {}", srcs.join(", "))
224230
};
225231
body.push_str(&format!(
226232
"def {fname}(inp: Input) -> Output:\n \
227-
\"\"\"`{part_of}:{is_a}` — DO arm. {cite}.\"\"\"\n \
233+
\"\"\"`{part_of}:{is_a_disp}` — DO arm. {cite}.\"\"\"\n \
228234
raise NotImplementedError(\"port {}\")\n\n",
229235
srcs[0]
230236
));

0 commit comments

Comments
 (0)