<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Data Accuracy on RockB</title><link>https://baeseokjae.github.io/tags/data-accuracy/</link><description>Recent content in Data Accuracy on RockB</description><image><title>RockB</title><url>https://baeseokjae.github.io/images/og-default.png</url><link>https://baeseokjae.github.io/images/og-default.png</link></image><generator>Hugo</generator><language>en-us</language><lastBuildDate>Tue, 07 Jul 2026 21:00:00 +0000</lastBuildDate><atom:link href="https://baeseokjae.github.io/tags/data-accuracy/index.xml" rel="self" type="application/rss+xml"/><item><title/><link>https://baeseokjae.github.io/posts/copilot-usage-metrics-accuracy-coverage-guide-2026/</link><pubDate>Tue, 07 Jul 2026 21:00:00 +0000</pubDate><guid>https://baeseokjae.github.io/posts/copilot-usage-metrics-accuracy-coverage-guide-2026/</guid><description>A practical deep-dive into GitHub Copilot usage metrics accuracy, data coverage gaps, and building reliable team-level reports. What the numbers actually mean and where they break down.</description><content:encoded><![CDATA[<p>If you&rsquo;re running Copilot Enterprise across a few hundred engineers, you&rsquo;ve probably stared at the usage dashboard and asked yourself: <em>can I trust these numbers?</em> The short answer is yes, with caveats. The longer answer is what this article is about.</p>
<p>I&rsquo;ve been working with Copilot&rsquo;s metrics pipeline across several mid-to-large enterprise deployments, and I keep running into the same three questions: How accurate are these metrics? What&rsquo;s actually covered by the data? And how do I build reliable team-level reports when the API doesn&rsquo;t give me a team endpoint? This guide tackles each of those head-on.</p>
<h2 id="accuracy-what-the-numbers-actually-mean">Accuracy: What the Numbers Actually Mean</h2>
<p>The first thing to understand is that Copilot usage metrics come from two separate telemetry pipelines, and they don&rsquo;t tell the same story.</p>
<h3 id="client-side-vs-server-side-telemetry">Client-Side vs Server-Side Telemetry</h3>
<p><strong>Client-side IDE telemetry</strong> is the primary source. When a developer uses Copilot in VS Code, JetBrains, or another supported IDE, the extension sends events directly to GitHub — completions accepted or rejected, chat requests sent, agent mode sessions started. This is where the rich data lives: language breakdowns, model usage, LoC metrics, the works.</p>
<p><strong>Server-side telemetry</strong> is the fallback. GitHub detects activity through server-side signals — API calls, license checks, Copilot Chat on GitHub.com. It confirms a user is active, but that&rsquo;s about it.</p>
<p>Here&rsquo;s the problem: server-side users count fully toward your Daily Active Users (DAU) and Weekly Active Users (WAU), but their dimensional breakdowns are empty. You&rsquo;ll see a user in your active count with zero lines of code, zero chat requests, zero completions attributed to them.</p>
<p>In practice, I&rsquo;ve seen 5-15% of DAU come from server-side only in standard setups, and up to 20% in heavily locked-down enterprises with restrictive proxies or custom network policies. If you&rsquo;re running a weekly report that shows average chat requests per active user, that average is artificially low because the server-side cohort drags it down with zeroes.</p>
<p><strong>What to do about it:</strong> When computing per-user engagement metrics, I filter to users who have at least one client-side telemetry event in the period. The denominator becomes &ldquo;users with measurable activity&rdquo; rather than &ldquo;all active users.&rdquo; This gives a more honest picture of engagement depth, even though it lowers your headline adoption numbers slightly.</p>
<h3 id="the-2-day-data-lag">The 2-Day Data Lag</h3>
<p>Data is available within 2 full UTC days after the day closes. Monday&rsquo;s numbers land on Wednesday morning. This isn&rsquo;t a bug — it&rsquo;s the processing pipeline doing deduplication, attribution, and aggregation.</p>
<p>The practical impact: don&rsquo;t build real-time dashboards. I&rsquo;ve seen teams try to wire Copilot metrics into a live Grafana panel and then panic when Tuesday afternoon shows zero data for Monday. Build daily or weekly batch reports instead. A Monday-morning script that pulls the previous week&rsquo;s data (Saturday through Friday, accounting for the 2-day lag) gives you clean, consistent snapshots.</p>
<h3 id="attribution-accuracy-the-org-membership-trap">Attribution Accuracy: The Org Membership Trap</h3>
<p>This is the one that trips up most engineering leaders. Organization-level metrics are based on <strong>organization membership</strong>, not on where the user&rsquo;s actions occurred.</p>
<p>Concrete example: Alice belongs to the &ldquo;Platform Engineering&rdquo; org and the &ldquo;Security&rdquo; org. She writes code in a Platform Engineering repo. Her usage appears in <em>both</em> org dashboards. If you sum the DAU across all your orgs, you&rsquo;ll overcount Alice twice.</p>
<p>The enterprise-level endpoint deduplicates users. The org-level endpoints don&rsquo;t. This is by design — org-level metrics are meant for visibility into adoption within a specific org, not for enterprise-wide rollups. But if you&rsquo;re building a consolidated report by summing org dashboards, your numbers will be wrong.</p>
<p><strong>The fix:</strong> Always use the enterprise-level endpoints (<code>/enterprises/{enterprise}/copilot/metrics/reports/...</code>) for your top-line numbers. Use org-level endpoints only when you need to understand adoption patterns within a specific org, and never sum them.</p>
<h2 id="coverage-what-gets-counted-and-what-doesnt">Coverage: What Gets Counted and What Doesn&rsquo;t</h2>
<p>Coverage is the second dimension of accuracy. Even when the telemetry is working correctly, it doesn&rsquo;t cover every scenario.</p>
<h3 id="ide-and-version-coverage">IDE and Version Coverage</h3>
<p>LoC metrics require minimum IDE versions. If your team hasn&rsquo;t upgraded, you&rsquo;re flying blind on lines-of-code data:</p>
<table>
  <thead>
      <tr>
          <th>IDE</th>
          <th>Minimum Version</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>VS Code</td>
          <td>1.104.0</td>
      </tr>
      <tr>
          <td>JetBrains</td>
          <td>2024.2.6</td>
      </tr>
      <tr>
          <td>Visual Studio</td>
          <td>17.14</td>
      </tr>
      <tr>
          <td>Xcode</td>
          <td>14.3.1</td>
      </tr>
  </tbody>
