Dropbox Ingestion
Overview
The dropbox is the standard-conformant on-ramp for getting time series into a
dms_datastore repository. It lets collection and pre-processing happen in
whatever scratch area, web scrape, or incoming pipeline is convenient, and then
moves the result into a repository (formatted, processed,
structures_formatted, …) with consistent naming, metadata, units, and
headers.
A single YAML recipe describes one or more ingestion tasks. Each task moves data through four steps:
Read the data from its scratch/incoming location. Input is CSV and a single column can be selected.
Transform the data. Typical transforms convert SCADA-style local (daylight-saving) clock time to a fixed offset, coarsen high-rate irregular data, or trim empty edges. Unit conversions and similar reshaping happen here.
Stage a standardized CSV (canonical filename, YAML front-matter header,
value/user_flagcolumns) into a staging directory.Reconcile the staged file into the repository under an explicit policy (top-off vs. replace, whether new series may be created, etc.).
The entry point is the dms dropbox CLI or the dropbox_data() function.
Motivation
The dropbox exists so that the messy part of data handling — vendor-specific CSV layouts, wrong time zones, legacy units, one-off backfills — stays out of the repository. The repository only ever receives files that already conform to the datastore conventions. Benefits:
Separation of concerns. Cleaning/scraping can live anywhere. The recipe is the single, reviewable description of how a raw feed becomes a repo series.
A self-documenting trail. A recipe (and its
skip:markers) records exactly how each series was produced. One-time backfills stay in the file as documentation rather than living only in someone’s shell history.Repeatability. Ongoing feeds re-run the same recipe to top off existing series with new data.
Safety. Metadata, coordinates, and naming are validated against the station registry before anything touches the repo.
Concepts to Settle Before Writing a Recipe
Recipes connect a source file to one repository time series. A few datastore concepts determine the recipe’s metadata and output policy:
Receiving repository is the target repository for the data, which determines both its final location and the filename convention. The repo name must match one of the configured repositories in
dstore_config.yamland align with the data’s regularity and family. The stations you are working with have to be in the station registry for that repo, which is a CSV file listing all recognized stations and listing simple metadata like location and operating agency (e.g. the canonical one is station_dbase.csv which applies to the continuous stations).Usually the correct receiving repository is determined by a few questions:
Is the data regularly sampled?
Is the data linked to structure operations?
Is the data an abstract, such as Net Delta Outflow, a regulation objective, or a harmonic forecast?
Is the data reported at a daily time step?
Repository tier is a hierarchy that applies to all the continuous stations. The lowest,
raw, isn’t even a repo that is stored persistently; it is for initial ingestion and staging. The next tier,formatted, holds standardized source observations; Thescreened `` tier unifies units and makes sure that multiple sources of the same instrument's data are resolved. Finally ``processedis for derived, filled, or application-oriented products;structures_formattedis used for irregular structure and gate records. Choose arepo_namethat exists indstore_config.yamland whose regularity matches the data. It is expected that new repositories creation is to be done infrequently and cautiously. Don’t make that your first move, but DO use the “repo_data_dir” field to override the target initially. This allows a safe dry run to test your output before plastering data over a production repository.Series identity is the station, sublocation, parameter and in some cases agency that are needed to fully identify a series. Use a sublocation when a station has distinct sensors or products for the same parameter; otherwise use
defaultwhich in most data use cases can be left blank but in a recipe is better made explicit. The station_id must be an entry in the station registry, which avoids orphans and certain kinds of mistakes. Coordinates belong in the registry, not in recipe-authored metadata, but don’t cheat on this because a placeholder will be tedious to correct in the repository. See Concepts and Conventions and thestation-registryskill. You will find that many recipes defer to the registry explicitly by saying:latitude: registry_lookup longitude: registry_lookup projection_x_coordinate: registry_lookup projection_y_coordinate: registry_lookup
Provenance fields answer different questions. You will encounter names like
agency,sourceandprocessorwhich answer different questions aboutwhere the data comes from.
agencyidentifies the instrument-owning agency,agency_idis its native station identifier, andsourcedescribes the collection or processing source for this product. So, for instance theagencymight beNCRO(DWR North Central Regional Office) but thesourcefor the most recent months is often CDEC, which is a data mart that supplies real-time provisional-quality data over a number of stations.Staging is not reconciliation. Staging writes a standardized file format to a specified staging location. Often this file is a longer stretch or superset of what will ultimately be spliced or merged into the repository. Add the
output.reconcile, the final step, only when that staged result should be merged into the configured repository. Decide explicitly whether the new staged series or the original repo series should win where timestamps overlap. Familiarize yourself with the vtools methods for splicing or merging series. Generally we do not “shuffle timestamps” arbitrarily – one or the other will prevail over time blocks. Lastly, you can do a practice run by settingoutput.repo_data_dir(directly underoutput, not underreconcile) to point to a temporary mock repository instead of the actual repository. You absolutely should do this before running on the actual repository to catch any issues early. See Dry Run and Production.
Recipe Decision Path
Use this sequence to choose the recipe settings before filling in YAML. Each box is labeled with the part of the recipe that the decisions inside it settle.
%%{init: {"themeVariables": {"fontSize": "18px"}, "flowchart": {"subGraphTitleMargin": {"top": 6, "bottom": 24}}}}%%
flowchart TD
A["Use file_pattern<br/>to discover files"] --> B{"Does each match<br/>represent a distinct series/datastream?"}
B -- Yes --> C["Inference mode:<br/>filename template +<br/>metadata inference;<br/>omit collect.wildcard"]
B -- "No: output series/stream;<br/>distributed over<br/>files by time" --> D{"How many files<br/>match this series?"}
D -- One --> E["Omit collect.wildcard<br/>or set it to none"]
D -- Several --> F{"Do their time<br/>ranges overlap?"}
F -- No --> G["collect.wildcard:<br/>time_shard"]
F -- Yes --> H["collect.wildcard:<br/>time_overlap + merge_method"]
subgraph REGULARITY ["collect.reader_args"]
I{"Regularly<br/>sampled?"}
I -- Yes --> J["force_regular: true<br/>metadata.freq: infer<br/>or explicit ('15min');<br/>regular repo_name"]
I -- No --> K["force_regular: false<br/>metadata.freq: none;<br/>irregular repo_name"]
end
C --> I
E --> I
G --> I
H --> I
subgraph TRANSFORMS ["transforms"]
L{"Timestamps<br/>DST-local?"}
L -- Yes --> M["dst_tz, before<br/>any coarsen"]
L -- No --> N["no time-zone<br/>transform"]
end
J --> L
K --> L
subgraph OUTPUT ["output"]
O{"Review only or<br/>update repo?"}
O -- Review only --> P["omit reconcile section"]
O -- Update repo --> Q["reconcile: set <br/>prefer: (repo/staged) and <br/>allow_new_series: true/false<br/>deliberately"]
Q --> R{"Dry run or<br/>production?"}
R -- Dry run --> S["repo_data_dir:<br/>mock repo<br/>directory"]
R -- Production --> T["omit repo_data_dir;<br/>target inferred from<br/>repo_name"]
end
M --> O
N --> O
The boxes correspond to these decisions:
- File discovery and inference
collect.file_patterndiscovers candidate files. A glob pattern such as*.csvorflow_*.csvuses glob wildcards (*and?); these are different from{field}placeholders in a filename template. Use template inference when matched files represent distinct series and their identity can be resolved from filename fields and/or the station registry. Each matched file is then processed as its own output, andcollect.wildcardmust be omitted. Do not choose inference merely because filenames contain years. See Filename Templates (Inference Mode).collect.wildcardWhen multiple files share one output-series identity,
collect.wildcardcombines their data along time; it does not combine distinct series. A glob can match year-named files without any{year},{syear}, or{eyear}template fields: choosetime_shardwhen the files’ time ranges do not overlap, andtime_overlap(withmerge_method) only when they do. Decide from the data’s time coverage, not just from the filenames. Omit the setting or usenonewhen exactly one file should match. See Wildcard Modes.collect.reader_argsRegular series keep the frequency contract (
force_regular: true), declaremetadata.freqasinferor an explicit frequency, and go to a regularoutput.repo_name. Gate and event records are read irregularly (force_regular: false,metadata.freq: none) and go to an irregular repository. See Regular vs. Irregular Data.transformsLocal clock time with daylight saving is converted to a fixed offset with
dst_tzbefore any coarsening. See Transforms.outputStage only, to inspect the result, or add
reconcileto merge into the repository with an explicit overlap preference and a deliberateallow_new_seriessetting. Do a dry run first by pointingoutput.repo_data_dirat a mock repository; for production, removerepo_data_dirso the target comes fromrepo_name. See Output and Reconciliation and Dry Run and Production.
For reconciliation, prefer: staged is appropriate when the staged data is
authoritative over its covered interval, such as a consistent full-window
recompute. prefer: repo is for deliberate gap-filling or a one-off backfill
where existing repository values must win. It is usually not the right routine
policy for a producer that republishes a changing recent tail; see
Output and Reconciliation for the reconcile options.
CLI
dms dropbox --input dropbox_smscg_scada.yaml # run all entries
dms dropbox --input dropbox_smscg_scada.yaml --name smscg_scada # one entry
dms dropbox --input dropbox_smscg_scada.yaml --debug # verbose logging
dms dropbox --input dropbox_smscg_scada.yaml --logdir ./logs --quiet
dms dropbox --input dropbox_daily.yaml --omit-unregistered # skip unknown stations
Options:
--input(required): the recipe to run. May be a path to a YAML file (absolute or relative to the current directory) or the bare name of a recipe bundled indms_datastore/dropbox_recipes/(e.g.dropbox_dailyordropbox_daily.yaml).--name(repeatable): run only the named recipe entry/entries.--logdir: directory for log files.--debug: enable debug-level logging and per-file output.--quiet: suppress console output.--omit-unregistered: in inference mode, skip files that cannot be matched to a station registry entry and stage the rest. Without this flag, any unregistered file causes the recipe to fail.
Recipe Resolution
Recipes are centralized in the bundled dms_datastore/dropbox_recipes/
directory so bare names resolve identically in a development install
(pip install -e .) or a deployed wheel. --input (and the
dropbox_data() argument) is resolved in this order, first match wins:
The value as given, if it is an existing file path.
./dropbox_recipes/<name>— a project-local override directory in the cwd.<package>/dropbox_recipes/<name>— the bundled recipes.
A .yaml extension is appended automatically when omitted.
Programmatic Use
from dms_datastore.dropbox_data import dropbox_data
dropbox_data("dropbox_smscg_scada.yaml")
dropbox_data("dropbox_smscg_scada.yaml", selected_names=["smscg_scada"])
How to Write a Recipe
A recipe is a YAML file with top-level interpolation variables and a data
list of entries. Values are resolved with OmegaConf, so ${...} interpolation is available
(use it for path composition instead of Python .format).
# Top-level variables available via ${...} interpolation
repo_home: //cnrastore-bdo/Modeling_Data/
target_tz: "Etc/GMT+8" # POSIX sign reversed => PST
target_tz_label: PST
data:
- name: my_series # unique entry name (used by --name)
skip: false # optional; true leaves the entry as a documented no-op
collect:
file_pattern: "*.csv" # glob or filename template
location: "${repo_home}/incoming/my_source"
recursive_search: false
reader: read_ts # currently the only supported reader
reader_args: {} # kwargs passed to read_ts
selector: none # column name (or list) to keep, or none
wildcard: none # none | time_shard | time_overlap
merge_method: ts_splice # ts_splice | ts_merge (for time_overlap)
merge_args: {} # e.g. {transition: prefer_last}
splice_args: {} # optional {rename: value} or {rename: {old: new}}
transforms: # optional, applied in order
- name: dst_tz
args:
src_tz: US/Pacific
target_tz: ${target_tz}
metadata:
station_id: my_id # required
source: my_source # required
agency: usgs # required
param: flow # required
unit: ft^3/s # required
time_zone: ${target_tz} # required
freq: infer # required: literal freq | "infer" | none (irregular)
subloc: default # optional
output:
repo_name: formatted # must match a repo in dstore_config.yaml
# repo_data_dir: ./mock_repo # dry run only; directly under output, never under reconcile
staging:
dir: ${repo_home}/incoming/drop_staging
write_args:
float_format: "%.2f"
chunk_years: false
reconcile: # optional; omit to stage only
prefer: staged # staged | repo
allow_new_series: false
Step by step:
Point ``collect`` at the raw files. Use
selectorto keep one column (or a list of columns for multi-gate frames). Usereader_argsfor source-specific quirks (names,usecols,na_values,hint).Add ``transforms`` only when needed. Regular scientific feeds usually need none. SCADA feeds typically need
dst_tzand oftencoarsen.Fill in ``metadata``. Keep string fields lower case. Coordinates come from the registry — never hard-code them (see below).
Choose an ``output`` policy. Stage into a scratch directory, then decide whether/how to reconcile into the repo. Reconcile into a mock repository (
repo_data_dir) before reconciling into the real one.
Naming convention: keep recipe file names consistent and descriptive
(dropbox_smscg_scada.yaml, dropbox_ccf.yaml). Avoid the legacy spec
infix in new recipes.
Example AI Prompt for Recipe Creation
Use the dropbox-ingestion and station-registry skills to create a recipe
for the hourly CIMIS CSVs in W:/test_cimis/cimis_download, initially for
wind speed and air temperature only.
Unusually, this requires a repository. A registry has been started.
Target a regular, year-sharded repository named atmospheric. Check existing
configuration and the atmospheric station registry; create the repository
configuration and empty receiving directory if needed. Coordinates must
come from the registry.
Inspect the source headers, units, timestamps, and QC columns. Use
per-variable usecols where appropriate, including the timestamp and any
required QC fields. Make unit conversions and QC handling explicit. If
a specialized reader is required, describe the options: put the complexities
in the original script, extend read_ts or make a custom reader as needed for dropbox.
This has never been done before, but there is a config slot for it. The data are
hourly averages stamped at the end of the interval.
Prefer standard variable names from the configured definitions and
mappings. Flag any filename-parser limitations, including underscores,
and discuss the naming fix with me rather than silently inventing aliases.
Start with staging only and provide instructions for mock-repo
reconciliation before production. Validate the complete
recipe-to-annual-shards-to-mock-repo path with small local test data.
Do not run the full ingestion or large diagnostics on W:; give me those
commands to run.
Regular vs. Irregular Data
Whether a series is regular (evenly sampled, with a fixed frequency) or irregular (event/gate data) drives several settings. Getting this wrong is the most common recipe mistake.
Regular data (nearly everything: stage, flow, EC, temperature, …)
metadata.freq: infer(or a literal like15min). The pipeline infers and records the frequency.reader_args.force_regularshould be left at its default (True). Do not setforce_regular: Falsefor regular data — that is an anti-pattern that silently drops the frequency contract. Report and fix gaps instead of reverting to irregular reads.The target repo is a regular repo (
regular: trueindstore_config.yaml); reconciliation applies regular-frequency checks.
Irregular data (gates, structures, event logs)
metadata.freq: none.reader_args.force_regular: Falseis correct here — gate positions are not evenly sampled. This is one of the few placesforce_regular: Falsebelongs.The target repo is an irregular repo (e.g.
structures_formatted); reconciliation skips the frequency-mismatch gate and stitches steps withts_spliceby time rather than forcing a union index.
Rule of thumb: force_regular: False is often right for gates and almost never
right for anything else.
Wildcard Modes
collect.wildcard controls how multiple matching files are handled:
omitted / ``none``: the pattern must match exactly one file. This is the normal case for a single consolidated source file. YAML’s bare
nullkeyword (orNone) is also accepted as a synonym fornone, butnoneis the preferred, self-explanatory spelling in new recipes.``time_shard``: pass the glob straight to the reader for year-sharded / non-overlapping blocked files (
..._2023.csv,..._2024.csv). Lexicographic order is assumed to match chronological order.``time_overlap``: glob, read each file individually, then merge via
merge_method. Use this only when incoming files genuinely overlap in time.
Note
time_overlap is frequently over-used by copy-and-paste from SCADA
recipes. Overlap in time is uncommon, especially for regular data. If your
files do not actually overlap, prefer time_shard (sharded blocks) or a
single-file pattern. Reach for time_overlap only when it is truly needed.
For time_overlap, merge_method selects the combiner (ts_splice or
ts_merge) and merge_args passes options such as
transition: prefer_last / prefer_first.
Filename Templates (Inference Mode)
When file_pattern contains {field} placeholders (e.g.
{source}_{station_id}_{agency_id}_{param}_{syear}_{eyear}.csv), the recipe
enters inference mode: each matched file name is parsed to extract metadata
fields marked infer_from_filename, and each file produces a separate output.
In this mode wildcard must be omitted. Files that cannot be matched to a
registry entry cause a failure unless --omit-unregistered is passed.
Transforms
Transforms run after reading (and after merging, for time_overlap). Each
entry is either a bare string (no args) or a {name, args} mapping. They are
applied in order. Built-ins:
dst_tz(aliasdst_st)Convert local, daylight-saving-aware clock time to a fixed offset. This is the typical need for SCADA/Wonderware exports; major scientific collectors usually already report in a fixed offset and need no conversion. Most commonly used for SCADA gate data, applied before coarsening. See CCF and SCADA recipes for examples. Args:
src_tz(e.g.US/Pacific),target_tz(e.g.Etc/GMT+8).coarsenReduce high-rate irregular data (e.g. gate positions) to an adaptive grid while preserving transitions. Useful for SCADA data (CCF gates, Suisun Marsh) which are stored on DWR SCADA systmes and come out too fine. Args:
grid(output interval, e.g.2min),preserve_vals(values to snap to, e.g.[0.0]),qwidth(quantization width),hyst(hysteresis as a fraction ofqwidth),heartbeat_freq(force a keep-alive sample during idle periods).trim_dataDrop contiguous all-/any-NaN rows from the leading and/or trailing edge. Args:
side(first|last|both),criterion(all_nan|any_nan).add_columnAdd a constant/typed column. Args:
name(required),default,dtype.linearApply an affine transform
value * scale + offsetto the series. Handy for a sign convention (scale: -1.0to flip a stored-negative inflow to a positive-inflow convention) or a simple unit/datum shift. Args:scale(default1.0),offset(default0.0).unit_conversionUse the public vtools
convert_unitscallable directly. Args:in_unitandout_unit. Existing recipes must renamefrom_unittoin_unitandto_unittoout_unit.
Custom transforms can be registered at runtime with
register_transform(name, func).
Transforms are ordinary callables invoked as func(ts, **args) and may
accept a Series or a multicolumn DataFrame. Register public vtools functions
directly, without forwarding wrappers or decorators. Vtools supplies reusable
algorithms; dropbox may also supply local operations. For example:
from dms_datastore.dropbox_data import register_transform
from vtools.functions.unit_conversions import convert_units
register_transform("my_conversion", convert_units)
Vtools-backed dst_tz/dst_st, coarsen, and unit_conversion
use their callable’s native signatures and reject unknown arguments. All
public ts_coarsen keyword arguments are available through coarsen.
Note
Unit conversion (e.g. legacy feet/cfs, or specific conductance) is handled as
part of preparing the data. The legacy DMS convention is feet for stage and
cfs for flow with a sane number of significant digits — hundredths of a foot
and 1 (or 0.1) cfs are enough for all non-DeltaCD purposes. Prefer a modest
float_format (e.g. "%.2f") over emitting spurious precision.
Common Transforms
Most recipes need no transforms at all — a clean, fixed-offset regular feed is
staged as-is. The patterns below cover the cases that do come up. Transforms are
listed under a transforms: key at the recipe-entry level (a sibling of
collect / metadata / output) and are applied top to bottom.
No transform (the common case). Regular scientific data that already reports in a fixed offset needs nothing:
transforms: # omit entirely, or leave empty
Convert SCADA local time to a fixed offset. Wonderware/SCADA exports are usually in local, daylight-saving-aware clock time. Convert once, up front:
transforms:
- name: dst_tz
args:
src_tz: US/Pacific # DST-aware source clock
target_tz: ${target_tz} # e.g. Etc/GMT+8 (PST)
Coarsen high-rate irregular gate data. For adaptive gates recorded at a high
raw rate, reduce to a regular-ish grid while preserving open/close transitions.
Typically applied after dst_tz:
transforms:
- name: dst_tz
args:
src_tz: US/Pacific
target_tz: ${target_tz}
- name: coarsen
args:
grid: 2min # output sampling interval
preserve_vals: [0.0] # snap to fully-closed state + boundaries
qwidth: 0.01 # quantization width (ft)
hyst: 0.5 # re-quantize only when value moves > 0.5*qwidth
heartbeat_freq: 60min # force a keep-alive sample during idle periods
Trim empty edges. Drop leading/trailing all-NaN rows left by feed that padded the record:
transforms:
- name: trim_data
args:
side: both # first | last | both
criterion: all_nan # all_nan | any_nan
Add a constant column. Occasionally useful to stamp a fixed auxiliary column:
transforms:
- name: add_column
args:
name: quality
default: 1
dtype: Int64
Ordering matters. Convert time zones before coarsening (coarsening assumes a
monotonic, correctly-offset index), and note that for time_overlap recipes
transforms run after the files are merged — so a dst_tz transform cannot
repair a fall-back duplicate that exists in a single raw file. In that case
provide a pre-cleaned, DST-adjusted source file instead.
Metadata
Metadata is validated by _check_metadata before staging. String fields
(source, agency, param, station_id, subloc) must be lower
case.
Required
station_id— datastore station id (registry key).source— the processing/collection source of this particular product.agency— the agency that owns the instrument in the water.param— variable (e.g.flow,ec,height).unit— physical unit (ft,cfs/ft^3/s,uS/cm, …).time_zone— a pandas/vtools-compatible fixed offset (e.g.Etc/GMT+8).freq— a literal frequency string,infer(regular), ornone(irregular;null/Noneare accepted synonyms).
Optional
subloc— sublocation (defaultwhen omitted; e.g.upper,bottom,radial).agency_id— the station identifier in the agency’s own record.station_name,processor,time_zone_label, and other descriptive fields.
``agency`` vs. ``agency_id`` — what they mean
For observed data these two answer distinct questions:
agency— “who has an instrument in the water?” (e.g.usgs,dwr).agency_id— “what does that agency call this station in their own data of record?” (their native site number/name).
This is unambiguous for observed feeds. For a processed repo (derived, filled,
or model-input series that merely reuse a station location), agency and
agency_id are often not meaningful and may be better omitted or documented
explicitly. When a processed series blends providers for different variables
(e.g. one station whose flow comes from USGS but whose EC comes from a different
agency), encode the provider per variable (usgs for flow, the EC provider
for EC) so agency stays truthful; do not force a single agency label that is
wrong for some variables. When in doubt, add a short note in the recipe
explaining what the metadata means for the processed product.
Metadata sentinels
Values may be literals or one of these sentinels:
infer_from_filename— parsed from the filename template.registry_lookup— looked up from the station registry bystation_idoragency_id(supportsstation_name,agency,agency_id, and coordinate fields).infer_from_agency_id— special value forstation_id; resolves the station id from the registry by matchingagency_id.
Coordinate Policy
Coordinates are the single responsibility of the station registry (e.g.
station_dbase.csv / structures_registry.csv). They are auto-populated
from the registry during processing; recipe authors must not hard-code them.
The following keys are rejected in recipe metadata (unless supplied via
registry_lookup):
lat,lon,latitude,longitude,agency_lat,agency_lon,x,y,projection_x_coordinate,projection_y_coordinate
To fix missing or wrong coordinates, update the registry CSV — not the recipe.
The registry provides agency_lat/agency_lon (the agency-reported
location, EPSG:4269, kept as reported) and our preferred location as
lat/lon (EPSG:4269, written as latitude/longitude) and
x/y (EPSG:26910, written as
projection_x_coordinate/projection_y_coordinate).
Output and Reconciliation
output controls staging and (optionally) reconciliation:
repo_name— must match a repo defined indstore_config.yaml. The repo’sregular,dtypes, andfloat_formatsettings are honored during reconciliation, and its configured root is the reconcile target unlessrepo_data_diris set.repo_data_dir— optional, for dry runs only. An existing directory that replaces the configured root as the reconcile target. It belongs directly underoutput; placing it underreconcileis an error. See Dry Run and Production.staging.dir— a scratch directory that must exist (its leaf is created if the parent exists). Standardized files are written here.staging.write_args— kwargs towrite_ts_csv(e.g.float_format,chunk_years).reconcile— omit to stop at staging. When present, staged files are merged into the repo:prefer(staged|repo): which side wins on overlapping timestamps.allow_new_series(bool): whether a staged series with no repo namesake may be initialized.inspection(recent_years,p3,p10): sampling policy for re-inspecting older shards (see cautions below).
Reconciliation is content-smart: it hashes the data section (ignoring header
churn such as date_formatted) and only rewrites when values actually differ.
The dropbox passes the exact series it just produced to the reconciler, so
unrelated files sharing the staging directory are not swept into the repo. If a
staged shard’s frequency differs from the repository shard’s, the repository
shard is replaced as a whole rather than merged. The full shard-by-shard rules
are described in Automatic Ingestion.
Dry Run and Production
Reconciliation writes to the repository in place; there is no plan-only mode for
the dropbox. The dry run is done by redirecting the write with
output.repo_data_dir:
output:
repo_name: formatted # still governs naming, regularity, dtypes, float_format
repo_data_dir: ./mock_repo # dry run: reconcile here instead of the configured root
staging:
dir: ./drop_staging
reconcile:
prefer: staged
allow_new_series: false
Dry run. Create the mock directory and copy into it the current repository
files for the series the recipe produces. The directory must exist; a path that
does not exist is interpreted as a repository name and fails. Seeding it matters:
with an empty mock repository, allow_new_series: false raises because there is
no namesake series, and allow_new_series: true only exercises series creation,
not the overlap merge. Run the recipe, then compare the mock files with the
originals (compare_directories or a diff) and inspect the joins.
Production. Remove or comment out repo_data_dir and run again. The target
is then the configured root for repo_name. Staging may not coincide with or
nest inside the target in either mode.
Recommended Workflow: Backfill, then Top Off
A good pattern for a series that has a legacy history plus an ongoing feed is to keep both in one recipe file:
A one-time backfill entry. Load the legacy record (in the right units and a sane precision) into the repo. Use
prefer: stagedso the backfill wins while initializing the series.Mark it ``skip: true`` once it has run. The entry stays in the file as a self-documenting record of how the history was produced, but does not run again.
An ongoing entry in the same file that tops off the series with new data as it arrives.
The dropbox_smscg_scada.yaml recipe (radial_init backfill +
smscg_scada ongoing) is a working example of this pattern.
Common Pitfalls
A few habits tend to spread by copy-and-paste. Watch for them in review:
``wildcard: time_overlap`` where it is not needed. True time overlap is uncommon, especially for regular data. Use
time_shardor a single-file pattern unless files genuinely overlap.``force_regular: False`` on regular data. Correct for gates; wrong for essentially everything else. Fix the underlying gaps instead of dropping the frequency contract.
``allow_new_series: true`` used casually. It permits creating a new parallel series file — which means a misspelled
station_idorparamsilently produces a phantom series next to the real one. Preferfalsefor ongoing feeds into existing series; enable it deliberately only when initializing a genuinely new series.``inspection:`` on unsharded files. The
recent_years/p3/p10sampling only has meaning for time-sharded (_2023.csv) files, where it randomly re-checks older shards to bound nightly workload. On a single unsharded file it does nothing — do not add it reflexively.Spurious precision. Match
float_formatto the measurement (e.g."%.2f"ft,"%.1f"cfs). Do not emit an unsupportable number of significant figures.Coordinates in the recipe. Always fix these in the registry, never in
metadata.
Failure Handling
Each recipe entry is processed independently. If one fails, the error is logged
and processing continues with the next entry. At the end, if any entries failed,
a RuntimeError is raised listing the failed names. Re-run individual failures
with --name <entry>.
Bundled Examples
The bundled recipes in dms_datastore/dropbox_recipes/ are working examples
of decisions made for real ingestion workflows:
dropbox_smscg_scada.yaml— irregular SCADA gatesThe ongoing
smscg_scadaentry selects the three radial gate columns, reads overlapping SCADA exports, converts DST-aware local timestamps to a fixed offset, then coarsens the high-rate record. It usesstructures_formatted,freq: none, andforce_regular: False. The separateradial_initentry records a historical backfill; after it has run successfully,skip: truekeeps that history in the recipe without re-importing it.dropbox_daily.yaml— daily records from filename templatesThe
dailyentry parses station, source, and agency ID from each filename and emits one regular daily series per matched file intodaily_formatted. This is a useful pattern when filenames follow a stable metadata-bearing convention; it uses inference mode rather than a wildcard mode.dropbox_mrz_stage_processed.yaml— a recomputed processed productThe MRZ stage-elevation entry writes a regular, filled product to
processedwith processing provenance. Its producer recomputes the full covered history, soprefer: stagedkeeps overlapping values internally consistent when the result is reconciled.
Before adapting an example, confirm that its station, sublocation, repository, source semantics, and overlap policy match the new data. Similar-looking CSVs do not necessarily represent the same series or have the same update behavior.
Key Functions
dropbox_data/apply_dropbox_workflow— run a recipe.DataCollector— file discovery from patterns.populate_meta/infer_meta— resolve and infer metadata (registry lookups, filename inference)._check_metadata— validate required fields, casing, units, time zone, and coordinates.register_transform— register a custom transform.reconcile_data.update_repo— content-smart reconciliation of staged files into a repository.