kicad-sch-api 0.3.0__py3-none-any.whl → 0.5.1__py3-none-any.whl

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.
Files changed (112) hide show
  1. kicad_sch_api/__init__.py +68 -3
  2. kicad_sch_api/cli/__init__.py +45 -0
  3. kicad_sch_api/cli/base.py +302 -0
  4. kicad_sch_api/cli/bom.py +164 -0
  5. kicad_sch_api/cli/erc.py +229 -0
  6. kicad_sch_api/cli/export_docs.py +289 -0
  7. kicad_sch_api/cli/kicad_to_python.py +169 -0
  8. kicad_sch_api/cli/netlist.py +94 -0
  9. kicad_sch_api/cli/types.py +43 -0
  10. kicad_sch_api/collections/__init__.py +36 -0
  11. kicad_sch_api/collections/base.py +604 -0
  12. kicad_sch_api/collections/components.py +1623 -0
  13. kicad_sch_api/collections/junctions.py +206 -0
  14. kicad_sch_api/collections/labels.py +508 -0
  15. kicad_sch_api/collections/wires.py +292 -0
  16. kicad_sch_api/core/__init__.py +37 -2
  17. kicad_sch_api/core/collections/__init__.py +5 -0
  18. kicad_sch_api/core/collections/base.py +248 -0
  19. kicad_sch_api/core/component_bounds.py +34 -7
  20. kicad_sch_api/core/components.py +213 -52
  21. kicad_sch_api/core/config.py +110 -15
  22. kicad_sch_api/core/connectivity.py +692 -0
  23. kicad_sch_api/core/exceptions.py +175 -0
  24. kicad_sch_api/core/factories/__init__.py +5 -0
  25. kicad_sch_api/core/factories/element_factory.py +278 -0
  26. kicad_sch_api/core/formatter.py +60 -9
  27. kicad_sch_api/core/geometry.py +94 -5
  28. kicad_sch_api/core/junctions.py +26 -75
  29. kicad_sch_api/core/labels.py +324 -0
  30. kicad_sch_api/core/managers/__init__.py +30 -0
  31. kicad_sch_api/core/managers/base.py +76 -0
  32. kicad_sch_api/core/managers/file_io.py +246 -0
  33. kicad_sch_api/core/managers/format_sync.py +502 -0
  34. kicad_sch_api/core/managers/graphics.py +580 -0
  35. kicad_sch_api/core/managers/hierarchy.py +661 -0
  36. kicad_sch_api/core/managers/metadata.py +271 -0
  37. kicad_sch_api/core/managers/sheet.py +492 -0
  38. kicad_sch_api/core/managers/text_elements.py +537 -0
  39. kicad_sch_api/core/managers/validation.py +476 -0
  40. kicad_sch_api/core/managers/wire.py +410 -0
  41. kicad_sch_api/core/nets.py +305 -0
  42. kicad_sch_api/core/no_connects.py +252 -0
  43. kicad_sch_api/core/parser.py +194 -970
  44. kicad_sch_api/core/parsing_utils.py +63 -0
  45. kicad_sch_api/core/pin_utils.py +103 -9
  46. kicad_sch_api/core/schematic.py +1328 -1079
  47. kicad_sch_api/core/texts.py +316 -0
  48. kicad_sch_api/core/types.py +159 -23
  49. kicad_sch_api/core/wires.py +27 -75
  50. kicad_sch_api/exporters/__init__.py +10 -0
  51. kicad_sch_api/exporters/python_generator.py +610 -0
  52. kicad_sch_api/exporters/templates/default.py.jinja2 +65 -0
  53. kicad_sch_api/geometry/__init__.py +38 -0
  54. kicad_sch_api/geometry/font_metrics.py +22 -0
  55. kicad_sch_api/geometry/routing.py +211 -0
  56. kicad_sch_api/geometry/symbol_bbox.py +608 -0
  57. kicad_sch_api/interfaces/__init__.py +17 -0
  58. kicad_sch_api/interfaces/parser.py +76 -0
  59. kicad_sch_api/interfaces/repository.py +70 -0
  60. kicad_sch_api/interfaces/resolver.py +117 -0
  61. kicad_sch_api/parsers/__init__.py +14 -0
  62. kicad_sch_api/parsers/base.py +145 -0
  63. kicad_sch_api/parsers/elements/__init__.py +22 -0
  64. kicad_sch_api/parsers/elements/graphics_parser.py +564 -0
  65. kicad_sch_api/parsers/elements/label_parser.py +216 -0
  66. kicad_sch_api/parsers/elements/library_parser.py +165 -0
  67. kicad_sch_api/parsers/elements/metadata_parser.py +58 -0
  68. kicad_sch_api/parsers/elements/sheet_parser.py +352 -0
  69. kicad_sch_api/parsers/elements/symbol_parser.py +485 -0
  70. kicad_sch_api/parsers/elements/text_parser.py +250 -0
  71. kicad_sch_api/parsers/elements/wire_parser.py +242 -0
  72. kicad_sch_api/parsers/registry.py +155 -0
  73. kicad_sch_api/parsers/utils.py +80 -0
  74. kicad_sch_api/symbols/__init__.py +18 -0
  75. kicad_sch_api/symbols/cache.py +467 -0
  76. kicad_sch_api/symbols/resolver.py +361 -0
  77. kicad_sch_api/symbols/validators.py +504 -0
  78. kicad_sch_api/utils/logging.py +555 -0
  79. kicad_sch_api/utils/logging_decorators.py +587 -0
  80. kicad_sch_api/utils/validation.py +16 -22
  81. kicad_sch_api/validation/__init__.py +25 -0
  82. kicad_sch_api/validation/erc.py +171 -0
  83. kicad_sch_api/validation/erc_models.py +203 -0
  84. kicad_sch_api/validation/pin_matrix.py +243 -0
  85. kicad_sch_api/validation/validators.py +391 -0
  86. kicad_sch_api/wrappers/__init__.py +14 -0
  87. kicad_sch_api/wrappers/base.py +89 -0
  88. kicad_sch_api/wrappers/wire.py +198 -0
  89. kicad_sch_api-0.5.1.dist-info/METADATA +540 -0
  90. kicad_sch_api-0.5.1.dist-info/RECORD +114 -0
  91. kicad_sch_api-0.5.1.dist-info/entry_points.txt +4 -0
  92. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/top_level.txt +1 -0
  93. mcp_server/__init__.py +34 -0
  94. mcp_server/example_logging_integration.py +506 -0
  95. mcp_server/models.py +252 -0
  96. mcp_server/server.py +357 -0
  97. mcp_server/tools/__init__.py +32 -0
  98. mcp_server/tools/component_tools.py +516 -0
  99. mcp_server/tools/connectivity_tools.py +532 -0
  100. mcp_server/tools/consolidated_tools.py +1216 -0
  101. mcp_server/tools/pin_discovery.py +333 -0
  102. mcp_server/utils/__init__.py +38 -0
  103. mcp_server/utils/logging.py +127 -0
  104. mcp_server/utils.py +36 -0
  105. kicad_sch_api/core/manhattan_routing.py +0 -430
  106. kicad_sch_api/core/simple_manhattan.py +0 -228
  107. kicad_sch_api/core/wire_routing.py +0 -380
  108. kicad_sch_api-0.3.0.dist-info/METADATA +0 -483
  109. kicad_sch_api-0.3.0.dist-info/RECORD +0 -31
  110. kicad_sch_api-0.3.0.dist-info/entry_points.txt +0 -2
  111. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/WHEEL +0 -0
  112. {kicad_sch_api-0.3.0.dist-info → kicad_sch_api-0.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,502 @@
1
+ """
2
+ Format Synchronization Manager for KiCAD schematic data consistency.
3
+
4
+ Handles bidirectional synchronization between Python object models and
5
+ raw S-expression data structures while maintaining exact format preservation
6
+ and tracking changes for efficient updates.
7
+ """
8
+
9
+ import logging
10
+ from typing import Any, Dict, List, Optional, Set, Union
11
+
12
+ from ..components import Component
13
+ from ..types import Point, Wire
14
+ from .base import BaseManager
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class FormatSyncManager(BaseManager):
20
+ """
21
+ Manages synchronization between object models and S-expression data.
22
+
23
+ Responsible for:
24
+ - Bidirectional data synchronization
25
+ - Change tracking and dirty flag management
26
+ - Format preservation during updates
27
+ - Incremental update optimization
28
+ - Data consistency validation
29
+ """
30
+
31
+ def __init__(self, schematic_data: Dict[str, Any]):
32
+ """
33
+ Initialize FormatSyncManager.
34
+
35
+ Args:
36
+ schematic_data: Reference to schematic data
37
+ """
38
+ super().__init__(schematic_data)
39
+ self._dirty_flags: Set[str] = set()
40
+ self._change_log: List[Dict[str, Any]] = []
41
+ self._sync_lock = False
42
+
43
+ def mark_dirty(
44
+ self, section: str, operation: str = "update", context: Optional[Dict] = None
45
+ ) -> None:
46
+ """
47
+ Mark a data section as dirty for synchronization.
48
+
49
+ Args:
50
+ section: Data section that changed (e.g., 'components', 'wires')
51
+ operation: Type of operation (update, add, remove)
52
+ context: Additional context about the change
53
+ """
54
+ if self._sync_lock:
55
+ logger.debug(f"Sync locked, deferring dirty mark for {section}")
56
+ return
57
+
58
+ self._dirty_flags.add(section)
59
+
60
+ change_entry = {
61
+ "section": section,
62
+ "operation": operation,
63
+ "timestamp": None, # Would use datetime in real implementation
64
+ "context": context or {},
65
+ }
66
+ self._change_log.append(change_entry)
67
+
68
+ logger.debug(f"Marked section '{section}' as dirty ({operation})")
69
+
70
+ def sync_component_to_data(self, component: Component) -> None:
71
+ """
72
+ Synchronize a component object back to S-expression data.
73
+
74
+ Args:
75
+ component: Component to sync
76
+ """
77
+ symbols = self._data.get("symbol", [])
78
+
79
+ # Find the corresponding symbol entry
80
+ for symbol_data in symbols:
81
+ if symbol_data.get("uuid") == component.uuid:
82
+ self._update_symbol_data_from_component(symbol_data, component)
83
+ self.mark_dirty("symbol", "update", {"uuid": component.uuid})
84
+ return
85
+
86
+ logger.warning(f"Component not found in data for sync: {component.uuid}")
87
+
88
+ def sync_component_from_data(self, component: Component, symbol_data: Dict[str, Any]) -> None:
89
+ """
90
+ Update component object from S-expression data.
91
+
92
+ Args:
93
+ component: Component to update
94
+ symbol_data: Source symbol data
95
+ """
96
+ # Update component properties from symbol data
97
+ if "at" in symbol_data:
98
+ at_data = symbol_data["at"]
99
+ component.position = Point(at_data[0], at_data[1])
100
+ if len(at_data) > 2:
101
+ component.rotation = at_data[2]
102
+
103
+ # Update properties
104
+ if "property" in symbol_data:
105
+ for prop in symbol_data["property"]:
106
+ name = prop.get("name")
107
+ value = prop.get("value")
108
+ if name and value is not None:
109
+ component.set_property(name, value)
110
+
111
+ logger.debug(f"Synced component from data: {component.uuid}")
112
+
113
+ def sync_wire_to_data(self, wire: Wire) -> None:
114
+ """
115
+ Synchronize a wire object back to S-expression data.
116
+
117
+ Args:
118
+ wire: Wire to sync
119
+ """
120
+ wires = self._data.get("wire", [])
121
+
122
+ # Find the corresponding wire entry
123
+ for wire_data in wires:
124
+ if wire_data.get("uuid") == wire.uuid:
125
+ self._update_wire_data_from_object(wire_data, wire)
126
+ self.mark_dirty("wire", "update", {"uuid": wire.uuid})
127
+ return
128
+
129
+ logger.warning(f"Wire not found in data for sync: {wire.uuid}")
130
+
131
+ def sync_wire_from_data(self, wire: Wire, wire_data: Dict[str, Any]) -> None:
132
+ """
133
+ Update wire object from S-expression data.
134
+
135
+ Args:
136
+ wire: Wire to update
137
+ wire_data: Source wire data
138
+ """
139
+ # Update wire endpoints
140
+ if "pts" in wire_data:
141
+ pts = wire_data["pts"]
142
+ if len(pts) >= 2:
143
+ start_pt = pts[0]
144
+ end_pt = pts[-1]
145
+ wire.start = Point(start_pt["xy"][0], start_pt["xy"][1])
146
+ wire.end = Point(end_pt["xy"][0], end_pt["xy"][1])
147
+
148
+ # Update stroke properties
149
+ if "stroke" in wire_data:
150
+ wire.stroke_width = wire_data["stroke"].get("width", 0.0)
151
+
152
+ logger.debug(f"Synced wire from data: {wire.uuid}")
153
+
154
+ def sync_all_to_data(self, component_collection=None, wire_collection=None) -> None:
155
+ """
156
+ Perform full synchronization of all objects to S-expression data.
157
+
158
+ Args:
159
+ component_collection: Collection of components to sync
160
+ wire_collection: Collection of wires to sync
161
+ """
162
+ self._sync_lock = True
163
+
164
+ try:
165
+ # Sync components
166
+ if component_collection:
167
+ for component in component_collection:
168
+ self.sync_component_to_data(component)
169
+
170
+ # Sync wires
171
+ if wire_collection:
172
+ for wire in wire_collection:
173
+ self.sync_wire_to_data(wire)
174
+
175
+ # Clear dirty flags after successful sync
176
+ self._dirty_flags.clear()
177
+ logger.info("Full synchronization to data completed")
178
+
179
+ finally:
180
+ self._sync_lock = False
181
+
182
+ def sync_all_from_data(self, component_collection=None, wire_collection=None) -> None:
183
+ """
184
+ Perform full synchronization from S-expression data to objects.
185
+
186
+ Args:
187
+ component_collection: Collection of components to update
188
+ wire_collection: Collection of wires to update
189
+ """
190
+ self._sync_lock = True
191
+
192
+ try:
193
+ # Sync components from symbols
194
+ if component_collection:
195
+ symbols = self._data.get("symbol", [])
196
+ for symbol_data in symbols:
197
+ uuid = symbol_data.get("uuid")
198
+ if uuid:
199
+ component = component_collection.get_by_uuid(uuid)
200
+ if component:
201
+ self.sync_component_from_data(component, symbol_data)
202
+
203
+ # Sync wires
204
+ if wire_collection:
205
+ wires = self._data.get("wire", [])
206
+ for wire_data in wires:
207
+ uuid = wire_data.get("uuid")
208
+ if uuid:
209
+ wire = wire_collection.get_by_uuid(uuid)
210
+ if wire:
211
+ self.sync_wire_from_data(wire, wire_data)
212
+
213
+ logger.info("Full synchronization from data completed")
214
+
215
+ finally:
216
+ self._sync_lock = False
217
+
218
+ def perform_incremental_sync(self, component_collection=None, wire_collection=None) -> None:
219
+ """
220
+ Perform incremental synchronization of only dirty sections.
221
+
222
+ Args:
223
+ component_collection: Collection of components
224
+ wire_collection: Collection of wires
225
+ """
226
+ if not self._dirty_flags:
227
+ logger.debug("No dirty sections, skipping sync")
228
+ return
229
+
230
+ self._sync_lock = True
231
+
232
+ try:
233
+ # Sync dirty components
234
+ if "symbol" in self._dirty_flags and component_collection:
235
+ for component in component_collection:
236
+ self.sync_component_to_data(component)
237
+
238
+ # Sync dirty wires
239
+ if "wire" in self._dirty_flags and wire_collection:
240
+ for wire in wire_collection:
241
+ self.sync_wire_to_data(wire)
242
+
243
+ # Clear processed dirty flags
244
+ self._dirty_flags.clear()
245
+ logger.info("Incremental synchronization completed")
246
+
247
+ finally:
248
+ self._sync_lock = False
249
+
250
+ def add_component_to_data(self, component: Component) -> None:
251
+ """
252
+ Add a new component to S-expression data.
253
+
254
+ Args:
255
+ component: Component to add
256
+ """
257
+ symbol_data = self._create_symbol_data_from_component(component)
258
+
259
+ if "symbol" not in self._data:
260
+ self._data["symbol"] = []
261
+
262
+ self._data["symbol"].append(symbol_data)
263
+ self.mark_dirty("symbol", "add", {"uuid": component.uuid})
264
+
265
+ logger.debug(f"Added component to data: {component.reference}")
266
+
267
+ def remove_component_from_data(self, component_uuid: str) -> bool:
268
+ """
269
+ Remove a component from S-expression data.
270
+
271
+ Args:
272
+ component_uuid: UUID of component to remove
273
+
274
+ Returns:
275
+ True if removed, False if not found
276
+ """
277
+ symbols = self._data.get("symbol", [])
278
+
279
+ for i, symbol_data in enumerate(symbols):
280
+ if symbol_data.get("uuid") == component_uuid:
281
+ del symbols[i]
282
+ self.mark_dirty("symbol", "remove", {"uuid": component_uuid})
283
+ logger.debug(f"Removed component from data: {component_uuid}")
284
+ return True
285
+
286
+ logger.warning(f"Component not found for removal: {component_uuid}")
287
+ return False
288
+
289
+ def add_wire_to_data(self, wire: Wire) -> None:
290
+ """
291
+ Add a new wire to S-expression data.
292
+
293
+ Args:
294
+ wire: Wire to add
295
+ """
296
+ wire_data = self._create_wire_data_from_object(wire)
297
+
298
+ if "wire" not in self._data:
299
+ self._data["wire"] = []
300
+
301
+ self._data["wire"].append(wire_data)
302
+ self.mark_dirty("wire", "add", {"uuid": wire.uuid})
303
+
304
+ logger.debug(f"Added wire to data: {wire.uuid}")
305
+
306
+ def remove_wire_from_data(self, wire_uuid: str) -> bool:
307
+ """
308
+ Remove a wire from S-expression data.
309
+
310
+ Args:
311
+ wire_uuid: UUID of wire to remove
312
+
313
+ Returns:
314
+ True if removed, False if not found
315
+ """
316
+ wires = self._data.get("wire", [])
317
+
318
+ for i, wire_data in enumerate(wires):
319
+ if wire_data.get("uuid") == wire_uuid:
320
+ del wires[i]
321
+ self.mark_dirty("wire", "remove", {"uuid": wire_uuid})
322
+ logger.debug(f"Removed wire from data: {wire_uuid}")
323
+ return True
324
+
325
+ logger.warning(f"Wire not found for removal: {wire_uuid}")
326
+ return False
327
+
328
+ def is_dirty(self, section: Optional[str] = None) -> bool:
329
+ """
330
+ Check if data sections are dirty.
331
+
332
+ Args:
333
+ section: Specific section to check, or None for any
334
+
335
+ Returns:
336
+ True if section(s) are dirty
337
+ """
338
+ if section:
339
+ return section in self._dirty_flags
340
+ return bool(self._dirty_flags)
341
+
342
+ def get_dirty_sections(self) -> Set[str]:
343
+ """
344
+ Get all dirty data sections.
345
+
346
+ Returns:
347
+ Set of dirty section names
348
+ """
349
+ return self._dirty_flags.copy()
350
+
351
+ def clear_dirty_flags(self) -> None:
352
+ """Clear all dirty flags."""
353
+ self._dirty_flags.clear()
354
+ logger.debug("Cleared all dirty flags")
355
+
356
+ def get_change_log(self) -> List[Dict[str, Any]]:
357
+ """
358
+ Get the change log.
359
+
360
+ Returns:
361
+ List of change entries
362
+ """
363
+ return self._change_log.copy()
364
+
365
+ def clear_change_log(self) -> None:
366
+ """Clear the change log."""
367
+ self._change_log.clear()
368
+ logger.debug("Cleared change log")
369
+
370
+ def _update_symbol_data_from_component(
371
+ self, symbol_data: Dict[str, Any], component: Component
372
+ ) -> None:
373
+ """Update symbol S-expression data from component object."""
374
+ # Update position and rotation
375
+ symbol_data["at"] = [component.position.x, component.position.y, component.rotation]
376
+
377
+ # Update lib_id
378
+ symbol_data["lib_id"] = component.lib_id
379
+
380
+ # Update properties
381
+ if "property" not in symbol_data:
382
+ symbol_data["property"] = []
383
+
384
+ properties = symbol_data["property"]
385
+
386
+ # Update existing properties and add new ones
387
+ property_names = {prop.get("name") for prop in properties}
388
+
389
+ for name, value in component.properties.items():
390
+ # Find existing property or create new one
391
+ existing_prop = None
392
+ for prop in properties:
393
+ if prop.get("name") == name:
394
+ existing_prop = prop
395
+ break
396
+
397
+ if existing_prop:
398
+ existing_prop["value"] = value
399
+ else:
400
+ new_prop = {
401
+ "name": name,
402
+ "value": value,
403
+ "at": [0, 0, 0], # Default position
404
+ "effects": {"font": {"size": [1.27, 1.27]}},
405
+ }
406
+ properties.append(new_prop)
407
+
408
+ def _update_wire_data_from_object(self, wire_data: Dict[str, Any], wire: Wire) -> None:
409
+ """Update wire S-expression data from wire object."""
410
+ # Update endpoints
411
+ wire_data["pts"] = [{"xy": [wire.start.x, wire.start.y]}, {"xy": [wire.end.x, wire.end.y]}]
412
+
413
+ # Update stroke
414
+ if "stroke" not in wire_data:
415
+ wire_data["stroke"] = {}
416
+
417
+ wire_data["stroke"]["width"] = wire.stroke_width
418
+
419
+ def _create_symbol_data_from_component(self, component: Component) -> Dict[str, Any]:
420
+ """Create S-expression data structure from component object."""
421
+ symbol_data = {
422
+ "lib_id": component.lib_id,
423
+ "at": [component.position.x, component.position.y, component.rotation],
424
+ "uuid": component.uuid,
425
+ "property": [],
426
+ }
427
+
428
+ # Add properties
429
+ for name, value in component.properties.items():
430
+ prop_data = {
431
+ "name": name,
432
+ "value": value,
433
+ "at": [0, 0, 0], # Default position relative to symbol
434
+ "effects": {"font": {"size": [1.27, 1.27]}},
435
+ }
436
+ symbol_data["property"].append(prop_data)
437
+
438
+ return symbol_data
439
+
440
+ def _create_wire_data_from_object(self, wire: Wire) -> Dict[str, Any]:
441
+ """Create S-expression data structure from wire object."""
442
+ wire_data = {
443
+ "pts": [{"xy": [wire.start.x, wire.start.y]}, {"xy": [wire.end.x, wire.end.y]}],
444
+ "stroke": {"width": wire.stroke_width, "type": "default"},
445
+ "uuid": wire.uuid,
446
+ }
447
+
448
+ return wire_data
449
+
450
+ def validate_data_consistency(
451
+ self, component_collection=None, wire_collection=None
452
+ ) -> List[str]:
453
+ """
454
+ Validate consistency between objects and S-expression data.
455
+
456
+ Args:
457
+ component_collection: Collection of components to validate
458
+ wire_collection: Collection of wires to validate
459
+
460
+ Returns:
461
+ List of consistency issues found
462
+ """
463
+ issues = []
464
+
465
+ # Validate components
466
+ if component_collection:
467
+ symbols = self._data.get("symbol", [])
468
+ symbol_uuids = {sym.get("uuid") for sym in symbols if sym.get("uuid")}
469
+ component_uuids = {comp.uuid for comp in component_collection}
470
+
471
+ # Check for missing symbols
472
+ missing_symbols = component_uuids - symbol_uuids
473
+ for uuid in missing_symbols:
474
+ issues.append(f"Component {uuid} missing from symbol data")
475
+
476
+ # Check for orphaned symbols
477
+ orphaned_symbols = symbol_uuids - component_uuids
478
+ for uuid in orphaned_symbols:
479
+ issues.append(f"Symbol {uuid} has no corresponding component object")
480
+
481
+ # Validate wires
482
+ if wire_collection:
483
+ wires = self._data.get("wire", [])
484
+ wire_data_uuids = {w.get("uuid") for w in wires if w.get("uuid")}
485
+ wire_object_uuids = {wire.uuid for wire in wire_collection}
486
+
487
+ # Check for missing wire data
488
+ missing_wire_data = wire_object_uuids - wire_data_uuids
489
+ for uuid in missing_wire_data:
490
+ issues.append(f"Wire {uuid} missing from wire data")
491
+
492
+ # Check for orphaned wire data
493
+ orphaned_wire_data = wire_data_uuids - wire_object_uuids
494
+ for uuid in orphaned_wire_data:
495
+ issues.append(f"Wire data {uuid} has no corresponding wire object")
496
+
497
+ if issues:
498
+ logger.warning(f"Found {len(issues)} data consistency issues")
499
+ else:
500
+ logger.debug("Data consistency validation passed")
501
+
502
+ return issues