Skip to content
cashflow-scheduler logo

Cashflow Scheduler

cashflow-scheduler

Optimize 30-day work schedules for cashflow management using CP-SAT and DP solvers. Use this skill when users need to plan work schedules around bills, deposits, and target balances while minimizing workdays and maximizing rest distribution.

SKILL.md

Full skill instructions

Cashflow Scheduler

Overview

This skill provides constraint-based work schedule optimization for 30-day cashflow planning. It solves the problem: "Given my bills, deposits, and target ending balance, what's the optimal work schedule that minimizes workdays while maintaining positive daily balances?"

Key Features:

  • Dual solvers: CP-SAT (OR-Tools) primary with automatic DP fallback
  • Lexicographic optimization: Minimize workdays → back-to-back work → distance from target
  • Constraint validation: Ensures non-negative balances, rent guard, and final band requirements
  • Self-contained: Works immediately, no installation required (OR-Tools optional)

When to use this skill:

  • User needs to plan gig work schedule (e.g., DoorDash, Uber, freelance)
  • Optimizing work-life balance while meeting financial obligations
  • Ensuring enough cash for rent while saving towards target
  • Comparing different financial scenarios
  • Validating existing schedules against constraints

Quick Start

Generate a Default Schedule (Fastest)

Want to see it work immediately? Run this:

from core import solve, Plan, Bill, Deposit, to_cents, cents_to_str

# Default plan from plan.json (real-world example)
plan = Plan(
    start_balance_cents=to_cents(90.50),
    target_end_cents=to_cents(90.50),
    band_cents=to_cents(100.0),
    rent_guard_cents=to_cents(1636.0),
    deposits=[
        Deposit(day=10, amount_cents=to_cents(1021.0)),
        Deposit(day=24, amount_cents=to_cents(1021.0))
    ],
    bills=[
        Bill(day=1, name="Auto Insurance", amount_cents=to_cents(108.0)),
        Bill(day=2, name="YouTube Premium", amount_cents=to_cents(8.0)),
        Bill(day=5, name="Groceries", amount_cents=to_cents(112.5)),
        Bill(day=5, name="Weed", amount_cents=to_cents(20.0)),
        Bill(day=6, name="Electric", amount_cents=to_cents(139.0)),
        Bill(day=8, name="Paramount Plus", amount_cents=to_cents(12.0)),
        Bill(day=8, name="iPad AppleCare", amount_cents=to_cents(8.49)),
        Bill(day=10, name="Streaming Svcs", amount_cents=to_cents(230.0)),
        Bill(day=10, name="AI Subscription", amount_cents=to_cents(220.0)),
        Bill(day=11, name="Cat Food", amount_cents=to_cents(40.0)),
        Bill(day=12, name="Groceries", amount_cents=to_cents(112.5)),
        Bill(day=12, name="Weed", amount_cents=to_cents(20.0)),
        Bill(day=14, name="iPad AppleCare", amount_cents=to_cents(8.49)),
        Bill(day=16, name="Cat Food", amount_cents=to_cents(40.0)),
        Bill(day=19, name="Groceries", amount_cents=to_cents(112.5)),
        Bill(day=19, name="Weed", amount_cents=to_cents(20.0)),
        Bill(day=22, name="Cell Phone", amount_cents=to_cents(177.0)),
        Bill(day=23, name="Cat Food", amount_cents=to_cents(40.0)),
        Bill(day=25, name="Ring Subscription", amount_cents=to_cents(10.0)),
        Bill(day=26, name="Groceries", amount_cents=to_cents(112.5)),
        Bill(day=26, name="Weed", amount_cents=to_cents(20.0)),
        Bill(day=28, name="iPhone AppleCare", amount_cents=to_cents(13.49)),
        Bill(day=29, name="Internet", amount_cents=to_cents(30.0)),
        Bill(day=29, name="Cat Food", amount_cents=to_cents(40.0)),
        Bill(day=30, name="Rent", amount_cents=to_cents(1636.0))
    ],
    actions=[None] * 30,
    manual_adjustments=[],
    locks=[],
    metadata={}
)

