# Public Activity Intelligence v1 contract

**Status:** release contract before implementation  
**Decision date:** 12 August 2026  
**Public job:** given one completed cycling activity, establish what kind of session it appears to be, explain what happened, and state what cannot be concluded.

**Public discovery state (15 August 2026):** evaluation-only and deliberately absent from the public MCP `tools/list`. SEMIPRO Cycling Intelligence may describe this planned capability in its manifest, but agents cannot call it until its resolver and released family modules satisfy the gates in this contract.

This is the transport-independent contract for the public web tool, direct API, MCP tool and marketplace adapters. It is not the Connected Athlete Intelligence contract and it never reaches SEMIPRO's private coaching kernel.

## Product boundary

Public Activity Intelligence operates at four distinct layers:

| Layer | Public v1 treatment |
|---|---|
| Tier 0: telemetry and deterministic structure | Required foundation. Power, heart rate, cadence, speed, distance, altitude, laps and derived effort structure where available. |
| Tier 1: intent and portable athlete context | Optional, explicitly sourced input. Never inferred from telemetry or silently invented. |
| Released diagnostic judgement | Allowed through versioned specialist modules and bounded generic analysis. |
| Private SEMIPRO kernel and athlete world | Never available through this contract. No weekly notes, current coaching direction, private judgement library, coach memory or unpublished athlete history. |

Structure and intent are different fields. Telemetry may support `looks_like: sprint`; it cannot support `the athlete intended sprint work` unless intent was supplied by an authoritative source.

## One front door

The public capability is one general analysis job:

```text
analyse_cycling_activity
```

Endurance, sprint, VO2 and future interval-family modules sit behind a resolver. They are implementation modules, not separate products a general user must select. Advanced callers may request a family as a hypothesis, but that request cannot override evidence or release gates.

Suggested tool description:

> Analyse a completed cycling activity using deterministic ride evidence and the appropriate released SEMIPRO specialist module. Use it to identify probable session structure, explain sprint, endurance or interval execution when reliably resolved, and expose uncertainty. Do not use it to plan future training, diagnose a medical issue, analyse a route without activity data, or claim athlete development from one ride.

Tool annotations:

- `readOnlyHint: true`
- `destructiveHint: false`
- `openWorldHint: false` for an uploaded activity; `true` only for a future connector that retrieves external data

## Transport surfaces

All surfaces must return the same `semipro_activity_intelligence_v1` object.

| Surface | v1 input |
|---|---|
| Public web | Direct FIT upload plus optional context form |
| HTTP API | Multipart FIT upload or an opaque prepared `activity_ref` |
| MCP | An opaque, short-lived `activity_ref` created by the upload/preparation route |
| Marketplace | Adapter around the HTTP API; never its own analysis doctrine |

Large activity streams must not be placed directly in MCP arguments. A local file path is not a public server input. The preparation route accepts the file, validates it, and returns a short-lived opaque reference that reveals no storage path.

GPX-only input belongs to Event Demand Intelligence. Strava and other connectors can be added later as ingestion adapters over the same normalized activity contract.

## Request contract

The logical request is:

```json
{
  "activity_ref": "actref_opaque",
  "context": {
    "intent": {
      "source": "user_supplied",
      "label": "optional plain-language purpose",
      "planned_structure": null
    },
    "athlete": {
      "power_anchor": {
        "kind": "critical_power",
        "watts": 310,
        "source": "athlete_supplied",
        "effective_date": "2026-08-01",
        "confidence": "asserted"
      },
      "mass_kg": 72.5,
      "threshold_hr_bpm": null
    },
    "comparison_refs": []
  },
  "requested_family": null
}
```

Rules:

- `activity_ref` is single-purpose, short-lived and bound to the requester or payment flow.
- Every context field is optional and must carry its source.
- Caller-supplied thresholds are treated as assertions, not facts discovered by SEMIPRO.
- Critical Power uses the versioned `critical-power-anchor-v1` object. A bare number is not accepted because it loses provenance.
- FTP is not silently converted to Critical Power. A future FTP degraded mode must identify itself separately and cannot emit CP-relative claims.
- `estimated_from_history` and file-metadata anchors remain provisional. They may support a disclosed analysis but must never update a coach-owned Critical Power value.
- An activity-only request must still produce a valid bounded response.
- `requested_family` is a hypothesis. It never forces specialist routing.
- Comparisons are returned only when the comparison basis is valid and disclosed. Cross-athlete ranking is out of scope for v1.

