# Workflow `cleverly` makes the order of a causal analysis visible. The estimator cannot rescue an undefined question or an implausible identification argument, so those decisions precede learner selection. ## 1. Formulate the causal question State the population, treatment strategy, outcome, time horizon, and contrast. Choose the target family. It can be an arm contrast, a regime mean, a treatment policy, an incremental intervention, an MSM projection, or a longitudinal quantity. A label such as “ATE” is insufficient when the reference arm or target population is ambiguous. Store the study context in a `StudyProtocol` when the analysis needs a durable record: ```python from cleverly import StudyProtocol protocol = StudyProtocol( target_population="Adults discharged home from participating hospitals", eligibility=("Adult at discharge", "Discharged alive", "Discharged home"), time_zero="Hospital discharge, before navigation assignment", treatment_strategies=("Offer navigation", "Provide usual discharge support"), treatment_versions=("Bedside plan and two scheduled contacts", "No navigation offer"), outcome="Thirty-day patient-reported transition score", horizon="30 days after discharge", intercurrent_event_handling=("Use the score regardless of readmission",), interference_unit="Patient", assumption_rationale=( "Baseline adjustment covers the measured common causes of assignment and outcome", "Reserved navigator capacity supports the no-interference argument", ), ) ``` This record combines selected vocabulary from the [target-trial components](https://miguelhernan.org/whatifbook), [ICH E9(R1)](https://database.ich.org/sites/default/files/E9-R1_Step4_Guideline_2019_1203.pdf), and the literature on [treatment versions](https://pmc.ncbi.nlm.nih.gov/articles/PMC4219328/). It is not a complete target-trial protocol or proof of ICH estimand compliance. ## 2. Declare the observed-data design Use `PointTreatment` for one treatment node and `LongitudinalTreatment` for an ordered treatment history. Column roles are part of the design rather than learner arguments. ```python from sklearn.linear_model import LinearRegression, LogisticRegression from cleverly import CausalStudy, PointTreatment study = CausalStudy( data, design=PointTreatment( outcome="outcome", treatment="treatment", adjustment=("age", "baseline_score", "site"), weights="sampling_weight", cluster="household", ), protocol=protocol, ) ``` An empty adjustment set is an identification claim. Set `randomized=True` when randomization, not omission, justifies it. `randomized=True` does not give the fit the allocation probabilities: the treatment mechanism is still estimated. A trial that records its allocation probabilities declares them with `treatment_probabilities=`, and every method then divides by them ([known treatment mechanism](technical-reference/point-treatment-tmle.md#known-treatment-mechanism)). Each public object keeps one responsibility: | object | owns | does not own | | --- | --- | --- | | `StudyProtocol` | the population, eligibility, timing, strategies, versions, outcome, follow-up, intercurrent-event handling, interference unit, and assumption rationale | the estimand contrast or analysis configuration | | study design | observed column roles and treatment-time structure | the scientific rationale for those choices | | typed estimand | the mathematical contrast, intervention, and target-specific metadata | observed column roles or learner choices | | identification | the observed-data functional, assumptions, nuisances, and remainder condition | the evidence that assumptions hold in this study | | typed method | learners, cross-fitting, targeting, inference, and runtime settings | the causal question or identification argument | `CausalStudy` stamps the optional protocol onto the identified effect and each fitted result. A summary states `causal study protocol: absent` when the study has no protocol record. A summary prints the whole record by default, and `summary(protocol="fingerprint")` prints only its fingerprint line. ## 3. Choose a typed estimand ```python from cleverly import ATE question = ATE(reference=0) ``` Typed estimands carry their required design and intervention information. They prevent a continuous-dose policy from being interpreted as an arm contrast and keep longitudinal regimen, horizon, and cause metadata intact. ## 4. Inspect identification ```python effect = study.identify(question) print(effect.functional) print(effect.identification.assumptions) print(effect.identification.required_nuisances) print(effect.available_methods()) ``` The identified effect is the boundary between scientific and computational choices. If a composition is well posed but unsupported, `cleverly` raises a capability error with the missing derivation or implementation rather than estimating a convenient different parameter. ## 5. Configure estimation Start with the ordinary `"tmle"` preset. Use an immutable method object when the analysis needs an auditable configuration: ```python from cleverly import CrossFitting, Inference, ModelSpec, Runtime, TMLEMethod method = TMLEMethod( models=ModelSpec( outcome_learner=LinearRegression(), treatment_learner=LogisticRegression(max_iter=1000) ), cross_fitting=CrossFitting(n_folds=5, learner_folds=3, repeats=1), inference=Inference(alpha=0.05, simultaneous=False), runtime=Runtime(random_state=17, n_jobs=1), ) result = effect.estimate(method=method) ``` Common keyword shortcuts normalize into the same objects. Unknown or inapplicable options raise `MethodConfigurationError` before an engine is constructed. The example above fits a binary outcome. A cross-fitted fit of a *continuous* outcome also needs `Targeting(q_bounds=(lower, upper))`, holding the outcome's known support. Without it the fit would read its scale off every observed outcome, held-out rows included, and `cleverly` refuses it before any learner runs. The narrated transition score is bounded by its own maximum, so declare that range. An unbounded outcome has no such range, so fit it in sample with `CrossFitting(enabled=False)` instead. [Fold and outcome-scale rules](technical-reference/cv-tmle.md#fold-and-outcome-scale-rules) gives the messages. ## 6. Assess estimation quality Inspect support, nuisance performance, targeting scores, and sensitivity in the context of the identified estimand. A solved score is evidence about the targeting step, not proof that nuisance models are consistent or that identification assumptions hold. ```python print(result.diagnostics.support().summary()) print(result.diagnostics.nuisance_models().summary()) print(result.diagnostics.score_equations().summary()) print(result.sensitivity.run_all().summary()) ``` ## 7. Report the result and its limits Report the parameter definition, population, identification assumptions, learner and cross-fitting configuration, estimate and interval, support diagnostics, sensitivity analysis, and package version or commit. This page's fit is weighted and cross-fitted by household, and the households differ in size. Below 40 households the interval uses a Student t reference. Below 10 households the fit reports no interval, and `result.inference_status` names the status. Then report the point estimate and the reason of the status in place of the interval ([clusters](technical-reference/inference.md#clusters)). Save structured results when an audit trail matters: ```python result.save("analysis.joblib") ``` The joblib artifact stores the complete fitted result, including estimator configuration and nuisance-model objects. Load only trusted artifacts in a compatible Python environment. `cleverly.load()` warns with `VersionMismatchWarning` when another `cleverly` version saved the artifact. It then loads the artifact as saved, with no migration. See [results, inference, and assessment](user-guide/results-assessment.md) for replayability rules.