schedule = solve(plan)

# Show results
w, b2b, diff = schedule.objective
print(f"✅ Schedule created!")
print(f"Workdays: {w}")
print(f"Schedule: {' '.join(schedule.actions)}")
print(f"Work on days: {[i+1 for i, a in enumerate(schedule.actions) if a == 'Spark']}")

Or use the helper script: python examples/​create_default.py

Then customize it! Adjust bills, deposits, or target balance and re-solve.


Adjust Mid-Month (Primary Use Case)

Scenario: You're on day 20 and have $230 actual balance. What should you do for the rest of the month?

Use adjust_from_day(): This function handles mid-month adjustments automatically.

from core import adjust_from_day

# Your original plan (from above, or load from JSON)
# plan = Plan(...)

# Adjust from current day with actual balance
new_schedule = adjust_from_day(
    original_plan=plan,
    current_day=20,
    current_eod_balance=230.00  # Your actual balance today
)

# See what to do for days 21-30
print(f"Days 21-30: {' '.join(new_schedule.actions[20:])}")
work_days_remaining = [i+1 for i, a in enumerate(new_schedule.actions[20:], start=20) if a == 'Spark']
print(f"Work on days: {work_days_remaining}")

How it works:

  1. Solves baseline schedule for the full month
  2. Locks days 1-20 to baseline schedule
  3. Adds adjustment to match your actual $230 balance on day 20
  4. Re-solves days 21-30 optimally

Result: You get an updated schedule that accounts for your actual financial position!


Basic Usage

from core import solve, Plan, Bill, Deposit, to_cents, cents_to_str

# Create a plan
plan = Plan(
    start_balance_cents=to_cents(100.00),
    target_end_cents=to_cents(500.00),
    band_cents=to_cents(25.0),
    rent_guard_cents=to_cents(1600.0),
    deposits=[
        Deposit(day=11, amount_cents=to_cents(1021.0)),
        Deposit(day=25, amount_cents=to_cents(1021.0))
    ],
    bills=[
        Bill(day=1, name="Insurance", amount_cents=to_cents(177.0)),
        Bill(day=30, name="Rent", amount_cents=to_cents(1636.0))
    ],
    actions=[None] * 30,  # Let solver decide all days
    manual_adjustments=[],
    locks=[],
    metadata={}
)

# Solve (auto-selects CP-SAT, falls back to DP if OR-Tools unavailable)
schedule = solve(plan)

# Display results
print(f"Workdays: {schedule.objective[0]}")
print(f"Back-to-back pairs: {schedule.objective[1]}")
print(f"Distance from target: ${cents_to_str(schedule.objective[2])}")
print(f"Schedule: {' '.join(schedule.actions)}")

Loading from JSON

import json
from pathlib import Path
from core import Plan, Bill, Deposit, to_cents, solve

def load_plan(path: Path) -> Plan:
    with open(path) as f:
        data = json.load(f)

    return Plan(
        start_balance_cents=to_cents(data['start_balance']),
        target_end_cents=to_cents(data['target_end']),
        band_cents=to_cents(data['band']),
        rent_guard_cents=to_cents(data['rent_guard']),
        deposits=[Deposit(day=d['day'], amount_cents=to_cents(d['amount']))
                  for d in data['deposits']],
        bills=[Bill(day=b['day'], name=b['name'],
                    amount_cents=to_cents(b['amount']))
               for b in data['bills']],
        actions=data.get('actions', [None] * 30),
        manual_adjustments=[],
        locks=[],
        metadata=data.get('metadata', {})
    )

# Usage
plan = load_plan(Path('plan.json'))
schedule = solve(plan)

Core Concepts

Plan Object

