Skip to content

Getting started — build a minimal configuration

This tutorial builds a small, complete configuration from nothing: a single gauge whose observed discharge is imported from a CSV file, made browsable in the FEWS Explorer, and run from a workflow. It’s the FEWS equivalent of “hello, world” — deliberately tiny, but every piece is real and each one maps to a hop in the data-flow model.

One observed time series, all the way through the pipeline:

observations.csv IdMap Import module Filter
(external names) ──▶ (rename to ──▶ (writes internal ──▶ (makes it
FEWS names) time series) browsable)
GAUGE-A RiverGauge_01 "Observed
discharge Q.obs Discharge"
└──────────── hop ② ────────────┘ └─ hop ③ ─┘ └─ hop ⑤ ─┘

By the end you’ll have this on disk:

  • DirectoryMyRegion/
    • DirectoryConfig/
      • DirectoryRegionConfigFiles/
        • Locations 1.00 default.xml
        • Parameters 1.00 default.xml
        • Filters 1.00 default.xml
      • DirectoryIdMapFiles/
        • IdImportObservations 1.00 default.xml
      • DirectoryModuleConfigFiles/
        • ImportObservations 1.00 default.xml
      • DirectoryWorkflowFiles/
        • ImportObserved 1.00 default.xml
    • DirectoryImport/
      • observations.csv
  1. Make the folders. You don’t need every folder FEWS supports — only the ones this tutorial writes into.

    Terminal window
    mkdir -p MyRegion/Config/RegionConfigFiles
    mkdir -p MyRegion/Config/IdMapFiles
    mkdir -p MyRegion/Config/ModuleConfigFiles
    mkdir -p MyRegion/Config/WorkflowFiles
    mkdir -p MyRegion/Import

    This is the configuration directory in miniature: the RegionConfigFiles “what exists” layer, the ModuleConfigFiles + WorkflowFiles “what to do” layer, and an Import folder for incoming data.

  2. FEWS can’t store a value until it knows the place it belongs to. Declare one gauge. geoDatum is required; it tells FEWS what the x/y coordinates mean.

    Config/RegionConfigFiles/Locations 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <locations xmlns="http://www.wldelft.nl/fews" version="1.1">
    <geoDatum>WGS 1984</geoDatum>
    <location id="RiverGauge_01" name="River Gauge at Town Bridge">
    <shortName>TownBr</shortName>
    <x>4.895</x>
    <y>52.370</y>
    <z>2.0</z>
    </location>
    </locations>

    RiverGauge_01 is the internal location id — the name FEWS uses everywhere from here on. Full element list: locations.xsd.

  3. Now the quantity. Parameters live in groups that carry the shared properties (type, unit, resolution); individual parameters sit inside.

    Config/RegionConfigFiles/Parameters 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <parameters xmlns="http://www.wldelft.nl/fews" version="1.0">
    <parameterGroups>
    <parameterGroup id="Q">
    <parameterType>instantaneous</parameterType>
    <unit>m3/s</unit>
    <valueResolution>0.001</valueResolution>
    <usesDatum>false</usesDatum>
    <parameter id="Q.obs">
    <shortName>Q.obs</shortName>
    <description>Observed discharge</description>
    </parameter>
    </parameterGroup>
    </parameterGroups>
    </parameters>

    Q.obs is the internal parameter id. instantaneous means each value is a snapshot in time (not a total over the interval — that would be accumulative). Full element list: parameters.xsd.

  4. Add the input data — the “outside world”

    Section titled “Add the input data — the “outside world””

    Here’s the raw data as an external system might hand it to you. Note it uses its own names — GAUGE-A, discharge — not FEWS’s.

    Import/observations.csv
    location,parameter,datetime,value
    GAUGE-A,discharge,2026-07-27 00:00:00,12.4
    GAUGE-A,discharge,2026-07-27 01:00:00,12.9
    GAUGE-A,discharge,2026-07-27 02:00:00,13.6
  5. Map the external names to FEWS names — hop ②

    Section titled “Map the external names to FEWS names — hop ②”

    This is the translation table. It says: when you see GAUGE-A / discharge in the incoming file, that is RiverGauge_01 / Q.obs internally.

    Config/IdMapFiles/IdImportObservations 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <idMap xmlns="http://www.wldelft.nl/fews" version="1.1">
    <map internalLocation="RiverGauge_01" internalParameter="Q.obs"
    externalLocation="GAUGE-A" externalParameter="discharge"/>
    </idMap>
  6. The import module ties it together: read files from ../Import, apply the ID map, and write the result as an internal time series. The timeSeriesSet describes the internal series to create — so it uses the internal ids (Q.obs, RiverGauge_01).

    Config/ModuleConfigFiles/ImportObservations 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <timeSeriesImportRun xmlns="http://www.wldelft.nl/fews">
    <import>
    <general>
    <importType>generalCsv</importType>
    <folder>$IMPORT_FOLDER$/../Import</folder>
    <fileNamePatternFilter>*.csv</fileNamePatternFilter>
    <idMapId>IdImportObservations</idMapId>
    <importTimeZone>
    <timeZoneName>GMT</timeZoneName>
    </importTimeZone>
    </general>
    <timeSeriesSet>
    <moduleInstanceId>ImportObservations</moduleInstanceId>
    <valueType>scalar</valueType>
    <parameterId>Q.obs</parameterId>
    <locationId>RiverGauge_01</locationId>
    <timeSeriesType>external historical</timeSeriesType>
    <timeStep unit="hour" multiplier="1"/>
    <readWriteMode>add originals</readWriteMode>
    </timeSeriesSet>
    </import>
    </timeSeriesImportRun>

    idMapId points at the map from the previous step (base name, no version). external historical is the time series type for observed data that arrived from outside.

  7. Data is now in the store, but nobody can see it yet. A filter adds an entry to the browsable tree in the FEWS Explorer; clicking it shows the series on the default chart. The timeSeriesSet here is a query — it must match the header the import wrote, especially moduleInstanceId.

    Config/RegionConfigFiles/Filters 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <filters xmlns="http://www.wldelft.nl/fews" version="1.1">
    <filter id="observations" name="Observed Discharge">
    <timeSeriesSet>
    <moduleInstanceId>ImportObservations</moduleInstanceId>
    <valueType>scalar</valueType>
    <parameterId>Q.obs</parameterId>
    <locationId>RiverGauge_01</locationId>
    <timeSeriesType>external historical</timeSeriesType>
    <timeStep unit="hour" multiplier="1"/>
    <readWriteMode>read only</readWriteMode>
    </timeSeriesSet>
    </filter>
    </filters>
  8. A workflow is the ordered recipe of what to run. Right now it has one step: the import. Later you’d add transformation and model activities after it.

    Config/WorkflowFiles/ImportObserved 1.00 default.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <workflow xmlns="http://www.wldelft.nl/fews" version="1.1">
    <activity>
    <runIndependent>true</runIndependent>
    <moduleInstanceId>ImportObservations</moduleInstanceId>
    </activity>
    </workflow>

    runIndependent true means the workflow keeps going even if this activity fails — useful once there are several activities. To add a second step, you’d just add another <activity> with its own moduleInstanceId, in order.

