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.
@@ -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())