Skip to content

Commit 2fb7e9e

Browse files
Merge pull request #10 from OpenStatSpec/agent/conditional-transforms-v0.5
Add conditional SPSS transformations for Python v0.5
1 parent f2fce6f commit 2fb7e9e

23 files changed

Lines changed: 2012 additions & 196 deletions

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
uses: actions/checkout@v7
2121
with: &specification-checkout
2222
repository: OpenStatSpec/specification
23-
ref: 79339ec3d8f8aa81789b7e85f6b8afa6f1374e50
23+
ref: e49252c00890aed76dcaabc5d1ab5121b45929db
2424
path: openstatspec-specification
2525
- name: Checkout required SPSS engine
2626
uses: actions/checkout@v7

‎CHANGELOG.md‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@
22

33
All notable changes to this reference implementation are documented here.
44

5+
## Unreleased
6+
7+
Planned adapter release: `0.5.0`, after lifecycle integration and final specification conformance.
8+
9+
### Added
10+
11+
- Added bounded typed expressions with variable references, numeric literals,
12+
parentheses, `=`, `<`, `<=`, `>`, `>=`, `AND`, and `OR`. String comparison
13+
and v0.2 string assignment fail closed pending exact portable semantics.
14+
- Added sequential SPSS-like `COMPUTE` and `IF` assignment operations plus
15+
`FORMATS`, `VARIABLE LEVEL`, and `EXECUTE`.
16+
- Added atomic numeric target creation on SQLite and PostgreSQL. MySQL,
17+
MariaDB, and Dolt fail closed on `target_mode=create`; their targets must be
18+
provisioned physically and in the catalog by a separate versioned stage
19+
before this executor applies assignment and metadata operations.
20+
- Added synthetic exact-program, catalog, failure-boundary, and pre-existing
21+
target regression coverage.
22+
23+
### Changed
24+
25+
- Bumped the canonical transformation-plan and SPSS frontend contracts to
26+
`v0.2`; canonical JSON and hashes include every sequential operation.
27+
- In-place apply now records variable label, value labels, `F` print/write
28+
format, and measurement level in both normative and compatibility catalogs.
29+
- Dolt still requires an exact branch, exact HEAD, and clean working set;
30+
successful apply leaves an inspectable diff and never calls `DOLT_COMMIT`.
31+
32+
### Specification basis
33+
34+
- Release validation is pinned to the untagged OpenStatSpec specification
35+
release candidate at immutable commit
36+
`e49252c00890aed76dcaabc5d1ab5121b45929db`. Its
37+
`specification_release` remains null until that commit receives a stable tag.
38+
539
## 0.4.0 — 2026-07-31
640

741
### Added

‎README.md‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,19 @@ implemented capability boundary.
6767

6868
## SPSS-like transformation frontend
6969

70-
The SPSS-like frontend lowers supported `RECODE`, `VARIABLE LABELS`, and
71-
`VALUE LABELS` syntax into a language-neutral canonical plan. The in-place
72-
path applies it to the same logical dataset, physical wide table, and metadata
73-
catalog without a derived dataset, copied table, snapshot, or separate
74-
rollback/history layer. Dolt remains the sole versioning layer for Dolt-backed
75-
edits, and the transformer never calls `DOLT_COMMIT`.
70+
The bounded SPSS-like frontend lowers `RECODE`, sequential `COMPUTE` and `IF`,
71+
`VARIABLE LABELS`, `VALUE LABELS`, numeric `FORMATS`, `VARIABLE LEVEL`, and
72+
`EXECUTE` into a language-neutral typed canonical plan. Conditions support
73+
parentheses, variable and numeric literal operands, comparisons, `AND`, and
74+
`OR`. String comparison and v0.2 string assignment currently fail closed.
75+
The in-place path applies the plan to the same logical dataset, physical wide
76+
table, and normative/compatibility metadata without a derived dataset, copied
77+
table, snapshot, or hidden history layer. Numeric targets may be created
78+
atomically on SQLite and PostgreSQL. MySQL, MariaDB, and Dolt fail closed on
79+
`target_mode=create` and require a separately provisioned physical and catalog
80+
target before assignment. Dolt requires the caller's exact clean branch/HEAD,
81+
leaves success as an inspectable working-set diff, and never calls
82+
`DOLT_COMMIT`.
7683

