TelemetryCore

DeepSpaceTelemetry.TelemetryCoreModule
TelemetryCore

Shared infrastructure of the simulation: configuration loading, schema-aware validation, storage governance (artifact estimation, budgets, retention settings), the accelerated mission clock and its persisted anchor, run provenance (event logs, snapshots, safesave rotation), and batch/segment I/O. Contains no routing logic — the queuing doctrine lives in Emitter and Receiver.

source
DeepSpaceTelemetry.TelemetryCore.PROJECT_ROOTConstant
PROJECT_ROOT

Absolute path of the package root, anchored at this source file through @__DIR__ so that it is stable across precompilation and relocation of the checkout. Relative configuration paths (physics.external_data_path, contacts.schedule_csv, the config file itself) resolve against it.

source
DeepSpaceTelemetry.TelemetryCore.latest_run_idFunction
latest_run_id() -> Union{Nothing, String}

ID of the most recently modified run directory under runs_root that carries a config_snapshot.toml, or nothing when no run exists. Used by the post-processing scripts when no run ID is given on the command line.

source
DeepSpaceTelemetry.TelemetryCore.batch_nameFunction
batch_name(id::Integer, live::Bool) -> String

Directory name of batch id: LIVE_batch_<id> for a batch finalized while the link was transmittable, ARCH_batch_<id> otherwise.

Examples

julia> TelemetryCore.batch_name(42, true)
"LIVE_batch_42"

julia> TelemetryCore.batch_name(42, false)
"ARCH_batch_42"
source
DeepSpaceTelemetry.TelemetryCore.batch_idFunction
batch_id(name::AbstractString) -> Int

Numeric ID parsed from a batch directory name (the trailing _<id> field); 0 for a name that does not carry one, so directory sweeps tolerate stray entries instead of throwing.

Examples

julia> TelemetryCore.batch_id("LIVE_batch_42")
42

julia> TelemetryCore.batch_id("stray_entry")
0
source
DeepSpaceTelemetry.TelemetryCore.is_live_batchFunction
is_live_batch(name::AbstractString) -> Bool

true for a LIVE_batch_<id> directory name (finalized while the link was transmittable).

Examples

julia> TelemetryCore.is_live_batch("LIVE_batch_7"), TelemetryCore.is_live_batch("ARCH_batch_7")
(true, false)
source
DeepSpaceTelemetry.TelemetryCore.load_configFunction
load_config(path::String="")

Loads the mission configuration from config.toml. Uses the provided path (relative paths resolve against the current directory first, then the project root) or defaults to config.toml at the project root.

source
DeepSpaceTelemetry.TelemetryCore.load_run_configFunction
load_run_config(run_dir::String)

Loads the configuration that produced a given run: the run's own config_snapshot.toml when present, falling back to the project-level config.toml for legacy runs. Post-processing must always use this instead of load_config, otherwise editing config.toml silently re-parametrizes the analysis of old runs (disruption windows, session times, physics rates).

source
DeepSpaceTelemetry.TelemetryCore.checked_numberFunction
checked_number(v, name::String) -> Float64

Coerces a config value to Float64, aborting with a clean [CONFIG] error when the TOML value is not numeric (e.g. a quoted "3600"), instead of surfacing a raw MethodError from deep inside the validator or a builder.

source
DeepSpaceTelemetry.TelemetryCore.dashboard_settingsFunction
dashboard_settings(cfg::AbstractDict) -> NamedTuple

