feat(parquet): variables metadata and download API for parquet outputs - #3367
feat(parquet): variables metadata and download API for parquet outputs#3367sylvlecl wants to merge 29 commits into
Conversation
- 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: |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Easier to focus only on areas for now, but others will need to follow of course
|
|
||
| simulation_range = _extract_simulation_range(dir_path) | ||
|
|
||
| # TODO: first, extract variables metadata to database |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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>
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, |
There was a problem hiding this comment.
should be renamed element_id if re-used for links
| ScenarioAggregation: TypeAlias = Literal["mc-ind", "mc-all"] | ||
|
|
||
|
|
||
| class IntList(types.TypeDecorator[list[int]]): |
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
Important:
this is the source of truth for what variables an area contain
| mc_years: Mapped[list[int]] = mapped_column(IntList) | ||
|
|
||
|
|
||
| class DbParquetVariable(Base): |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
This function contains the complexity of retrieving only the right columns for the right areas
| variables: Sequence[int] | ||
|
|
||
|
|
||
| class IParquetOutputMetadata(ABC): |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Other element types to be added
| data: pl.DataFrame | ||
|
|
||
|
|
||
| class ParquetOutputWriter: |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
For information, this has been fixed since on antares-simulator side
Signed-off-by: Sylvain Leclerc <sylvain.leclerc@rte-france.com>
Done
Implementation is considered complete for area data only (values):
interface
IParquetOutputMetadataactually defined for each area
To be completed with
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
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