Skip to content

feat(parquet): variables metadata and download API for parquet outputs - #3367

Draft
sylvlecl wants to merge 29 commits into
devfrom
feat/implement-download-parquet
Draft

feat(parquet): variables metadata and download API for parquet outputs#3367
sylvlecl wants to merge 29 commits into
devfrom
feat/implement-download-parquet

Conversation

@sylvlecl

@sylvlecl sylvlecl commented Aug 20, 2026

Copy link
Copy Markdown
Member

Done
Implementation is considered complete for area data only (values):

  • DB models for variables and areas
  • function for extracting them from file outputs and dump them to database
  • methods for reading them back from database, using a consolidated
    interface IParquetOutputMetadata
  • implementation of iteration over area dataframes, respecting the variables that are
    actually defined for each area
  • implementation of the "download" API on top of that iteration function

To be completed with

  • implementation for other element types: links, thermal clusters ...
    Pay attention: thermal clusters and similar elements will need some special work to re-shape the
    dataframe when writing to parquet (like today in aggregation), and the opposite transformation for
    the download API
  • technical "detail":
    merge the "parquet_output" table I added into the existing output metadata table.
    The creation needs to be slightly changed to get the integer primary key from the DB
  • alembic migration

- DB models for variables and areas
- functions for extracting them from file outputs

to be continued with:
 - some tests
 - implement the download API for just areas for now
 - implement other element types

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
from functools import cached_property
from pathlib import Path
from typing import Callable, Generic, Literal, Sequence, TypeAlias, TypeVar
from typing import Callable, Generic, Iterable, Literal, Sequence, TypeAlias, TypeVar
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
raise OutputSubFolderNotFound(output_dir.name, "economy|adequacy")


class FileOutput:

@sylvlecl sylvlecl Aug 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

helper class to make output directories analyze easier, could be re-used in other places to simplify the code (in particular iteration module of filestudy outputs)


__tablename__ = "parquet_output"

id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as for study_data_id, the idea here is to introduce a technical ID for outputs, separate from study_id / output_id

This will be beneficial in the future for:

  • more compact foreign keys in other tables
  • possibility to rename the output if the user wants it

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
mc_ind_vars: Mapped[list[int]] = mapped_column(Columns)


# TODO: add tables for other element types: links, thermal clusters, etc

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Easier to focus only on areas for now, but others will need to follow of course

Comment thread antarest/output/storage/v2/storage.py Outdated

simulation_range = _extract_simulation_range(dir_path)

# TODO: first, extract variables metadata to database

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we'll need to call the new functions from variables_parsing, and provide the variables index to the parquet creation function so that it uses the column that have been defined there



@dataclass(frozen=True)
class OutputParsingResult:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may not need that class if we dump to database the "partial" result one after the other

yield parse_output_file(data_file, start_col)


def _extract_areas(

@sylvlecl sylvlecl Aug 21, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is completely to be done:

now we will need to take care of writing the columns in the order that has been defined at variables parsing time, so that the parquet file is consistent with the variable definitions we have stored in database.

It may involve to re-write most of the code in this file unfortunately.

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Comment thread antarest/output/storage/v2/download.py Fixed
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
self, study_id: str, output_id: str, data_selection: StudyDownloadDTO
) -> MatrixAggregationResultDTO:
raise NotImplementedError()
metadata = self._require_metadata(study_id, output_id)
from antarest.study.model import MatrixFrequency


def _parquet_file_name(element_type: ElementType, frequency: MatrixFrequency) -> str:
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
def get_mc_all_file(
self,
file_type: MCAllAreasQueryFile | MCAllLinksQueryFile,
area_id: str,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be renamed element_id if re-used for links

ScenarioAggregation: TypeAlias = Literal["mc-ind", "mc-all"]


class IntList(types.TypeDecorator[list[int]]):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I introduce a sqlalchemy custom type to encapsulate transformation of list of ints to comma separated list

statistic_type: Mapped[str | None]


class DbParquetArea(Base):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important:
this is the source of truth for what variables an area contain

mc_years: Mapped[list[int]] = mapped_column(IntList)


class DbParquetVariable(Base):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important:
this is the source of truth for what each column of parquet files represent.

the column names of parquet files should not be relied upon

MC_IND_AREA_COL_OFFSET = len(MC_IND_AREA_INDEX)


def iterate_areas_df(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function contains the complexity of retrieving only the right columns for the right areas

variables: Sequence[int]


class IParquetOutputMetadata(ABC):

@sylvlecl sylvlecl Aug 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is aimed at being the central object used for retrieving information about the output.
Retrieval of other metadata could be moved here too.

def get_variables(
self, aggregation: ScenarioAggregation, element_type: ElementType
) -> Sequence[VariableDescription]:
return self.variables_index.get_variables(aggregation, element_type)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assumption is that retrieving all variable definitions is OK for any use case.
Could be refactored if it becomes a problem, but it looks OK.

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
raise NotImplementedError(f"Not yet implemented: {metadata.file_type}")


def extract_areas_refacto(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be renamed later, of course :)

"""
Creates parquet files in target_dir in consistence with columns that have been defined in the metadata object.
"""
extract_areas_refacto(metadata, file_output, target_dir)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other element types to be added

data: pl.DataFrame


class ParquetOutputWriter:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That class plays an important part too:

it allows to append to the parquet file dataframes that only have a subset of the desired columns, and in any order.

self.writer.close()

def _create_schema(self) -> pa.Schema:
return pa.schema(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We now create explicitly the arrow schema, based on the columns definition.

output = FileOutput(output_dir)
parsing_result = parse_output_variables(output)

# Notes: weird stuff in input data: CO2 emissions in MWh, and different naming for thermal production groups

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For information, this has been fixed since on antares-simulator side

Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant