From f44a8951e948019ec110edd69f13dc3e48e2e4ac Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 12 Mar 2026 14:11:28 +0000 Subject: [PATCH] Add bridge files for ndi.epoch and ndi.common packages; remove get_logger alias Create ndi_matlab_python_bridge.yaml contracts for the ndi.epoch and ndi.common namespaces, documenting all classes, methods, properties, and standalone functions with their MATLAB-Python type mappings. Remove the backwards-compat get_logger alias in favor of the MATLAB-mirrored getLogger per Strict Naming policy. https://claude.ai/code/session_012G5yTp2wY8NwPHZzNbbahD --- src/ndi/__init__.py | 4 +- src/ndi/common/__init__.py | 5 - src/ndi/common/ndi_matlab_python_bridge.yaml | 130 +++++ src/ndi/epoch/ndi_matlab_python_bridge.yaml | 578 +++++++++++++++++++ 4 files changed, 710 insertions(+), 7 deletions(-) create mode 100644 src/ndi/common/ndi_matlab_python_bridge.yaml create mode 100644 src/ndi/epoch/ndi_matlab_python_bridge.yaml diff --git a/src/ndi/__init__.py b/src/ndi/__init__.py index 23f2dec..e9c4c95 100644 --- a/src/ndi/__init__.py +++ b/src/ndi/__init__.py @@ -41,7 +41,7 @@ # Import session and cache modules (Phase 7) from .cache import Cache from .calculator import Calculator -from .common import PathConstants, get_logger, getLogger, timestamp +from .common import PathConstants, getLogger, timestamp from .database import Database, open_database from .dataset import Dataset, DatasetDir from .document import Document @@ -100,7 +100,7 @@ def version() -> tuple: "open_database", "PathConstants", "timestamp", - "get_logger", + "getLogger", "time", "daq", "file", diff --git a/src/ndi/common/__init__.py b/src/ndi/common/__init__.py index dce5b24..d984521 100644 --- a/src/ndi/common/__init__.py +++ b/src/ndi/common/__init__.py @@ -154,10 +154,6 @@ def getLogger(name: str = "ndi"): return logging.getLogger(name) -# Keep old name for backwards compatibility -get_logger = getLogger - - # --------------------------------------------------------------------------- # Singleton cache — mirrors MATLAB ndi.common.getCache # --------------------------------------------------------------------------- @@ -260,7 +256,6 @@ def assertDIDInstalled() -> None: "PathConstants", "timestamp", "getLogger", - "get_logger", "getCache", "getDatabaseHierarchy", "assertDIDInstalled", diff --git a/src/ndi/common/ndi_matlab_python_bridge.yaml b/src/ndi/common/ndi_matlab_python_bridge.yaml new file mode 100644 index 0000000..34f9cfb --- /dev/null +++ b/src/ndi/common/ndi_matlab_python_bridge.yaml @@ -0,0 +1,130 @@ +# ndi_matlab_python_bridge.yaml — src/ndi/common/ +# The Primary Contract for the ndi.common namespace. + +project_metadata: + bridge_version: "1.1" + naming_policy: "Strict MATLAB Mirror" + indexing_policy: "Semantic Parity (1-based for user concepts, 0-based for internal data)" + +# ========================================================================= +# Standalone functions +# ========================================================================= +functions: + + - name: timestamp + type: function + matlab_path: null + python_path: "ndi/common/__init__.py" + input_arguments: [] + output_arguments: + - name: ts + type_python: "str" + decision_log: > + Python-specific utility. No direct MATLAB equivalent. + Returns ISO 8601 timestamp in UTC. + + - name: getLogger + type: function + matlab_path: "+ndi/+common/getLogger.m" + python_path: "ndi/common/__init__.py" + input_arguments: + - name: name + type_matlab: "char" + type_python: "str" + default: "'ndi'" + output_arguments: + - name: logger + type_python: "logging.Logger" + decision_log: "Exact match." + + - name: getCache + type: function + matlab_path: "+ndi/+common/getCache.m" + python_path: "ndi/common/__init__.py" + input_arguments: [] + output_arguments: + - name: cache + type_python: "Cache" + decision_log: "Exact match. Returns a global singleton." + + - name: getDatabaseHierarchy + type: function + matlab_path: "+ndi/+common/getDatabaseHierarchy.m" + python_path: "ndi/common/__init__.py" + input_arguments: [] + output_arguments: + - name: hierarchy + type_python: "dict[str, Any]" + decision_log: > + Exact match. Reads document definitions from ndi_common/database_documents. + Result is cached after first call. + + - name: assertDIDInstalled + type: function + matlab_path: "+ndi/+common/assertDIDInstalled.m" + python_path: "ndi/common/__init__.py" + input_arguments: [] + output_arguments: [] + decision_log: > + Exact match. Raises ImportError if 'did' package not installed. + MATLAB equivalent raises error if DID toolbox is missing. + +# ========================================================================= +# Classes +# ========================================================================= +classes: + + # ========================================================================= + # ndi.common.PathConstants + # ========================================================================= + - name: PathConstants + type: class + matlab_path: "+ndi/+common/PathConstants.m" + python_path: "ndi/common/__init__.py" + python_class: "PathConstants" + + properties: + - name: NDI_ROOT + type_matlab: "char" + type_python: "Path" + access: "class property (read-only)" + decision_log: "Exact match. Python uses pathlib.Path." + + - name: COMMON_FOLDER + type_matlab: "char" + type_python: "Path" + access: "class property (read-only)" + decision_log: "Exact match. Python uses pathlib.Path." + + - name: DOCUMENT_PATH + type_matlab: "char" + type_python: "Path" + access: "class property (read-only)" + decision_log: "Exact match. Python uses pathlib.Path." + + - name: SCHEMA_PATH + type_matlab: "char" + type_python: "Path" + access: "class property (read-only)" + decision_log: "Exact match. Python uses pathlib.Path." + + methods: + - name: set_paths + kind: classmethod + input_arguments: + - name: ndi_root + type_python: "Path | None" + default: "None" + - name: common_folder + type_python: "Path | None" + default: "None" + - name: document_path + type_python: "Path | None" + default: "None" + - name: schema_path + type_python: "Path | None" + default: "None" + output_arguments: [] + decision_log: > + Python-specific method for programmatic path overrides. + MATLAB uses environment variables or property assignment. diff --git a/src/ndi/epoch/ndi_matlab_python_bridge.yaml b/src/ndi/epoch/ndi_matlab_python_bridge.yaml new file mode 100644 index 0000000..935c32e --- /dev/null +++ b/src/ndi/epoch/ndi_matlab_python_bridge.yaml @@ -0,0 +1,578 @@ +# ndi_matlab_python_bridge.yaml — src/ndi/epoch/ +# The Primary Contract for the ndi.epoch namespace. + +project_metadata: + bridge_version: "1.1" + naming_policy: "Strict MATLAB Mirror" + indexing_policy: "Semantic Parity (1-based for user concepts, 0-based for internal data)" + +# ========================================================================= +# Standalone functions +# ========================================================================= +functions: + + - name: epochrange + type: function + matlab_path: "+ndi/+epoch/epochrange.m" + python_path: "ndi/epoch/functions.py" + input_arguments: + - name: epochset_obj + type_matlab: "ndi.epoch.epochset" + type_python: "Any" + - name: clocktype + type_matlab: "ndi.time.clocktype" + type_python: "ClockType" + - name: first_epoch + type_matlab: "integer | char" + type_python: "int | str" + - name: last_epoch + type_matlab: "integer | char" + type_python: "int | str" + output_arguments: + - name: epoch_ids + type_python: "list[str]" + - name: epoch_table + type_python: "list[dict]" + - name: t0_t1 + type_python: "list[tuple[float, float]]" + decision_log: > + Exact match. Returns a 3-tuple. + Epoch numbers are 1-indexed (user concept). + + - name: findepochnode + type: function + matlab_path: "+ndi/+epoch/findepochnode.m" + python_path: "ndi/epoch/functions.py" + input_arguments: + - name: epoch_node + type_matlab: "struct" + type_python: "dict[str, Any]" + - name: epoch_node_array + type_matlab: "struct array" + type_python: "list[dict[str, Any]]" + output_arguments: + - name: index + type_python: "list[int]" + decision_log: > + MATLAB returns 1-based indices. Python returns 0-based indices + (internal data structure access). This is an intentional divergence + per the Semantic Parity rule: the returned indices are used for + internal array indexing, not user-facing epoch counting. + + - name: is_epoch_or_empty + type: function + matlab_path: null + python_path: "ndi/epoch/epoch.py" + input_arguments: + - name: value + type_python: "Any" + output_arguments: + - name: result + type_python: "bool" + decision_log: > + Python-specific validation utility. No direct MATLAB equivalent. + + - name: parse_devicestring + type: function + matlab_path: "+ndi/+epoch/epochprobemap_daqsystem.m (inline)" + python_path: "ndi/epoch/epochprobemap.py" + input_arguments: + - name: devicestring + type_matlab: "char" + type_python: "str" + output_arguments: + - name: components + type_python: "dict[str, str]" + decision_log: > + Extracted as a standalone function in Python. MATLAB has this + logic inline in the epochprobemap_daqsystem class. + + - name: build_devicestring + type: function + matlab_path: "+ndi/+epoch/epochprobemap_daqsystem.m (inline)" + python_path: "ndi/epoch/epochprobemap.py" + input_arguments: + - name: name + type_matlab: "char" + type_python: "str" + - name: deviceclass + type_matlab: "char" + type_python: "str" + default: "''" + - name: details + type_matlab: "char" + type_python: "str" + default: "''" + output_arguments: + - name: devicestring + type_python: "str" + decision_log: > + Extracted as a standalone function in Python. MATLAB has this + logic inline in the epochprobemap_daqsystem class. + +# ========================================================================= +# Classes +# ========================================================================= +classes: + + # ========================================================================= + # ndi.epoch.epochset (abstract base class) + # ========================================================================= + - name: epochset + type: class + matlab_path: "+ndi/+epoch/epochset.m" + python_path: "ndi/epoch/epochset.py" + python_class: "EpochSet" + + methods: + # --- Abstract methods --- + - name: buildepochtable + kind: abstract + input_arguments: [] + output_arguments: + - name: et + type_python: "list[dict[str, Any]]" + decision_log: "Exact match. Abstract in both." + + - name: epochsetname + kind: abstract + input_arguments: [] + output_arguments: + - name: name + type_python: "str" + decision_log: "Exact match. Abstract in both." + + - name: issyncgraphroot + kind: abstract + input_arguments: [] + output_arguments: + - name: b + type_python: "bool" + decision_log: "Exact match. Abstract in both." + + # --- Public instance methods --- + - name: epochtable + input_arguments: + - name: force_rebuild + type_matlab: "logical (implicit via cache)" + type_python: "bool" + default: "False" + output_arguments: + - name: et + type_python: "list[dict[str, Any]]" + - name: hashvalue + type_python: "str" + decision_log: > + MATLAB returns (et, hashvalue) tuple. Python matches exactly. + Python adds explicit force_rebuild parameter; MATLAB rebuilds + implicitly when cache is stale. + + - name: numepochs + input_arguments: [] + output_arguments: + - name: n + type_python: "int" + decision_log: "Exact match." + + - name: epochclock + input_arguments: + - name: epoch_number + type_matlab: "integer" + type_python: "int" + output_arguments: + - name: ec + type_python: "list[ClockType]" + decision_log: > + Semantic Parity: epoch_number is 1-indexed (user concept). + Internally maps to 0-based index. + + - name: t0_t1 + input_arguments: + - name: epoch_number + type_matlab: "integer" + type_python: "int" + output_arguments: + - name: t0t1 + type_python: "list[tuple[float, float]]" + decision_log: > + Semantic Parity: epoch_number is 1-indexed (user concept). + Internally maps to 0-based index. + + - name: epochid + input_arguments: + - name: epoch_number + type_matlab: "integer" + type_python: "int" + output_arguments: + - name: epoch_id + type_python: "str" + decision_log: > + Semantic Parity: epoch_number is 1-indexed (user concept). + Internally maps to 0-based index. + + - name: epochnumber + input_arguments: + - name: epoch_id + type_matlab: "char" + type_python: "str" + output_arguments: + - name: epoch_number + type_python: "int" + decision_log: > + Semantic Parity: returns 1-indexed epoch number + (user-facing concept). + + - name: matchedepochtable + input_arguments: + - name: epoch_number + type_matlab: "integer (optional)" + type_python: "int | None" + default: "None" + - name: epoch_id + type_matlab: "char (optional)" + type_python: "str | None" + default: "None" + output_arguments: + - name: et + type_python: "list[dict[str, Any]]" + decision_log: "Exact match." + + - name: epochtableentry + input_arguments: + - name: epoch_number + type_matlab: "integer" + type_python: "int" + output_arguments: + - name: entry + type_python: "dict[str, Any]" + decision_log: > + Semantic Parity: epoch_number is 1-indexed (user concept). + Internally maps to 0-based index. + + - name: epochgraph + input_arguments: [] + output_arguments: + - name: nodes + type_python: "list[dict[str, Any]]" + decision_log: "Exact match." + + - name: resetepochtable + input_arguments: [] + output_arguments: [] + decision_log: "Exact match. Clears the epoch table cache." + + # ========================================================================= + # ndi.epoch.epoch (immutable data class) + # ========================================================================= + - name: epoch + type: class + matlab_path: "+ndi/+epoch/epoch.m" + python_path: "ndi/epoch/epoch.py" + python_class: "Epoch" + + properties: + - name: epoch_number + type_matlab: "integer" + type_python: "int" + decision_log: "Exact match. Default 0 = unknown." + + - name: epoch_id + type_matlab: "char" + type_python: "str" + decision_log: "Exact match." + + - name: epoch_session_id + type_matlab: "char" + type_python: "str" + decision_log: "Exact match." + + - name: epochprobemap + type_matlab: "ndi.epoch.epochprobemap array" + type_python: "tuple[EpochProbeMap, ...]" + decision_log: "Python uses immutable tuple. MATLAB uses cell array." + + - name: epoch_clock + type_matlab: "ndi.time.clocktype array" + type_python: "tuple[ClockType, ...]" + decision_log: "Python uses immutable tuple. MATLAB uses cell array." + + - name: t0_t1 + type_matlab: "cell array of [t0 t1]" + type_python: "tuple[tuple[float, float], ...]" + decision_log: "Python uses immutable nested tuples." + + - name: epochset_object + type_matlab: "ndi.epoch.epochset" + type_python: "EpochSet | None" + decision_log: "Exact match. None when not associated." + + - name: underlying_epochs + type_matlab: "ndi.epoch.epoch array" + type_python: "tuple[Epoch, ...]" + decision_log: "Python uses immutable tuple." + + - name: underlying_files + type_matlab: "cell array of char" + type_python: "tuple[str, ...]" + decision_log: "Python uses immutable tuple." + + methods: + - name: from_dict + kind: classmethod + input_arguments: + - name: data + type_python: "dict[str, Any]" + output_arguments: + - name: epoch_obj + type_python: "Epoch" + decision_log: > + Python-specific factory method for deserialization. + No direct MATLAB equivalent. + + - name: to_dict + input_arguments: [] + output_arguments: + - name: data + type_python: "dict[str, Any]" + decision_log: > + Python-specific serialization method. + No direct MATLAB equivalent. + + - name: has_clock + input_arguments: + - name: clock + type_python: "ClockType" + output_arguments: + - name: b + type_python: "bool" + decision_log: "Python-specific convenience method." + + - name: time_range + input_arguments: + - name: clock + type_python: "ClockType" + output_arguments: + - name: t0_t1 + type_python: "tuple[float, float] | None" + decision_log: "Python-specific convenience method." + + - name: matches_probe + input_arguments: + - name: name + type_matlab: "char" + type_python: "str" + - name: reference + type_matlab: "integer" + type_python: "int" + - name: type + type_matlab: "char" + type_python: "str" + output_arguments: + - name: b + type_python: "bool" + decision_log: "Python-specific convenience method." + + # ========================================================================= + # ndi.epoch.epochprobemap + # ========================================================================= + - name: epochprobemap + type: class + matlab_path: "+ndi/+epoch/epochprobemap.m" + python_path: "ndi/epoch/epochprobemap.py" + python_class: "EpochProbeMap" + + properties: + - name: name + type_matlab: "char" + type_python: "str" + decision_log: "Exact match. No whitespace allowed." + + - name: reference + type_matlab: "integer" + type_python: "int" + decision_log: "Exact match. Non-negative." + + - name: type + type_matlab: "char" + type_python: "str" + decision_log: "Exact match. No whitespace allowed." + + - name: devicestring + type_matlab: "char" + type_python: "str" + decision_log: "Exact match. Format: 'devicename:class:details'." + + - name: subjectstring + type_matlab: "char" + type_python: "str" + decision_log: "Exact match." + + methods: + - name: epochprobemap + kind: constructor + input_arguments: + - name: name + type_matlab: "char" + type_python: "str" + - name: reference + type_matlab: "integer" + type_python: "int" + - name: type + type_matlab: "char" + type_python: "str" + - name: devicestring + type_matlab: "char" + type_python: "str" + default: "''" + - name: subjectstring + type_matlab: "char" + type_python: "str" + default: "''" + output_arguments: + - name: epm_obj + type_python: "EpochProbeMap" + decision_log: "Exact match." + + - name: devicename + kind: property + type_python: "str" + decision_log: "Computed property. Extracts name from devicestring." + + - name: deviceclass + kind: property + type_python: "str" + decision_log: "Computed property. Extracts class from devicestring." + + - name: matches + input_arguments: + - name: name + type_matlab: "char (optional)" + type_python: "str | None" + default: "None" + - name: reference + type_matlab: "integer (optional)" + type_python: "int | None" + default: "None" + - name: type + type_matlab: "char (optional)" + type_python: "str | None" + default: "None" + output_arguments: + - name: b + type_python: "bool" + decision_log: "Exact match." + + - name: to_dict + input_arguments: [] + output_arguments: + - name: data + type_python: "dict[str, Any]" + decision_log: "Python-specific serialization method." + + - name: from_dict + kind: classmethod + input_arguments: + - name: data + type_python: "dict[str, Any]" + output_arguments: + - name: epm_obj + type_python: "EpochProbeMap" + decision_log: "Python-specific factory method." + + - name: eq + input_arguments: + - name: other + type_python: "Any" + output_arguments: + - name: b + type_python: "bool" + decision_log: "Mapped to Python __eq__ dunder method." + + # ========================================================================= + # ndi.epoch.epochprobemap_daqsystem + # ========================================================================= + - name: epochprobemap_daqsystem + type: class + matlab_path: "+ndi/+epoch/epochprobemap_daqsystem.m" + python_path: "ndi/epoch/epochprobemap_daqsystem.py" + python_class: "EpochProbeMapDAQSystem" + inherits: "ndi.epoch.epochprobemap" + + properties: + - name: daqsystemstring + type_matlab: "ndi.daq.daqsystemstring" + type_python: "DAQSystemString" + access: "computed property" + decision_log: > + Lazy-parsed from devicestring. MATLAB stores explicitly; + Python computes on access. + + methods: + - name: epochprobemap_daqsystem + kind: constructor + input_arguments: + - name: name + type_matlab: "char" + type_python: "str" + - name: reference + type_matlab: "integer" + type_python: "int" + - name: type + type_matlab: "char" + type_python: "str" + - name: devicestring + type_matlab: "char" + type_python: "str" + default: "''" + - name: subjectstring + type_matlab: "char" + type_python: "str" + default: "''" + output_arguments: + - name: epm_daq_obj + type_python: "EpochProbeMapDAQSystem" + decision_log: "Exact match." + + - name: serialization_struct + input_arguments: [] + output_arguments: + - name: s + type_python: "dict[str, Any]" + decision_log: "Exact match. MATLAB returns a struct; Python returns a dict." + + - name: serialize + input_arguments: [] + output_arguments: + - name: s + type_python: "str" + decision_log: "Exact match. Tab-delimited string." + + - name: savetofile + input_arguments: + - name: filename + type_matlab: "char" + type_python: "str" + output_arguments: [] + decision_log: "Exact match." + + - name: decode + kind: classmethod + input_arguments: + - name: s + type_matlab: "char" + type_python: "str" + output_arguments: + - name: epm_daq_obj + type_python: "EpochProbeMapDAQSystem" + decision_log: "MATLAB is a static method. Python uses classmethod." + + - name: loadfromfile + kind: classmethod + input_arguments: + - name: filename + type_matlab: "char" + type_python: "str" + output_arguments: + - name: epm_list + type_python: "list[EpochProbeMapDAQSystem]" + decision_log: "MATLAB is a static method. Python uses classmethod."