@finbheara/domain 0.38.0 → 0.40.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
@@ -45,7 +45,7 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
45
45
  | `@finbheara/domain/ceili` | `ceili()`, `ceiliRelation`, `CEILI_NODES`: the céilí handbook dances |
46
46
  | `@finbheara/domain/dance` | `dance()`: a print read as a tune and a céilí dance, narrowed by its round |
47
47
  | `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `Age.fromTitle`, `ageTitleToken`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, `intersectBirthYears`, `validYOB`, the age constants |
48
- | `@finbheara/domain/noun-index` | `createIndex`, `indexSchema`, `personEncoder`, and the `SqlHandle`/`NounEncoder` types: a database-backed name map (below) |
48
+ | `@finbheara/domain/noun-index` | `createIndex`, `indexSchema`, `personEncoder`, `roleEncoder`, `roleSchoolGlob`, and the `SqlHandle`/`NounEncoder` types: a database-backed name map (below) |
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) |
@@ -512,6 +512,28 @@ schools.nouns(11).map((s) => s.sn0); // ["Ardvarna Academy", "
512
512
  schools.get(read("Ardvarna Academy of Irish Dance (Calgary, AB)"), "sn3"); // [10]
513
513
  ```
514
514
 
515
+ An adjudicator and a teacher are one type, a `RolePerson`: a name read in its role, the
516
+ person's number in the register that lists them (`null` where none does), the role, and the
517
+ ids of the schools the person is filed at. `roleEncoder` stores one as `personEncoder`'s
518
+ columns plus `id`, `role` and `schools`. Its levels are `nn0`-`nn4` and `id`, and `id` is
519
+ indexed. The name is re-read with `roleName`, so a print on a role's not-a-name list comes
520
+ back refused. `schools` is one column, and `roleSchoolGlob(school)` matches a row filed at
521
+ that school, the whole id and never a part of another:
522
+
523
+ ```ts
524
+ import { roleEncoder, roleSchoolGlob } from "@finbheara/domain/noun-index";
525
+ import { seatName, teacherName } from "@finbheara/domain/person";
526
+
527
+ const staff = createIndex(db, roleEncoder(), { table: "staff" }); // after its indexSchema
528
+ staff.add({ name: seatName("Aoife Ni Mhaolvarna"), id: "1001", role: "adjudicator", schools: ["s1", "s10"] }, 1);
529
+ staff.add({ name: teacherName("Kestrel Evbatyrov"), id: null, role: "teacher", schools: ["s10"] }, 2);
530
+
531
+ staff.find("id", "1001"); // [1]
532
+ staff.get({ name: teacherName("AOIFE NÍ MHAOLVARNA"), id: null, role: "teacher", schools: [] }, "nn3"); // [1]
533
+ staff.glob("schools", roleSchoolGlob("s10")).map((r) => r.value); // [1, 2]
534
+ staff.nouns(1)[0]?.schools; // ["s1", "s10"]
535
+ ```
536
+
515
537
  `rows({ column: keys, ... })` answers every row where any column holds one of its keys, and
516
538
  `glob(column, pattern)` a SQLite `GLOB` on one column. An encoder is a plain object: add a
