# Tennis Match Dataset Quality Analyzer (`zinin/tennis-match-dataset-quality-analyzer`) Actor

Validate supplied tennis records and produce historical descriptive player summaries with explicit sample sizes.

- **URL**: https://apify.com/zinin/tennis-match-dataset-quality-analyzer.md
- **Developed by:** [Tim Zinin](https://apify.com/zinin) (community)
- **Categories:** Sports, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $127.50 / 1,000 tennis dataset quality report delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tennis Match Dataset Quality Analyzer

Validate tennis match records you supply and receive dataset-quality findings plus descriptive historical player summaries. The Actor does not fetch records or predict performance, supply odds, recommend wagers or rank future skill.

This Actor is available on Apify and analyzes records you supply. Run another check for each new or corrected batch of match records before using it in a report or analysis.

On Apify's Free plan, a useful report costs **$0.15**, plus **$0.005 per start event**. At the default 512 MB memory, one useful report costs **$0.155**. Other plan tiers have different rates. Fixed demos and invalid-input diagnostics request no result event; a platform start charge can still apply. The examples below are synthetic and do not establish live source accuracy.

When Apify rejects input against the input schema, the Actor does not run. The diagnostics and partial reports below describe checks performed after the Actor starts.

### Input

For the fixed synthetic demo, supply only `{ "demo": true }`. It requests no result event; a platform start charge may still apply. Demo mode rejects supplied matches.

For your own records, omit `demo` or set it to `false`. This mode requests one report event on the platform when the report is useful. This copyable example contains a **fictional synthetic match**; `demo: false` selects the real analysis route even for fictional input:

```json
{
  "demo": false,
  "matches": [
    {
      "namespace": "synthetic-example",
      "matchId": "demo-match",
      "playedOn": "2026-09-01",
      "winnerId": "demo-player-a",
      "loserId": "demo-player-b",
      "status": "completed",
      "surface": "hard",
      "bestOf": 3,
      "sets": [
        { "winnerGames": 6, "loserGames": 3 },
        { "winnerGames": 7, "loserGames": 5 }
      ]
    }
  ]
}
```

The only top-level fields are required `matches` and optional boolean `demo`. Real mode accepts 1–5000 rows and at most 2 MiB of serialized UTF-8 match-input JSON. Each row allows only these fields:

| Field | Required | Accepted value |
| --- | --- | --- |
| `namespace` | Yes | Nonempty source namespace, at most 100 characters. |
| `matchId` | Yes | Nonempty match ID, at most 100 characters; scoped to namespace. |
| `playedOn` | Yes | Proleptic Gregorian date in `YYYY-MM-DD` format, with a four-digit year 0000–9999. |
| `winnerId`, `loserId` | Yes | Nonempty distinct player IDs, each at most 100 characters. |
| `status` | Yes | `completed`, `retired` or `walkover`. |
| `surface` | Yes | `hard`, `clay`, `grass`, `carpet` or `unknown`. |
| `bestOf` | Yes | Integer `3` or `5`. |
| `sets` | Yes | Array of 0–5 objects containing only integer `winnerGames` and `loserGames`, each 0–99; the scoring rules below further restrict validity. |
| `tournamentId` | No | String of at most 100 characters, including empty string; not `null`. |

String lengths use JavaScript UTF-16 units. IDs and tournament IDs are checked for control characters and raw length, then trimmed. Required identifiers must remain nonempty and winner/loser IDs must differ after trimming. There is no case folding, fuzzy player matching or cross-provider merging. An omitted tournament ID and an explicitly empty ID remain distinct for duplicate comparison. Unknown fields or non-JSON values are invalid.

### Supported scoring

Set scores use the **overall match winner/loser orientation**, so `winnerGames` may be lower in a set that the match winner lost. Standard completed tiebreak sets are 6–0 through 6–4, 7–5 or 7–6, in either orientation. Short sets, match tiebreaks and advantage-set formats are unsupported and are reported as issues instead of guessed valid.

A completed match must end immediately when the declared winner wins the best-of majority: two sets for best-of-three or three for best-of-five. Extra sets after the decision, incomplete matches and reversed overall winners are invalid.

A retired match may contain no sets, completed sets before retirement, or an unfinished final set consistent with standard scoring. Only the final set may be unfinished, and neither player may already have won the match. A walkover must contain zero sets. Retired and walkover records are counted separately and excluded from completed-match performance statistics.

### Report and invalid rows

A useful report has `schemaVersion: "1.0"`, `reportType: "tennis_dataset_quality"`, `status: "ok"` or `"partial"`, and `useful: true`.

- `counts` includes received, accepted, duplicates, invalid, completed, retired and walkover totals. `received = accepted + duplicates + invalid` always holds.
- `acceptedMatchIds` contains exact `(namespace, matchId)` identities. Player identities are also scoped to namespace; output is deterministically ordered.
- `players` includes completed-match sample sizes, wins, losses and win rates, plus separate `bySurface` buckets for hard, clay, grass, carpet and unknown. `winRate` is wins divided by the explicit completed-match count, or null at zero. Players present only in accepted retired/walkover records have zero completed matches and null rates.
- `issues` contains at most 100 issue codes and zero-based original `rowIndexes`; `issuesTruncated` indicates further findings. Counting continues after truncation.

Normalized exact duplicates count once. Property order alone does not create a conflict. Any malformed row with a readable identity quarantines the entire identity group, including valid siblings. Conflicting valid rows also quarantine their whole group; no last-write winner is selected. Independent valid identities remain usable. Accepted rows with invalid or duplicate rows produce partial status; all-invalid input produces an unbilled diagnostic with bounded counts, issue codes and indices, subject to the platform pricing checks below.

The report returns identifiers and aggregates without echoing full match rows or tournament IDs. It describes the supplied history and does not certify source rights, accuracy or representativeness. Input is unchanged. Malformed whole-input structure or size yields a bounded diagnostic, while individual malformed records remain visible in quality findings.

### Actor integration and delivery

This published Actor uses Node 22 and Apify SDK 3.7.2. You can also run the same package locally for development. Install locked dependencies with `npm ci`, then use `npm start` with local Apify input storage. Local execution requests no result event. The package makes no source-provider calls and uses no credentials, proxies, child Actors or cross-run storage.

For the fixed synthetic example, supply only:

```json
{ "demo": true }
```

Demo mode rejects extra snapshots or matches; supplied customer data is never processed as a free demo. Demo reports carry `isDemo: true` and `dataProvenance: "fixed_synthetic_demo"`. For real mode supply the JSON input shown above, with optional `demo: false`; the report is marked `isDemo: false`, `dataProvenance: "user_supplied"`. These labels describe the input origin, not verified source rights or accuracy.

A useful real input produces exactly one default Dataset report, including useful partial results. On the platform, that report requests one `result-found` event at the buyer's readable configured event price. Inspect the Actor's configured pricing before running and optionally set **Max total charge** sufficient to cover the report plus any platform start charge. A verified unset limit permits the one-report delivery; an explicit zero prevents a paid report. Unreadable or inconsistent run-limit settings fail safely before paid delivery. The runtime verifies pricing, exact remaining allowance and named counters before analysis and again before the paid write. Every platform Dataset write, including demos and diagnostics, requires consistent confirmed pay-per-event pricing and an absent or numeric-zero default Dataset item price. Free, pay-per-result, rental, unknown or inconsistent pricing models are unsupported and fail without a Dataset write. An empty event-price table alone does not prove that writing is free. Local development continues without result billing.

When the supported PPE/free-item pricing and transport checks pass, malformed or all-invalid input receives an unbilled diagnostic, retaining bounded counts, issue codes and row indices when available. It omits source IDs, field values and raw exception text. A known insufficient budget stops normally. Broken pricing, budget state, SDK operations or unexpected computation errors fail the run. “Unbilled” means no result event is requested; a platform start charge may already apply.

Download the complete nested report from the Dataset link in the run output. The Dataset is the delivery authority; there is no mandatory OUTPUT or cache write after paid delivery. The SDK writes the row before requesting its linked charge, so a paid row records `billing.settlement: "unconfirmed_at_write"`. A successful run verifies the SDK receipt and an increase of exactly one in the named `result-found` counter. A failed push or unreadable receipt leaves delivery and settlement unknown: data may exist and a charge may have occurred. The Actor does not retry that write. For the pinned SDK, it sets the actual API HTTP client to zero retries and verifies that the default Dataset uses that same client before each write; an unreadable or different transport blocks delivery. Inspect that run's Dataset and charged event counts before another analysis.

Each new run is a new analysis and can incur a new result event. There is no cross-run idempotency promise. Automatic resurrection is unsupported: if the same run already records a result event, the runtime refuses another report and directs you to its existing Dataset. This check cannot resolve a previous Dataset write with a zero or unavailable result counter; do not automatically resurrect or rerun a failed delivery.

Local tests use synthetic fixtures and guarded SDK transports. The pure algorithms and SDK runtime have independent local acceptance; cloud release acceptance remains outstanding. Future release gates include an approved price decision, a bounded real cloud run with Dataset/event readback, real-source provenance, icon upload/readback and a complete Store listing. Local tests do not establish market demand, profitability or cloud acceptance.

# Actor input Schema

## `demo` (type: `boolean`):

True uses only a fixed synthetic example. Supply no matches or snapshots in demo mode. A platform start charge may still apply.

## `matches` (type: `array`):

Buyer-supplied records; this Actor does not fetch any source. Omit this field in demo mode. See README for the strict record contract.

## Actor input object example

```json
{
  "demo": true
}
```

# Actor output Schema

## `report` (type: `string`):

One report or diagnostic. A paid row is written before charge acknowledgement; inspect the run event counts if the run fails.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "demo": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("zinin/tennis-match-dataset-quality-analyzer").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "demo": True }

# Run the Actor and wait for it to finish
run = client.actor("zinin/tennis-match-dataset-quality-analyzer").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "demo": true
}' |
apify call zinin/tennis-match-dataset-quality-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,zinin/tennis-match-dataset-quality-analyzer"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/XqBkJW1pfaub4wPVx/builds/gDFJh50dTmhscNQ3e/openapi.json
