Add /llms.txt API reference and POST /api/notebooks/compose endpoint
Machine-readable API docs at /llms.txt for LLM collaboration on circuit design notebooks linked from Mims Electronics Reference Library. Compose endpoint creates fully-populated notebooks in one call with optional SPICE simulation. Per-cell try/except ensures partial simulation failures don't lose the notebook. Also extracts get_engine to spicebook.engine and makes generate_notebook_id a public API.
This commit is contained in:
parent
c581786372
commit
1e08be4409
@ -0,0 +1,13 @@
|
||||
"""SPICE simulation engine registry."""
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from spicebook.engine.base import SpiceEngine
|
||||
from spicebook.engine.ngspice import NgspiceEngine
|
||||
|
||||
|
||||
def get_engine(engine_name: str) -> SpiceEngine:
|
||||
"""Resolve a simulation engine by name."""
|
||||
if engine_name == "ngspice":
|
||||
return NgspiceEngine()
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported engine: '{engine_name}'")
|
||||
@ -10,7 +10,7 @@ from fastapi import FastAPI, Request, Response
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from spicebook.config import settings
|
||||
from spicebook.routers import notebooks, schematics, simulation, waveforms
|
||||
from spicebook.routers import compose, notebooks, schematics, simulation, waveforms
|
||||
|
||||
logger = logging.getLogger("spicebook")
|
||||
|
||||
@ -59,6 +59,9 @@ def create_app() -> FastAPI:
|
||||
)
|
||||
return response
|
||||
|
||||
# compose MUST be registered before notebooks so that
|
||||
# POST /api/notebooks/compose matches before {notebook_id}
|
||||
application.include_router(compose.router)
|
||||
application.include_router(notebooks.router)
|
||||
application.include_router(schematics.router)
|
||||
application.include_router(simulation.router)
|
||||
|
||||
@ -67,3 +67,16 @@ class UpdateCellRequest(BaseModel):
|
||||
|
||||
class ReorderCellsRequest(BaseModel):
|
||||
cell_ids: list[str] # Ordered list of all cell IDs
|
||||
|
||||
|
||||
class ComposeCellInput(BaseModel):
|
||||
type: CellType
|
||||
source: str = ""
|
||||
|
||||
|
||||
class ComposeNotebookRequest(BaseModel):
|
||||
title: str = Field("Untitled Notebook", max_length=256)
|
||||
engine: str = "ngspice"
|
||||
tags: list[str] = Field(default_factory=list, max_length=20)
|
||||
cells: list[ComposeCellInput] = Field(..., max_length=100)
|
||||
run: bool = False
|
||||
|
||||
94
backend/src/spicebook/routers/compose.py
Normal file
94
backend/src/spicebook/routers/compose.py
Normal file
@ -0,0 +1,94 @@
|
||||
"""Compose endpoint — create a fully-populated notebook in one call."""
|
||||
|
||||
import logging
|
||||
import tempfile
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from spicebook.config import settings
|
||||
from spicebook.engine import get_engine
|
||||
from spicebook.models.notebook import (
|
||||
Cell,
|
||||
CellOutput,
|
||||
CellType,
|
||||
ComposeNotebookRequest,
|
||||
Notebook,
|
||||
NotebookMetadata,
|
||||
)
|
||||
from spicebook.models.simulation import SimulationResponse
|
||||
from spicebook.storage.filesystem import generate_notebook_id, save_notebook
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/notebooks", tags=["compose"])
|
||||
|
||||
|
||||
@router.post("/compose", status_code=201)
|
||||
async def compose_notebook(req: ComposeNotebookRequest):
|
||||
"""Create a notebook with pre-populated cells, optionally running SPICE cells."""
|
||||
if not req.cells:
|
||||
raise HTTPException(status_code=400, detail="At least one cell is required")
|
||||
|
||||
nb_id = generate_notebook_id(req.title)
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
cells: list[Cell] = []
|
||||
for item in req.cells:
|
||||
cells.append(
|
||||
Cell(
|
||||
id=f"cell-{uuid.uuid4().hex[:12]}",
|
||||
type=item.type,
|
||||
source=item.source,
|
||||
)
|
||||
)
|
||||
|
||||
notebook = Notebook(
|
||||
metadata=NotebookMetadata(
|
||||
title=req.title,
|
||||
engine=req.engine,
|
||||
tags=req.tags,
|
||||
created=now,
|
||||
modified=now,
|
||||
),
|
||||
cells=cells,
|
||||
)
|
||||
|
||||
if req.run:
|
||||
engine = get_engine(req.engine)
|
||||
for cell in notebook.cells:
|
||||
if cell.type != CellType.SPICE:
|
||||
continue
|
||||
if not cell.source.strip():
|
||||
continue
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory(prefix="spicebook-sim-") as tmpdir:
|
||||
result = await engine.run(cell.source, Path(tmpdir))
|
||||
except Exception:
|
||||
logger.exception("Simulation failed for cell %s", cell.id)
|
||||
result = SimulationResponse(
|
||||
success=False,
|
||||
error="Internal simulation error",
|
||||
elapsed_seconds=0.0,
|
||||
)
|
||||
|
||||
run_ts = datetime.now(timezone.utc).isoformat()
|
||||
cell.outputs.append(
|
||||
CellOutput(
|
||||
output_type="simulation_result" if result.success else "error",
|
||||
data={
|
||||
"success": result.success,
|
||||
"waveform": result.waveform.model_dump() if result.waveform else None,
|
||||
"log": result.log,
|
||||
"error": result.error,
|
||||
"elapsed_seconds": result.elapsed_seconds,
|
||||
},
|
||||
timestamp=run_ts,
|
||||
)
|
||||
)
|
||||
|
||||
save_notebook(settings.notebook_dir, nb_id, notebook)
|
||||
return {"id": nb_id, **notebook.model_dump()}
|
||||
@ -7,7 +7,7 @@ from pathlib import Path
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from spicebook.config import settings
|
||||
from spicebook.engine.ngspice import NgspiceEngine
|
||||
from spicebook.engine import get_engine
|
||||
from spicebook.models.notebook import CellOutput, CellType
|
||||
from spicebook.models.simulation import SimulationRequest, SimulationResponse
|
||||
from spicebook.storage.filesystem import load_notebook, save_notebook
|
||||
@ -15,17 +15,10 @@ from spicebook.storage.filesystem import load_notebook, save_notebook
|
||||
router = APIRouter(prefix="/api", tags=["simulation"])
|
||||
|
||||
|
||||
def _get_engine(engine_name: str) -> NgspiceEngine:
|
||||
"""Resolve engine by name. Only ngspice is supported in Phase 1."""
|
||||
if engine_name == "ngspice":
|
||||
return NgspiceEngine()
|
||||
raise HTTPException(status_code=400, detail=f"Unsupported engine: '{engine_name}'")
|
||||
|
||||
|
||||
@router.post("/simulate", response_model=SimulationResponse)
|
||||
async def simulate(req: SimulationRequest):
|
||||
"""Run a standalone SPICE simulation."""
|
||||
engine = _get_engine(req.engine)
|
||||
engine = get_engine(req.engine)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="spicebook-sim-") as tmpdir:
|
||||
result = await engine.run(req.netlist, Path(tmpdir))
|
||||
@ -61,7 +54,7 @@ async def run_cell(notebook_id: str, cell_id: str):
|
||||
if not cell.source.strip():
|
||||
raise HTTPException(status_code=400, detail="Cell source is empty")
|
||||
|
||||
engine = _get_engine(nb.metadata.engine)
|
||||
engine = get_engine(nb.metadata.engine)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="spicebook-sim-") as tmpdir:
|
||||
result = await engine.run(cell.source, Path(tmpdir))
|
||||
|
||||
@ -33,7 +33,7 @@ def _slugify(text: str) -> str:
|
||||
return slug or "notebook"
|
||||
|
||||
|
||||
def _generate_notebook_id(title: str) -> str:
|
||||
def generate_notebook_id(title: str) -> str:
|
||||
"""Generate a notebook ID from title, with short UUID suffix for uniqueness."""
|
||||
slug = _slugify(title)
|
||||
short_uid = uuid.uuid4().hex[:8]
|
||||
@ -105,7 +105,7 @@ def save_notebook(directory: Path, notebook_id: str, notebook: Notebook) -> None
|
||||
|
||||
def create_notebook(directory: Path, title: str, engine: str = "ngspice") -> tuple[str, Notebook]:
|
||||
"""Create a new notebook and save it. Returns (notebook_id, notebook)."""
|
||||
nb_id = _generate_notebook_id(title)
|
||||
nb_id = generate_notebook_id(title)
|
||||
now = _now_iso()
|
||||
|
||||
notebook = Notebook(
|
||||
|
||||
546
frontend/public/llms.txt
Normal file
546
frontend/public/llms.txt
Normal file
@ -0,0 +1,546 @@
|
||||
# SpiceBook
|
||||
|
||||
> Notebook interface for SPICE circuit simulation. Create, edit, and run SPICE netlists in a cell-based notebook UI with waveform visualization and schematic generation. Powered by ngspice.
|
||||
|
||||
Base URL: `https://spicebook.warehack.ing`
|
||||
|
||||
## API Reference
|
||||
|
||||
All endpoints accept and return JSON unless noted otherwise. Prefix all paths with the base URL.
|
||||
|
||||
---
|
||||
|
||||
### Notebooks
|
||||
|
||||
#### List notebooks
|
||||
|
||||
```
|
||||
GET /api/notebooks
|
||||
```
|
||||
|
||||
**Response** `200`
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "rc-low-pass-a1b2c3d4",
|
||||
"title": "RC Low-Pass Filter",
|
||||
"engine": "ngspice",
|
||||
"tags": ["filter", "rc"],
|
||||
"cell_count": 3,
|
||||
"modified": "2026-02-13T18:30:00+00:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### Create notebook
|
||||
|
||||
```
|
||||
POST /api/notebooks
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"title": "My Circuit",
|
||||
"engine": "ngspice"
|
||||
}
|
||||
```
|
||||
|
||||
Both fields are optional. Defaults: title = `"Untitled Notebook"`, engine = `"ngspice"`.
|
||||
|
||||
**Response** `201`
|
||||
```json
|
||||
{
|
||||
"id": "my-circuit-f8e2a91b",
|
||||
"spicebook_version": "2026-02-13",
|
||||
"metadata": {
|
||||
"title": "My Circuit",
|
||||
"engine": "ngspice",
|
||||
"tags": [],
|
||||
"created": "2026-02-13T18:30:00+00:00",
|
||||
"modified": "2026-02-13T18:30:00+00:00"
|
||||
},
|
||||
"cells": [
|
||||
{
|
||||
"id": "cell-a1b2c3d4e5f6",
|
||||
"type": "markdown",
|
||||
"source": "# My Circuit\n\nAdd SPICE cells below to begin simulating.",
|
||||
"outputs": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get notebook
|
||||
|
||||
```
|
||||
GET /api/notebooks/{notebook_id}
|
||||
```
|
||||
|
||||
**Response** `200` — Full `Notebook` object (same shape as create response, without `id` wrapper).
|
||||
|
||||
#### Update notebook
|
||||
|
||||
```
|
||||
PUT /api/notebooks/{notebook_id}
|
||||
```
|
||||
|
||||
**Request body** — Full `Notebook` object (replaces entire notebook).
|
||||
|
||||
**Response** `200` — The saved `Notebook`.
|
||||
|
||||
#### Delete notebook
|
||||
|
||||
```
|
||||
DELETE /api/notebooks/{notebook_id}
|
||||
```
|
||||
|
||||
**Response** `204` — No content. Only user-created notebooks can be deleted.
|
||||
|
||||
---
|
||||
|
||||
### Cells
|
||||
|
||||
Cells are ordered elements within a notebook. Each cell has a `type` (`markdown`, `spice`, `python`, `schematic`) and `source` (text content).
|
||||
|
||||
#### Add cell
|
||||
|
||||
```
|
||||
POST /api/notebooks/{notebook_id}/cells
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"type": "spice",
|
||||
"source": "V1 1 0 DC 5\nR1 1 0 1k\n.op\n.end",
|
||||
"after_cell_id": "cell-a1b2c3d4e5f6"
|
||||
}
|
||||
```
|
||||
|
||||
`after_cell_id` is optional — omit to append at end.
|
||||
|
||||
**Response** `201`
|
||||
```json
|
||||
{
|
||||
"id": "cell-b2c3d4e5f6a7",
|
||||
"type": "spice",
|
||||
"source": "V1 1 0 DC 5\nR1 1 0 1k\n.op\n.end",
|
||||
"outputs": []
|
||||
}
|
||||
```
|
||||
|
||||
#### Update cell
|
||||
|
||||
```
|
||||
PUT /api/notebooks/{notebook_id}/cells/{cell_id}
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"source": "V1 1 0 DC 10\nR1 1 0 2k\n.op\n.end",
|
||||
"type": "spice"
|
||||
}
|
||||
```
|
||||
|
||||
Both fields optional — only provided fields are updated.
|
||||
|
||||
**Response** `200` — Updated `Cell`.
|
||||
|
||||
#### Delete cell
|
||||
|
||||
```
|
||||
DELETE /api/notebooks/{notebook_id}/cells/{cell_id}
|
||||
```
|
||||
|
||||
**Response** `204`
|
||||
|
||||
#### Reorder cells
|
||||
|
||||
```
|
||||
PUT /api/notebooks/{notebook_id}/cells/reorder
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"cell_ids": ["cell-b2c3d4e5f6a7", "cell-a1b2c3d4e5f6"]
|
||||
}
|
||||
```
|
||||
|
||||
Must include every cell ID exactly once.
|
||||
|
||||
**Response** `200` — Array of `Cell` objects in new order.
|
||||
|
||||
---
|
||||
|
||||
### Simulation
|
||||
|
||||
#### Run standalone simulation
|
||||
|
||||
```
|
||||
POST /api/simulate
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"netlist": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.op\n.end",
|
||||
"engine": "ngspice"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** `200`
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"waveform": {
|
||||
"variables": [
|
||||
{"name": "v(1)", "type": "voltage"},
|
||||
{"name": "v(2)", "type": "voltage"}
|
||||
],
|
||||
"points": 1,
|
||||
"x_data": [0.0],
|
||||
"y_data": {"v(1)": [5.0], "v(2)": [3.333]},
|
||||
"x_type": "time",
|
||||
"is_complex": false,
|
||||
"y_magnitude_db": null,
|
||||
"y_phase_deg": null
|
||||
},
|
||||
"log": "ngspice output...",
|
||||
"error": null,
|
||||
"elapsed_seconds": 0.42
|
||||
}
|
||||
```
|
||||
|
||||
#### Run cell in notebook
|
||||
|
||||
```
|
||||
POST /api/notebooks/{notebook_id}/cells/{cell_id}/run
|
||||
```
|
||||
|
||||
No request body — uses the cell's `source` as the netlist and the notebook's `engine`.
|
||||
|
||||
**Response** `200` — Same `SimulationResponse` shape. The cell's outputs are updated in the saved notebook.
|
||||
|
||||
---
|
||||
|
||||
### Schematics
|
||||
|
||||
#### Generate schematic from cell
|
||||
|
||||
```
|
||||
POST /api/notebooks/{notebook_id}/cells/{cell_id}/schematic
|
||||
```
|
||||
|
||||
No request body. Cell must be type `spice`.
|
||||
|
||||
**Response** `200`
|
||||
```json
|
||||
{
|
||||
"svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>",
|
||||
"success": true,
|
||||
"error": null,
|
||||
"component_map": {"R1": "resistor", "V1": "voltage_source"}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Waveforms
|
||||
|
||||
#### Generate SVG plot (JSON-wrapped)
|
||||
|
||||
```
|
||||
POST /api/waveforms/svg
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"waveform": { "...WaveformData from simulation response..." },
|
||||
"title": "Output Voltage",
|
||||
"width": 800,
|
||||
"height": 500,
|
||||
"signals": ["v(2)"]
|
||||
}
|
||||
```
|
||||
|
||||
`signals` is optional — omit to plot all signals. `width`, `height`, `title` have defaults.
|
||||
|
||||
**Response** `200`
|
||||
```json
|
||||
{
|
||||
"svg": "<svg ...>...</svg>"
|
||||
}
|
||||
```
|
||||
|
||||
#### Generate SVG plot (raw)
|
||||
|
||||
```
|
||||
POST /api/waveforms/svg/raw
|
||||
```
|
||||
|
||||
Same request body as above.
|
||||
|
||||
**Response** `200` with `Content-Type: image/svg+xml` — raw SVG string.
|
||||
|
||||
---
|
||||
|
||||
### Compose (convenience)
|
||||
|
||||
Create a fully-populated notebook with multiple cells in a single call.
|
||||
|
||||
#### Compose notebook
|
||||
|
||||
```
|
||||
POST /api/notebooks/compose
|
||||
```
|
||||
|
||||
**Request body**
|
||||
```json
|
||||
{
|
||||
"title": "RC Low-Pass Filter",
|
||||
"engine": "ngspice",
|
||||
"tags": ["filter", "rc", "analog"],
|
||||
"cells": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"source": "# RC Low-Pass Filter\n\nA simple first-order low-pass filter."
|
||||
},
|
||||
{
|
||||
"type": "spice",
|
||||
"source": "V1 in 0 AC 1\nR1 in out 1k\nC1 out 0 1u\n.ac dec 100 1 1meg\n.end"
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"source": "## Analysis\n\nThe -3dB cutoff is at f = 1/(2*pi*R*C) = 159 Hz."
|
||||
}
|
||||
],
|
||||
"run": false
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|----------|-----------------|----------------------|------------------------------------------------------|
|
||||
| title | string | "Untitled Notebook" | Notebook title |
|
||||
| engine | string | "ngspice" | Simulation engine |
|
||||
| tags | list of strings | [] | Searchable tags |
|
||||
| cells | list of objects | (required) | Cells to create, each with `type` and `source` |
|
||||
| run | bool | false | If true, execute each SPICE cell after creation |
|
||||
|
||||
**Response** `201` — Same shape as `POST /api/notebooks` (notebook with `id` at top level).
|
||||
|
||||
When `run` is `true`, each `spice` cell is executed sequentially using the notebook's engine. Simulation results are stored in each cell's `outputs` array. Non-SPICE cells are unaffected.
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### CellType (enum)
|
||||
|
||||
`"markdown"` | `"spice"` | `"python"` | `"schematic"`
|
||||
|
||||
### Cell
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "cell-a1b2c3d4e5f6",
|
||||
"type": "spice",
|
||||
"source": "V1 1 0 DC 5\n.op\n.end",
|
||||
"outputs": [
|
||||
{
|
||||
"output_type": "simulation_result",
|
||||
"data": { "success": true, "waveform": {...}, "log": "...", "error": null, "elapsed_seconds": 0.3 },
|
||||
"timestamp": "2026-02-13T18:35:00+00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Notebook
|
||||
|
||||
```json
|
||||
{
|
||||
"spicebook_version": "2026-02-13",
|
||||
"metadata": {
|
||||
"title": "string",
|
||||
"engine": "ngspice",
|
||||
"tags": ["string"],
|
||||
"created": "ISO-8601 datetime",
|
||||
"modified": "ISO-8601 datetime"
|
||||
},
|
||||
"cells": [Cell, ...]
|
||||
}
|
||||
```
|
||||
|
||||
### SimulationResponse
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"waveform": WaveformData | null,
|
||||
"log": "string",
|
||||
"error": "string | null",
|
||||
"elapsed_seconds": 0.0
|
||||
}
|
||||
```
|
||||
|
||||
### WaveformData
|
||||
|
||||
```json
|
||||
{
|
||||
"variables": [{"name": "v(out)", "type": "voltage"}],
|
||||
"points": 100,
|
||||
"x_data": [0.0, 0.001, ...],
|
||||
"y_data": {"v(out)": [0.0, 0.5, ...]},
|
||||
"x_type": "time",
|
||||
"is_complex": false,
|
||||
"y_magnitude_db": null,
|
||||
"y_phase_deg": null
|
||||
}
|
||||
```
|
||||
|
||||
For AC analysis (`is_complex: true`), `y_magnitude_db` and `y_phase_deg` contain per-signal arrays. `x_type` will be `"frequency"` and `x_data` holds frequency values in Hz.
|
||||
|
||||
---
|
||||
|
||||
## SPICE Netlist Primer
|
||||
|
||||
SpiceBook uses **ngspice** as its simulation engine. Netlists are plain text describing a circuit and the analysis to perform.
|
||||
|
||||
### Basic structure
|
||||
|
||||
```spice
|
||||
* Title line (optional comment)
|
||||
V1 node_pos node_neg DC 5 * DC voltage source
|
||||
R1 node_a node_b 1k * Resistor: 1 kilo-ohm
|
||||
C1 node_a node_b 100n * Capacitor: 100 nanofarads
|
||||
L1 node_a node_b 10m * Inductor: 10 millihenrys
|
||||
|
||||
.analysis_type parameters
|
||||
.end
|
||||
```
|
||||
|
||||
Node `0` is always ground.
|
||||
|
||||
### Supported analysis types
|
||||
|
||||
| Command | Description | Example |
|
||||
|---------|-------------|---------|
|
||||
| `.op` | DC operating point | `.op` |
|
||||
| `.dc` | DC sweep | `.dc V1 0 5 0.1` |
|
||||
| `.tran` | Transient (time-domain) | `.tran 1u 10m` |
|
||||
| `.ac` | AC frequency sweep | `.ac dec 100 1 1meg` |
|
||||
|
||||
### Engineering suffixes
|
||||
|
||||
| Suffix | Multiplier | Example |
|
||||
|--------|-----------|---------|
|
||||
| T | 10^12 | `1T` = 1 tera |
|
||||
| G | 10^9 | `2.2G` = 2.2 giga |
|
||||
| meg | 10^6 | `1meg` = 1 mega (note: not `M` — that's milli in SPICE) |
|
||||
| k | 10^3 | `4.7k` = 4700 |
|
||||
| m | 10^-3 | `10m` = 0.01 |
|
||||
| u | 10^-6 | `100u` = 100 micro |
|
||||
| n | 10^-9 | `47n` = 47 nano |
|
||||
| p | 10^-12 | `10p` = 10 pico |
|
||||
| f | 10^-15 | `1f` = 1 femto |
|
||||
|
||||
### Common sources
|
||||
|
||||
```spice
|
||||
V1 node+ node- DC 5 * DC voltage
|
||||
V2 node+ node- AC 1 * AC source (for .ac analysis)
|
||||
V3 node+ node- PULSE(0 5 0 1n 1n 5u 10u) * Pulse source
|
||||
I1 node+ node- DC 1m * DC current source
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Example Workflow
|
||||
|
||||
### 1. Create a notebook with the compose endpoint
|
||||
|
||||
```bash
|
||||
curl -X POST https://spicebook.warehack.ing/api/notebooks/compose \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "Voltage Divider",
|
||||
"engine": "ngspice",
|
||||
"tags": ["resistive", "dc", "beginner"],
|
||||
"cells": [
|
||||
{
|
||||
"type": "markdown",
|
||||
"source": "# Voltage Divider\n\nTwo resistors divide a 5V supply."
|
||||
},
|
||||
{
|
||||
"type": "spice",
|
||||
"source": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.op\n.end"
|
||||
},
|
||||
{
|
||||
"type": "markdown",
|
||||
"source": "## Expected Result\n\nV(2) = 5 * 2k / (1k + 2k) = 3.333V"
|
||||
}
|
||||
],
|
||||
"run": true
|
||||
}'
|
||||
```
|
||||
|
||||
Response includes the notebook `id` and all cells. Because `run: true`, the SPICE cell's `outputs` array will contain the simulation result with `v(2) = 3.333`.
|
||||
|
||||
### 2. View the notebook in the browser
|
||||
|
||||
Open `https://spicebook.warehack.ing/notebook/{id}` — the notebook renders with the markdown cells formatted and the SPICE cell showing its simulation output.
|
||||
|
||||
### 3. Add another cell
|
||||
|
||||
```bash
|
||||
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"type": "spice",
|
||||
"source": "V1 1 0 DC 5\nR1 1 2 1k\nR2 2 0 2k\n.dc V1 0 10 0.5\n.end"
|
||||
}'
|
||||
```
|
||||
|
||||
### 4. Run the new cell
|
||||
|
||||
```bash
|
||||
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells/{cell_id}/run
|
||||
```
|
||||
|
||||
### 5. Generate a schematic
|
||||
|
||||
```bash
|
||||
curl -X POST https://spicebook.warehack.ing/api/notebooks/{id}/cells/{cell_id}/schematic
|
||||
```
|
||||
|
||||
### 6. Visualize waveform data
|
||||
|
||||
Take the `waveform` from the simulation response and POST it to the waveform endpoint:
|
||||
|
||||
```bash
|
||||
curl -X POST https://spicebook.warehack.ing/api/waveforms/svg/raw \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"waveform": { ...waveform object from simulation response... },
|
||||
"title": "DC Sweep",
|
||||
"signals": ["v(2)"]
|
||||
}' \
|
||||
-o plot.svg
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
```
|
||||
GET /health
|
||||
```
|
||||
|
||||
**Response** `200`
|
||||
```json
|
||||
{"status": "ok", "version": "2026.02.13"}
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user