Plan Serialisation
COASTSim can save an observation plan produced by a DITL run to a portable JSON file and
reload it later. The serialisation layer is built on Pydantic v2 and lives in
conops.targets.plan_schema. Convenience methods are also available directly on
Plan so you rarely need to import the schema classes explicitly.
Overview
Two Pydantic models handle the conversion:
PlanEntrySchema— represents a single observation entry (aPlanEntryorPointing).PlanSchema— top-level container that bundles metadata (version, timestamps, entry count) with the list of entries.
Both models support model_validate(..., from_attributes=True), so they accept plain Python
objects produced by the scheduler without any intermediate conversion step.
Quick Start
Save a plan after a DITL run
The simplest approach is to call save() directly on the plan:
# `ditl` is a QueueDITL (or similar) instance that has already been run
saved_path = ditl.plan.save("plan_20251201.json")
print(f"Saved to {saved_path}")
You can also go via PlanSchema if you need access to the
metadata fields before writing:
from conops.targets import PlanSchema
schema = PlanSchema.from_plan(ditl.plan)
print(f"Saving {schema.num_entries} entries (version {schema.version})")
schema.save("plan_20251201.json")
Load it back
load() is a class method on Plan that
returns a PlanSchema (preserving all metadata):
schema = Plan.load("plan_20251201.json")
print(schema.version) # schema format version (integer)
print(schema.num_entries) # number of plan entries
print(schema.entries[0].name) # first target name
Or equivalently via PlanSchema directly:
from conops.targets import PlanSchema
schema = PlanSchema.load("plan_20251201.json")
Round-trip via model_validate
schema = PlanSchema.model_validate(ditl.plan, from_attributes=True)
JSON File Format
The JSON file contains a metadata envelope followed by the entry list.
{
"version": 3,
"coast_sim_version": "0.1.3",
"created_at": "2025-12-01T00:00:00+00:00",
"start": "2025-12-01T00:00:00+00:00",
"end": "2025-12-01T23:59:00+00:00",
"num_entries": 42,
"entries": [
{
"name": "TEST_001",
"ra": 83.82,
"dec": -5.39,
"roll": 0.0,
"begin": "2025-12-01T00:00:00+00:00",
"end": "2025-12-01T00:16:40+00:00",
"merit": 95.0,
"slewtime": 120,
"insaa": 0,
"obsid": 1001,
"obstype": "AT",
"slewdist": 10.3,
"ss_min": 300.0,
"ss_max": 1000000.0,
"exptime": 880,
"exporig": 1000,
"isat": false,
"done": true,
"exposure": 880
},
{
"name": "SGS_PASS",
"ra": 120.0,
"dec": 45.0,
"roll": 0.0,
"begin": "2025-12-01T00:18:00+00:00",
"end": "2025-12-01T00:28:00+00:00",
"merit": 101.0,
"slewtime": 120,
"insaa": 0,
"obsid": 65535,
"obstype": "GSP",
"slewdist": 5.2,
"ss_min": 45.0,
"ss_max": 180.0,
"exptime": 480,
"exporig": 600,
"isat": false,
"done": true,
"exposure": 480,
"station": "SGS",
"contact_begin": "2025-12-01T00:20:00+00:00",
"contact_end": "2025-12-01T00:28:00+00:00"
}
]
}
Metadata Fields
Field |
Type |
Description |
|---|---|---|
|
int |
Integer plan-file revision counter. Starts at 0 and is incremented automatically each time a new plan is saved to the same directory for the same time window (see Auto-versioning below). |
|
string |
COASTSim package version that produced the file (e.g. |
|
string |
ISO-8601 UTC timestamp of when the |
|
string |
ISO-8601 UTC timestamp of the first entry’s |
|
string |
ISO-8601 UTC timestamp of the last entry’s |
|
int |
Total number of entries in the file. |
Entry Fields
Field |
Type |
Description |
|---|---|---|
|
string |
Human-readable target name (e.g. |
|
float |
Right ascension in degrees (J2000). |
|
float |
Declination in degrees (J2000). |
|
float |
Spacecraft roll angle in degrees ( |
|
string |
Start of the observation window (ISO-8601 UTC). |
|
string |
End of the observation window (ISO-8601 UTC). |
|
float |
Scheduler merit/priority figure of merit. |
|
int |
Slew duration in seconds. |
|
int |
Time spent in the South Atlantic Anomaly during the window (seconds). |
|
int |
Numeric observation identifier. |
|
string |
Observation type. Valid values: |
|
float |
Angular slew distance in degrees. |
|
float |
Minimum Sun-spacecraft separation angle encountered (degrees). |
|
float |
Maximum Sun-spacecraft separation angle encountered (degrees). |
|
int |
Exposure time in seconds (may be shorter than original if interrupted). |
|
int |
Originally requested exposure time in seconds. |
|
bool |
|
|
bool |
|
|
int |
Net science exposure time: |
|
string | null |
Ground station code for |
|
string | null |
ISO-8601 UTC timestamp of when the ground station contact window begins. Only present
for |
|
string | null |
ISO-8601 UTC timestamp of when the ground station contact window ends. Only present
for |
Ground Station Pass (GSP) Entries
Ground station pass entries (obstype="GSP") represent commanded communication windows
with ground stations. Unlike science observations, these entries capture the reservation
and execution of data downlink passes.
Key characteristics of GSP entries:
Automatic creation: Created by
QueueDITLwhen the spacecraft enters a ground station visibility window.Pass reservation: The
begintime marks when the spacecraft reserves the window (potentially including slew preparation), whilecontact_beginmarks the actual start of the ground station contact.Metadata fields: The
station,contact_begin, andcontact_endfields are only present for GSP entries (nullor omitted for other observation types).Deconfliction: When multiple ground stations are visible simultaneously, COASTSim automatically selects the pass with the highest expected data volume (downlink rate × duration). Dropped overlapping opportunities are logged but not exported to the plan.
Visualization: GSP entries are excluded from the science observation bands in
plot_ditl_timeline()andplot_ditl_timeline(). Ground station passes are still shown in the timeline’s dedicated “Ground Contact” row.Safe mode: No GSP entries are created when the spacecraft is in SAFE mode.
Example GSP entry:
{
"name": "TRO_PASS",
"obstype": "GSP",
"station": "TRO",
"begin": "2025-12-01T12:00:00+00:00",
"contact_begin": "2025-12-01T12:02:00+00:00",
"contact_end": "2025-12-01T12:12:00+00:00",
"end": "2025-12-01T12:12:00+00:00",
"slewtime": 120,
"exptime": 600,
"obsid": 65535
}
In this example, the spacecraft begins reserving the pass window at 12:00:00 (begin),
uses 2 minutes for slew preparation (slewtime), and the actual ground station contact
runs from 12:02:00 to 12:12:00 (contact_begin to contact_end).
Auto-versioning
When path is a directory (or ends with /), save()
scans the directory for existing files matching
plan_<start>_<end>_v<N>.json and sets version to max(N) + 1
(or 0 if no matching files exist). Saving to an explicit file path
leaves version unchanged.
Backward Compatibility
save() creates any missing parent directories
automatically, so you can pass a nested path without creating it first.
load() accepts files written by older versions of
COASTSim that predate PlanSchema. Fields not present in the file (e.g. created_at,
num_entries) are filled with schema defaults; num_entries is always recomputed from
the actual entry list after loading. Legacy files that store start, end, begin,
or end as numeric Unix timestamps (float/int) are accepted and converted to
datetime objects internally — only the on-disk format changed to ISO-8601.
Legacy files that stored version as a semantic-version string (e.g. "0.1.3") are
coerced to 0. Legacy files must already use the field names documented above (including
exporig); there is currently no automatic renaming or aliasing of deprecated keys.
The from_attributes=True model configuration means the schema can also validate against
any object that exposes the expected attributes, not just plain dicts.