|
| 1 | +# MIG SQLModels Design |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Add SQLModel support for Message Implementation Guides (MIGs), mirroring the existing AHB SQLModel implementation. This enables persisting MIG data to SQL databases with the same patterns used for AHBs. |
| 6 | + |
| 7 | +## File Structure |
| 8 | + |
| 9 | +Create new file: `src/fundamend/sqlmodels/messageimplementationguide.py` |
| 10 | + |
| 11 | +## Classes |
| 12 | + |
| 13 | +| SQL Model Class | Pydantic Source | Has `position`? | |
| 14 | +|-----------------|-----------------|-----------------| |
| 15 | +| `MigCode` | `models.messageimplementationguide.Code` | Yes | |
| 16 | +| `MigDataElement` | `models.messageimplementationguide.DataElement` | Yes | |
| 17 | +| `MigDataElementGroup` | `models.messageimplementationguide.DataElementGroup` | Yes | |
| 18 | +| `MigSegment` | `models.messageimplementationguide.Segment` | Yes | |
| 19 | +| `MigSegmentGroupLink` | (artificial join table) | No | |
| 20 | +| `MigSegmentGroup` | `models.messageimplementationguide.SegmentGroup` | Yes | |
| 21 | +| `MessageImplementationGuide` | `models.messageimplementationguide.MessageImplementationGuide` | No | |
| 22 | + |
| 23 | +## Field Mappings |
| 24 | + |
| 25 | +### MigCode |
| 26 | +- `primary_key: UUID` |
| 27 | +- `name: str` |
| 28 | +- `description: str | None` |
| 29 | +- `value: str | None` |
| 30 | +- `position: Optional[int]` |
| 31 | +- FK: `data_element_primary_key` |
| 32 | + |
| 33 | +### MigDataElement |
| 34 | +- `primary_key: UUID` |
| 35 | +- `id: str` |
| 36 | +- `name: str` |
| 37 | +- `description: str | None` |
| 38 | +- `status_std: str` (MigStatus stored as string) |
| 39 | +- `status_specification: str` |
| 40 | +- `format_std: str` |
| 41 | +- `format_specification: str` |
| 42 | +- `codes: list[MigCode]` |
| 43 | +- `position: Optional[int]` |
| 44 | +- FK: `data_element_group_primary_key` (optional) |
| 45 | +- FK: `segment_primary_key` (optional) |
| 46 | + |
| 47 | +### MigDataElementGroup |
| 48 | +- `primary_key: UUID` |
| 49 | +- `id: str` |
| 50 | +- `name: str` |
| 51 | +- `description: str | None` |
| 52 | +- `status_std: str` |
| 53 | +- `status_specification: str` |
| 54 | +- `data_elements: list[MigDataElement]` |
| 55 | +- `position: Optional[int]` |
| 56 | +- FK: `segment_primary_key` |
| 57 | + |
| 58 | +### MigSegment |
| 59 | +- `primary_key: UUID` |
| 60 | +- `id: str` |
| 61 | +- `name: str` |
| 62 | +- `description: str | None` |
| 63 | +- `counter: str` |
| 64 | +- `level: int` |
| 65 | +- `number: str` |
| 66 | +- `max_rep_std: int` |
| 67 | +- `max_rep_specification: int` |
| 68 | +- `status_std: str` |
| 69 | +- `status_specification: str` |
| 70 | +- `example: str | None` |
| 71 | +- `is_on_uebertragungsdatei_level: bool` |
| 72 | +- `data_elements: list[MigDataElement]` |
| 73 | +- `data_element_groups: list[MigDataElementGroup]` |
| 74 | +- `position: Optional[int]` |
| 75 | +- FK: `segmentgroup_primary_key` (optional) |
| 76 | +- FK: `mig_primary_key` (optional) |
| 77 | + |
| 78 | +### MigSegmentGroupLink |
| 79 | +- `parent_id: UUID` (FK to MigSegmentGroup, PK) |
| 80 | +- `child_id: UUID` (FK to MigSegmentGroup, PK) |
| 81 | + |
| 82 | +### MigSegmentGroup |
| 83 | +- `primary_key: UUID` |
| 84 | +- `id: str` |
| 85 | +- `name: str` |
| 86 | +- `counter: str` |
| 87 | +- `level: int` |
| 88 | +- `max_rep_std: int` |
| 89 | +- `max_rep_specification: int` |
| 90 | +- `status_std: str` |
| 91 | +- `status_specification: str` |
| 92 | +- `segments: list[MigSegment]` |
| 93 | +- `segment_groups: list[MigSegmentGroup]` (via MigSegmentGroupLink) |
| 94 | +- `parent_segment_group: MigSegmentGroup | None` (via MigSegmentGroupLink) |
| 95 | +- `position: Optional[int]` |
| 96 | +- FK: `mig_primary_key` (optional) |
| 97 | + |
| 98 | +### MessageImplementationGuide |
| 99 | +- `primary_key: UUID` |
| 100 | +- `veroeffentlichungsdatum: date` |
| 101 | +- `autor: str` |
| 102 | +- `versionsnummer: str` |
| 103 | +- `format: EdifactFormat` |
| 104 | +- `segments: list[MigSegment]` |
| 105 | +- `segment_groups: list[MigSegmentGroup]` |
| 106 | +- SQL-only fields: |
| 107 | + - `gueltig_von: Optional[date]` |
| 108 | + - `gueltig_bis: Optional[date]` |
| 109 | + - `edifact_format_version: Optional[EdifactFormatVersion]` |
| 110 | + |
| 111 | +## Relationships |
| 112 | + |
| 113 | +``` |
| 114 | +MessageImplementationGuide |
| 115 | +├── segments: list[MigSegment] (FK: mig_primary_key) |
| 116 | +└── segment_groups: list[MigSegmentGroup] (FK: mig_primary_key) |
| 117 | +
|
| 118 | +MigSegmentGroup |
| 119 | +├── segments: list[MigSegment] (FK: segmentgroup_primary_key) |
| 120 | +├── segment_groups: list[MigSegmentGroup] (via MigSegmentGroupLink) |
| 121 | +└── parent_segment_group: MigSegmentGroup (via MigSegmentGroupLink) |
| 122 | +
|
| 123 | +MigSegment |
| 124 | +├── data_elements: list[MigDataElement] (FK: segment_primary_key) |
| 125 | +└── data_element_groups: list[MigDataElementGroup] (FK: segment_primary_key) |
| 126 | +
|
| 127 | +MigDataElementGroup |
| 128 | +└── data_elements: list[MigDataElement] (FK: data_element_group_primary_key) |
| 129 | +
|
| 130 | +MigDataElement |
| 131 | +└── codes: list[MigCode] (FK: data_element_primary_key) |
| 132 | +``` |
| 133 | + |
| 134 | +## UniqueConstraints |
| 135 | + |
| 136 | +Following AHB pattern - position unique within parent: |
| 137 | +- `MigCode`: `(data_element_primary_key, position)` |
| 138 | +- `MigDataElementGroup`: `(segment_primary_key, position)` |
| 139 | +- `MigSegment`: position unique per parent (MIG or SegmentGroup) - separate constraints |
| 140 | +- `MigSegmentGroup`: `(mig_primary_key, position)` |
| 141 | + |
| 142 | +## Conversion Logic |
| 143 | + |
| 144 | +### `from_model()` pattern |
| 145 | +- Classmethod taking Pydantic model + optional `position` parameter |
| 146 | +- Direct field mappings |
| 147 | +- Enumerate children to get position index |
| 148 | +- Use `isinstance` checks for union types to route to correct list |
| 149 | + |
| 150 | +### `to_model()` pattern |
| 151 | +- Instance method returning Pydantic model |
| 152 | +- Merge union-type lists, sort by `position` |
| 153 | +- Return `tuple()` for Pydantic tuple fields |
| 154 | +- MigStatus enum restored via Pydantic validation |
| 155 | + |
| 156 | +## Exports |
| 157 | + |
| 158 | +Update `sqlmodels/__init__.py`: |
| 159 | +```python |
| 160 | +from .messageimplementationguide import ( |
| 161 | + MigCode, |
| 162 | + MigDataElement, |
| 163 | + MigDataElementGroup, |
| 164 | + MigSegment, |
| 165 | + MigSegmentGroup, |
| 166 | + MessageImplementationGuide, |
| 167 | +) |
| 168 | +``` |
| 169 | + |
| 170 | +## Testing |
| 171 | + |
| 172 | +New file: `unittests/test_sqlmodels_messageimplementationguide.py` |
| 173 | + |
| 174 | +Tests: |
| 175 | +1. Single MIG roundtrip: XML → Pydantic → SQL → Pydantic → compare equality |
| 176 | +2. All MIGs from private submodule (skip if not checked out) |
| 177 | + |
| 178 | +Reuse `sqlite_session` fixture from conftest. |
| 179 | + |
| 180 | +## Future Work (out of scope) |
| 181 | + |
| 182 | +- `MigHierarchyMaterialized` view for flattening |
| 183 | +- Prefix AHB classes (`AhbCode`, `AhbDataElement`, etc.) for consistency |
0 commit comments