@finbheara/domain 0.2.0 → 0.4.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 +76 -1
- package/dist/age.js +55 -13
- package/dist/ceili.js +2 -2
- package/dist/chunks/{place-7gqecaxd.js → place-0ehc2q69.js} +1 -1
- package/dist/chunks/{place-kr42294h.js → place-2ja9yx08.js} +1 -1
- package/dist/chunks/{place-g6yw4cq6.js → place-htxgptyv.js} +1 -0
- package/dist/dance-types.js +1 -1
- package/dist/dance.js +3 -3
- package/dist/promotion.js +105 -0
- package/dist/tune.js +2 -2
- package/dist/types/age-types.d.ts +4 -1
- package/dist/types/age.d.ts +16 -4
- package/dist/types/promotion.d.ts +16 -0
- package/dist/types/validated.d.ts +4 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -39,9 +39,10 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
|
|
|
39
39
|
| `@finbheara/domain/tune` | `tune()`, `tuneRelation`, `tempoLegal`, `settingAt`, `TUNE_NODES`: the CLRG set dance list |
|
|
40
40
|
| `@finbheara/domain/ceili` | `ceili()`, `ceiliRelation`, `CEILI_NODES`: the céilí handbook dances |
|
|
41
41
|
| `@finbheara/domain/dance` | `dance()`: a print read as a tune and a céilí dance, narrowed by its round |
|
|
42
|
-
| `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, the age constants |
|
|
42
|
+
| `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, `intersectBirthYears`, the `Validated` type, the age constants |
|
|
43
43
|
| `@finbheara/domain/noun-index` | `createIndex`, `indexSchema`, `personEncoder`, and the `SqlHandle`/`NounEncoder` types: a database-backed name map (below) |
|
|
44
44
|
| `@finbheara/domain/dance-types` | `danceTypes()`, `DANCE_TYPES`, `rhythmOfTime`, and the shared `DanceMatch`/`DanceRelation`/`SetRhythm`/`TimeSignature` types |
|
|
45
|
+
| `@finbheara/domain/promotion` | `samePromotedRow`, `samePromotedSchool`, `samePromotedHeir`, `samePromotedName`, `questionMarksRead`, `foldCharSynonyms`: is a row the same row after a promotion (below) |
|
|
45
46
|
|
|
46
47
|
The library does no I/O and installs nothing process-wide. Every reader has a default and a
|
|
47
48
|
factory: build your own with options when the default is not what you want.
|
|
@@ -180,6 +181,19 @@ Age.fromYOB(1999).fitsIn(Age.from("Over 21", { year: 2026, top: true })); // tru
|
|
|
180
181
|
canOverlap(u12, Age.from("U14", { year: 2026 }), dancer); // true
|
|
181
182
|
```
|
|
182
183
|
|
|
184
|
+
A value the reader cannot stand behind is returned, not thrown. Every `Age` answers
|
|
185
|
+
`isValid()` and `invalidReason()`: an age group under `U3` admits nobody old enough to dance,
|
|
186
|
+
and a year of birth outside 1900 to 2099 is not one. An invalid `Age` has no birth years and
|
|
187
|
+
rules nothing out.
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
Age.from("U2", { year: 2026 }).isValid(); // false
|
|
191
|
+
Age.from("U2", { year: 2026 }).invalidReason(); // "below-youngest-band"
|
|
192
|
+
Age.fromYOB(2222).invalidReason(); // "year-out-of-range"
|
|
193
|
+
Age.from("All", { year: 2026 }).invalidReason(); // "not-a-band"
|
|
194
|
+
Age.from("", { year: 2026 }).isValid(); // true: empty, not invalid
|
|
195
|
+
```
|
|
196
|
+
|
|
183
197
|
A team's age is the average of its dancers' ages, rounded to two decimals. `averageAge`
|
|
184
198
|
takes exact ages only and returns an `AverageAge`, a separate type that refuses, by name,
|
|
185
199
|
any use that would treat it as one dancer's age.
|
|
@@ -264,6 +278,67 @@ column of your own by spreading one, `{ ...enc, columns: [...enc.columns, "k"],
|
|
|
264
278
|
type (`{ value: "id", valueType: "INTEGER PRIMARY KEY" }` makes the value the row id); on such a table `map(level)` iterates in value order, and a `set` to a value another row
|
|
265
279
|
holds is refused by the key).
|
|
266
280
|
|
|
281
|
+
## Decide whether a promoted row is the same row
|
|
282
|
+
|
|
283
|
+
A results sheet can be collected twice: first as a secondary view (an aggregator's copy), and
|
|
284
|
+
later as the primary transcript read from the publisher's own document, which supersedes it.
|
|
285
|
+
Whoever filed something against a row of the old view must then ask whether the row at the
|
|
286
|
+
same card of the new transcript is the same row. The producer that retires old rows and every
|
|
287
|
+
consumer that moves filed records onto successors must give the same answer, so the rule lives
|
|
288
|
+
here once. **A consumer re-deciding a superseded view's address must use these functions, not a
|
|
289
|
+
copy of them.**
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import {
|
|
293
|
+
samePromotedHeir,
|
|
294
|
+
samePromotedRow,
|
|
295
|
+
samePromotedSchool,
|
|
296
|
+
} from "@finbheara/domain/promotion";
|
|
297
|
+
|
|
298
|
+
samePromotedSchool("Brambleton : Mid Region", "Brambleton"); // true: a location tail
|
|
299
|
+
samePromotedSchool("Brambleton :", "Brambleton"); // true: a colon alone
|
|
300
|
+
samePromotedSchool("Brambleton Academy", "Brambleton"); // false
|
|
301
|
+
|
|
302
|
+
samePromotedRow(
|
|
303
|
+
{ name: "Foireann A", school: "Brambleton" },
|
|
304
|
+
{ name: "Team A", school: "Brambleton" },
|
|
305
|
+
); // true: one designation
|
|
306
|
+
|
|
307
|
+
samePromotedHeir(
|
|
308
|
+
{ name: "?ilis O?zumock", school: "Rinceoir? Na Zumock" },
|
|
309
|
+
{ name: "Éilis O'zumock", school: "Rinceoirí Na Zumock" },
|
|
310
|
+
); // true: `?` for one character
|
|
311
|
+
samePromotedHeir(
|
|
312
|
+
{ name: "LenaZumock", school: "Brambleton" },
|
|
313
|
+
{ name: "Lena (Xia) Zumock", school: "Brambleton" },
|
|
314
|
+
); // true: glued, an aside added
|
|
315
|
+
samePromotedHeir(
|
|
316
|
+
{ name: "?ilis Zumock", school: "Brambleton" },
|
|
317
|
+
{ name: "Éilis Rervton", school: "Brambleton" },
|
|
318
|
+
); // false: another surname
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
What counts as the same row:
|
|
322
|
+
|
|
323
|
+
- **Both prints go through the character fold first** (`foldCharSynonyms`): the apostrophe,
|
|
324
|
+
dash and space glyphs each to their lowest code point, then runs of whitespace as one space.
|
|
325
|
+
- **The school** (`samePromotedSchool`) is equal after the fold, or the old print is the new one
|
|
326
|
+
with an apostrophe restored, or followed by `:` and a location, or by `:` alone.
|
|
327
|
+
- **An ordinary promotion** (`samePromotedRow`): the school the same, and the name equal after
|
|
328
|
+
the fold, an apostrophe restored, or the same team designation.
|
|
329
|
+
- **The heir of a retired row** (`samePromotedHeir`), where the producer retired the old row and
|
|
330
|
+
named no successor: the name byte-identical, or the school the same and the name the same
|
|
331
|
+
person's (`samePromotedName`). A `?` in the old print stands for exactly one character, an
|
|
332
|
+
apostrophe or a letter outside ASCII, and is read back from the new print at the same
|
|
333
|
+
position (`questionMarksRead`). The names are then compared through `@finbheara/names` at
|
|
334
|
+
NN3, which drops case, punctuation and accents and strips a parenthetical aside; a given name
|
|
335
|
+
and surname the old view glued into one token are compared by the name fold against the new
|
|
336
|
+
name's fields, in either order.
|
|
337
|
+
|
|
338
|
+
Anything else is another row: a different surname or given name, one letter printed for
|
|
339
|
+
another, a `?` over an ASCII letter, a school that differs anywhere but a trailing `: location`.
|
|
340
|
+
The caller pairs the card; nothing here reads an address.
|
|
341
|
+
|
|
267
342
|
## Known gaps
|
|
268
343
|
|
|
269
344
|
- `place()` reads `"Derry in Ireland"` and `"Calgary in Western Canada"` as `unread`: it does
|
package/dist/age.js
CHANGED
|
@@ -6,6 +6,8 @@ var UNDER_CAP_MAX_TOP = 21;
|
|
|
6
6
|
var YOUNG_FLOOR = Object.freeze({ hard: 2, soft: 4 });
|
|
7
7
|
var MIN_MAJOR_AGE = 4;
|
|
8
8
|
var MIN_FEIS_AGE = 3;
|
|
9
|
+
var YOUNGEST_BAND_TOP = MAJORS_FLOOR_YEARS;
|
|
10
|
+
var YOB_RANGE = Object.freeze({ first: 1900, last: 2099 });
|
|
9
11
|
var DANCE_UP_YEARS = 1;
|
|
10
12
|
var ageOnJanuary1 = (year, born) => year - born - 1;
|
|
11
13
|
var yearsSinceBirth = (year, born) => year - born;
|
|
@@ -116,6 +118,11 @@ function read(token, ladder, top) {
|
|
|
116
118
|
}
|
|
117
119
|
return { kind: null, interval: null, refusal: "not-a-band", clauses: [], parts: [] };
|
|
118
120
|
}
|
|
121
|
+
function refuseBelowYoungest(r) {
|
|
122
|
+
if (r.interval === null || r.interval.hi >= YOUNGEST_BAND_TOP)
|
|
123
|
+
return r;
|
|
124
|
+
return { ...r, interval: null, refusal: "below-youngest-band", clauses: [] };
|
|
125
|
+
}
|
|
119
126
|
|
|
120
127
|
class AgeMisuseError extends Error {
|
|
121
128
|
misuse;
|
|
@@ -146,7 +153,7 @@ function fromPrint(print, context = {}) {
|
|
|
146
153
|
const ladder = context.ladder ?? NO_LADDER;
|
|
147
154
|
const year = context.year === undefined ? currentYear() : context.year;
|
|
148
155
|
const ag1 = print.trim().toLowerCase();
|
|
149
|
-
const reading = ag1 === "" ? { kind: null, interval: null, refusal: null, clauses: [], parts: [] } : read(print.trim(), ladder, context.top === true);
|
|
156
|
+
const reading = ag1 === "" ? { kind: null, interval: null, refusal: null, clauses: [], parts: [] } : refuseBelowYoungest(read(print.trim(), ladder, context.top === true));
|
|
150
157
|
const iv = reading.interval;
|
|
151
158
|
const arm = ag1 === "" ? "empty" : iv === null ? "open" : "band";
|
|
152
159
|
const ag2 = iv !== null ? display(iv) : ag1 === "" ? "" : `${ag1}?`;
|
|
@@ -162,6 +169,8 @@ function fromPrint(print, context = {}) {
|
|
|
162
169
|
parts: reading.parts,
|
|
163
170
|
length: ag2.length,
|
|
164
171
|
refusal: reading.refusal,
|
|
172
|
+
isValid: () => reading.refusal === null,
|
|
173
|
+
invalidReason: () => reading.refusal,
|
|
165
174
|
source,
|
|
166
175
|
clauses: reading.clauses,
|
|
167
176
|
ladder,
|
|
@@ -186,7 +195,7 @@ function fromPrint(print, context = {}) {
|
|
|
186
195
|
floorObserved: () => reading.kind !== "under" || iv === null ? null : reading.clauses.includes("under-floored-on-rung")
|
|
187
196
|
});
|
|
188
197
|
}
|
|
189
|
-
function fromYOB(from, to = from) {
|
|
198
|
+
function fromYOB(from, to = from, context = {}) {
|
|
190
199
|
for (const y of [from, to])
|
|
191
200
|
if (y !== null && !Number.isInteger(y))
|
|
192
201
|
throw new RangeError(`Age.fromYOB(${from}, ${to}): a year of birth is a whole number`);
|
|
@@ -194,29 +203,34 @@ function fromYOB(from, to = from) {
|
|
|
194
203
|
throw new RangeError("Age.fromYOB(null, null): at least one end is a year");
|
|
195
204
|
if (from !== null && to !== null && from > to)
|
|
196
205
|
throw new RangeError(`Age.fromYOB(${from}, ${to}): the earliest year comes first`);
|
|
197
|
-
const
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
206
|
+
const at = context.year ?? null;
|
|
207
|
+
const valid = [from, to].every((y) => y === null || y >= YOB_RANGE.first && y <= YOB_RANGE.last) && (at === null || to === null || yearsSinceBirth(at, to) >= YOUNG_FLOOR.hard);
|
|
208
|
+
const refusal = valid ? null : "year-out-of-range";
|
|
209
|
+
const exact = valid && from !== null && from === to;
|
|
210
|
+
const ag0 = from !== null && from === to ? String(from) : `${from ?? ""}..${to ?? ""}`;
|
|
211
|
+
const ag2 = valid ? `b${ag0}` : `b${ag0}?`;
|
|
212
|
+
const years = Object.freeze(valid ? { from, to } : { from: null, to: null });
|
|
201
213
|
return withRelations({
|
|
202
214
|
[Symbol.toStringTag]: "Age",
|
|
203
215
|
toString: () => ag2,
|
|
204
|
-
arm: "yob",
|
|
216
|
+
arm: valid ? "yob" : "open",
|
|
205
217
|
kind: "yob",
|
|
206
218
|
ag0,
|
|
207
219
|
ag1: ag0,
|
|
208
220
|
ag2,
|
|
209
|
-
parts:
|
|
221
|
+
parts: from !== null && from === to ? [from] : [from, to].filter((y) => y !== null),
|
|
210
222
|
length: ag2.length,
|
|
211
|
-
refusal
|
|
212
|
-
|
|
223
|
+
refusal,
|
|
224
|
+
isValid: () => valid,
|
|
225
|
+
invalidReason: () => refusal,
|
|
226
|
+
source: valid ? "printed-yob" : "open",
|
|
213
227
|
clauses: [],
|
|
214
228
|
ladder: NO_LADDER,
|
|
215
229
|
year: null,
|
|
216
230
|
isEmpty: () => false,
|
|
217
|
-
isAge: () =>
|
|
231
|
+
isAge: () => valid,
|
|
218
232
|
isExact: () => exact,
|
|
219
|
-
isAndOver: () => from === null,
|
|
233
|
+
isAndOver: () => valid && from === null,
|
|
220
234
|
isAdult: () => false,
|
|
221
235
|
interval: () => null,
|
|
222
236
|
birthYears: () => years,
|
|
@@ -250,7 +264,7 @@ function birthYearsOfInterval(iv, year) {
|
|
|
250
264
|
to: Math.min(year - iv.lo - 1, year - MAJORS_FLOOR_YEARS)
|
|
251
265
|
};
|
|
252
266
|
}
|
|
253
|
-
var inBirthYears = (a) => a.
|
|
267
|
+
var inBirthYears = (a) => a.kind === "yob" || a.year !== null;
|
|
254
268
|
function spanOf(a) {
|
|
255
269
|
if (!a.isAge())
|
|
256
270
|
return null;
|
|
@@ -355,6 +369,25 @@ function birthYearsDisjoint(a, b) {
|
|
|
355
369
|
return true;
|
|
356
370
|
return false;
|
|
357
371
|
}
|
|
372
|
+
function intersectBirthYears(windows) {
|
|
373
|
+
let from = null;
|
|
374
|
+
let to = null;
|
|
375
|
+
let n = 0;
|
|
376
|
+
for (const w of windows) {
|
|
377
|
+
if (w.from === null && w.to === null)
|
|
378
|
+
continue;
|
|
379
|
+
n++;
|
|
380
|
+
if (w.from !== null)
|
|
381
|
+
from = from === null ? w.from : Math.max(from, w.from);
|
|
382
|
+
if (w.to !== null)
|
|
383
|
+
to = to === null ? w.to : Math.min(to, w.to);
|
|
384
|
+
}
|
|
385
|
+
if (n === 0)
|
|
386
|
+
return { from: null, to: null, n: 0 };
|
|
387
|
+
if (from !== null && to !== null && from > to)
|
|
388
|
+
return null;
|
|
389
|
+
return { from, to, n };
|
|
390
|
+
}
|
|
358
391
|
function birthYearsMeet(card, other, slack = 0) {
|
|
359
392
|
if (card.from !== null && other.to !== null && card.from > other.to)
|
|
360
393
|
return false;
|
|
@@ -378,6 +411,11 @@ function underCappedYears(b, year, n = UNDER_CAP.plain) {
|
|
|
378
411
|
function underCapRefuses(bandFrom, identityFrom, n) {
|
|
379
412
|
return identityFrom > bandFrom + n;
|
|
380
413
|
}
|
|
414
|
+
function validYOB(y, year) {
|
|
415
|
+
if (y === null || y === undefined || !Number.isInteger(y))
|
|
416
|
+
return null;
|
|
417
|
+
return Age.fromYOB(y, y, { year: year ?? null }).isValid() ? y : null;
|
|
418
|
+
}
|
|
381
419
|
function youngFloor(year, oldest) {
|
|
382
420
|
const s = yearsSinceBirth(year, oldest);
|
|
383
421
|
if (s >= YOUNG_FLOOR.soft)
|
|
@@ -399,6 +437,8 @@ export {
|
|
|
399
437
|
MIN_MAJOR_AGE,
|
|
400
438
|
UNDER_CAP,
|
|
401
439
|
UNDER_CAP_MAX_TOP,
|
|
440
|
+
YOB_RANGE,
|
|
441
|
+
YOUNGEST_BAND_TOP,
|
|
402
442
|
YOUNGEST_FLOOR,
|
|
403
443
|
YOUNG_FLOOR,
|
|
404
444
|
ageOnJanuary1,
|
|
@@ -408,6 +448,7 @@ export {
|
|
|
408
448
|
birthYearsMeet,
|
|
409
449
|
birthYearsOfInterval,
|
|
410
450
|
canOverlap,
|
|
451
|
+
intersectBirthYears,
|
|
411
452
|
isAveraged,
|
|
412
453
|
roundedMeanHundredths,
|
|
413
454
|
tooYoungForFeis,
|
|
@@ -415,6 +456,7 @@ export {
|
|
|
415
456
|
underCapApplies,
|
|
416
457
|
underCapRefuses,
|
|
417
458
|
underCappedYears,
|
|
459
|
+
validYOB,
|
|
418
460
|
yearsSinceBirth,
|
|
419
461
|
youngFloor
|
|
420
462
|
};
|
package/dist/ceili.js
CHANGED
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
ceiliById2,
|
|
5
5
|
ceili2,
|
|
6
6
|
ceiliRelation2
|
|
7
|
-
} from "./chunks/place-
|
|
8
|
-
import"./chunks/place-
|
|
7
|
+
} from "./chunks/place-0ehc2q69.js";
|
|
8
|
+
import"./chunks/place-htxgptyv.js";
|
|
9
9
|
export {
|
|
10
10
|
CEILI_NODES2 as CEILI_NODES,
|
|
11
11
|
allCeilis2 as allCeilis,
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
matchNodes,
|
|
4
4
|
candidateRelation,
|
|
5
5
|
isDanceTypePrint2
|
|
6
|
-
} from "./place-
|
|
6
|
+
} from "./place-htxgptyv.js";
|
|
7
7
|
|
|
8
8
|
function node(id, name, irish, family, formation, opts = {}) {
|
|
9
9
|
const kind = "round" in formation ? "round" : ("long" in formation) ? "long" : "set";
|
package/dist/dance-types.js
CHANGED
package/dist/dance.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import"./chunks/place-
|
|
1
|
+
import"./chunks/place-htxgptyv.js";
|
|
2
2
|
import {
|
|
3
3
|
ceili2
|
|
4
|
-
} from "./chunks/place-
|
|
4
|
+
} from "./chunks/place-0ehc2q69.js";
|
|
5
5
|
import {
|
|
6
6
|
tune2
|
|
7
|
-
} from "./chunks/place-
|
|
7
|
+
} from "./chunks/place-2ja9yx08.js";
|
|
8
8
|
|
|
9
9
|
function dance(print, round) {
|
|
10
10
|
const setting = round === "traditional-set" ? "traditional" : undefined;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { canonicalTeamName, nameFold, personName, probablySame } from "@finbheara/names/normalize";
|
|
2
|
+
var CHAR_SYNONYM_CLASSES = [
|
|
3
|
+
{
|
|
4
|
+
name: "apostrophe",
|
|
5
|
+
canonical: "'",
|
|
6
|
+
members: ["'", "", "ʼ", "‘", "’"]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: "dash",
|
|
10
|
+
canonical: "-",
|
|
11
|
+
members: ["-", "‐", "‑", "–", "—"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "space",
|
|
15
|
+
canonical: " ",
|
|
16
|
+
members: [" ", " ", " ", " ", " ", " "]
|
|
17
|
+
}
|
|
18
|
+
];
|
|
19
|
+
var FOLD_MAP = new Map(CHAR_SYNONYM_CLASSES.flatMap((c) => c.members.map((m) => [m, c.canonical])));
|
|
20
|
+
var FOLD_PATTERN = new RegExp(`[${[...FOLD_MAP.keys()].map((m) => `\\u${(m.codePointAt(0) ?? 0).toString(16).padStart(4, "0")}`).join("")}]`, "gu");
|
|
21
|
+
function foldCharSynonyms(text) {
|
|
22
|
+
return text.replace(FOLD_PATTERN, (ch) => FOLD_MAP.get(ch) ?? ch);
|
|
23
|
+
}
|
|
24
|
+
var collapse = (s) => foldCharSynonyms(s).replace(/\s+/gu, " ").trim();
|
|
25
|
+
function samePromotedSchool(was, now) {
|
|
26
|
+
const a = collapse(was);
|
|
27
|
+
const b = collapse(now);
|
|
28
|
+
if (a === b || apostropheRestored(was, now))
|
|
29
|
+
return true;
|
|
30
|
+
return b !== "" && a.startsWith(b) && /^ ?:( ?\S|$)/u.test(a.slice(b.length));
|
|
31
|
+
}
|
|
32
|
+
function apostropheRestored(was, now) {
|
|
33
|
+
const count = (s) => (collapse(s).match(/'/gu) ?? []).length;
|
|
34
|
+
const bare = (s) => collapse(collapse(s).replace(/'/gu, ""));
|
|
35
|
+
return count(now) >= count(was) && bare(was) === bare(now);
|
|
36
|
+
}
|
|
37
|
+
function samePromotedRow(was, now) {
|
|
38
|
+
if (!samePromotedSchool(was.school, now.school))
|
|
39
|
+
return false;
|
|
40
|
+
if (collapse(was.name) === collapse(now.name))
|
|
41
|
+
return true;
|
|
42
|
+
if (apostropheRestored(was.name, now.name))
|
|
43
|
+
return true;
|
|
44
|
+
const team = canonicalTeamName(collapse(was.name));
|
|
45
|
+
return team !== null && team === canonicalTeamName(collapse(now.name));
|
|
46
|
+
}
|
|
47
|
+
var STOOD_FOR = /^(?:'|(?![A-Za-z])\p{L})$/u;
|
|
48
|
+
function questionMarksRead(was, now) {
|
|
49
|
+
const a = [...collapse(was)];
|
|
50
|
+
if (!a.includes("?"))
|
|
51
|
+
return a.join("");
|
|
52
|
+
const b = [...collapse(now)];
|
|
53
|
+
if (a.length !== b.length)
|
|
54
|
+
return null;
|
|
55
|
+
const out = [];
|
|
56
|
+
for (let i = 0;i < a.length; i++) {
|
|
57
|
+
const x = a[i];
|
|
58
|
+
const y = b[i];
|
|
59
|
+
if (x === "?") {
|
|
60
|
+
if (!STOOD_FOR.test(y))
|
|
61
|
+
return null;
|
|
62
|
+
out.push(y);
|
|
63
|
+
} else if (x !== y)
|
|
64
|
+
return null;
|
|
65
|
+
else
|
|
66
|
+
out.push(x);
|
|
67
|
+
}
|
|
68
|
+
return out.join("");
|
|
69
|
+
}
|
|
70
|
+
function samePromotedName(was, now) {
|
|
71
|
+
const read = questionMarksRead(was, now);
|
|
72
|
+
if (read === null)
|
|
73
|
+
return false;
|
|
74
|
+
const n = collapse(now);
|
|
75
|
+
if (read === n)
|
|
76
|
+
return true;
|
|
77
|
+
const b = personName(n);
|
|
78
|
+
if (!b.isPerson())
|
|
79
|
+
return false;
|
|
80
|
+
const a = personName(read);
|
|
81
|
+
if (a.isPerson())
|
|
82
|
+
return probablySame(a, b, "nn3");
|
|
83
|
+
const glued = nameFold(read);
|
|
84
|
+
const given = b.givenFields().join("");
|
|
85
|
+
const surname = b.surnameKey();
|
|
86
|
+
return glued !== "" && (glued === nameFold(`${given}${surname}`) || glued === nameFold(`${surname}${given}`));
|
|
87
|
+
}
|
|
88
|
+
function samePromotedHeir(was, now) {
|
|
89
|
+
if (was.name !== "" && was.name === now.name)
|
|
90
|
+
return true;
|
|
91
|
+
const school = questionMarksRead(was.school, now.school);
|
|
92
|
+
if (school === null || !samePromotedSchool(school, now.school))
|
|
93
|
+
return false;
|
|
94
|
+
return samePromotedName(was.name, now.name);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
CHAR_SYNONYM_CLASSES,
|
|
98
|
+
collapse,
|
|
99
|
+
foldCharSynonyms,
|
|
100
|
+
questionMarksRead,
|
|
101
|
+
samePromotedHeir,
|
|
102
|
+
samePromotedName,
|
|
103
|
+
samePromotedRow,
|
|
104
|
+
samePromotedSchool
|
|
105
|
+
};
|
package/dist/tune.js
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
settingAt2,
|
|
9
9
|
tune2,
|
|
10
10
|
tuneRelation2
|
|
11
|
-
} from "./chunks/place-
|
|
12
|
-
import"./chunks/place-
|
|
11
|
+
} from "./chunks/place-2ja9yx08.js";
|
|
12
|
+
import"./chunks/place-htxgptyv.js";
|
|
13
13
|
export {
|
|
14
14
|
PLAUSIBLE_SET_TEMPO_MAX2 as PLAUSIBLE_SET_TEMPO_MAX,
|
|
15
15
|
SET_MIN_SPEED2 as SET_MIN_SPEED,
|
|
@@ -11,6 +11,9 @@ export interface AgeLadder {
|
|
|
11
11
|
readonly overs: readonly number[];
|
|
12
12
|
readonly ranges: readonly number[];
|
|
13
13
|
}
|
|
14
|
+
export interface YOBContext {
|
|
15
|
+
readonly year?: number | null;
|
|
16
|
+
}
|
|
14
17
|
export interface AgeContext {
|
|
15
18
|
readonly ladder?: AgeLadder;
|
|
16
19
|
readonly year?: number | null;
|
|
@@ -18,7 +21,7 @@ export interface AgeContext {
|
|
|
18
21
|
}
|
|
19
22
|
export type AgeArm = "band" | "yob" | "open" | "empty";
|
|
20
23
|
export type AgeKind = "under" | "over" | "range" | "adult" | "senior" | "yob";
|
|
21
|
-
export type AgeRefusal = "not-a-band" | "backwards-range" | "senior-without-ladder";
|
|
24
|
+
export type AgeRefusal = "not-a-band" | "backwards-range" | "senior-without-ladder" | "below-youngest-band" | "year-out-of-range";
|
|
22
25
|
export type AgeSource = "sibling-narrowed" | "printed-band" | "printed-range" | "printed-yob" | "open";
|
|
23
26
|
export type AgeClause = "under-floored-on-rung" | "youngest-absorbs" | "over-ceiling-from-next-over" | "over-ceiling-refused-gap" | "and-over-no-ceiling" | "range-overlapping" | "range-inclusive" | "range-floor-on-rung" | "range-floor-widened" | "range-adult-floor";
|
|
24
27
|
export type AgeRelation = "same" | "contains" | "overlaps" | "disjoint" | "silent";
|
package/dist/types/age.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, YoungFloorVerdict } from "./age-types.ts";
|
|
2
|
-
|
|
1
|
+
import type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, YOBContext, YoungFloorVerdict } from "./age-types.ts";
|
|
2
|
+
import type { Validated } from "./validated.ts";
|
|
3
|
+
export type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, Validated, YOBContext, YoungFloorVerdict, };
|
|
3
4
|
export declare const YOUNGEST_FLOOR = 0;
|
|
4
5
|
export declare const ADULT_FLOOR = 18;
|
|
5
6
|
export declare const MAJORS_FLOOR_YEARS = 3;
|
|
@@ -14,10 +15,15 @@ export declare const YOUNG_FLOOR: Readonly<{
|
|
|
14
15
|
}>;
|
|
15
16
|
export declare const MIN_MAJOR_AGE = 4;
|
|
16
17
|
export declare const MIN_FEIS_AGE = 3;
|
|
18
|
+
export declare const YOUNGEST_BAND_TOP = 3;
|
|
19
|
+
export declare const YOB_RANGE: Readonly<{
|
|
20
|
+
readonly first: 1900;
|
|
21
|
+
readonly last: 2099;
|
|
22
|
+
}>;
|
|
17
23
|
export declare const DANCE_UP_YEARS = 1;
|
|
18
24
|
export declare const ageOnJanuary1: (year: number, born: number) => number;
|
|
19
25
|
export declare const yearsSinceBirth: (year: number, born: number) => number;
|
|
20
|
-
export interface Age {
|
|
26
|
+
export interface Age extends Validated<AgeRefusal> {
|
|
21
27
|
readonly [Symbol.toStringTag]: "Age";
|
|
22
28
|
toString(): string;
|
|
23
29
|
readonly arm: AgeArm;
|
|
@@ -48,7 +54,7 @@ export declare class AgeMisuseError extends Error {
|
|
|
48
54
|
constructor(misuse: AgeMisuse, detail: string);
|
|
49
55
|
}
|
|
50
56
|
declare function fromPrint(print: string, context?: AgeContext): Age;
|
|
51
|
-
declare function fromYOB(from: number | null, to?: number | null): Age;
|
|
57
|
+
declare function fromYOB(from: number | null, to?: number | null, context?: YOBContext): Age;
|
|
52
58
|
export declare const Age: Readonly<{
|
|
53
59
|
from: typeof fromPrint;
|
|
54
60
|
fromYOB: typeof fromYOB;
|
|
@@ -75,10 +81,16 @@ export interface AverageAge {
|
|
|
75
81
|
export declare function isAveraged(a: Age | AverageAge): a is AverageAge;
|
|
76
82
|
export declare function averageAge(ages: readonly Age[]): AverageAge;
|
|
77
83
|
export declare function birthYearsDisjoint(a: BirthYears, b: BirthYears): boolean;
|
|
84
|
+
export declare function intersectBirthYears(windows: readonly BirthYears[]): {
|
|
85
|
+
from: number | null;
|
|
86
|
+
to: number | null;
|
|
87
|
+
n: number;
|
|
88
|
+
} | null;
|
|
78
89
|
export declare function birthYearsMeet(card: BirthYears, other: BirthYears, slack?: number): boolean;
|
|
79
90
|
export declare function underCapApplies(bandFrom: number | null, year: number): boolean;
|
|
80
91
|
export declare function underCappedYears(b: BirthYears, year: number, n?: number): BirthYears;
|
|
81
92
|
export declare function underCapRefuses(bandFrom: number, identityFrom: number, n: number): boolean;
|
|
93
|
+
export declare function validYOB(y: number | null | undefined, year?: number | null): number | null;
|
|
82
94
|
export declare function youngFloor(year: number, oldest: number): YoungFloorVerdict;
|
|
83
95
|
export declare const tooYoungForMajor: (year: number, oldest: number) => boolean;
|
|
84
96
|
export declare const tooYoungForFeis: (year: number, oldest: number) => boolean;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface PromotedPrint {
|
|
2
|
+
name: string;
|
|
3
|
+
school: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const CHAR_SYNONYM_CLASSES: readonly {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly canonical: string;
|
|
8
|
+
readonly members: readonly string[];
|
|
9
|
+
}[];
|
|
10
|
+
export declare function foldCharSynonyms(text: string): string;
|
|
11
|
+
export declare const collapse: (s: string) => string;
|
|
12
|
+
export declare function samePromotedSchool(was: string, now: string): boolean;
|
|
13
|
+
export declare function samePromotedRow(was: PromotedPrint, now: PromotedPrint): boolean;
|
|
14
|
+
export declare function questionMarksRead(was: string, now: string): string | null;
|
|
15
|
+
export declare function samePromotedName(was: string, now: string): boolean;
|
|
16
|
+
export declare function samePromotedHeir(was: PromotedPrint, now: PromotedPrint): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@finbheara/domain",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Places, CLRG regions, school names, ages and dances as they are printed: read, compared and told apart",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"places",
|
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
"types": "./dist/types/noun-index.d.ts",
|
|
54
54
|
"default": "./dist/noun-index.js"
|
|
55
55
|
},
|
|
56
|
+
"./promotion": {
|
|
57
|
+
"types": "./dist/types/promotion.d.ts",
|
|
58
|
+
"default": "./dist/promotion.js"
|
|
59
|
+
},
|
|
56
60
|
"./package.json": "./package.json"
|
|
57
61
|
},
|
|
58
62
|
"files": ["dist/*.js", "dist/chunks/*.js", "dist/types/**/*.d.ts", "README.md", "LICENSE"],
|
|
@@ -69,6 +73,6 @@
|
|
|
69
73
|
"prepublishOnly": "bun scripts/build.ts && bun scripts/guard-tarball.ts"
|
|
70
74
|
},
|
|
71
75
|
"dependencies": {
|
|
72
|
-
"@finbheara/names": "0.
|
|
76
|
+
"@finbheara/names": "0.17.0"
|
|
73
77
|
}
|
|
74
78
|
}
|