</table>
<p>In one deployment I worked with, about 30% of the JetBrains users were on 2024.1.x because the team had a policy of staying one major version behind. Those users generated completions and chat activity, but their LoC metrics were completely absent. The dashboard showed healthy adoption numbers but suspiciously low LoC totals.</p>
<p><strong>What to check:</strong> Run a quick IDE version survey across your org. If more than 10% of your users are below the minimum versions for LoC metrics, factor that into your reporting. Your LoC numbers are an undercount, and the gap will shrink as users upgrade.</p>
<h3 id="agent-mode-coverage">Agent Mode Coverage</h3>
<p>Agent mode doesn&rsquo;t follow the &ldquo;suggest then accept&rdquo; flow that inline completions use. Instead of a suggestion event followed by an accept or reject event, agent mode edits are counted as <code>loc_added_sum</code> and <code>loc_deleted_sum</code> under the <code>agent_edit</code> feature bucket.</p>
<p>This means agent mode activity won&rsquo;t show up in your acceptance rate metrics at all. If your team is heavy on agent mode (and more teams are every quarter, based on what I&rsquo;m seeing), your acceptance rate will look lower than the actual value Copilot is delivering. The code is being written — it&rsquo;s just not being measured the same way.</p>
<p>For more on how agent mode changes the Copilot experience, see the <a href="/posts/github-copilot-agent-mode-2026/">Copilot Agent Mode guide</a>.</p>
<h3 id="pr-lifecycle-coverage">PR Lifecycle Coverage</h3>
<p>Pull request lifecycle metrics come from a completely different pipeline than IDE telemetry. PR data is derived from repository activity on GitHub.com. This means a developer who uses Copilot CLI to write code, commits via the terminal, and opens a PR through the web UI will show Copilot attribution on the PR even if their IDE never reported a single telemetry event.</p>
<p>This is actually a feature, not a bug — it captures Copilot usage that IDE telemetry would miss. But it creates a confusing reporting situation where your PR metrics show more Copilot activity than your IDE metrics do. If you&rsquo;re reconciling the two, expect PR-based metrics to be 5-15% higher than IDE-based metrics in orgs with significant terminal-based workflows.</p>
<h2 id="team-level-reporting-building-it-yourself">Team-Level Reporting: Building It Yourself</h2>
<p>There is no dedicated team-level endpoint in the Copilot Usage Metrics API. If you want per-team numbers, you build them yourself by joining two reports.</p>
<h3 id="the-join-approach">The Join Approach</h3>
<p>The method is straightforward:</p>
<ol>
<li>Download the <strong>user usage report</strong> for a given day (per-user metrics)</li>
<li>Download the <strong>user-teams report</strong> for the same day (which teams each user belongs to)</li>
<li>Inner-join on <code>user_id</code>, then group by <code>team_id</code></li>
</ol>
<p>The user-teams endpoints are:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 632 41"
      >
      <g transform='translate(8,16)'>
