"""Integration tests for real functionality: persistence, CSV import, and statistical attribution.""" from __future__ import annotations import os import tempfile from io import StringIO from pathlib import Path import pytest from spinor_os import ExperimentationOS, PersistenceManager, import_events_csv from spinor_os.config import AttributionMethod, EventType def test_csv_import_allocates_missions_and_records_events(): os = ExperimentationOS() os.register_employee("import-001", "field-scientist", "northeast") hypothesis = os.propose_hypothesis( statement="Imported outreach increases appointments.", causal_claim="Tailored outreach causes higher appointment rate.", predicted_effect={ "metric": "appointment_rate", "direction": "increase", "magnitude": 0.1, "unit": "percentage_point", "timing": "7d", }, employee_owner="import-001", falsification_criteria=["No increase observed."], customer_segment="enterprise", territory="northeast", modification="imported_outreach", ) experiment = os.start_experiment(hypothesis.hypothesis_id) csv_text = f"""experiment_id,actor_id,event_type,outcome_value,metric,execution_quality,actor_role {experiment.experiment_id},import-001,OUTCOME_OBSERVED,0.15,appointment_rate,0.88,field-scientist {experiment.experiment_id},import-001,OUTCOME_OBSERVED,0.18,appointment_rate,0.90,field-scientist """ summary = import_events_csv(os, StringIO(csv_text), experiment_id=experiment.experiment_id) assert summary["rows_read"] == 2 assert summary["rows_ingested"] == 2 assert len(summary["event_ids"]) == 2 assert not summary["errors"] assert len(os.events) == 2 def test_real_attribution_ols_and_diff_in_diff(): os = ExperimentationOS() os.register_employee("alice", "field-scientist", "northeast") os.register_employee("bob", "field-scientist", "southwest") hypothesis = os.propose_hypothesis( statement="X lifts Y in enterprise segment", causal_claim="X causes Y", predicted_effect={ "metric": "y_rate", "direction": "increase", "magnitude": 0.1, "unit": "pp", "timing": "7d", }, employee_owner="alice", falsification_criteria=["no effect"], customer_segment="enterprise", territory="northeast", modification="treatment_a", ) treat = os.start_experiment(hypothesis.hypothesis_id) control = os.start_experiment(hypothesis.hypothesis_id) m_t = os.allocate_mission( treat.experiment_id, "alice", [ { "hypothesis_id": hypothesis.hypothesis_id, "modification": "treatment_a", "customer_segment": "enterprise", "territory": "northeast", "timing": "2026-W31", "resource_allocation": 1.0, } ], ) m_c = os.allocate_mission( control.experiment_id, "bob", [ { "hypothesis_id": hypothesis.hypothesis_id, "modification": "control", "customer_segment": "enterprise", "territory": "southwest", "timing": "2026-W31", "resource_allocation": 1.0, } ], ) os.record_event( treat.experiment_id, m_t.mission_id, EventType.OUTCOME_OBSERVED, "alice", outcome_value=0.05, metric="y_rate", execution_quality=0.9, ) os.record_event( control.experiment_id, m_c.mission_id, EventType.OUTCOME_OBSERVED, "bob", outcome_value=0.07, metric="y_rate", execution_quality=0.9, ) os.record_event( treat.experiment_id, m_t.mission_id, EventType.OUTCOME_OBSERVED, "alice", outcome_value=0.35, metric="y_rate", execution_quality=0.9, ) os.record_event( control.experiment_id, m_c.mission_id, EventType.OUTCOME_OBSERVED, "bob", outcome_value=0.07, metric="y_rate", execution_quality=0.85, ) claim_ols = os.attribute( treat.experiment_id, "y_rate", 0.35, 0.05, AttributionMethod.OLS, 0.95, falsification_survived=True, ) assert claim_ols.real_attribution is not None assert claim_ols.real_attribution["method"] == "ols_regression" assert claim_ols.real_attribution["estimated_effect"] > 0 assert claim_ols.real_attribution["n_observations"] >= 4 claim_did = os.attribute( treat.experiment_id, "y_rate", 0.35, 0.05, AttributionMethod.DIFF_IN_DIFF, 0.95, falsification_survived=True, ) assert claim_did.real_attribution is not None assert claim_did.real_attribution["method"] == "difference_in_differences" assert claim_did.real_attribution["estimated_effect"] > 0 assert claim_did.real_attribution["n_observations"] >= 4 def test_persistence_reloads_imported_data(): with tempfile.TemporaryDirectory() as tmp: db = Path(tmp) / "spinor_real.sqlite" os1 = ExperimentationOS(persistence=PersistenceManager(db)) os1.register_employee("persist-001", "field-scientist", "northeast") hypothesis = os1.propose_hypothesis( statement="Persistent outreach works.", causal_claim="Outreach causes lift.", predicted_effect={ "metric": "lift", "direction": "increase", "magnitude": 0.1, "unit": "pp", "timing": "7d", }, employee_owner="persist-001", falsification_criteria=["no lift"], customer_segment="enterprise", territory="northeast", modification="outreach", ) experiment = os1.start_experiment(hypothesis.hypothesis_id) csv_text = f"""experiment_id,actor_id,event_type,outcome_value,metric,execution_quality {experiment.experiment_id},persist-001,OUTCOME_OBSERVED,0.12,lift,0.88 """ summary = import_events_csv(os1, StringIO(csv_text), experiment_id=experiment.experiment_id) assert summary["rows_ingested"] == 1 # Reopen from the same SQLite file. os2 = ExperimentationOS(persistence=PersistenceManager(db)) assert "persist-001" in os2.employees assert experiment.experiment_id in os2.experiments assert len(os2.events) == 1 def test_server_import_and_real_attribution(client): """Live-server style verification using the FastAPI TestClient.""" # Register employee and hypothesis. r = client.post("/employees", json={ "employee_id": "real-api-001", "role": "field-scientist", "territory": "northeast", }) assert r.status_code == 200 r = client.post("/hypotheses", json={ "statement": "Real outreach increases conversions", "causal_claim": "Outreach causes conversions", "predicted_effect": { "metric": "real_conversion_rate", "direction": "increase", "magnitude": 0.1, "unit": "pp", "timing": "7d", }, "employee_owner": "real-api-001", "falsification_criteria": ["No lift"], "customer_segment": "enterprise", "territory": "northeast", "modification": "real_outreach", }) assert r.status_code == 200 hypothesis_id = r.json()["hypothesis_id"] # Create treatment and control experiments. treat = client.post("/experiments", json={"hypothesis_id": hypothesis_id}).json() control = client.post("/experiments", json={"hypothesis_id": hypothesis_id}).json() treat_id = treat["experiment_id"] control_id = control["experiment_id"] # Allocate missions. m_t = client.post(f"/experiments/{treat_id}/missions", json={ "employee_id": "real-api-001", "candidates": [{ "hypothesis_id": hypothesis_id, "modification": "real_outreach", "customer_segment": "enterprise", "territory": "northeast", "timing": "2026-W31", "resource_allocation": 1.0, }], }).json() m_c = client.post(f"/experiments/{control_id}/missions", json={ "employee_id": "real-api-001", "candidates": [{ "hypothesis_id": hypothesis_id, "modification": "control", "customer_segment": "enterprise", "territory": "northeast", "timing": "2026-W31", "resource_allocation": 1.0, }], }).json() # Record pre and post outcomes. for (exp, mid, val, eq) in [ (treat_id, m_t["mission_id"], 0.05, 0.9), (control_id, m_c["mission_id"], 0.06, 0.9), (treat_id, m_t["mission_id"], 0.35, 0.9), (control_id, m_c["mission_id"], 0.07, 0.85), ]: r = client.post(f"/experiments/{exp}/events", json={ "mission_id": mid, "event_type": "outcome_observed", "actor_id": "real-api-001", "outcome_value": val, "metric": "real_conversion_rate", "execution_quality": eq, }) assert r.status_code == 200 # Compute real OLS attribution. r = client.post(f"/experiments/{treat_id}/attributes", json={ "outcome_metric": "real_conversion_rate", "outcome_value": 0.35, "counterfactual_estimate": 0.05, "method": "ols", "confidence": 0.95, "falsification_survived": True, }) assert r.status_code == 200 data = r.json() assert data["real_attribution"] is not None assert data["real_attribution"]["method"] == "ols_regression" assert data["real_attribution"]["estimated_effect"] > 0 # Force a save and check persistence status. r = client.post("/persistence/save") assert r.status_code == 200 r = client.get("/persistence/status") assert r.status_code == 200 status = r.json() assert status["persistence_enabled"] is True assert status["experiments"] >= 2