Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nextmv/nextmv/_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def _custom_serial(obj: Any) -> str:
If the object type is not supported for serialization.
"""

if isinstance(obj, (datetime.datetime, datetime.date)):
if isinstance(obj, datetime.datetime | datetime.date):
return obj.isoformat()

raise TypeError(f"Type {type(obj)} not serializable")
24 changes: 12 additions & 12 deletions nextmv/nextmv/cli/cloud/acceptance/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
- Multiple metrics as a [magenta]json[/magenta] array in a single --metrics flag.

Each metric must have the following fields:
- [magenta]field[/magenta]: Field of the metric to measure (e.g., "solution.objective").
- [magenta]field[/magenta]: Field of the metric to measure (e.g., "result.custom.unassigned").
- [magenta]metric_type[/magenta]: Type of metric comparison. Allowed values: {enum_values(MetricType)}.
- [magenta]params[/magenta]: Parameters of the metric comparison.
- [magenta]operator[/magenta]: Comparison operator. Allowed values: {enum_values(Comparison)}.
Expand All @@ -71,8 +71,8 @@
[bold][underline]Examples[/underline][/bold]

- Create an acceptance test with a single metric.
$ [dim]METRIC='{{
"field": "solution.objective",
$ [green]METRIC='{{
"field": "result.custom.unassigned",
"metric_type": "direct-comparison",
"params": {{
"operator": "lt",
Expand All @@ -85,8 +85,8 @@
--metrics "$METRIC" --input-set-id input-set-123[/dim]

- Create with multiple metrics by repeating the flag.
$ [dim]METRIC1='{{
"field": "solution.objective",
$ [green]METRIC1='{{
"field": "result.custom.unassigned",
"metric_type": "direct-comparison",
"params": {{
"operator": "lt",
Expand All @@ -95,7 +95,7 @@
"statistic": "mean"
}}'
METRIC2='{{
"field": "statistics.run.duration",
"field": "run.duration",
"metric_type": "direct-comparison",
"params": {{
"operator": "le",
Expand All @@ -110,7 +110,7 @@
- Create with multiple metrics in a single [magenta]json[/magenta] array.
$ [dim]METRICS='[
{{
"field": "solution.objective",
"field": "result.custom.unassigned",
"metric_type": "direct-comparison",
"params": {{
"operator": "lt",
Expand All @@ -119,7 +119,7 @@
"statistic": "mean"
}},
{{
"field": "statistics.run.duration",
"field": "run.duration",
"metric_type": "direct-comparison",
"params": {{
"operator": "le",
Expand All @@ -133,8 +133,8 @@
--metrics "$METRICS" --input-set-id input-set-123[/dim]

- Create an acceptance test and wait for it to complete.
$ [dim]METRIC='{{
"field": "solution.objective",
$ [green]METRIC='{{
"field": "result.custom.unassigned",
"metric_type": "direct-comparison",
"params": {{
"operator": "lt",
Expand All @@ -147,8 +147,8 @@
--metrics "$METRIC" --input-set-id input-set-123 --wait[/dim]

- Create an acceptance test and save the results to a file, waiting for completion.
$ [dim]METRIC='{{
"field": "solution.objective",
$ [green]METRIC='{{
"field": "result.custom.unassigned",
"metric_type": "direct-comparison",
"params": {{
"operator": "lt",
Expand Down
2 changes: 2 additions & 0 deletions nextmv/nextmv/cloud/application/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,8 @@ def __convert_manifest_to_payload(manifest: Manifest) -> dict[str, Any]: # noqa
output_config = multi_config["output_configuration"] = {}
if content.multi_file.output.statistics:
output_config["statistics_path"] = content.multi_file.output.statistics
if content.multi_file.output.metrics:
output_config["metrics_path"] = content.multi_file.output.metrics
if content.multi_file.output.assets:
output_config["assets_path"] = content.multi_file.output.assets
if content.multi_file.output.solutions:
Expand Down
9 changes: 8 additions & 1 deletion nextmv/nextmv/cloud/application/_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@
from nextmv.input import Input, InputFormat
from nextmv.logger import log
from nextmv.options import Options
from nextmv.output import ASSETS_KEY, STATISTICS_KEY, Asset, Output, OutputFormat, Statistics
from nextmv.output import (
ASSETS_KEY,
STATISTICS_KEY,
Asset,
Output,
OutputFormat,
Statistics,
)
from nextmv.polling import DEFAULT_POLLING_OPTIONS, PollingOptions, poll
from nextmv.run import (
ExternalRunResult,
Expand Down
2 changes: 1 addition & 1 deletion nextmv/nextmv/cloud/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def request(
if data is not None:
kwargs["data"] = data
if payload is not None:
if isinstance(payload, (dict, list)):
if isinstance(payload, dict | list):
data = deflated_serialize_json(payload, json_configurations=json_configurations)
kwargs["data"] = data
else:
Expand Down
10 changes: 4 additions & 6 deletions nextmv/nextmv/default_app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@
output = nextmv.Output(
options=options,
solution={"message": message},
statistics=nextmv.Statistics(
result=nextmv.ResultStatistics(
value=1.23,
custom={"message": message},
),
),
metrics={
"value": 1.23,
"custom": {"message": message},
},
assets=assets,
)
nextmv.write(output)
86 changes: 83 additions & 3 deletions nextmv/nextmv/local/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
Function to update run metadata including duration and status.
process_run_logs
Function to process and save run logs.
process_run_metrics
Function to process and save run metrics.
process_run_statistics
Function to process and save run statistics.
process_run_assets
Expand Down Expand Up @@ -57,7 +59,16 @@
)
from nextmv.local.plotly_handler import handle_plotly_visual
from nextmv.manifest import Manifest, ManifestType
from nextmv.output import ASSETS_KEY, OUTPUTS_KEY, SOLUTIONS_KEY, STATISTICS_KEY, Asset, OutputFormat, VisualSchema
from nextmv.output import (
ASSETS_KEY,
METRICS_KEY,
OUTPUTS_KEY,
SOLUTIONS_KEY,
STATISTICS_KEY,
Asset,
OutputFormat,
VisualSchema,
)
from nextmv.status import StatusV2


Expand Down Expand Up @@ -305,7 +316,7 @@ def process_run_output(
) -> None:
"""
Processes the result of the subprocess run. This function is in charge of
handling the run results, including solutions, statistics, logs, assets,
handling the run results, including solutions, statistics, metrics, logs, assets,
and visuals.