<path d='M 28,24 L 36,8' fill='none' stroke='currentColor'></path>
<path d='M 120,16 L 128,0' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='0' y='4' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='0' y='20' fill='currentColor' style='font-size:1em'>G</text>
<text text-anchor='middle' x='8' y='4' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='8' y='20' fill='currentColor' style='font-size:1em'>E</text>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='48' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='80' y='20' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='88' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='96' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='96' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='104' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='104' y='20' fill='currentColor' style='font-size:1em'>g</text>
<text text-anchor='middle' x='112' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='112' y='20' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='120' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='136' y='4' fill='currentColor' style='font-size:1em'>{</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='176' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='192' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='200' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='208' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='208' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='216' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='216' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='224' y='4' fill='currentColor' style='font-size:1em'>}</text>
<text text-anchor='middle' x='224' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='232' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='232' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='240' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='240' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='248' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='256' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='264' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='296' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='304' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='320' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='328' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='328' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='336' y='4' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='336' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='4' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='344' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='352' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='352' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='360' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='360' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='368' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='368' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='376' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='376' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='384' y='4' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='384' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='392' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='416' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>/</text>
<text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='448' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='448' y='20' fill='currentColor' style='font-size:1em'>?</text>
<text text-anchor='middle' x='456' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='464' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='464' y='20' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='472' y='4' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='472' y='20' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='480' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='480' y='20' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='488' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='488' y='20' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='496' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='496' y='20' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='504' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='504' y='20' fill='currentColor' style='font-size:1em'>Y</text>
<text text-anchor='middle' x='512' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='520' y='4' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='528' y='4' fill='currentColor' style='font-size:1em'>-</text>
<text text-anchor='middle' x='536' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='544' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='552' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='560' y='4' fill='currentColor' style='font-size:1em'>?</text>
<text text-anchor='middle' x='568' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='576' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='584' y='4' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='592' y='4' fill='currentColor' style='font-size:1em'>=</text>
<text text-anchor='middle' x='600' y='4' fill='currentColor' style='font-size:1em'>D</text>
<text text-anchor='middle' x='608' y='4' fill='currentColor' style='font-size:1em'>A</text>
<text text-anchor='middle' x='616' y='4' fill='currentColor' style='font-size:1em'>Y</text>
</g>

    </svg>
  