7784
See the [dataset transformations manual](docs/transformations.md) for schema
7885
installation, Python and CLI surfaces, database invariants, audit provenance,

‎docs/release-readiness.md‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 0.4.0 release readiness
1+
# 0.5.0 release readiness
22

33
This page records the expected release contract, not a publication event.
44
Creating a version tag remains a separate maintainer action.
@@ -67,6 +67,17 @@ The gate must prove that:
6767

6868
- a TransformationPlan object and its strict JSON mapping produce the same
6969
plan hash and in-place result;
70+
- the exact bounded `COMPUTE`/`IF` program compiles to all seven ordered
71+
operations without dropping `FORMATS`, `VARIABLE LEVEL`, or `EXECUTE`;
72+
- boolean data results match the equivalent expression and the target's label,
73+
0/1 value labels, `F1.0` print/write format, and nominal level exist in both
74+
normative and compatibility catalogs;
75+
- injected schema, data, catalog, and audit failures leave no partial apply;
76+
- compensation tracks only newly created targets and never drops or rewrites a
77+
pre-existing target;
78+
- MySQL, MariaDB, and Dolt reject create-target plans before mutation; their
79+
service evidence covers assignment to a separately provisioned physical and
80+
catalog target without schema DDL;
7081
- top-level SPSS compiler imports and legacy openstatspec.transform re-exports
7182
still load from an installed wheel;
7283
- install-in-place-schema, apply-plan, and apply-spss execute their documented
@@ -78,8 +89,11 @@ The gate must prove that:
7889
source/plan hashes and frontend contract, and contain no copied data;
7990
- no OpenStatSpec rollback, snapshot, staging, copy, derived-dataset, or
8091
parallel history artifacts are created; and
81-
- Dolt checks expected branch, HEAD, and a clean working set without committing
82-
or changing HEAD; other supported SQL connections remain allowed.
92+
- Dolt checks expected branch, HEAD, and a clean working set; success changes
93+
neither HEAD nor branch and never commits or resets; state is rechecked after
94+
the dataset lock and success must leave an inspectable working-set diff;
95+
other supported SQL connections remain allowed; and
96+
- string comparisons and v0.2 string assignments fail closed until exact
8397

8498
The built wheel must contain the generic openstatspec.transform modules and the
8599
implemented openstatspec.frontends.spss package. Stata and SAS remain empty
@@ -98,9 +112,12 @@ capability claim, or implied support.
98112
4. Build with `python -m build` and install the generated wheel in a clean
99113
environment.
100114
5. Confirm `openstatspec capabilities` reflects the intended support boundary.
101-
6. Confirm the release tag matches the package version and that CI, release
102-
fixtures, and capabilities use OpenStatSpec specification release `v0.2.0`
103-
at exact commit `79339ec3d8f8aa81789b7e85f6b8afa6f1374e50`.
115+
6. Confirm CI, release fixtures, and capabilities use the untagged OpenStatSpec
116+
specification release candidate at exact commit
117+
`e49252c00890aed76dcaabc5d1ab5121b45929db`, publish
118+
`specification_status=release_candidate`, and leave
119+
`specification_release` null. If that exact commit receives a stable tag
120+
before this package is tagged, update the identity and re-run every gate.
104121
7. Review this document, the README, and CHANGELOG for accurate scope.
105122

106123
The tag-triggered release workflow repeats the non-service test suite, builds

‎docs/transformations.md‎

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@ OpenStatSpec separates transformation syntax, canonical meaning, and database
44
mutation. This lets multiple language frontends produce the same plan without
55
coupling the executor to any one language.
66

