Skip to main content

Advanced analytics

Joining Viva Insights queries

Combine Person, Consumption, GitHub and Agent query exports into a person-week panel for analysis and reporting.

Overview

This tutorial walks through how to join the four custom queries in Viva Insights (Person, Consumption, GitHub and Agent) to bring together behavioural and AI usage metrics, alongside organisational attributes, in a single panel data file for analysis and reporting. The resulting file has one row per person per week.

This is the current reference for these cross-query joins and takes precedence over older schema and join guidance in this repository. Historical layouts and synthetic fixture manifests remain available for reproducing earlier examples. This guide does not extend the existing dashboard runners' supported inputs.

You need the Person Query plus at least one of the Consumption, GitHub or Agent queries. We recommend using all four for a broader view of a population's ways of working and AI usage across the products and activities they measure.

All four queries are available under Create analysis > Customized query in Viva Insights, where you can adjust parameters such as date grouping and population filters. We recommend this route because it offers more customisation parameters than dashboard exports.

We recommend exporting all selected queries grouped by Week, with the same week start. Consumption and GitHub default to Day, so change their grouping at setup. Weekly exports avoid daily-to-weekly aggregation and sparse-day handling, although service, spending-policy and agent rules still apply. If you need daily detail or must align different week starts, export those queries by Day and aggregate to the Person Query weeks.

Record the configured grouping for every query before validating its dates. Sparse observed dates alone cannot distinguish daily from weekly or monthly data.

The cards below use the query icons and descriptions shown in Viva Insights. Select a query to jump to its schema.

Join architecture

The weekly Person Query is the spine. Validate each other selected query, bring it to one row per person per week, and left join it onto the Person Query. The diagram shows all four queries. Follow the paths for the queries you have, then select your analysis cohort from the joined population.

Join architecture Consumption, GitHub and Agent files are validated and aggregated to person-week summaries, then left joined onto the weekly Person Query on PersonId and WeekStart. A cohort is selected after integration. GitHub breakdowns stay as linked detail. Consumption query 4 files, day or week Validate keys, check additivity Bring to person-week GitHub query 5 files, day or week Validate keys, check additivity Bring to person-week Agent query 2 files, week Validate keys, check additivity Bring to person-week Person query Weekly spine: one row per PersonId and WeekStart LEFT JOIN each summary, one-to-one Select cohort Then analyse and publish groups of 10 or more people Linked detail, outside the panel: service, spending policy, agent, GitHub breakdowns

Query schemas

Export layouts vary between tenants and over time, so check each supplied export against the schemas below. For all four queries, we recommend the Create analysis route because it offers more customisation parameters than dashboard exports.

Person query

Recommended route: Create analysis > Customized query > Person query.

The Person query is available only through this route. It gives you access to the full Person query metric list, including behavioural metrics such as Email hours, Meeting hours and Internal network size. Select the metrics you need when setting up the query.

Weekly. Daily or monthly groupings are outside this contract.

Person_<username>_<date>_<time>Hours.csv

Grain: One row per person per week. Native key: PersonId, MetricDate. A single CSV, for example Person_jdoe_Oct27_2026_1027Hours.csv. Localised exports translate the words in the name, and the public sample fixture is PersonQuery.csv, so identify the file by its PersonId and MetricDate columns.

Columns in Person_<username>_<date>_<time>Hours.csv
ColumnTypeRoleNotes
PersonIdstringKeyPseudonymised person identifier. Keep as text.
MetricDatedateKeyWeek start. Detect the weekday from the data.
HR attributesvariesColumn groupConfigured at query setup. Observed examples: Organization, FunctionType, SupervisorIndicator, Layer. Values can change between weeks.
Metric columnsvariesColumn groupSelected at query setup. See the metric families table.

Metric families

Metric columns are chosen when the query is set up, so each list is representative rather than complete, and wording or capitalisation can vary by query version. Headers are listed in their raw export form, with spaces. The vivainsights R and Python packages replace spaces with underscores when they import a query, so Collaboration hours becomes Collaboration_hours. Treat the two forms as the same column. This applies to headers only, never to identifier values. See the metric definitions on Microsoft Learn for meanings.

Person Query metric families
FamilyKindHeaders
Collaborationweekly totalCollaboration hours, External collaboration hours, Meeting hours, Email hours, Chat hours, After hours collaboration hours
Focusweekly totalAvailable to focus hours, Uninterrupted hours
NetworkstockInternal network size, External network size, Strong ties, Diverse ties, Network outside organization
Copilot eligibilityday countTotal Copilot enabled days
Copilot activity, totalsweekly total or day countTotal Copilot actions taken, Total Copilot active days
Copilot activity, by surfaceweekly total or day countCopilot actions taken in Copilot Chat, Copilot actions taken in Microsoft 365 Copilot app, Days of active Copilot Chat usage, Days of active Copilot Chat (legacy web) usage, Days of active Copilot usage in Excel, Days of active Copilot usage in Loop, Days of active Copilot usage in OneNote, Days of active Copilot usage in Outlook, Days of active Copilot usage in Powerpoint, Days of active Copilot usage in Teams, Days of active Copilot usage in Word
Coworkweekly total or day countCowork tasks submitted, User-initiated Cowork tasks submitted, Days of active Cowork usage
Unlicensed Copilot Chatweekly total or day countInspect the export
A separate metric family for Copilot Chat use without a Microsoft 365 Copilot licence. Inspect the export for its headers and keep it separate from licensed Copilot metrics.

Consumption query

Recommended route: Create analysis > Customized query > Consumption query.

You can also use the Export data button in the Consumption dashboard.

Daily by default, or weekly when grouped by week at setup. Facts plus metadata files.

PersonM365CreditsMetrics.csv

Grain: One row per person, service, spending policy and period (day or week). Native key: PersonId, ServiceId, SpendingPolicyId, MetricDate.

Columns in PersonM365CreditsMetrics.csv
ColumnTypeRoleNotes
PersonIdstringKey
ServiceIdstringKey
ServiceNamestringAttribute
SpendingPolicyIdstringKey
MetricDatedateKeyDay or week start, as set at query setup.
Session countnumberMeasure
Spending policy limitnumberLimit
Total Copilot Credits usednumberMeasure
User limitnumberLimit
PeopleHistoricalIdstringJoin keyLinks to PeopleMetaData.csv.

PersonGitHubCreditsMetrics.csv

Grain: One row per person and period with billable GitHub usage (sparse). Native key: PersonId, MetricDate.

Columns in PersonGitHubCreditsMetrics.csv
ColumnTypeRoleNotes
PersonIdstringKey
MetricDatedateKeyDay or week start, as set at query setup.
Total GitHub AI Credits usednumberMeasure
PeopleHistoricalIdstringJoin keyLinks to PeopleMetaData.csv.

PeopleMetaData.csv

Grain: One row per historical person record. Native key: PeopleHistoricalId. Carries no HR attributes. Take organisational attributes from the Person Query.

Columns in PeopleMetaData.csv
ColumnTypeRoleNotes
PeopleHistoricalIdstringKeyNo PersonId in this file.
IsCopilotLicensedboolean-like textAttributeHistorical Microsoft 365 Copilot licence evidence only. Not evidence of GitHub Copilot provisioning.

M365SpendingPolicyMetaData.csv outside the core panel

Grain: One row per spending policy. Native key: SpendingPolicyId.

Columns in M365SpendingPolicyMetaData.csv
ColumnTypeRoleNotes
SpendingPolicyIdstringKey
Policy attributesvariesColumn groupDescriptive policy fields. Inspect the headers in each export.

