From f9fe77e35c9f8b281049f253b4f4fb2f85fe9059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Wed, 24 Sep 2025 12:33:42 +0200 Subject: [PATCH 01/19] Added support for new task type Tag Detection for Workflows --- cognite/client/data_classes/workflows.py | 73 +++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index fa35416476..4278795145 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -19,6 +19,7 @@ WriteableCogniteResource, WriteableCogniteResourceList, ) +from cognite.client.data_classes.data_modeling import NodeId from cognite.client.data_classes.data_modeling.query import Query, ResultSetExpression, Select from cognite.client.data_classes.simulators.runs import ( SimulationInputOverride, @@ -136,7 +137,7 @@ def as_write(self) -> WorkflowUpsertList: return WorkflowUpsertList([workflow.as_write() for workflow in self.data]) -ValidTaskType = Literal["function", "transformation", "cdf", "dynamic", "subworkflow", "simulation"] +ValidTaskType = Literal["function", "transformation", "cdf", "dynamic", "subworkflow", "simulation", "tagDetection"] class WorkflowTaskParameters(CogniteObject, ABC): @@ -166,6 +167,8 @@ def load_parameters(cls, data: dict) -> WorkflowTaskParameters: return SubworkflowReferenceParameters._load(parameters) elif type_ == "simulation": return SimulationTaskParameters._load(parameters) + elif type_ == "tagDetection": + return TagDetectionTaskParameters._load(parameters) else: raise ValueError(f"Unknown task type: {type_}. Expected {ValidTaskType}") @@ -513,6 +516,57 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: } +class TagDetectionTaskParameters(WorkflowTaskParameters): + """ + The tag detection task parameters are used to specify a tag detection task. + + Args: + file_instance_ids (list[NodeId] | str): List of files to detect tags in. Can be a reference. + entity_filters (str): The DMS filter query to fetch entities to match on + min_tokens (int): Each detected item must match the detected entity on at least this number of tokens. A token is a substring of consecutive letters or digits. + partial_match (bool): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. + write_annotations (bool): Whether annotations should be automatically be written for the files + + """ + + task_type = "tagDetection" + + def __init__( + self, + file_instance_ids: list[NodeId] | str, + entity_filters: str, + min_tokens: int, + partial_match: bool, + write_annotations: bool = False, + ) -> None: + self.file_instance_ids = file_instance_ids + self.entity_filters = entity_filters + self.min_tokens = min_tokens + self.partial_match = partial_match + self.write_annotations = write_annotations + + @classmethod + def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + return cls( + file_instance_ids=resource["fileInstanceIds"], + entity_filters=resource["entityFilters"], + min_tokens=resource["minTokens"], + partial_match=resource["partialMatch"], + write_annotations=resource["writeAnnotations"], + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return { + self.task_type: { + "fileInstanceIds": self.file_instance_ids, + "entityFilters": self.entity_filters, + "minTokens": self.min_tokens, + "partialMatch": self.partial_match, + "writeAnnotations": self.write_annotations, + } + } + + class WorkflowTask(CogniteResource): """ This class represents a workflow task. @@ -779,6 +833,23 @@ def dump(self, camel_case: bool = False) -> dict[str, Any]: return {} +class TagDetectionTaskOutput(WorkflowTaskOutput): + """ + The tag detection task output is used to specify the output of a tag detection task. + """ + + task_type: ClassVar[str] = "tagDetection" + + def __init__(self) -> None: ... + + @classmethod + def load(cls, data: dict[str, Any]) -> TagDetectionTaskOutput: + return cls() + + def dump(self, camel_case: bool = False) -> dict[str, Any]: + return {} + + class WorkflowTaskExecution(CogniteObject): """ This class represents a task execution. From ffa9c535dfd73376ce1fd9fd92418d3066e8b40b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 25 Sep 2025 09:13:06 +0200 Subject: [PATCH 02/19] Fixed dump/load --- cognite/client/data_classes/workflows.py | 33 +++++++++++++++++++----- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 4278795145..412573605f 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -527,6 +527,12 @@ class TagDetectionTaskParameters(WorkflowTaskParameters): partial_match (bool): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. write_annotations (bool): Whether annotations should be automatically be written for the files + Note: + A Reference is an expression that allows dynamically injecting input to a task during execution. + References can be used to reference the input of the Workflow, the output of a previous task in the Workflow, + or the input of a previous task in the Workflow. Note that the injected value must be valid in the context of + the property it is injected into. Example Task reference: ${myTaskExternalId.output.someKey} Example Workflow input reference: ${workflow.input.myKey} + """ task_type = "tagDetection" @@ -547,18 +553,31 @@ def __init__( @classmethod def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + tag_detection = resource[cls.task_type] + file_instance_ids: list[NodeId] | str + if isinstance(tag_detection["fileInstanceIds"], str): + file_instance_ids = tag_detection["fileInstanceIds"] + else: + file_instance_ids = [NodeId.load(file_instance_id) for file_instance_id in tag_detection["fileInstanceIds"]] + return cls( - file_instance_ids=resource["fileInstanceIds"], - entity_filters=resource["entityFilters"], - min_tokens=resource["minTokens"], - partial_match=resource["partialMatch"], - write_annotations=resource["writeAnnotations"], + file_instance_ids=file_instance_ids, + entity_filters=tag_detection["entityFilters"], + min_tokens=tag_detection["minTokens"], + partial_match=tag_detection["partialMatch"], + write_annotations=tag_detection["writeAnnotations"], ) def dump(self, camel_case: bool = True) -> dict[str, Any]: + fileInstanceIds: list[dict[str, str]] | str + if isinstance(self.file_instance_ids, str): + fileInstanceIds = self.file_instance_ids + else: + fileInstanceIds = [file_instance_id.dump(camel_case) for file_instance_id in self.file_instance_ids] + return { self.task_type: { - "fileInstanceIds": self.file_instance_ids, + "fileInstanceIds": fileInstanceIds, "entityFilters": self.entity_filters, "minTokens": self.min_tokens, "partialMatch": self.partial_match, @@ -669,6 +688,8 @@ def load_output(cls, data: dict) -> WorkflowTaskOutput: return SubworkflowTaskOutput.load(data) elif task_type == "simulation": return SimulationTaskOutput.load(data) + elif task_type == "tagDetection": + return TagDetectionTaskOutput.load(data) else: raise ValueError(f"Unknown task type: {task_type}") From 931e450889e5f9f92ab78c408b59614c85288073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 25 Sep 2025 14:20:58 +0200 Subject: [PATCH 03/19] separate class for filter property --- cognite/client/data_classes/workflows.py | 47 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 412573605f..3acf271fe4 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -19,8 +19,9 @@ WriteableCogniteResource, WriteableCogniteResourceList, ) -from cognite.client.data_classes.data_modeling import NodeId +from cognite.client.data_classes.data_modeling import NodeId, ViewId from cognite.client.data_classes.data_modeling.query import Query, ResultSetExpression, Select +from cognite.client.data_classes.filters import Filter from cognite.client.data_classes.simulators.runs import ( SimulationInputOverride, ) @@ -516,13 +517,39 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: } +class TagDetectionTaskEntityFilter(CogniteObject): + view: ViewId + filters: Filter + search_field: str + + def __init__(self, view: ViewId, filters: Filter, search_field: str) -> None: + self.view = view + self.filters = filters + self.search_field = search_field + + @classmethod + def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + return cls( + view=ViewId.load(resource["view"]), + filters=Filter.load(resource["filters"]), + search_field=resource["searchField"], + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return { + "view": self.view.dump(camel_case=camel_case, include_type=False), + "filters": self.filters.dump(), + "searchField": self.search_field, + } + + class TagDetectionTaskParameters(WorkflowTaskParameters): """ The tag detection task parameters are used to specify a tag detection task. Args: file_instance_ids (list[NodeId] | str): List of files to detect tags in. Can be a reference. - entity_filters (str): The DMS filter query to fetch entities to match on + entity_filters (list[TagDetectionTaskEntityFilter]): Entity search specification(s) used to fetch DMS entities to match on. min_tokens (int): Each detected item must match the detected entity on at least this number of tokens. A token is a substring of consecutive letters or digits. partial_match (bool): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. write_annotations (bool): Whether annotations should be automatically be written for the files @@ -540,7 +567,7 @@ class TagDetectionTaskParameters(WorkflowTaskParameters): def __init__( self, file_instance_ids: list[NodeId] | str, - entity_filters: str, + entity_filters: list[TagDetectionTaskEntityFilter], min_tokens: int, partial_match: bool, write_annotations: bool = False, @@ -557,12 +584,18 @@ def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> S file_instance_ids: list[NodeId] | str if isinstance(tag_detection["fileInstanceIds"], str): file_instance_ids = tag_detection["fileInstanceIds"] - else: + elif isinstance(tag_detection["fileInstanceIds"], list): file_instance_ids = [NodeId.load(file_instance_id) for file_instance_id in tag_detection["fileInstanceIds"]] + else: + raise ValueError(f"Invalid file instance ids: {tag_detection['fileInstanceIds']}") + + entity_filters: list[TagDetectionTaskEntityFilter] = [ + TagDetectionTaskEntityFilter.load(item) for item in tag_detection["entityFilters"] + ] return cls( file_instance_ids=file_instance_ids, - entity_filters=tag_detection["entityFilters"], + entity_filters=entity_filters, min_tokens=tag_detection["minTokens"], partial_match=tag_detection["partialMatch"], write_annotations=tag_detection["writeAnnotations"], @@ -575,10 +608,12 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: else: fileInstanceIds = [file_instance_id.dump(camel_case) for file_instance_id in self.file_instance_ids] + entityFilters = [ef.dump(camel_case) for ef in self.entity_filters] + return { self.task_type: { "fileInstanceIds": fileInstanceIds, - "entityFilters": self.entity_filters, + "entityFilters": entityFilters, "minTokens": self.min_tokens, "partialMatch": self.partial_match, "writeAnnotations": self.write_annotations, From a24148df3595324b2b24e299fb4eefe2297097a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 25 Sep 2025 14:53:33 +0200 Subject: [PATCH 04/19] added task output classes --- cognite/client/data_classes/workflows.py | 78 ++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 3acf271fe4..0c1d5c7662 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -889,21 +889,87 @@ def dump(self, camel_case: bool = False) -> dict[str, Any]: return {} -class TagDetectionTaskOutput(WorkflowTaskOutput): +class TagDetectionJobFilePageRange(CogniteObject): """ - The tag detection task output is used to specify the output of a tag detection task. + A list of file page ranges that is being processed or was processed by the job. + + Args: + instanceId (NodeId): The identifier of the instance. + begin (int): The beginning of the page range. + end (int): The end of the page range. """ - task_type: ClassVar[str] = "tagDetection" + def __init__(self, instanceId: NodeId, begin: int, end: int) -> None: + self.instanceId = instanceId + self.begin = begin + self.end = end - def __init__(self) -> None: ... + @classmethod + def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + return cls( + NodeId.load(resource["instanceId"]), + resource["pageRange"]["begin"], + resource["pageRange"]["end"], + ) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return { + "instanceId": self.instanceId.dump(camel_case=camel_case), + "pageRange": { + "begin": self.begin, + "end": self.end, + }, + } + + +class TagDetectionJob(CogniteObject): + """ + A tag detection job. + + Args: + jobId (int): The identifier of the tag detection job + status (str): Enum: "Queued" "Distributing" "Running" "Collecting" "Completed" "Failed" "Timeout" + The last observed status of the job. + filePageRanges (list[TagDetectionJobFilePageRange]): A list of file page ranges that is being processed or was processed by the job. + """ + + def __init__(self, jobId: int, status: str, filePageRanges: list[TagDetectionJobFilePageRange]) -> None: + self.jobId = jobId + self.status = status + self.filePageRanges = filePageRanges + + @classmethod + def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + filePageRanges = [ + TagDetectionJobFilePageRange.load(filePageRange) for filePageRange in resource["filePageRanges"] + ] + + return cls(resource["jobId"], resource["status"], filePageRanges) + + def dump(self, camel_case: bool = True) -> dict[str, Any]: + return { + "jobId": self.jobId, + "status": self.status, + "filePageRanges": [filePageRange.dump(camel_case) for filePageRange in self.filePageRanges], + } + + +class TagDetectionTaskOutput(WorkflowTaskOutput): + """ + The tag detection task output is used to specify the output of tag detection task. + """ + + def __init__(self, jobs: list[TagDetectionJob]) -> None: + self.jobs = jobs @classmethod def load(cls, data: dict[str, Any]) -> TagDetectionTaskOutput: - return cls() + return cls([TagDetectionJob.load(tagDetectionJob) for tagDetectionJob in data["jobs"]]) def dump(self, camel_case: bool = False) -> dict[str, Any]: - return {} + return { + "jobs": [tagDetectionJob.dump(camel_case) for tagDetectionJob in self.jobs], + } class WorkflowTaskExecution(CogniteObject): From e70a9bd659cce309b41466e9d958de3a6dd6c25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 08:05:10 +0200 Subject: [PATCH 05/19] Housekeeping --- cognite/client/data_classes/workflows.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 0c1d5c7662..69035aa882 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -923,14 +923,13 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: class TagDetectionJob(CogniteObject): - """ - A tag detection job. + """A tag detection job. - Args: - jobId (int): The identifier of the tag detection job - status (str): Enum: "Queued" "Distributing" "Running" "Collecting" "Completed" "Failed" "Timeout" - The last observed status of the job. - filePageRanges (list[TagDetectionJobFilePageRange]): A list of file page ranges that is being processed or was processed by the job. + Args: + jobId (int): The identifier of the tag detection job. + status (str): The last observed status of the job. One of ``"Queued"``, ``"Distributing"``, ``"Running"``, + ``"Collecting"``, ``"Completed"``, ``"Failed"``, ``"Timeout"``. + filePageRanges (list[TagDetectionJobFilePageRange]): File page ranges that are or were processed by the job. """ def __init__(self, jobId: int, status: str, filePageRanges: list[TagDetectionJobFilePageRange]) -> None: From 90c4f87da303dbd8bea70c84d3809374f5723d06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:07:41 +0200 Subject: [PATCH 06/19] Update cognite/client/data_classes/workflows.py Co-authored-by: Anders Albert <60234212+doctrino@users.noreply.github.com> --- cognite/client/data_classes/workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 69035aa882..8db42f621a 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -568,8 +568,8 @@ def __init__( self, file_instance_ids: list[NodeId] | str, entity_filters: list[TagDetectionTaskEntityFilter], - min_tokens: int, - partial_match: bool, + min_tokens: int | None = None, + partial_match: bool | None = None, write_annotations: bool = False, ) -> None: self.file_instance_ids = file_instance_ids From 4e72ff20ce8d37e9f959a0c09d93cdeb0f656a77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:08:16 +0200 Subject: [PATCH 07/19] Update cognite/client/data_classes/workflows.py Co-authored-by: Anders Albert <60234212+doctrino@users.noreply.github.com> --- cognite/client/data_classes/workflows.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 8db42f621a..16dd7fcaeb 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -518,10 +518,6 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: class TagDetectionTaskEntityFilter(CogniteObject): - view: ViewId - filters: Filter - search_field: str - def __init__(self, view: ViewId, filters: Filter, search_field: str) -> None: self.view = view self.filters = filters From fc78eb25b658d90a08029bbf2f5d5721c741aeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:15:50 +0200 Subject: [PATCH 08/19] Docstring and snake_case --- cognite/client/data_classes/workflows.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 16dd7fcaeb..8e1a5fd407 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -546,8 +546,8 @@ class TagDetectionTaskParameters(WorkflowTaskParameters): Args: file_instance_ids (list[NodeId] | str): List of files to detect tags in. Can be a reference. entity_filters (list[TagDetectionTaskEntityFilter]): Entity search specification(s) used to fetch DMS entities to match on. - min_tokens (int): Each detected item must match the detected entity on at least this number of tokens. A token is a substring of consecutive letters or digits. - partial_match (bool): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. + min_tokens (int | None): Each detected item must match the detected entity on at least this number of tokens. A token is a substring of consecutive letters or digits. + partial_match (bool | None): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. write_annotations (bool): Whether annotations should be automatically be written for the files Note: @@ -598,18 +598,18 @@ def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> S ) def dump(self, camel_case: bool = True) -> dict[str, Any]: - fileInstanceIds: list[dict[str, str]] | str + file_instance_ids: list[dict[str, str]] | str if isinstance(self.file_instance_ids, str): - fileInstanceIds = self.file_instance_ids + file_instance_ids = self.file_instance_ids else: - fileInstanceIds = [file_instance_id.dump(camel_case) for file_instance_id in self.file_instance_ids] + file_instance_ids = [file_instance_id.dump(camel_case) for file_instance_id in self.file_instance_ids] - entityFilters = [ef.dump(camel_case) for ef in self.entity_filters] + entity_filters = [ef.dump(camel_case) for ef in self.entity_filters] return { self.task_type: { - "fileInstanceIds": fileInstanceIds, - "entityFilters": entityFilters, + "fileInstanceIds": file_instance_ids, + "entityFilters": entity_filters, "minTokens": self.min_tokens, "partialMatch": self.partial_match, "writeAnnotations": self.write_annotations, From 270e56f992ba54fe6124af219d33afae7c5e9030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:17:14 +0200 Subject: [PATCH 09/19] Update cognite/client/data_classes/workflows.py Co-authored-by: Anders Albert <60234212+doctrino@users.noreply.github.com> --- cognite/client/data_classes/workflows.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 8e1a5fd407..0d30cb8cda 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -928,7 +928,7 @@ class TagDetectionJob(CogniteObject): filePageRanges (list[TagDetectionJobFilePageRange]): File page ranges that are or were processed by the job. """ - def __init__(self, jobId: int, status: str, filePageRanges: list[TagDetectionJobFilePageRange]) -> None: + def __init__(self, jobId: int, status: TagDetectionStatus, filePageRanges: list[TagDetectionJobFilePageRange]) -> None: self.jobId = jobId self.status = status self.filePageRanges = filePageRanges From c144e21a6887a6ab35e4faefcb0f218f158e84a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:20:08 +0200 Subject: [PATCH 10/19] Update cognite/client/data_classes/workflows.py Co-authored-by: Anders Albert <60234212+doctrino@users.noreply.github.com> --- cognite/client/data_classes/workflows.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 0d30cb8cda..17a330ed81 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -885,6 +885,16 @@ def dump(self, camel_case: bool = False) -> dict[str, Any]: return {} +@dataclass +class PageRange(CogniteObject): + begin: int + end: int + + @classmethod + def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: + return cls(resource["begin"], resource["end"]) + + class TagDetectionJobFilePageRange(CogniteObject): """ A list of file page ranges that is being processed or was processed by the job. @@ -895,29 +905,25 @@ class TagDetectionJobFilePageRange(CogniteObject): end (int): The end of the page range. """ - def __init__(self, instanceId: NodeId, begin: int, end: int) -> None: + def __init__(self, instanceId: NodeId, page_range: PageRange) -> None: self.instanceId = instanceId - self.begin = begin - self.end = end + self.page_range = page_range @classmethod def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: return cls( NodeId.load(resource["instanceId"]), - resource["pageRange"]["begin"], - resource["pageRange"]["end"], + PageRange._load(resource["pageRange"]), ) def dump(self, camel_case: bool = True) -> dict[str, Any]: return { "instanceId": self.instanceId.dump(camel_case=camel_case), - "pageRange": { - "begin": self.begin, - "end": self.end, - }, + "pageRange": self.page_range.dump(camel_case=camel_case), } + class TagDetectionJob(CogniteObject): """A tag detection job. From b28005de904999c6acd34fdf234ff38718c61187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:20:48 +0200 Subject: [PATCH 11/19] following suggestions --- cognite/client/data_classes/workflows.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 0d30cb8cda..de932121c3 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -45,6 +45,16 @@ WorkflowStatus: TypeAlias = Literal["completed", "failed", "running", "terminated", "timed_out"] +TagDetectionStatus: TypeAlias = Literal[ + "Queued", + "Distributing", + "Running", + "Collecting", + "Completed", + "Failed", + "Timeout", +] + class WorkflowCore(WriteableCogniteResource["WorkflowUpsert"], ABC): def __init__(self, external_id: str, description: str | None = None, data_set_id: int | None = None) -> None: @@ -923,12 +933,13 @@ class TagDetectionJob(CogniteObject): Args: jobId (int): The identifier of the tag detection job. - status (str): The last observed status of the job. One of ``"Queued"``, ``"Distributing"``, ``"Running"``, - ``"Collecting"``, ``"Completed"``, ``"Failed"``, ``"Timeout"``. + status (TagDetectionStatus): The last observed status of the job. filePageRanges (list[TagDetectionJobFilePageRange]): File page ranges that are or were processed by the job. """ - def __init__(self, jobId: int, status: TagDetectionStatus, filePageRanges: list[TagDetectionJobFilePageRange]) -> None: + def __init__( + self, jobId: int, status: TagDetectionStatus, filePageRanges: list[TagDetectionJobFilePageRange] + ) -> None: self.jobId = jobId self.status = status self.filePageRanges = filePageRanges From e1d5db2412a8c9ec6f245f55cfd4a0192b61eed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Fri, 26 Sep 2025 09:44:02 +0200 Subject: [PATCH 12/19] Docstring --- cognite/client/data_classes/workflows.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 8654b4a82e..648529d38a 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -895,11 +895,11 @@ def dump(self, camel_case: bool = False) -> dict[str, Any]: return {} -@dataclass class PageRange(CogniteObject): - begin: int - end: int - + def __init__(self, begin: int, end: int) -> None: + self.begin = begin + self.end = end + @classmethod def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: return cls(resource["begin"], resource["end"]) @@ -911,8 +911,7 @@ class TagDetectionJobFilePageRange(CogniteObject): Args: instanceId (NodeId): The identifier of the instance. - begin (int): The beginning of the page range. - end (int): The end of the page range. + page_range (PageRange): No description. """ def __init__(self, instanceId: NodeId, page_range: PageRange) -> None: @@ -933,7 +932,6 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: } - class TagDetectionJob(CogniteObject): """A tag detection job. From ca1205eeb9387d734cd0532cccff06c2f46f8c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 9 Oct 2025 09:51:29 +0200 Subject: [PATCH 13/19] Added new error message property to the TagDetectionJob class --- cognite/client/data_classes/workflows.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 648529d38a..44ce97ba9c 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -939,14 +939,20 @@ class TagDetectionJob(CogniteObject): jobId (int): The identifier of the tag detection job. status (TagDetectionStatus): The last observed status of the job. filePageRanges (list[TagDetectionJobFilePageRange]): File page ranges that are or were processed by the job. + errorMessage (str | None): Describes the job failure reason in case of job failure. """ def __init__( - self, jobId: int, status: TagDetectionStatus, filePageRanges: list[TagDetectionJobFilePageRange] + self, + jobId: int, + status: TagDetectionStatus, + filePageRanges: list[TagDetectionJobFilePageRange], + errorMessage: str | None, ) -> None: self.jobId = jobId self.status = status self.filePageRanges = filePageRanges + self.errorMessage = errorMessage @classmethod def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: @@ -954,7 +960,7 @@ def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> S TagDetectionJobFilePageRange.load(filePageRange) for filePageRange in resource["filePageRanges"] ] - return cls(resource["jobId"], resource["status"], filePageRanges) + return cls(resource["jobId"], resource["status"], filePageRanges, resource.get("errorMessage")) def dump(self, camel_case: bool = True) -> dict[str, Any]: return { From 48724ffcb39d993109b728938e593294970aa73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 9 Oct 2025 10:01:06 +0200 Subject: [PATCH 14/19] Fixed docstring precision --- cognite/client/data_classes/workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 44ce97ba9c..160e6881ff 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -554,8 +554,8 @@ class TagDetectionTaskParameters(WorkflowTaskParameters): The tag detection task parameters are used to specify a tag detection task. Args: - file_instance_ids (list[NodeId] | str): List of files to detect tags in. Can be a reference. - entity_filters (list[TagDetectionTaskEntityFilter]): Entity search specification(s) used to fetch DMS entities to match on. + file_instance_ids (list[NodeId] | str): List of files to detect tags in. A minimum of 1 file is expected. Can be a reference. + entity_filters (list[TagDetectionTaskEntityFilter]): Entity search specification(s) used to fetch DMS entities to match on. Must contain between 1 and 10 filters. min_tokens (int | None): Each detected item must match the detected entity on at least this number of tokens. A token is a substring of consecutive letters or digits. partial_match (bool | None): Allow partial (fuzzy) matching of entities in the engineering diagrams. Creates a match only when it is possible to do so unambiguously. write_annotations (bool): Whether annotations should be automatically be written for the files From 322aac22d45046d13da783dd2d4795863fa2528b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 9 Oct 2025 09:38:19 +0200 Subject: [PATCH 15/19] Added additional TagDetectionStatus statuses --- cognite/client/data_classes/workflows.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 160e6881ff..669f291eea 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -48,7 +48,12 @@ TagDetectionStatus: TypeAlias = Literal[ "Queued", "Distributing", + "Distributed", + "Loading Entities", + "Loaded Entities", "Running", + "Detected", + "Annotated", "Collecting", "Completed", "Failed", From 1f1dadb3ebc29aff8f0c5d278f5a69bc80d0fb05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 9 Oct 2025 10:24:27 +0200 Subject: [PATCH 16/19] Added errormessage to dump method --- cognite/client/data_classes/workflows.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index 669f291eea..d7723834b8 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -972,6 +972,7 @@ def dump(self, camel_case: bool = True) -> dict[str, Any]: "jobId": self.jobId, "status": self.status, "filePageRanges": [filePageRange.dump(camel_case) for filePageRange in self.filePageRanges], + "errorMessage": self.errorMessage, } From d7cf8ac99b09e5731037282bc9470065b20ec059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Thu, 9 Oct 2025 11:49:50 +0200 Subject: [PATCH 17/19] Added rountrip load/dump for ABC dataclasses in workflows --- cognite/client/data_classes/workflows.py | 3 +- .../test_data_classes/test_workflows.py | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index d7723834b8..d6a990655d 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -986,7 +986,8 @@ def __init__(self, jobs: list[TagDetectionJob]) -> None: @classmethod def load(cls, data: dict[str, Any]) -> TagDetectionTaskOutput: - return cls([TagDetectionJob.load(tagDetectionJob) for tagDetectionJob in data["jobs"]]) + output = data["output"] + return cls([TagDetectionJob.load(tagDetectionJob) for tagDetectionJob in output["jobs"]]) def dump(self, camel_case: bool = False) -> dict[str, Any]: return { diff --git a/tests/tests_unit/test_data_classes/test_workflows.py b/tests/tests_unit/test_data_classes/test_workflows.py index 840a45ad58..c45c9add3f 100644 --- a/tests/tests_unit/test_data_classes/test_workflows.py +++ b/tests/tests_unit/test_data_classes/test_workflows.py @@ -12,6 +12,7 @@ FunctionTaskParameters, SimulationInputOverride, SimulationTaskParameters, + TagDetectionTaskOutput, TransformationTaskOutput, TransformationTaskParameters, WorkflowDefinition, @@ -49,6 +50,48 @@ class TestWorkflowTaskOutput: def test_serialization(self, output: WorkflowTaskOutput, expected: dict): assert output.dump(camel_case=True) == expected + @pytest.mark.parametrize( + ["data", "cls"], + [ + ( + { + "taskType": "function", + "output": {"callId": 123, "functionId": 3456, "response": {"test": 1}}, + }, + FunctionTaskOutput, + ), + ({"taskType": "dynamic", "output": {}}, DynamicTaskOutput), + ({"taskType": "cdf", "output": {"response": {"test": 1}, "statusCode": 200}}, CDFTaskOutput), + ({"taskType": "transformation", "output": {"jobId": 789}}, TransformationTaskOutput), + ( + { + "taskType": "tagDetection", + "output": { + "jobs": [ + { + "jobId": 321, + "status": "Completed", + "filePageRanges": [ + { + "instanceId": {"space": "sp", "externalId": "id", "type": "node"}, + "pageRange": {"begin": 1, "end": 5}, + } + ], + "errorMessage": None, + } + ] + }, + }, + TagDetectionTaskOutput, + ), + ], + ids=["function", "dynamic", "cdf", "transformation", "tagDetection"], + ) + def test_serialization_roundtrip(self, data: dict, cls: type[WorkflowTaskOutput]): + task_output = WorkflowTaskOutput.load_output(data) + assert isinstance(task_output, cls) + assert task_output.dump(camel_case=True) == data["output"] + class TestWorkflowId: @pytest.mark.parametrize( From 7c70f7fbf36b816b9c6d75b9b114d628ab7442d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Mon, 20 Oct 2025 15:23:20 +0200 Subject: [PATCH 18/19] added errorMessage to filePageRange dataclass --- cognite/client/data_classes/workflows.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cognite/client/data_classes/workflows.py b/cognite/client/data_classes/workflows.py index d6a990655d..f485f4fcbd 100644 --- a/cognite/client/data_classes/workflows.py +++ b/cognite/client/data_classes/workflows.py @@ -917,23 +917,27 @@ class TagDetectionJobFilePageRange(CogniteObject): Args: instanceId (NodeId): The identifier of the instance. page_range (PageRange): No description. + error_message (str | None): Describes why the page range failed to be processed in case of page range processing failure. """ - def __init__(self, instanceId: NodeId, page_range: PageRange) -> None: + def __init__(self, instanceId: NodeId, page_range: PageRange, error_message: str | None) -> None: self.instanceId = instanceId self.page_range = page_range + self.error_message = error_message @classmethod def _load(cls, resource: dict, cognite_client: CogniteClient | None = None) -> Self: return cls( NodeId.load(resource["instanceId"]), PageRange._load(resource["pageRange"]), + resource.get("errorMessage"), ) def dump(self, camel_case: bool = True) -> dict[str, Any]: return { "instanceId": self.instanceId.dump(camel_case=camel_case), "pageRange": self.page_range.dump(camel_case=camel_case), + "errorMessage": self.error_message, } From 16d973891058240caff9f76ab571926114a89346 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A5l=20R=C3=B8nning?= Date: Mon, 20 Oct 2025 15:28:12 +0200 Subject: [PATCH 19/19] Updated test --- tests/tests_unit/test_data_classes/test_workflows.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tests_unit/test_data_classes/test_workflows.py b/tests/tests_unit/test_data_classes/test_workflows.py index c45c9add3f..4a39eb3356 100644 --- a/tests/tests_unit/test_data_classes/test_workflows.py +++ b/tests/tests_unit/test_data_classes/test_workflows.py @@ -75,6 +75,7 @@ def test_serialization(self, output: WorkflowTaskOutput, expected: dict): { "instanceId": {"space": "sp", "externalId": "id", "type": "node"}, "pageRange": {"begin": 1, "end": 5}, + "errorMessage": None, } ], "errorMessage": None,