Defines the problem constraints:

  • start_balance_cents: Starting cash on Day 1
  • target_end_cents: Desired ending balance on Day 30
  • band_cents: Tolerance around target (final must be in [target-band, target+band])
  • rent_guard_cents: Minimum balance required before paying rent on Day 30
  • deposits: List of scheduled cash inflows (paychecks, deposits)
  • bills: List of scheduled cash outflows (rent, utilities, etc.)
  • actions: Optional pre-filled days (None = solver decides, "O" = off, "Spark" = work)
  • manual_adjustments: One-time corrections (e.g., refunds, found money)

Schedule Object

Contains the solution:

  • actions: 30-element list of "O" (off) or "Spark" (work at $100/​day)
  • objective: Tuple of (workdays, back_to_back_pairs, abs_diff_from_target)
  • final_closing_cents: Final balance on Day 30
  • ledger: Daily ledger entries with opening/​closing balances

Constraints

Hard constraints (must satisfy):

  1. Day 1 must be "Spark" (work day)
  2. Daily closing balances must be ≥ 0
  3. Day 30 pre-rent balance must be ≥ rent_guard
  4. Final balance must be in [target - band, target + band]

Soft constraints (optimized lexicographically):

  1. Minimize total workdays
  2. Minimize back-to-back work pairs (prefer alternating work/​rest)
  3. Minimize absolute difference from exact target

Solver Selection

The skill includes two solvers with automatic selection:

# Auto-select (default): Try CP-SAT, fall back to DP
schedule = solve(plan)  # Recommended

# Force DP only
schedule = solve(plan, solver="dp")

# Force CP-SAT (raises error if OR-Tools unavailable)
schedule = solve(plan, solver="cpsat")

Solver comparison:

  • CP-SAT: Faster on complex plans, requires OR-Tools (pip install ortools)
  • DP: Pure Python, always available, slightly slower on large problems
  • Both produce identical optimal results (proven equivalent)

Validation

Always validate schedules before trusting them:

from core import validate

report = validate(plan, schedule)

if report.ok:
    print("✅ Schedule is valid")
else:
    print("❌ Validation failed:")
    for name, ok, detail in report.checks:
        if not ok:
            print(f"  ✗ {name}: {detail}")

Validation checks:

  • Day 1 is "Spark"
  • All daily balances non-negative
  • Final balance within band
  • Day 30 pre-rent guard satisfied

Example Workflows

Workflow 1: Basic Schedule Optimization

User request: "I need to plan my October work schedule. I have rent on the 30th and want to save $500."

Steps:

  1. Create Plan with bills, deposits, targets
  2. Solve with solve(plan)
  3. Validate result
  4. Display work schedule

See: examples/​solve_basic.py

Workflow 2: Load Existing Plan

User request: "Here's my plan.json file, can you optimize it?"

Steps:

  1. Load JSON into Plan object
  2. Solve
  3. Show ledger with daily balances

See: examples/​solve_from_json.py

Workflow 3: Compare Scenarios

User request: "What if I delay my internet bill to the 15th instead of the 5th?"

Steps:

  1. Create two Plan variants
  2. Solve both
  3. Compare objectives and schedules

See: examples/​compare_solvers.py (adapt for scenario comparison)

Workflow 4: Interactive Plan Creation

User request: "Help me create a plan from scratch."

Steps:

  1. Use examples/​interactive_create.py for guided prompts
  2. Build Plan incrementally
  3. Solve and save to JSON

See: examples/​interactive_create.py

Workflow 5: Iterative Adjustments

User request: "Can you make me work less?" or "What if I move my internet bill to the 15th?"

The skill is designed for iterative refinement:

# 1. Start with a schedule
schedule = solve(plan)
print(f"Current: {schedule.objective[0]} workdays")

# 2. User wants fewer workdays? Increase target tolerance
plan.band_cents = to_cents(150.0)  # Was 100.0
schedule = solve(plan)
print(f"Adjusted: {schedule.objective[0]} workdays")

