cognite-neat 0.123.12__py3-none-any.whl → 0.123.14__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,2 +1,2 @@
1
- __version__ = "0.123.12"
1
+ __version__ = "0.123.14"
2
2
  __engine__ = "^2.0.4"
@@ -25,6 +25,8 @@ CLASSES_QUERY = """SELECT ?concept ?name ?description ?implements
25
25
  }}
26
26
  """
27
27
 
28
+ CLASSES_QUERY_PARAMETERS = {"concept", "name", "description", "implements"}
29
+
28
30
  PROPERTIES_QUERY = """
29
31
 
30
32
  SELECT ?concept ?property_ ?name ?description ?value_type ?minCount ?maxCount ?default
@@ -44,34 +46,36 @@ PROPERTIES_QUERY = """
44
46
  FILTER (!bound(?description) || LANG(?description) = "" || LANGMATCHES(LANG(?description), "{language}"))
45
47
  }}
46
48
  """
49
+ PROPERTIES_QUERY_PARAMETERS = {
50
+ "concept",
51
+ "property_",
52
+ "name",
53
+ "description",
54
+ "value_type",
55
+ "minCount",
56
+ "maxCount",
57
+ "default",
58
+ }
47
59
 
48
60
 
49
61
  class OWLImporter(BaseRDFImporter):
50
62
  """Convert OWL ontology to unverified data model.
51
63
 
52
- Args:
53
- filepath: Path to OWL ontology
54
-
55
- !!! Note
56
- OWL Ontologies are information models which completeness varies. As such, constructing functional
57
- data model directly will often be impossible, therefore the produced data model object will be ill formed.
58
- To avoid this, neat will automatically attempt to make the imported data model compliant by adding default
59
- values for missing information, attaching dangling properties to default containers based on the
60
- property type, etc.
61
-
62
- One has to be aware that NEAT will be opinionated about how to make the ontology
63
- compliant, and that the resulting data model may not be what you expect.
64
-
64
+ Args:
65
+ filepath: Path to OWL ontology
65
66
  """
66
67
 
67
68
  def _to_data_model_components(
68
69
  self,
69
70
  ) -> dict:
70
- concepts, issue_list = parse_concepts(self.graph, CLASSES_QUERY, self.language, self.issue_list)
71
+ concepts, issue_list = parse_concepts(
72
+ self.graph, CLASSES_QUERY, CLASSES_QUERY_PARAMETERS, self.language, self.issue_list
73
+ )
71
74
  self.issue_list = issue_list
72
75
 
73
- # NeatError
74
- properties, issue_list = parse_properties(self.graph, PROPERTIES_QUERY, self.language, self.issue_list)
76
+ properties, issue_list = parse_properties(
77
+ self.graph, PROPERTIES_QUERY, PROPERTIES_QUERY_PARAMETERS, self.language, self.issue_list
78
+ )
75
79
  self.issue_list = issue_list
76
80
 
77
81
  components = {
@@ -14,12 +14,17 @@ from cognite.neat.core._issues.warnings._resources import (
14
14
  from cognite.neat.core._utils.rdf_ import convert_rdflib_content
15
15
 
16
16
 
17
- def parse_concepts(graph: Graph, query: str, language: str, issue_list: IssueList) -> tuple[dict, IssueList]:
17
+ def parse_concepts(
18
+ graph: Graph, query: str, parameters: set, language: str, issue_list: IssueList
19
+ ) -> tuple[dict, IssueList]:
18
20
  """Parse concepts from graph
19
21
 
20
22
  Args:
21
23
  graph: Graph containing concept definitions
24
+ query: SPARQL query to use for parsing concepts
25
+ parameters: Set of parameters to extract from the query results
22
26
  language: Language to use for parsing, by default "en"
27
+ issue_list: List to collect issues during parsing
23
28
 
24
29
  Returns:
25
30
  Dataframe containing owl classes
