@finbheara/domain 0.4.0 → 0.5.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
@@ -36,10 +36,10 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
36
36
  | `@finbheara/domain/place` | `place()`, `createPlaceReader`, `placeRelation`, the `Place` types |
37
37
  | `@finbheara/domain/clrg-regions` | `REGIONS`, `regionOf`, `resolvePhrase` |
38
38
  | `@finbheara/domain/school` | `createSchoolNameReader`, `DEFAULT_SCHOOL_NAME_READER`, `compareSchoolNames`, `foldSchoolName`, `schoolEncoder`, `schoolColumns` |
39
- | `@finbheara/domain/tune` | `tune()`, `tuneRelation`, `tempoLegal`, `settingAt`, `TUNE_NODES`: the CLRG set dance list |
39
+ | `@finbheara/domain/tune` | `tune()`, `tuneRelation`, `tempoLegal`, `settingAt`, `TUNE_NODES`: the CLRG set dance list, each tune with its time, speeds and bars |
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`, `intersectBirthYears`, the `Validated` type, the age constants |
42
+ | `@finbheara/domain/age` | `Age.from`, `Age.fromYOB`, `ageRelation`, `canOverlap`, `averageAge`, `isAveraged`, `intersectBirthYears`, `validYOB`, 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
45
  | `@finbheara/domain/promotion` | `samePromotedRow`, `samePromotedSchool`, `samePromotedHeir`, `samePromotedName`, `questionMarksRead`, `foldCharSynonyms`: is a row the same row after a promotion (below) |
@@ -47,6 +47,36 @@ ESM, Node ≥ 22 or Bun ≥ 1.4.2. MIT. Not affiliated with or endorsed by any d
47
47
  The library does no I/O and installs nothing process-wide. Every reader has a default and a
48
48
  factory: build your own with options when the default is not what you want.
49
49
 
50
+ ## Empty, refused, valid
51
+
52
+ A reader never throws on what was printed. Every value it returns (`Place`, `SchoolName`,
53
+ `Age`, `TuneReading`, `CeiliReading`, `DanceReading`) is `Validated`, and answers three
54
+ questions:
55
+
56
+ | method | true when |
57
+ |---|---|
58
+ | `isEmpty()` | nothing was printed: an empty or blank cell, no dance named |
59
+ | `isRefused()` | something was printed and the type declines it: a placeholder, a school in a place field, `U2`, a `1900` year of birth |
60
+ | `isValid()` | neither: `!isEmpty() && !isRefused()` |
61
+
62
+ `invalidReason()` is `null` when the value is valid, `"empty"` when it is empty, and the
63
+ refusal's reason otherwise, in the type's own words. Every entry point exports the `Validated`
64
+ type.
65
+
66
+ Valid does not mean usable. A print the reader does not know but cannot rule out is valid:
67
+ `place("Ardmore")` is valid and `isPlace()` is false. Ask the type's own question (`isPlace()`,
68
+ `isSchool()`, `isAge()`, `isTune()`, `isCeili()`) for a value you can use.
69
+
70
+ ```ts
71
+ place("").invalidReason(); // "empty"
72
+ place("n/a").invalidReason(); // "placeholder"
73
+ place("Ardmore").isValid(); // true: valid, unknown
74
+ DEFAULT_SCHOOL_NAME_READER.schoolName("N/A").isRefused(); // true
75
+ Age.fromYOB(1900).invalidReason(); // "year-out-of-range"
76
+ tune("").isEmpty(); // true
77
+ tune("Reel").invalidReason(); // "dance-type"
78
+ ```
79
+
50
80
  ## Read a place
51
81
 
52
82
  ```ts
@@ -156,6 +186,31 @@ invisible characters dropped and its whitespace collapsed; a `regex` is compiled
156
186
  flag and anchors itself. Your rows are asked before the shipped ones and the first match
157
187
  answers.
158
188
 
189
+ ## Read a set dance
190
+
191
+ `tune()` reads a spoken or printed set dance as the tunes of the official list it may name. A
192
+ stated tempo is taken out of the print and narrows the reading. Each tune answers its time
193
+ signature, its speed (a fixed speed for the seven traditional sets, a minimum for any
194
+ contemporary set) and the bars of music in its two parts, the step and the set.
195
+
196
+ ```ts
197
+ import { tempoLegal, tune } from "@finbheara/domain/tune";
198
+
199
+ const r = tune("Kilkenny Races at 80");
200
+ r.tune?.name; // "Kilkenny Races"
201
+ r.bpm; // 80
202
+ r.tune?.time(); // "4/4"
203
+ tempoLegal(r, 80); // true: hornpipe time, at or above 76
204
+ r.tune?.stepBars(); // 8
205
+ r.tune?.setBars(); // 24
206
+
207
+ tune("Blackbird").tune?.speed(); // 144: a traditional set's fixed speed
208
+ tune("Planxty").candidates.length; // 3: Davis, Drury and Hugh O'Donnell
209
+ ```
210
+
211
+ The bars are data on the tune and never read from a print: they say how long a step and a
212
+ set run at a given speed, and are the same in a traditional and a contemporary round.
213
+
159
214
  ## Read an age
