DEV Community

Cover image for Ridesharing profit calculator: architecture deep dive
SignalFast
SignalFast

Posted on • Originally published at driverprofit.ro

Ridesharing profit calculator: architecture deep dive

TL;DR

  • A ridesharing profit calculator lives or dies on cost modeling: commissions, fuel/charging, maintenance, depreciation, and taxes.
  • Treat calculations as a pure domain service with deterministic inputs; keep UI and storage separate.
  • Use money-safe rounding, explicit units, and a transparent breakdown to build trust.
  • Add “should I stay online?” recommendations as a separate layer that consumes computed metrics.

Building a ridesharing profit calculator that drivers trust

If you’ve ever shipped financial logic, you know the hardest part isn’t math—it’s assumptions. A ridesharing profit calculator for Uber/Bolt drivers in Romania has to reconcile:

  • platform “earnings” vs. real profit
  • daily and monthly costs
  • time-based metrics (profit/hour, cost/hour)
  • incomplete inputs (drivers don’t track everything)

This post is a technical deep dive into how I’d structure a ridesharing profit calculator like DriverProfit (driverprofit.ro): domain model, calculation engine, architecture boundaries, and a recommendation layer that answers “merită să mai stau pe tură?”.

Problem framing: why “earnings” is the wrong primitive

Most driver apps optimize for gross: “you made X today.” A ridesharing profit calculator must optimize for net.

The challenge: costs are heterogeneous.

  • Variable per shift: fuel/charging, tolls, parking, car washes.
  • Semi-variable: maintenance that correlates with kilometers.
  • Fixed: insurance, licensing, phone plan.
  • Platform-related: commission, incentives, cancellation penalties.

To keep results defensible, you need two things:
1) a clear cost taxonomy
2) consistent allocation rules

For authoritative grounding on units/rounding and currency pitfalls, see the guidance around floating-point issues and currency arithmetic in IEEE 754 floating-point and consider implementing money as integers (minor units).

Solution architecture: split domain, app, and UI

A ridesharing profit calculator should be boring to test and hard to break. The architecture I’ve found cleanest is:

  • Domain layer: types + pure calculation functions
  • Application layer: input normalization, orchestration, persistence
  • UI layer: forms, charts, explanations

This makes it straightforward to:

  • unit test the math without a browser
  • version calculation rules
  • add new cost categories without rewriting UI

Domain model: make units explicit

You don’t need a huge DDD ceremony, but you do need explicit data structures.

// Domain types (TypeScript)
export type RON = number; // stored as bani in practice; see below

export interface ShiftInput {
  startedAt: string;        // ISO
  endedAt: string;          // ISO
  trips: number;
  grossEarningsRon: RON;    // what driver sees as “incasari”
  platformCommissionRon?: RON;
  tipsRon?: RON;

  fuelOrChargingRon?: RON;
  tollsRon?: RON;
  parkingRon?: RON;

  kmDriven?: number;
  maintenancePerKmRon?: RON; // optional heuristic

  fixedDailyRon?: RON;        // allocated daily fixed costs
  taxesRon?: RON;             // if driver wants to model it
}

export interface ShiftResult {
  durationMinutes: number;
  totalCostsRon: RON;
  netProfitRon: RON;
  profitPerHourRon: RON;
  costPerHourRon: RON;
  costBreakdown: Record<string, RON>;
}
Enter fullscreen mode Exit fullscreen mode

Notice the tension: you’ll get partial inputs. That’s normal. Your calculator should work with “minimum viable truth” (gross + hours + one or two costs), then progressively refine.

Money and rounding: avoid floating-point drift

Currency math with floats will betray you at the worst time (e.g., a 0.01 RON mismatch that flips a recommendation). A ridesharing profit calculator should store money in bani (integer minor units) and only format at the edges.

// Store 12.34 RON as 1234 bani
export type Bani = number;

export const ronToBani = (ron: number): Bani => Math.round(ron * 100);
export const baniToRon = (bani: Bani): number => bani / 100;

export const add = (...xs: Bani[]) => xs.reduce((a, b) => a + b, 0);
Enter fullscreen mode Exit fullscreen mode

If you’re in JS/TS and want an established approach, the community often uses integer minor units or dedicated libraries; the key is deterministic arithmetic.

Implementation: a pure calculation engine

Treat the calculator as a pure function: input → output, no I/O.

