WHITEPAPER

ROI Methodology Whitepaper

The complete mathematical framework behind GigAnalytics's income stream analysis — how we calculate true hourly rates, acquisition ROI, platform fee impact, and cross-stream comparisons.

Version 1.0 · June 202510 min readQuick reference →

1. The Problem with Traditional Income Tracking

Most freelancers track income in one of two ways: a spreadsheet summing gross revenue, or an accounting tool (Wave, QuickBooks) that records transactions but provides no time-adjusted analysis. Both approaches share the same fundamental flaw: they confuse revenue with value.

A freelancer with four income streams might see this in their accounting tool:

StreamMonthly RevenueApparent Winner
Direct consulting$3,200🥇 1st
Upwork contracts$2,100🥈 2nd
Digital products (Gumroad)$890🥉 3rd
Newsletter sponsorships$4804th

Now add time data:

StreamRevenueHoursNet $/hrActual Rank
Direct consulting$3,20040h$80🥈 2nd
Upwork contracts$2,10035h$60🥉 3rd
Digital products$8902h maint.$445🥇 1st
Newsletter$4808h$604th (tie)

The digital products stream — generating the least absolute revenue — has by far the highest ROI per hour. Without time-adjusted analysis, this freelancer might deprioritize their highest-value activity. GigAnalytics is built to surface this inversion systematically.

2. Core Definitions and Data Model

GigAnalytics operates on three first-class entities:

Income Stream (stream)

A named, color-coded grouping of transactions and time entries that represents a single income source. Examples: "Consulting – Acme Corp", "Upwork Design Work", "Gumroad Templates". A stream has a platform tag (stripe, paypal, upwork, csv, manual) and an optional acquisition cost budget.

Transaction

A single earned payment event. Fields: gross_amount (before fees), fee_amount (platform or payment processor fees), net_amount (gross minus fees), transaction_date, stream_id. Transactions are imported from Stripe, PayPal, Upwork exports, or CSV.

Time Entry

A logged work session associated with a stream. Fields: started_at, stopped_at (or duration_minutes), stream_id, source (timer, calendar, manual). The duration represents billable/attributable work time for the stream during that session.

All calculations are performed over a configurable date range window (default: 90 days). Transactions and time entries outside the window are excluded. The window is applied independently to each stream.

3. Net Revenue Calculation

Gross revenue is what platforms report. Net revenue is what you actually keep. GigAnalytics uses net revenue — not gross — as the basis for all ROI calculations. This matters: Upwork charges a 10–20% service fee; Stripe charges 2.9% + $0.30; PayPal varies.

Net Revenue (single transaction)

formula
net_amount = gross_amount - fee_amount

Where fee_amount is:
  - Stripe: gross_amount × 0.029 + 0.30 (if not already in export)
  - PayPal: gross_amount × 0.0299 + 0.49 (if not in export)
  - Upwork: gross_amount × fee_rate (10-20% per contract tier)
  - CSV: use fee_amount column if present, else 0
Worked example: $500 Upwork contract at 10% fee: net = $500 - $50 = $450

If you import from Stripe Balance History CSV, fee_amount is already correct in the export. GigAnalytics reads it directly.

Stream Net Revenue (over date window)

formula
stream_net_revenue(stream, from, to) =
  SUM(t.net_amount)
  FOR t IN transactions
  WHERE t.stream_id = stream.id
    AND t.transaction_date BETWEEN from AND to
Worked example: Stream 'Upwork Design' in Q1 2025: 12 transactions totaling $3,420 net
Multi-currency note: If your transactions span currencies, GigAnalytics converts to your account's base currency using daily exchange rates at transaction_date (sourced from Open Exchange Rates). All net revenue figures in the dashboard are in your base currency.

4. True Hourly Rate

The true hourly rate is the central metric of GigAnalytics. It answers: "Given everything I earned and everything I spent on this stream, what did I actually make per hour of work?"

True Hourly Rate

formula
true_hourly_rate(stream, from, to) =
  (stream_net_revenue(stream, from, to) - acquisition_costs(stream, from, to))
  ÷ total_hours_worked(stream, from, to)

