@finbheara/domain 0.48.0 → 0.50.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 CHANGED
@@ -49,7 +49,7 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
49
49
  | `@finbheara/domain/dance-types` | `danceTypes()`, `DANCE_TYPES`, `rhythmOfTime`, and the shared `DanceMatch`/`DanceRelation`/`SetRhythm`/`TimeSignature` types |
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
- | `@finbheara/domain/title` | `compTitle`, `titleRelation`, `matchTitle`, `ladderOf`, `populationIn`, `populationRelation`, `roundNumbersIn`: a competition title read and matched onto syllabus entries (below) |
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
53
  | `@finbheara/domain/schedule` | `scheduleAt`, `sameHall`, `readRunningOrder`, the `ScheduleSlot` type: which entries a hall runs (below) |
54
54
 
55
55
  The library does no I/O and installs nothing process-wide. Every reader has a default and a
@@ -426,10 +426,34 @@ compTitle("Treble Reel Special").invalidReason(); // "no-age"
426
426
  A spoken dash is read as a space: `boys u-17`, `Boys U-17` and `boys u- 17` read as `boys u17`,
427
427
  `girls o-16` as `girls o16`, and `under-15` as `under 15`.
428
428
 
429
+ A count printed before `Rounds` is how many rounds are danced, and the number after it is the
430
+ band: `Ladies Champion 3 Rounds 18 & Over` reads `[18,∞)` and no round, and `2 Rounds 1 & 2`
431
+ lists the two rounds it counts. A number an and-over closes (`18 & Over`, `18&O`, `18+`) is the
432
+ band, so `Round 2 - 18&O` is round 2, and so is the number after an and-over word (`Rounds 1-3
433
+ Over 18` is rounds 1 and 3). An and-over word with no connector pops nothing: `Round 2 Plus` is
434
+ round 2.
435
+
429
436
  A band split into parallel competitions (`Girls Under 13 A`, `Girls Under 13 B`) answers every
430
437
  split, unless the title prints the letter. A title naming no band names nothing. Hand in the
431
438
  entries of the kind you mean: a solo championship's ages are often a team event's too.
432
439
 
440
+ A code may carry the sex where the title does not. `codePopulation` reads a suffix against every
441
+ code the same event printed, as a letter alone also marks sections and merges: `B` beside `G` of
442
+ the same code (and no `A` or `C` of it) is the boys' and the girls' half, and a trailing `M` is
443
+ boys only beside the code without it. `secondIn` says whether a print names a second chance or a
444
+ supplementary competition beside the main one, from the words alone (`2nd Chance`, `2nd Chc`,
445
+ `Second Chance`, `Supplementary`, `2nd` before `Open` or `Prelim`), never from a code's digit.
446
+
447
+ ```ts
448
+ import { codePopulation, secondIn } from "@finbheara/domain/title";
449
+
450
+ codePopulation("720OCB", ["720OCB", "720OCG"]); // "boys"
451
+ codePopulation("512M", ["512", "512M"]); // "boys"
452
+ codePopulation("713OCM", ["713OCM", "714OC"]); // "": alone, it may be a merge
453
+ secondIn("Preliminary Champion, Supplementary Prelim (U15)"); // true
454
+ secondIn("Preliminary Champion, Hard Shoe (U10)"); // false
455
+ ```
456
+
433
457
  ## Ask a schedule
434
458
 
435
459
  A split band's competitions share their card numbers, so a card heard on a stage fits each of
@@ -24,11 +24,48 @@ function populationIn2(text) {
24
24
  return "boys";
25
25
  return "";
26
26
  }
