# Ticket Facts And Base Dashboard

> Funnel and current non-L1 exclusion semantics are revised by
> `2026-07-17-l1-funnel-and-non-l1-exclusion.md`.

## Current Design

- The Jira poller enqueues `auto_oncall_jobs`; the worker later fetches the
  current Jira issue and converts it to `SupportCase`.
- `SupportCase` contains normalized ticket metadata, but that metadata is only
  written to a run workspace. MySQL persists jobs, gates, runs, feedback and
  report artifacts, not a canonical ticket snapshot.
- Daily and weekly reports query job/run/gate/feedback state. There is no
  ticket-granularity analytical source and no Base projection.

## Risks

- Building Base from a report artifact would make one presentation output the
  source for another and would prevent independent ticket-level analysis.
- Re-fetching Jira while rendering reports or dashboards would rewrite history
  when mutable Jira fields change.
- A single latest-ticket row without immutable snapshots would lose the exact
  metadata observed by the worker.
- Base writes can time out after succeeding, so retries must reconcile by
  Ticket Key instead of blindly creating another row.
- Ticket descriptions, comments, raw payloads and infrastructure identifiers
  must not be copied to a broadly visible analytical Base.

## Target Contract

```text
Jira ticket metadata
  -> immutable normalized ticket snapshots in MySQL
  -> latest ticket fact joined with job/gate/run/feedback state
  -> independent daily report, weekly report and Base projections
```

- One Base record represents one Jira Ticket Key.
- Jira `created` and the first initial-job time are both preserved as separate
  time dimensions.
- The Base row contains normalized metadata and final automation outcomes, not
  report-generated prose.
- Base synchronization is idempotent by Ticket Key and independently retryable.
- Existing daily/weekly semantics stay unchanged in this change; they continue
  to read canonical DB state and can later be migrated to the ticket fact query
  without changing visible output.

### Dashboard Contract Revision

- Ticket time-series dimensions use the Asia/Shanghai calendar week derived
  from Jira `created`; day-level charts are not part of the dashboard contract.
- The top-level ticket total and in-scope total may show the funnel boundary,
  but every lower-level chart filters `In Auto-oncall Scope = 1`.
- AI effectiveness is a ticket-level analytical outcome, not report prose:
  generated AI-vs-human verdicts are persisted in a dedicated DB table and
  joined to the latest ticket fact alongside human feedback.
- Human feedback has precedence over generated comparison verdicts and uses
  the source-qualified `ao: accept/revise/wrong/human` labels. Without feedback,
  generated verdicts use `llm_review: 比人工好/与人工相当/不同但有效/比人工差`
  so dashboard IDs never mix manual feedback with later LLM review; all other
  states are `待评价`.
- AI acceptable value counts `accept/revise` as 1 and `wrong/human` as 0.
  Without feedback, `ai_better/equivalent/different_but_valid` is 1,
  `human_better` is 0, and unevaluated rows remain empty.
- Base stores only the compact verdict/category and evaluation timestamp. It
  does not store AI answers, human answers, comparison narratives, strengths,
  gaps, or report prose.

### Skipped Job Contract

- A latest job with `status = skipped` did not enter the auto-oncall execution
  set. Its `In Auto-oncall Scope` value is therefore `0`, and it must not appear
  in automatic handling rate, environment, scenario, or Job Status charts.
- A business-gate skip still preserves its canonical `Auto-oncall Category`
  instead of being flattened to `不在 auto-oncall 范围`.
- `不可自动处理原因` is the explicit funnel-fallout exception: it includes
  business-gate skipped tickets with an initial job, while excluding tickets
  rejected by the L1 boundary, OP non-Wetech boundary, or no-job boundary.
- The Job Status chart also filters `Job Status != skipped` as a defensive
  dashboard assertion in addition to the persisted scope value.

## Invariants

- Persist the ticket snapshot immediately after Jira fetch and before the L1
  or Auto Coverage business gates.
- Worker gates remain in their current order.
- Read APIs and report preview paths never write Base records.
- Base failures never fail a worker job, Jira write or Feishu report.
- Base never stores description, comments, attachment bodies, AI/human full
  answers, raw Jira payloads, cluster IDs, tenant IDs, org IDs or account IDs.
- Base never reads a daily or weekly artifact. Report generation may persist a
  structured comparison verdict to the shared evaluation table, after which
  reports and Base remain peer consumers.
- Re-running the one-week backfill updates existing Ticket Keys and creates no
  duplicates.
- Prod automation and prod Base synchronization remain disabled.

## Implementation Steps

1. Add immutable ticket snapshot storage and a latest ticket analytical query.
2. Save normalized metadata before business gates for every worker-fetched
   ticket, including tickets later skipped by L1 or Auto Coverage.
3. Add a Feishu Base client that searches by Ticket Key, then creates or
   updates one record with normalized ticket and automation fields.
4. Add an explicit CLI for bounded backfill/sync and runtime configuration for
   periodic staging synchronization.
5. Create a new bot-owned Base, grant the current user access, create the
   ticket table, fields and dashboard, then configure staging with its token
   and table ID through the app-delivery source template.
6. Deploy staging, backfill the previous seven rolling days of initial jobs,
   and validate Base records against DB/Jira samples.
7. Persist structured AI-vs-human verdicts independently, add weekly and AI
   effectiveness fields to the ticket projection, and migrate lower dashboard
   charts to the in-scope-only weekly contract.

## Verification Checklist

- Snapshot write is idempotent for the same Jira update and preserves a new
  row when Jira metadata changes.
- L1-skipped, Auto Coverage-skipped, succeeded and failed jobs all remain
  representable as ticket facts.
- Empty optional metadata, repeat sync and Base create/update/error paths are
  covered locally.
- `npm run typecheck` and `npm test` pass before deployment.
- App-delivery staging source contains Base config; prod stays disabled.
- Live pod environment, migration, logs and DB snapshot counts are verified.
- Backfill range, distinct initial Ticket Keys and Base record count reconcile;
  sampled rows match Jira metadata and DB gate/run/feedback outcomes.
- Weekly labels use the Asia/Shanghai week containing Jira `created`.
- Every lower dashboard block excludes out-of-scope records.
- AI-effectiveness counts reconcile with persisted feedback and comparison
  verdict rows; no report narrative is copied to Base.
- Skipped jobs have scope value `0`, do not appear in the Job Status chart,
  and reconcile into the non-automatic reason chart by canonical category.

## Non-Goals

- No report-to-Base dependency.
- No customer-visible Jira writes, transitions or production operations.
- No Base-to-DB reverse synchronization.
- No full historical backfill beyond the requested rolling seven-day window.
- No change to the current daily or weekly user-facing format in this rollout.
- No attempt to compare tickets without a stable AI/human answer pair.
