Skip to main content
This page describes how CSV data is loaded into an Atoti Enterprise Risk Suite solution: how a solution ships its default loading, how a customer application overrides or extends it, and how a load is triggered and sourced from a local filesystem or a cloud bucket (AWS S3, Azure, GCP).

How does data loading work?

Each solution ships its default channels and CSV topics. These are present as soon as the solution’s starter is on the classpath. However, no data loads until the application requests a load of the topics to load. See How does an application request a load?. A customer application customizes or extends loading with its own bean of type IDataLoadConfigurer, from com.activeviam.source.common.api.foundry.config. This customer bean runs after the solution’s default loading, against the same configurator instance. It can fetch a channel or topic the solution already declared and change it. It can also declare new channels and topics. A multi-solution application runs more than one solution in the same process. Each co-deployed solution’s default channels and topics are jointly shipped and available for loading. Two independent aspects of loading are configured separately:

What declares the channels and topics?

A channel is source-agnostic and model-bound. It targets a datastore table and selects which fields it fills. It can also attach a calculator or a custom tuple publisher. A topic is the source: for CSV, it can be a file path or cloud location, plus a file pattern. It also carries the parsers that map columns to fields. A topic is attached to one or more channels by name. Channels and topics are independent. A topic can feed several channels, and a channel can be fed by several topics. Their names are fixed strings. A cube publishes these names through two dedicated beans, each with one named accessor per channel or topic, the way the cube’s database model carries one per table. Both include the channels and topics the cube shares with other cubes, named as those cubes name them.
  • I[Cube name]CubeChannels (e.g. IVaRCubeChannels) returns the channel names, plus the names of the post-load triggers the cube declares.
  • I[Cube name]CubeTopics (e.g. IVaRCubeTopics) returns the topic names, plus the aliases grouping them (see How does an application request a load?).
So topics.markToFuture() is the name of the topic reading the mark-to-future files, and channels.markToFuture() the name of the channel filling the mark-to-future table. A customer overrides or extends loading by referring to these names rather than writing them out, the same way application code refers to levels, measures, and tables by name. The following example shows what a customer’s IDataLoadConfigurer looks like when it declares a new channel and one CSV topic for a Positions table:
  • addChannel creates a channel targeting the given table. attachChannel attaches a topic to a channel by name. parsersFromTable registers parsers driven by the target table’s field types, so plain, header-based files need no manual column mapping.
  • Each load request targets one as-of date. When base-path contains the #{AS_OF_DATE} placeholder, as it does in a generated application, the date is substituted into it, and the topic’s date-agnostic file pattern (positions/*.csv) resolves under that date’s folder. Without the placeholder, every request reads the files directly under base-path, whatever the date.
  • A cloud topic uses the same code, but with IAwsCsvTopicConfigurator (or the Azure/GCP equivalent) and a .bucket(...) call. A solution declares one mutually exclusive bean per topic-type value it ships topics for, plus one declaring its channels alone for the values it does not, so your own topics for those still have channels to attach to.
See How is the data source configured? for the full base-path, topic-type, and cloud property reference. See What happens when solutions share a table? for how additional channels and topics interact with the tables a solution already loads. A customer application overrides loading with its own IDataLoadConfigurer bean. The following example fetches a topic the solution’s default loading already declared and repoints it at a different file pattern. It names the topic through the cube’s topics bean (IPfeCubeTopics here) rather than as a literal, so an override cannot drift from what the solution declared:
The same configurator.topics().topic(name, ICsvTopicConfigurator.class) call also works to change the path, add parsers, or attach the topic to additional channels. Use ICsvTopicConfigurator rather than a source-specific type such as ILocalCsvTopicConfigurator: the lookup would fail when the topic is declared against a different one. Naming the shared interface keeps the override working regardless of the source topic-type selects.
Note: an application declares at most one IDataLoadConfigurer bean. It is injected as a single bean by type, so a second bean of that type fails startup with NoUniqueBeanDefinitionException. The two examples above show two things that one bean can do: declare a new channel and topic, and fetch and repoint an existing one. They are not two separate beans.
Tip: name each topic after the table it loads. The data-loading API builds an implicit topic for every requested name. A name that does not match a datastore table still works. It produces a harmless, noisy Implicit topic for table '…' cannot be built warning at load time.

What happens when solutions share a table?

Several solutions can run in one application, and some of them share data model components: the counterparty and trade attributes tables are common to the risk cubes, and the netting-set tables are specific to Counterparty Credit Risk (Enterprise Risk). Each solution’s shipped loading declares what it needs against those tables, without knowing which other solutions are deployed. Identical declarations are reused rather than duplicated. Whichever solution is configured first declares the channel and the topic, and the ones after it reuse them. A file feeding a table that several solutions use is therefore read by one topic, into that one table, through one channel each of their cubes reads from. Nothing has to be configured for this: a solution deployed on its own is always the one declaring, so its loading is unchanged either way. A customer application declares its own channel names, so it declares them directly. Which topic it needs depends on where its rows go. A channel filling a table of its own can attach to the topic the solution already declared, leaving the file read once and its rows delivered to both tables. A channel filling a table the solution already loads needs a topic of its own over the same files, because a topic only fans out to channels on different tables. Both cases refer to something the solution named: a topic to attach to, or the files it reads. Take those from the solution’s topics and file-patterns beans rather than writing them out, so they cannot drift from what the solution actually declares. The example below is Counterparty Credit Risk (Enterprise Risk); every solution publishes the equivalent bean:
Declaring channels and topics is not enough on its own. A load must be requested. The suite provides the machinery for both ways below.
Note: requesting a load belongs to the application rather than to the starter. A starter carries the cube, its channels, and its topics; the application decides when they are read. An application that opts out starts normally and serves its cube, but loads nothing, because no load is ever requested.
Each solution also declares two aliases over the topics it ships, and publishes their names as loadAlias() and unloadAlias() on its topics bean (I[Cube name]CubeTopics). An application names aliases rather than individual topics, so adding a topic to a solution does not change any application’s configuration. The unload alias covers the topics scoped on the as-of date. It holds the same topics as the load alias unless some are reloaded wholesale, such as market-data curves, which stay loaded when an as-of date is unloaded.

