plan
tit.server.routes.plan ¶
POST /api/plan/{kind} -- resolve what running a config would do.
For every kind this route builds the concrete output directory (or directories, for a batch of
subjects/montages) via :class:~tit.paths.PathManager -- the UI never recomputes a path
convention itself (design rule R1) -- reports whether it already exists, estimates the job's
resource cost from :mod:tit.jobs.costs, and checks for held lock conflicts via
:mod:tit.jobs.locks / :mod:tit.jobs.api (empty when the job manager, B1, is not wired up yet
in this environment).
config uses the same kind -> dataclass resolution as
:mod:tit.server.routes.validate (:func:~tit.server.routes.validate.cls_for) -- a body that
fails validation here fails with the same ValueError/TypeError a
POST /api/validate/{kind} call against it would report, just as a 422 instead of a 200
{ok: false} (a plan cannot be computed for a config that does not even deserialize).
Per-kind output-dir resolution¶
sim
One :class:PlanJob per (subject, montage). config.montages applies to every
subject in the plan; the request body's top-level montage_sources field
(contracts/openapi.yaml's MontageSources -- {"flex": [{"subject"?, "run",
"electrode_type"?, "eeg_net"?}], "freehand": [{"subject"?, "name"} | "name"]}) is
resolved via :mod:tit.sim.montage_sources into extra montages attached only to
their own subject (flex-search runs and freehand stim-configs are inherently
subject-scoped resources, unlike a plain montage_list.json entry). For one release,
the pre-contract convention of the same data nested under config["montage_sources"]
(with run_name/subject_id field names) is still read as a fallback when the
top-level field is absent -- see :func:_montage_sources_for_request. Either shape is
ignored by :func:~tit.config_io.deserialize_config when read from config (not a
SimulationConfig field).
flex / flex_adaptive / flex_pareto
config.output_folder when set, else a previewed
:func:~tit.opt.flex.utils.generate_run_dirname name under flex-search/<subject>/ (the
real run may pick a different timestamp if run later -- flagged as a warning).
ex / mex
config.run_name or a timestamp, under ex-search / m-ex-search.
resolved.search_space gives the exact combination count from
:mod:tit.opt.ex.logic / :mod:tit.opt.mex.logic (never materializing the search).
leadfield
Existing leadfield lookup via LeadfieldGenerator.list_leadfields (no SimNIBS import).
analyzer
config.output_dir when set, else :meth:PathManager.analysis_output_dir with the
same arguments the Analyzer passes (field and tissue included; an atlas analysis named
by its regions as requested). A group run plans each subject's own default folder
(output_dir never reaches them) plus one project-level job (subject="") for the
group summary folder: output_dir, or the timestamped
group_analysis/<name>_<YYYYmmdd_HHMMSS> a run started now would name.
pre
:func:tit.jobs.plans.plan_preprocessing's G1-G6/report DAG, flattened to one
:class:PlanJob per stage per subject (not one per subject) -- exactly the granularity
POST /api/jobs/groups submits. Each stage's output_dir is a best-effort mapping to
the directory that stage's flag writes to (see :func:_pre_stage_output_dir); its
exists is :func:tit.pre.preflight.existing_outputs_for_step for that stage -- the
output the run's skip/replace policy acts on -- not the directory's existence (G2a
and G6 both touch m2m_<subject>/). Under "skip", a stage whose output an
interrupted run left incomplete gets a warning naming the subject and the stage. The
stages run one at a time (one job per product), so the ETA is their sum and the cost is
one stage's.
source
forward mode: :meth:PathManager.forward per subject. fsavg_map mode:
:meth:PathManager.sim_fsaverage per (subject, simulation) pair; it exists when
that spacing's projection does, and is overwritten with fsavg_map.overwrite or
when it lacks a requested field.
stats
Project-level (PlanJob.subject is ""): :meth:PathManager.stats_output with the
literal analysis_type tit.stats.permutation itself uses ("group_comparison" /
"correlation").
blender
One :class:PlanJob whose output_dir is the directory the matching exporter will
write into -- see :func:_blender_output_dir, which restates each mode's own
_resolve_paths formula (the exporters cannot be imported here: they need
bpy/trimesh, which exist only inside the SimNIBS container).
nifti_average / nilearn
One project-level :class:PlanJob (subject="", like stats): output_dir mirrors
tit.stats.nifti_average.main / tit.plotting.nilearn.__main__.main's own formula
exactly (derivatives/ti-toolbox/{nifti_average,nilearn_visuals}/<output_name or
subdir_name>/). Real validation/planning since the runners lane registered
NiftiAverageConfig/NilearnConfig in
:data:tit.config_io.CONFIG_CLASS_REGISTRY -- NO_SCHEMA_KINDS (see
:mod:tit.server.routes.validate) is empty as a result, kept only as an extension point.
PlanSystem ¶
Bases: BaseModel
The machine an eta_minutes was computed for (:mod:tit.jobs.eta).
plan_jobs ¶
plan_jobs(kind: str, body: PlanRequest) -> tuple[list[PlanJob], dict[str, Any] | None, list[str]] | None
The per-kind planners' answer for body: (jobs, resolved, warnings).
None when the kind has no config schema to plan from. Raises :class:HTTPException
(404 unknown kind, 422 a config that does not deserialize or an output outside the
project) exactly as POST /api/plan/{kind} answers. This is the folder resolution alone;
:func:plan adds the cost estimate and the lock conflicts. The Jobs API reuses it to name a
job's output folder (:func:tit.server.job_outputs.job_output_dir), so the two cannot
disagree.
Source code in tit/server/routes/plan.py
968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 | |