## Preparation and payment sequence

Public v1 uses two stages so an unparsable file is not presented as a successful paid analysis:

1. `prepare` validates the FIT, normalizes channels, records coverage, and returns an expiring `activity_ref`, a capability preview and a price quote.
2. `analyse` accepts the reference and optional context. A paid machine route may return an MPP `402 Payment Required` challenge before performing the full analysis.

The direct paid route uses an MPP `charge` intent for one completed analysis. A marketplace may authenticate with a scoped provider key and settle separately. A funded MPP session may be introduced for high-volume callers without changing this contract.

Every paid analysis requires an idempotency key. A replay returns the original result without a second charge. Invalid, expired or already-consumed references receive a stable error. If payment succeeds but the analysis cannot be returned, the gateway must follow a declared compensation or refund path.

The generic one-payment/one-hour proxy is not suitable for strict per-analysis billing; the gateway must bind the payment receipt, idempotency key and analysis reference to one result.

## Data quality contract

Preparation returns channel and coverage facts before specialist analysis:

```json
{
  "input_coverage": {
    "source_type": "fit_upload",
    "duration_seconds": 5412,
    "channels": {
      "power": { "present": true, "coverage": 0.997 },
      "heart_rate": { "present": true, "coverage": 0.991 },
      "cadence": { "present": true, "coverage": 0.982 },
      "speed": { "present": true, "coverage": 0.999 },
      "position": { "present": true, "coverage": 0.999 }
    },
    "quality_flags": []
  }
}
```

Each specialist module declares required and useful channels, minimum coverage, athlete anchors, exclusions and degraded modes. Missing heart rate must not invalidate power-only sprint evidence. Missing power must prevent power-specific conclusions. Relative-intensity claims require a sourced threshold such as critical power.

The preparation preview must expose the anchor state separately from file coverage:

```json
{
  "power_anchor": {
    "contract_version": "critical-power-anchor-v1",
    "status": "available",
    "kind": "critical_power",
    "watts": 310,
    "source": "athlete_supplied",
    "authority": "caller_asserted",
    "usable_for_relative_intensity": true
  }
}
```

Allowed anchor states are `available`, `provisional`, `unavailable` and `invalid`. A missing or invalid anchor degrades CP-relative and zone interpretation; it does not prevent raw structure detection.

The public contract must distinguish:

- `measured`: present in the source activity;
- `derived`: deterministic calculation from measured data;
- `supplied`: caller-provided context;
- `inferred_structure`: classification from telemetry; and
- `interpretation`: released SEMIPRO judgement over disclosed evidence.

## Resolver contract

The resolver returns candidates before it invokes a specialist module:

```json
{
  "session_resolution": {
    "status": "resolved",
    "resolved_family": "sprint",
    "language": "looks_like",
    "basis": ["telemetry_structure"],
    "candidates": [
      { "family": "sprint", "confidence": 0.94 },
      { "family": "vo2", "confidence": 0.04 }
    ],
    "intent": { "status": "not_supplied" }
  }
}
```

Allowed statuses are `resolved`, `mixed`, `ambiguous`, `unstructured`, `unknown` and `unsupported_data`.

Candidate families may include `endurance`, `sprint`, `vo2`, `threshold`, `tempo`, `recovery`, `mixed`, `unstructured` and `unknown`. Specialist routing is allowed only when that family has a released module and the evaluated auto-route threshold is met. Unreleased families receive generic evidence, not a borrowed neighbouring module.

A numeric confidence may be returned only after calibration against labelled cases. It represents the observed reliability of comparable resolver outputs, not the model's self-belief. Before calibration, use a non-numeric evidence-strength label.

Resolution precedence is:

1. structured, explicitly supplied or connected planned workout;
2. explicitly supplied user intent;
3. telemetry-inferred structure;
4. weak metadata such as title or description, separately labelled;
5. abstention.

Intent and structure can disagree. The output reports the disagreement; intent never silently wins and structure never rewrites intent.

## Analysis modes

The resolver selects exactly one mode:

- `specialist`: a released family module is valid for this activity;
- `generic_evidence`: useful evidence exists but specialist routing is unsafe;
- `needs_context`: one bounded missing fact could unlock an interpretation;
- `unsupported`: the source cannot support a useful, safe analysis.

Mixed rides may expose deterministic segments, but v1 must not assemble multiple family judgements unless that composition has its own labelled evaluation gate.