GitHub query

Recommended route: Create analysis > Customized query > GitHub query.

Daily by default, or weekly when grouped by week at setup. Activity facts plus dimensional breakdowns.

PersonGitHubActivityMetrics.csv

Grain: One row per person and period. Native key: PersonId, MetricDate.

Columns in PersonGitHubActivityMetrics.csv
ColumnTypeRoleNotes
PersonIdstringKey
MetricDatedateKeyDay or week start, as set at query setup.
Agent adoptionnumberIndicatorAdoption indicator or percentage. Excludes code review.
Code completions acceptednumberMeasure
Code completions suggestednumberMeasure
User-initiated chat requestsnumberMeasure

Breakdown files part of the export, outside the core panel

Each adds one or two dimensions to the person-period grain, so the four files do not share a grain with each other or with the activity file. Keep them as linked detail (G11).

GitHub breakdown files
FileNative keyMeasure
GitHubActivityBreakdownByFeatureMetrics.csvPersonId, MetricDate, FeatureFeature Usage Count
GitHubActivityBreakdownByLanguageFeatureMetrics.csvPersonId, MetricDate, Language, FeatureUsage count by language and feature
GitHubActivityBreakdownByLanguageModelMetrics.csvPersonId, MetricDate, Language, ModelUsage count by language and model
GitHubActivityBreakdownByModelFeatureMetrics.csvPersonId, MetricDate, Model, FeatureUsage count by model and feature

Agent query

Recommended route: Create analysis > Customized query > Agent query.

You can also use the Export data button in the Agent Dashboard.

Weekly facts with an optional metadata file.

PersonAgentActivityMetrics.csv

Grain: One row per person, agent and week. Native key: PersonId, AgentId, MetricDate.

Columns in PersonAgentActivityMetrics.csv
ColumnTypeRoleNotes
PersonIdstringKey
AgentIdstringKey
MetricDatedateKeyWeekly. Sunday-start in observed exports.
Conversational agent sessionsnumberMeasure
Returning Agent 365 user (most recent and prior 7 days)numberIndicator0 or 1 over a 7-day window.
Returning Agent 365 user (most recent and prior 28 days)numberIndicator0 or 1 over a 28-day window.

AgentMetadata.csv optional in the export, outside the core panel

Grain: One row per agent. Native key: AgentId. Present in some exports and absent in others.

Columns in AgentMetadata.csv
ColumnTypeRoleNotes
AgentIdstringKey
Agent attributesvariesColumn groupDescriptive agent fields. Inspect the headers in each export. Never use them to identify people.

Join keys

Keys and where they appear
KeyAppears inUse
PersonIdEvery person-level file in all four queriesPseudonymised person and the panel join key. Verify overlap between sources (G15).
MetricDateEvery fact file and the Person QueryPeriod start. Weekly in the Person and Agent queries. Daily by default in Consumption and GitHub, or weekly when grouped by week at setup. Real exports and the sample fixtures use Sunday-start weeks, while some documentation describes Monday, so detect it (G03, G03b).
WeekStartChosen name in this guideWeekStart is this guide's chosen name for an aligned week-start date. It is not an extra column supplied by the exports. You may keep MetricDate or choose another name, provided every source uses the same week definition.
PeopleHistoricalIdBoth Consumption fact files and PeopleMetaData.csvHistorical person record. Read it from each fact row. It cannot be derived from PersonId.
ServiceIdPersonM365CreditsMetrics.csvPart of the native key. Keep service detail as linked detail.
SpendingPolicyIdPersonM365CreditsMetrics.csv and M365SpendingPolicyMetaData.csvPart of the native key. Unique keys do not establish additivity (G05).
AgentIdBoth Agent filesStable agent key. Agent names can change.
Feature, Language, ModelGitHub breakdown filesExtra dimensions that change the grain (G11).

Summaries you create

The names below describe intermediate tables you create in your script. They are not extra downloads or required object names. Choose your own names for data frames, SQL views or other objects. Each summary, and the final combined panel, has one row per person per week.

WeekStart is this guide's chosen name for an aligned week-start date. It is not an extra column supplied by the exports. You may keep MetricDate or choose another name, provided every source uses the same week definition. The source filenames below link to their schemas. The step links take you to the numbered Join procedure later on this page.

Preparing summaries and the final panel
Table you createSource exportWhat to do
Microsoft 365 credits summaryPersonM365CreditsMetrics.csvSum credits across services and spending policies after G05, and across days if the export is daily. Include sessions summed across policies only when G05b passes.
Step 5: Check policy additivity
Step 6: Bring each source to person-week
GitHub credits summaryPersonGitHubCreditsMetrics.csvSum credits across days if the export is daily. A weekly export is already one row per person-week.
Step 6: Bring each source to person-week
GitHub activity summaryPersonGitHubActivityMetrics.csvApply the measure rules, aggregating across days only if the export is daily.
Step 6: Bring each source to person-week
Agent activity summaryPersonAgentActivityMetrics.csvSum sessions across agents. Derive any person-level returning flag explicitly.
Step 6: Bring each source to person-week
Combined person-week panelPerson_<username>_<date>_<time>Hours.csvStart with the Person Query and left join each summary. Keep every Person Query row.
Step 7: Join onto the spine

How to connect the data

Attach metadata before summarising

In step 4: Validate each file, match each fact row to at most one metadata row using the exported key shown below. First check that the key is unique in the metadata file. Report unmatched keys and ensure the join does not multiply fact rows.

Metadata lookups on exported columns
Fact fileMetadata fileMatch onUse
PersonM365CreditsMetrics.csvPeopleMetaData.csvPeopleHistoricalIdMicrosoft 365 Copilot licence evidence, before aggregation
PersonGitHubCreditsMetrics.csvPeopleMetaData.csvPeopleHistoricalIdCheck that each historical person record exists. IsCopilotLicensed describes Microsoft 365 only and cannot confirm GitHub eligibility.
PersonM365CreditsMetrics.csvM365SpendingPolicyMetaData.csvSpendingPolicyIdCheck that each spending policy exists in the metadata.
PersonAgentActivityMetrics.csvAgentMetadata.csvAgentIdOptional enrichment, only when the metadata file is supplied

Add summaries to the Person Query

In step 7: Join onto the spine, start with the weekly Person Query on the left and add each summary on the right with a left join. The summary names below refer to the tables you created above. Match on PersonId and the aligned week-start date, called WeekStart in this guide. Both sides must have at most one row per person per week.

Left joins that preserve the Person Query rows
Summary to add (right side)Match onExpected result
Microsoft 365 credits summaryPersonId, WeekStartKeep every Person Query row. Match at most one summary row per person and week.
GitHub credits summaryPersonId, WeekStartKeep every Person Query row. Match at most one summary row per person and week.
GitHub activity summaryPersonId, WeekStartKeep every Person Query row. Match at most one summary row per person and week.
Agent activity summaryPersonId, WeekStartKeep every Person Query row. Match at most one summary row per person and week.

Keep GitHub breakdowns separate

These files have multiple rows per person and period. Joining them directly to the person-week panel would repeat panel rows and inflate totals. Keep each as a separate detail table, aligning its dates to the Person Query weeks when needed.