Validated [dashboard] flags, each a boolean: open_live_viewer, open_receiver_log, and open_emitter_log (the terminals the dashboard launcher opens; default true) and receiver_status_panel (the receiver's in-console status panel; default false). A non-boolean value is rejected with a [CONFIG] error.

Examples

julia> TelemetryCore.dashboard_settings(Dict{String,Any}()).receiver_status_panel
false
source
DeepSpaceTelemetry.TelemetryCore.config_errorFunction
config_error(msg::AbstractString)

Rejects a configuration with an ArgumentError carrying the [CONFIG] message — the single throw point for every validator and coercion rejection, so callers can rely on the exception type.

source
DeepSpaceTelemetry.TelemetryCore.normalize_target_rowsFunction
normalize_target_rows(raw) -> Union{Symbol, Vector{Int}}

Parses the post_processing.target_event_rows configuration entry into a canonical form. Accepts the bare string "all", or an array mixing integer row indices (-1 meaning the final row), "start:stop" range strings, and the string "all". Returns the symbol :all when every row is requested, otherwise a sorted vector of unique row indices. Unrecognized entries are skipped with a warning.

Examples

julia> TelemetryCore.normalize_target_rows("all")
:all

julia> TelemetryCore.normalize_target_rows([3, "1:2", 3, -1])
4-element Vector{Int64}:
 -1
  1
  2
  3
source
DeepSpaceTelemetry.TelemetryCore.reject_removed_keyFunction
reject_removed_key(section::AbstractDict, sec_name::String, key::String)

Raises a [CONFIG] ArgumentError naming the replacement when section still carries key, a configuration key retired at 1.0.0 (REMOVED_CONFIG_KEYS); returns nothing otherwise. Called by the accessors that once read the key through a fallback and by validate_config.

source
DeepSpaceTelemetry.TelemetryCore.mission_wall_secondsFunction
mission_wall_seconds(cfg::AbstractDict) -> Float64

Validated wall-clock mission span simulation.mission_wall_seconds (> 0); the retired alias simulation.test_duration_sec is rejected with the replacement named.

Examples

julia> TelemetryCore.mission_wall_seconds(Dict{String,Any}("simulation" => Dict{String,Any}("mission_wall_seconds" => 168.0)))
168.0
source
DeepSpaceTelemetry.TelemetryCore.normalize_profile!Function
normalize_profile!(df::DataFrame) -> DataFrame

Brings a mission_profile.csv frame to the current column schema: the pre-0.10 Ground_Archive column (the live + archive total) is renamed Ground_Total. Every reader of the profile passes through here.

source
DeepSpaceTelemetry.TelemetryCore.telemetry_settingsFunction
telemetry_settings(cfg::AbstractDict) -> NamedTuple

Validated [telemetry] parameters: session_start::Time, session_duration::Second, bandwidth_profile::String, sigmoid_steepness, gaussian_sigma, max_batches_per_hour, max_inflight_batches::Int, min_link_factor, range_million_km ≥ 0 (spacecraft–Earth range; 0 disables the light-time delay), and the derived round_trip_light_time_sec = 2 · range / c. The link capacity is given either as max_batches_per_hour or as the physical pair downlink_kbps and onboard_data_rate_kbps (both > 0), from which the capacity in batches per hour follows through the batch content span ([physics]); the two forms are mutually exclusive. The result carries nominal_batch_transfer_sec (the transfer time of one batch at full capacity), catch_up_ratio (downlink over production rate; NaN in the batches-per-hour form), and onboard_data_rate_kbps (NaN likewise). Bounds are enforced with [CONFIG] errors; absent keys take the documented defaults (post-processing of legacy snapshots), while the live-config required-key policy is applied by validate_config.

Examples

julia> cfg = Dict{String,Any}(
           "telemetry" => Dict{String,Any}("downlink_kbps" => 230.0, "onboard_data_rate_kbps" => 75.0),
           "physics" => Dict{String,Any}(
               "data_source" => "synthetic",
               "sample_rate" => 4.0,
               "segment_duration_sec" => 60.0,
               "batch_size" => 10,
           ),
       );

julia> tel = TelemetryCore.telemetry_settings(cfg);

julia> round(tel.nominal_batch_transfer_sec; digits = 1), round(tel.max_batches_per_hour; digits = 1), round(tel.catch_up_ratio; digits = 2)
(195.7, 18.4, 3.07)

julia> tel = TelemetryCore.telemetry_settings(Dict{String,Any}("telemetry" => Dict{String,Any}("max_batches_per_hour" => 20.0)));

julia> tel.nominal_batch_transfer_sec, isnan(tel.catch_up_ratio)
(180.0, true)
source
DeepSpaceTelemetry.TelemetryCore.physics_settingsFunction
physics_settings(cfg::AbstractDict) -> NamedTuple

Validated [physics] parameters: data_source ("synthetic" or "external"), external_data_path (as configured; consumers resolve it against the package root), sample_rate > 0, segment_duration_sec > 0, and batch_size ≥ 1, with at least two samples per segment (the FFT synthesis block). The four core keys are required. The optional confusion_observation_years (one of 0.5, 1.0, 2.0, 4.0; default 1.0) selects the galactic-confusion fit of the noise model and noise_f_min_hz > 0 (default 1e-5) the lower edge of the synthesized band. The retired signal_injection_probability is rejected (event instants are [[events.markers]]). The existence of the external file is checked by validate_config only, so post-processing of a finished run does not depend on the input file still being present.

source
DeepSpaceTelemetry.TelemetryCore.supervision_settingsFunction
supervision_settings(cfg::AbstractDict) -> NamedTuple

Validated [supervision] parameters: on_component_failure ("abort", "continue", or "restart", lower-cased; default "abort"), max_restarts ≥ 0 (default 3), watchdog_sec > 0 (default 30).

source
DeepSpaceTelemetry.TelemetryCore.capacity_balanceFunction
capacity_balance(cfg::AbstractDict) -> NamedTuple

Capacity of one nominal pass against the daily production, from telemetry_settings, physics_settings, and the pass profile: rate_form (the capacity is given as the physical rate pair), profile_mean (profile_mean of the pass profile), pass_hours (telemetry.session_duration_hours), capacity_per_pass (batches, max_batches_per_hour × profile_mean × pass_hours), and produced_per_day (batches, 86 400 / (batch_size × segment_duration_sec)). Seasonal extensions, exceptions, and low-latency periods are not included: the figure is the balance of the nominal daily pass.

source
DeepSpaceTelemetry.TelemetryCore.ContactWindowType
ContactWindow

One ground-contact interval on the mission timeline: start and stop (DateTime, closed interval), the link capacity relative to peak (1.0 for a nominal pass; the station-availability fraction of a low-latency period), the low_latency flag, and a free-text label (empty for nominal passes). ContactWindow(start, stop) builds a nominal pass.

source
DeepSpaceTelemetry.TelemetryCore.contacts_settingsFunction
contacts_settings(cfg::AbstractDict) -> ContactsSettings

Validated [contacts] section — the ground-contact schedule layered on the daily window of [telemetry]; every key is optional and an absent section reproduces the plain daily window.

  • seasonal_extension_hours ≥ 0 (default 0): peak extension of the daily window, cosine-modulated over season_period_days > 0 (default 365.25) and peaking at season_peak_day_of_year ∈ [1, 366] (default 172); telemetry.session_duration_hours plus the extension may not exceed 24 h.
  • [[contacts.passes]] (start datetime, duration_hours > 0) or schedule_csv (columns Start, DurationHours; relative paths resolve against the current directory, then the project root) — an explicit, non-overlapping pass list that replaces the daily generator. The two forms are mutually exclusive.
  • [[contacts.exceptions]] (date, optional start, duration_hours ∈ [0, 24], 0 = missed pass): the window of that date verbatim, in place of the generated one; incompatible with an explicit schedule.
  • low_latency_enabled (default true), low_latency_capacity_fraction ∈ (0, 1] (default 1), and [[contacts.low_latency_periods]] (start, duration_hours > 0, optional capacity_fraction, label): extra contact windows at constant capacity outside the nominal passes. Periods triggered by [[events.markers]] (event_marker_settings) are appended, labelled by the marker.

Malformed entries raise a [CONFIG] error: a silently dropped pass or period invalidates the scenario.

source
DeepSpaceTelemetry.TelemetryCore.EventMarkerType
EventMarker

One [[events.markers]] entry: the mission instant time of an event of interest (a transient, a glitch), its label, and an optional triggered low-latency period — low_latency_after_hours after the marker, lasting low_latency_duration_hours (0 = none) at low_latency_capacity_fraction of peak capacity (NaN = the [contacts] default). EventMarker(time, label) builds a marker without a triggered period.

source
DeepSpaceTelemetry.TelemetryCore.event_marker_settingsFunction
event_marker_settings(cfg::AbstractDict) -> Vector{EventMarker}

Validated [[events.markers]] entries sorted by time: time (datetime, required), label (default marker <i>), low_latency_after_hours ≥ 0, low_latency_duration_hours ≥ 0, and low_latency_capacity_fraction ∈ (0, 1] when given. Labels must be unique. A malformed marker raises a [CONFIG] error.

source
DeepSpaceTelemetry.TelemetryCore.publication_settingsFunction
publication_settings(cfg::AbstractDict) -> NamedTuple

Validated [post_processing.publication]: enabled (default false), format ("pdf" | "svg", default "pdf"), column_width_mm ∈ [40, 400] (default 178, the double-column width the figures are designed at; 86–90 for a single column), and export_dir (default "" = <run_dir>/publication; relative paths resolve against the current directory). Unrecognized keys warn.

source
DeepSpaceTelemetry.TelemetryCore.save_markersFunction
save_markers(run_dir::String, markers::Vector{EventMarker})

Writes <run_dir>/markers.csv (SimTime, Label) — the run's event instants for consumers and post-processing; nothing is written when there are no markers.

source
DeepSpaceTelemetry.TelemetryCore.parsed_datetimeFunction
parsed_datetime(v, name::String) -> DateTime

Coerces a config value to DateTime: TOML local datetimes and dates arrive already parsed, strings are read as ISO-8601; anything else is rejected with a [CONFIG] error naming name.

source
DeepSpaceTelemetry.TelemetryCore.loss_channel_settingsFunction
loss_channel_settings(cfg::AbstractDict) -> NamedTuple

Validated [packet_loss] parameters: enabled, model ("bernoulli" or "gilbert_elliott", lower-cased), the five probabilities p_loss, p_good_to_bad, p_bad_to_good, p_loss_good, p_loss_bad (each in [0, 1]), on_loss ("retransmit" or "drop"), and max_retries ≥ 0. Types, enumerations, and bounds are enforced regardless of enabled: a malformed-but-disabled section fails fast instead of surfacing only once the channel is enabled.

source
DeepSpaceTelemetry.TelemetryCore.disruption_event_settingsFunction
disruption_event_settings(cfg::AbstractDict) -> Vector{DisruptionEventSettings}

Validated [[disruption.events]] entries in file order (the pre-1.0 [[disaster.events]] section name is rejected): type, label, start_day ≥ 0, duration_hours > 0, recovery_hours ≥ 0, severity ∈ [0, 1], loss_multiplier, and affects"link" (the default: a capacity and loss disruption) or "generation" (a scheduled gap in data production of duration_hours; severity, recovery, and loss keys are ignored). type = "antenna_repointing" defaults to "generation". A malformed event raises an error rather than being skipped: a silently missing disruption invalidates the scenario.

source
DeepSpaceTelemetry.TelemetryCore.onboard_capacityFunction
onboard_capacity(cfg::AbstractDict) -> NamedTuple

The on-board recorder ceiling from storage.onboard_capacity_days > 0 (default 14, the Definition Study Report's autonomy without ground contact): days, batches (the ceiling the emitter enforces — production over that span in whole batches, at least one), and gigabit (the physical volume when the link is given as a rate pair, else NaN).

source
DeepSpaceTelemetry.TelemetryCore.open_recorder_gapFunction
open_recorder_gap(run_dir::String) -> Bool

Whether the last recorder-overflow gap in events_tx.csv (rows with Batch = RECORDER) is still open — a gap_start without its gap_end — so a re-attaching emitter closes it when the buffer has room again.

source
DeepSpaceTelemetry.TelemetryCore.validate_configFunction
validate_config(cfg::AbstractDict)

Validates every tunable against its safe interval (documented inline in config.toml) before any directory is created or any computation starts. Code-breaking values are rejected with an ArgumentError (early termination); suspicious but runnable values emit a @warn. Returns cfg for chaining.

Hard errors (would break the pipeline):

  • non-positive speed_up, mission_wall_seconds, sample_rate, segment_duration_sec, max_batches_per_hour, max_storage_gb
  • batch_size < 1, initial_downtime_days < 0
  • session_duration_hours outside (0, 24] (the daily session scheduler wraps Time arithmetic at 24 h)
  • fewer than 2 samples per segment (sample_rate * segment_duration_sec < 2 breaks the FFT synthesis block)
  • unknown data_source; data_source = "external" with a missing file
  • packet-loss probabilities outside [0, 1], unknown loss model or on_loss policy, negative max_retries
  • disruption events with negative start_day, non-positive duration_hours, negative recovery_hours, or severity outside [0, 1]
  • type-mismatched values anywhere (a quoted "3600" where a number is expected, a float where an integer is expected) — reported as a precise [CONFIG] message instead of a raw conversion stacktrace
  • configuration keys retired at 1.0.0 (simulation.test_duration_sec, simulation.max_storage_gb, physics.signal_injection_probability, post_processing.generate_batch_matrix) and the [disaster] section name — rejected with the replacement named

Warnings (runnable but likely unintended):

  • emitter wall-clock period segment_duration_sec / speed_up below EMITTER_PERIOD_WARN_MS (the generation loop cannot keep pace; sim-time desync)
  • receiver nominal download slot 3600 / (max_batches_per_hour · speed_up) below RECEIVER_SLOT_WARN_MS (the RECEIVER_SLEEP_FLOOR_SEC sleep floor distorts the download rate)
  • unknown bandwidth_profile (falls back to "sine")
  • the physical rate pair combined with a shaped bandwidth_profile (the profile mean scales a link rate that the pass sustains; the capacity of one nominal pass against the daily production is stated)
  • non-integer sample_rate * segment_duration_sec (rounded)
  • Gilbert–Elliott p_bad_to_good = 0 (the channel never recovers)
  • disruption events starting at or after mission end (never fire), events whose blackout + recovery tail is truncated by mission end, blackouts spanning the entire remaining mission, and events overlapping in time (capacity composes as the minimum, loss multiplier as the maximum)
  • unrecognized sections or keys anywhere in the config (typo guard — an unknown key would otherwise silently fall back to its default)
  • loss saturation — the worst-channel-state per-attempt loss composed with the largest disruption loss_multiplier reaches ≥ 1 (every transfer fails while that regime is active)
source
DeepSpaceTelemetry.TelemetryCore.check_storage_limitsFunction
check_storage_limits(cfg::AbstractDict)

Pre-run storage safety gate. Prints the per-class artifact estimate (estimate_artifacts), then enforces the [storage] budgets with mitigation awareness:

  • retention disabled: abort when the projected total exceeds the budget (the error names [retention] as the mitigation); warn within 10 % of it.
  • retention enabled: abort only when even the steady-state footprint (non-prunable classes + payload capped at the watermark) exceeds the budget; otherwise warn if the unbounded projection exceeds the budget and proceed. Additionally warns when the payload generated within one grace_hours window alone exceeds the watermark (the custodian could never satisfy both constraints simultaneously).

The same logic gates storage.max_file_count, and the post-processing replay RAM estimate is gated against storage.max_ram_gb (a mitigation-free hard budget). Every abort is a StorageBudgetError. Returns nothing; called before any run directory is created.

source
DeepSpaceTelemetry.TelemetryCore.estimate_artifactsFunction
estimate_artifacts(cfg::AbstractDict) -> NamedTuple

Closed-form per-class artifact estimate for a run, computed entirely from the configuration (upper bounds where exact counts depend on stochastic outcomes). Classes: payload segment CSVs, batch metadata, event logs, metrics, the 2D mask timeline, point-wise expansions, plots (session + summary figures, PNG and vector-PDF twins; the optional GIF is a manual post-processing product and is excluded), and text logs. replay_ram_bytes estimates the post-processing replay RAM (gated against storage.max_ram_gb). Calibration constants default to measured values and are overridable key-by-key in [storage].

Returns counts (n_segments, n_batches, n_points, mission_days, metrics_rows), per-class byte fields, total_bytes, file_count, and the retention-prunable subset (prunable_bytes, prunable_files): the payload scaled by the expected delivered fraction under the configured loss model — terminally lost batches land in lost/, and metadata, event logs, metrics, masks, and lost/ are never prunable by construction.

source
DeepSpaceTelemetry.TelemetryCore.storage_budgetFunction
storage_budget(cfg::AbstractDict) -> (max_gb, max_files, max_ram_gb)

Resolves the run-directory disk budget [GB] and inode budget from [storage]. The retired simulation.max_storage_gb is rejected with the replacement named; without the key the default of 5.0 GB applies. max_ram_gb (default 8.0) budgets the post-processing replay RAM.

source
DeepSpaceTelemetry.TelemetryCore.RetentionPolicyType
RetentionPolicy

Immutable operating parameters of the retention custodian: enabled, grace (the mission-time availability guarantee for delivered payloads, as a Millisecond period), watermark_bytes (prunable-payload size that triggers pruning), and log_rotate_bytes (size-capped log rotation, active regardless of enabled). Constructed by retention_settings.

source
DeepSpaceTelemetry.TelemetryCore.STORAGE_CALIBRATION_DEFAULTSConstant
STORAGE_CALIBRATION_DEFAULTS

Measured calibration constants of the artifact estimator, overridable key-by-key in [storage]. The bytes_* disk constants are per-artifact CSV or figure sizes; bytes_replay_cell is the post-processing replay RAM per (metrics row × batch) membership. The schema entry for [storage] and the validator both iterate this table, so a new calibration key is declared exactly once.

source
DeepSpaceTelemetry.TelemetryCore.platform_provenanceFunction
platform_provenance() -> Dict{String,Any}

Hardware and runtime fingerprint stamped into every run's config_snapshot.toml under [provenance.platform]: hostname, OS kernel and architecture, CPU model and logical core count, total memory, Julia version, thread/BLAS-thread counts, the package version, and the git commit of the checkout (git_commit, empty outside a repository). Together with the configuration snapshot and the recorded input identity, every result is attributable to config + commit + platform. (No GPU fields: the pipeline is I/O- and event-loop-bound and uses no GPU backend.)

source
DeepSpaceTelemetry.TelemetryCore.setup_run_dirFunction
setup_run_dir(run_id::String; cfg=nothing)

Creates and returns the base directory for a simulation run along with its required subdirectories; a non-empty directory under the same run ID is rejected with an ArgumentError. When the parsed configuration cfg is provided, a config_snapshot.toml is written into the run directory (with safesave-style backup rotation) so every run's exact parameters remain reproducible after config.toml changes; the snapshot additionally carries the platform_provenance fingerprint under [provenance.platform].

source
DeepSpaceTelemetry.TelemetryCore.backup_existingFunction
backup_existing(path::String) -> Union{String, Nothing}

If path exists, renames it to <name>#<k><ext> using the smallest unused k, mirroring DrWatson's safesave backup rotation so no result file is ever silently overwritten. Returns the backup path, or nothing if path did not exist.

source
DeepSpaceTelemetry.TelemetryCore.SimulationClockType
SimulationClock(start_real_time::DateTime, start_sim_time::DateTime, speed_up::Float64)

The accelerated mission clock: mission time advances speed_up times faster than wall-clock time from the anchor pair start_real_time (the wall instant of the anchor) and start_sim_time (the mission instant at that anchor). get_current_sim_time maps the wall clock to mission SimTime; due_wall_time is its inverse.

source
DeepSpaceTelemetry.TelemetryCore.due_wall_timeFunction
due_wall_time(clock::SimulationClock, sim_time::DateTime) -> DateTime

Wall-clock instant at which the mission clock reaches sim_time — the inverse of get_current_sim_time. Each due time is computed from the clock anchor and the absolute mission instant, so pacing loops that sleep until a due time accumulate no rounding across iterations.

source
DeepSpaceTelemetry.TelemetryCore.EMITTER_MAX_SLEEP_SECConstant
EMITTER_MAX_SLEEP_SEC

Upper bound on a single emitter pacing sleep [wall-clock s]. The generation loop wakes at least this often to refresh its heartbeat and to honor the stop flag, the HALT sentinel, and the deadline even when one segment period is long (real-time rehearsals at low speed_up).

source
DeepSpaceTelemetry.TelemetryCore.EMITTER_LAG_WARN_SECConstant
EMITTER_LAG_WARN_SEC

Wall-clock duration [s] for which the emitter's content lag must persist above one segment period before the loop warns that the host cannot keep pace with the accelerated clock. Startup compilation and transient stalls are recovered by burst catch-up within this window and never warn.

source
DeepSpaceTelemetry.TelemetryCore.thread_advisoryFunction
thread_advisory() -> Union{Nothing, String}

Returns an advisory message when the process runs on a single Julia thread, nothing otherwise. The emitter, the receiver, and the supervisor are cooperative tasks: on one thread any non-yielding stretch in one of them (compilation warm-up, garbage collection, figure rendering) pauses the others until it yields. Three threads let each task own one; more bring no benefit because nothing else in the pipeline is parallel.

source
DeepSpaceTelemetry.TelemetryCore.save_clock_anchorFunction
save_clock_anchor(run_dir::String, clock::SimulationClock, deadline::DateTime)

Persists the mission clock anchor (wall epoch, mission epoch, speed-up) and the absolute wall-clock deadline into <run_dir>/clock_anchor.toml (an existing file is rotated to clock_anchor#k.toml first, never overwritten). Written once at mission start; a re-attaching or restarted component reconstructs the identical clock from it (load_clock_anchor), so mission time survives component outages — the outage simply elapses as mission time.

source
DeepSpaceTelemetry.TelemetryCore.max_logged_batch_idFunction
max_logged_batch_id(run_dir::String) -> Int

Highest batch ID recorded in events_tx.csv (0 when the log is absent or empty) — the authoritative resume point for a re-attaching emitter's batch counter, immune to batches already delivered out of onboard/.

source
DeepSpaceTelemetry.TelemetryCore.DataSegmentType
DataSegment(id::Int, timestamp::DateTime, data::Vector{Float32})

One contiguous segment of the observed time series: the Float32 samples data of one segment_duration_sec span whose first sample lies at the mission instant timestamp; id is the instrument's running segment counter.

source
DeepSpaceTelemetry.TelemetryCore.DataBatchType
DataBatch(id::Int, segments::Vector{DataSegment}, created_at::DateTime)

A collection of DataSegments prepared for bulk transmission over the DSN: id is the batch counter, segments the payload in content order, and created_at the mission instant of finalization (when the batch became transmittable; the content epoch is segments[1].timestamp).

source
DeepSpaceTelemetry.TelemetryCore.MissionMetricsType
MissionMetrics

A snapshot of the mission state including queue sizes, effective and nominal bandwidth, packet-loss counters, and the disruption flag. bandwidth_pct is the effective link capacity (visibility × disruption factor); nominal_bandwidth_pct is the visibility profile alone.

source
DeepSpaceTelemetry.TelemetryCore.save_metricsFunction
save_metrics(run_dir::String, m::MissionMetrics)

Appends a new metrics snapshot to mission_profile.csv. The packet-loss and disruption columns are appended after the legacy columns so pre-loss readers of old profiles keep working.

source
DeepSpaceTelemetry.TelemetryCore.log_tx_eventFunction
log_tx_event(run_dir::String, sim_t::DateTime, batch::String, event::String)

Appends one emitter-side batch milestone to events_tx.csv. Events: "gen" (batch finalized onboard), "tx" (batch placed on the downlink), and "marker" (sim_t = an event-marker instant, batch = the batch holding it; state-preserving). Together with log_rx_event this forms the exact per-batch state history used by the mask/animation reconstruction — no heuristic replay. Only the emitter task writes this file (single-writer; no lock needed).

source
DeepSpaceTelemetry.TelemetryCore.log_rx_eventFunction
log_rx_event(run_dir::String, sim_t::DateTime, batch::String, event::String, attempt::Int)

Appends one receiver-side batch milestone to events_rx.csv. Events: "ingested" (batch reached the ground archive), "retry" (transfer attempt lost, batch remains on the link), "lost" (retry budget exhausted, batch moved to lost/), and "pruned" (retention custodian deleted the delivered payload CSVs after the grace window; state-preserving for the mask replay). attempt counts failed transfer attempts so far (0 for "pruned"). Only the receiver task writes this file (single-writer; no lock needed).

source
DeepSpaceTelemetry.TelemetryCore.save_batchFunction
save_batch(path::String, batch::DataBatch; markers = String[])

Serializes a DataBatch and its metadata to the specified physical directory. metadata.json carries batch_id, segment_count, created_at (mission time at which the batch was finalized and became transmittable), content_epoch (mission timestamp of the first sample of the payload — the physical epoch the segment data belong to), and, when given, markers — the labels of the event markers whose instant lies in the payload.

source
DeepSpaceTelemetry.TelemetryCore.read_batch_metadataFunction
read_batch_metadata(batch_dir::String) -> Dict{String,Any}

Parses <batch_dir>/metadata.json. Returns an empty dictionary when the file is absent or unparsable (a foreign or truncated directory), so directory sweeps degrade to "unknown" instead of faulting.

source
DeepSpaceTelemetry.TelemetryCore.batch_content_epochsFunction
batch_content_epochs(run_dir::String) -> Dict{String,DateTime}

Batch name → content epoch (first-sample mission timestamp) for every batch directory under onboard/, link/, ground/, and lost/ whose metadata.json records a content_epoch (batches written before that key existed are omitted).

source
DeepSpaceTelemetry.TelemetryCore.load_segmentFunction
load_segment(path::String; timestamp::DateTime = DateTime(0))

Loads a 1D CSV time series back into a DataSegment. Segment CSVs persist only amplitudes, so the mission timestamp cannot be recovered from the file: callers that know the epoch (e.g. from metadata.json's created_at) pass it via timestamp; otherwise the DateTime(0) sentinel marks it unknown — never a fabricated wall-clock time.

source
DeepSpaceTelemetry.TelemetryCore.VisibilityModelType
VisibilityModel

Ground-contact model of the downlink. The nominal daily window opens at session_start for session_duration with the capacity profile (sigmoid_steepness, gaussian_sigma); seasonal_extension widens it symmetrically about its centre, cosine-modulated with period season_period_days and peaking at season_peak_day_of_year; exceptions (date => (start, duration), zero duration = missed pass) replace the window of a date verbatim; a non-empty schedule of explicit passes replaces the daily generator altogether; low_latency periods are additional windows at constant capacity outside the nominal passes. The three- to five-argument constructors build the plain daily model.

Examples

julia> model = TelemetryCore.VisibilityModel(Time(8), Second(8 * 3600), "sine");

julia> TelemetryCore.is_visible(model, DateTime(2035, 1, 1, 10)), TelemetryCore.is_visible(model, DateTime(2035, 1, 1, 3))
(true, false)

julia> round(TelemetryCore.profile_mean(model); digits = 3)
0.5
source
DeepSpaceTelemetry.TelemetryCore.seasonal_window_durationFunction
seasonal_window_duration(model::VisibilityModel, date::Date) -> Second

Duration of the generated window on date: session_duration plus the seasonal extension modulated as ½ [1 + cos(2π (d − d_peak) / P)] with the day of year d, so the full extension applies at season_peak_day_of_year and none half a period away.

source
DeepSpaceTelemetry.TelemetryCore.nominal_windowFunction
nominal_window(model::VisibilityModel, date::Date) -> Union{Nothing,ContactWindow}

The nominal pass anchored on date under the daily generator: the exception of that date verbatim when one exists (nothing for a missed pass), otherwise the configured window extended symmetrically by the seasonal term. nothing in explicit-schedule mode.

source
DeepSpaceTelemetry.TelemetryCore.contact_windowsFunction
contact_windows(model::VisibilityModel, t_lo::DateTime, t_hi::DateTime) -> Vector{ContactWindow}

Every contact window — nominal passes and low-latency periods — that overlaps [t_lo, t_hi], sorted by start.

source
DeepSpaceTelemetry.TelemetryCore.active_windowFunction
active_window(model::VisibilityModel, t::DateTime) -> Union{Nothing,ContactWindow}

The contact window containing t — a nominal pass first, else a low-latency period — or nothing when the spacecraft is out of contact. Under the daily generator only the windows anchored on the date of t and on the previous date (a pass crossing midnight) can contain t.

source
DeepSpaceTelemetry.TelemetryCore.profile_factorFunction
profile_factor(model::VisibilityModel, progress::Float64) -> Float64

The capacity profile of a nominal pass at the normalized position progress ∈ [0, 1] within the window: sine, sigmoid, gaussian, or flat (unknown names fall back to sine).

source
DeepSpaceTelemetry.TelemetryCore.profile_meanFunction
profile_mean(model::VisibilityModel) -> Float64

Mean of the capacity profile over one nominal pass, ∫₀¹ profile_factor(model, p) dp, by composite Simpson quadrature on PROFILE_MEAN_SUBINTERVALS: 1 for flat, 1/2 for sine, ln(cosh k)/k for sigmoid with steepness k, and σ √(2π) erf(1/(2√2 σ)) for gaussian. Multiplied by the full-capacity rate and the pass length it gives the capacity of one pass in batches (capacity_balance).

source
DeepSpaceTelemetry.TelemetryCore.get_bandwidth_factorFunction
get_bandwidth_factor(model::VisibilityModel, t::DateTime) -> Float64

Effective link capacity in [0, 1] at t: the pass profile evaluated at the position of t within the active nominal window, the constant capacity fraction inside a low-latency period, and 0 out of contact.

Examples

julia> model = TelemetryCore.VisibilityModel(Time(8), Second(8 * 3600), "sine");

julia> TelemetryCore.get_bandwidth_factor(model, DateTime(2035, 1, 1, 12))
1.0

julia> TelemetryCore.get_bandwidth_factor(model, DateTime(2035, 1, 1, 8))
0.0

julia> TelemetryCore.get_bandwidth_factor(model, DateTime(2035, 1, 1, 20))
0.0
source