From f04b1acb2190d04131f9ce42d02791b4576d7691 Mon Sep 17 00:00:00 2001 From: Michael Hobbs Date: Sat, 25 Jul 2026 18:46:11 -0400 Subject: [PATCH] Derive the confidentiality profile ID from the tag alone Table E.1-1 gives the tag for its "Private Attributes" row as "(gggg,eeee) where gggg is odd". The trailing prose was carried into both the tag and the slug derived from it, so that row is published as "tag": "(GGGG,EEEE) WHERE GGGG IS ODD", "id": "ggggeeee-where-gggg-is-odd" which is the only entry in confidentiality_profile_attributes.json whose ID is not an eight-character tag slug, and the only one whose tag is a sentence. Uppercasing intended to normalise hex digits shouts the prose. Isolate the tag before deriving the ID, so this row is published as "(GGGG,EEEE)" with the ID "ggggeeee" - the same shape as the existing repeating-group rows such as "(50XX,XXXX)" / "50xxxxxx". The qualifier is not lost in any meaningful sense: an odd group number is what makes an attribute private, which the row's name already states. Rows whose tag cell contains no tag at all are passed through unchanged. --- .../extract_conf_profile_attributes.py | 22 +++++++++++-- tests/extract_conf_profile_attributes_test.py | 32 +++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 tests/extract_conf_profile_attributes_test.py diff --git a/dicom_standard/extract_conf_profile_attributes.py b/dicom_standard/extract_conf_profile_attributes.py index 3a5ac19..f0bdeca 100644 --- a/dicom_standard/extract_conf_profile_attributes.py +++ b/dicom_standard/extract_conf_profile_attributes.py @@ -2,6 +2,7 @@ Extract the listing of all attributes given in table E.1-1 from part 15 of the DICOM Standard. ''' from typing import cast, Dict, List, Union +import re import sys from bs4 import BeautifulSoup @@ -18,6 +19,8 @@ RETIREMENT_MISMATCH_ATTRIBUTES = ['Referenced Patient Alias Sequence'] +TAG_PATTERN = re.compile(r'\(\w{4},\w{4}\)') + AttrTableType = List[Dict[str, Union[str, bool]]] @@ -39,11 +42,26 @@ def get_conf_profile_table(standard: BeautifulSoup) -> List[TableDictType]: return table_to_dict(list_table, COLUMN_TITLES, omit_empty=True) +def extract_tag(raw_tag: str) -> str: + """Standard workaround: isolate the tag from a cell that qualifies it with prose + The "Private Attributes" row of Table E.1-1 gives its tag as + "(gggg,eeee) where gggg is odd". Without this, the trailing prose ends up in the + attribute's tag and ID, giving that one row a shape no other row has. + Args: + raw_tag (str): the contents of the table's tag cell + Returns: + str: the tag alone, or the original cell if it contains no tag + """ + match = TAG_PATTERN.search(raw_tag) + return match.group(0) if match else raw_tag + + def table_to_json(table: List[TableDictType]) -> List[TableDictType]: attributes = [] for attr in table: - attr['id'] = pl.create_slug(attr['tag']) - attr['tag'] = attr['tag'].upper() + tag = extract_tag(attr['tag']) + attr['id'] = pl.create_slug(tag) + attr['tag'] = tag.upper() attributes.append(attr) return attributes diff --git a/tests/extract_conf_profile_attributes_test.py b/tests/extract_conf_profile_attributes_test.py new file mode 100644 index 0000000..2d9217a --- /dev/null +++ b/tests/extract_conf_profile_attributes_test.py @@ -0,0 +1,32 @@ +''' +Unit tests covering functions in `extract_conf_profile_attributes.py`. +''' +import pytest + +from dicom_standard.extract_conf_profile_attributes import extract_tag, table_to_json + + +@pytest.mark.parametrize('raw_tag,expected', [ + ('(0008,0050)', '(0008,0050)'), + ('(50xx,xxxx)', '(50xx,xxxx)'), + ('(gggg,eeee) where gggg is odd', '(gggg,eeee)'), +]) +def test_extract_tag(raw_tag, expected): + assert extract_tag(raw_tag) == expected + + +def test_extract_tag_passes_through_cell_without_a_tag(): + assert extract_tag('No tag here') == 'No tag here' + + +def test_table_to_json_derives_id_from_the_tag_alone(): + table = [ + {'name': 'Accession Number', 'tag': '(0008,0050)'}, + {'name': 'Curve Data', 'tag': '(50xx,xxxx)'}, + {'name': 'Private Attributes', 'tag': '(gggg,eeee) where gggg is odd'}, + ] + attributes = table_to_json(table) + assert [attr['tag'] for attr in attributes] == [ + '(0008,0050)', '(50XX,XXXX)', '(GGGG,EEEE)'] + assert [attr['id'] for attr in attributes] == [ + '00080050', '50xxxxxx', 'ggggeeee']