export function computeShift(input: ShiftInput): ShiftResult {
  const started = new Date(input.startedAt).getTime();
  const ended = new Date(input.endedAt).getTime();
  const durationMinutes = Math.max(0, Math.round((ended - started) / 60000));
  const hours = Math.max(durationMinutes / 60, 1 / 60); // avoid division by zero

  const costs: Record<string, number> = {
    platformCommissionRon: input.platformCommissionRon ?? 0,
    fuelOrChargingRon: input.fuelOrChargingRon ?? 0,
    tollsRon: input.tollsRon ?? 0,
    parkingRon: input.parkingRon ?? 0,
    fixedDailyRon: input.fixedDailyRon ?? 0,
    taxesRon: input.taxesRon ?? 0,
  };

  // Optional heuristic: maintenance cost modeled per km
  if (input.kmDriven != null && input.maintenancePerKmRon != null) {
    costs.maintenanceRon = input.kmDriven * input.maintenancePerKmRon;
  }

  const totalCostsRon = Object.values(costs).reduce((a, b) => a + b, 0);

  // Decide: do you treat tips as part of gross or separate? Be explicit.
  const grossRon = input.grossEarningsRon + (input.tipsRon ?? 0);
  const netProfitRon = grossRon - totalCostsRon;

  return {
    durationMinutes,
    totalCostsRon,
    netProfitRon,
    profitPerHourRon: netProfitRon / hours,
    costPerHourRon: totalCostsRon / hours,
    costBreakdown: costs,
  };
}
Enter fullscreen mode Exit fullscreen mode

For a production ridesharing profit calculator, I’d implement the same logic in bani and return formatted numbers for the UI. But the structure above shows the separation clearly.

Validation and normalization: keep the domain forgiving

Drivers will enter “2,5” hours, forget to add commission, or input negative values by mistake. Handle this before computeShift.

  • Coerce decimal commas → dots
  • Clamp negatives to zero (or show errors)
  • Enforce a maximum duration (e.g., 0–24h)
export function normalizeMoney(input: unknown): number {
  if (input == null) return 0;
  const s = String(input).trim().replace(/,/g, ".");
  const n = Number(s);
  return Number.isFinite(n) ? Math.max(0, n) : 0;
}
Enter fullscreen mode Exit fullscreen mode

A ridesharing profit calculator that silently accepts nonsense will lose credibility quickly. My preference: normalize lightly, but show warnings (e.g., “endedAt earlier than startedAt”).

Recommendation layer: “should I stay online?” without magic

Once you have netProfitRon and profitPerHourRon, recommendations become a policy problem.

Keep it separate:

  • Calculator: computes metrics
  • Advisor: interprets metrics with thresholds

Example policy:

  • if profit/hour < driver’s minimum target for 30+ minutes → suggest ending shift
  • if fuel cost share > 35% → suggest route/vehicle efficiency review
  • if platform commission share spikes → check incentive structure or time window
export interface Advice {
  status: "good" | "watch" | "stop";
  message: string;
  reasons: string[];
}

export function advise(result: ShiftResult, minProfitPerHourRon: number): Advice {
  const reasons: string[] = [];

  if (result.durationMinutes >= 30 && result.profitPerHourRon < minProfitPerHourRon) {
    reasons.push(`Profit/oră (${result.profitPerHourRon.toFixed(2)} RON) sub prag.`);
  }

  const fuel = result.costBreakdown.fuelOrChargingRon ?? 0;
  if (result.totalCostsRon > 0 && fuel / result.totalCostsRon > 0.35) {
    reasons.push("Combustibil/încărcare reprezintă o pondere mare din costuri.");
  }

  const status = reasons.length === 0 ? "good" : (reasons.length === 1 ? "watch" : "stop");
  const message = status === "good"
    ? "Tura arată bine pe baza datelor introduse."
    : "Merită să reevaluezi tura pe baza indicatorilor.";

  return { status, message, reasons };
}
Enter fullscreen mode Exit fullscreen mode

This approach avoids pretending you can predict the market; you’re simply encoding transparent rules.

Data points and transparency: show your work

A ridesharing profit calculator should present a breakdown that helps drivers act:

  • Profit net
  • Cost total pe zi
  • Câștig pe oră
  • Top 3 cost drivers

A small UI trick: add a “What’s included?” drawer so users see assumptions.

For external references on time handling and ISO formats, the MDN Date documentation is a useful baseline—though many apps move to better time libraries for production.

What I learned (and what surprised me)

  • Drivers trust breakdowns more than totals. A single net number feels like a black box; a category list feels auditable.
  • Allocation beats precision. Even with perfect rounding, if fixed costs aren’t allocated consistently, shift comparisons become noise.
  • Defaults should be conservative. If a user omits maintenance and depreciation, don’t “invent” costs silently; instead prompt them with optional fields.

Gotchas: the edge cases that bite

1) Time windows crossing midnight: storing ISO timestamps prevents “duration = negative” bugs.
2) Commission ambiguity: some drivers treat it as already deducted from earnings. Your ridesharing profit calculator must clarify whether gross is pre- or post-commission.
3) Promos and bonuses: decide if bonuses are part of gross or tracked separately for analytics.
4) Per-km maintenance heuristics: useful, but label them as estimates.

Practical CTA: make your calculator easier to validate

If you’re building your own ridesharing profit calculator (or extending one), add an “Export inputs + results” button (JSON/CSV). It helps users sanity-check numbers, share a shift with a friend, and report bugs with reproducible data. If you’re curious how DriverProfit frames net profit for Uber/Bolt drivers in Romania, explore the calculator at driverprofit.ro and compare two of your recent shifts using the same cost rules.


Originally about ridesharing profit calculator.

Top comments (0)