Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ jobs:
run: npm ci
- name: Run tests
run: npm test
- name: Check types
run: npm run typecheck
- name: Run format check
run: npm run format:check
- name: Calculate coverage
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,18 @@ This package contains TypeScript interfaces for the different classes used in WC
npm install @wca/helpers --save
```

## WCIF version compatibility

`@wca/helpers` 2.x targets WCIF 2.x, including WCIF 2.2. Use
`@wca/helpers` 1.x for WCIF 1.x.

## Usage example

## Development setup

Run `npm test` for the unit tests and `npm run typecheck` to check the
TypeScript source.

## Release process

Releases are managed by [Release Please](https://github.com/googleapis/release-please).
Expand Down
85 changes: 45 additions & 40 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
],
"scripts": {
"test": "jest",
"typecheck": "tsc --noEmit",
"test:coverage": "jest --coverage",
"format": "prettier --write \"**/*.{ts,js}\"",
"format:check": "prettier --check \"**/*.{ts,js}\"",
Expand Down
17 changes: 17 additions & 0 deletions spec/format.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
getFormatExpectedSolves,
getFormatName,
getFormatRanking,
getFormatTrimBest,
getFormatTrimWorst,
} from '../src/helpers/format';

describe('Format helper', function () {
it('returns the Head-to-Head format properties', function () {
expect(getFormatName('h')).toBe('Head-to-Head');
expect(getFormatRanking('h')).toEqual([]);
expect(getFormatExpectedSolves('h')).toBe(0);
expect(getFormatTrimBest('h')).toBe(0);
expect(getFormatTrimWorst('h')).toBe(0);
});
});
16 changes: 15 additions & 1 deletion spec/ranking.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,28 @@ function createResult(personId: number, plain: number[]): Result {
};
plain.forEach((p) =>
r.attempts.push({
result: p,
value: p,
reconstruction: null,
}),
);
return r;
}

describe('Ranking Helper', function () {
it('keeps the supplied order and rankings when no ranking order exists', function () {
let results: Result[] = [
{ ...createResult(2, [900]), ranking: 1 },
{ ...createResult(1, [800]), ranking: 2 },
];

let rankedResults = rank(results, []);

expect(rankedResults[0].personId).toBe(2);
expect(rankedResults[0]).toHaveRanking(1);
expect(rankedResults[1].personId).toBe(1);
expect(rankedResults[1]).toHaveRanking(2);
});

describe('Average only', function () {
describe('Ao5', function () {
it('ranks 2 people with the same average the same', function () {
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/average.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AttemptResult } from '../models/attemptResult';
import { ResultValue } from '../models/resultValue';
import { isMultiResult } from './result';

export function Ao5(results: AttemptResult[]): AttemptResult | null {
export function Ao5(results: ResultValue[]): ResultValue | null {
results = results.filter((r) => r !== 0);

if (results.some(isMultiResult)) return null; // cannot calculate average or mean for MBLD
Expand Down Expand Up @@ -38,7 +38,7 @@ export function Ao5(results: AttemptResult[]): AttemptResult | null {
return avg;
}

export function Mo3(results: AttemptResult[]): AttemptResult | null {
export function Mo3(results: ResultValue[]): ResultValue | null {
results = results.filter((r) => r !== 0); // remove non-existing attempts

if (results.some(isMultiResult)) return null; // cannot calculate average or mean for MBLD
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export function getFormatName(format: RoundFormat): string {
return 'Average of 5';
case 'm':
return 'Mean of 3';
case 'h':
return 'Head-to-Head';
}
}

Expand All @@ -24,6 +26,8 @@ export function getFormatRanking(format: RoundFormat): RankingType[] {
case '3':
case '5':
return ['single'];
case 'h':
return [];
case 'a':
case 'm':
return ['average', 'single'];
Expand All @@ -44,6 +48,8 @@ export function getFormatExpectedSolves(format: RoundFormat): number {
return 5;
case 'm':
return 3;
case 'h':
return 0;
}
}

Expand All @@ -53,6 +59,7 @@ export function getFormatTrimBest(format: RoundFormat): number {
case '2':
case '3':
case 'm':
case 'h':
return 0;
case 'a':
case '5':
Expand All @@ -66,6 +73,7 @@ export function getFormatTrimWorst(format: RoundFormat): number {
case '2':
case '3':
case 'm':
case 'h':
return 0;
case 'a':
case '5':
Expand Down
8 changes: 5 additions & 3 deletions src/helpers/ranking.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Result } from '../models/result';
import { RankingType } from '../models/rankingType';
import { Ao5, Mo3 } from './average';
import { AttemptResult } from '../models/attemptResult';
import { ResultValue } from '../models/resultValue';

export function rank(results: Result[], rankingOrder: RankingType[]): Result[] {
let averageCache: { [personId: number]: AttemptResult | null } = {};
if (rankingOrder.length === 0) return results;

let averageCache: { [personId: number]: ResultValue | null } = {};
let bestCache: { [personId: number]: number } = {};

// first calc bests and averages where applicable
results.forEach((r) => {
let plain = r.attempts.map((a) => a.result);
let plain = r.attempts.map((a) => a.value);
if (rankingOrder.indexOf('average') > -1) {
let average = r.attempts.length === 5 ? Ao5(plain) : Mo3(plain);
if ((average as number) < 0) {
Expand Down
14 changes: 7 additions & 7 deletions src/helpers/result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AttemptResult } from '../models/attemptResult';
import { ResultValue } from '../models/resultValue';
import { formatCentiseconds } from './time';

type DnfMultiResult = { isDnf: true };
Expand All @@ -11,21 +11,21 @@ type ValidMultiResult = {

type DecodedMultiResult = DnfMultiResult | DnsMultiResult | ValidMultiResult;

export function isDnf(result: AttemptResult): boolean {
export function isDnf(result: ResultValue): boolean {
return result === -1;
}

export function isDns(result: AttemptResult): boolean {
export function isDns(result: ResultValue): boolean {
return result === -2;
}

export function isMultiResult(result: AttemptResult): boolean {
export function isMultiResult(result: ResultValue): boolean {
// Formatted as a base 10 string, a multi result will always have 8 or 9 digits.
// New style mbld has an implied 0 at the beginning which is not included in the length
return [9, 10].indexOf(result.toString().length) > -1;
}

export function decodeMultiResult(result: AttemptResult): DecodedMultiResult {
export function decodeMultiResult(result: ResultValue): DecodedMultiResult {
let r = result;
if (r === -1) {
return { isDnf: true };
Expand Down Expand Up @@ -88,7 +88,7 @@ function isDnsMultiResult(
return result.hasOwnProperty('isDns');
}

function decodeOldMultiResult(result: AttemptResult): DecodedMultiResult {
function decodeOldMultiResult(result: ResultValue): DecodedMultiResult {
// Handles DNF and DNS
if (result <= 0) return { solved: 0, attempted: 0, centiseconds: result };

Expand All @@ -104,7 +104,7 @@ function decodeOldMultiResult(result: AttemptResult): DecodedMultiResult {
return res;
}

function decodeNewMultiResult(result: AttemptResult): DecodedMultiResult {
function decodeNewMultiResult(result: ResultValue): DecodedMultiResult {
// Handles DNF and DNS
if (result <= 0) return { solved: 0, attempted: 0, centiseconds: result };

Expand Down
21 changes: 0 additions & 21 deletions src/models/advancementCondition.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/models/attempt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AttemptResult } from './attemptResult';
import { ResultValue } from './resultValue';

export interface Attempt {
result: AttemptResult;
value: ResultValue;
reconstruction: string | null;
}
1 change: 0 additions & 1 deletion src/models/attemptResult.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/models/competition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface Competition {
persons: Person[];
events: Event[];
schedule: Schedule;
series: Series[];
series: Series | null;
competitorLimit: number | null;
extensions: Extension[];
registrationInfo: RegistrationInfo;
Expand Down
Loading
Loading