Detail outside the person-week panel
Source filesTreatmentHow to relate them
GitHubActivityBreakdownByFeatureMetrics.csv, GitHubActivityBreakdownByLanguageFeatureMetrics.csv, GitHubActivityBreakdownByLanguageModelMetrics.csv, GitHubActivityBreakdownByModelFeatureMetrics.csvNever joined wide. Keep as separate detail tables keyed to the panelUse PersonId and the aligned week to relate detail to the panel. Retain each file's additional dimensions.

Aggregation rules

Apply these rules to panel-bound summaries and when rolling person-weeks up to longer periods. Weekly grouping removes daily aggregation, but does not make every measure additive across services, spending policies or agents. GitHub breakdowns remain separate, retaining their dimensions.

Person-week rule for each measure
IDColumnsFileKindPerson-week rule
M01Total Copilot Credits usedPersonM365CreditsMetrics.csvadditiveSum across services and spending policies after G05 passes, and across days when the export is daily.
M02Session countPersonM365CreditsMetrics.csvconditionally additiveSum across services, and across days when the export is daily. Sum across SpendingPolicyId rows only when G05b passes. Sessions are not people.
M03Spending policy limit, User limitPersonM365CreditsMetrics.csvlimitNever sum. Keep per policy as linked detail.
M04Total GitHub AI Credits usedPersonGitHubCreditsMetrics.csvadditiveSum across days when the export is daily. Keep separate from Microsoft 365 credits.
M05Code completions accepted, Code completions suggested, User-initiated chat requestsPersonGitHubActivityMetrics.csvadditiveSum across days when the export is daily. Acceptance rate is summed accepted divided by summed suggested, with a stated zero-denominator rule.
M06Agent adoptionPersonGitHubActivityMetrics.csvindicatorDo not sum. For a daily export, state a derived weekly rule, for example any positive day in the week. For a weekly export, use the exported value.
M07Feature Usage CountGitHubActivityBreakdownByFeatureMetrics.csvadditive within fileSum days within this file when daily, retaining Feature for each person-week. Keep as linked detail. Never add to another breakdown.
M08Usage count by language and featureGitHubActivityBreakdownByLanguageFeatureMetrics.csvadditive within fileSum days within this file when daily, retaining Language and Feature for each person-week. Keep as linked detail. Never add to another breakdown.
M09Usage count by language and modelGitHubActivityBreakdownByLanguageModelMetrics.csvadditive within fileSum days within this file when daily, retaining Language and Model for each person-week. Keep as linked detail. Never add to another breakdown.
M10Usage count by model and featureGitHubActivityBreakdownByModelFeatureMetrics.csvadditive within fileSum days within this file when daily, retaining Model and Feature for each person-week. Keep as linked detail. Never add to another breakdown.
M11Conversational agent sessionsPersonAgentActivityMetrics.csvadditiveSum across agents to person-week.
M12Returning Agent 365 user (most recent and prior 7 days)PersonAgentActivityMetrics.csvwindow indicatorDo not sum across agents or weeks. A person-level flag, such as returning for any agent, is a stated derived definition.
M13Returning Agent 365 user (most recent and prior 28 days)PersonAgentActivityMetrics.csvwindow indicatorDo not sum across agents or weeks. A person-level flag, such as returning for any agent, is a stated derived definition.
M14Internal network size, External network sizePerson_<username>_<date>_<time>Hours.csvstockDetect and record the measurement window. Values can repeat within a month. Average or deduplicate snapshots according to that window, never sum.
M15Days of active Copilot Chat usage, Days of active Copilot Chat (legacy web) usage, Days of active Copilot usage in Excel, Days of active Copilot usage in Loop, Days of active Copilot usage in OneNote, Days of active Copilot usage in Outlook, Days of active Copilot usage in Powerpoint, Days of active Copilot usage in Teams, Days of active Copilot usage in WordPerson_<username>_<date>_<time>Hours.csvday countNever add across surfaces to make total active days, because one day can include several surfaces. Use Total Copilot active days.

Join procedure

Follow the procedure for the queries you have. Apply source-specific checks only when that source is supplied, for example step 5: Check policy additivity applies to Consumption. Record omitted queries as unavailable, without treating their absence as evidence of non-use.

  1. Match the layout G01
    Confirm that the Person Query and at least one of Consumption, GitHub or Agent are supplied. List the files for each selected query and compare their names and key columns with the schema tables. Record the export date, configured population, date window and product filters, which layout matched and which queries are unavailable.
  2. Load safely G02
    Read every identifier column as text and parse dates explicitly. Headers may arrive in raw form with spaces, or with underscores after import through the vivainsights packages.
  3. Fix the target grain G03, G03b
    Recommend weekly grouping for every selected query. Record each configured grouping from query setup or equivalent export metadata before checking dates. Confirm the Person Query is weekly with one row per PersonId and MetricDate, and detect its weekday as WeekStart. Validate daily fact dates or matching weekly starts. Stop if grouping is unknown, rather than inferring it from sparse dates.
  4. Validate each file G04, G06
    Assert each native key. Join metadata files many-to-one and report unmatched counts. Check that each PeopleHistoricalId maps to one PersonId.
  5. Check policy additivity G05, G05b
    In PersonM365CreditsMetrics.csv, count person-service-period keys with more than one positive-credit SpendingPolicyId row. Zero means credits can be summed. Otherwise stop for a documented decision, and confirm separately whether it covers sessions.
  6. Bring each source to person-week G07, G10, G11
    Build only panel-bound summaries at one row per person-week. For a configured daily export, map MetricDate to WeekStart and sum permitted measures across days. For a weekly export, use MetricDate as WeekStart. Follow the measure-specific service, policy and agent rules, including G05b for sessions. Keep unconfirmed per-policy sessions and dimension-preserving GitHub breakdowns as linked detail. Keep Microsoft 365 and GitHub credits separate.
  7. Join onto the spine G08, G15
    Left join each summary to the Person Query on PersonId and WeekStart. Assert one-to-one, check PersonId overlap, and reconcile row counts and additive totals.
  8. Label missing values G09
    Assume supplied Viva Insights data are complete within the recorded query scope. For activity measures, treat omitted inactive person-days, or an entire inactive person-week, as zero. Label unavailable or out-of-scope data, known export problems and null cells separately. Record partial boundary weeks and keep licence eligibility separate from activity zeros.
  9. Select the cohort G12, G13
    Choose the population for the question, for example everyone eligible for an adoption view, and record the rule and the measurement windows.
  10. Publish safely G14, G16, G17
    Publish only groups of at least 10 distinct people in every slice, withhold related breakdowns that would allow subtraction, keep identifiers out of every output and describe results as associations.

Gates

