@finbheara/domain 0.50.0 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/dist/code.js +136 -0
- package/dist/types/code.d.ts +41 -0
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
|
|
|
50
50
|
| `@finbheara/domain/promotion` | `samePromotedRow`, `samePromotedSchool`, `samePromotedHeir`, `samePromotedName`, `questionMarksRead`, `foldCharSynonyms`: is a row the same row after a promotion (below) |
|
|
51
51
|
| `@finbheara/domain/person` | `seatName`, `dancerName`, `teacherName`, `roleName`, `roleSentinelOf`, `rolePrintsNoName`, `dancerPrintsNoName`, the role lists and `SHARED_ROW_SENTINELS`: a person's print read in its role (below) |
|
|
52
52
|
| `@finbheara/domain/title` | `compTitle`, `titleRelation`, `matchTitle`, `ladderOf`, `populationIn`, `populationRelation`, `roundNumbersIn`, `roundFold`, `codePopulation`, `secondIn`: a competition title read and matched onto syllabus entries, a code's sex suffix, a second chance (below) |
|
|
53
|
+
| `@finbheara/domain/code` | `createCodeReader`, `compCode`, `DEFAULT_CODE_READER`, the `CompCode` type: a competition code read at the feis it belongs to, from that feis's syllabus or, on a platform you name as standardized, from the code alone (below) |
|
|
53
54
|
| `@finbheara/domain/schedule` | `scheduleAt`, `sameHall`, `readRunningOrder`, the `ScheduleSlot` type: which entries a hall runs (below) |
|
|
54
55
|
|
|
55
56
|
The library does no I/O and installs nothing process-wide. Every reader has a default and a
|
|
@@ -454,6 +455,50 @@ secondIn("Preliminary Champion, Supplementary Prelim (U15)"); // true
|
|
|
454
455
|
secondIn("Preliminary Champion, Hard Shoe (U10)"); // false
|
|
455
456
|
```
|
|
456
457
|
|
|
458
|
+
## Read a competition code
|
|
459
|
+
|
|
460
|
+
A code is a feis's own number for a competition. Some platforms number every feis by one
|
|
461
|
+
grammar: a level digit (`1` first feis to `7` open championship), two digits for the age column
|
|
462
|
+
(fifty more for a boys' column, `99` for adults) and letters for the dance, so `512SJ` is a
|
|
463
|
+
prizewinner slip jig under 12 wherever it is printed. Most platforms let each feis number as it
|
|
464
|
+
likes, and the same `344` is a novice under 11 at one feis and a beginner reel at the next. Read
|
|
465
|
+
with the grammar, such a code gives an age and a level that look right and are wrong.
|
|
466
|
+
|
|
467
|
+
So a code is read only at a feis. `read` takes the platform and the feis's address or slug, and
|
|
468
|
+
answers from the feis's syllabus where you hand it in, on any platform. With no syllabus, only a
|
|
469
|
+
platform you name as standardized reads its code alone. Everything else is refused, with its
|
|
470
|
+
reason: `no-feis`, `feis-specific`, `not-in-syllabus`, `two-entries` or `not-a-code`. An empty
|
|
471
|
+
`syllabus: []` is no syllabus, never a syllabus that prints no code. The default
|
|
472
|
+
reader names no platform, so with no configuration a code reads only from a syllabus. A title is
|
|
473
|
+
not a code: `compTitle` still reads it at any feis.
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
import { createCodeReader } from "@finbheara/domain/code";
|
|
477
|
+
|
|
478
|
+
const codes = createCodeReader({ standardized: ["examplefeis"] });
|
|
479
|
+
|
|
480
|
+
const c = codes.read("512SJ", { platform: "examplefeis", feis: "ex:1001" });
|
|
481
|
+
c.age.ag1; // "u12"
|
|
482
|
+
c.level; // 500
|
|
483
|
+
c.source; // "code"
|
|
484
|
+
|
|
485
|
+
codes.read("344", { platform: "other", feis: "x:ABC26" }).invalidReason(); // "feis-specific"
|
|
486
|
+
codes.read("512SJ", { platform: "examplefeis", feis: null }).invalidReason(); // "no-feis"
|
|
487
|
+
|
|
488
|
+
const syllabus = [{ code: "344", title: "Novice Girls Under 11 Light Jig" }];
|
|
489
|
+
const s = codes.read("344", { platform: "other", feis: "x:ABC26", syllabus });
|
|
490
|
+
s.age.ag1; // "u11"
|
|
491
|
+
s.entry; // the entry: read its level yourself
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
The level comes from the grammar's level digit only. A set, a treble reel, a figure and a future
|
|
495
|
+
champions competition run off the grade ladder and read no level, and a syllabus answer reads none,
|
|
496
|
+
on a standardized platform too (the syllabus wins, and the package reads no level word): the
|
|
497
|
+
matching `entry` comes back for you to read. The oldest
|
|
498
|
+
column a feis offers in one series is its and-over (`719OC` is 18 and over where it is the top
|
|
499
|
+
open column). Hand in every code the feis printed (`codes`) to read the top column and a level
|
|
500
|
+
split by sex. Without them a column reads as its `Under` band and who dances is not said.
|
|
501
|
+
|
|
457
502
|
## Ask a schedule
|
|
458
503
|
|
|
459
504
|
A split band's competitions share their card numbers, so a card heard on a stage fits each of
|
package/dist/code.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Age2
|
|
3
|
+
} from "./chunks/place-kmh59zkw.js";
|
|
4
|
+
import {
|
|
5
|
+
populationIn2,
|
|
6
|
+
compTitle2
|
|
7
|
+
} from "./chunks/place-7jfd4m3j.js";
|
|
8
|
+
import {
|
|
9
|
+
validity
|
|
10
|
+
} from "./chunks/place-q9kwsmzd.js";
|
|
11
|
+
|
|
12
|
+
var TRAILING = /(?:-[A-Za-z0-9]+|\[[A-Za-z0-9]+\])$/;
|
|
13
|
+
var GROUP = /^([1-7])(\d{2})([A-Za-z]+?)2?$/;
|
|
14
|
+
var OFF_LADDER = new Set(["TR", "TS", "SD", "FD", "FC"]);
|
|
15
|
+
function group(print) {
|
|
16
|
+
let s = print.trim();
|
|
17
|
+
let male = false;
|
|
18
|
+
let female = false;
|
|
19
|
+
for (let m = TRAILING.exec(s);m !== null; m = TRAILING.exec(s)) {
|
|
20
|
+
const t = m[0].toUpperCase();
|
|
21
|
+
if (t === "-M" || t === "[M]")
|
|
22
|
+
male = true;
|
|
23
|
+
if (t === "-F" || t === "[F]")
|
|
24
|
+
female = true;
|
|
25
|
+
s = s.slice(0, m.index);
|
|
26
|
+
}
|
|
27
|
+
const g = GROUP.exec(s);
|
|
28
|
+
if (g === null)
|
|
29
|
+
return null;
|
|
30
|
+
const mm = Number(g[2]);
|
|
31
|
+
const plus50 = mm >= 50 && mm <= 89;
|
|
32
|
+
return {
|
|
33
|
+
digit: g[1],
|
|
34
|
+
letters: g[3].toUpperCase(),
|
|
35
|
+
column: mm === 99 ? null : plus50 ? mm - 50 : mm,
|
|
36
|
+
boys: plus50 || male,
|
|
37
|
+
girls: female
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function groups(print) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const part of print.split("/")) {
|
|
43
|
+
const g = group(part);
|
|
44
|
+
if (g === null)
|
|
45
|
+
return null;
|
|
46
|
+
out.push(g);
|
|
47
|
+
}
|
|
48
|
+
return out.length === 0 ? null : out;
|
|
49
|
+
}
|
|
50
|
+
function decode(print, year, codes) {
|
|
51
|
+
const gs = groups(print);
|
|
52
|
+
if (gs === null)
|
|
53
|
+
return null;
|
|
54
|
+
const first = gs[0];
|
|
55
|
+
if (gs.some((g) => g.digit !== first.digit))
|
|
56
|
+
return null;
|
|
57
|
+
const adult = gs.filter((g) => g.column === null).length;
|
|
58
|
+
if (adult > 0 && adult < gs.length)
|
|
59
|
+
return null;
|
|
60
|
+
const level = OFF_LADDER.has(first.letters) ? null : Number(first.digit) * 100;
|
|
61
|
+
const others = codes === null ? [] : codes.flatMap((c) => groups(c) ?? []);
|
|
62
|
+
const boys = gs.every((g) => g.boys);
|
|
63
|
+
const split = others.some((g) => g.digit === first.digit && g.boys);
|
|
64
|
+
const population = gs.some((g) => g.boys) && !boys ? "mixed" : boys ? "boys" : codes === null ? "" : split || first.girls && others.some((g) => g.boys) ? "girls" : "mixed";
|
|
65
|
+
if (adult > 0)
|
|
66
|
+
return { age: Age2.from("Adult", { year }), level, population };
|
|
67
|
+
const cols = gs.map((g) => g.column);
|
|
68
|
+
const lo = Math.min(...cols);
|
|
69
|
+
const hi = Math.max(...cols);
|
|
70
|
+
const series = others.filter((g) => g.digit === first.digit && g.letters === first.letters && g.boys === boys);
|
|
71
|
+
const offered = series.map((g) => g.column).filter((c) => c !== null);
|
|
72
|
+
const top = offered.length > 1 && hi >= Math.max(...offered);
|
|
73
|
+
const band = top ? `O${lo - 1}` : lo === hi ? `U${hi}` : `U${hi}/${hi - lo + 1}yr`;
|
|
74
|
+
return { age: Age2.from(band, { year }), level, population };
|
|
75
|
+
}
|
|
76
|
+
var foldCode = (code) => code.trim().toUpperCase();
|
|
77
|
+
var NO_AGE = Age2.from("", { year: null });
|
|
78
|
+
function createCodeReader(options = {}) {
|
|
79
|
+
const standardized = new Set([...options.standardized ?? []].map((p) => p.trim().toLowerCase()));
|
|
80
|
+
const scheme = (platform) => standardized.has(platform.trim().toLowerCase()) ? "standardized" : "feis-specific";
|
|
81
|
+
function read(code, ctx) {
|
|
82
|
+
const s = scheme(ctx.platform);
|
|
83
|
+
const feis = (ctx.feis ?? "").trim() === "" ? null : ctx.feis.trim();
|
|
84
|
+
const year = ctx.year ?? null;
|
|
85
|
+
const make = (refusal, source, age, level, population, entry) => {
|
|
86
|
+
const v = validity(code.trim() === "", refusal);
|
|
87
|
+
return Object.freeze({
|
|
88
|
+
[Symbol.toStringTag]: "CompCode",
|
|
89
|
+
...v,
|
|
90
|
+
print: code,
|
|
91
|
+
platform: ctx.platform,
|
|
92
|
+
feis,
|
|
93
|
+
scheme: s,
|
|
94
|
+
source: v.isValid() ? source : null,
|
|
95
|
+
age: v.isValid() ? age : NO_AGE,
|
|
96
|
+
level: v.isValid() ? level : null,
|
|
97
|
+
population: v.isValid() ? population : "",
|
|
98
|
+
entry: v.isValid() ? entry : null,
|
|
99
|
+
isComp: () => v.isValid() && age.isAge()
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
const refuse = (r) => make(r, null, NO_AGE, null, "", null);
|
|
103
|
+
if (code.trim() === "")
|
|
104
|
+
return refuse("not-a-code");
|
|
105
|
+
if (feis === null)
|
|
106
|
+
return refuse("no-feis");
|
|
107
|
+
const codes = ctx.codes === undefined ? null : [...ctx.codes];
|
|
108
|
+
if (ctx.syllabus !== undefined && ctx.syllabus.length > 0) {
|
|
109
|
+
const hits = ctx.syllabus.filter((e) => foldCode(e.code) === foldCode(code));
|
|
110
|
+
if (hits.length === 0)
|
|
111
|
+
return refuse("not-in-syllabus");
|
|
112
|
+
if (hits.length > 1)
|
|
113
|
+
return refuse("two-entries");
|
|
114
|
+
const e = hits[0];
|
|
115
|
+
const printed = e.age !== undefined && e.age.trim() !== "" ? Age2.from(e.age, { year }) : null;
|
|
116
|
+
const age = printed?.isAge() === true ? printed : compTitle2(e.title, { year }).age;
|
|
117
|
+
return make(null, "syllabus", age, null, populationIn2(e.title), e);
|
|
118
|
+
}
|
|
119
|
+
if (s === "feis-specific")
|
|
120
|
+
return refuse("feis-specific");
|
|
121
|
+
const decoded = decode(code, year, codes);
|
|
122
|
+
if (decoded === null || !decoded.age.isAge())
|
|
123
|
+
return refuse("not-a-code");
|
|
124
|
+
return make(null, "code", decoded.age, decoded.level, decoded.population, null);
|
|
125
|
+
}
|
|
126
|
+
return Object.freeze({ scheme, read });
|
|
127
|
+
}
|
|
128
|
+
var DEFAULT_CODE_READER = createCodeReader();
|
|
129
|
+
function compCode(code, ctx) {
|
|
130
|
+
return DEFAULT_CODE_READER.read(code, ctx);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
DEFAULT_CODE_READER,
|
|
134
|
+
compCode,
|
|
135
|
+
createCodeReader
|
|
136
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Age } from "./age.ts";
|
|
2
|
+
import { type Population } from "./title.ts";
|
|
3
|
+
import { type Validated } from "./validated.ts";
|
|
4
|
+
export type { EmptyReason, Validated } from "./validated.ts";
|
|
5
|
+
export type CodeScheme = "standardized" | "feis-specific";
|
|
6
|
+
export type CodeRefusal = "no-feis" | "feis-specific" | "not-in-syllabus" | "two-entries" | "not-a-code";
|
|
7
|
+
export interface CodeEntry {
|
|
8
|
+
readonly code: string;
|
|
9
|
+
readonly title: string;
|
|
10
|
+
readonly age?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface CodeContext<E extends CodeEntry = CodeEntry> {
|
|
13
|
+
readonly platform: string;
|
|
14
|
+
readonly feis: string | null | undefined;
|
|
15
|
+
readonly year?: number | null;
|
|
16
|
+
readonly syllabus?: readonly E[];
|
|
17
|
+
readonly codes?: Iterable<string>;
|
|
18
|
+
}
|
|
19
|
+
export interface CompCode<E extends CodeEntry = CodeEntry> extends Validated<CodeRefusal> {
|
|
20
|
+
readonly [Symbol.toStringTag]: "CompCode";
|
|
21
|
+
readonly print: string;
|
|
22
|
+
readonly platform: string;
|
|
23
|
+
readonly feis: string | null;
|
|
24
|
+
readonly scheme: CodeScheme;
|
|
25
|
+
readonly source: "syllabus" | "code" | null;
|
|
26
|
+
readonly age: Age;
|
|
27
|
+
readonly level: number | null;
|
|
28
|
+
readonly population: Population | "";
|
|
29
|
+
readonly entry: E | null;
|
|
30
|
+
isComp(): boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface CodeReaderOptions {
|
|
33
|
+
readonly standardized?: Iterable<string>;
|
|
34
|
+
}
|
|
35
|
+
export interface CodeReader {
|
|
36
|
+
scheme(platform: string): CodeScheme;
|
|
37
|
+
read<E extends CodeEntry>(code: string, ctx: CodeContext<E>): CompCode<E>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createCodeReader(options?: CodeReaderOptions): CodeReader;
|
|
40
|
+
export declare const DEFAULT_CODE_READER: CodeReader;
|
|
41
|
+
export declare function compCode<E extends CodeEntry>(code: string, ctx: CodeContext<E>): CompCode<E>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/domain",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Places, CLRG regions, school names, ages, dances, competition titles and schedules, and a person's print in its role, as they are printed: read, compared and told apart",
|
|
3
|
+
"version": "0.51.0",
|
|
4
|
+
"description": "Places, CLRG regions, school names, ages, dances, competition titles, codes and schedules, and a person's print in its role, as they are printed: read, compared and told apart",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"places",
|
|
7
7
|
"regions",
|
|
@@ -73,6 +73,10 @@
|
|
|
73
73
|
"types": "./dist/types/schedule.d.ts",
|
|
74
74
|
"default": "./dist/schedule.js"
|
|
75
75
|
},
|
|
76
|
+
"./code": {
|
|
77
|
+
"types": "./dist/types/code.d.ts",
|
|
78
|
+
"default": "./dist/code.js"
|
|
79
|
+
},
|
|
76
80
|
"./package.json": "./package.json"
|
|
77
81
|
},
|
|
78
82
|
"files": ["dist/*.js", "dist/chunks/*.js", "dist/types/**/*.d.ts", "README.md", "LICENSE"],
|
|
@@ -89,7 +93,7 @@
|
|
|
89
93
|
"prepublishOnly": "bun scripts/build.ts && bun scripts/guard-tarball.ts"
|
|
90
94
|
},
|
|
91
95
|
"dependencies": {
|
|
92
|
-
"@finbheara/names": "0.
|
|
96
|
+
"@finbheara/names": "0.25.0"
|
|
93
97
|
},
|
|
94
98
|
"devDependencies": {
|
|
95
99
|
"typescript": "7.0.2"
|