LC Studio — Project Hub

Everything
I'm building.

Tools, simulations and experiments. Click any project for architecture and tech detail — then open the live version.

01
Productivity Tool
2026

Excel AI Engine

Auditable AI mutations for structured spreadsheet data

Describe a change in plain English. The engine builds a typed plan, previews every cell that changes and every downstream formula that recalculates, then commits atomically — or rolls back entirely. Nothing happens without your approval.

Python 3.12 + FastAPI Anthropic Claude openpyxl + pydantic v2 Next.js 15 PostgreSQL Vercel + Railway
View project
02
Scientific Computing
2026

Hurricane Simulator

Interactive parametric hurricane track & loss simulator

Drag a start, end, and curve handle to set a storm track on the map; tune pressure, size, radius-of-max-wind and forward speed, then simulate the Holland (1980) parametric wind field and the resulting property losses along the path in real time.

Next.js 15 + React Leaflet Python + Flask NumPy + paratc Vercel + Railway
View project

Excel AI Engine

Type a change in plain English. The engine reads your workbook, builds a typed mutation plan, and shows you a cell-by-cell diff — every value that changes, every formula that recalculates downstream — before a single byte is written. Commit atomically, or discard entirely. Every operation is logged with a full audit trail.

The Problem

AI tools for Excel either generate unauditable code or mutate files silently. When something goes wrong — and it does — there is no record of what changed, why, or what else it broke. The Excel AI Engine treats your workbook as a typed, auditable artifact. Every mutation is a structured plan: validated before it runs, previewed before it commits, and logged permanently. The blast radius of any change — every cell and formula that depends on what you touched — is computed and shown to you upfront.

Architecture

The pipeline runs in four stages:

01 — Workbook Read + Dependency Graph
openpyxl parses the .xlsx into a typed cell model. The engine builds a formula dependency graph — every cell's precedents and dependents — so the blast radius of any change is known before anything runs.
02 — Typed Mutation Plan
The LLM (Claude, temperature 0) reads the workbook context and your prompt, then emits a structured Plan — a validated, pydantic-typed list of atomic operations. No raw code, no imperative mutation; only typed operations the engine understands.
03 — Dry-Run + Cell Diff Preview
The plan runs in-memory against a copy of the workbook. The engine produces a cell-level diff — every value that changes, every formula affected downstream — and surfaces it for review. Nothing is written to disk yet.
04 — Atomic Commit or Rollback + Audit Log
On approval the plan executes with snapshot-and-restore rollback — it either applies fully or reverts entirely, no partial state. Every committed operation is written to a Postgres audit log with the plan, diff, model provenance, and timing.
Tech Stack
Python 3.12 + FastAPI
Engine + API backend
Anthropic Claude
LLM planner (temp 0)
openpyxl + pydantic v2
Workbook parsing + plan schema
Next.js 15 + TypeScript
Web console frontend
PostgreSQL
Audit log + rate limiting
Vercel + Railway
Frontend + backend hosting
Try It — Sample Workbooks

Download one of these workbooks, upload it to the engine, and try the prompts below. These are real operations the engine handles reliably today.

demo_pricings.xlsx
3 sheets — Prices (16 rows, 8 companies × 2 months), Inflation (8 regions), Summary (empty target)
↓ Download
Prompts that work
"Multiply the prices on the Prices tab for March and June with their corresponding March and June inflation rates for each region using the Inflation tab"
"Bold the header row and apply a navy fill to it on the Prices sheet"
"In the Summary sheet, create a table showing total Price by Region for March and June"
demo_budgets.xlsx
3 sheets — Actuals (7 accounts), Budget (same 7 accounts), Variance (empty target)
↓ Download
Prompts that work
"In the Variance sheet, add a table with Account, Actual, Budget, and Variance (Actual minus Budget) for each account code"
"Change the Marketing budget on the Budget sheet to 80000"
"Make the header row on Budget tab bold and navy fill"
Ready to explore it?
Opens the full interface in a new tab
Open Project

Hurricane Simulator

An interactive parametric hurricane simulator: drag a start point, end point, and a single curve handle to define a storm track, tune its pressure, size, radius-of-max-wind and forward speed, then watch the Holland (1980) gradient wind field and property losses play out along the path in real time.

The Problem

Atmospheric simulation is largely locked inside opaque academic or government systems — difficult to explore, modify, or understand intuitively. This project makes the physics of hurricane formation visible and interactive: a learning tool as much as an engineering one.

How It Actually Works

This is a parametric model — closed-form equations driven by a handful of inputs (track, pressure, radius of max wind, speed) — not a fluid-dynamics solver. Every equation below is quoted directly from the running code, not idealised.