# 3. User wants to move a bill? Update and re-solve
# Find and update bill
for bill in plan.bills:
    if bill.name == "Internet":
        plan.bills.remove(bill)
        plan.bills.append(Bill(day=15, name="Internet", amount_cents=bill.amount_cents))
        break

schedule = solve(plan)
print(f"After moving bill: {schedule.objective[0]} workdays")

# 4. User wants specific days off? Lock them
plan.actions[5:8] = ["O", "O", "O"]  # Lock days 6-8 as off
schedule = solve(plan)
print(f"With days 6-8 off: {schedule.objective[0]} workdays")

Common adjustment patterns:

  • Fewer workdays? Increase band_cents or lower target_end_cents
  • Different work days? Lock specific days with plan.actions[day-1] = "Spark" or "O"
  • Move bills? Modify plan.bills list and re-solve
  • Add income? Append to plan.deposits and re-solve
  • Account for one-time cash? Use plan.manual_adjustments

Common Issues

"No feasible schedule found"

Causes:

  • Bills exceed available income (start + deposits + max work income)
  • target_end too high relative to available funds
  • band too tight (try increasing from $25 to $50)
  • rent_guard too high (reduce to just cover rent)

Solutions:

  1. Increase band: plan.band_cents = to_cents(50.0)
  2. Lower target: plan.target_end_cents = to_cents(450.0)
  3. Add deposit: plan.deposits.append(Deposit(day=15, amount_cents=to_cents(200.0)))
  4. Reduce bills or remove locked actions

See: references/​troubleshooting.md

"OR-Tools CP-SAT not installed"

Cause: OR-Tools library missing and dp_fallback=False

Solution:

pip install ortools

Or use auto-fallback:

schedule = solve(plan)  # Auto-falls back to DP

Schedule has negative balance

Cause: Validation bug (should be impossible with correct solver)

Solution: Re-solve with fresh Plan, report as bug if persistent

Available Resources

Example Plans

Located in assets/​example_plans/:

  • simple_plan.json - Basic example with minimal bills

Example Scripts

Located in examples/:

  • solve_basic.py - Basic solve workflow
  • solve_from_json.py - Load and solve JSON plans
  • compare_solvers.py - Benchmark DP vs CP-SAT
  • interactive_create.py - Interactive plan builder

Reference Documentation

Located in references/:

  • plan_schema.md - Complete JSON schema documentation
  • constraints.md - Constraint system details
  • troubleshooting.md - Common issues and solutions

API Reference

Core Functions

solve(plan, solver="auto", **kwargs)

Main solver entry point for full-month scheduling.

Args:

  • plan (Plan): Problem definition
  • solver (str): "auto", "dp", or "cpsat"
  • **kwargs: Solver-specific options

Returns: Schedule object

Raises: RuntimeError if no solution exists

adjust_from_day(original_plan, current_day, current_eod_balance, solver="auto", **kwargs)

PRIMARY FUNCTION for mid-month adjustments. Use when you know your actual balance on a specific day.

Args:

  • original_plan (Plan): The original full-month plan
  • current_day (int): Current day number (1-30)
  • current_eod_balance (float): Your actual end-of-day balance in dollars
  • solver (str): "auto", "dp", or "cpsat"
  • **kwargs: Solver-specific options

Returns: Schedule object with days 1-current_day locked and days current_day+1 to 30 re-optimized

Raises:

  • ValueError if current_day not in 1-30
  • RuntimeError if no feasible schedule exists

Example:

# You're on day 20 with $230 actual balance
new_schedule = adjust_from_day(plan, current_day=20, current_eod_balance=230.0)
print(f"Days 21-30: {' '.join(new_schedule.actions[20:])}")
validate(plan, schedule)

Validate a schedule against constraints.

Returns: ValidationReport with ok status and check details