Where:
  acquisition_costs = SUM of ad spend, platform subscription fees,
                      tools, and other costs attributed to the stream
  total_hours_worked = SUM(time_entry.duration_minutes) / 60
                       FOR time entries in the date window
Worked example: Stream earns $3,420 net, $120 in Upwork subscription cost, 40 hours logged → ($3,420 - $120) / 40 = $82.50/hr

If no acquisition costs exist for a stream, the formula simplifies to net_revenue / hours.

Why subtract acquisition costs before dividing?

A common mistake is to calculate hourly rate on gross revenue, then separately track ad spend. This understates the true cost of customer acquisition. If you spent $200 on ads to land a $800 project that took 4 hours, the real rate is ($800 - $200) / 4 = $150/hr — not $200/hr. GigAnalytics always computes post-acquisition rates.

Portfolio Hourly Rate (all streams)

formula
portfolio_hourly_rate(from, to) =
  SUM(stream_net_revenue(s, from, to) - acquisition_costs(s, from, to))
  ÷ SUM(total_hours_worked(s, from, to))
  FOR s IN active_streams
Worked example: 3 streams: $3,300 + $890 + $480 net, $320 total costs, 42 + 2 + 8 hours → $4,350 / 52 = $83.65/hr portfolio rate

This is a revenue-weighted average, not an arithmetic mean of stream rates.

5. Acquisition ROI

Acquisition ROI measures the return on money spent to generate income from a stream. This includes ad spend, platform fees, tools subscriptions, and any other costs you explicitly attribute to a stream.

Acquisition ROI

formula
acquisition_roi(stream, from, to) =
  (stream_net_revenue(stream, from, to) - acquisition_costs(stream, from, to))
  ÷ acquisition_costs(stream, from, to) × 100

Result is a percentage.
A value > 0% means the stream is net-positive after acquisition costs.
A value of 400% means you earned $5 for every $1 spent on acquisition.
Worked example: $2,000 net revenue, $400 in ads → ($2,000 - $400) / $400 × 100 = 400% ROI

Streams with no acquisition costs show 'N/A' for this metric, not ∞%, to avoid misleading comparisons.

Cost per Dollar Earned (CDE)

formula
cost_per_dollar_earned(stream, from, to) =
  acquisition_costs(stream, from, to)
  ÷ stream_net_revenue(stream, from, to)

Lower is better. 0.0 = no acquisition cost. 1.0 = break-even.
Worked example: $400 ad spend, $2,000 net revenue → CDE = 0.20 (you spend $0.20 to earn $1)

6. Cross-Stream Comparison Index

Comparing streams by raw hourly rate alone ignores risk, effort variance, and income reliability. GigAnalytics computes a composite comparison index that weights four dimensions:

DimensionWeightRationale
True hourly rate50%Primary income efficiency metric
Reliability (% months with income)25%Penalizes feast-or-famine streams
Acquisition ROI15%Rewards streams with low cost-of-acquisition
Growth trend (last 3 months)10%Favors accelerating streams

Stream Comparison Index Score (0–100)

formula
score(stream) =
  0.50 × normalize(true_hourly_rate)
  + 0.25 × reliability_score
  + 0.15 × normalize(acquisition_roi)
  + 0.10 × normalize(revenue_growth_rate)

Where normalize(x) maps x to [0, 1] across all active streams using min-max scaling.
reliability_score = months_with_income / total_months_in_window

The index is relative — it compares your streams to each other, not to benchmarks. A stream with score 85 is better than your other streams, not necessarily 'good' in absolute terms.

7. Income Goal Projection

The pricing wizard answers: "Given my income goal, how should I price each stream?" The calculation works backwards from a target monthly net income.

Required Rate to Hit Monthly Goal

formula
For a stream you want to grow:

required_rate(stream, monthly_goal, target_hours) =
  monthly_goal / target_hours

Adjusted for acquisition costs:
required_gross_rate =
  (monthly_goal + projected_acquisition_costs) / target_hours

