🎉 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:
2026-04-11 15:54:42 -07:00
commit 18a3b464f2
14 changed files with 2367 additions and 0 deletions

View File

@ -0,0 +1,9 @@
subsystem,meas_current_mA,meas_voltage_mV,meas_power_mW,meas_temp_C,rail_power_mW,subsys_power_mW,cluster_power_mW,soc_power_mW,battery_power_mW
CPU,850,1050,892.5,72.3,910.0,1820.0,3640.0,5460.0,6200.0
GPU,1200,950,1140.0,81.5,1160.0,2320.0,4640.0,6960.0,7900.0
DRAM,430,1200,516.0,55.1,530.0,1060.0,2120.0,3180.0,3600.0
NPU,620,1100,682.0,68.4,700.0,1400.0,2800.0,4200.0,4750.0
ISP,310,1050,325.5,49.7,340.0,680.0,1360.0,2040.0,2300.0
PCIe,180,1800,324.0,44.2,335.0,670.0,1340.0,2010.0,2250.0
USB,95,1800,171.0,38.6,180.0,360.0,720.0,1080.0,1200.0
Display,540,1200,648.0,61.8,660.0,1320.0,2640.0,3960.0,4500.0
1 subsystem meas_current_mA meas_voltage_mV meas_power_mW meas_temp_C rail_power_mW subsys_power_mW cluster_power_mW soc_power_mW battery_power_mW
2 CPU 850 1050 892.5 72.3 910.0 1820.0 3640.0 5460.0 6200.0
3 GPU 1200 950 1140.0 81.5 1160.0 2320.0 4640.0 6960.0 7900.0
4 DRAM 430 1200 516.0 55.1 530.0 1060.0 2120.0 3180.0 3600.0
5 NPU 620 1100 682.0 68.4 700.0 1400.0 2800.0 4200.0 4750.0
6 ISP 310 1050 325.5 49.7 340.0 680.0 1360.0 2040.0 2300.0
7 PCIe 180 1800 324.0 44.2 335.0 670.0 1340.0 2010.0 2250.0
8 USB 95 1800 171.0 38.6 180.0 360.0 720.0 1080.0 1200.0
9 Display 540 1200 648.0 61.8 660.0 1320.0 2640.0 3960.0 4500.0

View File

@ -0,0 +1,9 @@
subsystem,meas_current_mA,meas_voltage_mV,meas_power_mW,meas_temp_C,rail_power_mW,subsys_power_mW,cluster_power_mW,soc_power_mW,battery_power_mW
CPU,790,1050,829.5,69.1,845.0,1690.0,3380.0,5070.0,5750.0
GPU,1380,950,1311.0,88.2,1335.0,2670.0,5340.0,8010.0,9100.0
DRAM,410,1200,492.0,53.4,505.0,1010.0,2020.0,3030.0,3430.0
NPU,710,1100,781.0,74.9,800.0,1600.0,3200.0,4800.0,5450.0
ISP,295,1050,309.75,47.3,322.0,644.0,1288.0,1932.0,2180.0
PCIe,175,1800,315.0,43.0,326.0,652.0,1304.0,1956.0,2190.0
USB,88,1800,158.4,37.1,166.0,332.0,664.0,996.0,1110.0
Display,610,1200,732.0,66.5,748.0,1496.0,2992.0,4488.0,5100.0
1 subsystem meas_current_mA meas_voltage_mV meas_power_mW meas_temp_C rail_power_mW subsys_power_mW cluster_power_mW soc_power_mW battery_power_mW
2 CPU 790 1050 829.5 69.1 845.0 1690.0 3380.0 5070.0 5750.0
3 GPU 1380 950 1311.0 88.2 1335.0 2670.0 5340.0 8010.0 9100.0
4 DRAM 410 1200 492.0 53.4 505.0 1010.0 2020.0 3030.0 3430.0
5 NPU 710 1100 781.0 74.9 800.0 1600.0 3200.0 4800.0 5450.0
6 ISP 295 1050 309.75 47.3 322.0 644.0 1288.0 1932.0 2180.0
7 PCIe 175 1800 315.0 43.0 326.0 652.0 1304.0 1956.0 2190.0
8 USB 88 1800 158.4 37.1 166.0 332.0 664.0 996.0 1110.0
9 Display 610 1200 732.0 66.5 748.0 1496.0 2992.0 4488.0 5100.0

View 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 14 (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 59 (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 59
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 14: 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 59: 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 59 (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 59.
One group of bars per subsystem row; one bar per derived column (59).
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 59) 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()