160
215
 
161
216
  Ages are as of 1 January of the competition year. An `Age` is a closed range of birth years,
@@ -181,17 +236,18 @@ Age.fromYOB(1999).fitsIn(Age.from("Over 21", { year: 2026, top: true })); // tru
181
236
  canOverlap(u12, Age.from("U14", { year: 2026 }), dancer); // true
182
237
  ```
183
238
 
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.
239
+ A value the reader cannot stand behind is returned, not thrown. An `Age` is `Validated` (above):
240
+ an age group under `U3` admits nobody old enough to dance, and a year of birth outside 1901 to
241
+ 2099 is not one, so the placeholders `1000` and `1900` are refused. An invalid `Age` has no
242
+ birth years and rules nothing out.
188
243
 
189
244
  ```ts
190
245
  Age.from("U2", { year: 2026 }).isValid(); // false
191
246
  Age.from("U2", { year: 2026 }).invalidReason(); // "below-youngest-band"
192
247
  Age.fromYOB(2222).invalidReason(); // "year-out-of-range"
193
248
  Age.from("All", { year: 2026 }).invalidReason(); // "not-a-band"
194
- Age.from("", { year: 2026 }).isValid(); // true: empty, not invalid
249
+ Age.from("", { year: 2026 }).isEmpty(); // true: empty, not refused
250
+ Age.from("", { year: 2026 }).invalidReason(); // "empty"
195
251
  ```
196
252
 
197
253
  A team's age is the average of its dancers' ages, rounded to two decimals. `averageAge`
package/dist/age.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ validity
3
+ } from "./chunks/place-q9kwsmzd.js";
4
+
1
5
  var YOUNGEST_FLOOR = 0;
2
6
  var ADULT_FLOOR = 18;
3
7
  var MAJORS_FLOOR_YEARS = 3;
@@ -7,7 +11,7 @@ var YOUNG_FLOOR = Object.freeze({ hard: 2, soft: 4 });
7
11
  var MIN_MAJOR_AGE = 4;
8
12
  var MIN_FEIS_AGE = 3;
9
13
  var YOUNGEST_BAND_TOP = MAJORS_FLOOR_YEARS;
10
- var YOB_RANGE = Object.freeze({ first: 1900, last: 2099 });
14
+ var YOB_RANGE = Object.freeze({ first: 1901, last: 2099 });
11
15
  var DANCE_UP_YEARS = 1;
12
16
  var ageOnJanuary1 = (year, born) => year - born - 1;
13
17
  var yearsSinceBirth = (year, born) => year - born;
@@ -169,13 +173,11 @@ function fromPrint(print, context = {}) {
169
173
  parts: reading.parts,
170
174
  length: ag2.length,
171
175
  refusal: reading.refusal,
172
- isValid: () => reading.refusal === null,
173
- invalidReason: () => reading.refusal,
176
+ ...validity(arm === "empty", reading.refusal),
174
177
  source,
175
178
  clauses: reading.clauses,
176
179
  ladder,
177
180
  year,
178
- isEmpty: () => arm === "empty",
179
181
  isAge: () => iv !== null,
180
182
  isExact: () => {
181
183
  if (iv === null || year === null)
@@ -221,13 +223,11 @@ function fromYOB(from, to = from, context = {}) {
221
223
  parts: from !== null && from === to ? [from] : [from, to].filter((y) => y !== null),
222
224
  length: ag2.length,
223
225
  refusal,
224
- isValid: () => valid,
225
- invalidReason: () => refusal,
226
+ ...validity(false, refusal),
226
227
  source: valid ? "printed-yob" : "open",
227
228
  clauses: [],
228
229
  ladder: NO_LADDER,
229
230
  year: null,
230
- isEmpty: () => false,
231
231
  isAge: () => valid,
232
232
  isExact: () => exact,
233
233
  isAndOver: () => valid && from === null,
package/dist/ceili.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  ceiliById2,
5
5
  ceili2,
6
6
  ceiliRelation2
7
- } from "./chunks/place-0ehc2q69.js";
7
+ } from "./chunks/place-kk48pfja.js";
8
8
  import"./chunks/place-htxgptyv.js";
9
9
  export {
10
10
  CEILI_NODES2 as CEILI_NODES,
@@ -1,3 +1,7 @@
1
+ import {
2
+ validity
3
+ } from "./place-q9kwsmzd.js";
4
+
1
5
  var PLACE_NODES = [
2
6
  {
3
7
  id: "world",
@@ -7098,7 +7102,7 @@ function createPlaceReader2(options = {}) {
7098
7102
  refusal: r.refusal,
7099
7103
  misses,
7100
7104
  length: pl2.length,
7101
- isEmpty: () => pl1 === "",
7105
+ ...validity(pl1 === "", r.refusal),
7102
7106
  isPlace: () => pl1 !== "" && r.refusal === null && chains.length > 0,
7103
7107
  isSingular() {
7104
7108
  return this.arm === "resolved";
@@ -4,6 +4,9 @@ import {
4
4
  candidateRelation,
5
5
  isDanceTypePrint2
6
6
  } from "./place-htxgptyv.js";
7
+ import {
8
+ validity
9
+ } from "./place-q9kwsmzd.js";
7
10
 
8
11
  function node(id, name, irish, family, formation, opts = {}) {
9
12
  const kind = "round" in formation ? "round" : ("long" in formation) ? "long" : "set";
@@ -169,6 +172,7 @@ function reading(print, folded, candidates, score, match, refusal, misses) {
169
172
  score,
170
173
  match,
171
174
  refusal,
175
+ ...validity(refusal === "empty", refusal === "empty" ? null : refusal),
172
176
  misses: Object.freeze([...misses]),
173
177
  ceili: candidates.length === 1 ? candidates[0] : undefined,
174
178
  isCeili: () => candidates.length > 0,
@@ -0,0 +1,11 @@
1
+ function validity(empty, refusal) {
2
+ const reason = empty ? "empty" : refusal;
3
+ return {
4
+ isEmpty: () => empty,
5
+ isRefused: () => !empty && refusal !== null,
6
+ isValid: () => reason === null,
7
+ invalidReason: () => reason
8
+ };
9
+ }
10
+
11
+ export { validity };
@@ -6,13 +6,16 @@ import {
6
6
  rhythmOfTime2,
7
7
  isDanceTypePrint2
8
8
  } from "./place-htxgptyv.js";
9
+ import {
10
+ validity
11
+ } from "./place-q9kwsmzd.js";
9
12
 
10
13
  var SET_MIN_SPEED2 = Object.freeze({
11
14
  jig: 66,
12
15
  hornpipe: 76
13
16
  });
14
17
  var PLAUSIBLE_SET_TEMPO_MAX2 = 150;
15
- function node(id, name, irish, time, opts = {}) {
18
+ function node(id, name, irish, time, [stepBars, setBars], opts = {}) {
16
19
  return Object.freeze({
17
20
  id,
18
21
  name,
@@ -21,124 +24,126 @@ function node(id, name, irish, time, opts = {}) {
21
24
  irishPrints: Object.freeze(opts.irishPrints ?? []),
22
25
  time,
23
26
  traditionalSpeed: opts.traditionalSpeed ?? null,
24
- maxSpeed: null
27
+ maxSpeed: null,
28
+ stepBars,
29
+ setBars
25
30
  });
26
31
  }
27
32
  var TUNE_NODES2 = Object.freeze([
28
- node("blackbird", "The Blackbird", "An Lon Dubh", "2/4", {
33
+ node("blackbird", "The Blackbird", "An Lon Dubh", "2/4", [15, 30], {
29
34
  prints: ["Blackbird", "Blackbird (Traditional)"],
30
35
  irishPrints: ["An Londubh"],
31
36
  traditionalSpeed: 144
32
37
  }),
33
- node("lodge-road", "The Lodge Road", "Bóthar an Lóiste", "2/4", {
38
+ node("lodge-road", "The Lodge Road", "Bóthar an Lóiste", "2/4", [8, 20], {
34
39
  irishPrints: ["Bóthar An Lóiste"]
35
40
  }),
36
- node("downfall-of-paris", "Downfall of Paris", "Titim Phárais", "2/4", {
41
+ node("downfall-of-paris", "Downfall of Paris", "Titim Phárais", "2/4", [8, 16], {
37
42
  prints: ["The Downfall of Paris"],
38
43
  irishPrints: ["Titim Paris", "Titim Páras"]
39
44
  }),
40
- node("planxty-davis", "Planxty Davis", "Plancstaí Dáibhéis", "2/4", {
45
+ node("planxty-davis", "Planxty Davis", "Plancstaí Dáibhéis", "2/4", [16, 16], {
41
46
  irishPrints: ["Plancstaí Daibhéis"]
42
47
  }),
43
- node("king-of-the-fairies", "King of the Fairies", "Rí na Síog", "2/4", {
48
+ node("king-of-the-fairies", "King of the Fairies", "Rí na Síog", "2/4", [8, 16], {
44
49
  traditionalSpeed: 130
45
50
  }),
46
- node("rodneys-glory", "Rodney's Glory", "Glóir Rodney", "2/4", {
51
+ node("rodneys-glory", "Rodney's Glory", "Glóir Rodney", "2/4", [8, 12], {
47
52
  irishPrints: ["Glór Rodnaí"]
48
53
  }),
49
- node("ace-and-deuce-of-pipering", "Ace and Deuce of Pipering", "Aon agus dó na Píobaireachta", "4/4", {
54
+ node("ace-and-deuce-of-pipering", "Ace and Deuce of Pipering", "Aon agus dó na Píobaireachta", "4/4", [12, 12], {
50
55
  prints: ["Ace & Deuce of Pipering"],
51
56
  irishPrints: ["Aon agus Dó na Píobaireachta"]
52
57
  }),
53
- node("kilkenny-races", "Kilkenny Races", "Rásaí Chill Choinnigh", "4/4", {
58
+ node("kilkenny-races", "Kilkenny Races", "Rásaí Chill Choinnigh", "4/4", [8, 24], {
54
59
  prints: ["Killkenny Races"]
55
60
  }),
56
- node("blue-eyed-rascal", "The Blue-Eyed Rascal", "Cladhaire na Súile Gorma", "4/4", {
61
+ node("blue-eyed-rascal", "The Blue-Eyed Rascal", "Cladhaire na Súile Gorma", "4/4", [8, 12], {
57
62
  prints: ["Blue Eyed Rascal"]
58
63
  }),
59
- node("madame-bonaparte", "Madame Bonaparte", "Madame Bonaparte", "4/4"),
60
- node("bonapartes-retreat", "Bonaparte's Retreat", "Cúlú Bhonaparte", "4/4", {
64
+ node("madame-bonaparte", "Madame Bonaparte", "Madame Bonaparte", "4/4", [8, 12]),
65
+ node("bonapartes-retreat", "Bonaparte's Retreat", "Cúlú Bhonaparte", "4/4", [8, 20], {
61
66
  irishPrints: ["Cúlú Bonaparte"]
62
67
  }),
63
- node("piper", "The Piper Through the Meadow Astraying", "An Píobaire sa Mhóinéar ar Strae/Fáin", "4/4", {
68
+ node("piper", "The Piper Through the Meadow Astraying", "An Píobaire sa Mhóinéar ar Strae/Fáin", "4/4", [8, 12], {
64
69
  prints: ["The Piper", "The Piper thro' the Meadow Straying"],
65
70
  irishPrints: ["An Píobaire"]
66
71
  }),
67
- node("four-masters", "The Four Masters", "Na Ceithre Máistrí", "4/4"),
68
- node("rambling-rake", "Rambling Rake", "An Réice Fáin", "4/4", {
72
+ node("four-masters", "The Four Masters", "Na Ceithre Máistrí", "4/4", [8, 12]),
73
+ node("rambling-rake", "Rambling Rake", "An Réice Fáin", "4/4", [8, 16], {
69
74
  prints: ["The Rambling Rake"]
70
75
  }),
71
- node("garden-of-daisies", "Garden of Daisies", "Gáirdíní na Nóinín", "4/4", {
76
+ node("garden-of-daisies", "Garden of Daisies", "Gáirdíní na Nóinín", "4/4", [8, 16], {
72
77
  irishPrints: ["Gairdín na Nóiníní", "Gáirdíní na Nóiníní"],
73
78
  traditionalSpeed: 138
74
79
  }),
75
- node("roving-pedlar", "The Roving Pedlar", "An Mangaire Fáin", "4/4"),
76
- node("hunt", "The Hunt", "An Fiach", "4/4"),
77
- node("white-blanket", "White Blanket", "An Súisín Bán", "4/4", {
80
+ node("roving-pedlar", "The Roving Pedlar", "An Mangaire Fáin", "4/4", [8, 14]),
81
+ node("hunt", "The Hunt", "An Fiach", "4/4", [8, 12]),
82
+ node("white-blanket", "White Blanket", "An Súisín Bán", "4/4", [6, 12], {
78
83
  prints: ["The White Blanket"]
79
84
  }),
80
- node("job-of-journeywork", "Job of Journeywork", "An Greas Giúrnála", "4/4", {
85
+ node("job-of-journeywork", "Job of Journeywork", "An Greas Giúrnála", "4/4", [8, 14], {
81
86
  irishPrints: ["Greas Giúrnála"],
82
87
  traditionalSpeed: 138
83
88
  }),
84
- node("youghal-harbour", "Youghal Harbour", "Cuan Eochaille", "4/4"),
85
- node("blackthorn-stick", "Blackthorn Stick", "An Maide Droighneáin", "6/8", {
89
+ node("youghal-harbour", "Youghal Harbour", "Cuan Eochaille", "4/4", [6, 14]),
90
+ node("blackthorn-stick", "Blackthorn Stick", "An Maide Droighneáin", "6/8", [15, 15], {
86
91
  prints: ["The Blackthorn Stick"],
87
92
  irishPrints: ["An Maide Droighnéan"]
88
93
  }),
89
- node("planxty-drury", "Planxty Drury", "Plancstaí Drúirí", "6/8", {
94
+ node("planxty-drury", "Planxty Drury", "Plancstaí Drúirí", "6/8", [12, 16], {
90
95
  prints: ["Planxy Drury"]
91
96
  }),
92
- node("charlady", "The Charlady", "An Bhean Ghlantacháin", "6/8", {
97
+ node("charlady", "The Charlady", "An Bhean Ghlantacháin", "6/8", [8, 24], {
93
98
  irishPrints: ["Bean an Ghlantacháin"]
94
99
  }),
95
- node("planxty-hugh-odonnell", "Planxty Hugh O'Donnell", "Plancstaí Aodh Uí Dhomhnaill", "6/8", {
100
+ node("planxty-hugh-odonnell", "Planxty Hugh O'Donnell", "Plancstaí Aodh Uí Dhomhnaill", "6/8", [16, 16], {
96
101
  irishPrints: ["Plancstaí Aodh Ó Domhnaill"]
97
102
  }),
98
- node("drunken-gauger", "Drunken Gauger", "An Gaueger Meisceach", "6/8", {
103
+ node("drunken-gauger", "Drunken Gauger", "An Gaueger Meisceach", "6/8", [15, 15], {
99
104
  prints: ["The Drunken Gauger", "The Funny Tailor"]
100
105
  }),
101
- node("rub-the-bag", "Rub the Bag", "Cuimil an Mála", "6/8"),
102
- node("fiddler-around-the-fairy-green", "The Fiddler Around the Fairy Green", "An Bheidhleadóir ag an Crann Sí", "6/8", { prints: ["The Fiddler 'Round the Fairy Tree", "The Fiddler Around the Fairy Tree"] }),
103
- node("st-patricks-day", "St. Patrick's Day", "Lá Fhéile Phádraig", "6/8", {
106
+ node("rub-the-bag", "Rub the Bag", "Cuimil an Mála", "6/8", [8, 14]),
107
+ node("fiddler-around-the-fairy-green", "The Fiddler Around the Fairy Green", "An Bheidhleadóir ag an Crann Sí", "6/8", [8, 12], { prints: ["The Fiddler 'Round the Fairy Tree", "The Fiddler Around the Fairy Tree"] }),
108
+ node("st-patricks-day", "St. Patrick's Day", "Lá Fhéile Phádraig", "6/8", [8, 14], {
104
109
  irishPrints: ["Lá Fhéile Pádraig", "Lá Féile Phádraig"],
105
110
  traditionalSpeed: 94
106
111
  }),
107
- node("humours-of-bandon", "Humours of Bandon", "Pléarácha na Banndon", "6/8", {
112
+ node("humours-of-bandon", "Humours of Bandon", "Pléarácha na Banndon", "6/8", [8, 16], {
108
113
  prints: ["Humour's of Bandon"],
109
114
  irishPrints: ["Pléaracha na Bandan"]
110
115
  }),
111
- node("story-teller", "The Story-Teller", "An Scéalaí", "6/8", {
116
+ node("story-teller", "The Story-Teller", "An Scéalaí", "6/8", [8, 16], {
112
117
  prints: ["The Storyteller"]
113
118
  }),
114
- node("hurling-boys", "The Hurling Boys", "Na Buachaillí Báire", "6/8", {
119
+ node("hurling-boys", "The Hurling Boys", "Na Buachaillí Báire", "6/8", [8, 14], {
115
120
  prints: ["The Hurling Boy"]
116
121
  }),
117
- node("sprig-of-shillelagh", "The Sprig of Shillelagh", "An Ghéag Sail Éille", "6/8", {
122
+ node("sprig-of-shillelagh", "The Sprig of Shillelagh", "An Ghéag Sail Éille", "6/8", [6, 10], {
118
123
  prints: ["The Spring of Shillelagh"],
119
124
  irishPrints: ["An Geag Síléaladh"]
120
125
  }),
121
- node("hurry-the-jug", "Hurry the Jug", "Cuir Thart an Crúsca", "6/8"),
122
- node("three-sea-captains", "The Three Sea Captains", "An Triúr Chaptaen Mara", "6/8", {
126
+ node("hurry-the-jug", "Hurry the Jug", "Cuir Thart an Crúsca", "6/8", [8, 16]),
127
+ node("three-sea-captains", "The Three Sea Captains", "An Triúr Chaptaen Mara", "6/8", [8, 20], {
123
128
  prints: ["Three Sea Captains"],
124
129
  irishPrints: ["Na Trí Captéin Maraí", "Triúr Captaen Mara"],
125
130
  traditionalSpeed: 96
126
131
  }),
127
- node("jockey-to-the-fair", "Jockey to the Fair", "An Marcach Chuig an Aonach", "6/8", {
132
+ node("jockey-to-the-fair", "Jockey to the Fair", "An Marcach Chuig an Aonach", "6/8", [8, 14], {
128
133
  traditionalSpeed: 90
129
134
  }),
130
- node("vanishing-lake", "Vanishing Lake", "An Loch Mhadhma", "6/8", {
135
+ node("vanishing-lake", "Vanishing Lake", "An Loch Mhadhma", "6/8", [14, 18], {
131
136
  prints: ["The Vanishing Lake"],
132
137
  irishPrints: ["Luch An Mhadhma"]
133
138
  }),
134
- node("miss-browns-fancy", "Miss Brown's Fancy", "Rogha Iníon De Brún", "6/8"),
135
- node("wandering-musician", "The Wandering Musician", "An Ceoltóir Fáin", "6/8", {
139
+ node("miss-browns-fancy", "Miss Brown's Fancy", "Rogha Iníon De Brún", "6/8", [8, 16]),
140
+ node("wandering-musician", "The Wandering Musician", "An Ceoltóir Fáin", "6/8", [8, 16], {
136
141
  prints: ["Wandering Musicianr"]
137
142
  }),
138
- node("orange-rogue", "Orange Rogue", "An Rógaire Buí", "6/8", {
143
+ node("orange-rogue", "Orange Rogue", "An Rógaire Buí", "6/8", [8, 16], {
139
144
  prints: ["The Orange Rogue"]
140
145
  }),
141
- node("is-the-big-man-within", "Is the Big Man Within?", "An bhfuil an Fear Mór Istigh?", "6/8+9/8", {
146
+ node("is-the-big-man-within", "Is the Big Man Within?", "An bhfuil an Fear Mór Istigh?", "6/8+9/8", [8, 8], {
142
147
  irishPrints: ["An ‘Bhfuil an Fear Mór Istigh?"]
143
148
  })
144
149
  ]);
@@ -164,7 +169,9 @@ function makeTune(n) {
164
169
  if (n.traditionalSpeed !== null)
165
170
  return n.traditionalSpeed;
166
171
  return setting === "traditional" ? null : range;
167
- }
172
+ },
173
+ stepBars: () => n.stepBars,
174
+ setBars: () => n.setBars
168
175
  });
169
176
  }
170
177
  var TUNES = Object.freeze(TUNE_NODES2.map(makeTune));
@@ -231,6 +238,7 @@ function reading(print, folded, bpm, candidates, score, match, refusal, misses)
231
238
  score,
232
239
  match,
233
240
  refusal,
241
+ ...validity(refusal === "empty", refusal === "empty" ? null : refusal),
234
242
  misses: Object.freeze([...misses]),
235
243
  tune: candidates.length === 1 ? candidates[0] : undefined,
236
244
  isTune: () => candidates.length > 0,
package/dist/dance.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import"./chunks/place-htxgptyv.js";
2
2
  import {
3
3
  ceili2
4
- } from "./chunks/place-0ehc2q69.js";
4
+ } from "./chunks/place-kk48pfja.js";
5
5
  import {
6
6
  tune2
7
- } from "./chunks/place-2ja9yx08.js";
7
+ } from "./chunks/place-yndzaae5.js";
8
+ import {
9
+ validity
10
+ } from "./chunks/place-q9kwsmzd.js";
8
11
 
9
12
  function dance(print, round) {
10
13
  const setting = round === "traditional-set" ? "traditional" : undefined;
@@ -25,7 +28,11 @@ function dance(print, round) {
25
28
  }
26
29
  misses.push(...tunes.misses, ...ceilis.misses);
27
30
  const candidates = Object.freeze([...tunes.candidates, ...ceilis.candidates]);
31
+ const empty = tunes.isEmpty() && ceilis.isEmpty();
32
+ const refused = !empty && !tunes.isValid() && !ceilis.isValid();
33
+ const reason = !refused ? null : tunes.invalidReason() === "dance-type" || ceilis.invalidReason() === "dance-type" ? "dance-type" : "unmatched";
28
34
  return Object.freeze({
35
+ ...validity(empty, reason),
29
36
  print,
30
37
  tunes,
31
38
  ceilis,
package/dist/place.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  placesRelation2,
16
16
  probablySamePlace2,
17
17
  sameReading2
18
- } from "./chunks/place-qbp3xfb4.js";
18
+ } from "./chunks/place-0j6k50wf.js";
19
19
  export {
20
20
  DEFAULT_PLACE_CAPACITY2 as DEFAULT_PLACE_CAPACITY,
21
21
  DEFAULT_PLACE_READER2 as DEFAULT_PLACE_READER,
package/dist/school.js CHANGED
@@ -5,7 +5,10 @@ import {
5
5
  areAllPlaceTokens2,
6
6
  placeRelation2,
7
7
  sameReading2
8
- } from "./chunks/place-qbp3xfb4.js";
8
+ } from "./chunks/place-0j6k50wf.js";
9
+ import {
10
+ validity
11
+ } from "./chunks/place-q9kwsmzd.js";
9
12
 
10
13
  import { normalizeApostrophes } from "@finbheara/names/lexicon";
11
14
  import { SURNAME_SEPARATOR, lexiconKey, titleCaseNamePart } from "@finbheara/names/normalize";
@@ -1782,7 +1785,7 @@ function createSchoolNameReader(options = {}) {
1782
1785
  institution: b.institution,
1783
1786
  location: b.location,
1784
1787
  refusal: b.refusal,
1785
- isEmpty: () => b.empty,
1788
+ ...validity(b.empty, b.refusal?.reason ?? null),
1786
1789
  isSchool: () => !b.empty && b.refusal === null,
1787
1790
  surnames: () => b.surnames,
1788
1791
  concerns: b.concerns
package/dist/tune.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  settingAt2,
9
9
  tune2,
10
10
  tuneRelation2
11
- } from "./chunks/place-2ja9yx08.js";
11
+ } from "./chunks/place-yndzaae5.js";
12
12
  import"./chunks/place-htxgptyv.js";
13
13
  export {
14
14
  PLAUSIBLE_SET_TEMPO_MAX2 as PLAUSIBLE_SET_TEMPO_MAX,
@@ -1,6 +1,6 @@
1
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, };
2
+ import { type EmptyReason, type Validated } from "./validated.ts";
3
+ export type { AgeArm, AgeClause, AgeComparison, AgeContext, AgeInterval, AgeKind, AgeLadder, AgeMisuse, AgeRefusal, AgeRelation, AgeRelationReason, AgeSource, BirthYears, EmptyReason, Validated, YOBContext, YoungFloorVerdict, };
4
4
  export declare const YOUNGEST_FLOOR = 0;
5
5
  export declare const ADULT_FLOOR = 18;
6
6
  export declare const MAJORS_FLOOR_YEARS = 3;
@@ -17,7 +17,7 @@ export declare const MIN_MAJOR_AGE = 4;
17
17
  export declare const MIN_FEIS_AGE = 3;
18
18
  export declare const YOUNGEST_BAND_TOP = 3;
19
19
  export declare const YOB_RANGE: Readonly<{
20
- readonly first: 1900;
20
+ readonly first: 1901;
21
21
  readonly last: 2099;
22
22
  }>;
23
23
  export declare const DANCE_UP_YEARS = 1;
@@ -38,7 +38,6 @@ export interface Age extends Validated<AgeRefusal> {
38
38
  readonly clauses: readonly AgeClause[];
39
39
  readonly ladder: AgeLadder;
40
40
  readonly year: number | null;
41
- isEmpty(): boolean;
42
41
  isAge(): boolean;
43
42
  isExact(): boolean;
44
43
  isAndOver(): boolean;
@@ -1,4 +1,6 @@
1
1
  import { type DanceMatch, type DanceRelation } from "./dance-match.ts";
2
+ import { type Validated } from "./validated.ts";
3
+ export type { EmptyReason, Validated } from "./validated.ts";
2
4
  export type CeiliFamily = "eight-hand" | "four-hand" | "handbook";
3
5
  export type CeiliFormation = "set" | "long" | "round";
4
6
  export interface CeiliNode {
@@ -33,7 +35,7 @@ export interface CeiliContext {
33
35
  readonly hands?: number;
34
36
  }
35
37
  export type CeiliRefusal = "empty" | "dance-type" | "unmatched";
36
- export interface CeiliReading {
38
+ export interface CeiliReading extends Validated<Exclude<CeiliRefusal, "empty">> {
37
39
  readonly [Symbol.toStringTag]: "CeiliReading";
38
40
  readonly print: string;
39
41
  readonly folded: string;
@@ -1,7 +1,10 @@
1
1
  import { type Ceili, type CeiliReading } from "./ceili.ts";
2
2
  import { type Tune, type TuneReading } from "./tune.ts";
3
+ import { type Validated } from "./validated.ts";
4
+ export type { EmptyReason, Validated } from "./validated.ts";
3
5
  export type DanceRound = "set" | "traditional-set" | "ceili" | "eight-hand" | "four-hand";
4
- export interface DanceReading {
6
+ export type DanceRefusal = "dance-type" | "unmatched";
7
+ export interface DanceReading extends Validated<DanceRefusal> {
5
8
  readonly print: string;
6
9
  readonly tunes: TuneReading;
7
10
  readonly ceilis: CeiliReading;
@@ -1,7 +1,8 @@
1
1
  import type { PlaceContext, PlaceKind, PlaceMatch, PlaceNode, PlaceRefusal, PlaceRelation } from "./place-types.ts";
2
- export type { PlaceContext, PlaceKind, PlaceMatch, PlaceNode, PlaceRefusal, PlaceRelation };
2
+ import { type EmptyReason, type Validated } from "./validated.ts";
3
+ export type { EmptyReason, PlaceContext, PlaceKind, PlaceMatch, PlaceNode, PlaceRefusal, PlaceRelation, Validated, };
3
4
  export type PlaceArm = "resolved" | "plural" | "ambiguous" | "unread";
4
- export interface AbstractPlace {
5
+ export interface AbstractPlace extends Validated<PlaceRefusal> {
5
6
  readonly [Symbol.toStringTag]: "Place";
6
7
  toString(): string;
7
8
  readonly arm: PlaceArm;
@@ -18,7 +19,6 @@ export interface AbstractPlace {
18
19
  readonly refusal: PlaceRefusal | null;
19
20
  readonly misses: readonly string[];
20
21
  readonly length: number;
21
- isEmpty(): boolean;
22
22
  isPlace(): boolean;
23
23
  isSingular(): this is ResolvedPlace;
24
24
  isPlural(): this is PluralPlaces;
@@ -1,6 +1,7 @@
1
1
  import { type Place } from "./place.ts";
2
2
  import { type NotASchool, type RefusalReason, type SchoolNameException } from "./school-exception-vocabulary.ts";
3
3
  import { type InstitutionType } from "./school-institution-vocabulary.ts";
4
+ import { type Validated } from "./validated.ts";
4
5
  export interface LexiconFlags {
5
6
  surname: boolean;
6
7
  given: boolean;
@@ -26,7 +27,7 @@ export declare const SCHOOL_REFUSED = "!!!NOTASCHOOL";
26
27
  export type SchoolConcern = "institution-inside-name" | "promoted-name" | "no-name-part" | "truncated-tail" | "lexicon-absent" | "ambiguous-part" | "place-from-lexicon";
27
28
  export type Refusal = SchoolRefusal;
28
29
  export type Concern = SchoolConcern;
29
- export interface SchoolName {
30
+ export interface SchoolName extends Validated<RefusalReason> {
30
31
  readonly [Symbol.toStringTag]: "SchoolName";
31
32
  toString(): string;
32
33
  readonly sn0: string;
@@ -38,7 +39,6 @@ export interface SchoolName {
38
39
  readonly institution: InstitutionType | null;
39
40
  readonly location: Place | null;
40
41
  readonly refusal: SchoolRefusal | null;
41
- isEmpty(): boolean;
42
42
  isSchool(): boolean;
43
43
  surnames(): readonly string[];
44
44
  readonly concerns: readonly SchoolConcern[];
@@ -8,4 +8,5 @@ export { compareSchoolNames, compareSchools } from "./school-identity.ts";
8
8
  export type { IdentityResult, IdentityVerdict } from "./school-identity.ts";
9
9
  export { foldSchoolName, hasSpecificToken, schoolKeyTokens } from "./school-match-key.ts";
10
10
  export { NOT_A_SCHOOL, SCHOOL_NAME_EXCEPTIONS, } from "./school-exception-vocabulary.ts";
11
+ export type { EmptyReason, Validated } from "./validated.ts";
11
12
  export type { NotASchool, RefusalReason, SchoolNameException, } from "./school-exception-vocabulary.ts";
@@ -1,5 +1,7 @@
1
1
  import { type DanceMatch, type DanceRelation } from "./dance-match.ts";
2
2
  import { type SetRhythm, type TimeSignature } from "./dance-types.ts";
3
+ import { type Validated } from "./validated.ts";
4
+ export type { EmptyReason, Validated } from "./validated.ts";
3
5
  export type TuneSetting = "traditional" | "contemporary";
4
6
  export interface SpeedRange {
5
7
  readonly min: number;
@@ -16,7 +18,10 @@ export interface TuneNode {
16
18
  readonly time: TimeSignature;
17
19
  readonly traditionalSpeed: number | null;
18
20
  readonly maxSpeed: number | null;
21
+ readonly stepBars: number;
22
+ readonly setBars: number;
19
23
  }
24
+ export type TuneBars = readonly [step: number, set: number];
20
25
  export declare const TUNE_NODES: readonly TuneNode[];
21
26
  export interface Tune {
22
27
  readonly [Symbol.toStringTag]: "Tune";
@@ -30,6 +35,8 @@ export interface Tune {
30
35
  time(): TimeSignature;
31
36
  rhythm(): SetRhythm;
32
37
  speed(setting?: TuneSetting): number | SpeedRange | null;
38
+ stepBars(): number;
39
+ setBars(): number;
33
40
  }
34
41
  export declare function allTunes(): readonly Tune[];
35
42
  export declare function tuneById(id: string): Tune | undefined;
@@ -42,7 +49,7 @@ export interface TuneContext {
42
49
  readonly time?: TimeSignature;
43
50
  }
44
51
  export type TuneRefusal = "empty" | "dance-type" | "unmatched";
45
- export interface TuneReading {
52
+ export interface TuneReading extends Validated<Exclude<TuneRefusal, "empty">> {
46
53
  readonly [Symbol.toStringTag]: "TuneReading";
47
54
  readonly print: string;
48
55
  readonly folded: string;
@@ -1,4 +1,8 @@
1
- export interface Validated<Reason extends string> {
1
+ export type EmptyReason = "empty";
2
+ export interface Validated<Refusal extends string> {
3
+ isEmpty(): boolean;
4
+ isRefused(): boolean;
2
5
  isValid(): boolean;
3
- invalidReason(): Reason | null;
6
+ invalidReason(): EmptyReason | Refusal | null;
4
7
  }
8
+ export declare function validity<Refusal extends string>(empty: boolean, refusal: Refusal | null): Validated<Refusal>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finbheara/domain",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",