Skip to content

[WIP] Add VAR Decision Integrity Layer to SportsVertical#46

Open
Copilot wants to merge 1 commit into
mainfrom
copilot/add-var-decision-integrity-layer
Open

[WIP] Add VAR Decision Integrity Layer to SportsVertical#46
Copilot wants to merge 1 commit into
mainfrom
copilot/add-var-decision-integrity-layer

Conversation

Copilot AI commented Mar 25, 2026

Copy link
Copy Markdown

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.

Original prompt

Overview

Extend the existing Sports Vertical in backend/src/services/verticals/sports/SportsVerticalExpanded.ts to add a FIFA-specific VAR Decision Integrity Layer. This adds VAR officiating decision types, schemas, agent presets, and compliance frameworks — all wired into the existing SportsVerticalImplementation.

This is for a FIFA submission under:

  • Primary category: Audio Transcription Tool
  • Secondary category: Officiating Technologies
  • Product name (FIFA only): "VAR Decision Integrity Layer"

Changes Required

1. New Decision Type: VARDecision interface

Add to the SPORTS DECISION TYPES section in SportsVerticalExpanded.ts:

export interface VARDecision extends BaseDecision {
  type: 'var-review';
  inputs: {
    matchId: string;
    incidentTimestamp: string;
    incidentType: 'goal' | 'penalty' | 'red-card' | 'mistaken-identity';
    pitchLocation: { x: number; y: number; zone: string };
    cameraAngles: { cameraId: string; url: string; frameRange: [number, number] }[];
    audioTranscript: { speaker: string; role: 'referee' | 'var' | 'avar' | 'offside-specialist'; timestamp: string; text: string }[];
    semiAutomatedOffside?: {
      limbPoints: Record<string, { x: number; y: number }>;
      calibrationHash: string;
      result: 'onside' | 'offside';
      confidence: number;
    };
    onFieldDecision: string;
    varRecommendation: string;
  };
  outcome: {
    finalDecision: 'uphold' | 'overturn' | 'on-field-review';
    decisionRationale: string;
    incidentClassification: string;
    sanctionApplied?: string;
    reviewDurationMs: number;
    audioTranscriptHash: string;
    cameraEvidenceHash: string;
    cryptographicSeal: string;
  };
}

2. Add VARDecision to the SportsDecision union type

Update:

export type SportsDecision =
  | PlayerTransferDecision | SalaryCapDecision | PlayerSafetyDecision | AntiDopingDecision
  | YouthDevelopmentDecision | MatchIntegrityDecision | VenueDecision | BroadcastRightsDecision
  | DraftSelectionDecision | SponsorshipDecision | DisciplinaryDecision | FinancialFairPlayDecision
  | VARDecision;

3. New Decision Schema: VARDecisionSchema

Add a new class after FinancialFairPlaySchema:

export class VARDecisionSchema extends DecisionSchema<VARDecision> {
  readonly verticalId = 'sports';
  readonly decisionType = 'var-review';
  readonly requiredFields = [
    'inputs.matchId', 'inputs.incidentType', 'inputs.audioTranscript',
    'inputs.cameraAngles', 'outcome.finalDecision', 'outcome.audioTranscriptHash'
  ];
  readonly requiredApprovers = ['var-official', 'head-referee'];

  validate(d: Partial<VARDecision>): ValidationResult {
    const errors: string[] = [], warnings: string[] = [];
    if (!d.inputs?.matchId) errors.push('Match ID required');
    if (!d.inputs?.incidentType) errors.push('Incident type required');
    if (!d.inputs?.audioTranscript || d.inputs.audioTranscript.length === 0) errors.push('Audio transcript required — VAR protocol mandates recorded communications');
    if (!d.inputs?.cameraAngles || d.inputs.cameraAngles.length === 0) errors.push('At least one camera angle required');
    if (d.outcome?.reviewDurationMs && d.outcome.reviewDurationMs < 15000) warnings.push('Review duration under 15 seconds — flag for quality check');
    if (d.inputs?.incidentType !== 'goal' && d.outcome?.finalDecision === 'overturn') warnings.push('Non-goal overturn — ensure clear & obvious error standard met');
    return { valid: errors.length === 0, errors, warnings, requiredFields: this.requiredFields };
  }

  async sign(d: VARDecision, sId: string, sRole: string, pk: string): Promise<VARDecision> {
    d.signatures.push({
      signerId: sId, signerRole: sRole, signedAt: new Date(),
      signature: this.generateSignature(this.hashDecision(d), pk),
      publicKeyFingerprint: crypto.createHash('sha256').update(pk).digest('hex').slice(0, 16)
    });
    return d;
  }

  async toDefensibleArtifact(d: VARDecision, t: DefensibleArtifact['type']): Promise<DefensibleArtifact> {
    return {
      id: uuidv4(), decisionId: d.metadata.id, type: t,
      content: {
        match: d.inputs.matchId,
        incident: d.inputs.incidentType,
        onFieldDecision: d.inputs.onFieldDecision,
        varRecommendation: d.inputs.varRecommendation,
        finalDecision: d.outcome.finalDecision,
        rationale: d.outcome.decisionRationale,
        audioTranscriptHash: d.outcome.audioTranscriptHash,
        cameraEvidenceHash: d.outcome.cameraEvidenceHash,
        reviewDurationMs: d.outcome.reviewDurationMs,
        deliberation: d.deliberation
      },
      hash: crypto.createHash('sha256').update(JSON.stringify(d)).digest('hex'),
      generatedAt: new Date()
    };
  }
}

4. New Agent Preset: VAROfficiatingPreset

Add after SportsGovernancePreset:

export class VAROfficiatingPreset extends AgentPreset {
  readonly verticalId = '...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

<!-- START COPILOT CODING AGENT TIPS -->
---

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. [Learn more about Advanced Security.](https://gh.io/cca-advanced-security)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants