A Python CLI for small business profitability analysis. Model your business as a scenario with products, costs, and traffic assumptions - then run monthly and annual P&L simulations with break-even analysis.
Small business owners often make pricing and cost decisions without seeing the full financial picture. Marginal lets you model "what if" scenarios before committing capital: what if rent goes up? What if wastage drops from 8% to 5%? What if July traffic doubles due to seasonality?
Instead of building a spreadsheet from scratch, Marginal provides a domain-modeled system with unit economics, contribution margins, break-even point calculation, and monthly P&L across a full year.
- Scenario management - create, update, delete named business scenarios
- Product & recipe modeling - products with ingredient recipes, per-portion costs, wastage
- Fixed costs - categorized monthly costs (rent, utilities, insurance, etc.)
- Traffic assumptions - daily customers × products per customer
- Seasonality factors - monthly multipliers for realistic revenue projections
- Full simulation - contribution margin, BEP, monthly and annual P&L
- JSON export - back up scenarios or share configurations
- Persistent storage - SQLite with Alembic migrations
- Interactive CLI - Typer-based with
--helpfor every command - Tested - pytest suite for calculations and repository layer
- CI/CD - GitHub Actions runs lint and tests on every push
git clone https://github.com/JakubD02/marginal.git
cd marginal
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -e .alembic upgrade headseedThis creates an ice cream parlor scenario with 8 ingredients, seasonal traffic (2.5x in July), and 5000 PLN monthly fixed costs.
marginal simulate "Ice cream parlor 59"Output:
============================================================
Ice cream parlor 59 (PLN)
Fixed costs: 5000 PLN/month
Contribution margin: 8.21 per portion (91.2%)
Monthly P&L:
Month Portions Revenue Variable Profit
1 366 2928 289 -2361 ✗
2 366 2928 289 -2361 ✗
...
7 1830 14640 1447 8193 ✓
8 1683 13464 1331 7133 ✓
...
Annual 87840 8681 24159
marginal scenario list
marginal scenario show "Ice cream parlor 59"
marginal scenario create "Cafe Milano" --currency EUR --working-days 24
marginal scenario update "Cafe Milano" --working-days 26
marginal scenario delete "Cafe Milano" --forcemarginal product list "Ice cream parlor 59"
marginal product get "Ice cream parlor 59" "Strawberry ice cream scoop"
marginal product create "Cafe Milano" "Espresso" --price 8 --category food --wastage 0.03
marginal product update "Cafe Milano" "Espresso" --price 9
marginal product delete "Cafe Milano" "Espresso"marginal fixed-cost list "Ice cream parlor 59"
marginal fixed-cost add "Ice cream parlor 59" "Insurance" 200 --category insurance
marginal fixed-cost delete "Ice cream parlor 59" "Insurance"marginal traffic-assumption set "Ice cream parlor 59" --customers 150 --avg-product 2.5
marginal traffic-assumption get "Ice cream parlor 59"
marginal seasonality set "Ice cream parlor 59" --month 7 --multiplier 2.5
marginal seasonality list "Ice cream parlor 59"marginal simulate "Ice cream parlor 59"marginal export-json "Ice cream parlor 59"
marginal export-json "Ice cream parlor 59" --output backup.jsonExports a complete scenario snapshot (products, ingredients, recipes, fixed costs, traffic, seasonality) to JSON. Useful for backups, sharing configurations, or re-importing in another environment.
Marginal follows a layered architecture, with all code organized under the marginal/ package:
marginal/models.py— SQLAlchemy 2.0 domain entities (Scenario, Product, Ingredient, RecipeItem, FixedCost, TrafficAssumption, SeasonalityFactor)marginal/repository.py— data access layer with eager loading viaselectinloadmarginal/calculations.py— pure functions for unit cost, contribution margin, BEP, monthly P&Lmarginal/presenters.py— display formatting (plain print, Rich planned)marginal/cli/— Typer-based CLI split by entitymain.py— app initialization and sub-app registrationhelpers.py— shared helpers (scenario/product lookup)simulate.py— top-level simulate commandcommands/— sub-app modules per entity (scenario, product, fixed_cost, traffic, seasonality)
marginal/schemas/— Pydantic v2 schemas split by entity- One file per entity with Base/Create/Update/Read/Export pattern
marginal/exporters/— JSON export for scenario backupsmarginal/scripts/— utility scripts (seed data, smoke tests)alembic/— database migrationstests/— pytest suite with in-memory SQLite fixtures
Financial calculations use Decimal throughout to avoid float precision errors.
- Python 3.13
- SQLAlchemy 2.0 (ORM with typed mappers)
- Pydantic v2 (validation)
- Typer (CLI framework)
- SQLite (persistence)
- Alembic (migrations)
- pytest (testing)
- Ruff (linting and formatting)
- GitHub Actions (CI/CD)
- Domain model with 7 entities
- Repository pattern with eager loading
- Contribution margin, BEP, monthly P&L calculations
- Full CLI CRUD for all entities
- Deterministic simulation output
- Modular package structure (cli/, schemas/, exporters/)
- pytest test suite for calculations and repository layers
- GitHub Actions CI (lint + test on every push)
- JSON export for scenario backups
- Comprehensive README with usage examples
Currently the simulator assumes all products sell in equal proportion — a naive arithmetic average. Real businesses have uneven demand: 60% of ice cream customers may buy vanilla, only 10% pick premium flavors. This distorts BEP calculations by 10-20% in practice.
expected_sales_sharefield on Product - each product declares its % of total scenario sales (must sum to 1.0)- Weighted contribution margin - replaces arithmetic mean with sales-share-weighted average
- Per-product BEP breakdown - shows exactly how many units of each product must sell to break even, not just a total
- Validation - sales shares are enforced to sum to 1.0 per scenario, with clear error messages
Current simulation is deterministic — same inputs always produce the same profit. But real businesses face uncertainty: customer counts vary day-to-day, wastage fluctuates, sales mix shifts. A single "profit = 12,430 PLN" answer hides the range of possible outcomes.
v0.4 introduces Monte Carlo simulation and scenario snapshots for versioning and comparison.
Monte Carlo simulation - run the same scenario thousands of times with randomized inputs:
- Distribution-based inputs -
daily_customers ~ Normal(100, 15),wastage ~ Normal(5%, 1%), sales shares with variability - Configurable iterations -
marginal simulate "Cafe" --monte-carlo --iterations 10000 - Compare runs -
marginal simulation compare <id1> <id2>shows diff of inputs and outputs side-by-side - Restore state -
marginal simulation restore <id>reverts scenario to snapshot's inputs (useful for "what changed after price hike?")
Once the simulation model is complete, adding an LLM as an analysis layer becomes powerful. The simulator produces structured business data; Claude interprets it and suggests optimizations.
marginal advise <scenario>command - sends full scenario context to Anthropic API, returns 3-5 actionable recommendations- Streaming output - recommendations appear progressively via Rich
- Contextual suggestions - "your wastage of 8% is high for gastro industry; reducing to 5% would save ~2400 PLN annually"
- What-if exploration -
marginal advise --what-if "price up 10%"runs a simulation variant and explains impact - Tool use - Claude can invoke calculations directly, e.g. re-run simulation with modified parameters mid-conversation
Marginal aims to become the tool a small business owner opens before signing a lease or launching a product — the same way developers open a REPL before writing production code. Fast iteration on financial models, with realistic assumptions and AI-assisted interpretation, replacing the "gut feeling + Excel spreadsheet" approach that currently dominates small business planning.
pip install -e ".[dev]"ruff check .
ruff format .pytest # all tests
pytest tests/test_calculations.py # single file
pytest -v # verbose output
pytest -k "margin" # tests matching patternTests use in-memory SQLite for isolation - no impact on your development database.
Every push and pull request triggers GitHub Actions workflow which runs:
ruff check .andruff format --check .pyteston Python 3.13