From 3deb48a5e712d19d378b39bfcc6a8c2a5628290e Mon Sep 17 00:00:00 2001 From: DEMOCODE675 Date: Tue, 3 Mar 2026 22:45:52 +0530 Subject: [PATCH] Fix ISO control IDs being numericised to floats --- application/database/db.py | 4 +++- application/defs/cre_defs.py | 8 ++++++++ application/utils/spreadsheet.py | 11 ++++++++--- application/utils/spreadsheet_parsers.py | 7 ++++++- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/application/database/db.py b/application/database/db.py index bee7c45de..099a28e9a 100644 --- a/application/database/db.py +++ b/application/database/db.py @@ -2151,7 +2151,9 @@ def dbNodeFromStandard(standard: cre_defs.Node) -> Node: section=standard.section, subsection=standard.subsection, version=standard.version, - section_id=standard.sectionID, + # str() guard: sectionID must reach the DB column as a string. + # Protects against any float that slipped through upstream coercion. + section_id=str(standard.sectionID) if standard.sectionID is not None else "", ) diff --git a/application/defs/cre_defs.py b/application/defs/cre_defs.py index 5edf7121b..b17daa6d1 100644 --- a/application/defs/cre_defs.py +++ b/application/defs/cre_defs.py @@ -452,6 +452,11 @@ class Standard(Node): subsection: Optional[str] = "" def __post_init__(self): + # Coerce sectionID to str before building self.id. + # YAML or gspread can deliver numeric types (e.g. float 7.1 instead of + # str "7.10" when the numericise_ignore range was off-by-one). + # None is normalised to "" so the field type is always str. + self.sectionID = str(self.sectionID) if self.sectionID is not None else "" self.id = f"{self.name}" if self.sectionID: self.id += f":{self.sectionID}" @@ -496,6 +501,9 @@ class Tool(Standard): doctype: Credoctypes = Credoctypes.Tool def __post_init__(self): + # Tool builds self.id before calling super().__post_init__(), so the + # same sectionID str-coercion guard from Standard must be repeated here. + self.sectionID = str(self.sectionID) if self.sectionID is not None else "" self.id = f"{self.name}" if self.sectionID: self.id += f":{self.sectionID}" diff --git a/application/utils/spreadsheet.py b/application/utils/spreadsheet.py index b18b493dd..ebe3d4c35 100644 --- a/application/utils/spreadsheet.py +++ b/application/utils/spreadsheet.py @@ -60,8 +60,11 @@ def read_spreadsheet( ) records = wsh.get_all_records( head=1, + # +1 fixes off-by-one: range(1, col_count) excluded the last + # column, leaving it numericized by gspread and converting + # section IDs like "7.10" silently to float 7.1. numericise_ignore=list( - range(1, wsh.col_count) + range(1, wsh.col_count + 1) ), # Added numericise_ignore parameter ) # workaround because of https://github.com/burnash/gspread/issues/1007 # this will break if the column names are in any other line toyaml = yaml.safe_load(yaml.safe_dump(records)) @@ -69,9 +72,11 @@ def read_spreadsheet( elif not parse_numbered_only: records = wsh.get_all_records( head=1, + # +1 fixes off-by-one: range(1, col_count) excluded the last column. + # DO NOT make this 'all', gspread has a bug numericise_ignore=list( - range(1, wsh.col_count) - ), # Added numericise_ignore parameter -- DO NOT make this 'all', gspread has a bug + range(1, wsh.col_count + 1) + ), # Added numericise_ignore parameter ) # workaround because of https://github.com/burnash/gspread/issues/1007 # this will break if the column names are in any other line toyaml = yaml.safe_load(yaml.safe_dump(records)) result[wsh.title] = toyaml diff --git a/application/utils/spreadsheet_parsers.py b/application/utils/spreadsheet_parsers.py index 867b0f42e..16415dd33 100644 --- a/application/utils/spreadsheet_parsers.py +++ b/application/utils/spreadsheet_parsers.py @@ -209,7 +209,12 @@ def parse_export_format(lfile: List[Dict[str, Any]]) -> Dict[str, List[defs.Docu if not is_empty(mapping_line.get(f"{s}{defs.ExportFormat.separator}name")): working_standard = defs.Standard( name=s, - sectionID=mapping_line.get(f"{s}{defs.ExportFormat.separator}id"), + # Explicit str() cast: YAML/gspread can deliver a numeric + # type (e.g. float 7.1 instead of str "7.10"). Casting here + # ensures Standard always receives a string. + sectionID=str( + mapping_line.get(f"{s}{defs.ExportFormat.separator}id") or "" + ), section=mapping_line.get(f"{s}{defs.ExportFormat.separator}name"), hyperlink=mapping_line.get( f"{s}{defs.ExportFormat.separator}hyperlink", ""