Source code for diveplan.dive.formatters.console

"""Plain-text report formatter for terminals and logs."""

from collections.abc import Iterable
from datetime import timedelta

from diveplan.core.dive_segment import DiveSegment
from diveplan.dive.dive_report import DiveReport, ReportRow
from diveplan.dive.formatters import BaseFormatter

__all__ = ["ConsoleFormatter"]

_SCHEDULE_HEADER = "  runtime     action                  duration   gas    kind"


def _minutes(td: timedelta) -> str:
    return f"{td.total_seconds() / 60:6.1f} min"


def _row_line(row: ReportRow) -> str:
    if row.kind == "GAS_SWITCH":
        action = f"switch to {row.gas}"
    elif row.start_depth_m == row.end_depth_m:
        action = f"hold {row.start_depth_m:4.0f} m"
    else:
        action = f"{row.start_depth_m:4.0f} m -> {row.end_depth_m:3.0f} m"
    runtime_start = row.runtime - row.duration
    return (
        f"  {_minutes(runtime_start)}  {action:<22}"
        f"{_minutes(row.duration)}   {row.gas.name:<6} {row.kind}"
    )


[docs] class ConsoleFormatter(BaseFormatter): """Human-readable dive plan table with a summary block.""" NAME = "console"
[docs] @staticmethod def format_schedule(segments: Iterable[DiveSegment]) -> str: """Render any segment sequence (a profile's or an ascent plan) as a runtime table — reusable outside full reports.""" rows: list[ReportRow] = [] elapsed = timedelta(0) for segment in segments: elapsed += segment.duration rows.append( ReportRow( runtime=elapsed, start_depth_m=segment.start_pressure.depth_m, end_depth_m=segment.end_pressure.depth_m, duration=segment.duration, gas=segment.gas, kind=segment.kind.name, ) ) return "\n".join([_SCHEDULE_HEADER, *map(_row_line, rows)])
[docs] def format(self, report: DiveReport) -> str: """Render the full report: header, schedule table, totals block.""" lines = [ f"Dive plan — {report.model_name}", f"runtime {_minutes(report.runtime).strip()}, " f"max depth {report.max_depth.depth_m:.1f} m", f"SAC: bottom {report.sac_bottom:g} L/min, deco {report.sac_deco:g} L/min", ] if report.tts_variations is not None: per_m = report.tts_variations.per_meter.total_seconds() per_min = report.tts_variations.per_minute.total_seconds() lines.append( f"TTS variation: +{per_m / 60:.1f} min/m, +{per_min / 60:.1f} min/min" ) lines.append("") lines.append(_SCHEDULE_HEADER) lines.extend(_row_line(row) for row in report.rows) lines.append("") for gas, litres in report.consumption_l: lines.append(f" {gas.name:<6} consumed : {litres:7.0f} L") lines.append( f" rock bottom @ {report.max_depth.depth_m:.0f} m " f"(SAC x{report.sac_factor:g}) : {report.rock_bottom_l:7.0f} L" ) lines.append(f" CNS {report.cns:.0f} % OTU {report.otus:.0f}") return "\n".join(lines)