Demand metrics #750
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Demand metrics | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "17 */4 * * *" | |
| issues: | |
| types: [opened, edited, reopened, labeled, unlabeled, closed] | |
| release: | |
| types: [published, edited, deleted] | |
| concurrency: | |
| group: demand-metrics | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| issues: read | |
| jobs: | |
| update: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: main | |
| - uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require("fs"); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const price = 29; | |
| const teamPrice = 203; | |
| const target = 200; | |
| const allIssues = await github.paginate(github.rest.issues.listForRepo, { | |
| owner, | |
| repo, | |
| state: "all", | |
| per_page: 100 | |
| }); | |
| const plainIssues = allIssues.filter((issue) => !issue.pull_request); | |
| const issueText = (issue) => `${issue.title || ""}\n${issue.body || ""}`; | |
| const hasLabel = (issue, label) => (issue.labels || []).some((item) => item.name === label); | |
| const isNotBuyerIntent = (issue) => { | |
| const text = issueText(issue); | |
| return hasLabel(issue, "not-buyer-intent") || | |
| text.includes("agent-ops-discussion-order-bridge-v1") || | |
| text.includes("automation-generated bridge from owner-authored discussion"); | |
| }; | |
| const isOrderIntent = (issue) => { | |
| const text = issueText(issue); | |
| return hasLabel(issue, "order-request") || | |
| hasLabel(issue, "template-pack-request") || | |
| hasLabel(issue, "budget-request") || | |
| hasLabel(issue, "invoice-request") || | |
| hasLabel(issue, "expense-report") || | |
| hasLabel(issue, "quote-request") || | |
| hasLabel(issue, "purchase-order") || | |
| hasLabel(issue, "vendor-onboarding") || | |
| hasLabel(issue, "security-review") || | |
| hasLabel(issue, "delivery-acceptance") || | |
| hasLabel(issue, "payment-ready") || | |
| text.includes("Order request: Agent Ops Command Center") || | |
| text.includes("Template pack request: Agent Ops Command Center") || | |
| text.includes("Budget request: Agent Ops Command Center") || | |
| text.includes("Invoice request: Agent Ops Command Center") || | |
| text.includes("Expense report request: Agent Ops Command Center") || | |
| text.includes("Quote request: Agent Ops Command Center") || | |
| text.includes("Purchase order request: Agent Ops Command Center") || | |
| text.includes("Vendor onboarding request: Agent Ops Command Center") || | |
| text.includes("Security review request: Agent Ops Command Center") || | |
| text.includes("Delivery acceptance request: Agent Ops Command Center") || | |
| text.includes("Payment-ready request: Agent Ops Command Center") || | |
| text.includes("Primary team request: Agent Ops Command Center") || | |
| text.includes("Fast team checkout request: Agent Ops Command Center") || | |
| text.includes("Invoice request packet: Agent Ops Command Center") || | |
| text.includes("Request package: Team license - 7 seats - $203 gross") || | |
| text.includes("Request package: Individual seat - $29"); | |
| }; | |
| const issues = plainIssues.filter((issue) => !isNotBuyerIntent(issue) && isOrderIntent(issue)); | |
| const unlabeledOrders = issues.filter((issue) => !hasLabel(issue, "order-request")).length; | |
| const revenueProofIssues = await github.paginate(github.rest.issues.listForRepo, { | |
| owner, | |
| repo, | |
| state: "all", | |
| labels: "revenue-proof", | |
| per_page: 100 | |
| }); | |
| const releases = await github.rest.repos.listReleases({ | |
| owner, | |
| repo, | |
| per_page: 20 | |
| }); | |
| const releaseAssets = releases.data.flatMap((release) => release.assets || []); | |
| const previewDownloads = releaseAssets | |
| .filter((asset) => asset.name.includes("preview")) | |
| .reduce((sum, asset) => sum + asset.download_count, 0); | |
| const releaseTarballDownloads = releaseAssets | |
| .filter((asset) => asset.name.endsWith(".tgz")) | |
| .reduce((sum, asset) => sum + asset.download_count, 0); | |
| const totalReleaseDownloads = releaseAssets | |
| .reduce((sum, asset) => sum + asset.download_count, 0); | |
| const repoInfo = await github.rest.repos.get({ owner, repo }); | |
| const repositoryStars = repoInfo.data.stargazers_count || 0; | |
| const repositoryForks = repoInfo.data.forks_count || 0; | |
| const repositoryWatchers = repoInfo.data.subscribers_count || 0; | |
| const orders = issues.length; | |
| const teamOrders = issues.filter((issue) => { | |
| const body = issue.body || ""; | |
| return body.includes("Team license - 7 seats - $203 gross") || | |
| body.includes("I am ready to pay $203 for the 7-seat team license when checkout is ready"); | |
| }).length; | |
| const previewOnlyOrders = issues.filter((issue) => { | |
| const body = issue.body || ""; | |
| return body.includes("Evaluating preview only") || | |
| body.includes("I want to evaluate the preview first") || | |
| body.includes("I only want updates for now"); | |
| }).length; | |
| const individualOrders = Math.max(0, orders - teamOrders - previewOnlyOrders); | |
| const possibleGross = (teamOrders * teamPrice) + (individualOrders * price); | |
| const parseGrossUsd = (body) => { | |
| const match = (body || "").match(/### Gross revenue proven USD\s+([0-9]+(?:\.[0-9]{1,2})?)/i); | |
| if (!match) return 0; | |
| const amount = Number.parseFloat(match[1]); | |
| return Number.isFinite(amount) && amount > 0 ? amount : 0; | |
| }; | |
| const confirmedRevenueProofs = revenueProofIssues | |
| .map((issue) => ({ issue, amount: parseGrossUsd(issue.body || "") })) | |
| .filter(({ issue, amount }) => amount > 0 && | |
| hasLabel(issue, "revenue-confirmed") && | |
| (issue.body || "").includes("I confirm this issue records real checkout, receipt, payout, or seller-dashboard evidence.")); | |
| const grossRevenue = confirmedRevenueProofs.reduce((sum, proof) => sum + proof.amount, 0); | |
| const status = grossRevenue >= target | |
| ? "revenue target reached" | |
| : grossRevenue > 0 | |
| ? "partial paid revenue proven" | |
| : orders > 0 | |
| ? "orders pending checkout" | |
| : "no paid checkout"; | |
| const metrics = { | |
| orders, | |
| individual_order_intents: individualOrders, | |
| team_license_order_intents: teamOrders, | |
| preview_only_intents: previewOnlyOrders, | |
| unlabeled_order_intents: unlabeledOrders, | |
| revenue_proof_issues: revenueProofIssues.length, | |
| confirmed_revenue_proof_issues: confirmedRevenueProofs.length, | |
| preview_downloads: previewDownloads, | |
| release_tarball_downloads: releaseTarballDownloads, | |
| total_release_downloads: totalReleaseDownloads, | |
| repository_stars: repositoryStars, | |
| repository_forks: repositoryForks, | |
| repository_watchers: repositoryWatchers, | |
| possible_gross_if_all_orders_paid_usd: possibleGross, | |
| gross_revenue_usd: Number(grossRevenue.toFixed(2)), | |
| target_revenue_usd: target, | |
| price_usd: price, | |
| team_license_price_usd: teamPrice, | |
| sales_needed: Math.ceil(target / price), | |
| team_licenses_needed: Math.ceil(target / teamPrice), | |
| checkout_live: false, | |
| revenue_proof_required: "checkout, receipt, payout, or seller-dashboard evidence", | |
| revenue_counting_rule: "gross revenue counts only from revenue-proof issues that include a positive amount, the required confirmation text, and the revenue-confirmed label", | |
| status, | |
| updated_at: new Date().toISOString() | |
| }; | |
| fs.mkdirSync("metrics", { recursive: true }); | |
| fs.writeFileSync("metrics/status.json", `${JSON.stringify(metrics, null, 2)}\n`); | |
| const readme = [ | |
| "# Demand Metrics", | |
| "", | |
| "Current status:", | |
| "", | |
| `- Orders: \`${metrics.orders}\``, | |
| `- Individual order intents: \`${metrics.individual_order_intents}\``, | |
| `- Team license order intents: \`${metrics.team_license_order_intents}\``, | |
| `- Preview-only intents: \`${metrics.preview_only_intents}\``, | |
| `- Unlabeled order intents: \`${metrics.unlabeled_order_intents}\``, | |
| `- Revenue proof issues: \`${metrics.revenue_proof_issues}\``, | |
| `- Confirmed revenue proof issues: \`${metrics.confirmed_revenue_proof_issues}\``, | |
| `- Preview downloads: \`${metrics.preview_downloads}\``, | |
| `- Release tarball downloads: \`${metrics.release_tarball_downloads}\``, | |
| `- Total release downloads: \`${metrics.total_release_downloads}\``, | |
| `- Repository stars: \`${metrics.repository_stars}\``, | |
| `- Repository forks: \`${metrics.repository_forks}\``, | |
| `- Repository watchers: \`${metrics.repository_watchers}\``, | |
| `- Possible gross if all order requests pay: \`$${metrics.possible_gross_if_all_orders_paid_usd}\``, | |
| `- Gross revenue proven: \`$${metrics.gross_revenue_usd}\``, | |
| `- Target revenue: \`$${metrics.target_revenue_usd}\``, | |
| `- Price target: \`$${metrics.price_usd}\``, | |
| `- Team license target: \`$${metrics.team_license_price_usd}\``, | |
| `- Sales needed: \`${metrics.sales_needed}\``, | |
| `- Team licenses needed: \`${metrics.team_licenses_needed}\``, | |
| `- Checkout live: \`${metrics.checkout_live}\``, | |
| `- Revenue proof required: \`${metrics.revenue_proof_required}\``, | |
| `- Revenue counting rule: \`${metrics.revenue_counting_rule}\``, | |
| `- Status: \`${metrics.status}\``, | |
| `- Updated: \`${metrics.updated_at}\``, | |
| `- Auto-refresh cadence: \`every 4 hours, plus issue/release/workflow events\``, | |
| "", | |
| "This repo cannot prove paid revenue until an authenticated checkout or seller dashboard exists.", | |
| "" | |
| ].join("\n"); | |
| fs.writeFileSync("metrics/README.md", readme); | |
| - name: Commit metrics | |
| run: | | |
| if git diff --quiet -- metrics/status.json metrics/README.md; then | |
| echo "No metrics changes" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add metrics/status.json metrics/README.md | |
| git commit -m "Update demand metrics" | |
| git pull --rebase origin main | |
| git push origin HEAD:main |