Trace it back against the five hops:

HopIn this tutorial
① ImportImportObservations reads observations.csv
② ID mapIdImportObservations renames GAUGE-A/dischargeRiverGauge_01/Q.obs
③ StoreThe series is written with a header: loc RiverGauge_01, param Q.obs, module ImportObservations, hourly, external historical
⑤ DisplayThe Observed Discharge filter surfaces it in the Explorer

Hop ④ (processing/models) is the natural next thing to add — an activity in the workflow that reads Q.obs and writes a derived or simulated series.

When it doesn’t work — the usual suspects

Section titled “When it doesn’t work — the usual suspects”

Almost every first-config problem is one of these:

  • Data imports but doesn’t display. The filter’s timeSeriesSet doesn’t match the import’s header — usually a different moduleInstanceId or timeStep. Line the two up field by field.
  • Nothing imports at all. An idMap mismatch — an external name in the CSV doesn’t exactly match a map entry (case and punctuation count).
  • A change has no effect. A higher-versioned copy of the file is winning. Check for a sibling like Locations 1.01 default.xml. See how FEWS finds a file.
  • An id can’t be resolved. You referenced a locationId or parameterId that isn’t declared in RegionConfigFiles. Declare the noun first.

Layer in the next piece

Add a transformation that derives a new series from Q.obs, then add it as a second activity in the workflow. Or wire up a real model with the General Adapter. Each new capability is just another module + another activity — the shape you already know.

The task guides go deep on each file type you touched here, and every reference page links back to the underlying schema.