From 94da3f24212b3a142e8eaf7cd936234767a07db9 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Sun, 12 Apr 2026 02:38:22 -0400 Subject: [PATCH 1/3] feat: add public CI, Homebrew, and benchmark assets --- .github/workflows/ci.yml | 80 +++++++++++++++++++ CHANGELOG.md | 4 + Formula/profitctl.rb | 35 ++++++++ README.md | 9 ++- benchmark_scenarios/README.md | 8 ++ .../reports/hybrid_safe_vs_breach.md | 49 ++++++++++++ .../reports/hybrid_steady_vs_pilot.md | 41 ++++++++++ .../reports/open_core_tiered_vs_mix.md | 41 ++++++++++ docs/INSTALL.md | 17 +++- docs/QUICK_START.md | 43 ++++++++++ docs/growth/design-partner-intake.md | 19 +++-- docs/release/MVP_PRICING_RELEASE.md | 2 + scripts/release/publish-homebrew-tap.sh | 31 +++++++ 13 files changed, 370 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 Formula/profitctl.rb create mode 100644 benchmark_scenarios/reports/hybrid_safe_vs_breach.md create mode 100644 benchmark_scenarios/reports/hybrid_steady_vs_pilot.md create mode 100644 benchmark_scenarios/reports/open_core_tiered_vs_mix.md create mode 100644 scripts/release/publish-homebrew-tap.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..73b41cb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,80 @@ +name: GitHub CI + +on: + pull_request: + branches: + - main + push: + branches: + - main + +permissions: + contents: read + +jobs: + verify-go: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Verify formatting + run: test -z "$(gofmt -l .)" + + - name: Verify module files + run: | + go mod tidy + git diff --exit-code go.mod go.sum + + - name: Run go vet + run: go vet ./... + + - name: Run tests + run: go test ./... + + verify-install-smoke: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Verify installer syntax + run: bash -n scripts/install.sh + + - name: Build local release artifacts + run: | + TAG=v0.0.0-ci + bash scripts/release/build-artifacts.sh "${TAG}" + bash scripts/release/create-checksums.sh "${TAG}" + + - name: Build local mirror layout + run: | + TAG=v0.0.0-ci + MIRROR_ROOT="${RUNNER_TEMP}/profitctl-mirror" + mkdir -p "${MIRROR_ROOT}/releases/${TAG}" "${MIRROR_ROOT}/current" + cp "dist/${TAG}/profitctl_${TAG}_"* "dist/${TAG}/SHA256SUMS" "${MIRROR_ROOT}/releases/${TAG}/" + printf '{"latest":"%s"}\n' "${TAG}" > "${MIRROR_ROOT}/current/index.json" + echo "PROFITCTL_TEST_MIRROR=file://${MIRROR_ROOT}" >> "${GITHUB_ENV}" + + - name: Run installer smoke test + run: | + INSTALL_DIR="${RUNNER_TEMP}/profitctl-bin" + mkdir -p "${INSTALL_DIR}" + PROFITCTL_DOWNLOAD_BASE_URL="${PROFITCTL_TEST_MIRROR}" \ + PROFITCTL_VERSION=v0.0.0-ci \ + PROFITCTL_INSTALL_DIR="${INSTALL_DIR}" \ + bash scripts/install.sh + "${INSTALL_DIR}/profitctl" --help >/dev/null + "${INSTALL_DIR}/profitctl" validate -f examples/mix_profit.yml >/dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index bba4308..1b22b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ All notable changes to this project will be documented in this file. - OSS baseline docs and governance files. - Published-asset release smoke verification. - Design-partner issue routing and maintainer workflow guidance. +- GitHub Actions PR/main verification workflow with stable `verify-go` and `verify-install-smoke` checks. +- Homebrew formula and tap-publish script for the public release channel. +- Committed benchmark comparison reports for open-core and hybrid pricing scenarios. ### Changed - Canonical module/repository identity aligned to `IntelIP/ProfitCtl`. @@ -18,3 +21,4 @@ All notable changes to this project will be documented in this file. - Simulation benchmarks are now discoverable through `go test -bench`. - Public installer defaults now use GitHub Releases as the canonical source, with the Hostinger mirror available as an explicit override. - Open-core packaging docs now define who the product is for, what stays free, and what the first paid layer should cover. +- Install docs now include Homebrew and the Quick Start includes concrete output snippets. diff --git a/Formula/profitctl.rb b/Formula/profitctl.rb new file mode 100644 index 0000000..ed57d3d --- /dev/null +++ b/Formula/profitctl.rb @@ -0,0 +1,35 @@ +class Profitctl < Formula + desc "CLI for profit-first unit economics simulations" + homepage "https://github.com/IntelIP/ProfitCtl" + version "0.1.2" + license "MIT" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/IntelIP/ProfitCtl/releases/download/v#{version}/profitctl_v#{version}_darwin_arm64.tar.gz" + sha256 "7a16e98a82d96b8c1256deea599b10ee469bc5fa5e19b35a3ece803bc0b93a7d" + else + url "https://github.com/IntelIP/ProfitCtl/releases/download/v#{version}/profitctl_v#{version}_darwin_amd64.tar.gz" + sha256 "fc13529c0e44a7401d9c50ab75a56fbc63cbca0d4031bd69b737499ca264c951" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/IntelIP/ProfitCtl/releases/download/v#{version}/profitctl_v#{version}_linux_arm64.tar.gz" + sha256 "4a744754afdbea618e07d74463d788e3e91c1fc67e506479263af8b09c7203af" + else + url "https://github.com/IntelIP/ProfitCtl/releases/download/v#{version}/profitctl_v#{version}_linux_amd64.tar.gz" + sha256 "425003e38aab4133a3cc37275a97ca31a008d9a011bfbbb9425bf43dbb33c712" + end + end + + def install + bin.install "profitctl" + pkgshare.install "README.md" if File.exist?("README.md") + end + + test do + assert_match "profit-first unit economics simulations", shell_output("#{bin}/profitctl --help") + end +end diff --git a/README.md b/README.md index ebb7413..6c112e2 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,14 @@ It helps teams model fixed + variable costs, simulate growth and stress scenario curl -fsSL https://raw.githubusercontent.com/IntelIP/ProfitCtl/main/scripts/install.sh | bash ``` -See [Install Guide](docs/INSTALL.md) for pinned versions, custom prefixes, and source-based installs. +### Homebrew + +```bash +brew tap IntelIP/profitctl +brew install profitctl +``` + +See [Install Guide](docs/INSTALL.md) for pinned versions, Homebrew, custom prefixes, and source-based installs. ### Build from source diff --git a/benchmark_scenarios/README.md b/benchmark_scenarios/README.md index bc1dd75..4082769 100644 --- a/benchmark_scenarios/README.md +++ b/benchmark_scenarios/README.md @@ -31,6 +31,12 @@ Use them with `profitctl compare` for pricing review: ./profitctl compare benchmark_scenarios/hybrid_operating_safe.yml benchmark_scenarios/hybrid_operating_breach.yml ``` +Committed shareable outputs now live under `benchmark_scenarios/reports/`: + +- `reports/open_core_tiered_vs_mix.md` +- `reports/hybrid_steady_vs_pilot.md` +- `reports/hybrid_safe_vs_breach.md` + These scenarios are curated to answer three product questions: - should we ship open-core pricing as tiered bands or explicit plan mix? @@ -53,6 +59,8 @@ Recommended starting pairs: - `hybrid_steady_contract.yml` vs `hybrid_pilot_contract.yml` - `hybrid_operating_safe.yml` vs `hybrid_operating_breach.yml` +If you want an artifact to share instead of raw terminal output, start with the matching file in `benchmark_scenarios/reports/`. + If you are turning one of these into a conversation, keep the frame simple: 1. what are we comparing? diff --git a/benchmark_scenarios/reports/hybrid_safe_vs_breach.md b/benchmark_scenarios/reports/hybrid_safe_vs_breach.md new file mode 100644 index 0000000..9682a52 --- /dev/null +++ b/benchmark_scenarios/reports/hybrid_safe_vs_breach.md @@ -0,0 +1,49 @@ +# Recurring Covenant Safety: Safe vs Breach + +Command: + +```bash +profitctl compare benchmark_scenarios/hybrid_operating_safe.yml benchmark_scenarios/hybrid_operating_breach.yml --markdown +``` + +Question: + +What does a clearly covenant-safe hybrid contract look like against one that fails recurring operating health? + +Result: + +`hybrid_operating_breach` fails because operating margin drops well below the configured covenant threshold. This is the benchmark to show when the goal is risk detection, not just revenue comparison. + +Note: + +This command exits non-zero because the second scenario breaches its covenant. That is expected and useful in CI. + +Output: + +```markdown +## profitctl Scenario Comparison + +Baseline: **hybrid_operating_safe** + +| Scenario | Mode | Revenue | Recurring Revenue | Payment Fees | Booked Margin | Operating Margin | Cost/User | Covenants | +|----------|------|---------|-------------------|--------------|---------------|------------------|-----------|-----------| +| hybrid_operating_safe | hybrid | $2000.00 | $2000.00 | $0.00 | 95.00% | 95.00% | $2.00 | PASS | +| hybrid_operating_breach | hybrid | $250.00 | $250.00 | $0.00 | -100.00% | -100.00% | $10.00 | FAIL (1) | + +### Delta vs Baseline + +| Scenario | Revenue Delta | Booked Margin Delta | Operating Margin Delta | Cost/User Delta | +|----------|---------------|---------------------|------------------------|-----------------| +| hybrid_operating_breach | $-1750.00 | -195.00 pts | -195.00 pts | $+8.00 | + +### Failing Scenarios + +- **hybrid_operating_breach**: Operating margin must be >= 30% + +### Leaders + +- Highest revenue: **hybrid_operating_safe** +- Highest operating margin: **hybrid_operating_safe** +- Lowest cost/user: **hybrid_operating_safe** +- Best covenant health: **hybrid_operating_safe** +``` diff --git a/benchmark_scenarios/reports/hybrid_steady_vs_pilot.md b/benchmark_scenarios/reports/hybrid_steady_vs_pilot.md new file mode 100644 index 0000000..79e0e73 --- /dev/null +++ b/benchmark_scenarios/reports/hybrid_steady_vs_pilot.md @@ -0,0 +1,41 @@ +# Hybrid Contracts: Steady-State vs Pilot + +Command: + +```bash +profitctl compare benchmark_scenarios/hybrid_steady_contract.yml benchmark_scenarios/hybrid_pilot_contract.yml --markdown +``` + +Question: + +Does pilot revenue improve the contract, or does it only flatter booked economics while steady-state health gets worse? + +Result: + +The pilot contract increases total revenue, but it weakens operating margin and raises cost per user. This is the right benchmark to show when a team needs to separate one-time onboarding revenue from recurring business health. + +Output: + +```markdown +## profitctl Scenario Comparison + +Baseline: **hybrid_steady_contract** + +| Scenario | Mode | Revenue | Recurring Revenue | Payment Fees | Booked Margin | Operating Margin | Cost/User | Covenants | +|----------|------|---------|-------------------|--------------|---------------|------------------|-----------|-----------| +| hybrid_steady_contract | hybrid | $2000.00 | $2000.00 | $0.00 | 95.00% | 95.00% | $2.00 | PASS | +| hybrid_pilot_contract | hybrid | $6500.00 | $2000.00 | $200.50 | 95.38% | 91.50% | $3.40 | PASS | + +### Delta vs Baseline + +| Scenario | Revenue Delta | Booked Margin Delta | Operating Margin Delta | Cost/User Delta | +|----------|---------------|---------------------|------------------------|-----------------| +| hybrid_pilot_contract | $+4500.00 | +0.38 pts | -3.50 pts | $+1.40 | + +### Leaders + +- Highest revenue: **hybrid_pilot_contract** +- Highest operating margin: **hybrid_steady_contract** +- Lowest cost/user: **hybrid_steady_contract** +- Best covenant health: **hybrid_pilot_contract** +``` diff --git a/benchmark_scenarios/reports/open_core_tiered_vs_mix.md b/benchmark_scenarios/reports/open_core_tiered_vs_mix.md new file mode 100644 index 0000000..7deb3a8 --- /dev/null +++ b/benchmark_scenarios/reports/open_core_tiered_vs_mix.md @@ -0,0 +1,41 @@ +# Open-Core Pricing: Tiered vs Mix + +Command: + +```bash +profitctl compare benchmark_scenarios/open_core_tiered.yml benchmark_scenarios/open_core_mix.yml --markdown +``` + +Question: + +Should the open-core packaging lead with cumulative tiered pricing or an explicit plan-mix model? + +Result: + +`open_core_tiered` wins on revenue, operating margin, and cost per user in the current benchmark assumptions. The `mix` configuration still passes covenants, but it gives up too much revenue without reducing cost enough to justify the trade. + +Output: + +```markdown +## profitctl Scenario Comparison + +Baseline: **open_core_tiered** + +| Scenario | Mode | Revenue | Recurring Revenue | Payment Fees | Booked Margin | Operating Margin | Cost/User | Covenants | +|----------|------|---------|-------------------|--------------|---------------|------------------|-----------|-----------| +| open_core_tiered | tiered | $2425.00 | $2425.00 | $0.00 | 75.26% | 75.26% | $6.00 | PASS | +| open_core_mix | mix | $880.00 | $880.00 | $35.66 | 27.77% | 27.77% | $6.36 | PASS | + +### Delta vs Baseline + +| Scenario | Revenue Delta | Booked Margin Delta | Operating Margin Delta | Cost/User Delta | +|----------|---------------|---------------------|------------------------|-----------------| +| open_core_mix | $-1545.00 | -47.49 pts | -47.49 pts | $+0.36 | + +### Leaders + +- Highest revenue: **open_core_tiered** +- Highest operating margin: **open_core_tiered** +- Lowest cost/user: **open_core_tiered** +- Best covenant health: **open_core_tiered** +``` diff --git a/docs/INSTALL.md b/docs/INSTALL.md index c5e5fab..b37ba90 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -19,7 +19,20 @@ The script installs to `~/.local/bin` by default. If that directory is not on yo ## Pin a Version ```bash -curl -fsSL https://raw.githubusercontent.com/IntelIP/ProfitCtl/main/scripts/install.sh | PROFITCTL_VERSION=v0.1.1 bash +curl -fsSL https://raw.githubusercontent.com/IntelIP/ProfitCtl/main/scripts/install.sh | PROFITCTL_VERSION=v0.1.2 bash +``` + +## Homebrew + +```bash +brew tap IntelIP/profitctl +brew install profitctl +``` + +For explicit tap-qualified installs: + +```bash +brew install IntelIP/profitctl/profitctl ``` ## Custom Prefix @@ -54,7 +67,7 @@ go install github.com/IntelIP/ProfitCtl@latest ```bash profitctl --help -profitctl simulate --help +profitctl validate -f examples/mix_profit.yml ``` ## Exit Codes diff --git a/docs/QUICK_START.md b/docs/QUICK_START.md index f77bdf5..0c13c6f 100644 --- a/docs/QUICK_START.md +++ b/docs/QUICK_START.md @@ -9,6 +9,27 @@ profitctl simulate -f examples/hybrid_profit.yml profitctl simulate -f examples/hybrid_steady_profit.yml ``` +Example JSON excerpt from `examples/hybrid_steady_profit.yml`: + +```json +{ + "revenue": { + "mode": "hybrid", + "total": 2000, + "recurring_total": 2000, + "minimum_uplift": 500 + }, + "margin": { + "gross": 95, + "operating_gross": 95, + "cost_per_user": 2 + }, + "covenants": { + "passed": true + } +} +``` + ## 2. Validate config ```bash @@ -44,6 +65,28 @@ profitctl compare examples/hybrid_steady_profit.yml examples/hybrid_profit.yml - The first config acts as the baseline. `compare` exits non-zero if any scenario breaches its covenants, which makes it usable in pricing reviews and CI gates. For hybrid and pilot-style contracts, the comparison output separates booked margin from operating margin so one-time setup revenue does not masquerade as steady-state unit economics. +Example CLI excerpt: + +```text +=== profitctl Scenario Comparison === + +Baseline: open_core_tiered + +Scenario Mode Revenue Recurring Fees Booked Op Marg CPU Covenants +------------------------------------------------------------------------------------------------ +open_core_tiered tiered $2425.00 $2425.00 $0.00 75.26 75.26 $6.00 PASS +open_core_mix mix $880.00 $880.00 $35.66 27.77 27.77 $6.36 PASS + +Delta vs baseline: + open_core_mix: revenue -1545.00, booked -47.49 pts, operating -47.49 pts, cost/user +0.36 +``` + +For committed shareable compare artifacts, see: + +- `benchmark_scenarios/reports/open_core_tiered_vs_mix.md` +- `benchmark_scenarios/reports/hybrid_steady_vs_pilot.md` +- `benchmark_scenarios/reports/hybrid_safe_vs_breach.md` + ## 6. Normalize a calibration export ```bash diff --git a/docs/growth/design-partner-intake.md b/docs/growth/design-partner-intake.md index 163ee7b..76e4bb2 100644 --- a/docs/growth/design-partner-intake.md +++ b/docs/growth/design-partner-intake.md @@ -10,13 +10,13 @@ Turn one useful benchmark run into one of three outcomes: - a calibration request - a design-partner pilot -## Intake Sequence +## 15-Minute Evaluation Path 1. Send the benchmark comparison that matches their problem. -2. Ask them to run `profitctl compare` or share their current pricing inputs. -3. Collect their real contract shape and usage assumptions. -4. Calibrate the model against their numbers. -5. Decide whether they need docs help, a feature, or a pilot. +2. Point them to the public install path or Homebrew formula. +3. Ask them to run `profitctl compare` once or share the closest pricing shape. +4. If the result is useful, ask for real inputs so the model can be calibrated. +5. Decide whether they need docs help, a feature, or a design-partner call. ## Minimum Qualification Questions @@ -43,6 +43,14 @@ Ask only what is needed to map their scenario: - calibration help: ask for the minimum exported inputs - design-partner pilot: agree on one contract shape to test and one success criterion +## What We Need From The First 5 Evaluations + +- which benchmark pair made sense immediately +- whether install worked without intervention +- which metric they trusted first +- which input they could not map into the current config +- whether they would use `compare` before a real pricing or contract decision + ## Good Design-Partner Criteria Treat a user as a good fit if they: @@ -59,4 +67,3 @@ Do not create a heavyweight sales process yet. The only thing we need is a consistent loop from: benchmark -> install -> compare -> calibrate -> follow-up - diff --git a/docs/release/MVP_PRICING_RELEASE.md b/docs/release/MVP_PRICING_RELEASE.md index 75e28ef..9ebe4cb 100644 --- a/docs/release/MVP_PRICING_RELEASE.md +++ b/docs/release/MVP_PRICING_RELEASE.md @@ -40,6 +40,8 @@ Use `GOCACHE` and `GOTMPDIR` overrides in restricted environments. - `operating_margin` covenants for contract safety checks 8. Verify the exact public install path from the README against the new tag: - `curl -fsSL https://raw.githubusercontent.com/IntelIP/ProfitCtl/main/scripts/install.sh | env -u PROFITCTL_DOWNLOAD_BASE_URL PROFITCTL_VERSION= bash` +9. Update and publish the Homebrew tap if `Formula/profitctl.rb` changed: + - `bash scripts/release/publish-homebrew-tap.sh` ## Suggested Release Notes diff --git a/scripts/release/publish-homebrew-tap.sh b/scripts/release/publish-homebrew-tap.sh new file mode 100644 index 0000000..cbb3a66 --- /dev/null +++ b/scripts/release/publish-homebrew-tap.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +TAP_REPO="${HOMEBREW_TAP_REPO:-IntelIP/homebrew-profitctl}" +TMP_DIR="$(mktemp -d)" +cleanup() { + rm -rf "${TMP_DIR}" +} +trap cleanup EXIT + +if [[ ! -f "${ROOT}/Formula/profitctl.rb" ]]; then + echo "Formula/profitctl.rb is missing" >&2 + exit 1 +fi + +gh repo clone "${TAP_REPO}" "${TMP_DIR}/tap" -- --depth=1 >/dev/null + +mkdir -p "${TMP_DIR}/tap/Formula" +cp "${ROOT}/Formula/profitctl.rb" "${TMP_DIR}/tap/Formula/profitctl.rb" + +if [[ -z "$(git -C "${TMP_DIR}/tap" status --short -- Formula/profitctl.rb)" ]]; then + echo "Homebrew tap already matches Formula/profitctl.rb" + exit 0 +fi + +git -C "${TMP_DIR}/tap" add Formula/profitctl.rb +git -C "${TMP_DIR}/tap" commit -m "chore: update profitctl formula" +git -C "${TMP_DIR}/tap" push origin HEAD + +echo "Published formula to ${TAP_REPO}" From 54a30186bc0e8c534d952eed7df99f24b8ff817f Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Sun, 12 Apr 2026 02:41:47 -0400 Subject: [PATCH 2/3] chore: trigger GitHub Actions From 563ccace1dcb1c0ef2c65725667a6664bf04d613 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Sun, 12 Apr 2026 02:43:16 -0400 Subject: [PATCH 3/3] style: normalize gofmt output for CI --- internal/config/parser.go | 18 +++++++++--------- internal/cost/engine.go | 2 +- internal/cost/engine_test.go | 2 +- internal/covenant/engine.go | 20 ++++++++++---------- internal/pricing/margin.go | 12 ++++++------ internal/pricing/margin_test.go | 2 +- internal/scanner/collector_test.go | 12 ++++++------ 7 files changed, 34 insertions(+), 34 deletions(-) diff --git a/internal/config/parser.go b/internal/config/parser.go index d74de8d..23ee161 100644 --- a/internal/config/parser.go +++ b/internal/config/parser.go @@ -12,15 +12,15 @@ import ( // Config represents the complete profit.yml configuration. type Config struct { - Project *ProjectInfo `yaml:"project"` - FixedCosts []types.FixedCost `yaml:"fixed_costs"` - VariableCosts []types.VariableCost `yaml:"variable_costs"` - Pricing *PricingConfig `yaml:"pricing"` - PaymentFees *PaymentFeesConfig `yaml:"payment_fees"` - Calibration *CalibrationConfig `yaml:"calibration"` - CalibrationFile string `yaml:"calibration_file"` - Covenants []Covenant `yaml:"covenants"` - Simulation *SimulationConfig `yaml:"simulation"` + Project *ProjectInfo `yaml:"project"` + FixedCosts []types.FixedCost `yaml:"fixed_costs"` + VariableCosts []types.VariableCost `yaml:"variable_costs"` + Pricing *PricingConfig `yaml:"pricing"` + PaymentFees *PaymentFeesConfig `yaml:"payment_fees"` + Calibration *CalibrationConfig `yaml:"calibration"` + CalibrationFile string `yaml:"calibration_file"` + Covenants []Covenant `yaml:"covenants"` + Simulation *SimulationConfig `yaml:"simulation"` } type ProjectInfo struct { diff --git a/internal/cost/engine.go b/internal/cost/engine.go index 9c886ea..3f24737 100644 --- a/internal/cost/engine.go +++ b/internal/cost/engine.go @@ -70,4 +70,4 @@ func (e *costEngineImpl) CalculateTotalCosts(users int, months int) TotalCostRes GrandTotal: grandTotal, GrandByLayer: grandByLayer, } -} \ No newline at end of file +} diff --git a/internal/cost/engine_test.go b/internal/cost/engine_test.go index 0e45624..f16fb80 100644 --- a/internal/cost/engine_test.go +++ b/internal/cost/engine_test.go @@ -255,4 +255,4 @@ func TestCostEngine_EmptyConfig(t *testing.T) { func newFloat64Ptr(v float64) *float64 { return &v -} \ No newline at end of file +} diff --git a/internal/covenant/engine.go b/internal/covenant/engine.go index 13fee20..81caa33 100644 --- a/internal/covenant/engine.go +++ b/internal/covenant/engine.go @@ -23,22 +23,22 @@ type Violation struct { // SimulationResults contains all simulation data needed for covenant validation type SimulationResults struct { - Margin float64 // Gross margin percentage - OperatingMargin float64 // Operating / recurring margin percentage - CostPerUser float64 // Cost per user + Margin float64 // Gross margin percentage + OperatingMargin float64 // Operating / recurring margin percentage + CostPerUser float64 // Cost per user OperatingCostPerUser float64 // Operating cost per user - P95Margin float64 // p95 margin percentage (optional) - P95OperatingMargin float64 // p95 operating margin percentage (optional) - P95CostPerUser float64 // p95 cost per user (optional) - P99Margin float64 // p99 margin percentage (optional) - P99OperatingMargin float64 // p99 operating margin percentage (optional) - P99CostPerUser float64 // p99 cost per user (optional) + P95Margin float64 // p95 margin percentage (optional) + P95OperatingMargin float64 // p95 operating margin percentage (optional) + P95CostPerUser float64 // p95 cost per user (optional) + P99Margin float64 // p99 margin percentage (optional) + P99OperatingMargin float64 // p99 operating margin percentage (optional) + P99CostPerUser float64 // p99 cost per user (optional) } // ValidateCovenants validates all covenants against simulation results func ValidateCovenants(covenants []config.Covenant, results SimulationResults) ValidationResult { validation := ValidationResult{ - Passed: true, + Passed: true, Violations: []Violation{}, } diff --git a/internal/pricing/margin.go b/internal/pricing/margin.go index 7276894..94df6ed 100644 --- a/internal/pricing/margin.go +++ b/internal/pricing/margin.go @@ -8,11 +8,11 @@ import ( // MarginResult represents calculated margin and cost metrics type MarginResult struct { - GrossMargin float64 // Gross margin percentage (0-100) - MarginPercentage float64 // Margin as decimal (0-1) - CostPerUser float64 // Total cost per user - LayerMargins LayerMarginBreakdown // Margin contribution by layer - LayerCostPercent types.CostLayerBreakdown // Cost percentage by layer (0-100) + GrossMargin float64 // Gross margin percentage (0-100) + MarginPercentage float64 // Margin as decimal (0-1) + CostPerUser float64 // Total cost per user + LayerMargins LayerMarginBreakdown // Margin contribution by layer + LayerCostPercent types.CostLayerBreakdown // Cost percentage by layer (0-100) } // LayerMarginBreakdown represents margin contribution by layer @@ -74,4 +74,4 @@ func CalculateMargins(revenue float64, totalCosts float64, layerCosts types.Cost result.LayerCostPercent.Service = math.Round(result.LayerCostPercent.Service*100) / 100 return result -} \ No newline at end of file +} diff --git a/internal/pricing/margin_test.go b/internal/pricing/margin_test.go index 87c356d..c2f79c4 100644 --- a/internal/pricing/margin_test.go +++ b/internal/pricing/margin_test.go @@ -179,4 +179,4 @@ func TestCalculateMargins_AllLayersEqual(t *testing.T) { assert.InDelta(t, 33.33, result.LayerCostPercent.Infrastructure, 0.1, "Infrastructure should be ~33.33%") assert.InDelta(t, 33.33, result.LayerCostPercent.Application, 0.1, "Application should be ~33.33%") assert.InDelta(t, 33.34, result.LayerCostPercent.Service, 0.1, "Service should be ~33.34%") -} \ No newline at end of file +} diff --git a/internal/scanner/collector_test.go b/internal/scanner/collector_test.go index ad83f70..c14fdf7 100644 --- a/internal/scanner/collector_test.go +++ b/internal/scanner/collector_test.go @@ -54,12 +54,12 @@ func TestCollector_Collect(t *testing.T) { // Create test files testFiles := map[string]string{ - "go.mod": "module test\n\ngo 1.21\n\nrequire github.com/test v1.0.0", - "package.json": `{"name": "test", "version": "1.0.0"}`, - "config.yaml": "key: value\n", - "main.go": "package main\n\nfunc main() {}", - "README.md": "# Test Project", - "subdir/nested.json": `{"nested": true}`, + "go.mod": "module test\n\ngo 1.21\n\nrequire github.com/test v1.0.0", + "package.json": `{"name": "test", "version": "1.0.0"}`, + "config.yaml": "key: value\n", + "main.go": "package main\n\nfunc main() {}", + "README.md": "# Test Project", + "subdir/nested.json": `{"nested": true}`, } for path, content := range testFiles {