19 checks before and during the join
IDLevelRule
G01MUSTRequire the Person Query plus at least one of Consumption, GitHub or Agent. Record which queries are supplied and identify each export's layout. Within each selected query, files with required_for_layout_match set to true must all be present. If files or key columns differ from this contract, stop and record the difference.
G02MUSTRead identifiers as text. Do not change their case, trim them silently or fuzzy-match people.
G03MUSTConfirm weekly grouping from the Person Query configuration or equivalent export metadata, then validate one row per PersonId and MetricDate and detect the weekday of MetricDate. Stop if grouping is unknown, daily or monthly. Do not assume Monday or Sunday.
G03bMUSTRecord each fact export's configured date grouping from query setup or equivalent export metadata, then validate MetricDate against it. Sparse dates alone cannot establish cadence. Recommend weekly grouping for all queries. Daily and weekly exports are supported. A weekly export must start on the same weekday as the Person Query. Stop for unknown grouping, any other period such as monthly, or a different weekly start.
G04MUSTAssert uniqueness of each file's native key. Report duplicate counts and stop. Never keep an arbitrary first row.
G05MUSTBefore summing Microsoft 365 credits across SpendingPolicyId, count person-service-period keys with more than one positive-credit row. If any exist, obtain a documented confirmation that the rows record disjoint consumption.
G05bMUSTSum Session count across SpendingPolicyId rows only when the documented confirmation explicitly covers sessions. Otherwise keep sessions per policy.
G06MUSTJoin metadata many-to-one on unique keys. Report unmatched key counts without identifiers. Check that each PeopleHistoricalId maps to one PersonId.
G07MUSTBring only panel-bound summaries to one row per PersonId and WeekStart before joining. Apply the measure rules: sum days only for configured daily exports, credits across policies only after G05, and sessions across policies only after G05b. Otherwise retain sessions per policy outside the panel. Keep GitHub breakdown dimensions as linked detail under G11. Never expand weekly rows into invented daily rows.
G08MUSTLeft join each person-week summary onto the Person Query. Assert one-to-one, preserve the Person Query row count, and reconcile additive totals before and after.
G09MUSTAssume supplied Viva Insights queries are complete within their configured population, date window and product filters. Omitted activity rows represent inactivity by default, so record zero activity within that scope without requiring separate completeness evidence. Do not zero-fill unavailable queries, out-of-scope periods or people, null cells in existing rows, rolling indicators, limits, or known export problems. Record partial boundary weeks separately. Eligibility is a separate attribute: Microsoft 365 licence evidence never confirms GitHub eligibility.
G10MUSTKeep Microsoft 365 and GitHub credits in separate columns. Never derive tokens or currency from credits.
G11MUSTDo not join GitHub breakdown files wide. Keep them as linked detail tables.
G12SHOULDTreat integration and cohort selection as separate, declared steps. An unavailable query does not establish non-use.
G13SHOULDRecord each variable's measurement window. Network sizes can repeat within a month and are averaged rather than summed.
G14MUSTCoverage, licence and additivity confirmations apply only to the export they were given for.
G15MUSTCheck PersonId overlap between sources before joining. Investigate low overlap against activity filters, omitted inactive people and configured populations before concluding that pseudonymisation differs. Do not repair identifiers by matching on attributes.
G16MUSTPublish a group only when it contains at least 10 distinct people, counted with count_distinct(PersonId), in every filter and time slice. Withhold an entire related breakdown if remaining cells or totals would reveal a suppressed cell by subtraction. Never output identifiers.
G17MUSTDescribe joined results as associations. Credits, sessions and actions do not measure value, quality or productivity.

Missing values

Default: assume supplied Viva Insights queries are complete. Inactive person-days are excluded by default. Within the query's scope, treat omitted activity rows as zero activity, including a person-week with no activity rows. This does not require separate completeness evidence.

Scope: The configured population, date window and product filters of each supplied Viva Insights query. Keep unavailable queries, out-of-scope people or periods, null cells in existing rows and known export problems distinct from inactivity. Partial boundary weeks do not establish full-week totals.

Activity zeros do not establish product eligibility or licence status. Use product-specific evidence for adoption denominators. Every joined measure should carry a state below, judged separately for each product. Use absent_inactive_zero for omitted activity rows under this default. Do not fill null cells, rolling indicators or limits merely because omitted activity is zero.

Missingness states
StateMeaning
observed_positiveA row exists with a positive value
observed_zeroA row exists with a recorded zero
absent_inactive_zeroNo activity row within a supplied query's configured population and date window. Assume complete Viva Insights data and record zero activity
absent_unknownThe query is unavailable, the person or period is outside its scope, a known export problem applies, or an absent non-activity measure has no defined zero interpretation. Keep as unknown
observed_nullA row exists but the measure is null. Do not apply the omitted-inactive-row default to this cell
ineligibleNot eligible for that product in the week
outside_cohortNot selected by the analysis cohort rule

Common errors

Symptoms, causes and fixes
SymptomLikely causeFix
Row count grows after a joinA fact file was joined before aggregation, or GitHub breakdowns were joined wideAggregate to one row per PersonId and WeekStart first. Keep breakdowns as linked detail.
Duplicate key on PersonId, ServiceId and MetricDateThe export includes SpendingPolicyId in the native keyUse the four-part key, then run the additivity check before summing.
Credits double after joining metadataA metadata key is not uniqueAssert a unique PeopleHistoricalId or SpendingPolicyId before the join.
Most Consumption or Agent rows do not match the Person QueryWeek starts differ, daily dates were not mapped to WeekStart, or PersonId values come from different populationsAlign to the detected WeekStart. Check PersonId overlap. Never match on attributes.
No HR attributes for groupingPeopleMetaData.csv carries only PeopleHistoricalId and IsCopilotLicensedTake attributes from the Person Query with a stated assignment rule.
Network size totals look implausibly largeRepeated monthly snapshots were summedAverage snapshots and record the measurement window.
Total active days exceed seven per weekPer-surface active days were added togetherUse Total Copilot active days for the total.
Weekly Consumption or GitHub rows match no Person Query weeksThe weekly export starts on a different weekday from the Person QueryExport the query daily and aggregate the days to the Person Query weeks. Weekly totals cannot be shifted to another week start.
Out-of-scope GitHub periods treated as zeroThe query's configured date window or population differs from the Person QueryApply the inactivity default only within the supplied query's scope. Keep unavailable or out-of-scope data unknown. Microsoft 365 licence evidence does not establish GitHub eligibility.

For coding agents

How coding agents should use this page
  • This page is designed to be read by coding agents as well as people. The complete contract is embedded as JSON in the script element with id join-contract and type application/json. Parse it instead of scraping the tables. The tables are generated from the same JSON.
  • Use input_requirements to check that the Person Query and at least one other query are supplied. Apply file requirements, joins and source-specific gates only to the selected queries. Record the unavailable queries explicitly.
  • Read coverage_assumption before handling absent rows. Assume complete Viva Insights exports within their configured scope, with omitted inactive person-days contributing zero activity. This is separate from product eligibility and does not apply to unavailable queries or null cells.
  • Every file, derived dataset, relationship (J1 to J9), measure (M01 to M15) and gate has a stable ID. These are contract references, not exported columns or required script object names. Resolve file IDs to file and derived IDs to their label, then map them to your own objects. Cite contract IDs in logs and summaries.
  • WeekStart is a chosen name for the aligned week date. Map it to your implementation, including MetricDate if you keep that header. See naming_conventions in the contract. build_steps refers to the numbered Join procedure on this page.
  • columns lists literal headers to find. column_groups describes configurable sets whose headers must be inspected in each export.
  • MUST gates block the join when they fail. Stop and report the gate ID, a bounded count and the next required input. SHOULD gates require a recorded decision.
  • Never print identifiers or employee rows. Report counts only.
  • If a supplied export does not match a layout here, do not adapt the contract silently. Report the difference (G01).

Download the JSON contract.