to_cents(amount)

Convert dollars to integer cents.

Args: amount (float | int | str | Decimal)

Returns: int (cents)

cents_to_str(cents)

Convert integer cents to dollar string.

Args: cents (int)

Returns: str (e.g., "123.45")

Data Classes

Plan

Problem definition.

Fields: start_balance_cents, target_end_cents, band_cents, rent_guard_cents, deposits, bills, actions, manual_adjustments, locks, metadata

Schedule

Solution.

Fields: actions, objective, final_closing_cents, ledger

Bill

Scheduled outflow.

Fields: day (1-30), name (str), amount_cents (int)

Deposit

Scheduled inflow.

Fields: day (1-30), amount_cents (int)

Advanced Usage

Using CP-SAT Options

from core.cpsat_solver import CPSATSolveOptions

options = CPSATSolveOptions(
    max_time_seconds=30.0,
    num_search_workers=4,
    log_search_progress=True
)

schedule = solve(plan, solver="cpsat", options=options)

Locking Specific Days

# Force Day 1 to work, Day 2 to off, let solver decide rest
plan.actions = ["Spark", "O"] + [None] * 28

schedule = solve(plan)

Manual Adjustments

from core import Adjustment

# One-time correction on Day 15
plan.manual_adjustments = [
    Adjustment(day=15, amount_cents=to_cents(-50.0), note="Venmo refund")
]

schedule = solve(plan)

Tips for Success

  1. Start simple: Use example plans as templates
  2. Validate always: Check report.ok before trusting results
  3. Use auto-fallback: Default solver="auto" ensures robustness
  4. Increase band if infeasible: Try $50+ instead of $25
  5. Check total funds: Ensure start + deposits >= bills + target
  6. Prefer fewer locks: Only lock days if absolutely necessary

See Also

More Finance & Trading skills

ponytail-debt logo
Finance & Trading

ponytail-debt

Harvest every `ponytail:` comment in the codebase into a debt ledger, so the deliberate shortcuts and deferrals ponytail leaves behind get tracked instead of rotting into "later means never". One-shot report, changes nothing.

155.5K 27.9K
View
stripe-integration logo
Finance & Trading

stripe-integration

Stripe payment processing with checkout sessions, subscriptions, webhooks, and customer management.

40.2K 12.9K
View

Build comprehensive 3-5 year financial models with revenue projections, cost structures, cash flow analysis, and scenario planning for early-stage startups. Use this skill when creating financial projections, calculating burn rate or runway, modeling fundraising scenarios, or preparing investor-r...

40.2K 12K
View
stock-analysis logo
Finance & Trading

stock-analysis

Stock and cryptocurrency analysis with portfolio management and multi-dimensional scoring.

46 11.9K
View
coinglass logo
Finance & Trading

coinglass

Real-time crypto derivatives positioning, whale tracking, liquidations, and institutional ETF flows across 37 specialized tools.

27 11.5K
View

Master dbt (data build tool) for analytics engineering with model organization, testing, documentation, and incremental strategies. Use when building data transformations, creating data models, or implementing analytics engineering best practices.

40.2K 10.5K
View

Finance AI tools

C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Finalle.ai logo
Finance

Finalle.ai

Stay Ahead with Finalle.ai: The Ultimate Financial News and Tools Platform

Free
View

AI-powered Pine Script code generator for TradingView

Paid
View
TradeUI logo
Finance

TradeUI

Empower your investment strategies with AI signals, option flow data, and advanced chart patterns.

Paid
View
D
Finance

Danelfin

Danelfin review covering its AI stock and ETF scores, rankings, portfolio tools, current pricing, strengths, risks, and alternatives for investors.

Freemium
View
E
Finance

Ellisense

A software for comprehensive market sentiment analysis.

Paid
View

'Stock Correlations' App by Freshly.ai: Reveal Stock Patterns and Trends

Free
View