cognite-neat 0.77.6__py3-none-any.whl → 0.77.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cognite-neat might be problematic. Click here for more details.

cognite/neat/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.77.6"
1
+ __version__ = "0.77.8"
@@ -16,6 +16,7 @@ __all__ = [
16
16
  "MultipleDataModelsWarning",
17
17
  "UnknownPropertyTypeWarning",
18
18
  "FailedToInferValueTypeWarning",
19
+ "MoreThanOneNonAlphanumericCharacterWarning",
19
20
  "UnknownContainerConstraintWarning",
20
21
  "NoDataModelError",
21
22
  "ModelImportError",
@@ -275,6 +276,22 @@ class FailedImportWarning(ModelImportWarning):
275
276
  return {"identifier": list(self.identifier)}
276
277
 
277
278
 
279
+ @dataclass(frozen=True)
280
+ class MoreThanOneNonAlphanumericCharacterWarning(ModelImportWarning):
281
+ description = """This warning is raised when doing regex validation of strings which either represent class ids,
282
+ property ids, prefix, data model name, that contain more than one non-alphanumeric character,
283
+ such as for example '_' or '-'."""
284
+
285
+ field_name: str
286
+ value: str
287
+
288
+ def message(self) -> str:
289
+ return f"Field {self.field_name} with value {self.value} contains more than one non-alphanumeric character!"
290
+
291
+ def dump(self) -> dict[str, str]:
292
+ return {"field_name": self.field_name, "value": self.value}
293
+
294
+
278
295
  @dataclass(frozen=True)
279
296
  class ModelImportError(NeatValidationError, ABC):
280
297
  description = "An error was raised during importing."
@@ -1,4 +1,5 @@
1
1
  import re
2
+ import warnings
2
3
  from collections.abc import Callable
3
4
  from typing import Annotated, Any, cast
4
5
 
@@ -17,6 +18,7 @@ from pydantic.functional_serializers import PlainSerializer
17
18
  from pydantic_core import PydanticCustomError
18
19
 
19
20
  from cognite.neat.rules import exceptions
21
+ from cognite.neat.rules.issues.importing import MoreThanOneNonAlphanumericCharacterWarning
20
22
 
21
23
  from ._base import (
22
24
  MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX,
@@ -50,7 +52,6 @@ StrOrListType = Annotated[
50
52
  ),
51
53
  ]
52
54
 
53
-
54
55
  StrListType = Annotated[
55
56
  list[str],
56
57
  BeforeValidator(lambda value: [entry.strip() for entry in value.split(",")] if isinstance(value, str) else value),
@@ -96,19 +97,12 @@ VersionType = Annotated[
96
97
  ]
97
98
 
98
99
 
99
- PropertyType = Annotated[
100
- str,
101
- AfterValidator(
102
- lambda value: (
103
- _raise(exceptions.MoreThanOneNonAlphanumericCharacter("property", value).to_pydantic_custom_error())
104
- if re.search(MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX, value)
105
- else (
106
- value
107
- if re.match(PROPERTY_ID_COMPLIANCE_REGEX, value)
108
- else _raise(
109
- exceptions.PropertyIDRegexViolation(value, PROPERTY_ID_COMPLIANCE_REGEX).to_pydantic_custom_error()
110
- )
111
- )
112
- )
113
- ),
114
- ]
100
+ def _property_validation(value: str) -> str:
101
+ if not re.match(PROPERTY_ID_COMPLIANCE_REGEX, value):
102
+ _raise(exceptions.PropertyIDRegexViolation(value, PROPERTY_ID_COMPLIANCE_REGEX).to_pydantic_custom_error())
103
+ if re.search(MORE_THAN_ONE_NONE_ALPHANUMERIC_REGEX, value):
104
+ warnings.warn(MoreThanOneNonAlphanumericCharacterWarning("property", value), stacklevel=2)
105
+ return value
106
+
107
+
108
+ PropertyType = Annotated[str, AfterValidator(_property_validation)]
@@ -57,7 +57,10 @@ class DMSPostValidation:
57
57
  container_id = container.as_id()
58
58
  row_numbers = {prop_no for prop_no, _ in properties}
59
59
  value_types = {prop.value_type for _, prop in properties if prop.value_type}
60
- if len(value_types) > 1:
60
+ # The container type 'direct' is an exception. On a container the type direct can point to any
61
+ # node. The value type is typically set on the view.
62
+ is_all_direct = all(prop.connection == "direct" for _, prop in properties)
63
+ if len(value_types) > 1 and not is_all_direct:
61
64
  errors.append(
62
65
  issues.spreadsheet.MultiValueTypeError(
63
66
  container_id,
@@ -1,5 +1,6 @@
1
1
  import re
2
- from collections import defaultdict
2
+ from collections import Counter, defaultdict
3
+ from collections.abc import Collection
3
4
  from typing import TYPE_CHECKING, Literal
4
5
 
5
6
  from cognite.neat.rules.models._base import (
@@ -40,7 +41,6 @@ class _InformationRulesConverter:
40
41
  self.last_classes = {class_.class_: class_ for class_ in self.rules.last.classes}
41
42
  else:
42
43
  self.last_classes = {}
43
- self._created_classes_from: dict[ClassEntity, ClassEntity] = {}
44
44
 
45
45
  def as_domain_rules(self) -> DomainRules:
46
46
  raise NotImplementedError("DomainRules not implemented yet")
@@ -58,11 +58,13 @@ class _InformationRulesConverter:
58
58
  default_space = self._to_space(info_metadata.prefix)
59
59
  metadata = self._convert_metadata_to_dms(info_metadata)
60
60
 
61
- properties_by_class: dict[str, list[DMSProperty]] = defaultdict(list)
61
+ properties_by_class: dict[ClassEntity, list[DMSProperty]] = defaultdict(list)
62
+ referenced_containers: dict[ContainerEntity, Counter[ClassEntity]] = defaultdict(Counter)
62
63
  for prop in self.rules.properties:
63
- properties_by_class[prop.class_.versioned_id].append(
64
- self._as_dms_property(prop, default_space, default_version)
65
- )
64
+ dms_property = self._as_dms_property(prop, default_space, default_version)
65
+ properties_by_class[prop.class_].append(dms_property)
66
+ if dms_property.container:
67
+ referenced_containers[dms_property.container][prop.class_] += 1
66
68
 
67
69
  views: list[DMSView] = [
68
70
  DMSView(
@@ -76,41 +78,37 @@ class _InformationRulesConverter:
76
78
  for cls_ in self.rules.classes
77
79
  ]
78
80
 
79
- classes_without_properties: set[str] = set()
80
- for class_ in self.rules.classes:
81
- properties: list[DMSProperty] = properties_by_class.get(class_.class_.versioned_id, [])
82
- if not properties or all(
83
- isinstance(prop.value_type, ViewPropertyEntity) and prop.connection != "direct" for prop in properties
84
- ):
85
- classes_without_properties.add(class_.class_.versioned_id)
81
+ last_dms_rules = self.rules.last.as_dms_architect_rules() if self.rules.last else None
82
+ ref_dms_rules = self.rules.reference.as_dms_architect_rules() if self.rules.reference else None
86
83
 
87
84
  containers: list[DMSContainer] = []
88
- classes = list(self.rules.classes)
89
- for new_class_entity, created_from in self._created_classes_from.items():
90
- # We create new classes in case metadata is set to addition or reshape
91
- # and the class was present in the last schema to avoid creating
92
- # a changed container and instead create a new one.
93
- created_class = self.last_classes[created_from].copy(deep=True)
94
- created_class.class_ = new_class_entity
95
- classes.append(created_class)
96
-
97
- for class_ in classes:
98
- if class_.class_.versioned_id in classes_without_properties:
85
+ class_by_entity = {cls_.class_: cls_ for cls_ in self.rules.classes}
86
+ if self.rules.last:
87
+ for cls_ in self.rules.last.classes:
88
+ if cls_.class_ not in class_by_entity:
89
+ class_by_entity[cls_.class_] = cls_
90
+
91
+ existing_containers: set[ContainerEntity] = set()
92
+ for rule_set in [last_dms_rules, ref_dms_rules]:
93
+ if rule_set:
94
+ existing_containers.update({c.container for c in rule_set.containers or []})
95
+
96
+ for container_entity, class_entities in referenced_containers.items():
97
+ if container_entity in existing_containers:
99
98
  continue
100
- containers.append(
101
- DMSContainer(
102
- class_=class_.class_,
103
- name=class_.name,
104
- container=class_.class_.as_container_entity(default_space),
105
- description=class_.description,
106
- constraint=[
107
- parent.as_container_entity(default_space)
108
- for parent in class_.parent or []
109
- if parent.versioned_id not in classes_without_properties
110
- ]
111
- or None,
112
- )
99
+ constrains = self._create_container_constraint(
100
+ class_entities, default_space, class_by_entity, referenced_containers
101
+ )
102
+ most_used_class_entity = class_entities.most_common(1)[0][0]
103
+ class_ = class_by_entity[most_used_class_entity]
104
+ container = DMSContainer(
105
+ class_=class_.class_,
106
+ container=container_entity,
107
+ name=class_.name,
108
+ description=class_.description,
109
+ constraint=constrains or None,
113
110
  )
111
+ containers.append(container)
114
112
 
115
113
  return DMSRules(
116
114
  metadata=metadata,
@@ -119,10 +117,26 @@ class _InformationRulesConverter:
119
117
  ),
120
118
  views=SheetList[DMSView](data=views),
121
119
  containers=SheetList[DMSContainer](data=containers),
122
- last=self.rules.last.as_dms_architect_rules() if self.rules.last else None,
123
- reference=self.rules.reference.as_dms_architect_rules() if self.rules.reference else None,
120
+ last=last_dms_rules,
121
+ reference=ref_dms_rules,
124
122
  )
125
123
 
124
+ @staticmethod
125
+ def _create_container_constraint(
126
+ class_entities: Counter[ClassEntity],
127
+ default_space: str,
128
+ class_by_entity: dict[ClassEntity, InformationClass],
129
+ referenced_containers: Collection[ContainerEntity],
130
+ ) -> list[ContainerEntity]:
131
+ constrains: list[ContainerEntity] = []
132
+ for entity in class_entities:
133
+ class_ = class_by_entity[entity]
134
+ for parent in class_.parent or []:
135
+ parent_entity = parent.as_container_entity(default_space)
136
+ if parent_entity in referenced_containers:
137
+ constrains.append(parent_entity)
138
+ return constrains
139
+
126
140
  @classmethod
127
141
  def _convert_metadata_to_dms(cls, metadata: InformationMetadata) -> "DMSMetadata":
128
142
  from cognite.neat.rules.models.dms._rules import (
@@ -211,25 +225,35 @@ class _InformationRulesConverter:
211
225
  elif (self.is_addition or self.is_reshape) and prop.class_ in self.last_classes:
212
226
  # We need to create a new container for the property, as we cannot change
213
227
  # the existing container in the last schema
214
- class_entity = prop.class_.copy()
215
- class_entity.suffix = self._bump_suffix(class_entity.suffix)
216
- if class_entity not in self._created_classes_from:
217
- self._created_classes_from[class_entity] = prop.class_
218
- return class_entity.as_container_entity(default_space), prop.property_
228
+ container_entity = prop.class_.as_container_entity(default_space)
229
+ container_entity.suffix = self._bump_suffix(container_entity.suffix)
230
+ return container_entity, prop.property_
219
231
  else:
220
232
  return prop.class_.as_container_entity(default_space), prop.property_
221
233
 
222
- @classmethod
223
- def _get_view_implements(cls, cls_: InformationClass, metadata: InformationMetadata) -> list[ViewEntity]:
234
+ def _get_view_implements(self, cls_: InformationClass, metadata: InformationMetadata) -> list[ViewEntity]:
224
235
  if isinstance(cls_.reference, ReferenceEntity) and cls_.reference.prefix != metadata.prefix:
225
236
  # We use the reference for implements if it is in a different namespace
226
- implements = [
227
- cls_.reference.as_view_entity(metadata.prefix, metadata.version),
228
- ]
237
+ if self.rules.reference and cls_.reference.prefix == self.rules.reference.metadata.prefix:
238
+ implements = [
239
+ cls_.reference.as_view_entity(
240
+ self.rules.reference.metadata.prefix, self.rules.reference.metadata.version
241
+ )
242
+ ]
243
+ else:
244
+ implements = [
245
+ cls_.reference.as_view_entity(metadata.prefix, metadata.version),
246
+ ]
229
247
  else:
230
248
  implements = []
231
-
232
- implements.extend([parent.as_view_entity(metadata.prefix, metadata.version) for parent in cls_.parent or []])
249
+ for parent in cls_.parent or []:
250
+ if self.rules.reference and parent.prefix == self.rules.reference.metadata.prefix:
251
+ view_entity = parent.as_view_entity(
252
+ self.rules.reference.metadata.prefix, self.rules.reference.metadata.version
253
+ )
254
+ else:
255
+ view_entity = parent.as_view_entity(metadata.prefix, metadata.version)
256
+ implements.append(view_entity)
233
257
  return implements
234
258
 
235
259
  @staticmethod
@@ -281,7 +281,6 @@ class RulesToExcel(Step):
281
281
  value="",
282
282
  label="If you chose Dump Format 'reference', the provided ID will be use in the new medata sheet. "
283
283
  "Expected format 'sp_space:my_external_id'.",
284
- options=list(exporters.ExcelExporter.dump_options),
285
284
  ),
286
285
  Configurable(
287
286
  name="File path",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cognite-neat
3
- Version: 0.77.6
3
+ Version: 0.77.8
4
4
  Summary: Knowledge graph transformation
5
5
  Home-page: https://cognite-neat.readthedocs-hosted.com/
6
6
  License: Apache-2.0
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=v-rRiDOgZ3sQSMQKq0vgUQZvpeOkoHFXissAx6Ktg84,61
2
- cognite/neat/_version.py,sha256=gRPwu1nLGE_QfxUJtmhbOlmhiHQAZRF63Q3wmAlYElY,23
2
+ cognite/neat/_version.py,sha256=mXpwNIS8gqWNHN489kDHmqkjGuUNEJ_NwDA07_xdNQk,23
3
3
  cognite/neat/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/app/api/asgi/metrics.py,sha256=nxFy7L5cChTI0a-zkCiJ59Aq8yLuIJp5c9Dg0wRXtV0,152
5
5
  cognite/neat/app/api/configuration.py,sha256=2U5M6M252swvQPQyooA1EBzFUZNtcTmuSaywfJDgckM,4232
@@ -190,7 +190,7 @@ cognite/neat/rules/issues/base.py,sha256=i2aTC-wq3UVW2bj_7wKeuhYxCpMD06Bd9-m00bW
190
190
  cognite/neat/rules/issues/dms.py,sha256=CKztcpNu9E_ygbAmiODOhaYKPX6o9eaXeiod7Ak-kNY,23617
191
191
  cognite/neat/rules/issues/fileread.py,sha256=ao199mtvhPSW0IA8ZQZ0RzuLIIipYtL0jp6fLqxb4_c,5748
192
192
  cognite/neat/rules/issues/formatters.py,sha256=_ag2bJ9hncOj8pAGJvTTEPs9kTtxbD7vkqvS9Zcnizc,3385
193
- cognite/neat/rules/issues/importing.py,sha256=l0VKmOWN-qyvOMukNAVJZrN2WGD2YcGXeCKbxV-G_vA,12726
193
+ cognite/neat/rules/issues/importing.py,sha256=uSk4TXo_CO3bglBZkaiWekXLXXd31UWIZE95clVSLz4,13417
194
194
  cognite/neat/rules/issues/spreadsheet.py,sha256=1NZQ3a_zCtErAoMKzEt5UMJsukaysoOVmW7WUF6JK1s,15825
195
195
  cognite/neat/rules/issues/spreadsheet_file.py,sha256=YCp0Pk_TsiqYuOPdWpjUpre-zvi2c5_MvrC_dxw10YY,4964
196
196
  cognite/neat/rules/models/__init__.py,sha256=aqhQUidHYgOk5_iqdi6s72s2g8qyMRFXShYzh-ctNpw,782
@@ -198,7 +198,7 @@ cognite/neat/rules/models/_base.py,sha256=oQ8f0bvdythJ2m54K1jl2OXEuEZ4N8JqHDXyhC
198
198
  cognite/neat/rules/models/_rdfpath.py,sha256=RoHnfWufjnDtwJh7UUzWKoJz8luvX7Gb5SDQORfkQTE,11030
199
199
  cognite/neat/rules/models/_types/__init__.py,sha256=l1tGxzE7ezNHIL72AoEvNHN2IFuitxOLxiHJG__s6t4,305
200
200
  cognite/neat/rules/models/_types/_base.py,sha256=2GhLUE1ukV8X8SGL_JDxpbWGZyAvOnSqAE6JmDh5wbI,929
201
- cognite/neat/rules/models/_types/_field.py,sha256=74WfCSVbTubpK4n4VsysQqCch6VI8IcPqnHQpvNbFZ8,3197
201
+ cognite/neat/rules/models/_types/_field.py,sha256=h2RrhjxdaRbzHG5EyduyHLkCJJmQoyZb9pYCkgMcnVk,3203
202
202
  cognite/neat/rules/models/data_types.py,sha256=lanwkhwG8iHKfjYfia4v2SBTJrMeXOsqaVkVEP2QMXs,6078
203
203
  cognite/neat/rules/models/dms/__init__.py,sha256=Wzyqzz2ZIjpUbDg04CMuuIAw-f2A02DayNeqO9R-2Hw,491
204
204
  cognite/neat/rules/models/dms/_converter.py,sha256=QaJT0z0Yo4gkG9tHPZkU8idF8PK7c-tDahbyIT-WJQU,5959
@@ -207,11 +207,11 @@ cognite/neat/rules/models/dms/_rules.py,sha256=iqoPilY3tov3GvJ9N3K3go6xy9kiiMt9v
207
207
  cognite/neat/rules/models/dms/_rules_input.py,sha256=apDDTQll9UAyYL5gS2vDxHsujWrGBilTp7lK2kzJWO8,13467
208
208
  cognite/neat/rules/models/dms/_schema.py,sha256=A4z8CINmLQgWzHoScxejRPMRo40ngKlyp1gOdPto8yU,43808
209
209
  cognite/neat/rules/models/dms/_serializer.py,sha256=iqp2zyyf8jEcU-R3PERuN8nu248xIqyxiWj4owAn92g,6406
210
- cognite/neat/rules/models/dms/_validation.py,sha256=TUmBxfSB42lsgIKVIDPPbaTaJ8TKi-gTbayqgPAmnfs,13656
210
+ cognite/neat/rules/models/dms/_validation.py,sha256=nPSyfM1vGZ7d9Uv_2vF2HvMetygtehXW7eNtPD6eW8E,13937
211
211
  cognite/neat/rules/models/domain.py,sha256=tkKcHvDXnZ5IkOr1wHiuNBtE1h8OCFmf6GZSqzHzxjI,2814
212
212
  cognite/neat/rules/models/entities.py,sha256=iBG84Jr1qQ7PvkMJUJzJ1oWApeONb1IACixdJSztUhk,16395
213
213
  cognite/neat/rules/models/information/__init__.py,sha256=HR6g8xgyU53U7Ck8pPdbT70817Q4NC1r1pCRq5SA8iw,291
214
- cognite/neat/rules/models/information/_converter.py,sha256=qJl95-gPz6MwKLZpHyY_CIqf-ebZalHWZyYnsqr_FWc,9960
214
+ cognite/neat/rules/models/information/_converter.py,sha256=JN63_G5bygdL5WCz-q0_ygiU0NHkzUxm5mZ3WD8yUes,11029
215
215
  cognite/neat/rules/models/information/_rules.py,sha256=tdCjvAtqnFzEo2zcx-BZHmvjSs28gVun2wz8UaT-AOA,13268
216
216
  cognite/neat/rules/models/information/_rules_input.py,sha256=IZp_Wnrac0nVaHKth1YttWQOs-kULGKLMtngNQFY40A,10147
217
217
  cognite/neat/rules/models/information/_serializer.py,sha256=yti9I_xJruxrib66YIBInhze___Io-oPTQH6uWDumPE,3503
@@ -257,7 +257,7 @@ cognite/neat/workflows/steps/lib/current/__init__.py,sha256=c22IznGdCSNCpXCi_yon
257
257
  cognite/neat/workflows/steps/lib/current/graph_extractor.py,sha256=vW9UpJScx5dFVCSairpOdWRdBdLpkCt2kNh6litbF0o,5161
258
258
  cognite/neat/workflows/steps/lib/current/graph_loader.py,sha256=HfGg1HRZhbV58TFu89FTjKeUxGsbCYLeFJIQFDN_pQM,2341
259
259
  cognite/neat/workflows/steps/lib/current/graph_store.py,sha256=r7VTxdaz8jJQU7FJbnRDMxvEYbSAZFNMABhPyfNwiFk,6295
260
- cognite/neat/workflows/steps/lib/current/rules_exporter.py,sha256=BWscuNvTu_u6QQSxkVz4X4A1wAHW2Kfku8lhgrIK51M,23886
260
+ cognite/neat/workflows/steps/lib/current/rules_exporter.py,sha256=iFwzDWgUDBSPajaNAcXvu9pVw-jX66upvwyMXyTq7SE,23822
261
261
  cognite/neat/workflows/steps/lib/current/rules_importer.py,sha256=oGIBh9iFYP3w_K0kLpM-OKTw70R1Mw_opD62MGkCJlk,11451
262
262
  cognite/neat/workflows/steps/lib/current/rules_validator.py,sha256=LwF9lXlnuPOxDSsOMMTHBi2BHc5rk08IF5zahS9yQuw,4844
263
263
  cognite/neat/workflows/steps/lib/io/__init__.py,sha256=k7IPbIq3ey19oRc5sA_15F99-O6dxzqbm1LihGRRo5A,32
@@ -275,8 +275,8 @@ cognite/neat/workflows/steps_registry.py,sha256=fkTX14ZA7_gkUYfWIlx7A1XbCidvqR23
275
275
  cognite/neat/workflows/tasks.py,sha256=dqlJwKAb0jlkl7abbY8RRz3m7MT4SK8-7cntMWkOYjw,788
276
276
  cognite/neat/workflows/triggers.py,sha256=_BLNplzoz0iic367u1mhHMHiUrCwP-SLK6_CZzfODX0,7071
277
277
  cognite/neat/workflows/utils.py,sha256=gKdy3RLG7ctRhbCRwaDIWpL9Mi98zm56-d4jfHDqP1E,453
278
- cognite_neat-0.77.6.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
- cognite_neat-0.77.6.dist-info/METADATA,sha256=XhW7PcK8bdv1WEsMS8Oehu1AapiwLD9PrbjOrXkV4yY,9316
280
- cognite_neat-0.77.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
- cognite_neat-0.77.6.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
- cognite_neat-0.77.6.dist-info/RECORD,,
278
+ cognite_neat-0.77.8.dist-info/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
279
+ cognite_neat-0.77.8.dist-info/METADATA,sha256=fr3l3w0YmPmZwtA9G4rKyL5fVBvN8ThIeBQNtJtzLwk,9316
280
+ cognite_neat-0.77.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
281
+ cognite_neat-0.77.8.dist-info/entry_points.txt,sha256=61FPqiWb25vbqB0KI7znG8nsg_ibLHBvTjYnkPvNFso,50
282
+ cognite_neat-0.77.8.dist-info/RECORD,,