517
539
  column of your own by spreading one, `{ ...enc, columns: [...enc.columns, "k"], encode: (x) =>
package/dist/age.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  printsUnderOpen2,
3
+ printsAdultCode2,
3
4
  TITLE_RANGE_MIN2,
4
5
  TITLE_RANGE_MAX2,
5
6
  ageTitleToken2,
7
+ readsSeniorAdult2,
6
8
  adultBeside2,
7
9
  YOUNGEST_FLOOR2,
8
10
  ADULT_FLOOR2,
@@ -37,7 +39,7 @@ import {
37
39
  youngFloor2,
38
40
  tooYoungForMajor2,
39
41
  tooYoungForFeis2
40
- } from "./chunks/place-3w1mr2vh.js";
42
+ } from "./chunks/place-mpeps8tn.js";
41
43
  export {
42
44
  ADULT_FLOOR2 as ADULT_FLOOR,
43
45
  AVERAGE_AGE_DECIMALS2 as AVERAGE_AGE_DECIMALS,
@@ -67,7 +69,9 @@ export {
67
69
  canOverlap2 as canOverlap,
68
70
  intersectBirthYears2 as intersectBirthYears,
69
71
  isAveraged2 as isAveraged,
72
+ printsAdultCode2 as printsAdultCode,
70
73
  printsUnderOpen2 as printsUnderOpen,
74
+ readsSeniorAdult2 as readsSeniorAdult,
71
75
  roundedMeanHundredths2 as roundedMeanHundredths,
72
76
  tooYoungForFeis2 as tooYoungForFeis,
73
77
  tooYoungForMajor2 as tooYoungForMajor,
@@ -0,0 +1,122 @@
1
+ import {
2
+ SHARED_ROW_SENTINELS2,
3
+ sentinelIn2
4
+ } from "./place-tcgfvemw.js";
5
+
6
+ import {
7
+ NAME_REFUSED,
8
+ isNoNamePrint,
9
+ personName
10
+ } from "@finbheara/names/normalize";
11
+ var ADJUDICATOR_SENTINELS2 = [
12
+ {
13
+ print: "withdrawn adjudicator",
14
+ authority: "`Withdrawn Adjudicator`: 180 seat cells, one panel sheet's empty seat"
15
+ },
16
+ {
17
+ print: "no school judge",
18
+ authority: "`No School Judge`: an entry form's option for an adjudicator's entry"
19
+ }
20
+ ];
21
+ var DANCER_SENTINELS2 = [
22
+ {
23
+ print: "name withheld",
24
+ authority: "`Name Withheld`: 136 name cells, the entry platform printed that the name was withheld"
25
+ },
26
+ {
27
+ print: "test dancer",
28
+ authority: "`Test Dancer`: 7 name cells, an entry platform's test entry"
29
+ },
30
+ {
31
+ print: "none none",
32
+ authority: "`none none`: 4 name cells, the sentinel `none` typed into both name fields"
33
+ },
34
+ {
35
+ print: "not known",
36
+ authority: "`Not known`: 1 name cell"
37
+ },
38
+ {
39
+ print: "don t know",
40
+ authority: "`Don't Know`: 1 name cell"
41
+ }
42
+ ];
43
+ var TEACHER_SENTINELS2 = [];
44
+ var ROLE_SENTINELS2 = {
45
+ adjudicator: [...SHARED_ROW_SENTINELS2, ...ADJUDICATOR_SENTINELS2],
46
+ dancer: [...SHARED_ROW_SENTINELS2, ...DANCER_SENTINELS2],
47
+ teacher: [...SHARED_ROW_SENTINELS2, ...TEACHER_SENTINELS2]
48
+ };
49
+ var MATCHERS = {
50
+ adjudicator: sentinelIn2(ROLE_SENTINELS2.adjudicator),
51
+ dancer: sentinelIn2(ROLE_SENTINELS2.dancer),
52
+ teacher: sentinelIn2(ROLE_SENTINELS2.teacher)
53
+ };
54
+ function roleSentinelOf2(role, print) {
55
+ return MATCHERS[role](print);
56
+ }
57
+ var NO_PART = Object.freeze({ particles: [], base: "", parts: [], tradition: null });
58
+
59
+ class RoleRefusedName {
60
+ [Symbol.toStringTag] = "PersonName";
61
+ nn0;
62
+ nn1;
63
+ nn2 = NAME_REFUSED;
64
+ nn3 = NAME_REFUSED;
65
+ nn4 = NAME_REFUSED;
66
+ surname = NO_PART;
67
+ given = [];
68
+ suffixes = [];
69
+ refusal;
70
+ verdict;
71
+ role;
72
+ sentinel;
73
+ constructor(read, role, sentinel) {
74
+ this.role = role;
75
+ this.sentinel = sentinel;
76
+ this.nn0 = read.nn0;
77
+ this.nn1 = read.nn1;
78
+ this.refusal = { rule: "placeholder", printed: read.nn0, value: NAME_REFUSED };
79
+ this.verdict = { kind: "refused", refusal: this.refusal, rules: read.verdict.rules };
80
+ }
81
+ toString() {
82
+ return this.nn2;
83
+ }
84
+ get length() {
85
+ return this.nn2.length;
86
+ }
87
+ surnameKey() {
88
+ return "";
89
+ }
90
+ givenFields() {
91
+ return [];
92
+ }
93
+ givenName() {
94
+ return "";
95
+ }
96
+ isTeam() {
97
+ return false;
98
+ }
99
+ isPerson() {
100
+ return false;
101
+ }
102
+ isEmpty() {
103
+ return false;
104
+ }
105
+ printsNoName() {
106
+ return true;
107
+ }
108
+ }
109
+ function roleName2(role, print, order, factory) {
110
+ const read = factory === undefined ? personName(print, order) : factory.parse(print, order);
111
+ const sentinel = MATCHERS[role](print);
112
+ return sentinel === undefined ? read : new RoleRefusedName(read, role, sentinel);
113
+ }
114
+ function rolePrintsNoName2(role, print) {
115
+ return MATCHERS[role](print) !== undefined || isNoNamePrint(print);
116
+ }
117
+ var dancerPrintsNoName2 = (print) => rolePrintsNoName2("dancer", print);
118
+ var seatName2 = (print) => roleName2("adjudicator", print, "given-first");
119
+ var dancerName2 = (print, order, factory) => roleName2("dancer", print, order, factory);
120
+ var teacherName2 = (print, order) => roleName2("teacher", print, order);
121
+
122
+ export { ADJUDICATOR_SENTINELS2, DANCER_SENTINELS2, TEACHER_SENTINELS2, ROLE_SENTINELS2, roleSentinelOf2, roleName2, rolePrintsNoName2, dancerPrintsNoName2, seatName2, dancerName2, teacherName2 };
@@ -26,6 +26,7 @@ var RANGE_DASH = String.raw`(?:\u00c2?[-\u0096–—]|\bto\b)`;
26
26
  var RANGE = new RegExp(String.raw`(?<![0-9])(?<![0-9][a-z]*)(?<!(?:^|[^a-z0-9])[a-z])(?:u\s*)?(\d{1,2})\s*${RANGE_DASH}\s*(\d{1,2})(?![a-z0-9])`, "i");