@@ -28,11 +33,10 @@ def parse_concepts(graph: Graph, query: str, language: str, issue_list: IssueLis
28
33
  concepts: dict[str, dict] = {}
29
34
 
30
35
  query = prepareQuery(query.format(language=language), initNs={k: v for k, v in graph.namespaces()})
31
- expected_keys = [str(v) for v in query.algebra._vars]
32
36
 
33
37
  for raw in graph.query(query):
34
38
  res: dict = convert_rdflib_content(cast(ResultRow, raw).asdict(), True)
35
- res = {key: res.get(key, None) for key in expected_keys}
39
+ res = {key: res.get(key, None) for key in parameters}
36
40
 
37
41
  # Quote the concept id to ensure it is web-safe
38
42
  res["concept"] = quote(res["concept"], safe="")
@@ -75,12 +79,17 @@ def parse_concepts(graph: Graph, query: str, language: str, issue_list: IssueLis
75
79
  return concepts, issue_list
76
80
 
77
81
 
78
- def parse_properties(graph: Graph, query: str, language: str, issue_list: IssueList) -> tuple[dict, IssueList]:
82
+ def parse_properties(
83
+ graph: Graph, query: str, parameters: set, language: str, issue_list: IssueList
84
+ ) -> tuple[dict, IssueList]:
79
85
  """Parse properties from graph
80
86
 
81
87
  Args:
82
- graph: Graph containing owl classes
88
+ graph: Graph containing property definitions
89
+ query: SPARQL query to use for parsing properties
90
+ parameters: Set of parameters to extract from the query results
83
91
  language: Language to use for parsing, by default "en"
92
+ issue_list: List to collect issues during parsing
84
93
 
85
94
  Returns:
86
95
  Dataframe containing owl classes
@@ -89,41 +98,40 @@ def parse_properties(graph: Graph, query: str, language: str, issue_list: IssueL
89
98
  properties: dict[str, dict] = {}
90
99
 
91
100
  query = prepareQuery(query.format(language=language), initNs={k: v for k, v in graph.namespaces()})
92
- expected_keys = [str(v) for v in query.algebra._vars]
93
101
 
94
102
  for raw in graph.query(query):
95
103
  res: dict = convert_rdflib_content(cast(ResultRow, raw).asdict(), True)
96
- res = {key: res.get(key, None) for key in expected_keys}
104
+ res = {key: res.get(key, None) for key in parameters}
97
105
 
98
106
  # Quote the concept id to ensure it is web-safe
99
107
  res["property_"] = quote(res["property_"], safe="")
100
108
  property_id = res["property_"]
101
109
 
102
- # Safeguarding against incomplete semantic definitions
103
- if not res["concept"] or isinstance(res["concept"], BNode):
110
+ # Skip Bnode
111
+ if isinstance(res["concept"], BNode):
104
112
  issue_list.append(
105
113
  ResourceRetrievalWarning(
106
114
  property_id,
107
115
  "property",
108
- error=("Unable to determine to what concept property is being defined"),
116
+ error="Cannot determine concept of property as it is a blank node",
109
117
  )
110
118
  )
111
119
  continue
112
120
 
113
- # Safeguarding against incomplete semantic definitions
114
- if not res["value_type"] or isinstance(res["value_type"], BNode):
121
+ # Skip Bnode
122
+ if isinstance(res["value_type"], BNode):
115
123
  issue_list.append(
116
124
  ResourceRetrievalWarning(
117
125
  property_id,
118
126
  "property",
119
- error=("Unable to determine value type of property"),
127
+ error="Unable to determine value type of property as it is a blank node",
120
128
  )
121
129
  )
122
130
  continue
123
131
 
124
132
  # Quote the concept and value_type to ensure they are web-safe
125
- res["concept"] = quote(res["concept"], safe="")
126
- res["value_type"] = quote(res["value_type"], safe="")
133
+ res["concept"] = quote(res["concept"], safe="") if res["concept"] else "#N/A"
134
+ res["value_type"] = quote(res["value_type"], safe="") if res["value_type"] else "#N/A"
127
135
 
128
136
  id_ = f"{res['concept']}.{res['property_']}"
129
137
 
@@ -78,7 +78,7 @@ class UnverifiedConceptualMetadata(UnverifiedComponent[ConceptualMetadata]):
78
78
 
79
79
  @dataclass
80
80
  class UnverifiedConceptualProperty(UnverifiedComponent[ConceptualProperty]):
81
- concept: ConceptEntity | str
81
+ concept: ConceptEntity | str | UnknownEntity
82
82
  property_: str
83
83
  value_type: DataType | ConceptEntity | MultiValueTypeInfo | UnknownEntity | str
84
84
  name: str | None = None
@@ -11,7 +11,11 @@ from cognite.neat.core._issues.errors._resources import (
11
11
  ResourceDuplicatedError,
12
12
  ResourceNotDefinedError,
13
13
  )
14
- from cognite.neat.core._issues.warnings._models import ConceptOnlyDataModelWarning, UndefinedConceptWarning
14
+ from cognite.neat.core._issues.warnings._models import (
15
+ ConceptOnlyDataModelWarning,
16
+ DanglingPropertyWarning,
17
+ UndefinedConceptWarning,
18
+ )
15
19
  from cognite.neat.core._issues.warnings._resources import (
16
20
  ResourceNotDefinedWarning,
17
21
  ResourceRegexViolationWarning,
@@ -47,6 +51,7 @@ class ConceptualValidation:
47
51
  self._referenced_classes_exist()
48
52
  self._referenced_value_types_exist()
49
53
  self._concept_only_data_model()
54
+ self._dangling_properties()
50
55
  self._regex_compliance_with_physical_data_model()
51
56
 
52
57
  return self.issue_list
@@ -56,6 +61,13 @@ class ConceptualValidation:
56
61
  if not self._properties:
57
62
  self.issue_list.append(ConceptOnlyDataModelWarning())
58
63
 
64
+ def _dangling_properties(self) -> None:
65
+ """Check if there are properties that do not reference any concept."""
66
+ dangling_properties = [prop for prop in self._properties if prop.concept == UnknownEntity()]
67
+ if dangling_properties:
68
+ for prop in dangling_properties:
69
+ self.issue_list.append(DanglingPropertyWarning(property_id=prop.property_))
70
+
59
71
  def _duplicated_resources(self) -> None:
60
72
  properties_sheet = self._read_info_by_spreadsheet.get("Properties")
61
73
  concepts_sheet = self._read_info_by_spreadsheet.get("Concepts")
@@ -119,7 +131,7 @@ class ConceptualValidation:
119
131
 
120
132
  def _undefined_classes(self) -> None:
121
133
  defined_concept = {concept.concept for concept in self._concepts}
122
- referred_concepts = {property_.concept for property_ in self._properties}
134
+ referred_concepts = {property_.concept for property_ in self._properties} - {UnknownEntity()}
123
135
 
124
136
  if undefined_concepts := referred_concepts.difference(defined_concept):
125
137
  for concept in undefined_concepts:
@@ -153,7 +165,7 @@ class ConceptualValidation:
153
165
  def _referenced_classes_exist(self) -> None:
154
166
  # needs to be complete for this validation to pass
155
167
  defined_concept = {concept.concept for concept in self._concepts}
156
- classes_with_explicit_properties = {property_.concept for property_ in self._properties}
168
+ classes_with_explicit_properties = {property_.concept for property_ in self._properties} - {UnknownEntity()}
157
169
 
158
170
  # USE CASE: models are complete
159
171
  if missing_classes := classes_with_explicit_properties.difference(defined_concept):
@@ -199,7 +211,7 @@ class ConceptualValidation:
199
211
  PATTERNS.physical_property_id_compliance.pattern,
200
212
  )
201
213
  )
202
- if not PATTERNS.view_id_compliance.match(prop_.concept.suffix):
214
+ if prop_.concept != UnknownEntity() and not PATTERNS.view_id_compliance.match(prop_.concept.suffix):
203
215
  self.issue_list.append(
204
216
  ResourceRegexViolationWarning(
205
217
  prop_.concept,
@@ -123,7 +123,7 @@ class ConceptualProperty(SheetRow):
123
123
  knowledge graph. Defaults to None (no transformation)
124
124
  """
125
125
 
126
- concept: ConceptEntityType = Field(
126
+ concept: ConceptEntityType | UnknownEntity = Field(
127
127
  alias="Concept",
128
128
  description="Concept id that the property is defined for, strongly advise `PascalCase` usage.",
129
129
  )
@@ -109,6 +109,16 @@ class ConceptOnlyDataModelWarning(UserModelingWarning):
109
109
  fix = "Define properties for concepts or make sure that concepts implement other concepts that have properties."
110
110
 
111
111
 
112
+ @dataclass(unsafe_hash=True)
113
+ class DanglingPropertyWarning(UserModelingWarning):
114
+ """Property {property_id} is not defined for any concept.
115
+ This will likely cause issues when converting to a physical data model."""
116
+
117
+ fix = "Define the property for a concept or remove it from the data model."
118
+
119
+ property_id: str
120
+
121
+
112
122
  @dataclass(unsafe_hash=True)
113
123
  class UndefinedViewWarning(UserModelingWarning):
114
124
  """Undefined view {value_type} has been referred as value type for property <{view_property}> of view {view_id}."""
@@ -98,6 +98,10 @@ def read_individual_sheet(
98
98
  # Special handling for Value Type column, #N/A is treated specially by NEAT it means Unknown
99
99
  raw["Value Type"] = raw["Value Type"].replace(float("nan"), "#N/A")
100
100
 
101
+ if "Concept" in raw.columns:
102
+ # Special handling for Concept column, #N/A is treated specially by NEAT it means Unknown
103
+ raw["Concept"] = raw["Concept"].replace(float("nan"), "#N/A")
104
+
101
105
  output = raw.replace(float("nan"), None).to_dict(orient="records")
102
106
  if return_read_info:
103
107
  # If no rows are skipped, row 1 is the header row.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cognite-neat
3
- Version: 0.123.12
3
+ Version: 0.123.14
4
4
  Summary: Knowledge graph transformation
5
5
  Project-URL: Documentation, https://cognite-neat.readthedocs-hosted.com/
6
6
  Project-URL: Homepage, https://cognite-neat.readthedocs-hosted.com/
@@ -1,5 +1,5 @@
1
1
  cognite/neat/__init__.py,sha256=12StS1dzH9_MElqxGvLWrNsxCJl9Hv8A2a9D0E5OD_U,193
2
- cognite/neat/_version.py,sha256=Mzqn-YIDR_mqLMU1RkQCFPerYxlahGnGKv-rz3QId68,47
2
+ cognite/neat/_version.py,sha256=qLY0w9gF4NJv-QQnU8dofXCm32MMtagvnj-drOInmzU,47
3
3
  cognite/neat/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  cognite/neat/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  cognite/neat/core/_config.py,sha256=WT1BS8uADcFvGoUYOOfwFOVq_VBl472TisdoA3wLick,280
@@ -43,17 +43,17 @@ cognite/neat/core/_data_model/importers/_spreadsheet2data_model.py,sha256=2QqrxQ
43
43
  cognite/neat/core/_data_model/importers/_rdf/__init__.py,sha256=1yOjV2PKCxwH7uCTXVZhSdxtn5etmFX40cksvwtKcZ8,199
44
44
  cognite/neat/core/_data_model/importers/_rdf/_base.py,sha256=FKceKumKmhEGpMZvo1BwEewnUvfAsTF3Ax9fo1nxsGE,6020
45
45
  cognite/neat/core/_data_model/importers/_rdf/_inference2rdata_model.py,sha256=PCgM9-qGSLlupN7tYCFLHjivgICtMiahNry1ub8JCYk,28934
46
- cognite/neat/core/_data_model/importers/_rdf/_owl2data_model.py,sha256=WmncZNpELeZnt6mdw6X8yWnr7XsFXZGfoVe5wTd0HH4,3438
47
- cognite/neat/core/_data_model/importers/_rdf/_shared.py,sha256=KJz4HirSpWBXsOw8YGBJh-6Yj_pBjrMB9_K8UQ00sQg,6411
46
+ cognite/neat/core/_data_model/importers/_rdf/_owl2data_model.py,sha256=l_89N1LewZVjSttcJkFAJj63JW8wI-THKJYjAxcNiDg,3093
47
+ cognite/neat/core/_data_model/importers/_rdf/_shared.py,sha256=qibq94dFoy_g1ED5nIFYwHC_LYI4jI67HFVcMy7abrM,6665
48
48
  cognite/neat/core/_data_model/models/__init__.py,sha256=hmF7MDR1XmpLxYdMkOEuPuHUqOQKE4AgsuUqdc-ySSQ,1249
49
49
  cognite/neat/core/_data_model/models/_base_unverified.py,sha256=1Wfbp-tJaEF6hd1bFdp2FhTgPkInf-1ZokuEoVJRPxQ,6842
50
50
  cognite/neat/core/_data_model/models/_base_verified.py,sha256=nzPrlj7ZvYull_Fdh2zeDXz98hux-eQOdTGy9jhUtYA,15127
51
51
  cognite/neat/core/_data_model/models/_types.py,sha256=70E8fiLdZkVF2sDUGPuDhzXNA5niVECkVDI7YN0NF60,5488
52
52
  cognite/neat/core/_data_model/models/data_types.py,sha256=uQ_u9KxCetLjxo-VtFzOXSxQuuf97Kg-9lfTTGzY6hc,10150
53
53
  cognite/neat/core/_data_model/models/conceptual/__init__.py,sha256=9A6myEV8s0-LqdXejaljqPj8S0pIpUL75rNdRDZzyR8,585
54
- cognite/neat/core/_data_model/models/conceptual/_unverified.py,sha256=ufNHLd9czY0XHV_C2zbQDarKSPeew7ohRbmq6dyo-_o,6282
55
- cognite/neat/core/_data_model/models/conceptual/_validation.py,sha256=twgVP-SQypAf3R8RB5Rp8EjCgblePK_zUbUyYQfut28,13339
56
- cognite/neat/core/_data_model/models/conceptual/_verified.py,sha256=HPNjzASKP9hs8lI8Xxfvdl2a9MdlP-JoEtXFk0a2ISw,13700
54
+ cognite/neat/core/_data_model/models/conceptual/_unverified.py,sha256=VswgnTSjSCRzBX3z5HvintBGaWBPexxIs-7z7S4J57c,6298
55
+ cognite/neat/core/_data_model/models/conceptual/_validation.py,sha256=LOnLNArteqc9TlyyRNqQgMAKx2oZ8s91WePR8fqLRlE,13888
56
+ cognite/neat/core/_data_model/models/conceptual/_verified.py,sha256=BUB4Ur4kpBoWiwTf57tjxJ2l0tDTSbY7zGrg1g0yVNQ,13716
57
57
  cognite/neat/core/_data_model/models/entities/__init__.py,sha256=UsW-_6fwd-TW0WcnShPKf40h75l1elVn80VurUwRAic,1567
58
58
  cognite/neat/core/_data_model/models/entities/_constants.py,sha256=GXRzVfArwxF3C67VCkzy0JWTZRkRJUYXBQaaecrqcWc,351
59
59
  cognite/neat/core/_data_model/models/entities/_loaders.py,sha256=PkrVtGlZWYLvAVIRABrgVSgkMvJYpBqdrHBfz-H0Ut8,2783
@@ -131,7 +131,7 @@ cognite/neat/core/_issues/errors/_wrapper.py,sha256=clhuSwUuHy-FQXQopFIQRY8c_NZM
131
131
  cognite/neat/core/_issues/warnings/__init__.py,sha256=lzNZrguzwXyifehsCilAXa5UL94DWHIeO-slyC-EYZc,3165
132
132
  cognite/neat/core/_issues/warnings/_external.py,sha256=w-1R7ea6DXTIWqwlwMMjY0YxKDMSJ8gKAbp_nIIM1AI,1324
133
133
  cognite/neat/core/_issues/warnings/_general.py,sha256=_6dAFaMz-LIv7GsBBIBq2d-kmbuxVXKvU4jZeb7tjAo,972
134
- cognite/neat/core/_issues/warnings/_models.py,sha256=mnoNxVvTifkIGxOxXAgdY-5ZDwy_IyxNsIxdTIcXdNY,4759
134
+ cognite/neat/core/_issues/warnings/_models.py,sha256=dE8Ha96WtZ9m_Bozx64NCMuJY2gWZleicD5tnnOqbe8,5086
135
135
  cognite/neat/core/_issues/warnings/_properties.py,sha256=I3vqc1aL-ce_FRQNgQQy34RW7kQxcjbwhZIIVtGVmg8,3807
136
136
  cognite/neat/core/_issues/warnings/_resources.py,sha256=_iPRq0pRMmRu3LFjqZTaG3OqOzw4f8-Vc9G4Im__FHc,3578
137
137
  cognite/neat/core/_issues/warnings/user_modeling.py,sha256=Qn_S8TLw7MMYQaJcZBScJA48kz_PrTWz0NaepSR70Fk,4144
@@ -147,7 +147,7 @@ cognite/neat/core/_utils/collection_.py,sha256=zVrSmm4045pjw6Pt6y4VPTIJ4dXdMJPyO
147
147
  cognite/neat/core/_utils/graph_transformations_report.py,sha256=ORVH7lw357TPOq4elU5lH46Qx6GCLVrSj-1nX6Ggk1U,1235
148
148
  cognite/neat/core/_utils/io_.py,sha256=D2Mg8sOxfBoDg3fC0jBzaxO3vkXmr0QvZSgYIv6xRkM,386
149
149
  cognite/neat/core/_utils/rdf_.py,sha256=8AALp8H_nXEDSBo6jZ1idyT_x3K4PJT5ZyBEyxPmgxI,10403
150
- cognite/neat/core/_utils/spreadsheet.py,sha256=MMI_1zxeHEf9Ggu_-t_ryjj54ky085QIf8eArt5hXEY,5749
150
+ cognite/neat/core/_utils/spreadsheet.py,sha256=VdjcCh339cKu9UwxJkYrmmnrbLguD71tdFmyd3MlIZA,5951
151
151
  cognite/neat/core/_utils/tarjan.py,sha256=IZvwaIITryGVNbo9Bv5EA9_sW3DyfUNAe7uYyPOCL0g,1357
152
152
  cognite/neat/core/_utils/text.py,sha256=ON4ihfscFJkQqQ-Rj46XXtf-9tAobwXbbfa3wuekSu4,8519
153
153
  cognite/neat/core/_utils/time_.py,sha256=7ayUm0OWZm1JDmy32E4ip8WRr2o0GLwrHwJA8sJ43Z4,357
@@ -186,7 +186,7 @@ cognite/neat/session/engine/__init__.py,sha256=D3MxUorEs6-NtgoICqtZ8PISQrjrr4dvc
186
186
  cognite/neat/session/engine/_import.py,sha256=1QxA2_EK613lXYAHKQbZyw2yjo5P9XuiX4Z6_6-WMNQ,169
187
187
  cognite/neat/session/engine/_interface.py,sha256=3W-cYr493c_mW3P5O6MKN1xEQg3cA7NHR_ev3zdF9Vk,533
188
188
  cognite/neat/session/engine/_load.py,sha256=g52uYakQM03VqHt_RDHtpHso1-mFFifH5M4T2ScuH8A,5198
189
- cognite_neat-0.123.12.dist-info/METADATA,sha256=U5jaZlc_AdCI7uML_9DBtq-V7bEIciJgBDxR8brvp0E,9172
190
- cognite_neat-0.123.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
191
- cognite_neat-0.123.12.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
192
- cognite_neat-0.123.12.dist-info/RECORD,,
189
+ cognite_neat-0.123.14.dist-info/METADATA,sha256=OtegXPCjwnGT8EM8x8M1ekoHwwAhZ4NuNRKfz0uRxGM,9172
190
+ cognite_neat-0.123.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
191
+ cognite_neat-0.123.14.dist-info/licenses/LICENSE,sha256=W8VmvFia4WHa3Gqxq1Ygrq85McUNqIGDVgtdvzT-XqA,11351
192
+ cognite_neat-0.123.14.dist-info/RECORD,,