# /// script # requires-python = ">=3.12" # dependencies = [ # "matplotlib>=3.10,<4", # "polars>=1.32,<2", # ] # [tool.uv] # exclude-newer = "2026-08-07T00:00:00Z" # /// """Recompute and visualize Northline's straight-line depreciation schedule. Run the downloaded file directly with uv; do not insert ``python``: uv run straight-line-depreciation-year-one.py Change the command-line inputs to test a different estimate set. Add ``--save schedule.svg`` to retain the chart. """ from __future__ import annotations import argparse import io from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt # noqa: E402 import polars as pl # noqa: E402 def build_schedule(cost: float, residual_value: float, useful_life: int) -> pl.DataFrame: """Return one row per period, including the unallocated opening row.""" if not (cost > 0): raise ValueError("cost must be positive") if not (0 <= residual_value <= cost): raise ValueError("residual value must be between zero and cost") if useful_life <= 0: raise ValueError("useful life must be a positive whole number") depreciable_amount = cost - residual_value depreciation_per_period = depreciable_amount / useful_life periods = list(range(useful_life + 1)) accumulated = [depreciation_per_period * period for period in periods] return pl.DataFrame( { "end_of_period": periods, "current_depreciation": [0.0, *([depreciation_per_period] * useful_life)], "accumulated_depreciation": accumulated, "carrying_amount": [cost - amount for amount in accumulated], } ) def verify_schedule( schedule: pl.DataFrame, *, cost: float, residual_value: float, useful_life: int, ) -> None: """Fail if the schedule does not reconcile to its declared inputs.""" if schedule.height != useful_life + 1: raise AssertionError("schedule must include opening plus every useful-life period") if schedule["carrying_amount"][0] != cost: raise AssertionError("opening carrying amount must equal cost") if abs(schedule["carrying_amount"][-1] - residual_value) > 1e-9: raise AssertionError("ending carrying amount must equal residual value") allocated = schedule["current_depreciation"].sum() if abs(allocated - (cost - residual_value)) > 1e-9: raise AssertionError("total depreciation must equal depreciable amount") def save_chart(schedule: pl.DataFrame, destination: Path) -> None: """Write a deterministic, print-safe SVG or raster chart.""" matplotlib.rcParams["svg.hashsalt"] = "accounting-courses" figure, axis = plt.subplots(figsize=(8.4, 4.8), layout="constrained") years = schedule["end_of_period"].to_list() axis.plot( years, schedule["carrying_amount"].to_list(), marker="o", linewidth=2.5, color="#315f59", label="Carrying amount", ) axis.plot( years, schedule["accumulated_depreciation"].to_list(), marker="s", linewidth=2.0, color="#9a6b1f", label="Accumulated depreciation", ) axis.set( title="Northline equipment: straight-line schedule", xlabel="End of year", ylabel="US dollars", xticks=years, ) axis.grid(axis="y", color="#d9d7d0", linewidth=0.8) axis.legend(frameon=False) axis.spines[["top", "right"]].set_visible(False) metadata = {"Creator": "Accounting Courses", "Date": "2026-08-07"} if destination.suffix.lower() == ".svg": output = io.StringIO() figure.savefig(output, format="svg", dpi=160, metadata=metadata) destination.write_text( "\n".join(line.rstrip() for line in output.getvalue().splitlines()) + "\n", encoding="utf-8", ) else: figure.savefig(destination, dpi=160, metadata=metadata) plt.close(figure) def arguments() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--cost", type=float, default=36_000) parser.add_argument("--residual-value", type=float, default=6_000) parser.add_argument("--useful-life", type=int, default=5) parser.add_argument("--save", type=Path, help="optional .svg or .png chart path") return parser.parse_args() def main() -> int: args = arguments() schedule = build_schedule(args.cost, args.residual_value, args.useful_life) verify_schedule( schedule, cost=args.cost, residual_value=args.residual_value, useful_life=args.useful_life, ) # Display financial amounts with thousands separators and two decimals. # The DataFrame retains its original numeric precision. with pl.Config(thousands_separator=",", float_precision=2): print(schedule) print(f"\nDepreciable amount: {args.cost - args.residual_value:,.2f}") print(f"Depreciation per period: {(args.cost - args.residual_value) / args.useful_life:,.2f}") if args.save: args.save.parent.mkdir(parents=True, exist_ok=True) save_chart(schedule, args.save) print(f"Saved chart: {args.save}") return 0 if __name__ == "__main__": raise SystemExit(main())