27
27
  var LEAD_RANGE = new RegExp(String.raw`^\s*([1-9]\d?)\s*${RANGE_DASH}\s*([1-9]\d?)(?![a-z0-9])(?!\s*[/&])`, "i");
28
28
  var ADULT_CODE = /^\s*a(?:(\d{1,2})(u|o|&o))?\s*$/i;
29
+ var printsAdultCode2 = (phrase) => ADULT_CODE.test(phrase);
29
30
  var ADULT = /(?<!non[\s-])(?<!-)\b(?:adults?|advanced\s+adults?|masters?)\b/i;
30
31
  var SENIOR = /(?<!-)\bseniors?\b/i;
31
32
  var TITLE_RANGE_MIN2 = 3;
@@ -151,6 +152,10 @@ function ageTitleToken2(title) {
151
152
  }
152
153
  var ADULT_UNDER_MIN = 18;
153
154
  var SENIOR_ADULT_OVER = /\bsenior\s+adult\b/i;
155
+ var SENIOR_ADULT_ENDS = new RegExp(`${SENIOR_ADULT_OVER.source}$`, "i");
156
+ function readsSeniorAdult2(title, span) {
157
+ return SENIOR_ADULT_ENDS.test(title.slice(0, span.end));
158
+ }
154
159
  function adultBeside2(band) {
155
160
  const b = band.trim().toLowerCase();
156
161
  if (b === "")
@@ -662,4 +667,4 @@ function youngFloor2(year, oldest) {
662
667
  var tooYoungForMajor2 = (year, oldest) => yearsSinceBirth2(year, oldest) < MIN_MAJOR_AGE2;
663
668
  var tooYoungForFeis2 = (year, oldest) => yearsSinceBirth2(year, oldest) < MIN_FEIS_AGE2;
664
669
 
665
- export { printsUnderOpen2, TITLE_RANGE_MIN2, TITLE_RANGE_MAX2, ageTitleToken2, adultBeside2, YOUNGEST_FLOOR2, ADULT_FLOOR2, MAJORS_FLOOR_YEARS2, UNDER_CAP2, UNDER_CAP_MAX_TOP2, YOUNG_FLOOR2, MIN_MAJOR_AGE2, MIN_FEIS_AGE2, YOUNGEST_BAND_TOP2, OLDEST_BAND_FLOOR2, YOB_RANGE2, DANCE_UP_YEARS2, ageOnJanuary12, yearsSinceBirth2, AgeMisuseError2, Age2, birthYearsOfInterval2, ageRelation2, canOverlap2, AVERAGE_AGE_DECIMALS2, roundedMeanHundredths2, isAveraged2, averageAge2, birthYearsDisjoint2, intersectBirthYears2, birthYearsMeet2, underCapApplies2, underCappedYears2, underCapRefuses2, validYOB2, youngFloor2, tooYoungForMajor2, tooYoungForFeis2 };
670
+ export { printsUnderOpen2, printsAdultCode2, TITLE_RANGE_MIN2, TITLE_RANGE_MAX2, ageTitleToken2, readsSeniorAdult2, adultBeside2, YOUNGEST_FLOOR2, ADULT_FLOOR2, MAJORS_FLOOR_YEARS2, UNDER_CAP2, UNDER_CAP_MAX_TOP2, YOUNG_FLOOR2, MIN_MAJOR_AGE2, MIN_FEIS_AGE2, YOUNGEST_BAND_TOP2, OLDEST_BAND_FLOOR2, YOB_RANGE2, DANCE_UP_YEARS2, ageOnJanuary12, yearsSinceBirth2, AgeMisuseError2, Age2, birthYearsOfInterval2, ageRelation2, canOverlap2, AVERAGE_AGE_DECIMALS2, roundedMeanHundredths2, isAveraged2, averageAge2, birthYearsDisjoint2, intersectBirthYears2, birthYearsMeet2, underCapApplies2, underCappedYears2, underCapRefuses2, validYOB2, youngFloor2, tooYoungForMajor2, tooYoungForFeis2 };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  Age2,
3
3
  ageRelation2
4
- } from "./place-3w1mr2vh.js";
4
+ } from "./place-mpeps8tn.js";
5
5
  import {
6
6
  validity
7
7
  } from "./place-q9kwsmzd.js";