7-
The implemented frontend accepts a small SPSS-like subset: `RECODE`,
8-
`VARIABLE LABELS`, and `VALUE LABELS`. Stata and SAS are not implemented.
7+
The implemented bounded SPSS-like frontend accepts `RECODE`, sequential
8+
`COMPUTE` and `IF`, `VARIABLE LABELS`, `VALUE LABELS`, numeric `FORMATS`,
9+
`VARIABLE LEVEL`, and `EXECUTE`. Predicates support typed variable/literal
10+
operands, parentheses, numeric comparisons, `AND`, and `OR`. String comparison
11+
and v0.2 string assignment fail closed until exact profile-independent
12+
collation and explicit-width semantics are available; arbitrary SPSS, Python,
13+
and SQL expressions are rejected. Stata and SAS are not implemented.
914

1015
## Architecture
1116

@@ -106,9 +111,13 @@ result = openstatspec.apply_spss_in_place(
106111
dataset_id="responses",
107112
actor="agent@example.org",
108113
source_text="""
109-
RECODE age (18 THRU 34 = 1) (35 THRU 64 = 2).
110-
VARIABLE LABELS age 'Age group'.
111-
VALUE LABELS age 1 '18-34' 2 '35-64'.
114+
COMPUTE target = 0.
115+
IF (source_a = 1 AND source_b = 1) target = 1.
116+
VARIABLE LABELS target 'Example label'.
117+
VALUE LABELS target 0 'No' 1 'Yes'.
118+
FORMATS target (F1.0).
119+
VARIABLE LEVEL target (NOMINAL).
120+
EXECUTE.
112121
""",
113122
)
114123
```
@@ -130,20 +139,25 @@ On Dolt, also pass `--expected-branch` and `--expected-head`.
130139
Every successful apply preserves the logical `dataset_id` and physical
131140
schema/table identity. It creates no derived dataset, output table, full-table
132141
copy, staging table, snapshot, rollback artifact, or recovery/history layer.
133-
Existing-target recodes use direct `UPDATE`; label operations mutate existing
134-
catalog rows.
135-
136-
SQLite and PostgreSQL may add a numeric target where native transactions make
137-
the complete operation atomic. MySQL, MariaDB, and Dolt reject target-creating
138-
plans before the first mutation because implicit-commit DDL could leave a
139-
partial apply. Their target column and metadata must already exist.
140-
141-
Dolt is the sole history, diff, branch, and rollback layer for Dolt-backed
142-
datasets. Before mutation, the executor verifies the expected branch and
143-
`HEAD` and requires clean `dolt_status`. Success changes the same working set
144-
without changing `HEAD`. OpenStatSpec does not call `DOLT_COMMIT`, switch
145-
branches, merge, reset, tag, or create a hidden recovery commit. The caller
146-
reviews `dolt diff` and separately decides whether to commit or restore.
142+
Assignments and recodes use ordered `UPDATE` statements; later operations see
143+
earlier results. Label, value-label, format, and measurement-level operations
144+
update both the normative and compatibility catalogs.
145+
146+
A numeric create target is supported atomically on SQLite and PostgreSQL.
147+
MySQL, MariaDB, and Dolt reject `target_mode=create` before mutation. On those
148+
profiles a separate versioned stage must first provision the nullable numeric
149+
physical column and both catalog representations; the transformation executor
150+
then sees a pre-existing target and performs no schema DDL.
151+
The public operation reports success only after physical data, both metadata
152+
representations, and the compact audit row are mutually complete.
153+
154+
Before Dolt mutation, the executor verifies the expected branch and `HEAD` and
155+
requires clean `dolt_status`. Success changes the same working set without
156+
changing `HEAD`; OpenStatSpec does not call `DOLT_COMMIT`, `DOLT_RESET`, switch
157+
branches, merge, tag, or create a hidden recovery commit. It rechecks branch,
158+
HEAD, and a clean working set after locking the dataset and immediately before
159+
mutation. The caller reviews a successful `dolt diff` and
160+
separately decides whether to commit or restore.
147161

148162
## Audit and provenance
149163

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "openstatspec"
7-
version = "0.4.0"
7+
version = "0.5.0"
88
description = "Reference adapter for the OpenStatSpec relational contract"
99
readme = "README.md"
1010
requires-python = ">=3.11"

‎src/openstatspec/__init__.py‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,24 @@
1414
from .sql.workflow import TransformationError
1515
from .frontends.spss import SpssFrontendCompilation, compile_spss_syntax
1616
from .transform import (
17+
AssignOperation, BooleanExpression, ComparisonExpression,
18+
ConditionalAssignOperation, ExecuteOperation, Operand, PredicateExpression,
1719
RecodeMatch, RecodeOperation, RecodeResult, RecodeRule,
18-
ReplaceValueLabelsOperation, SetVariableLabelOperation,
20+
ReplaceValueLabelsOperation, SetFormatOperation,
21+
SetMeasurementLevelOperation, SetVariableLabelOperation,
1922
TransformationFrontendError, TransformationPlan, TypedValue, ValueLabel,
2023
VariableDefinition, VariableSchema, transformation_plan_from_dict,
2124
)
2225

2326
__all__ = [
27+
"AssignOperation", "BooleanExpression", "ComparisonExpression",
28+
"ConditionalAssignOperation", "ExecuteOperation", "Operand",
29+
"PredicateExpression",
2430
"CapabilityDeclaration", "LossReport", "SpssFrontendCompilation",
2531
"TransformationError", "TransformationFrontendError",
2632
"RecodeMatch", "RecodeOperation", "RecodeResult", "RecodeRule",
27-
"ReplaceValueLabelsOperation", "SetVariableLabelOperation",
33+
"ReplaceValueLabelsOperation", "SetFormatOperation",
34+
"SetMeasurementLevelOperation", "SetVariableLabelOperation",
2835
"TransformationPlan", "TypedValue", "ValueLabel",
2936
"VariableDefinition", "VariableSchema", "transformation_plan_from_dict",
3037
"UnsupportedOperationError", "capabilities", "capability_matrix",

‎src/openstatspec/api.py‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
)
2929
from .transform import TransformationPlan
3030
from .sql.capabilities import (
31-
SPECIFICATION_COMMIT, SPECIFICATION_RELEASE, active_connection, catalog_binding,
31+
SPECIFICATION_COMMIT, SPECIFICATION_RELEASE, SPECIFICATION_STATUS,
32+
active_connection, catalog_binding,
3233
)
3334

3435

@@ -44,7 +45,7 @@ def capability_matrix(database_url: str | None = None) -> Mapping[str, Any]:
4445
"""
4546
declaration = {
4647
"specification": "OpenStatSpec",
47-
"specification_status": "released",
48+
"specification_status": SPECIFICATION_STATUS,
4849
"specification_release": SPECIFICATION_RELEASE,
4950
"specification_commit": SPECIFICATION_COMMIT,
5051
"profile": "SPSS SAV/ZSAV 1.0",
@@ -145,7 +146,10 @@ def apply_spss_in_place(
145146
actor: str, expected_branch: str | None = None,
146147
expected_head: str | None = None,
147148
) -> Mapping[str, Any]:
148-
"""Apply supported SPSS-like syntax to the same SQL dataset/table."""
149+
"""Apply bounded sequential SPSS syntax to one SQL dataset/table.
150+
151+
Supports typed COMPUTE/IF predicates and dictionary metadata operations.
152+
"""
149153
return result(_apply_spss_in_place(
150154
database_url=str(database_url),
151155
dataset_id=dataset_id,

‎src/openstatspec/cli.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def main(argv: Sequence[str] | None = None) -> int:
7979

8080
apply_spss = commands.add_parser(
8181
"apply-spss",
82-
help="compile supported SPSS syntax and apply it in-place",
82+
help="compile bounded sequential SPSS syntax and apply it in-place",
8383
)
8484
apply_spss.add_argument("--database-url", required=True)
8585
apply_spss.add_argument("--dataset-id", required=True)

‎src/openstatspec/frontends/spss/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212

1313

14-
SPSS_FRONTEND_CONTRACT = "openstatspec-spss-syntax-frontend-v0.1"
14+
SPSS_FRONTEND_CONTRACT = "openstatspec-spss-syntax-frontend-v0.2"
1515

1616
__all__ = [
1717
"SPSS_FRONTEND_CONTRACT",

0 commit comments

Comments
 (0)