Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ jobs:
- name: Run the known-answer test suite
run: bash Scripts/test.sh

- name: Coverage gate (no regression beyond the documented baseline)
run: bash Scripts/coverage.sh check

- name: Build the app (release)
run: bash Scripts/build.sh release
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ bidlab-trading-run.*

# Intermediate research briefs (inputs to lesson authoring, not shipped product)
/Research/_briefs/
.build-cov/
79 changes: 79 additions & 0 deletions Scripts/coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Measure BidLabCore code coverage with swiftc profiling + llvm-cov.
#
# SwiftPM is broken on this machine (see lib.sh), so `swift test --enable-code-
# coverage` does not run. Instead we instrument the same swiftc build the test
# runner already uses (-profile-generate -profile-coverage-mapping), run the
# known-answer suite, and emit an llvm-cov report scoped to Sources/BidLabCore.
#
# Usage:
# ./Scripts/coverage.sh # build, run, print the per-file report
# ./Scripts/coverage.sh check # also fail if coverage regresses (the CI gate)
# ./Scripts/coverage.sh show File # annotate uncovered lines in one source file
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SDK="$(xcrun --sdk macosx --show-sdk-path)"
COV="$ROOT/.build-cov"
mkdir -p "$COV"

CORE_SRCS=( "$ROOT"/Sources/BidLabCore/*.swift )
TEST_SRCS=( "$ROOT"/Tests/BidLabCoreTests/*.swift )

echo "[coverage] building instrumented core ..."
swiftc -profile-generate -profile-coverage-mapping -parse-as-library \
-emit-module -emit-library -static -module-name BidLabCore \
-emit-module-path "$COV/BidLabCore.swiftmodule" \
-o "$COV/libBidLabCore.a" "${CORE_SRCS[@]}" -sdk "$SDK"

echo "[coverage] building instrumented test runner ..."
# -force_load pulls every core object (and its coverage map) into the binary,
# so even files not referenced by a test still report honestly instead of vanishing.
swiftc -profile-generate -profile-coverage-mapping -I "$COV" \
-Xlinker -force_load -Xlinker "$COV/libBidLabCore.a" \
-module-name BidLabCoreTests -o "$COV/bidlab-cov" \
"${TEST_SRCS[@]}" -sdk "$SDK"

echo "[coverage] running known-answer suite ..."
export BIDLAB_CONTENT="$ROOT/Content/Lessons"
export BIDLAB_EXAMS="$ROOT/Content/Exams"
LLVM_PROFILE_FILE="$COV/bidlab.profraw" "$COV/bidlab-cov"

xcrun llvm-profdata merge -sparse "$COV/bidlab.profraw" -o "$COV/bidlab.profdata"

if [ "${1:-}" = "show" ] && [ -n "${2:-}" ]; then
xcrun llvm-cov show "$COV/bidlab-cov" -instr-profile="$COV/bidlab.profdata" \
"$ROOT/Sources/BidLabCore/$2" --show-line-counts -use-color=false
exit 0
fi

REPORT=$(xcrun llvm-cov report "$COV/bidlab-cov" \
-instr-profile="$COV/bidlab.profdata" "$ROOT/Sources/BidLabCore")
echo
echo "[coverage] BidLabCore report:"
echo "$REPORT"

# `check` mode (the CI gate): fail if coverage regresses beyond the documented
# unreachable-defensive baseline. The TOTAL row's missed region/function/line
# counts must not exceed these; any new uncovered code raises a count and fails.
# (The 4/2/2 baseline is the unreachable defensive set documented in docs/COVERAGE.md.)
if [ "${1:-}" = "check" ]; then
MAX_MISSED_REGIONS=4
MAX_MISSED_FUNCTIONS=2
MAX_MISSED_LINES=2
TOTAL_LINE=$(echo "$REPORT" | grep -E '^TOTAL' | tail -1 || true)
mr=$(echo "$TOTAL_LINE" | awk '{print $3}')
mf=$(echo "$TOTAL_LINE" | awk '{print $6}')
ml=$(echo "$TOTAL_LINE" | awk '{print $9}')
echo
echo "[coverage] missed regions=${mr:-?} functions=${mf:-?} lines=${ml:-?} (baseline ${MAX_MISSED_REGIONS}/${MAX_MISSED_FUNCTIONS}/${MAX_MISSED_LINES}, all unreachable defensive; see docs/COVERAGE.md)"
if [ -z "$mr" ] || [ -z "$mf" ] || [ -z "$ml" ]; then
echo "[coverage] FAIL: could not parse the coverage TOTAL row."
exit 1
fi
if [ "$mr" -gt "$MAX_MISSED_REGIONS" ] || [ "$mf" -gt "$MAX_MISSED_FUNCTIONS" ] || [ "$ml" -gt "$MAX_MISSED_LINES" ]; then
echo "[coverage] FAIL: BidLabCore coverage regressed beyond the documented unreachable baseline."
exit 1
fi
echo "[coverage] OK: no coverage regression."
fi
26 changes: 12 additions & 14 deletions Sources/BidLabCore/Cohort.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,26 +232,24 @@ public enum Cohort {
/// Roll the cohort up by path, preserving first-seen path order, so a manager
/// can see certification and completion per path across the team.
public static func byPath(_ records: [LearnerRecord]) -> [PathRollup] {
// One accumulator per path, in first-seen order. An id is recorded here the
// first time its track appears, so every id in `order` has an entry and a
// learner count of at least one: no lookup below needs a fallback.
var order: [String] = []
var roles: [String: String] = [:]
var learners: [String: Int] = [:]
var certified: [String: Int] = [:]
var completionSum: [String: Double] = [:]
var accs: [String: (role: String, learners: Int, certified: Int, completion: Double)] = [:]
for r in records {
for t in r.tracks {
if learners[t.trackID] == nil { order.append(t.trackID); roles[t.trackID] = t.role }
learners[t.trackID, default: 0] += 1
if t.certified { certified[t.trackID, default: 0] += 1 }
completionSum[t.trackID, default: 0] += t.completionFraction
if accs[t.trackID] == nil { order.append(t.trackID); accs[t.trackID] = (t.role, 0, 0, 0) }
accs[t.trackID]!.learners += 1
if t.certified { accs[t.trackID]!.certified += 1 }
accs[t.trackID]!.completion += t.completionFraction
}
}
return order.map { id in
let n = learners[id] ?? 0
return PathRollup(
trackID: id, role: roles[id] ?? id, learnerCount: n,
certifiedCount: certified[id] ?? 0,
avgCompletion: n > 0 ? (completionSum[id] ?? 0) / Double(n) : 0
)
let a = accs[id]!
return PathRollup(trackID: id, role: a.role, learnerCount: a.learners,
certifiedCount: a.certified,
avgCompletion: a.completion / Double(a.learners))
}
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/BidLabCore/PacedFlight.swift
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public enum PacedFlight {
spend: r.spend,
cumulativeSpend: cumulativeSpend,
targetCumulativeSpend: Pacing.targetSpend(totalBudget: flightBudget, fractionElapsed: endFraction),
winRate: intervalOpportunities > 0 ? Double(r.impressionsWon) / Double(intervalOpportunities) : 0
winRate: Double(r.impressionsWon) / Double(intervalOpportunities) // intervalOpportunities is max(1, ...), always >= 1
))
}

Expand Down
3 changes: 3 additions & 0 deletions Tests/BidLabCoreTests/BenchmarksTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ func benchmarksTests(_ h: Harness) {
// No em dashes in user-facing benchmark strings.
let strings = catalog.flatMap { [$0.label, $0.value, $0.detail, $0.citation.note ?? ""] }
h.check("no em dashes in figures", strings.allSatisfy { !$0.contains("\u{2014}") })

// Identifiable conformance: a benchmark's id is its key.
h.check("benchmark id is its key", catalog[0].id == catalog[0].key)
}
10 changes: 10 additions & 0 deletions Tests/BidLabCoreTests/CampaignDataTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,14 @@ func campaignDataTests(_ h: Harness) {
h.check("a high-CPA line is costly", CampaignData.verdict(line: costlyLine, blendedCPA: blendedCPA) == .costly)
h.check("a near-blend line is typical", CampaignData.verdict(line: typicalLine, blendedCPA: blendedCPA) == .typical)
h.check("a line with no conversions is flagged", CampaignData.verdict(line: noConvLine, blendedCPA: blendedCPA) == .noConversions)

// Blended CTR/CVR/CPC on the summary (clicks 3500, impressions 150000, conv 250, spend 2400).
h.close("blended CTR", s.ctr, 3500.0 / 150000.0, tol: 1e-12)
h.close("blended CVR", s.cvr, 250.0 / 3500.0, tol: 1e-12)
h.close("blended CPC", s.cpc, 2400.0 / 3500.0, tol: 1e-12)
// An empty summary divides by zero safely (every rate falls back to 0).
let zeroSummary = CampaignData.summarize([])
h.close("empty summary CTR is zero", zeroSummary.ctr, 0, tol: 1e-12)
h.close("empty summary CVR is zero", zeroSummary.cvr, 0, tol: 1e-12)
h.close("empty summary CPC is zero", zeroSummary.cpc, 0, tol: 1e-12)
}
129 changes: 129 additions & 0 deletions Tests/BidLabCoreTests/EdgeCoverageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import BidLabCore

/// Boundary and guard-path coverage for the pure engine. The domain suites prove
/// the math on normal inputs; this suite exercises the defensive branches they do
/// not reach: divide-by-zero guards (which return infinity or zero), empty-input
/// early returns, and the distribution special cases (p == 0, p == 1, sd == 0,
/// and the normal-quantile tails). Every expected value is hand-checked.
func edgeCoverageTests(_ h: Harness) {
// MARK: Pricing divide-by-zero guards
h.check("cpa guards zero CVR", Pricing.cpa(cpc: 1, cvr: 0).isInfinite)
h.close("impressions guard zero CPM is 0", Pricing.impressions(spend: 100, cpm: 0), 0)
h.check("MER guards zero spend", Pricing.mer(totalRevenue: 1, totalSpend: 0).isInfinite)
h.check("payback guards zero margin", Pricing.paybackMonths(cac: 300, monthlyMargin: 0).isInfinite)
h.check("LTV:CAC guards zero CAC", Pricing.ltvToCac(ltv: 900, cac: 0).isInfinite)

// MARK: Scoring guards
h.close("no positive optimum with non-negative profit scores 100", Scoring.score(achievedProfit: 5, optimalProfit: 0), 100)
h.close("no positive optimum with a loss scores 0", Scoring.score(achievedProfit: -5, optimalProfit: 0), 0)
h.close("percentile of an empty field is 0", Scoring.percentile(of: 50, field: []), 0)

// MARK: Auction guards
h.check("first-price below market loses", Auction.firstPrice(bid: 1, value: 5, competingBids: [2]) == .lost)
let noScan = Auction.optimalFirstPriceBid(value: 0) { _ in 1 }
h.check("optimal first-price bid with zero value is (0,0)", noScan.bid == 0 && noScan.expectedProfit == 0)
h.close("shade factor with zero value is 0", Auction.shadeFactor(optimalBid: 1, value: 0), 0)

// MARK: ReachFrequency guards
h.close("effective reach with zero population is 0", ReachFrequency.effectiveReach(population: 0, impressions: 100, minFrequency: 1), 0)
h.close("effective reach with minFrequency < 1 equals total reach",
ReachFrequency.effectiveReach(population: 1000, impressions: 500, minFrequency: 0),
ReachFrequency.reach(population: 1000, impressions: 500), tol: 1e-12)
h.close("reach fraction with zero population is 0", ReachFrequency.reachFraction(population: 0, impressions: 100), 0)

// MARK: Systems guards and latency tails
h.close("latency quantile at p <= 0 is 0", Systems.latencyQuantile(meanMs: 10, p: 0), 0)
h.check("latency quantile at p >= 1 is infinite", Systems.latencyQuantile(meanMs: 10, p: 1).isInfinite)
h.close("timeout loss guards zero mean", Systems.timeoutLossRate(meanMs: 0, timeoutMs: 100), 0)
h.close("capacity guards zero service time", Systems.capacityQPS(workers: 4, serviceMeanMs: 0), 0)
h.check("utilization guards zero capacity", Systems.utilization(offeredQPS: 100, capacityQPS: 0).isInfinite)
h.check("M/M/1 guards zero service time", Systems.mm1ResponseTimeMs(arrivalQPS: 10, serviceMeanMs: 0).isInfinite)
h.check("cost per query guards zero volume", Systems.costPerQuery(monthlyCost: 1000, monthlyQueries: 0).isInfinite)

// MARK: Analytics guards and degenerate fits
h.check("sample size guards zero MDE", Analytics.sampleSizePerArm(baselineRate: 0.1, mde: 0) == 0)
h.check("power guards zero n", Analytics.power(baselineRate: 0.1, mde: 0.02, nPerArm: 0) == 0)
h.close("hill saturation at zero spend is 0", Analytics.hillSaturation(0, halfSaturation: 5), 0)
let degenFit = Analytics.linearFit(xs: [1], ys: [2])
h.check("linear fit needs two points", degenFit.slope == 0 && degenFit.intercept == 0 && degenFit.r2 == 0)
let flatX = Analytics.linearFit(xs: [3, 3, 3], ys: [1, 2, 3])
h.check("vertical scatter has zero slope", flatX.slope == 0)
h.close("vertical scatter intercept is mean Y", flatX.intercept, 2, tol: 1e-12)
let flatY = Analytics.linearFit(xs: [1, 2, 3], ys: [5, 5, 5])
h.close("flat scatter has R-squared 1", flatY.r2, 1, tol: 1e-12)

// MARK: Attribution empty guards
h.check("last touch of zero is empty", Attribution.lastTouch(0).isEmpty)
h.check("first touch of zero is empty", Attribution.firstTouch(0).isEmpty)
h.check("linear of zero is empty", Attribution.linear(0).isEmpty)
h.check("position based of zero is empty", Attribution.positionBased(0).isEmpty)

// MARK: BudgetAllocation guard
let zeroAlloc = BudgetAllocation.allocate(budget: 0, channels: [ResponseCurve(saturation: 1, scale: 1)])
h.check("zero budget allocates nothing", zeroAlloc.count == 1 && zeroAlloc[0] == 0)

// MARK: Probability special cases
h.check("logChoose out of range is -infinity", Probability.logChoose(5, 6) == -Double.infinity)
h.close("binomial out of range is 0", Probability.binomialPMF(k: -1, n: 5, p: 0.5), 0)
h.close("binomial at p=0, k=0 is 1", Probability.binomialPMF(k: 0, n: 5, p: 0), 1)
h.close("binomial at p=0, k>0 is 0", Probability.binomialPMF(k: 2, n: 5, p: 0), 0)
h.close("binomial at p=1, k=n is 1", Probability.binomialPMF(k: 5, n: 5, p: 1), 1)
h.close("binomial at p=1, k<n is 0", Probability.binomialPMF(k: 2, n: 5, p: 1), 0)
h.close("poisson out of range is 0", Probability.poissonPMF(k: -1, lambda: 2), 0)
h.close("poisson at lambda=0, k=0 is 1", Probability.poissonPMF(k: 0, lambda: 0), 1)
h.close("poisson at lambda=0, k>0 is 0", Probability.poissonPMF(k: 3, lambda: 0), 0)
h.close("normal pdf guards zero sd", Probability.normalPDF(0, mean: 0, sd: 0), 0)
h.close("normal cdf with zero sd below mean is 0", Probability.normalCDF(-1, mean: 0, sd: 0), 0)
h.close("normal cdf with zero sd at/above mean is 1", Probability.normalCDF(1, mean: 0, sd: 0), 1)
h.check("normal quantile at 0 is -infinity", Probability.normalQuantile(0) == -Double.infinity)
h.check("normal quantile at 1 is infinity", Probability.normalQuantile(1) == Double.infinity)
// The lower (p < 0.02425) and upper (p > 0.97575) tails use the outer
// branches of Acklam's rational approximation; Phi^-1(0.001) = -3.090232.
h.close("normal quantile lower tail", Probability.normalQuantile(0.001), -3.090232, tol: 1e-4)
h.close("normal quantile upper tail", Probability.normalQuantile(0.999), 3.090232, tol: 1e-4)
h.close("normal quantile at the median is 0", Probability.normalQuantile(0.5), 0, tol: 1e-9)

// MARK: Credential LinkedIn link and issuer JSON (trailing-slash trim)
let cred = Credential(recipientName: "Ada Lovelace", track: "dsp", role: "DSP Trader", year: 2026, month: 6, day: 21)
let addURL = cred.linkedInAddURL(certURL: "https://example.com/verify.html")
h.check("LinkedIn add URL targets the profile-add endpoint", addURL.contains("linkedin.com/profile/add"))
h.check("LinkedIn add URL carries the certification task", addURL.contains("CERTIFICATION_NAME"))
h.check("issuer JSON trims a trailing slash in the base URL",
Credential.issuerJSON(baseURL: "https://example.com/").contains("\"https://example.com/issuer.json\""))
h.check("issuer JSON without a trailing slash is unchanged",
Credential.issuerJSON(baseURL: "https://example.com").contains("\"https://example.com/issuer.json\""))

// MARK: Statistics empty-sample and zero-parameter guards
h.close("mean of an empty sample is 0", Statistics.mean([]), 0)
h.close("variance of an empty sample is 0", Statistics.variance([]), 0)
h.close("standard error of proportion guards zero n", Statistics.standardErrorOfProportion(p: 0.5, n: 0), 0)
h.close("z-score guards zero sd", Statistics.zScore(value: 5, mean: 0, sd: 0), 0)
let wilson0 = Statistics.wilsonInterval(successes: 0, n: 0)
h.check("wilson interval guards zero n", wilson0.low == 0 && wilson0.high == 0)
let zt0 = Statistics.twoProportionZTest(successesA: 1, nA: 0, successesB: 1, nB: 10)
h.check("two-proportion test guards zero n", zt0.z == 0 && zt0.pValue == 1)
let ztFlat = Statistics.twoProportionZTest(successesA: 0, nA: 100, successesB: 0, nB: 100)
h.check("two-proportion test guards zero pooled variance", ztFlat.z == 0 && ztFlat.pValue == 1)
h.close("median of an empty sample is 0", Statistics.median([]), 0)
h.close("percentile of an empty sample is 0", Statistics.percentile([], 0.5), 0)

// MARK: CampaignData zero-input getters and verdict/parse edge branches
let zeroLine = CampaignLine(name: "Z", impressions: 0, clicks: 0, conversions: 0, spend: 0, revenue: 0)
h.close("line CTR guards zero impressions", zeroLine.ctr, 0)
h.close("line CVR guards zero clicks", zeroLine.cvr, 0)
h.close("line CPM guards zero impressions", zeroLine.cpm, 0)
h.close("line CPC guards zero clicks", zeroLine.cpc, 0)
h.close("line CPA guards zero conversions", zeroLine.cpa, 0)
h.close("line ROAS guards zero spend", zeroLine.roas, 0)
let zeroSum = CampaignData.summarize([])
h.close("summary CPM guards zero impressions", zeroSum.cpm, 0)
h.close("summary CPA guards zero conversions", zeroSum.cpa, 0)
h.close("summary ROAS guards zero spend", zeroSum.roas, 0)
h.check("verdict with no blended CPA is typical",
CampaignData.verdict(line: CampaignLine(name: "A", impressions: 1000, clicks: 50, conversions: 5, spend: 100), blendedCPA: 0) == .typical)
// A short row leaves later columns empty; a blank name becomes a "Line N" label.
let edgeRows = "name,impressions,clicks,conversions,spend,revenue\nX,1000\n,2000,10,1,50,80"
let edgeLines = CampaignData.parseCSV(edgeRows)
h.check("a short row parses with later columns empty", edgeLines.first?.clicks == 0 && edgeLines.first?.spend == 0)
h.check("a blank name becomes a generated label", edgeLines.last?.name == "Line 2")
}
3 changes: 3 additions & 0 deletions Tests/BidLabCoreTests/LeagueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ func leagueTests(_ h: Harness) {
let lowRank = (League.weekly(userXP: 50).firstIndex { $0.isYou } ?? 99)
let highRank = (League.weekly(userXP: 5000).firstIndex { $0.isYou } ?? 99)
h.check("more xp ranks higher", highRank <= lowRank)

// Identifiable conformance: a league entry's id is its name.
h.check("league entry id is its name", LeagueEntry(name: "Ava", xp: 100, isYou: false).id == "Ava")
}
9 changes: 9 additions & 0 deletions Tests/BidLabCoreTests/PacedFlightTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ func pacedFlightTests(_ h: Harness) {
// Zero volatility reproduces a stationary market (constant per-interval volume).
let flat = PacedFlight.run(baseConfig: cfg, flightBudget: flightBudget, intervals: 12, volatility: 0, seed: 7)
h.check("zero volatility holds per-interval volume constant", Set(flat.points.map { $0.opportunities }).count == 1)

// Result convenience getters: profit and pacing error on a real flight.
h.close("flight profit = revenue - total spend", r.profit, r.revenue - r.totalSpend, tol: 1e-9)
h.check("pacing error is within [0,1] for a real flight", r.pacingError >= 0 && r.pacingError <= 1)
// A zero-budget flight cannot spend, so utilization and pacing error fall back to zero.
let zeroBudget = PacedFlight.run(baseConfig: cfg, flightBudget: 0, intervals: 4, seed: 1)
h.close("zero-budget flight pacing error is zero", zeroBudget.pacingError, 0, tol: 1e-12)
h.close("zero-budget flight utilization is zero", zeroBudget.budgetUtilization, 0, tol: 1e-12)
h.close("zero-budget flight profit", zeroBudget.profit, zeroBudget.revenue - zeroBudget.totalSpend, tol: 1e-9)
}
Loading
Loading