How does an application request a load?

Import the as-of-date data-load configuration:
That single import gives the application three things: the startup load of the configured dates, the REST endpoints described below, and the load and unload events any post-load action listens to. One import covers every cube the application deploys, however many starters it depends on.

What does a load cover?

atoti.solution.data-load.as-of-date.topics names what each request reads. An entry is a topic name or an alias over several:
Every name resolves into one request per date, so a table two cubes share is read once rather than once per cube. A name nothing declares is skipped with a warning, in topics and in unload-topics alike, rather than failing the request. Add each solution’s unload alias to unload-topics. Leaving it unset unloads the names in topics, which includes data that is reloaded wholesale and declares no unload condition, such as a market-data curve, so the unload fails with Creating implicit unload topics is not allowed. An empty list unloads nothing at all.

How is data loaded on startup?

Once the database is available, the configured as-of dates are loaded, one scoped request per date, in chronological order: For example:
The dates are application-wide: one request per date covers every cube deployed, rather than one set of dates per cube. See Configuration properties for the full property reference.

How is data loaded at runtime?

The same import exposes a REST controller mapped at /activeviam/as-of-date/rest/v1, which loads or unloads a single as-of date at runtime:
  • POST /activeviam/as-of-date/rest/v1/{asOfDate} loads data for the given as-of date. The asOfDate path variable must be in yyyy-MM-dd format.
  • DELETE /activeviam/as-of-date/rest/v1/{asOfDate} unloads the data previously loaded for that as-of date. It removes that date’s rows from every table scoped on the as-of date. A table not scoped on the as-of date, such as a market-data curve table, keeps its rows. Each solution’s data-loading page lists which tables lose rows and which keep them.
Both operations cover every configured topic in a single scoped request, across every cube the application deploys. A date that resolves to no configured topic answers normally and loads nothing. Data a solution derives from what it loads, such as flattened hierarchies and vectorized metrics, commits in the same transaction as the rows it comes from. A runtime load rebuilds it for that as-of date and an unload removes it, so it stays consistent with its sources, not only after the startup load. The in-memory caches a solution builds from a table are refreshed the same way, with no separate step to run. Set atoti.solution.data-load.as-of-date.controller.enabled to false to leave the endpoints out, for a deployment that must not accept load or unload requests over HTTP. The startup load is unaffected.

How is the data source configured?

Where CSV files are read from is controlled by the properties under atoti.solution.data-load.csv. This is a single, shared contract every solution consumes instead of declaring its own copy:

Local filesystem (default)

The default needs no extra setup beyond pointing at the data:
So a load scoped to 2026-01-09 reads from, for example, /path/to/data/2026-01-09/positions/*.csv.

AWS S3

Loading from S3 requires three additional pieces of setup.
  1. Add the AWS cloud-source dependency to the application module:
  2. Point the configuration at the bucket and create the S3 client:
  3. Provide AWS credentials and a region. Both are resolved through the standard AWS SDK chain (environment variables, ~/.aws/credentials and ~/.aws/config, instance profiles, …).
Note: dlc.create-default-cloud-client.aws: true is mandatory. The AWS source connector only registers when an S3Client bean is present. Without it, the application starts but silently loads nothing: the topics are declared but never fetched.
Note: the default S3Client is built without an explicit region, so a region must be resolvable from the SDK chain (for example the AWS_REGION environment variable). If none is found, the application fails to start with Unable to load region from any of the providers.
Azure and GCP follow the same pattern: set topic-type to AZURE/GCP, add the matching cloud-source-azure/cloud-source-gcp dependency, and enable dlc.create-default-cloud-client.azure/.gcp.

What file formats are expected?

Each solution loads data from CSV files, organized into dated folders selected by the #{AS_OF_DATE} placeholder in base-path. Each file pattern maps to a target datastore table via a glob pattern supported by the data-loading API. For common file format definitions, see the input file formats reference.

How must CSV files be named?

A CSV file loads into a topic when its name matches <Name>.csv or <Name>_<suffix>.csv. <Name> is the topic’s file name, for example TradeAttributes or MTF. The input file formats reference pages list the <Name> of every topic. The underscore separates the required name from an optional, free-form suffix. A suffix lets one table’s data be split across several files. For example, TradeAttributes.csv and TradeAttributes_PreTrade.csv can coexist, or MTF_1.csv and MTF_2.csv. Each currency pair can get its own FXScenarios_<pair>.csv file. A name that continues past <Name> without an underscore does not load. Neither does a name that merely ends with <Name>. This keeps one topic from picking up another topic’s files. The following examples use the TradeAttributes topic: The match applies at any depth below the as-of-date folder resolved from base-path (see How is the data source configured?). A file can sit directly in that folder or in any folder below it. For example, Trades/TradeAttributes.csv and Trades/Current/MTF_1.csv both match. Folder names play no part in the match. A file whose name matches no topic is ignored rather than rejected. Every topic a solution ships uses the glob glob:{,**/}<Name>{,_*}.csv. A topic overridden through a custom IDataLoadConfigurer keeps that pattern unless the override sets another one, and a topic the application declares itself uses the pattern it sets. See What declares the channels and topics? for how a customer application changes a topic’s file pattern.