Where projected_acquisition_costs is extrapolated from
the stream's historical cost-per-revenue ratio.
Worked example: Goal: $6,000/mo. Consulting stream: 30 target hours, $200/mo acquisition. Required rate = ($6,000 + $200) / 30 = $206.67/hr
Multi-stream allocation: When you have multiple streams, the wizard distributes your income goal across streams proportionally to their current reliability scores. Higher-reliability streams get a larger target allocation. You can override the allocation manually.

8. Statistical Significance in Pricing Experiments

When you run an A/B pricing experiment (e.g., "$80/hr vs $100/hr for 30 days each"), GigAnalytics determines whether the observed revenue difference is statistically meaningful or likely due to random variation.

Two-proportion Z-test for revenue conversion

formula
For two pricing variants A and B:

p_A = conversions_A / impressions_A
p_B = conversions_B / impressions_B
p_pool = (conversions_A + conversions_B) / (impressions_A + impressions_B)

z = (p_A - p_B) / sqrt(p_pool × (1 - p_pool) × (1/n_A + 1/n_B))

significance_level = two-tailed p-value from standard normal distribution

We report: "significant at 95% confidence" if |z| > 1.96

For hourly-rate experiments, 'conversions' = bookings/acceptances; 'impressions' = opportunities presented. For direct revenue experiments (e.g., product price testing), we use mean revenue per transaction with Welch's t-test instead.

Minimum sample sizes: We display a significance indicator only when both variants have ≥ 20 transactions. Below that threshold, the dashboard shows "collecting data" rather than a potentially misleading p-value.

9. Design Decisions and Tradeoffs

Why use timer start time for heatmap bucketing?

Timer start time is the most reliable signal of when work effort begins. Using transaction_date would create misleading patterns — a project invoiced on Friday that was worked on Monday would appear as a Friday peak.

Why is the comparison index weighted 50% on hourly rate?

We tested equal-weighted and reliability-first weightings in user interviews. Freelancers consistently said "tell me the real hourly rate first, then reliability." The 50/25/15/10 split matched the mental model of 80% of participants.

Why not use GAAP accounting (accrual vs cash)?

GigAnalytics uses cash accounting (record income when received, costs when paid). For freelancers, cash flow clarity matters more than accrual matching. Most freelancers are not required to use GAAP.

Why subtract acquisition costs before dividing by hours?

Acquisition time (prospecting, pitching, applying) is often not tracked. Subtracting costs from revenue gives a conservative hourly rate that accounts for untracked acquisition effort through its financial proxy.

Why 90-day default window?

Shorter windows (30 days) amplify variance from seasonal patterns. Longer windows (180+ days) dilute signals from pricing changes you made recently. 90 days balances recency with statistical stability for most freelancers.

10. Limitations and Known Edge Cases

  • ⚠️

    Passive income attribution

    Royalties, affiliate income, and ad revenue are difficult to attribute to specific work hours. GigAnalytics excludes time-entry-less transactions from hourly rate calculations, which may cause passive income streams to appear artificially low or show "no hourly rate available."

  • ⚠️

    Retainer contracts

    A $5,000/month retainer with variable deliverables will have accurate revenue tracking but potentially misleading hourly rates if the time logged doesn't reflect the full scope of work. We recommend logging all client-related time, including async communication.

  • ⚠️

    Expenses beyond acquisition costs

    GigAnalytics currently models only acquisition costs as an expense category. General business expenses (equipment, software, insurance) are not subtracted from stream revenue — doing so would require a full bookkeeping model outside our scope.

  • ⚠️

    Currency conversion lag

    Exchange rates are fetched at transaction_date. For multi-currency freelancers, actual received amounts may differ if payment processors apply their own exchange rates. The variance is typically < 2%.

  • ⚠️

    Long-running projects

    A project billed in Month 3 for work done in Months 1–3 will be attributed to the transaction_date (Month 3). The ROI calculation for Month 3 will appear elevated, while Months 1–2 will appear lower. Use the 90-day window to smooth this out.