@@ -1,3 +1,8 @@
1
+ import"./chunks/place-tcgfvemw.js";
2
+ import {
3
+ roleName2
4
+ } from "./chunks/place-4dkz48q6.js";
5
+
1
6
  import {
2
7
  NAME_COLUMNS,
3
8
  NAME_LEVEL_COLUMNS,
@@ -226,8 +231,61 @@ function personEncoder(options = {}) {
226
231
  }
227
232
  };
228
233
  }
234
+ var STAFF_ROLES = ["adjudicator", "teacher"];
235
+ function roleEncoder(options = {}) {
236
+ const read = options.read ?? ((role, p, o) => roleName2(role, p, o));
237
+ const named = (role) => personEncoder({
238
+ read: (p, o) => read(role, p, o),
239
+ print: options.print,
240
+ reading: options.reading
241
+ });
242
+ const names = {
243
+ adjudicator: named("adjudicator"),
244
+ teacher: named("teacher")
245
+ };
246
+ const roleOf = (r) => {
247
+ if (STAFF_ROLES.includes(r))
248
+ return r;
249
+ throw new Error(`roleEncoder: ${JSON.stringify(r)} is not a role`);
250
+ };
251
+ const base = names.teacher;
252
+ return {
253
+ columns: [...base.columns, "id", "role", "schools"],
254
+ levels: [...base.levels, "id"],
255
+ indexed: [...base.indexed, "id"],
256
+ encode(x) {
257
+ for (const s of x.schools)
258
+ if (typeof s !== "string" || s === "")
259
+ throw new Error(`roleEncoder: ${JSON.stringify(s)} is not a school id`);
260
+ return {
261
+ ...names[roleOf(x.role)].encode(x.name),
262
+ id: x.id,
263
+ role: x.role,
264
+ schools: x.schools.length === 0 ? null : JSON.stringify(x.schools)
265
+ };
266
+ },
267
+ key: (x, level) => level === "id" ? x.id : nameColumns(x.name)[level],
268
+ decode(row) {
269
+ const role = roleOf(row.role);
270
+ const schools = row.schools ?? null;
271
+ return {
272
+ name: names[role].decode(row),
273
+ id: row.id ?? null,
274
+ role,
275
+ schools: schools === null ? [] : JSON.parse(schools)
276
+ };
277
+ }
278
+ };
279
+ }
280
+ var globLiteral = (s) => s.replace(/[*?[]/gu, (c) => `[${c}]`);
281
+ function roleSchoolGlob(school) {
282
+ return `*[[,]${globLiteral(JSON.stringify(school))}[],]*`;
283
+ }
229
284
  export {
285
+ STAFF_ROLES,
230
286
  createIndex,
231
287
  indexSchema,
232
- personEncoder
288
+ personEncoder,
289
+ roleEncoder,
290
+ roleSchoolGlob
233
291
  };
package/dist/person.js CHANGED
@@ -1,135 +1,32 @@
1
+ import {
2
+ ADJUDICATOR_SENTINELS2,
3
+ DANCER_SENTINELS2,
4
+ TEACHER_SENTINELS2,
5
+ ROLE_SENTINELS2,
6
+ roleSentinelOf2,
7
+ roleName2,
8
+ rolePrintsNoName2,
9
+ dancerPrintsNoName2,
10
+ seatName2,
11
+ dancerName2,
12
+ teacherName2
13
+ } from "./chunks/place-4dkz48q6.js";
1
14
  import {
2
15
  SHARED_ROW_SENTINELS2,
3
16
  sentinelIn2
4
17
  } from "./chunks/place-tcgfvemw.js";
5
-
6
- import {
7
- NAME_REFUSED,
8
- isNoNamePrint,
9
- personName
10
- } from "@finbheara/names/normalize";
11
- var ADJUDICATOR_SENTINELS = [
12
- {
13
- print: "withdrawn adjudicator",
14
- authority: "`Withdrawn Adjudicator`: 180 seat cells, one panel sheet's empty seat"
15
- },
16
- {
17
- print: "no school judge",
18
- authority: "`No School Judge`: an entry form's option for an adjudicator's entry"
19
- }
20
- ];
21
- var DANCER_SENTINELS = [
22
- {
23
- print: "name withheld",
24
- authority: "`Name Withheld`: 136 name cells, the entry platform printed that the name was withheld"
25
- },
26
- {
27
- print: "test dancer",
28
- authority: "`Test Dancer`: 7 name cells, an entry platform's test entry"
29
- },
30
- {
31
- print: "none none",
32
- authority: "`none none`: 4 name cells, the sentinel `none` typed into both name fields"
33
- },
34
- {
35
- print: "not known",
36
- authority: "`Not known`: 1 name cell"
37
- },
38
- {
39
- print: "don t know",
40
- authority: "`Don't Know`: 1 name cell"
41
- }
42
- ];
43
- var TEACHER_SENTINELS = [];
44
- var ROLE_SENTINELS = {
45
- adjudicator: [...SHARED_ROW_SENTINELS2, ...ADJUDICATOR_SENTINELS],
46
- dancer: [...SHARED_ROW_SENTINELS2, ...DANCER_SENTINELS],
47
- teacher: [...SHARED_ROW_SENTINELS2, ...TEACHER_SENTINELS]
48
- };
49
- var MATCHERS = {
50
- adjudicator: sentinelIn2(ROLE_SENTINELS.adjudicator),
51
- dancer: sentinelIn2(ROLE_SENTINELS.dancer),
52
- teacher: sentinelIn2(ROLE_SENTINELS.teacher)
53
- };
54
- function roleSentinelOf(role, print) {
55
- return MATCHERS[role](print);
56
- }
57
- var NO_PART = Object.freeze({ particles: [], base: "", parts: [], tradition: null });
58
-
59
- class RoleRefusedName {
60
- [Symbol.toStringTag] = "PersonName";
61
- nn0;
62
- nn1;
63
- nn2 = NAME_REFUSED;
64
- nn3 = NAME_REFUSED;
65
- nn4 = NAME_REFUSED;
66
- surname = NO_PART;
67
- given = [];
68
- suffixes = [];
69
- refusal;
70
- verdict;
71
- role;
72
- sentinel;
73
- constructor(read, role, sentinel) {
74
- this.role = role;
75
- this.sentinel = sentinel;
76
- this.nn0 = read.nn0;
77
- this.nn1 = read.nn1;
78
- this.refusal = { rule: "placeholder", printed: read.nn0, value: NAME_REFUSED };
79
- this.verdict = { kind: "refused", refusal: this.refusal, rules: read.verdict.rules };
80
- }
81
- toString() {
82
- return this.nn2;
83
- }
84
- get length() {
85
- return this.nn2.length;
86
- }
87
- surnameKey() {
88
- return "";
89
- }
90
- givenFields() {
91
- return [];
92
- }
93
- givenName() {
94
- return "";
95
- }
96
- isTeam() {
97
- return false;
98
- }
99
- isPerson() {
100
- return false;
101
- }
102
- isEmpty() {
103
- return false;
104
- }
105
- printsNoName() {
106
- return true;
107
- }
108
- }
109
- function roleName(role, print, order, factory) {
110
- const read = factory === undefined ? personName(print, order) : factory.parse(print, order);
111
- const sentinel = MATCHERS[role](print);
112
- return sentinel === undefined ? read : new RoleRefusedName(read, role, sentinel);
113
- }
114
- function rolePrintsNoName(role, print) {
115
- return MATCHERS[role](print) !== undefined || isNoNamePrint(print);
116
- }
117
- var dancerPrintsNoName = (print) => rolePrintsNoName("dancer", print);
118
- var seatName = (print) => roleName("adjudicator", print, "given-first");
119
- var dancerName = (print, order, factory) => roleName("dancer", print, order, factory);
120
- var teacherName = (print, order) => roleName("teacher", print, order);
121
18
  export {
122
- ADJUDICATOR_SENTINELS,
123
- DANCER_SENTINELS,
124
- ROLE_SENTINELS,
19
+ ADJUDICATOR_SENTINELS2 as ADJUDICATOR_SENTINELS,
20
+ DANCER_SENTINELS2 as DANCER_SENTINELS,
21
+ ROLE_SENTINELS2 as ROLE_SENTINELS,
125
22
  SHARED_ROW_SENTINELS2 as SHARED_ROW_SENTINELS,
126
- TEACHER_SENTINELS,
127
- dancerName,
128
- dancerPrintsNoName,
129
- roleName,
130
- rolePrintsNoName,
131
- roleSentinelOf,
132
- seatName,
23
+ TEACHER_SENTINELS2 as TEACHER_SENTINELS,
24
+ dancerName2 as dancerName,
25
+ dancerPrintsNoName2 as dancerPrintsNoName,
26
+ roleName2 as roleName,
27
+ rolePrintsNoName2 as rolePrintsNoName,
28
+ roleSentinelOf2 as roleSentinelOf,
29
+ seatName2 as seatName,
133
30
  sentinelIn2 as sentinelIn,
134
- teacherName
31
+ teacherName2 as teacherName
135
32
  };
package/dist/schedule.js CHANGED
@@ -1,8 +1,8 @@
1
- import"./chunks/place-3w1mr2vh.js";
1
+ import"./chunks/place-mpeps8tn.js";
2
2
  import {
3
3
  compTitle2,
4
4
  matchTitle2
5
- } from "./chunks/place-36esw9p7.js";
5
+ } from "./chunks/place-y66f4138.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
@@ -6,8 +6,8 @@ import {
6
6
  titleRelation2,
7
7
  ladderOf2,
8
8
  matchTitle2
9
- } from "./chunks/place-36esw9p7.js";
10
- import"./chunks/place-3w1mr2vh.js";
9
+ } from "./chunks/place-y66f4138.js";
10
+ import"./chunks/place-mpeps8tn.js";
11
11
  export {
12
12
  compTitle2 as compTitle,
13
13
  ladderOf2 as ladderOf,
@@ -1,4 +1,5 @@
1
1
  export declare const printsUnderOpen: (print: string) => boolean;
2
+ export declare const printsAdultCode: (phrase: string) => boolean;
2
3
  export declare const TITLE_RANGE_MIN = 3;
3
4
  export declare const TITLE_RANGE_MAX = 40;
4
5
  export interface AgeTitleSpan {
@@ -11,4 +12,5 @@ export interface AgeTitleToken {
11
12
  readonly span: AgeTitleSpan | null;
12
13
  }
13
14
  export declare function ageTitleToken(title: string): AgeTitleToken;
15
+ export declare function readsSeniorAdult(title: string, span: AgeTitleSpan): boolean;
14
16
  export declare function adultBeside(band: string): string | undefined;
@@ -1,7 +1,7 @@
1
1
  import { type AgeTitleSpan } from "./age-title.ts";
2
2
  import type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, AgeSpan, BirthYears, YOBContext, YoungFloorVerdict } from "./age-types.ts";
3
3
  import { type EmptyReason, type Validated } from "./validated.ts";
4
- export { type AgeTitleSpan, type AgeTitleToken, TITLE_RANGE_MAX, TITLE_RANGE_MIN, adultBeside, ageTitleToken, printsUnderOpen, } from "./age-title.ts";
4
+ export { type AgeTitleSpan, type AgeTitleToken, TITLE_RANGE_MAX, TITLE_RANGE_MIN, adultBeside, ageTitleToken, printsAdultCode, printsUnderOpen, readsSeniorAdult, } from "./age-title.ts";
5
5
  export type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, AgeSpan, BirthYears, EmptyReason, Validated, YOBContext, YoungFloorVerdict, };