Show the JSON contract
{
  "contract_id": "viva-insights-query-join",
  "version": "0.7.0",
  "date": "2026-10-28",
  "status": "reference",
  "scope": "Joining weekly Viva Insights Person Query exports with Consumption, GitHub and Agent query exports into one person-week panel",
  "spine": "person_query.person",
  "input_requirements": {
    "required_query_ids": [
      "person_query"
    ],
    "additional_query_ids": [
      "consumption_query",
      "github_query",
      "agent_query"
    ],
    "minimum_additional_queries": 1,
    "recommendation": "Use all four queries for a broader view of ways of working and AI usage across the products and activities they measure.",
    "applicability": "Apply file requirements, joins and source-specific checks only to the selected queries. Record omitted queries as unavailable, without inferring non-use."
  },
  "naming_conventions": {
    "ids": "File and dataset IDs are references within this guide's contract. They are not exported columns, file paths or required names for objects in your script.",
    "derived_datasets": "Dataset labels describe intermediate summaries and the final panel that your script creates. Choose your own object names.",
    "week_start": "WeekStart is this guide's chosen name for an aligned week-start date. It is not an extra column supplied by the exports. You may keep MetricDate or choose another name, provided every source uses the same week definition."
  },
  "target_grain": {
    "keys": [
      "PersonId",
      "WeekStart"
    ],
    "person_query_cadence_required": "weekly",
    "week_start": "detect_from_person_query",
    "rule": "Export all selected queries grouped by week, with the same week start, wherever possible. This contract requires a weekly Person Query. `WeekStart` is its `MetricDate`. Consumption and GitHub default to daily, so change their grouping at setup. Use daily exports only when daily detail or week-start alignment is needed (G03, G03b)."
  },
  "coverage_assumption": {
    "complete_by_default": true,
    "scope": "The configured population, date window and product filters of each supplied Viva Insights query.",
    "missing_activity_rows": "Inactive person-days are excluded by default. Within the query's scope, treat omitted activity rows as zero activity, including a person-week with no activity rows. This does not require separate completeness evidence.",
    "exceptions": "Keep unavailable queries, out-of-scope people or periods, null cells in existing rows and known export problems distinct from inactivity. Partial boundary weeks do not establish full-week totals.",
    "eligibility": "Activity zeros do not establish product eligibility or licence status. Use product-specific evidence for adoption denominators."
  },
  "header_matching": "Headers are listed in their raw export form, with spaces. The vivainsights R and Python packages replace spaces with underscores when they import a query, so `Collaboration hours` becomes Collaboration_hours. Treat the two forms as the same column. This applies to headers only, never to identifier values.",
  "query_setup_recommendation": "For all four queries, we recommend the Create analysis route because it offers more customisation parameters than dashboard exports.",
  "queries": [
    {
      "id": "person_query",
      "name": "Person query",
      "role": "spine",
      "icon": "/assets/images/queries/person-query.svg",
      "product_description": "Analyse collaboration trends across the company",
      "recommended_path": [
        "Create analysis",
        "Customized query",
        "Person query"
      ],
      "access_context": "The Person query is available only through this route. It gives you access to the full Person query metric list, including behavioural metrics such as Email hours, Meeting hours and Internal network size. Select the metrics you need when setting up the query.",
      "cadence": "Weekly. Daily or monthly groupings are outside this contract.",
      "period_options": [
        "week"
      ],
      "default_period": "week",
      "verification": "observed_in_real_exports",
      "files": [
        {
          "id": "person_query.person",
          "file": "Person_<username>_<date>_<time>Hours.csv",
          "grain": "One row per person per week",
          "native_key": [
            "PersonId",
            "MetricDate"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": "Pseudonymised person identifier. Keep as text."
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": "Week start. Detect the weekday from the data."
            }
          ],
          "column_groups": [
            {
              "name": "HR attributes",
              "headers": "inspect_export",
              "note": "Configured at query setup. Observed examples: Organization, FunctionType, SupervisorIndicator, Layer. Values can change between weeks."
            },
            {
              "name": "Metric columns",
              "headers": "inspect_export",
              "note": "Selected at query setup. See the metric families table."
            }
          ],
          "file_pattern": "A single CSV, for example `Person_jdoe_Oct27_2026_1027Hours.csv`. Localised exports translate the words in the name, and the public sample fixture is `PersonQuery.csv`, so identify the file by its `PersonId` and `MetricDate` columns."
        }
      ]
    },
    {
      "id": "consumption_query",
      "name": "Consumption query",
      "role": "fact",
      "icon": "/assets/images/queries/consumption-query.svg",
      "product_description": "Analyse credit usage across services, users, groups, and spending policies.",
      "recommended_path": [
        "Create analysis",
        "Customized query",
        "Consumption query"
      ],
      "alternative_path": [
        "Consumption dashboard",
        "Export data"
      ],
      "cadence": "Daily by default, or weekly when grouped by week at setup. Facts plus metadata files.",
      "period_options": [
        "day",
        "week"
      ],
      "default_period": "day",
      "verification": "observed_in_real_exports",
      "files": [
        {
          "id": "consumption.m365_credits",
          "file": "PersonM365CreditsMetrics.csv",
          "grain": "One row per person, service, spending policy and period (day or week)",
          "native_key": [
            "PersonId",
            "ServiceId",
            "SpendingPolicyId",
            "MetricDate"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "ServiceId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "ServiceName",
              "type": "string",
              "role": "attribute",
              "note": ""
            },
            {
              "name": "SpendingPolicyId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": "Day or week start, as set at query setup."
            },
            {
              "name": "Session count",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "Spending policy limit",
              "type": "number",
              "role": "limit",
              "note": ""
            },
            {
              "name": "Total Copilot Credits used",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "User limit",
              "type": "number",
              "role": "limit",
              "note": ""
            },
            {
              "name": "PeopleHistoricalId",
              "type": "string",
              "role": "foreign_key",
              "note": "Links to `PeopleMetaData.csv`."
            }
          ],
          "column_groups": []
        },
        {
          "id": "consumption.github_credits",
          "file": "PersonGitHubCreditsMetrics.csv",
          "grain": "One row per person and period with billable GitHub usage (sparse)",
          "native_key": [
            "PersonId",
            "MetricDate"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": "Day or week start, as set at query setup."
            },
            {
              "name": "Total GitHub AI Credits used",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "PeopleHistoricalId",
              "type": "string",
              "role": "foreign_key",
              "note": "Links to `PeopleMetaData.csv`."
            }
          ],
          "column_groups": []
        },
        {
          "id": "consumption.people_metadata",
          "file": "PeopleMetaData.csv",
          "grain": "One row per historical person record",
          "native_key": [
            "PeopleHistoricalId"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PeopleHistoricalId",
              "type": "string",
              "role": "key",
              "note": "No `PersonId` in this file."
            },
            {
              "name": "IsCopilotLicensed",
              "type": "boolean-like text",
              "role": "attribute",
              "note": "Historical Microsoft 365 Copilot licence evidence only. Not evidence of GitHub Copilot provisioning."
            }
          ],
          "column_groups": [],
          "note": "Carries no HR attributes. Take organisational attributes from the Person Query."
        },
        {
          "id": "consumption.policy_metadata",
          "file": "M365SpendingPolicyMetaData.csv",
          "grain": "One row per spending policy",
          "native_key": [
            "SpendingPolicyId"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "SpendingPolicyId",
              "type": "string",
              "role": "key",
              "note": ""
            }
          ],
          "column_groups": [
            {
              "name": "Policy attributes",
              "headers": "inspect_export",
              "note": "Descriptive policy fields. Inspect the headers in each export."
            }
          ]
        }
      ]
    },
    {
      "id": "github_query",
      "name": "GitHub query",
      "role": "fact",
      "icon": "/assets/images/queries/github-query.svg",
      "product_description": "Analyse GitHub Copilot usage to understand developer activity and coding trends.",
      "recommended_path": [
        "Create analysis",
        "Customized query",
        "GitHub query"
      ],
      "cadence": "Daily by default, or weekly when grouped by week at setup. Activity facts plus dimensional breakdowns.",
      "period_options": [
        "day",
        "week"
      ],
      "default_period": "day",
      "verification": "reference_schema_not_yet_verified_on_real_exports",
      "files": [
        {
          "id": "github.activity",
          "file": "PersonGitHubActivityMetrics.csv",
          "grain": "One row per person and period",
          "native_key": [
            "PersonId",
            "MetricDate"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": "Day or week start, as set at query setup."
            },
            {
              "name": "Agent adoption",
              "type": "number",
              "role": "indicator",
              "note": "Adoption indicator or percentage. Excludes code review."
            },
            {
              "name": "Code completions accepted",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "Code completions suggested",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "User-initiated chat requests",
              "type": "number",
              "role": "measure",
              "note": ""
            }
          ],
          "column_groups": []
        },
        {
          "id": "github.by_feature",
          "file": "GitHubActivityBreakdownByFeatureMetrics.csv",
          "grain": "One row per person, period and feature",
          "native_key": [
            "PersonId",
            "MetricDate",
            "Feature"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": ""
            },
            {
              "name": "Feature",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Feature Usage Count",
              "type": "number",
              "role": "measure",
              "note": ""
            }
          ],
          "column_groups": []
        },
        {
          "id": "github.by_language_feature",
          "file": "GitHubActivityBreakdownByLanguageFeatureMetrics.csv",
          "grain": "One row per person, period, language and feature",
          "native_key": [
            "PersonId",
            "MetricDate",
            "Language",
            "Feature"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": ""
            },
            {
              "name": "Language",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Feature",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Usage count by language and feature",
              "type": "number",
              "role": "measure",
              "note": ""
            }
          ],
          "column_groups": []
        },
        {
          "id": "github.by_language_model",
          "file": "GitHubActivityBreakdownByLanguageModelMetrics.csv",
          "grain": "One row per person, period, language and model",
          "native_key": [
            "PersonId",
            "MetricDate",
            "Language",
            "Model"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": ""
            },
            {
              "name": "Language",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Model",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Usage count by language and model",
              "type": "number",
              "role": "measure",
              "note": ""
            }
          ],
          "column_groups": []
        },
        {
          "id": "github.by_model_feature",
          "file": "GitHubActivityBreakdownByModelFeatureMetrics.csv",
          "grain": "One row per person, period, model and feature",
          "native_key": [
            "PersonId",
            "MetricDate",
            "Model",
            "Feature"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": ""
            },
            {
              "name": "Model",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Feature",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "Usage count by model and feature",
              "type": "number",
              "role": "measure",
              "note": ""
            }
          ],
          "column_groups": []
        }
      ]
    },
    {
      "id": "agent_query",
      "name": "Agent query",
      "role": "fact",
      "icon": "/assets/images/queries/agent-query.svg",
      "product_description": "Analyse agent adoption trends across the company",
      "recommended_path": [
        "Create analysis",
        "Customized query",
        "Agent query"
      ],
      "alternative_path": [
        "Agent Dashboard",
        "Export data"
      ],
      "cadence": "Weekly facts with an optional metadata file.",
      "period_options": [
        "week"
      ],
      "default_period": "week",
      "verification": "observed_in_real_exports",
      "files": [
        {
          "id": "agent.activity",
          "file": "PersonAgentActivityMetrics.csv",
          "grain": "One row per person, agent and week",
          "native_key": [
            "PersonId",
            "AgentId",
            "MetricDate"
          ],
          "required_for_layout_match": true,
          "required_for_core_panel": true,
          "columns": [
            {
              "name": "PersonId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "AgentId",
              "type": "string",
              "role": "key",
              "note": ""
            },
            {
              "name": "MetricDate",
              "type": "date",
              "role": "key",
              "note": "Weekly. Sunday-start in observed exports."
            },
            {
              "name": "Conversational agent sessions",
              "type": "number",
              "role": "measure",
              "note": ""
            },
            {
              "name": "Returning Agent 365 user (most recent and prior 7 days)",
              "type": "number",
              "role": "indicator",
              "note": "0 or 1 over a 7-day window."
            },
            {
              "name": "Returning Agent 365 user (most recent and prior 28 days)",
              "type": "number",
              "role": "indicator",
              "note": "0 or 1 over a 28-day window."
            }
          ],
          "column_groups": []
        },
        {
          "id": "agent.metadata",
          "file": "AgentMetadata.csv",
          "grain": "One row per agent",
          "native_key": [
            "AgentId"
          ],
          "required_for_layout_match": false,
          "required_for_core_panel": false,
          "columns": [
            {
              "name": "AgentId",
              "type": "string",
              "role": "key",
              "note": ""
            }
          ],
          "column_groups": [
            {
              "name": "Agent attributes",
              "headers": "inspect_export",
              "note": "Descriptive agent fields. Inspect the headers in each export. Never use them to identify people."
            }
          ],
          "note": "Present in some exports and absent in others."
        }
      ]
    }
  ],
  "derived_datasets": [
    {
      "id": "summary.m365_credits_person_week",
      "label": "Microsoft 365 credits summary",
      "from_file_ids": [
        "consumption.m365_credits"
      ],
      "grain": [
        "PersonId",
        "WeekStart"
      ],
      "build_steps": [
        5,
        6
      ],
      "built_by": "Sum credits across services and spending policies after G05, and across days if the export is daily. Include sessions summed across policies only when G05b passes."
    },
    {
      "id": "summary.github_credits_person_week",
      "label": "GitHub credits summary",
      "from_file_ids": [
        "consumption.github_credits"
      ],
      "grain": [
        "PersonId",
        "WeekStart"
      ],
      "build_steps": [
        6
      ],
      "built_by": "Sum credits across days if the export is daily. A weekly export is already one row per person-week."
    },
    {
      "id": "summary.github_activity_person_week",
      "label": "GitHub activity summary",
      "from_file_ids": [
        "github.activity"
      ],
      "grain": [
        "PersonId",
        "WeekStart"
      ],
      "build_steps": [
        6
      ],
      "built_by": "Apply the measure rules, aggregating across days only if the export is daily."
    },
    {
      "id": "summary.agent_person_week",
      "label": "Agent activity summary",
      "from_file_ids": [
        "agent.activity"
      ],
      "grain": [
        "PersonId",
        "WeekStart"
      ],
      "build_steps": [
        6
      ],
      "built_by": "Sum sessions across agents. Derive any person-level returning flag explicitly."
    },
    {
      "id": "panel.person_week",
      "label": "Combined person-week panel",
      "from_file_ids": [
        "person_query.person"
      ],
      "grain": [
        "PersonId",
        "WeekStart"
      ],
      "build_steps": [
        7
      ],
      "built_by": "Start with the Person Query and left join each summary. Keep every Person Query row."
    }
  ],
  "person_query_metric_families": [
    {
      "family": "Collaboration",
      "kind": "weekly_total",
      "headers": [
        "Collaboration hours",
        "External collaboration hours",
        "Meeting hours",
        "Email hours",
        "Chat hours",
        "After hours collaboration hours"
      ]
    },
    {
      "family": "Focus",
      "kind": "weekly_total",
      "headers": [
        "Available to focus hours",
        "Uninterrupted hours"
      ]
    },
    {
      "family": "Network",
      "kind": "stock",
      "headers": [
        "Internal network size",
        "External network size",
        "Strong ties",
        "Diverse ties",
        "Network outside organization"
      ]
    },
    {
      "family": "Copilot eligibility",
      "kind": "day_count",
      "headers": [
        "Total Copilot enabled days"
      ]
    },
    {
      "family": "Copilot activity, totals",
      "kind": "weekly_total_or_day_count",
      "headers": [
        "Total Copilot actions taken",
        "Total Copilot active days"
      ]
    },
    {
      "family": "Copilot activity, by surface",
      "kind": "weekly_total_or_day_count",
      "headers": [
        "Copilot actions taken in Copilot Chat",
        "Copilot actions taken in Microsoft 365 Copilot app",
        "Days of active Copilot Chat usage",
        "Days of active Copilot Chat (legacy web) usage",
        "Days of active Copilot usage in Excel",
        "Days of active Copilot usage in Loop",
        "Days of active Copilot usage in OneNote",
        "Days of active Copilot usage in Outlook",
        "Days of active Copilot usage in Powerpoint",
        "Days of active Copilot usage in Teams",
        "Days of active Copilot usage in Word"
      ]
    },
    {
      "family": "Cowork",
      "kind": "weekly_total_or_day_count",
      "headers": [
        "Cowork tasks submitted",
        "User-initiated Cowork tasks submitted",
        "Days of active Cowork usage"
      ]
    },
    {
      "family": "Unlicensed Copilot Chat",
      "kind": "weekly_total_or_day_count",
      "note": "A separate metric family for Copilot Chat use without a Microsoft 365 Copilot licence. Inspect the export for its headers and keep it separate from licensed Copilot metrics.",
      "headers": []
    }
  ],
  "relationships": [
    {
      "id": "J1",
      "from_id": "consumption.m365_credits",
      "to_id": "consumption.people_metadata",
      "on": [
        "PeopleHistoricalId"
      ],
      "cardinality": "many_to_one",
      "purpose": "Microsoft 365 Copilot licence evidence, before aggregation"
    },
    {
      "id": "J2",
      "from_id": "consumption.github_credits",
      "to_id": "consumption.people_metadata",
      "on": [
        "PeopleHistoricalId"
      ],
      "cardinality": "many_to_one",
      "purpose": "Check that each historical person record exists. `IsCopilotLicensed` describes Microsoft 365 only and cannot confirm GitHub eligibility."
    },
    {
      "id": "J3",
      "from_id": "consumption.m365_credits",
      "to_id": "consumption.policy_metadata",
      "on": [
        "SpendingPolicyId"
      ],
      "cardinality": "many_to_one",
      "purpose": "Check that each spending policy exists in the metadata."
    },
    {
      "id": "J4",
      "from_id": "agent.activity",
      "to_id": "agent.metadata",
      "on": [
        "AgentId"
      ],
      "cardinality": "many_to_one",
      "purpose": "Optional enrichment, only when the metadata file is supplied"
    },
    {
      "id": "J5",
      "from_id": "summary.m365_credits_person_week",
      "to_id": "panel.person_week",
      "on": [
        "PersonId",
        "WeekStart"
      ],
      "cardinality": "one_to_one",
      "purpose": "Left join onto the spine"
    },
    {
      "id": "J6",
      "from_id": "summary.github_credits_person_week",
      "to_id": "panel.person_week",
      "on": [
        "PersonId",
        "WeekStart"
      ],
      "cardinality": "one_to_one",
      "purpose": "Left join onto the spine"
    },
    {
      "id": "J7",
      "from_id": "summary.github_activity_person_week",
      "to_id": "panel.person_week",
      "on": [
        "PersonId",
        "WeekStart"
      ],
      "cardinality": "one_to_one",
      "purpose": "Left join onto the spine"
    },
    {
      "id": "J8",
      "from_id": "summary.agent_person_week",
      "to_id": "panel.person_week",
      "on": [
        "PersonId",
        "WeekStart"
      ],
      "cardinality": "one_to_one",
      "purpose": "Left join onto the spine"
    },
    {
      "id": "J9",
      "from_ids": [
        "github.by_feature",
        "github.by_language_feature",
        "github.by_language_model",
        "github.by_model_feature"
      ],
      "to_id": "panel.person_week",
      "on": [
        "PersonId",
        "WeekStart"
      ],
      "cardinality": "linked_detail",
      "purpose": "Never joined wide. Keep as separate detail tables keyed to the panel"
    }
  ],
  "measures": [
    {
      "id": "M01",
      "file_id": "consumption.m365_credits",
      "columns": [
        "Total Copilot Credits used"
      ],
      "kind": "additive",
      "person_week_rule": "Sum across services and spending policies after G05 passes, and across days when the export is daily."
    },
    {
      "id": "M02",
      "file_id": "consumption.m365_credits",
      "columns": [
        "Session count"
      ],
      "kind": "conditionally_additive",
      "person_week_rule": "Sum across services, and across days when the export is daily. Sum across `SpendingPolicyId` rows only when G05b passes. Sessions are not people."
    },
    {
      "id": "M03",
      "file_id": "consumption.m365_credits",
      "columns": [
        "Spending policy limit",
        "User limit"
      ],
      "kind": "limit",
      "person_week_rule": "Never sum. Keep per policy as linked detail."
    },
    {
      "id": "M04",
      "file_id": "consumption.github_credits",
      "columns": [
        "Total GitHub AI Credits used"
      ],
      "kind": "additive",
      "person_week_rule": "Sum across days when the export is daily. Keep separate from Microsoft 365 credits."
    },
    {
      "id": "M05",
      "file_id": "github.activity",
      "columns": [
        "Code completions accepted",
        "Code completions suggested",
        "User-initiated chat requests"
      ],
      "kind": "additive",
      "person_week_rule": "Sum across days when the export is daily. Acceptance rate is summed accepted divided by summed suggested, with a stated zero-denominator rule."
    },
    {
      "id": "M06",
      "file_id": "github.activity",
      "columns": [
        "Agent adoption"
      ],
      "kind": "indicator",
      "person_week_rule": "Do not sum. For a daily export, state a derived weekly rule, for example any positive day in the week. For a weekly export, use the exported value."
    },
    {
      "id": "M07",
      "file_id": "github.by_feature",
      "columns": [
        "Feature Usage Count"
      ],
      "kind": "additive_within_file",
      "person_week_rule": "Sum days within this file when daily, retaining Feature for each person-week. Keep as linked detail. Never add to another breakdown."
    },
    {
      "id": "M08",
      "file_id": "github.by_language_feature",
      "columns": [
        "Usage count by language and feature"
      ],
      "kind": "additive_within_file",
      "person_week_rule": "Sum days within this file when daily, retaining Language and Feature for each person-week. Keep as linked detail. Never add to another breakdown."
    },
    {
      "id": "M09",
      "file_id": "github.by_language_model",
      "columns": [
        "Usage count by language and model"
      ],
      "kind": "additive_within_file",
      "person_week_rule": "Sum days within this file when daily, retaining Language and Model for each person-week. Keep as linked detail. Never add to another breakdown."
    },
    {
      "id": "M10",
      "file_id": "github.by_model_feature",
      "columns": [
        "Usage count by model and feature"
      ],
      "kind": "additive_within_file",
      "person_week_rule": "Sum days within this file when daily, retaining Model and Feature for each person-week. Keep as linked detail. Never add to another breakdown."
    },
    {
      "id": "M11",
      "file_id": "agent.activity",
      "columns": [
        "Conversational agent sessions"
      ],
      "kind": "additive",
      "person_week_rule": "Sum across agents to person-week."
    },
    {
      "id": "M12",
      "file_id": "agent.activity",
      "columns": [
        "Returning Agent 365 user (most recent and prior 7 days)"
      ],
      "kind": "window_indicator",
      "person_week_rule": "Do not sum across agents or weeks. A person-level flag, such as returning for any agent, is a stated derived definition."
    },
    {
      "id": "M13",
      "file_id": "agent.activity",
      "columns": [
        "Returning Agent 365 user (most recent and prior 28 days)"
      ],
      "kind": "window_indicator",
      "person_week_rule": "Do not sum across agents or weeks. A person-level flag, such as returning for any agent, is a stated derived definition."
    },
    {
      "id": "M14",
      "file_id": "person_query.person",
      "columns": [
        "Internal network size",
        "External network size"
      ],
      "kind": "stock",
      "person_week_rule": "Detect and record the measurement window. Values can repeat within a month. Average or deduplicate snapshots according to that window, never sum."
    },
    {
      "id": "M15",
      "file_id": "person_query.person",
      "columns": [
        "Days of active Copilot Chat usage",
        "Days of active Copilot Chat (legacy web) usage",
        "Days of active Copilot usage in Excel",
        "Days of active Copilot usage in Loop",
        "Days of active Copilot usage in OneNote",
        "Days of active Copilot usage in Outlook",
        "Days of active Copilot usage in Powerpoint",
        "Days of active Copilot usage in Teams",
        "Days of active Copilot usage in Word"
      ],
      "kind": "day_count",
      "person_week_rule": "Never add across surfaces to make total active days, because one day can include several surfaces. Use `Total Copilot active days`."
    }
  ],
  "missingness_states": [
    {
      "id": "observed_positive",
      "meaning": "A row exists with a positive value"
    },
    {
      "id": "observed_zero",
      "meaning": "A row exists with a recorded zero"
    },
    {
      "id": "absent_inactive_zero",
      "meaning": "No activity row within a supplied query's configured population and date window. Assume complete Viva Insights data and record zero activity"
    },
    {
      "id": "absent_unknown",
      "meaning": "The query is unavailable, the person or period is outside its scope, a known export problem applies, or an absent non-activity measure has no defined zero interpretation. Keep as unknown"
    },
    {
      "id": "observed_null",
      "meaning": "A row exists but the measure is null. Do not apply the omitted-inactive-row default to this cell"
    },
    {
      "id": "ineligible",
      "meaning": "Not eligible for that product in the week"
    },
    {
      "id": "outside_cohort",
      "meaning": "Not selected by the analysis cohort rule"
    }
  ],
  "gates": [
    {
      "id": "G01",
      "level": "MUST",
      "rule": "Require the Person Query plus at least one of Consumption, GitHub or Agent. Record which queries are supplied and identify each export's layout. Within each selected query, files with `required_for_layout_match` set to true must all be present. If files or key columns differ from this contract, stop and record the difference."
    },
    {
      "id": "G02",
      "level": "MUST",
      "rule": "Read identifiers as text. Do not change their case, trim them silently or fuzzy-match people."
    },
    {
      "id": "G03",
      "level": "MUST",
      "rule": "Confirm weekly grouping from the Person Query configuration or equivalent export metadata, then validate one row per `PersonId` and `MetricDate` and detect the weekday of `MetricDate`. Stop if grouping is unknown, daily or monthly. Do not assume Monday or Sunday."
    },
    {
      "id": "G03b",
      "level": "MUST",
      "rule": "Record each fact export's configured date grouping from query setup or equivalent export metadata, then validate `MetricDate` against it. Sparse dates alone cannot establish cadence. Recommend weekly grouping for all queries. Daily and weekly exports are supported. A weekly export must start on the same weekday as the Person Query. Stop for unknown grouping, any other period such as monthly, or a different weekly start."
    },
    {
      "id": "G04",
      "level": "MUST",
      "rule": "Assert uniqueness of each file's native key. Report duplicate counts and stop. Never keep an arbitrary first row."
    },
    {
      "id": "G05",
      "level": "MUST",
      "rule": "Before summing Microsoft 365 credits across `SpendingPolicyId`, count person-service-period keys with more than one positive-credit row. If any exist, obtain a documented confirmation that the rows record disjoint consumption."
    },
    {
      "id": "G05b",
      "level": "MUST",
      "rule": "Sum `Session count` across `SpendingPolicyId` rows only when the documented confirmation explicitly covers sessions. Otherwise keep sessions per policy."
    },
    {
      "id": "G06",
      "level": "MUST",
      "rule": "Join metadata many-to-one on unique keys. Report unmatched key counts without identifiers. Check that each `PeopleHistoricalId` maps to one `PersonId`."
    },
    {
      "id": "G07",
      "level": "MUST",
      "rule": "Bring only panel-bound summaries to one row per `PersonId` and `WeekStart` before joining. Apply the measure rules: sum days only for configured daily exports, credits across policies only after G05, and sessions across policies only after G05b. Otherwise retain sessions per policy outside the panel. Keep GitHub breakdown dimensions as linked detail under G11. Never expand weekly rows into invented daily rows."
    },
    {
      "id": "G08",
      "level": "MUST",
      "rule": "Left join each person-week summary onto the Person Query. Assert one-to-one, preserve the Person Query row count, and reconcile additive totals before and after."
    },
    {
      "id": "G09",
      "level": "MUST",
      "rule": "Assume supplied Viva Insights queries are complete within their configured population, date window and product filters. Omitted activity rows represent inactivity by default, so record zero activity within that scope without requiring separate completeness evidence. Do not zero-fill unavailable queries, out-of-scope periods or people, null cells in existing rows, rolling indicators, limits, or known export problems. Record partial boundary weeks separately. Eligibility is a separate attribute: Microsoft 365 licence evidence never confirms GitHub eligibility."
    },
    {
      "id": "G10",
      "level": "MUST",
      "rule": "Keep Microsoft 365 and GitHub credits in separate columns. Never derive tokens or currency from credits."
    },
    {
      "id": "G11",
      "level": "MUST",
      "rule": "Do not join GitHub breakdown files wide. Keep them as linked detail tables."
    },
    {
      "id": "G12",
      "level": "SHOULD",
      "rule": "Treat integration and cohort selection as separate, declared steps. An unavailable query does not establish non-use."
    },
    {
      "id": "G13",
      "level": "SHOULD",
      "rule": "Record each variable's measurement window. Network sizes can repeat within a month and are averaged rather than summed."
    },
    {
      "id": "G14",
      "level": "MUST",
      "rule": "Coverage, licence and additivity confirmations apply only to the export they were given for."
    },
    {
      "id": "G15",
      "level": "MUST",
      "rule": "Check `PersonId` overlap between sources before joining. Investigate low overlap against activity filters, omitted inactive people and configured populations before concluding that pseudonymisation differs. Do not repair identifiers by matching on attributes."
    },
    {
      "id": "G16",
      "level": "MUST",
      "rule": "Publish a group only when it contains at least 10 distinct people, counted with count_distinct(`PersonId`), in every filter and time slice. Withhold an entire related breakdown if remaining cells or totals would reveal a suppressed cell by subtraction. Never output identifiers."
    },
    {
      "id": "G17",
      "level": "MUST",
      "rule": "Describe joined results as associations. Credits, sessions and actions do not measure value, quality or productivity."
    }
  ]
}
Last updated: Oct 28, 2026 Edit this page on GitHub