---
title: Support matchup_opponent_type columns on team-identity data views
type: task
status: completed
description: >-
  Team row_grain data views with matchup_opponent_type columns (current/next_week_opponent_total)
  fail to execute: the opponent CTEs and source-attach rules exist only for player cell-identities,
  so a team-identity query references next_week_opponents without building or joining it.
base_uri: user:task/league/data-views/support-matchup-opponent-type-on-team-identity-views.md
created_at: '2026-06-25T13:01:00.569Z'
entity_id: d0941c6f-f549-4fa1-85f8-132438767d07
finished_at: '2026-09-22T18:20:19.267Z'
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: Medium
public_read: true
relations:
  - follows [[█████████████████████-████████████-██████-███████████████]]
  - follows [[█████████████████████-█████████████-██-████████-████████]]
  - relates [[user:task/league/data-views/finish-line-source-bridge-and-phase-c.md]]
started_at: '2026-09-22T17:24:21.893Z'
tags:
  - user:tag/league-xo-football.md
updated_at: '2026-09-22T18:23:07.452Z'
visibility_analyzed_at: '2026-11-05T23:49:02.231Z'
---

# Overview

A no-row-axes **team** data view (`row_grain: ['team']`) that includes columns with `matchup_opponent_type: ['next_week_opponent_total']` (or `current_week_opponent_total`) fails at execute time with:

```
ERROR: missing FROM-clause entry for table "next_week_opponents"
```

The matchup-opponent feature was built exclusively for **player-identity** views. Team-identity views have no team-keyed opponent CTE built, no join, and no source-attach rule — yet a matchup predicate still gets emitted referencing the (never-created) `next_week_opponents` CTE.

This surfaced while fixing the orthogonal `team-to-team-year` empty-`year_range` bug (see [[█████████████████████-████████████-████████-██-███-████████]] friction observation). The real production view is `/u/0004c034` ("Next Week Matchup Preview v2"), a team-grain view whose entire premise is showing each team's next-week opponent's defensive stats — it cannot render until team-identity matchup support exists.

# Background — exact gap

Three player-only touchpoints in the matchup path:

1. **CTE build + join** — `libs-server/get-data-view-results.mjs` (~line 1680): the loop over `data_view_options.matchup_opponent_types` is gated by `is_matchup_player_identity_active()` (`query_context.identity_id.startsWith('player')`) and `continue`s for team identity, so `add_week_opponent_cte_tables` (`libs-server/data-views/week-opponent-cte-tables.mjs`) is never called and the `current_week_opponents` / `next_week_opponents` CTEs are never created. The player join is `players_query.join('next_week_opponents', 'player.current_nfl_team', '=', 'next_week_opponents.nfl_team')` — a player-only key.

2. **Source-attach rules** — `libs-server/data-views/source-attach/rules/matchup-opponent.mjs` registers `(cell_identity, team_year, matchup_opponent_*)` rules only for `['player', 'player_year', 'player_year_week']`. There is no `(team, team_year, matchup_opponent_*)` rule.

3. **Opponent mapping semantics** — for a player, the opponent is derived from `player.current_nfl_team`. For a team-grain row the mapping is direct (the row's `team_code` → its scheduled opponent), so a team-keyed opponent CTE (`team_code → opponent`) is needed, joined onto the team spine (`team_years.team_code` / `team.team_code`).

# Plan

- [ ] Add a team-keyed variant of the opponent CTE (or generalize `week-opponent-cte-tables.mjs`) keyed by `team_code` → `opponent` for current-week and next-week.
- [ ] In `get-data-view-results.mjs`, build + join the team-keyed opponent CTE when the identity is team and `matchup_opponent_types` is non-empty (parallel to the existing player branch), joining on the team spine rather than `player.current_nfl_team`.
- [ ] Register `(team*, team_year, matchup_opponent_current_week|next_week)` source-attach rules in `matchup-opponent.mjs` emitting the team-key predicate against the team-keyed opponent CTE.
- [ ] Verify `/u/0004c034` builds and executes against production data (read-only), returning each team's next-week-opponent defensive columns populated.
- [ ] Add a regression spec asserting the team-identity matchup join shape (mirror `data-views.team-year-bridge-no-row-axes.spec.mjs`).

# Out of scope

- The `team->team_year` empty-`year_range` bridge throw — already fixed (bridge now falls back to `params.year` / `source.year_default`, mirroring `player_year->team_year`).