## Response contract

```json
{
  "object": "semipro.activity_intelligence",
  "contract_version": "semipro_activity_intelligence_v1",
  "request_id": "sai_opaque",
  "analysis_version": "resolver-and-module-registry-version",
  "generated_at": "2026-08-12T00:00:00Z",
  "input_coverage": {},
  "session_resolution": {},
  "analysis_mode": "specialist",
  "module": {
    "id": "sprint_analysis",
    "version": "versioned-release-id"
  },
  "summary": {
    "primary_finding": "bounded plain-language finding",
    "session_language": "This ride looks like a sprint session."
  },
  "evidence": [],
  "interpretation": {
    "supported": [],
    "not_supported": []
  },
  "questions": [],
  "boundaries": {
    "medical_diagnosis": false,
    "training_plan_change": false,
    "connected_athlete_context_used": false
  },
  "retention": {
    "raw_activity_retained": false
  }
}
```

Evidence items carry `name`, `value`, `unit`, `provenance`, `method_version` and optional `segment_ref`. Interpretive statements cite the evidence item IDs they rely on. The answer must disclose the module and analysis version, important missing channels, uncertainty, and any supplied athlete anchor.

## Public safety boundary

Without connected history and coaching authority, v1 must not:

- diagnose illness, injury or a medical cause;
- prescribe or modify a training plan;
- claim that one ride proves adaptation, fatigue or loss of fitness;
- judge whether the session was correct for the athlete's development;
- imply that inferred structure was known intent;
- expose internal ingestion mechanisms, private brain sources or proprietary reasoning traces; or
- present a generic analysis as specialist family judgement.

When useful, it may ask one or two bounded questions such as the planned purpose, critical power used at the time, or whether intervals were manually lapped.

## Retention and observability

The release default is ephemeral processing:

- raw FIT bytes, record streams, filenames and exact start/end coordinates are removed after the reference expires or the analysis completes;
- public responses do not contain raw records;
- operational logs may retain request ID, source type, channel coverage, quality flags, resolver candidates, selected module/version, outcome class, latency, price and distribution source;
- labelled evaluation retention requires explicit opt-in and a separate de-identified dataset policy; and
- no public call becomes part of an athlete world or the Judgement Library by default.

Directory and campaign attribution may be stored as non-sensitive `distribution_source` and `campaign_id` fields. These must not alter the analysis.

## Evaluation and release gates

The structure-only resolver is not released because it sounds plausible. It is released only after a blinded evaluation against rides whose true session is known from authoritative plan data or coach labels.

The evaluation set must:

- include released families, confusing neighbours, mixed/unstructured rides and real data-quality failures;
- hide title, description and intent during the telemetry-only pass;
- split development, calibration and holdout cases by athlete and time to reduce leakage;
- report precision, recall, coverage, abstention and calibration by family;
- report false specialist routes separately, because they are the primary safety failure; and
- version the dataset, resolver, thresholds and module registry.

Initial auto-route gate:

- at least **90% holdout precision among automatically routed activities** for each released family;
- zero known routes into an unreleased or data-incompatible specialist module;
- explicit coverage and abstention rate alongside precision;
- deterministic evidence tests passing for the selected module; and
- direct, indirect and negative tool-selection prompts passing before directory submission.

If a family misses the gate, the public product still launches with generic evidence and abstention for that family. The gate is not lowered to improve demo coverage.

### Executable baseline

The blinded benchmark is implemented by `scripts/benchmark-structure-resolver.js` and runs with:

```bash
npm run benchmark:structure-resolver
```

It queries only projected activity summary and detected-structure fields. Titles, descriptions, comments, planned codes, intent, coach prose and raw record streams are excluded at the database boundary. Manual confirmation supplies the hidden gold label after inference; existing automatic matches form a separately reported silver cohort and never determine release status.

The current evaluation-only candidate is `netlify/functions/_lib/ride-review/structure-resolver-v0.js`. It is not connected to the production Ride Review Pack or a public endpoint. Baseline results and their limitations are recorded in `docs/evals/public-activity-structure-resolver-baseline-2026-08-12.md`.

## Versioning

- Contract: `semipro_activity_intelligence_v1`
- Resolver: independently versioned
- Specialist module registry: independently versioned
- Evidence methods/manuals: independently versioned

The contract changes only for breaking request or response changes. Better resolver calibration, evidence calculations and released module judgement may ship under new analysis versions while retaining the v1 response shape.