01 — Storm Track Geometry
The track is a quadratic Bézier curve between three points you place on the map: a start marker P₀, an end marker P₂, and a single draggable curve handle P₁. At the handle's midpoint the track is a straight line; dragging it bows the track into a parabola-like arc — there's no free-form multi-point editing.
B(t) = (1−t)² P₀ + 2(1−t)t P₁ + t² P₂,   t ∈ [0, 1]
Sampling B(t) at even t does not give evenly spaced points — spacing would balloon or bunch up as the handle moves. Instead the curve is densely oversampled, its cumulative arc length is walked, and points are re-interpolated at a fixed spacing of STEP_KM = 25 km. Point count is a consequence of the great-circle distance between start and end, never a fixed constant — the storm always advances the same real-world distance per step regardless of how far the handle is dragged.
backend/app/models/hurricane.py — HurricaneModel.generate_path()
02 — Motion & Timing
Storm forward speed (10–60 km/h) converts the fixed step spacing into real elapsed time per step:
Δt = STEP_KM / speed_kmh   (hours per step)
Total storm duration is (points − 1) × Δt. Because spacing is fixed, Δt is constant for a whole run — a faster storm crosses the same ground in less time, so it spends less time exposing any one property to damaging winds (see the loss model below).
backend/app/models/hurricane.py — HurricaneModel.step_duration_hours
03 — Central Pressure Evolution
Central pressure evolves over the sequence of path steps (index s = 0…N−1, peaking at s* = 0.4N) in two phases:
p(s) = p_initial − (p_initial − p_min)(1 − e−3s/s*)   for s < s*
p(s) = p_min + (p_final − p_min) · (s − s*)/(N − s*)   for s ≥ s*
This is indexed by step, not by time — peak intensity always lands at 40% of the way along the track's points, so on a longer track (at a given speed) it simply takes proportionally longer, in hours, to get there. The radius of max wind (RMW) is then interpolated between your min/max sliders by how deep the pressure has dropped relative to this run's own strongest point:
RMW(s) = rmw_max − (rmw_max − rmw_min) · Δp(s) / max(Δp)
backend/app/utils/calculations.py — calculate_hurricane_pressure(), calculate_rmw_evolution()
04 — Wind Field: Holland (1980) Gradient Wind
At every path step, the radial wind profile is the parametric gradient-wind equation from Holland (1980), evaluated via the open-source paratc library at 20 km distance bands out to the storm's size:
Vg(r) = √[ B (RMW/r)B · (Δp/ρ) · e−(RMW/r)B + (fr/2)² ] − fr/2
where r is distance from the storm centre, RMW and Δp (= environmental − central pressure) come from step 03, ρ = 1.15 kg/m³ (fixed air density), f = 2Ω sin(latitude) is the Coriolis parameter, and B is the Holland shape parameter — fixed at 1.5 for every run here (the library also offers statistical B-models fitted to storm structure, but this app doesn't use them). The result: near-zero wind in the calm eye, a sharp peak at r = RMW, decaying outward. It's recomputed independently at each step from that step's pressure and RMW alone — a quasi-static profile with no memory of the previous step's wind field.
paratc.tc_models.Holland1980.gradient_wind_equation() — called directly from backend/app/models/hurricane.py — HurricaneModel.generate_wind_field()
05 — Property Exposure & Loss
At each step, every property's geodesic distance to the storm centre is checked; if within the storm's size it's "exposed" and picks up the wind speed of its nearest 20 km band. Wind speed maps to damage through two curves: a six-tier step function keyed to Saffir–Simpson category boundaries (74/96/111/130/157 mph → 5/15/35/60/85/95% damage ratio), and a logistic "probability of damage" centred at 85 mph — a smoothing heuristic, not a fitted statistical model:
P(v) = 1 / (1 + e−0.3(v−85))
Each step's damage is weighted by Δt and applied against whatever value is still undamaged — not the property's original full value — so cumulative loss is naturally bounded and can approach but never exceed 100%, however long a property stays exposed:
fi = min(1, R(vi) · P(vi) · Δt)
Vi = Vi−1 · (1 − fi),   V−1 = price × homes
lossi = V−1 − Vi
backend/app/models/loss.py — LossModel.calculate_exposure(), calculate_wind_exposure(), calculate_losses(); backend/app/utils/calculations.py — calculate_damage_ratio(), calculate_damage_probability()
Model Assumptions & Limitations
  • Parametric, not a fluid-dynamics solver: no discretised atmospheric grid, no primitive-equation integration, no explicit sea-surface temperature, wind shear, or moisture physics. Pressure and wind are closed-form functions of a few inputs, not the output of numerically integrating governing PDEs.
  • The Coriolis term is the only latitude-dependent physics in the wind equation.
  • The wind field is quasi-static — fully recomputed each step from that step's pressure/RMW alone, with no feedback between wind and pressure.
  • The Holland shape parameter B is fixed at 1.5 for every run rather than estimated from storm structure.
  • Property exposure is a point-distance check against a single wind-speed-by-distance-band lookup, not a footprint/swath integration.
  • Damage and probability curves are simplifying assumptions, not fitted vulnerability curves from a specific catastrophe model.
  • This is an interactive illustration of how track, pressure, wind, and speed interact to drive loss — not a forecast or engineering tool.
Tech Stack
Next.js 15 + React 19
TypeScript frontend
Leaflet
Interactive map + track
Python + Flask
Simulation API backend
NumPy + paratc
Holland (1980) TC equations
Vercel + Railway
Frontend + backend hosting
Ready to explore it?
Opens the full interface in a new tab
Open Project