openstatspec 0.1.0__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.
- openstatspec/__init__.py +6 -0
- openstatspec/api.py +95 -0
- openstatspec/cli.py +42 -0
- openstatspec/core/__init__.py +33 -0
- openstatspec/core/results.py +47 -0
- openstatspec/spss/__init__.py +22 -0
- openstatspec/spss/dictionary.py +180 -0
- openstatspec/spss/raw_dictionary.py +393 -0
- openstatspec/spss/sav.py +569 -0
- openstatspec/sql/__init__.py +14 -0
- openstatspec/sql/capabilities.py +260 -0
- openstatspec/sql/normative.py +513 -0
- openstatspec/sql/profiles.py +217 -0
- openstatspec/sql/wide.py +1087 -0
- openstatspec-0.1.0.dist-info/METADATA +90 -0
- openstatspec-0.1.0.dist-info/RECORD +18 -0
- openstatspec-0.1.0.dist-info/WHEEL +4 -0
- openstatspec-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"""Normative OpenStatSpec SPSS 1.0 relational catalogue.
|
|
2
|
+
|
|
3
|
+
Historical compatibility tables remain private to the adapter. Every new
|
|
4
|
+
import and export also writes this singular UUID-keyed public contract.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import re
|
|
10
|
+
from dataclasses import dataclass, fields
|
|
11
|
+
from datetime import UTC, datetime
|
|
12
|
+
from typing import Any, Iterable, Mapping
|
|
13
|
+
from uuid import uuid4
|
|
14
|
+
|
|
15
|
+
from sqlalchemy import (
|
|
16
|
+
BigInteger, CheckConstraint, Column, DateTime, Float, ForeignKey, Integer,
|
|
17
|
+
MetaData, String, Table, Text, UniqueConstraint, insert, inspect, select, update,
|
|
18
|
+
)
|
|
19
|
+
from sqlalchemy.dialects import mysql, postgresql, sqlite
|
|
20
|
+
|
|
21
|
+
SPEC_VERSION = "1.0"
|
|
22
|
+
CATALOG_CONTRACT_ID = "openstatspec-strict-wide-table-v1"
|
|
23
|
+
CATALOG_SCHEMA_VERSION = 1
|
|
24
|
+
FORMAT = re.compile(r"^([A-Za-z]+)([0-9]+)(?:[.]([0-9]+))?$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def binary64_type() -> Float:
|
|
28
|
+
return (
|
|
29
|
+
Float(precision=53)
|
|
30
|
+
.with_variant(mysql.DOUBLE(asdecimal=False), "mysql")
|
|
31
|
+
.with_variant(mysql.DOUBLE(asdecimal=False), "mariadb")
|
|
32
|
+
.with_variant(postgresql.DOUBLE_PRECISION(), "postgresql")
|
|
33
|
+
.with_variant(sqlite.REAL(), "sqlite")
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class NormativeTables:
|
|
39
|
+
catalog_identity: Table
|
|
40
|
+
dataset: Table
|
|
41
|
+
operation: Table
|
|
42
|
+
variable: Table
|
|
43
|
+
dataset_weight_variable: Table
|
|
44
|
+
value_label_set: Table
|
|
45
|
+
value_label: Table
|
|
46
|
+
variable_value_label_set: Table
|
|
47
|
+
missing_rule: Table
|
|
48
|
+
dataset_attribute: Table
|
|
49
|
+
variable_attribute: Table
|
|
50
|
+
document: Table
|
|
51
|
+
variable_set: Table
|
|
52
|
+
variable_set_member: Table
|
|
53
|
+
multiple_response_set: Table
|
|
54
|
+
multiple_response_member: Table
|
|
55
|
+
fidelity_event: Table
|
|
56
|
+
|
|
57
|
+
def all(self) -> tuple[Table, ...]:
|
|
58
|
+
return tuple(getattr(self, field.name) for field in fields(self))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def catalog(metadata: MetaData) -> NormativeTables:
|
|
62
|
+
catalog_identity = Table(
|
|
63
|
+
"catalog_identity", metadata,
|
|
64
|
+
Column("catalog_identity_key", Integer, primary_key=True, autoincrement=False),
|
|
65
|
+
Column("contract_id", String(128), nullable=False, unique=True),
|
|
66
|
+
Column("schema_version", Integer, nullable=False),
|
|
67
|
+
Column("created_at", DateTime, nullable=False),
|
|
68
|
+
CheckConstraint("catalog_identity_key = 1"),
|
|
69
|
+
CheckConstraint(f"contract_id = '{CATALOG_CONTRACT_ID}'"),
|
|
70
|
+
)
|
|
71
|
+
dataset = Table(
|
|
72
|
+
"dataset", metadata,
|
|
73
|
+
Column("dataset_id", String(36), primary_key=True),
|
|
74
|
+
Column("spec_version", String(32), nullable=False),
|
|
75
|
+
Column("source_format", String(16), nullable=False),
|
|
76
|
+
Column("physical_table_schema", String(255)),
|
|
77
|
+
Column("physical_table_name", String(255), nullable=False),
|
|
78
|
+
Column("dataset_name", String(255)),
|
|
79
|
+
Column("dataset_label", Text),
|
|
80
|
+
Column("source_encoding", String(128)),
|
|
81
|
+
Column("source_hash", String(128)),
|
|
82
|
+
Column("source_case_count", BigInteger, nullable=False),
|
|
83
|
+
Column("imported_at", DateTime, nullable=False),
|
|
84
|
+
)
|
|
85
|
+
operation = Table(
|
|
86
|
+
"operation", metadata,
|
|
87
|
+
Column("operation_id", String(36), primary_key=True),
|
|
88
|
+
Column("operation_kind", String(16), nullable=False),
|
|
89
|
+
Column("status", String(16), nullable=False),
|
|
90
|
+
Column("source_format", String(16)),
|
|
91
|
+
Column("started_at", DateTime, nullable=False),
|
|
92
|
+
Column("completed_at", DateTime),
|
|
93
|
+
)
|
|
94
|
+
variable = Table(
|
|
95
|
+
"variable", metadata,
|
|
96
|
+
Column("variable_id", String(36), primary_key=True),
|
|
97
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
98
|
+
Column("source_ordinal", Integer, nullable=False),
|
|
99
|
+
Column("source_name", String(255), nullable=False),
|
|
100
|
+
Column("physical_name", String(255), nullable=False),
|
|
101
|
+
Column("storage_kind", String(16), nullable=False),
|
|
102
|
+
Column("declared_string_width", Integer),
|
|
103
|
+
Column("variable_label", Text),
|
|
104
|
+
Column("print_format_family", String(64)),
|
|
105
|
+
Column("print_format_width", Integer),
|
|
106
|
+
Column("print_format_decimals", Integer),
|
|
107
|
+
Column("write_format_family", String(64)),
|
|
108
|
+
Column("write_format_width", Integer),
|
|
109
|
+
Column("write_format_decimals", Integer),
|
|
110
|
+
Column("measurement_level", String(32)),
|
|
111
|
+
Column("variable_role", String(32)),
|
|
112
|
+
Column("display_width", Integer),
|
|
113
|
+
Column("display_alignment", String(32)),
|
|
114
|
+
UniqueConstraint("dataset_id", "source_ordinal"),
|
|
115
|
+
UniqueConstraint("dataset_id", "source_name"),
|
|
116
|
+
UniqueConstraint("dataset_id", "physical_name"),
|
|
117
|
+
)
|
|
118
|
+
dataset_weight_variable = Table(
|
|
119
|
+
"dataset_weight_variable", metadata,
|
|
120
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), primary_key=True),
|
|
121
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), nullable=False, unique=True),
|
|
122
|
+
)
|
|
123
|
+
value_label_set = Table(
|
|
124
|
+
"value_label_set", metadata,
|
|
125
|
+
Column("value_label_set_id", String(36), primary_key=True),
|
|
126
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
127
|
+
Column("name", String(255)),
|
|
128
|
+
)
|
|
129
|
+
value_label = Table(
|
|
130
|
+
"value_label", metadata,
|
|
131
|
+
Column("value_label_id", String(36), primary_key=True),
|
|
132
|
+
Column("value_label_set_id", String(36), ForeignKey("value_label_set.value_label_set_id"), nullable=False),
|
|
133
|
+
Column("ordinal", Integer, nullable=False),
|
|
134
|
+
Column("code_kind", String(16), nullable=False),
|
|
135
|
+
Column("numeric_code", binary64_type()),
|
|
136
|
+
Column("string_code", Text),
|
|
137
|
+
Column("label", Text, nullable=False),
|
|
138
|
+
UniqueConstraint("value_label_set_id", "ordinal"),
|
|
139
|
+
)
|
|
140
|
+
variable_value_label_set = Table(
|
|
141
|
+
"variable_value_label_set", metadata,
|
|
142
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), primary_key=True),
|
|
143
|
+
Column("value_label_set_id", String(36), ForeignKey("value_label_set.value_label_set_id"), nullable=False),
|
|
144
|
+
)
|
|
145
|
+
missing_rule = Table(
|
|
146
|
+
"missing_rule", metadata,
|
|
147
|
+
Column("missing_rule_id", String(36), primary_key=True),
|
|
148
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), nullable=False),
|
|
149
|
+
Column("ordinal", Integer, nullable=False),
|
|
150
|
+
Column("rule_kind", String(32), nullable=False),
|
|
151
|
+
Column("code_kind", String(16)),
|
|
152
|
+
Column("numeric_value", binary64_type()),
|
|
153
|
+
Column("string_value", Text),
|
|
154
|
+
Column("numeric_lower", binary64_type()),
|
|
155
|
+
Column("numeric_upper", binary64_type()),
|
|
156
|
+
Column("lower_special", String(16)),
|
|
157
|
+
Column("upper_special", String(16)),
|
|
158
|
+
UniqueConstraint("variable_id", "ordinal"),
|
|
159
|
+
)
|
|
160
|
+
dataset_attribute = Table(
|
|
161
|
+
"dataset_attribute", metadata,
|
|
162
|
+
Column("dataset_attribute_id", String(36), primary_key=True),
|
|
163
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
164
|
+
Column("attribute_name", String(255), nullable=False),
|
|
165
|
+
Column("array_ordinal", Integer, nullable=False, default=1),
|
|
166
|
+
Column("attribute_value", Text, nullable=False),
|
|
167
|
+
UniqueConstraint("dataset_id", "attribute_name", "array_ordinal"),
|
|
168
|
+
)
|
|
169
|
+
variable_attribute = Table(
|
|
170
|
+
"variable_attribute", metadata,
|
|
171
|
+
Column("variable_attribute_id", String(36), primary_key=True),
|
|
172
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), nullable=False),
|
|
173
|
+
Column("attribute_name", String(255), nullable=False),
|
|
174
|
+
Column("array_ordinal", Integer, nullable=False, default=1),
|
|
175
|
+
Column("attribute_value", Text, nullable=False),
|
|
176
|
+
UniqueConstraint("variable_id", "attribute_name", "array_ordinal"),
|
|
177
|
+
)
|
|
178
|
+
document = Table(
|
|
179
|
+
"document", metadata,
|
|
180
|
+
Column("document_id", String(36), primary_key=True),
|
|
181
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
182
|
+
Column("source_ordinal", Integer, nullable=False),
|
|
183
|
+
Column("document_text", Text, nullable=False),
|
|
184
|
+
UniqueConstraint("dataset_id", "source_ordinal"),
|
|
185
|
+
)
|
|
186
|
+
variable_set = Table(
|
|
187
|
+
"variable_set", metadata,
|
|
188
|
+
Column("variable_set_id", String(36), primary_key=True),
|
|
189
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
190
|
+
Column("source_ordinal", Integer, nullable=False),
|
|
191
|
+
Column("set_name", String(255), nullable=False),
|
|
192
|
+
UniqueConstraint("dataset_id", "source_ordinal"),
|
|
193
|
+
UniqueConstraint("dataset_id", "set_name"),
|
|
194
|
+
)
|
|
195
|
+
variable_set_member = Table(
|
|
196
|
+
"variable_set_member", metadata,
|
|
197
|
+
Column("variable_set_id", String(36), ForeignKey("variable_set.variable_set_id"), primary_key=True),
|
|
198
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), nullable=False),
|
|
199
|
+
Column("source_ordinal", Integer, primary_key=True),
|
|
200
|
+
UniqueConstraint("variable_set_id", "variable_id"),
|
|
201
|
+
)
|
|
202
|
+
multiple_response_set = Table(
|
|
203
|
+
"multiple_response_set", metadata,
|
|
204
|
+
Column("multiple_response_set_id", String(36), primary_key=True),
|
|
205
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id"), nullable=False),
|
|
206
|
+
Column("source_ordinal", Integer, nullable=False),
|
|
207
|
+
Column("set_name", String(255), nullable=False),
|
|
208
|
+
Column("set_label", Text),
|
|
209
|
+
Column("set_kind", String(4), nullable=False),
|
|
210
|
+
Column("counted_value_kind", String(16)),
|
|
211
|
+
Column("counted_numeric_value", binary64_type()),
|
|
212
|
+
Column("counted_string_value", Text),
|
|
213
|
+
Column("category_label_behavior", Text),
|
|
214
|
+
Column("label_source", Text),
|
|
215
|
+
UniqueConstraint("dataset_id", "source_ordinal"),
|
|
216
|
+
UniqueConstraint("dataset_id", "set_name"),
|
|
217
|
+
)
|
|
218
|
+
multiple_response_member = Table(
|
|
219
|
+
"multiple_response_member", metadata,
|
|
220
|
+
Column("multiple_response_set_id", String(36), ForeignKey("multiple_response_set.multiple_response_set_id"), primary_key=True),
|
|
221
|
+
Column("variable_id", String(36), ForeignKey("variable.variable_id"), nullable=False),
|
|
222
|
+
Column("source_ordinal", Integer, primary_key=True),
|
|
223
|
+
UniqueConstraint("multiple_response_set_id", "variable_id"),
|
|
224
|
+
)
|
|
225
|
+
fidelity_event = Table(
|
|
226
|
+
"fidelity_event", metadata,
|
|
227
|
+
Column("fidelity_event_id", String(36), primary_key=True),
|
|
228
|
+
Column("operation_id", String(36), ForeignKey("operation.operation_id"), nullable=False),
|
|
229
|
+
Column("dataset_id", String(36), ForeignKey("dataset.dataset_id")),
|
|
230
|
+
Column("direction", String(16), nullable=False),
|
|
231
|
+
Column("severity", String(16), nullable=False),
|
|
232
|
+
Column("event_code", String(128), nullable=False),
|
|
233
|
+
Column("source_item", Text),
|
|
234
|
+
Column("detail_json", Text, nullable=False),
|
|
235
|
+
Column("created_at", DateTime, nullable=False),
|
|
236
|
+
)
|
|
237
|
+
return NormativeTables(
|
|
238
|
+
catalog_identity, dataset, operation, variable, dataset_weight_variable,
|
|
239
|
+
value_label_set, value_label,
|
|
240
|
+
variable_value_label_set, missing_rule, dataset_attribute,
|
|
241
|
+
variable_attribute, document, variable_set, variable_set_member,
|
|
242
|
+
multiple_response_set, multiple_response_member, fidelity_event,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def now() -> datetime:
|
|
247
|
+
return datetime.now(UTC).replace(tzinfo=None)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def timestamp(value: str | datetime | None = None) -> datetime:
|
|
251
|
+
if isinstance(value, datetime):
|
|
252
|
+
return value.astimezone(UTC).replace(tzinfo=None) if value.tzinfo else value
|
|
253
|
+
if value:
|
|
254
|
+
return datetime.fromisoformat(value.replace("Z", "+00:00")).astimezone(UTC).replace(tzinfo=None)
|
|
255
|
+
return now()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def create(connection: Any, tables: NormativeTables) -> None:
|
|
259
|
+
expected = {table.name for table in tables.all()}
|
|
260
|
+
existing = set(inspect(connection).get_table_names(schema=tables.dataset.schema))
|
|
261
|
+
if tables.catalog_identity.name not in existing:
|
|
262
|
+
conflicts = sorted(existing & expected)
|
|
263
|
+
if conflicts:
|
|
264
|
+
raise RuntimeError(
|
|
265
|
+
"The selected catalog namespace contains unowned OpenStatSpec relation names: "
|
|
266
|
+
+ ", ".join(conflicts)
|
|
267
|
+
)
|
|
268
|
+
tables.dataset.metadata.create_all(connection, tables=list(tables.all()))
|
|
269
|
+
connection.execute(insert(tables.catalog_identity).values(
|
|
270
|
+
catalog_identity_key=1,
|
|
271
|
+
contract_id=CATALOG_CONTRACT_ID,
|
|
272
|
+
schema_version=CATALOG_SCHEMA_VERSION,
|
|
273
|
+
created_at=now(),
|
|
274
|
+
))
|
|
275
|
+
return
|
|
276
|
+
identities = connection.execute(select(tables.catalog_identity)).mappings().all()
|
|
277
|
+
if len(identities) != 1:
|
|
278
|
+
raise RuntimeError("The selected catalog must contain exactly one identity marker.")
|
|
279
|
+
identity = identities[0]
|
|
280
|
+
if (
|
|
281
|
+
identity["catalog_identity_key"] != 1
|
|
282
|
+
or identity["contract_id"] != CATALOG_CONTRACT_ID
|
|
283
|
+
or identity["schema_version"] != CATALOG_SCHEMA_VERSION
|
|
284
|
+
):
|
|
285
|
+
raise RuntimeError("The selected catalog namespace belongs to an incompatible contract.")
|
|
286
|
+
tables.dataset.metadata.create_all(connection, tables=list(tables.all()))
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def record_operation(
|
|
290
|
+
connection: Any, tables: NormativeTables, *, operation_id: str,
|
|
291
|
+
operation_kind: str, status: str, source_format: str | None,
|
|
292
|
+
started_at: datetime | None = None, completed_at: datetime | None = None,
|
|
293
|
+
) -> None:
|
|
294
|
+
connection.execute(insert(tables.operation).values(
|
|
295
|
+
operation_id=operation_id, operation_kind=operation_kind, status=status,
|
|
296
|
+
source_format=source_format, started_at=started_at or now(),
|
|
297
|
+
completed_at=completed_at,
|
|
298
|
+
))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def finish_operation(connection: Any, tables: NormativeTables, *, operation_id: str, status: str) -> None:
|
|
302
|
+
connection.execute(
|
|
303
|
+
update(tables.operation).where(tables.operation.c.operation_id == operation_id)
|
|
304
|
+
.values(status=status, completed_at=now())
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def record_fidelity_events(
|
|
309
|
+
connection: Any, tables: NormativeTables, *, operation_id: str,
|
|
310
|
+
dataset_id: str | None, direction: str, events: Iterable[Mapping[str, Any]],
|
|
311
|
+
) -> None:
|
|
312
|
+
rows = []
|
|
313
|
+
for event in events:
|
|
314
|
+
detail_json = {"message": str(event.get("detail", ""))}
|
|
315
|
+
details = event.get("details")
|
|
316
|
+
if isinstance(details, Mapping):
|
|
317
|
+
detail_json.update(details)
|
|
318
|
+
rows.append({
|
|
319
|
+
"fidelity_event_id": str(uuid4()), "operation_id": operation_id,
|
|
320
|
+
"dataset_id": dataset_id,
|
|
321
|
+
"direction": str(event.get("direction", direction)),
|
|
322
|
+
"severity": str(event.get("severity", "warning")),
|
|
323
|
+
"event_code": str(event["code"]), "source_item": event.get("source_item"),
|
|
324
|
+
"detail_json": json.dumps(detail_json, default=str, ensure_ascii=False, sort_keys=True),
|
|
325
|
+
"created_at": now(),
|
|
326
|
+
})
|
|
327
|
+
if rows:
|
|
328
|
+
connection.execute(insert(tables.fidelity_event), rows)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def format_parts(variable: Mapping[str, Any], key: str) -> tuple[str | None, int | None, int | None]:
|
|
332
|
+
encoded = variable.get(key)
|
|
333
|
+
if encoded:
|
|
334
|
+
try:
|
|
335
|
+
decoded = json.loads(str(encoded))
|
|
336
|
+
except (TypeError, json.JSONDecodeError):
|
|
337
|
+
decoded = encoded
|
|
338
|
+
if isinstance(decoded, (list, tuple)) and len(decoded) >= 3:
|
|
339
|
+
return str(decoded[0]), int(decoded[1]), int(decoded[2])
|
|
340
|
+
legacy = variable.get("format")
|
|
341
|
+
if legacy:
|
|
342
|
+
match = FORMAT.match(str(legacy))
|
|
343
|
+
if match:
|
|
344
|
+
return match.group(1).upper(), int(match.group(2)), int(match.group(3) or 0)
|
|
345
|
+
return None, None, None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def member_names(value: Any) -> list[str]:
|
|
349
|
+
if isinstance(value, str):
|
|
350
|
+
return value.split()
|
|
351
|
+
if isinstance(value, (list, tuple)):
|
|
352
|
+
return [str(item) for item in value]
|
|
353
|
+
return []
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def store_imported_dataset(
|
|
357
|
+
connection: Any, tables: NormativeTables, *, dataset_name: str,
|
|
358
|
+
source_format: str, physical_table_name: str, dataset_label: str,
|
|
359
|
+
source_encoding: str | None, source_hash: str | None, source_case_count: int,
|
|
360
|
+
imported_at: str | datetime | None, variables: list[Mapping[str, Any]],
|
|
361
|
+
documents: Iterable[Mapping[str, Any]], value_labels: Iterable[Mapping[str, Any]],
|
|
362
|
+
missing_rules: Iterable[Mapping[str, Any]], attributes: Iterable[Mapping[str, Any]],
|
|
363
|
+
multiple_response_sets: Iterable[Mapping[str, Any]], source_extensions: Mapping[str, Any],
|
|
364
|
+
case_weight_variable: str | None = None,
|
|
365
|
+
) -> str:
|
|
366
|
+
dataset_id = str(uuid4())
|
|
367
|
+
connection.execute(insert(tables.dataset).values(
|
|
368
|
+
dataset_id=dataset_id, spec_version=SPEC_VERSION, source_format=source_format,
|
|
369
|
+
physical_table_schema=None, physical_table_name=physical_table_name,
|
|
370
|
+
dataset_name=dataset_name, dataset_label=dataset_label or None,
|
|
371
|
+
source_encoding=source_encoding, source_hash=source_hash or None,
|
|
372
|
+
source_case_count=source_case_count, imported_at=timestamp(imported_at),
|
|
373
|
+
))
|
|
374
|
+
variable_ids: dict[int, str] = {}
|
|
375
|
+
ids_by_name: dict[str, str] = {}
|
|
376
|
+
for variable in variables:
|
|
377
|
+
ordinal = int(variable["ordinal"])
|
|
378
|
+
variable_id = str(uuid4())
|
|
379
|
+
variable_ids[ordinal] = variable_id
|
|
380
|
+
ids_by_name[str(variable["source_name"])] = variable_id
|
|
381
|
+
pf, pw, pd = format_parts(variable, "print_format")
|
|
382
|
+
wf, ww, wd = format_parts(variable, "write_format")
|
|
383
|
+
if wf is None:
|
|
384
|
+
wf, ww, wd = pf, pw, pd
|
|
385
|
+
connection.execute(insert(tables.variable).values(
|
|
386
|
+
variable_id=variable_id, dataset_id=dataset_id, source_ordinal=ordinal,
|
|
387
|
+
source_name=str(variable["source_name"]), physical_name=str(variable["physical_name"]),
|
|
388
|
+
storage_kind=str(variable["storage_kind"]),
|
|
389
|
+
declared_string_width=variable.get("string_width"),
|
|
390
|
+
variable_label=variable.get("label") or None,
|
|
391
|
+
print_format_family=pf, print_format_width=pw, print_format_decimals=pd,
|
|
392
|
+
write_format_family=wf, write_format_width=ww, write_format_decimals=wd,
|
|
393
|
+
measurement_level=variable.get("measure"), variable_role=variable.get("role"),
|
|
394
|
+
display_width=variable.get("display_width"),
|
|
395
|
+
display_alignment=variable.get("alignment"),
|
|
396
|
+
))
|
|
397
|
+
if case_weight_variable is not None:
|
|
398
|
+
connection.execute(insert(tables.dataset_weight_variable).values(
|
|
399
|
+
dataset_id=dataset_id,
|
|
400
|
+
variable_id=ids_by_name[case_weight_variable],
|
|
401
|
+
))
|
|
402
|
+
labels_by_variable: dict[int, list[Mapping[str, Any]]] = {}
|
|
403
|
+
for row in value_labels:
|
|
404
|
+
labels_by_variable.setdefault(int(row["variable_ordinal"]), []).append(row)
|
|
405
|
+
for ordinal, rows in labels_by_variable.items():
|
|
406
|
+
set_id = str(uuid4())
|
|
407
|
+
connection.execute(insert(tables.value_label_set).values(
|
|
408
|
+
value_label_set_id=set_id, dataset_id=dataset_id, name=None,
|
|
409
|
+
))
|
|
410
|
+
connection.execute(insert(tables.variable_value_label_set).values(
|
|
411
|
+
variable_id=variable_ids[ordinal], value_label_set_id=set_id,
|
|
412
|
+
))
|
|
413
|
+
for row in rows:
|
|
414
|
+
connection.execute(insert(tables.value_label).values(
|
|
415
|
+
value_label_id=str(uuid4()), value_label_set_id=set_id,
|
|
416
|
+
ordinal=int(row["ordinal"]),
|
|
417
|
+
code_kind=(
|
|
418
|
+
"string" if str(row["value_type"]) == "text"
|
|
419
|
+
else str(row["value_type"])
|
|
420
|
+
),
|
|
421
|
+
numeric_code=row.get("numeric_value"), string_code=row.get("text_value"),
|
|
422
|
+
label=str(row["label"]),
|
|
423
|
+
))
|
|
424
|
+
for row in missing_rules:
|
|
425
|
+
variable_id = variable_ids[int(row["variable_ordinal"])]
|
|
426
|
+
discrete = str(row["kind"]) == "discrete"
|
|
427
|
+
code_kind = str(row["lower_type"]) if discrete else None
|
|
428
|
+
if code_kind == "text":
|
|
429
|
+
code_kind = "string"
|
|
430
|
+
connection.execute(insert(tables.missing_rule).values(
|
|
431
|
+
missing_rule_id=str(uuid4()), variable_id=variable_id,
|
|
432
|
+
ordinal=int(row["ordinal"]), rule_kind="discrete" if discrete else "numeric_range",
|
|
433
|
+
code_kind=code_kind if code_kind in {"numeric", "string"} else None,
|
|
434
|
+
numeric_value=row.get("lower_numeric") if discrete and code_kind == "numeric" else None,
|
|
435
|
+
string_value=row.get("lower_text") if discrete and code_kind == "string" else None,
|
|
436
|
+
numeric_lower=None if discrete else row.get("lower_numeric"),
|
|
437
|
+
numeric_upper=None if discrete else row.get("upper_numeric"),
|
|
438
|
+
lower_special=None if discrete or row.get("lower_type") == "numeric" else str(row.get("lower_type")).upper(),
|
|
439
|
+
upper_special=None if discrete or row.get("upper_type") == "numeric" else str(row.get("upper_type")).upper(),
|
|
440
|
+
))
|
|
441
|
+
for row in attributes:
|
|
442
|
+
if row["scope"] == "file":
|
|
443
|
+
connection.execute(insert(tables.dataset_attribute).values(
|
|
444
|
+
dataset_attribute_id=str(uuid4()), dataset_id=dataset_id,
|
|
445
|
+
attribute_name=str(row["attribute_name"]),
|
|
446
|
+
array_ordinal=int(row["value_ordinal"]),
|
|
447
|
+
attribute_value=str(row["attribute_value"]),
|
|
448
|
+
))
|
|
449
|
+
else:
|
|
450
|
+
connection.execute(insert(tables.variable_attribute).values(
|
|
451
|
+
variable_attribute_id=str(uuid4()),
|
|
452
|
+
variable_id=variable_ids[int(row["variable_ordinal"])],
|
|
453
|
+
attribute_name=str(row["attribute_name"]),
|
|
454
|
+
array_ordinal=int(row["value_ordinal"]),
|
|
455
|
+
attribute_value=str(row["attribute_value"]),
|
|
456
|
+
))
|
|
457
|
+
for row in documents:
|
|
458
|
+
connection.execute(insert(tables.document).values(
|
|
459
|
+
document_id=str(uuid4()), dataset_id=dataset_id,
|
|
460
|
+
source_ordinal=int(row["ordinal"]), document_text=str(row["text"]),
|
|
461
|
+
))
|
|
462
|
+
variable_sets = source_extensions.get("spss.variable_sets")
|
|
463
|
+
if isinstance(variable_sets, Mapping):
|
|
464
|
+
for set_ordinal, (set_name, raw_members) in enumerate(variable_sets.items(), start=1):
|
|
465
|
+
set_id = str(uuid4())
|
|
466
|
+
connection.execute(insert(tables.variable_set).values(
|
|
467
|
+
variable_set_id=set_id, dataset_id=dataset_id,
|
|
468
|
+
source_ordinal=set_ordinal, set_name=str(set_name),
|
|
469
|
+
))
|
|
470
|
+
for ordinal, member in enumerate(member_names(raw_members), start=1):
|
|
471
|
+
if member not in ids_by_name:
|
|
472
|
+
raise ValueError(f"Variable set {set_name!r} references unknown variable {member!r}.")
|
|
473
|
+
connection.execute(insert(tables.variable_set_member).values(
|
|
474
|
+
variable_set_id=set_id, variable_id=ids_by_name[member],
|
|
475
|
+
source_ordinal=ordinal,
|
|
476
|
+
))
|
|
477
|
+
mr_by_name: dict[str, list[Mapping[str, Any]]] = {}
|
|
478
|
+
for row in multiple_response_sets:
|
|
479
|
+
mr_by_name.setdefault(str(row["set_name"]), []).append(row)
|
|
480
|
+
for set_ordinal, (set_name, rows) in enumerate(mr_by_name.items(), start=1):
|
|
481
|
+
first = rows[0]
|
|
482
|
+
set_id = str(uuid4())
|
|
483
|
+
behavior = "counted_values" if first.get("use_category_labels") else "variable_labels"
|
|
484
|
+
label_source = "variable_label" if first.get("use_first_var_label") else "set_label"
|
|
485
|
+
counted_kind = first.get("counted_value_type")
|
|
486
|
+
if counted_kind == "text":
|
|
487
|
+
counted_kind = "string"
|
|
488
|
+
connection.execute(insert(tables.multiple_response_set).values(
|
|
489
|
+
multiple_response_set_id=set_id, dataset_id=dataset_id,
|
|
490
|
+
source_ordinal=set_ordinal, set_name=set_name,
|
|
491
|
+
set_label=first.get("label"), set_kind=str(first["kind"]),
|
|
492
|
+
counted_value_kind=counted_kind,
|
|
493
|
+
counted_numeric_value=first.get("counted_numeric"),
|
|
494
|
+
counted_string_value=first.get("counted_text"),
|
|
495
|
+
category_label_behavior=behavior, label_source=label_source,
|
|
496
|
+
))
|
|
497
|
+
for row in rows:
|
|
498
|
+
member = row.get("variable_name")
|
|
499
|
+
if member is None:
|
|
500
|
+
continue
|
|
501
|
+
if str(member) not in ids_by_name:
|
|
502
|
+
raise ValueError(f"Multiple-response set {set_name!r} references unknown variable {member!r}.")
|
|
503
|
+
connection.execute(insert(tables.multiple_response_member).values(
|
|
504
|
+
multiple_response_set_id=set_id, variable_id=ids_by_name[str(member)],
|
|
505
|
+
source_ordinal=int(row["member_ordinal"]),
|
|
506
|
+
))
|
|
507
|
+
return dataset_id
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def dataset_id_for_name(connection: Any, tables: NormativeTables, dataset_name: str) -> str:
|
|
511
|
+
return str(connection.execute(
|
|
512
|
+
select(tables.dataset.c.dataset_id).where(tables.dataset.c.dataset_name == dataset_name)
|
|
513
|
+
).scalar_one())
|