Parameters
Expand Down Expand Up @@ -347,6 +358,13 @@ def process_run_output(
result=result,
stdout_output=stdout_output,
)
process_run_metrics(
temp_run_outputs_dir=temp_run_outputs_dir,
outputs_dir=outputs_dir,
stdout_output=stdout_output,
temp_src=temp_src,
manifest=manifest,
)
process_run_statistics(
temp_run_outputs_dir=temp_run_outputs_dir,
outputs_dir=outputs_dir,
Expand Down Expand Up @@ -499,6 +517,65 @@ def process_run_logs(

f.write(std_err)

def process_run_metrics(
temp_run_outputs_dir: str,
outputs_dir: str,
stdout_output: str | dict[str, Any],
temp_src: str,
manifest: Manifest,
) -> None:
"""
Processes the metrics of the run. Checks for an outputs/metrics folder
or custom metrics file location from manifest. If found, copies to run
directory. Otherwise, attempts to extract metrics from stdout.

Parameters
----------
temp_run_outputs_dir : str
The path to the temporary outputs directory.
outputs_dir : str
The path to the outputs directory in the run directory.
stdout_output : Union[str, dict[str, Any]]
The stdout output of the run, either as raw string or parsed dictionary.
temp_src : str
The path to the temporary source directory.
manifest : Manifest
The application manifest containing configuration and custom paths.
"""

metrics_dst = os.path.join(outputs_dir, METRICS_KEY)
os.makedirs(metrics_dst, exist_ok=True)
metrics_file = f"{METRICS_KEY}.json"

# Check for custom location in manifest and override metrics_src if needed.
if (
manifest.configuration is not None
and manifest.configuration.content is not None
and manifest.configuration.content.format == OutputFormat.MULTI_FILE
and manifest.configuration.content.multi_file is not None
):
metrics_src_file = os.path.join(temp_src, manifest.configuration.content.multi_file.output.metrics)

# If the custom metrics file exists, copy it to the metrics destination
if os.path.exists(metrics_src_file) and os.path.isfile(metrics_src_file):
metrics_dst_file = os.path.join(metrics_dst, metrics_file)
shutil.copy2(metrics_src_file, metrics_dst_file)
return

metrics_src = os.path.join(temp_run_outputs_dir, METRICS_KEY)
if os.path.exists(metrics_src) and os.path.isdir(metrics_src):
shutil.copytree(metrics_src, metrics_dst, dirs_exist_ok=True)
return

if not isinstance(stdout_output, dict):
return

if METRICS_KEY not in stdout_output:
return

with open(os.path.join(metrics_dst, metrics_file), "w") as f:
metrics = {METRICS_KEY: stdout_output[METRICS_KEY]}
json.dump(metrics, f, indent=2)

def process_run_statistics(
temp_run_outputs_dir: str,
Expand All @@ -508,6 +585,9 @@ def process_run_statistics(
manifest: Manifest,
) -> None:
"""
!!! warning
`process_run_statistics` is deprecated, use `process_run_metrics` instead.

Processes the statistics of the run. Checks for an outputs/statistics folder
or custom statistics file location from manifest. If found, copies to run
directory. Otherwise, attempts to extract statistics from stdout.
Expand Down Expand Up @@ -848,7 +928,7 @@ def _copy_new_or_modified_files( # noqa: C901
This function identifies files that are either new (not present in the original
source) or have been modified (different content, checksum, or modification time)
compared to the original source. It excludes files that exist in specified
exclusion directories to avoid copying input data, statistics, or assets as
exclusion directories to avoid copying input data, statistics, metrics, or assets as
solution outputs.

Parameters
Expand Down
2 changes: 1 addition & 1 deletion nextmv/nextmv/local/geojson_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def extract_coordinates(coords, all_coords) -> None:
like Polygons and MultiPolygons
"""
if isinstance(coords, list):
if len(coords) == 2 and isinstance(coords[0], (int, float)) and isinstance(coords[1], (int, float)):
if len(coords) == 2 and isinstance(coords[0], int | float) and isinstance(coords[1], int | float):
# This is a coordinate pair [lon, lat]
all_coords.append(coords)
else:
Expand Down
18 changes: 11 additions & 7 deletions nextmv/nextmv/manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,9 @@ class ManifestContentMultiFileOutput(BaseModel):
Parameters
----------
statistics : Optional[str], default=""
The path to the statistics file.
Deprecated: Use `metrics` instead. The path to the statistics file.
metrics : Optional[str], default=""
The path to the metrics file.
assets : Optional[str], default=""
The path to the assets file.
solutions : Optional[str], default=""
Expand All @@ -839,16 +841,18 @@ class ManifestContentMultiFileOutput(BaseModel):
--------
>>> from nextmv import ManifestContentMultiFileOutput
>>> output_config = ManifestContentMultiFileOutput(
... statistics="my-outputs/statistics.json",
... metrics="my-outputs/metrics.json",
... assets="my-outputs/assets.json",
... solutions="my-outputs/solutions/"
... )
>>> output_config.statistics
'my-outputs/statistics.json'
>>> output_config.metrics
'my-outputs/metrics.json'
"""

statistics: str | None = ""
"""The path to the statistics file."""
"""Deprecated: Use `metrics` instead. The path to the statistics file."""
metrics: str | None = ""
"""The path to the metrics file."""
assets: str | None = ""
"""The path to the assets file."""
solutions: str | None = ""
Expand Down Expand Up @@ -878,7 +882,7 @@ class ManifestContentMultiFile(BaseModel):
>>> multi_file_config = ManifestContentMultiFile(
... input=ManifestContentMultiFileInput(path="data/input/"),
... output=ManifestContentMultiFileOutput(
... statistics="my-outputs/statistics.json",
... metrics="my-outputs/metrics.json",
... assets="my-outputs/assets.json",
... solutions="my-outputs/solutions/"
... )
Expand Down Expand Up @@ -919,7 +923,7 @@ class ManifestContent(BaseModel):
... multi_file=ManifestContentMultiFile(
... input=ManifestContentMultiFileInput(path="data/input/"),
... output=ManifestContentMultiFileOutput(
... statistics="my-outputs/statistics.json",
... metrics="my-outputs/metrics.json",
... assets="my-outputs/assets.json",
... solutions="my-outputs/solutions/"
... )
Expand Down
4 changes: 2 additions & 2 deletions nextmv/nextmv/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ class Model:
... return nextmv.Output(
... options=input.options,
... solution=nextroute_output.solutions[0].to_dict(),
... statistics=nextroute_output.statistics.to_dict(),
... metrics=nextroute_output.metrics.to_dict(),
... )
"""

Expand Down Expand Up @@ -234,7 +234,7 @@ def solve(self, input: Input) -> Output:
... return Output(
... options=input.options,
... solution=result,
... statistics={"processing_time": 0.5}
... metrics={"processing_time": 0.5}
... )
"""

Expand Down
Loading
Loading