🎉 Initial commit: project scaffold, agent modes, and gnarly_csv example
- Add .roomodes with four custom Roo agents:
• excalidraw-to-python – diagram → typed Python skeleton
• python-coder – skeleton → production implementation (design patterns)
• tester – pytest suite writer & runner
• orchestrator – coordinates the full excalidraw→code→test→execute pipeline
- Add src/csv_grok.py and tests/test_csv_grok.py (CSV diff utility)
- Add examples/gnarly_csv/ with gnarly_csv_delta.py and sample data (rev_a/rev_b)
- Add drawings/ with design.excalidraw and gnarly_csv_files.excalidraw
- Add docs/excalidraw-to-python-agent.md
- Add requirements.txt and .gitignore
This commit is contained in:
297
examples/gnarly_csv/gnarly_csv_delta.py
Normal file
297
examples/gnarly_csv/gnarly_csv_delta.py
Normal file
@ -0,0 +1,297 @@
|
||||
"""
|
||||
gnarly_csv_delta – Silicon subsystem power-delta visualiser
|
||||
============================================================
|
||||
Derived from ``drawings/gnarly_csv_files.excalidraw``.
|
||||
|
||||
Diagram summary
|
||||
---------------
|
||||
Two CSV files representing two chip revisions (rev_A, rev_B) share an
|
||||
identical schema:
|
||||
|
||||
Columns 1–4 (measured raw data)
|
||||
─────────────────────────────────
|
||||
meas_current_mA – rail current measurement
|
||||
meas_voltage_mV – rail voltage measurement
|
||||
meas_power_mW – directly measured power
|
||||
meas_temp_C – junction temperature
|
||||
|
||||
Columns 5–9 (derived complexity hierarchy, low → high)
|
||||
────────────────────────────────────────────────────────
|
||||
col 5 rail_power_mW – power at the rail for a given subsystem
|
||||
col 6 subsys_power_mW – subsystem-level aggregated power
|
||||
col 7 cluster_power_mW – cluster-level aggregated power
|
||||
col 8 soc_power_mW – full SoC power
|
||||
col 9 battery_power_mW – total battery draw (highest complexity)
|
||||
|
||||
Rows – silicon subsystem parts (CPU, GPU, DRAM, NPU, ISP, …)
|
||||
|
||||
Goal
|
||||
----
|
||||
Load both CSV files, compute the **delta** (rev_B − rev_A) for columns 5–9
|
||||
across every subsystem row, then render a grouped bar chart via matplotlib.
|
||||
|
||||
Data-flow (as drawn):
|
||||
rev_a.csv ──┐
|
||||
▼
|
||||
load & align ──► compute_deltas ──► plot_bar_chart
|
||||
▲
|
||||
rev_b.csv ──┘
|
||||
|
||||
CLI usage:
|
||||
python gnarly_csv_delta.py data/rev_a.csv data/rev_b.csv \\
|
||||
[--output delta_chart.png] [--show]
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Constants – the 9-column schema from the diagram
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
#: Columns 1–4: raw measured data (not used for delta visualisation).
|
||||
MEASURED_COLS: list[str] = [
|
||||
"meas_current_mA",
|
||||
"meas_voltage_mV",
|
||||
"meas_power_mW",
|
||||
"meas_temp_C",
|
||||
]
|
||||
|
||||
#: Columns 5–9: derived complexity hierarchy – these are the delta targets.
|
||||
DERIVED_COLS: list[str] = [
|
||||
"rail_power_mW", # col 5
|
||||
"subsys_power_mW", # col 6
|
||||
"cluster_power_mW", # col 7
|
||||
"soc_power_mW", # col 8
|
||||
"battery_power_mW", # col 9
|
||||
]
|
||||
|
||||
#: The index column that identifies each subsystem row.
|
||||
SUBSYSTEM_COL: str = "subsystem"
|
||||
|
||||
#: All expected columns in the CSV files.
|
||||
EXPECTED_COLS: list[str] = [SUBSYSTEM_COL] + MEASURED_COLS + DERIVED_COLS
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Data structures
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class RevisionBundle:
|
||||
"""A single chip-revision CSV loaded into memory."""
|
||||
|
||||
path: Path
|
||||
label: str # e.g. "rev_A"
|
||||
data: pd.DataFrame # indexed by SUBSYSTEM_COL after load
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeltaResult:
|
||||
"""Holds the per-subsystem, per-derived-column deltas (rev_B − rev_A)."""
|
||||
|
||||
delta_df: pd.DataFrame # shape: (n_subsystems, len(DERIVED_COLS))
|
||||
rev_a_label: str
|
||||
rev_b_label: str
|
||||
derived_cols: list[str] = field(default_factory=lambda: list(DERIVED_COLS))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pipeline steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def load_revision(path: Path, label: str) -> RevisionBundle:
|
||||
"""Load a single chip-revision CSV and validate its schema.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path:
|
||||
Filesystem path to the CSV file.
|
||||
label:
|
||||
Human-readable revision label (e.g. ``"rev_A"``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
RevisionBundle
|
||||
The loaded and schema-validated bundle.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If any expected column is missing from the CSV.
|
||||
"""
|
||||
# TODO: read the CSV with pd.read_csv(path)
|
||||
# TODO: validate that all EXPECTED_COLS are present; raise ValueError if not
|
||||
# TODO: set SUBSYSTEM_COL as the DataFrame index
|
||||
# TODO: return RevisionBundle(path=path, label=label, data=df)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def align_revisions(
|
||||
rev_a: RevisionBundle,
|
||||
rev_b: RevisionBundle,
|
||||
) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Align both revision DataFrames so they share the same subsystem rows.
|
||||
|
||||
The diagram shows both CSVs feeding into a single "load & align" step.
|
||||
Rows present in one revision but not the other are dropped with a warning.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
rev_a:
|
||||
The first (baseline) revision bundle.
|
||||
rev_b:
|
||||
The second (comparison) revision bundle.
|
||||
|
||||
Returns
|
||||
-------
|
||||
tuple[pd.DataFrame, pd.DataFrame]
|
||||
``(aligned_a, aligned_b)`` – DataFrames with identical row indices.
|
||||
"""
|
||||
# TODO: find the intersection of subsystem indices from both DataFrames
|
||||
# TODO: warn (via print/logging) about any rows dropped from either side
|
||||
# TODO: reindex both DataFrames to the common index
|
||||
# TODO: return (aligned_a, aligned_b)
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def compute_deltas(
|
||||
aligned_a: pd.DataFrame,
|
||||
aligned_b: pd.DataFrame,
|
||||
rev_a_label: str,
|
||||
rev_b_label: str,
|
||||
) -> DeltaResult:
|
||||
"""Compute per-subsystem deltas for columns 5–9 (rev_B − rev_A).
|
||||
|
||||
Parameters
|
||||
----------
|
||||
aligned_a:
|
||||
Aligned baseline DataFrame (rev_A).
|
||||
aligned_b:
|
||||
Aligned comparison DataFrame (rev_B).
|
||||
rev_a_label:
|
||||
Label string for rev_A (used in the result).
|
||||
rev_b_label:
|
||||
Label string for rev_B (used in the result).
|
||||
|
||||
Returns
|
||||
-------
|
||||
DeltaResult
|
||||
Populated delta result with a DataFrame of shape
|
||||
``(n_subsystems, len(DERIVED_COLS))``.
|
||||
"""
|
||||
# TODO: subtract aligned_a[DERIVED_COLS] from aligned_b[DERIVED_COLS]
|
||||
# TODO: store result in a DeltaResult dataclass
|
||||
# TODO: return the DeltaResult
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def plot_bar_chart(
|
||||
result: DeltaResult,
|
||||
output_path: Path | None = None,
|
||||
show: bool = False,
|
||||
) -> None:
|
||||
"""Render a grouped bar chart of the deltas for columns 5–9.
|
||||
|
||||
One group of bars per subsystem row; one bar per derived column (5–9).
|
||||
Positive delta = rev_B draws more power; negative = rev_B is more efficient.
|
||||
Uses plain matplotlib – no seaborn, no fancy theming.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
result:
|
||||
The populated :class:`DeltaResult`.
|
||||
output_path:
|
||||
If provided, save the figure to this path (PNG/SVG/PDF).
|
||||
show:
|
||||
If ``True``, call ``plt.show()`` to open an interactive window.
|
||||
"""
|
||||
# TODO: create a figure and axes with plt.subplots()
|
||||
# TODO: compute bar positions for a grouped layout
|
||||
# (one group per subsystem, one bar per derived column)
|
||||
# TODO: iterate over DERIVED_COLS and call ax.bar() for each column's deltas
|
||||
# TODO: add axis labels, title, legend, and a horizontal zero-line
|
||||
# TODO: if output_path is not None, call fig.savefig(output_path, bbox_inches="tight")
|
||||
# TODO: if show is True, call plt.show()
|
||||
# TODO: call plt.close(fig) to free memory
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI entry-point
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
"""Construct the :mod:`argparse` parser for the ``gnarly_csv_delta`` CLI."""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="gnarly_csv_delta",
|
||||
description=(
|
||||
"Compare two chip-revision CSV files and visualise power deltas "
|
||||
"(columns 5–9) as a grouped bar chart."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"rev_a",
|
||||
type=Path,
|
||||
help="Path to the baseline chip-revision CSV (rev_A).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"rev_b",
|
||||
type=Path,
|
||||
help="Path to the comparison chip-revision CSV (rev_B).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
"-o",
|
||||
type=Path,
|
||||
default=None,
|
||||
metavar="FILE",
|
||||
help=(
|
||||
"Save the bar chart to this file (e.g. delta_chart.png). "
|
||||
"Format is inferred from the extension. "
|
||||
"If omitted, the chart is not saved to disk."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--show",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Open an interactive matplotlib window after rendering.",
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: Sequence[str] | None = None) -> None:
|
||||
"""Parse CLI arguments and run the full delta-visualisation pipeline.
|
||||
|
||||
Pipeline
|
||||
--------
|
||||
1. :func:`load_revision` × 2
|
||||
2. :func:`align_revisions`
|
||||
3. :func:`compute_deltas`
|
||||
4. :func:`plot_bar_chart`
|
||||
"""
|
||||
args = build_parser().parse_args(list(argv) if argv is not None else sys.argv[1:])
|
||||
|
||||
# TODO: call load_revision(args.rev_a, label="rev_A")
|
||||
# TODO: call load_revision(args.rev_b, label="rev_B")
|
||||
# TODO: call align_revisions(rev_a_bundle, rev_b_bundle)
|
||||
# TODO: call compute_deltas(aligned_a, aligned_b, ...)
|
||||
# TODO: call plot_bar_chart(result, output_path=args.output, show=args.show)
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user