fix: use semantic qualifiers for stable id_path construction - #260
Conversation
…ath deduplication (#256) Replace the positional @sort_path tiebreaker in id_path construction with semantic qualifiers derived from EDIFACT code values. This makes id_paths stable across format versions when segments are inserted or removed. Before: SG4>DTM>@00004-00001- (shifts when upstream segments change position) After: SG4>DTM+92> (stable: qualifier 92 identifies the segment semantically) The approach: 1. Pre-compute qualifiers for segments, segment groups, and data elements by finding the first code value in each element's subtree 2. Inject +qualifier into id_path during the existing recursive CTE (only where sibling elements share the same ID) 3. Fall back to #N occurrence counter for remaining duplicates where qualifiers are NULL or shared (e.g. IFTSTA STS segments, PARTIN FII) Based on the bug report and analysis by hf-mrdachner in #256, #258, #259. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use raw SQL instead of ORM for id_path pattern checks (fixes pylint no-member/not-callable) - Use MIN(qualifier) in _sg_qual GROUP BY for deterministic results - Assert old_count > 0 loudly instead of silently skipping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t data The CI submodule may not contain UTILMD/44001 in FV2510. Skip gracefully instead of failing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Testing against production data (FV2510 → FV2604)Built a fresh DB with this branch and compared against the original (broken) view and our
UTILMD/44001 — slightly different but close+6/-1 instead of +5/-0 — one extra add and one delete. Minor. MSCONS/13016 — worse than original+43/-26 instead of +17/-0. The issue: the semantic qualifier for Similarly, This seems to be a case where the qualifier selection algorithm picks a different "first code" between versions, even though the semantic meaning is the same. |
|
Ich mag den Ansatz sehr das zu fixen! Anscheinend gibt es aber noch Probleme. Der id path ist doch aber gar nicht hauptsächlich für den cross version vergleich, oder? Wenn der Innerhalb einer Version konsistent ist, aber für den use case eben probleme hat, müssen wir den auch nicht unbedingt durch die Form pressen. Ich gehe den Weg aber gerne weiter! |
|
Thanks for the thorough testing, @hf-mrdachner! Great analysis. I investigated the MSCONS/13016 case. The root cause is that the AHB itself changed: FV2510: The qualifying data element was there in both versions, but the AHB authors didn't list codes under it in FV2510. When they added This is fundamentally a case where the AHB data structure itself changed (a code was added to a DE that previously had none). No qualifier algorithm can produce the same result when the source data is different. Options to discuss:
What do you think? Option 1 or 2 seems most pragmatic to me. The current PR already improves |
…qualifiers Reverse the COALESCE order in segment qualifier computation: try codes from data element groups (composites like C_C082) before direct data elements (like D_3035). DEG structure comes from the MIG (stable across AHB versions), whereas direct DE codes are AHB customizations that may be added/removed between format versions. Example: NAD's D_3035 qualifier was absent in FV2510 but added with code 'MS' in FV2604, changing the qualifier from 9 (C_C082>D_3055) to MS. Using DEG-first keeps it stable as 9 across both versions. This fixes the MSCONS/13016 cross-version instability reported by @hf-mrdachner: before this fix, SG2 got qualifier '+9' in FV2510 but '+MS' in FV2604, causing false adds/deletes in the diff view. Based on testing by hf-mrdachner in #260 review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@hf-mrdachner I pushed a fix for the MSCONS cross-version instability you found (c5b9266). Root causeThe qualifier selection preferred direct DE codes (like FixReversed the COALESCE order: now DEG-based codes (from EDIFACT composites/MIG structure) are preferred over direct DE codes (AHB customizations). DEG structure is defined by the MIG and stable across AHB versions, while direct DE codes can be added/removed. Results after fix
MSCONS/13016 now has 95% cross-version id_path overlap (was broken). SG2 is stable as Could you re-test with the latest push? Especially interested in the cases you checked before (INVOIC/31002, PARTIN/37000). |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover data elements directly under segments (without a DEG parent) in both the _de_needs_qual detection and the CTE id_path construction. Previously only DEs inside DEGs got qualifier injection; bare DEs fell through to the #N counter. Now both paths are consistent. Found by Python/SQLite expert review. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hmmm. Spannend. Ich muss das mal testen finde es aber komisch. NAD+MS war natürlich schon immer NAD+MS und auch in den vorherigen Versionen im EDIFACT und den Menschen AHBs. Dass das nicht in den gekauften Daten vom BDEW drin ist, klingt ja irgendwie eher nach einem entsprechenden Fehler, um den wir herum bauen. Und wir zeigen das doch in ahb-tabellen.de auch an? Da jetzt den Wert zu nehmen, der ja gar nicht die Bedeutung wiederspiegelt, fühlt sich irgendwie komisch an. Auch wenn das Ergebnis stimmt. Irgendwas in mir sagt, wir müssten eigentlich die Daten korrigieren 😅 |
wäre ja nicht das erste Mal, dass die XMLs Mist sind. Statustransaktionszusatzgrund oder was das letzte Mal das Problem war.... muss zugeben, dass ich in diesem Fall aber auch icht händisch verifiziert habe, ob das stimmt, was claude behauptet. |
|
ich hab nochmal nachgeahscut in ahb-tabellen.de und dem PDF für 13016 auf FV2504: die Daten sind einfach falsch. Natürlich ist das NAD+MS. Ein Feature rund um falsche Daten zu bauen, finde ich irgendwie falsch. Ich sehe also zwei Ansätze:
|
…version qualifiers" Revert "test: update MSCONS diff snapshot for DEG-first qualifier selection" The DEG-first approach worked around a data quality bug (missing D_3035 codes in FV2510 XMLs) rather than using semantically correct qualifiers. The PDF AHBs confirm NAD+MS/NAD+MR are the correct qualifiers. The XML data for FV2510 is simply missing these codes — this is a data bug, not an algorithm problem. Direct-DE-first gives the correct qualifiers when the data is correct. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@hf-mrdachner insgesamt ist es doch aber - unabhängig von den mscons quircks - eine verbesserung, dass man nicht so viel sort-path-artefakte im id-path hat. Das soltle die cross-version-kompatibilität schon verbessern |
|
Good catch, both of you. I reverted the DEG-first change (ecf57f7). @hf-mrdachner is right — the XML data for FV2510 is simply missing the Reverted to direct-DE-first — this gives the semantically correct The algorithm itself is now correct:
The data fix for FV2510 should be tracked separately — is there an existing issue for XML data corrections, or should we file one? |
|
eben. das war aber nicht im XML... Hab schon überlegt, ob wir einen Patch-Mechanismus dafür einbauen, um nicht in den fremden Daten zu schreiben. wobei da jetzt vielleicht auch egal ist. Eigentlich müsste man die beim BDEW reklamieren, als falsch |
|
Filed Hochfrequenz/xml-migs-and-ahbs#80 for the data bug. Summary: Exactly 2 I reverted the DEG-first workaround — the algorithm now correctly uses direct-DE-first, which gives |
Port the qualifier-based id_path dedup from materialize_ahb_view.sql to materialize_mig_view.sql. Replaces positional @sort_path with semantic +qualifier for stable cross-version MIG id_paths. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rows Now that id_path uses semantic qualifiers (from #260), match on id_path instead of path — consistent with AHB diff view. This eliminates the Cartesian product duplicates caused by non-unique path values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Summary
Fixes #256. Replaces the positional
@sort_pathtiebreaker inid_pathconstruction with semantic qualifiers derived from EDIFACT code values, makingid_pathstable across format versions.This is based on the excellent bug analysis by @hf-mrdachner in #256, who correctly identified that positional indices in
id_pathshift when segments are inserted between versions, causingv_ahb_diffto produce false adds/deletes. Their analysis in #258 and #259 also identified important edge cases that informed this fix.The Problem
When a new segment is inserted in a newer format version, all subsequent positional indices shift:
The
v_ahb_diffJOIN onid_pathfails → false adds + deletes for unchanged lines. For UTILMD/44001 (FV2510→FV2604), this produced 69 "added" + 64 "deleted" instead of just 5 genuinely new lines.The Fix
Approach: Qualifier injection during CTE construction
Instead of fixing the diff view (as proposed in #257), we fix
id_pathconstruction itself at the source — inmaterialize_ahb_view.sql. This preservesid_pathas a single unique column, maintaining all downstream guarantees.Before (positional, unstable):
After (semantic, stable):
How it works
Pre-compute qualifiers — Before the main CTE, we compute the first
code_valuein each segment/SG/DE's subtree using temp tables. This is the EDIFACT "qualifier" that semantically identifies the element (e.g., DTM qualifier 92 = delivery date, 93 = validity date).Inject into CTE — The existing recursive CTE is modified to append
+qualifierto the element ID when a sibling with the same ID exists under the same parent. Children automatically inherit the qualified prefix from their parent.Fallback counter — For ~0.6% of rows where the qualifier is NULL or shared among siblings, a
#Noccurrence counter is appended. This usesROW_NUMBER()pre-computed in a temp table (required because SQLite processes UPDATE rows sequentially, breaking self-referencing counters).Edge cases handled
#Ncounter fallback#Ncounter fallback#Ncounter fallback#Ncounter fallback#Ncounter fallbackWhy IFTSTA still needs the
#NtiebreakerIFTSTA (prüfi 21045) uses a flat segment group naming scheme where multiple STS segments under "Grund der Privilegierung nach EnFG" share the same structural path AND the same qualifying code value. Since the qualifier can't distinguish them, the
#Ncounter is the only option. This is acceptable because these truly identical elements are stable in order across versions.Production DB validation
Tested against the full production database (308,993 rows, 595 prüfidentifikators, 4 format versions):
#Ncounter fallbackZero duplicates. UNIQUE INDEX succeeds.
Cross-version stability
For UTILMD/44001 (FV2510→FV2604), >90% of
id_pathvalues now match between versions, compared to ~57% with the old@sort_pathapproach.Test plan
🤖 Generated with Claude Code