6
6
  export declare const YOUNGEST_FLOOR = 0;
7
7
  export declare const ADULT_FLOOR = 18;
@@ -1,4 +1,5 @@
1
1
  import { type PersonName, type ResolvedNameOrder, type TransitiveLevel } from "@finbheara/names/normalize";
2
+ import { type PersonRole } from "./person.ts";
2
3
  export type SqlValue = string | number | bigint | null;
3
4
  export interface SqlStatement {
4
5
  run(...params: SqlValue[]): unknown;
@@ -65,3 +66,20 @@ export interface PersonEncoderOptions {
65
66
  readonly reading?: string;
66
67
  }
67
68
  export declare function personEncoder(options?: PersonEncoderOptions): NounEncoder<PersonName, TransitiveLevel>;
69
+ export type StaffRole = Extract<PersonRole, "adjudicator" | "teacher">;
70
+ export declare const STAFF_ROLES: readonly StaffRole[];
71
+ export interface RolePerson {
72
+ readonly name: PersonName;
73
+ readonly id: string | null;
74
+ readonly role: StaffRole;
75
+ readonly schools: readonly string[];
76
+ }
77
+ export type RoleLevel = TransitiveLevel | "id";
78
+ export type RoleReader = (role: StaffRole, print: string, order: ResolvedNameOrder) => PersonName;
79
+ export interface RoleEncoderOptions {
80
+ readonly read?: RoleReader;
81
+ readonly print?: string;
82
+ readonly reading?: string;
83
+ }
84
+ export declare function roleEncoder(options?: RoleEncoderOptions): NounEncoder<RolePerson, RoleLevel>;
85
+ export declare function roleSchoolGlob(school: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/domain",
3
- "version": "0.38.0",
3
+ "version": "0.40.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",