</div>
<p>Here&rsquo;s a production script I use for weekly team reports:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> pandas <span style="color:#66d9ef">as</span> pd
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> json
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> datetime <span style="color:#f92672">import</span> datetime, timedelta
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">build_team_report</span>(org: str, date: str, token: str):
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;&#34;&#34;Build per-team Copilot metrics for a given day.&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Download reports (omitting curl logic for brevity)</span>
</span></span><span style="display:flex;"><span>    users_df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_json(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;users-</span><span style="color:#e6db74">{</span>date<span style="color:#e6db74">}</span><span style="color:#e6db74">.ndjson&#34;</span>, lines<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>    teams_df <span style="color:#f92672">=</span> pd<span style="color:#f92672">.</span>read_json(<span style="color:#e6db74">f</span><span style="color:#e6db74">&#34;user-teams-</span><span style="color:#e6db74">{</span>date<span style="color:#e6db74">}</span><span style="color:#e6db74">.ndjson&#34;</span>, lines<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Explode team memberships — one row per user-team pair</span>
</span></span><span style="display:flex;"><span>    team_members <span style="color:#f92672">=</span> teams_df<span style="color:#f92672">.</span>explode(<span style="color:#e6db74">&#34;teams&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Inner join: only users with both metrics and team membership</span>
</span></span><span style="display:flex;"><span>    joined <span style="color:#f92672">=</span> team_members<span style="color:#f92672">.</span>merge(
</span></span><span style="display:flex;"><span>        users_df,
</span></span><span style="display:flex;"><span>        left_on<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;user_id&#34;</span>,
</span></span><span style="display:flex;"><span>        right_on<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;id&#34;</span>,
</span></span><span style="display:flex;"><span>        how<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;inner&#34;</span>
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#75715e"># Aggregate by team</span>
</span></span><span style="display:flex;"><span>    team_agg <span style="color:#f92672">=</span> joined<span style="color:#f92672">.</span>groupby(<span style="color:#e6db74">&#34;teams&#34;</span>)<span style="color:#f92672">.</span>agg(
</span></span><span style="display:flex;"><span>        active_users<span style="color:#f92672">=</span>(<span style="color:#e6db74">&#34;user_id&#34;</span>, <span style="color:#e6db74">&#34;nunique&#34;</span>),
</span></span><span style="display:flex;"><span>        avg_acceptance_rate<span style="color:#f92672">=</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;copilot_ide_code_completions_acceptance_rate&#34;</span>, <span style="color:#e6db74">&#34;mean&#34;</span>
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        total_chat_requests<span style="color:#f92672">=</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;copilot_ide_chat_total_requests&#34;</span>, <span style="color:#e6db74">&#34;sum&#34;</span>
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        total_completions<span style="color:#f92672">=</span>(
</span></span><span style="display:flex;"><span>            <span style="color:#e6db74">&#34;copilot_ide_code_completions_total_engaged_users&#34;</span>, <span style="color:#e6db74">&#34;sum&#34;</span>
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    )<span style="color:#f92672">.</span>reset_index()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> team_agg
</span></span></code></pre></div><h3 id="critical-gotchas">Critical Gotchas</h3>
<p><strong>Teams with fewer than 5 seated Copilot users are excluded</strong> from the user-teams report. If you have a small team (a 3-person SRE squad, for example), they simply won&rsquo;t appear. This is a privacy safeguard from GitHub, and there&rsquo;s no way around it.</p>
<p><strong>Users on multiple teams contribute to each team&rsquo;s totals.</strong> If Alice is on both &ldquo;Frontend&rdquo; and &ldquo;API&rdquo; teams, her usage counts in both. Summing team rows will double-count her. When you present team-level data, make sure your audience understands this — or use <code>COUNT(DISTINCT user_id)</code> for distinct-user metrics and accept that team-level distinct counts won&rsquo;t sum to org-level totals.</p>
<p><strong>Team membership changes over time.</strong> If you&rsquo;re building rolling 28-day team reports, join each day&rsquo;s reports separately rather than joining once and assuming membership is stable. A user who moved teams mid-month will have their early-month activity attributed to the old team and late-month activity to the new team if you join per-day. If you join once and apply the same membership to all days, you&rsquo;ll misattribute.</p>
<p>For a broader look at setting up Copilot across your organization, see the <a href="/posts/github-copilot-enterprise-guide-2026/">GitHub Copilot Enterprise Guide</a>.</p>
<h2 id="practical-recommendations">Practical Recommendations</h2>
<p>After working through these accuracy, coverage, and reporting challenges across several orgs, here&rsquo;s what I&rsquo;d recommend:</p>
<p><strong>1. Document your data sources.</strong> In every report you produce, note whether the numbers come from client-side telemetry, server-side telemetry, or a mix. Your stakeholders need to know that &ldquo;active users&rdquo; and &ldquo;users with LoC data&rdquo; are different populations.</p>
<p><strong>2. Run a weekly reconciliation.</strong> Every Monday, pull the enterprise-level 28-day report and compare it against the sum of your org-level reports. The gap between the two is your cross-org deduplication rate. If it&rsquo;s growing, your users are joining more orgs — which might be worth investigating.</p>
<p><strong>3. Track agent adoption separately.</strong> Since agent mode doesn&rsquo;t contribute to acceptance rate, create a separate dashboard for agent activity. Watch the ratio of agent edits to inline completions over time. A rising ratio means your team is moving toward autonomous AI workflows, which changes how you should interpret the traditional metrics.</p>
<p>For more on how agentic workflows change the metrics picture, check out the <a href="/posts/github-copilot-agentic-code-review-2026/">Agentic Code Review guide</a>.</p>
<p><strong>4. Accept the 2-day lag.</strong> Build your reporting cadence around it. A weekly report that covers Monday through Sunday, pulled on Tuesday, gives you complete data for the previous week. Don&rsquo;t fight the pipeline — work with it.</p>
<p><strong>5. Cross-reference with DORA metrics.</strong> The real ROI story isn&rsquo;t in Copilot&rsquo;s dashboard — it&rsquo;s in your deployment frequency, lead time, and change failure rate. If you&rsquo;re already tracking DORA metrics, overlay your Copilot adoption data. Teams with high Copilot engagement that also ship faster with stable failure rates are your success stories. The <a href="/posts/github-copilot-usage-metrics-guide-2026/">general usage metrics guide</a> covers the five metric categories in more detail if you need a refresher.</p>
<h2 id="the-bottom-line">The Bottom Line</h2>
<p>GitHub Copilot&rsquo;s metrics are the most mature of any AI coding tool on the market, but they&rsquo;re not a turnkey solution. The dual telemetry pipelines, the org attribution model, the IDE version requirements, and the lack of a native team endpoint all mean you need to understand the data before you can trust it.</p>
<p>Build your reporting around the enterprise-level endpoints, account for the 2-day lag, filter server-side users when computing per-user engagement, and build team-level reports through the user-teams join. Do that, and you&rsquo;ll have a solid, defensible view of whether Copilot is actually making your engineering organization faster.</p>
]]></content:encoded></item></channel></rss>