27
+ function codePopulation2(code, codes) {
28
+ const c = code.trim().toUpperCase();
29
+ const all = new Set;
30
+ for (const x of codes)
31
+ all.add(x.trim().toUpperCase());
32
+ all.add(c);
33
+ if (!/\d/.test(c))
34
+ return "";
35
+ const pair = /^(.*\d[A-Z]*?)([BG])$/.exec(c);
36
+ if (pair !== null) {
37
+ const [, stem, letter] = pair;
38
+ const other = letter === "B" ? "G" : "B";
39
+ if (all.has(stem + other) && !all.has(`${stem}A`) && !all.has(`${stem}C`))
40
+ return letter === "B" ? "boys" : "girls";
41
+ }
42
+ const male = /^(.*\d[A-Z]*?)-?M$/.exec(c);
43
+ if (male !== null && all.has(male[1]))
44
+ return "boys";
45
+ return "";
46
+ }
47
+ var SECOND_WORDS = /\b(?:(?:2nd|second)[\s-]*ch(?:ance|c)?\b|supplementary\b|(?:2nd|second)\s+(?:open|prelim|preliminary)\b)/;
48
+ function secondIn2(text) {
49
+ return SECOND_WORDS.test(toAscii(text).toLowerCase());
50
+ }
51
+ var ROUND_LIST = / (?:(\d+) )?(rounds?) ((?:\d+ (?:and )?)+)/g;
27
52
  function roundNumbersIn2(folded, printed) {
28
53
  const out = new Set;
29
- for (const m of folded.matchAll(/ rounds? ((?:\d+ (?:and )?)+)/g))
30
- for (const n of m[1].match(/\d+/g) ?? [])
31
- out.add(Number(n));
54
+ for (const m of folded.matchAll(ROUND_LIST)) {
55
+ const list = m[3];
56
+ const after = folded.slice((m.index ?? 0) + m[0].length);
57
+ const ns = (list.match(/\d+/g) ?? []).map(Number);
58
+ const andOver = list.endsWith(" and ") && /^(?:over|o) /.test(after);
59
+ if (andOver || after.startsWith("+ "))
60
+ ns.pop();
61
+ if (m[1] !== undefined && m[2] === "rounds") {
62
+ const count = Number(m[1]);
63
+ if (ns.length < 2 || ns.some((n) => n < 1 || n > count))
64
+ continue;
65
+ }
66
+ for (const n of ns)
67
+ out.add(n);
68
+ }
32
69
  for (const m of folded.matchAll(/ r([1-9])(?= )/g))
33
70
  out.add(Number(m[1]));
34
71
  if (printed)
@@ -36,8 +73,8 @@ function roundNumbersIn2(folded, printed) {
36
73
  out.add(Number(m[1]));
37
74
  return out;
38
75
  }
39
- function roundFold(print) {
40
- return ` ${toAscii(print).toLowerCase().replace(/(\d)(\p{L})/gu, "$1 $2").replace(/[-–—,/()&+:;|.']/g, " ").replace(/\s+/g, " ").trim()} `;
76
+ function roundFold2(print) {
77
+ return ` ${toAscii(print).toLowerCase().replace(/(\d)(\p{L})/gu, "$1 $2").replace(/&/g, " and ").replace(/\+/g, " + ").replace(/[-–—,/():;|.']/g, " ").replace(/\s+/g, " ").trim()} `;
41
78
  }
42
79
  var SPLIT = / ([a-d]) $|(?:\d|girls?|boys?|lad(?:y|ies)|m[ae]n|mixed) ([a-d])(?= )/;
43
80
  var spoken = (print) => print.replace(/\b(under|over|u|o)-(?=[ \t]*\d)/giu, "$1 ");
@@ -52,7 +89,7 @@ function read(print, ctx, agePrint) {
52
89
  age = t.age;
53
90
  ageSpan = t.span;
54
91
  }
55
- const folded = roundFold(print);
92
+ const folded = roundFold2(print);
56
93
  const rounds = [...roundNumbersIn2(folded, / rounds? /.test(folded))].sort((a, b) => a - b);
57
94
  const split = SPLIT.exec(folded);
58
95
  const empty = print.trim() === "" && (agePrint ?? "").trim() === "";
@@ -136,4 +173,4 @@ function matchTitle2(print, entries, ctx = {}) {
136
173
  });
137
174
  }
138
175
 
139
- export { populationIn2, roundNumbersIn2, compTitle2, populationRelation2, titleRelation2, ladderOf2, matchTitle2 };
176
+ export { populationIn2, codePopulation2, secondIn2, roundNumbersIn2, roundFold2, compTitle2, populationRelation2, titleRelation2, ladderOf2, matchTitle2 };
package/dist/schedule.js CHANGED
@@ -2,7 +2,7 @@ import"./chunks/place-kmh59zkw.js";
2
2
  import {
3
3
  compTitle2,
4
4
  matchTitle2
5
- } from "./chunks/place-cdghn4v2.js";
5
+ } from "./chunks/place-7jfd4m3j.js";
6
6
 
7
7
  function hallKey(hall) {
8
8
  return hall.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
package/dist/title.js CHANGED
@@ -1,19 +1,25 @@
1
1
  import {
2
2
  populationIn2,
3
+ codePopulation2,
4
+ secondIn2,
3
5
  roundNumbersIn2,
6
+ roundFold2,
4
7
  compTitle2,
5
8
  populationRelation2,
6
9
  titleRelation2,
7
10
  ladderOf2,
8
11
  matchTitle2
9
- } from "./chunks/place-cdghn4v2.js";
12
+ } from "./chunks/place-7jfd4m3j.js";
10
13
  import"./chunks/place-kmh59zkw.js";
11
14
  export {
15
+ codePopulation2 as codePopulation,
12
16
  compTitle2 as compTitle,
13
17
  ladderOf2 as ladderOf,
14
18
  matchTitle2 as matchTitle,
15
19
  populationIn2 as populationIn,
16
20
  populationRelation2 as populationRelation,
21
+ roundFold2 as roundFold,
17
22
  roundNumbersIn2 as roundNumbersIn,
23
+ secondIn2 as secondIn,
18
24
  titleRelation2 as titleRelation
19
25
  };
@@ -5,7 +5,10 @@ export type { EmptyReason, Validated } from "./validated.ts";
5
5
  export type { AgeComparison, AgeLadder } from "./age.ts";
6
6
  export type Population = "girls" | "boys" | "mixed";
7
7
  export declare function populationIn(text: string): Population | "";
8
+ export declare function codePopulation(code: string, codes: Iterable<string>): Population | "";
9
+ export declare function secondIn(text: string): boolean;
8
10
  export declare function roundNumbersIn(folded: string, printed: boolean): Set<number>;
11
+ export declare function roundFold(print: string): string;
9
12
  export interface TitleContext {
10
13
  readonly year?: number | null;
11
14
  readonly ladder?: AgeLadder;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/domain",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
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",
5
5
  "keywords": [
6
6
  "places",
@@ -89,7 +89,7 @@
89
89
  "prepublishOnly": "bun scripts/build.ts && bun scripts/guard-tarball.ts"
90
90
  },
91
91
  "dependencies": {
92
- "@finbheara/names": "0.22.0"
92
+ "@finbheara/names": "0.23.0"
93
93
  },
94
94
  "devDependencies": {
95
95
  "typescript": "7.0.2"