group-planner 0.0.0-stage → 0.2.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/LICENSE +28 -0
- package/README.md +335 -2
- package/dist/index.d.mts +278 -0
- package/dist/index.d.ts +278 -0
- package/dist/index.js +1647 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1599 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +66 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Piotr Berlowski
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,336 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Group Planner
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A TypeScript library for competitor partitioning algorithms, designed to be integrated into Next.js applications. Provides multiple algorithmic approaches to partition competitors into balanced subsets based on equipment class, age category, and gender preferences.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multiple Algorithm Approaches**: Mixed Integer Programming (Greedy & HiGHS), K-means Clustering
|
|
8
|
+
- **Flexible Constraints**: Configurable subset sizes (3-6 competitors, prefer 4). A target is never size 1 or 2.
|
|
9
|
+
- **Guardian Constraint Enforcement**: Competitors must be in the same subset as their guardians
|
|
10
|
+
- **Multiple Optimization Criteria**:
|
|
11
|
+
- Even gender distribution within subsets
|
|
12
|
+
- Age category homogeneity
|
|
13
|
+
- Equipment class grouping or distribution
|
|
14
|
+
- Guardian relationship handling
|
|
15
|
+
- **TypeScript Support**: Full type safety and IntelliSense
|
|
16
|
+
- **Performance Optimized**: Efficient algorithms suitable for up to 168 competitors
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install group-planner
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { solve, type Competitor } from 'group-planner';
|
|
30
|
+
|
|
31
|
+
const competitors: Competitor[] = [
|
|
32
|
+
{ id: '001', equipmentClass: 'HB', ageCategory: 'J', gender: 'M' },
|
|
33
|
+
{ id: '002', equipmentClass: 'HB', ageCategory: 'J', gender: 'F' },
|
|
34
|
+
{ id: '003', equipmentClass: 'LB', ageCategory: 'A', gender: 'M' }, // Guardian
|
|
35
|
+
{ id: '004', equipmentClass: 'LB', ageCategory: 'A', gender: 'F' },
|
|
36
|
+
{ id: '005', equipmentClass: 'BBR', ageCategory: 'C', gender: 'M', guardianId: '003' }, // Must be with guardian 003
|
|
37
|
+
// ... more competitors
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Auto-select best algorithm (default)
|
|
41
|
+
const result = await solve(competitors, {
|
|
42
|
+
groupByEquipmentClass: true,
|
|
43
|
+
preferredSubsetSize: 4,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Or specify a particular algorithm
|
|
47
|
+
const mipResult = await solve(competitors, { algorithm: 'mip' });
|
|
48
|
+
const higgsResult = await solve(competitors, { algorithm: 'mip-lp' }); // HiGHS MIP solver
|
|
49
|
+
const clusteringResult = await solve(competitors, { algorithm: 'clustering' });
|
|
50
|
+
|
|
51
|
+
console.log(`Algorithm used: ${result.algorithmUsed}`);
|
|
52
|
+
console.log(`Score: ${result.result.score}`);
|
|
53
|
+
console.log(`Subsets:`, result.result.subsets);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Algorithm Comparison
|
|
57
|
+
|
|
58
|
+
If you want to compare all algorithms and see detailed results:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { compareAlgorithms } from 'group-planner';
|
|
62
|
+
|
|
63
|
+
const { bestResult, allResults } = await compareAlgorithms(competitors, {
|
|
64
|
+
groupByEquipmentClass: true,
|
|
65
|
+
preferredSubsetSize: 4,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
console.log(`Best algorithm: ${bestResult.algorithmUsed}`);
|
|
69
|
+
allResults.forEach(result => {
|
|
70
|
+
console.log(`${result.algorithmUsed}: Score ${result.result.score} (${result.executionTime}ms)`);
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
## Algorithm Approaches
|
|
76
|
+
|
|
77
|
+
### 1. Mixed Integer Programming (MIP)
|
|
78
|
+
Formulates the problem as an optimization problem with binary variables and constraints. Treats guardian families as atomic units that must be assigned together.
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { solveMIP } from 'group-planner';
|
|
82
|
+
|
|
83
|
+
const result = solveMIP(competitors, options);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 2. K-means Clustering
|
|
87
|
+
Uses feature encoding and weighted distance metrics to cluster guardian families based on similarity, then adjusts clusters to meet size constraints while keeping families together.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { solveClustering } from 'group-planner';
|
|
91
|
+
|
|
92
|
+
const result = solveClustering(competitors, options);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. Branch-and-bound
|
|
96
|
+
Systematically searches the solution space by assigning guardian families (not individual competitors) to subsets, ensuring guardians and their dependents stay together.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// Branch-and-bound algorithm has been removed due to performance issues
|
|
100
|
+
// Use 'mip', 'mip-lp', or 'clustering' algorithms instead
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration Options
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface PartitionOptions {
|
|
107
|
+
/** Prefer grouping competitors of same equipment class together vs distributing evenly */
|
|
108
|
+
groupByEquipmentClass: boolean;
|
|
109
|
+
/** Maximum number of subsets allowed (default: 28) */
|
|
110
|
+
maxSubsets?: number;
|
|
111
|
+
/** Minimum subset size. Below 3 is raised to 3 (default: 3) */
|
|
112
|
+
minSubsetSize?: number;
|
|
113
|
+
/** Maximum subset size (default: 6) */
|
|
114
|
+
maxSubsetSize?: number;
|
|
115
|
+
/** Preferred subset size (default: 4) */
|
|
116
|
+
preferredSubsetSize?: number;
|
|
117
|
+
/** Weight for same gender preference (default: 1.0) */
|
|
118
|
+
genderWeight?: number;
|
|
119
|
+
/** Weight for same age category preference (default: 1.0) */
|
|
120
|
+
ageCategoryWeight?: number;
|
|
121
|
+
/** Weight for equipment class grouping preference (default: 1.0) */
|
|
122
|
+
equipmentClassWeight?: number;
|
|
123
|
+
/** Extra weight for subsets that are exactly preferredSubsetSize (default: 0) */
|
|
124
|
+
closedGroupWeight?: number;
|
|
125
|
+
/** Host labels for bowstyle, age, and gender. Defaults to the built-in IFAA lists. */
|
|
126
|
+
solutionSpace?: SolutionSpace;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Competitor Interface
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
interface Competitor {
|
|
134
|
+
id: string;
|
|
135
|
+
equipmentClass: string; // IFAA code, or any label listed in solutionSpace
|
|
136
|
+
ageCategory: string; // IFAA age code, or any label listed in solutionSpace
|
|
137
|
+
gender: string; // F, M, or any label listed in solutionSpace
|
|
138
|
+
guardianId?: string; // Reference to another competitor who is the guardian
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Equipment Classes
|
|
143
|
+
- **HB**: Heavy Bow
|
|
144
|
+
- **LB**: Light Bow
|
|
145
|
+
- **TR**: Traditional Recurve
|
|
146
|
+
- **BHR**: Barebow Heavy Recurve
|
|
147
|
+
- **BBR**: Barebow Basic Recurve
|
|
148
|
+
- **FSR**: Freestyle Recurve
|
|
149
|
+
- **BBC**: Barebow Compound
|
|
150
|
+
- **BL**: Barebow Longbow
|
|
151
|
+
- **BU**: Barebow Unlimited
|
|
152
|
+
- **FSC**: Freestyle Compound
|
|
153
|
+
- **FU**: Freestyle Unlimited
|
|
154
|
+
|
|
155
|
+
### Age Groups
|
|
156
|
+
- **C**: Children
|
|
157
|
+
- **J**: Juniors
|
|
158
|
+
- **YA**: Young Adults
|
|
159
|
+
- **A**: Adults
|
|
160
|
+
- **V**: Veterans
|
|
161
|
+
- **S**: Seniors
|
|
162
|
+
|
|
163
|
+
### Gender
|
|
164
|
+
- **F**: Female
|
|
165
|
+
- **M**: Male
|
|
166
|
+
|
|
167
|
+
## Algorithm Selection
|
|
168
|
+
|
|
169
|
+
The library supports four algorithm options:
|
|
170
|
+
|
|
171
|
+
### Algorithm Options
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
type Algorithm = 'mip' | 'clustering' | 'branch-and-bound' | 'auto';
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **`'auto'` (default)**: Runs all three algorithms and returns the result with the highest score
|
|
178
|
+
- **`'mip'`**: Mixed Integer Programming approach using greedy heuristic
|
|
179
|
+
- **`'clustering'`**: K-means clustering with constraint adjustment
|
|
180
|
+
- **`'branch-and-bound'`**: Systematic search with pruning
|
|
181
|
+
|
|
182
|
+
### When to Use Each Algorithm
|
|
183
|
+
|
|
184
|
+
- **Auto**: Best for production use when you want optimal results
|
|
185
|
+
- **MIP**: Good balance of speed and quality, handles large datasets well
|
|
186
|
+
- **Clustering**: Fast execution, good for real-time applications
|
|
187
|
+
- **Branch-and-bound**: Most thorough search, best for smaller datasets (≤48 competitors)
|
|
188
|
+
|
|
189
|
+
### Algorithm Performance Characteristics
|
|
190
|
+
|
|
191
|
+
| Algorithm | Speed | Quality | Max Competitors | Memory Usage |
|
|
192
|
+
|-----------|-------|---------|-----------------|--------------|
|
|
193
|
+
| MIP | Fast | High | 168 | Low |
|
|
194
|
+
| Clustering | Fastest | Good | 168 | Low |
|
|
195
|
+
| Branch-and-bound | Slow | Highest | ~48 | Medium |
|
|
196
|
+
| Auto | Slow | Highest | 168 | Medium |
|
|
197
|
+
|
|
198
|
+
## Result Structure
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
interface PartitionResult {
|
|
202
|
+
subsets: Competitor[][];
|
|
203
|
+
score: number;
|
|
204
|
+
metadata: {
|
|
205
|
+
totalCompetitors: number;
|
|
206
|
+
totalSubsets: number;
|
|
207
|
+
averageSubsetSize: number;
|
|
208
|
+
scoreBreakdown: {
|
|
209
|
+
sizeScore: number;
|
|
210
|
+
closedGroupScore: number;
|
|
211
|
+
genderScore: number;
|
|
212
|
+
ageCategoryScore: number;
|
|
213
|
+
equipmentClassScore: number;
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Algorithm Selection
|
|
220
|
+
|
|
221
|
+
The library supports different algorithms via the `algorithm` option:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
type Algorithm = 'mip' | 'mip-lp' | 'clustering' | 'auto';
|
|
225
|
+
|
|
226
|
+
// Available algorithms:
|
|
227
|
+
const mipResult = await solve(competitors, { algorithm: 'mip' }); // Greedy MIP solver
|
|
228
|
+
const higgsResult = await solve(competitors, { algorithm: 'mip-lp' }); // HiGHS MIP solver (WebAssembly)
|
|
229
|
+
const clusteringResult = await solve(competitors, { algorithm: 'clustering' }); // K-means clustering
|
|
230
|
+
const autoResult = await solve(competitors, { algorithm: 'auto' }); // Auto-select best (default)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Performance Guide
|
|
234
|
+
|
|
235
|
+
- **MIP (Greedy)**: Fast, good for most cases, handles all constraint types
|
|
236
|
+
- **MIP-LP (HiGHS)**: Most optimal results, slower, excellent constraint satisfaction
|
|
237
|
+
- **Clustering**: Fast, good for large datasets, may need constraint post-processing
|
|
238
|
+
- **Auto**: Runs all algorithms and returns the best result
|
|
239
|
+
|
|
240
|
+
### Special Constraints
|
|
241
|
+
|
|
242
|
+
- **≤112 competitors**: When `maxSubsetSize` is omitted, maximum subset size is limited to 4. An explicit `maxSubsetSize` is kept.
|
|
243
|
+
- **Closed groups**: Raise `closedGroupWeight` to prefer subsets of exactly `preferredSubsetSize`. Gender, age, and equipment weights still trade off inside that size.
|
|
244
|
+
- **No short targets**: Subset size is at least 3. A leftover archer is not placed on a target of 1 or 2.
|
|
245
|
+
- **Guardian relationships**: Automatically preserved across all algorithms
|
|
246
|
+
- **Maximum 28 subsets**: Hard limit enforced by all algorithms
|
|
247
|
+
|
|
248
|
+
## Guardian Constraints
|
|
249
|
+
|
|
250
|
+
The library automatically handles guardian relationships:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import {
|
|
254
|
+
createGuardianFamilies,
|
|
255
|
+
validateGuardianConstraints,
|
|
256
|
+
validateCompetitors,
|
|
257
|
+
EQUIPMENT_CLASSES,
|
|
258
|
+
AGE_GROUPS
|
|
259
|
+
} from 'group-planner';
|
|
260
|
+
|
|
261
|
+
// Validate competitor data before processing
|
|
262
|
+
const validation = validateCompetitors(competitors);
|
|
263
|
+
if (!validation.valid) {
|
|
264
|
+
console.log('Validation errors:', validation.errors);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// View how competitors are grouped into families
|
|
269
|
+
const families = createGuardianFamilies(competitors);
|
|
270
|
+
console.log('Guardian families:', families);
|
|
271
|
+
|
|
272
|
+
// Validate that a solution respects guardian constraints
|
|
273
|
+
const guardianValidation = validateGuardianConstraints(competitors, subsets);
|
|
274
|
+
if (!guardianValidation.valid) {
|
|
275
|
+
console.log('Guardian constraint violations:', guardianValidation.errors);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Access available categories
|
|
279
|
+
console.log('Equipment classes:', EQUIPMENT_CLASSES);
|
|
280
|
+
console.log('Age groups:', AGE_GROUPS);
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Examples
|
|
284
|
+
|
|
285
|
+
See `src/example.ts` for complete usage examples including:
|
|
286
|
+
- Basic partitioning with all three algorithms
|
|
287
|
+
- Guardian relationship handling and validation
|
|
288
|
+
- Comparing equipment class grouping vs distribution strategies
|
|
289
|
+
- Custom scoring weights
|
|
290
|
+
|
|
291
|
+
## Development
|
|
292
|
+
|
|
293
|
+
### Setup
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
npm install
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Build
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npm run build
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Development Mode
|
|
306
|
+
|
|
307
|
+
```bash
|
|
308
|
+
npm run dev
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Type Checking
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
npm run type-check
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Algorithm Performance
|
|
318
|
+
|
|
319
|
+
- **MIP Solver**: Fast greedy heuristic, good for large datasets
|
|
320
|
+
- **K-means Clustering**: Balanced approach, handles feature similarity well
|
|
321
|
+
- **Branch-and-bound**: Most thorough search, best for smaller datasets (<50 competitors)
|
|
322
|
+
|
|
323
|
+
## Release
|
|
324
|
+
|
|
325
|
+
The package name `group-planner` is unscoped. `prepublishOnly` cleans and builds `dist` before publish:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm test
|
|
329
|
+
npm publish
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Installing from git runs `prepare`, which builds `dist` as well.
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for competitor partitioning algorithms
|
|
3
|
+
*/
|
|
4
|
+
/** Built-in IFAA catalogue. Callers can supply any other labels through SolutionSpace. */
|
|
5
|
+
type EquipmentClass = 'HB' | 'LB' | 'TR' | 'BHR' | 'BBR' | 'FSR' | 'BBC' | 'BL' | 'BU' | 'FSC' | 'FU';
|
|
6
|
+
type AgeGroup = 'C' | 'J' | 'YA' | 'A' | 'V' | 'S';
|
|
7
|
+
type Gender = 'F' | 'M';
|
|
8
|
+
type Algorithm = 'mip' | 'mip-lp' | 'clustering' | 'auto';
|
|
9
|
+
/**
|
|
10
|
+
* Labels the solver is allowed to see. When omitted, the built-in IFAA lists are used.
|
|
11
|
+
* Pass the host application's own age, gender, and bowstyle ids here.
|
|
12
|
+
*/
|
|
13
|
+
interface SolutionSpace {
|
|
14
|
+
equipmentClasses: readonly string[];
|
|
15
|
+
ageCategories: readonly string[];
|
|
16
|
+
genders: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
interface Competitor {
|
|
19
|
+
id: string;
|
|
20
|
+
equipmentClass: string;
|
|
21
|
+
ageCategory: string;
|
|
22
|
+
gender: string;
|
|
23
|
+
guardianId?: string;
|
|
24
|
+
}
|
|
25
|
+
interface PartitionOptions {
|
|
26
|
+
/** Algorithm to use for partitioning (default: 'auto') */
|
|
27
|
+
algorithm?: Algorithm;
|
|
28
|
+
/** Prefer grouping competitors of same equipment class together vs distributing evenly */
|
|
29
|
+
groupByEquipmentClass: boolean;
|
|
30
|
+
/** Maximum number of subsets allowed (default: 28) */
|
|
31
|
+
maxSubsets?: number;
|
|
32
|
+
/** Minimum subset size. Values below 3 are raised to 3. Size 1 and 2 are never a target. */
|
|
33
|
+
minSubsetSize?: number;
|
|
34
|
+
/** Maximum subset size (default: 6) */
|
|
35
|
+
maxSubsetSize?: number;
|
|
36
|
+
/** Preferred subset size (default: 4) */
|
|
37
|
+
preferredSubsetSize?: number;
|
|
38
|
+
/** Weight for same gender preference (default: 1.0) */
|
|
39
|
+
genderWeight?: number;
|
|
40
|
+
/** Weight for same age category preference (default: 1.0) */
|
|
41
|
+
ageCategoryWeight?: number;
|
|
42
|
+
/** Weight for equipment class grouping preference (default: 1.0) */
|
|
43
|
+
equipmentClassWeight?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Extra weight for subsets whose size equals preferredSubsetSize.
|
|
46
|
+
* Zero keeps the historical size score. Raise it when full groups should outrank mix.
|
|
47
|
+
*/
|
|
48
|
+
closedGroupWeight?: number;
|
|
49
|
+
/** When set, competitor labels are checked against these lists instead of the IFAA defaults. */
|
|
50
|
+
solutionSpace?: SolutionSpace;
|
|
51
|
+
}
|
|
52
|
+
interface PartitionResult {
|
|
53
|
+
subsets: Competitor[][];
|
|
54
|
+
score: number;
|
|
55
|
+
metadata: {
|
|
56
|
+
totalCompetitors: number;
|
|
57
|
+
totalSubsets: number;
|
|
58
|
+
averageSubsetSize: number;
|
|
59
|
+
scoreBreakdown: {
|
|
60
|
+
sizeScore: number;
|
|
61
|
+
closedGroupScore: number;
|
|
62
|
+
genderScore: number;
|
|
63
|
+
ageCategoryScore: number;
|
|
64
|
+
equipmentClassScore: number;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
interface AlgorithmResult {
|
|
69
|
+
result: PartitionResult;
|
|
70
|
+
executionTime: number;
|
|
71
|
+
algorithmUsed: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Utility functions for competitor partitioning
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
type ResolvedPartitionOptions = Omit<Required<PartitionOptions>, 'solutionSpace'> & {
|
|
79
|
+
solutionSpace?: SolutionSpace;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Default partition options
|
|
83
|
+
*/
|
|
84
|
+
declare const DEFAULT_OPTIONS: ResolvedPartitionOptions;
|
|
85
|
+
/**
|
|
86
|
+
* Validates that a partition solution meets all constraints
|
|
87
|
+
*/
|
|
88
|
+
declare function validatePartition(competitors: Competitor[], subsets: Competitor[][], options?: Partial<PartitionOptions>): {
|
|
89
|
+
valid: boolean;
|
|
90
|
+
errors: string[];
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Calculates the quality score for a partition solution
|
|
94
|
+
*/
|
|
95
|
+
declare function calculateScore(subsets: Competitor[][], options?: Partial<PartitionOptions>): {
|
|
96
|
+
score: number;
|
|
97
|
+
breakdown: PartitionResult['metadata']['scoreBreakdown'];
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Creates a partition result with metadata
|
|
101
|
+
*/
|
|
102
|
+
declare function createPartitionResult(subsets: Competitor[][], options?: Partial<PartitionOptions>): PartitionResult;
|
|
103
|
+
/**
|
|
104
|
+
* Helper to get competitors by guardian relationship
|
|
105
|
+
*/
|
|
106
|
+
declare function getGuardianGroups(competitors: Competitor[]): Map<string, Competitor[]>;
|
|
107
|
+
/**
|
|
108
|
+
* Groups competitors into guardian families (guardian + their dependents)
|
|
109
|
+
* Returns an array where each element is either a single competitor or a guardian family
|
|
110
|
+
*/
|
|
111
|
+
declare function createGuardianFamilies(competitors: Competitor[]): Competitor[][];
|
|
112
|
+
/**
|
|
113
|
+
* Validates that guardian constraints are satisfied in the partition
|
|
114
|
+
*/
|
|
115
|
+
declare function validateGuardianConstraints(competitors: Competitor[], subsets: Competitor[][]): {
|
|
116
|
+
valid: boolean;
|
|
117
|
+
errors: string[];
|
|
118
|
+
};
|
|
119
|
+
declare function isValidCompetitor(competitor: unknown, space?: SolutionSpace): competitor is Competitor;
|
|
120
|
+
/**
|
|
121
|
+
* Validates an array of competitors
|
|
122
|
+
*/
|
|
123
|
+
declare function validateCompetitors(competitors: any[], space?: SolutionSpace): {
|
|
124
|
+
valid: boolean;
|
|
125
|
+
errors: string[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/** A target smaller than this is not a group. Size 1 and size 2 are never emitted. */
|
|
129
|
+
declare const MINIMUM_SUBSET_SIZE = 3;
|
|
130
|
+
declare function smallestAllowedSubset(requestedMin: number): number;
|
|
131
|
+
/**
|
|
132
|
+
* Drops or repairs targets of size 1 or 2.
|
|
133
|
+
* A short group is filled from donors, merged into another group, or removed.
|
|
134
|
+
* Removed archers are not given a target.
|
|
135
|
+
*/
|
|
136
|
+
declare function retainLegalSubsets(subsets: Competitor[][], min: number, max: number): Competitor[][];
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Constants for competitor categories and validation
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Valid equipment classes
|
|
144
|
+
*/
|
|
145
|
+
declare const EQUIPMENT_CLASSES: readonly EquipmentClass[];
|
|
146
|
+
/**
|
|
147
|
+
* Valid age groups (ordered from youngest to oldest)
|
|
148
|
+
*/
|
|
149
|
+
declare const AGE_GROUPS: readonly AgeGroup[];
|
|
150
|
+
/**
|
|
151
|
+
* Valid genders
|
|
152
|
+
*/
|
|
153
|
+
declare const GENDERS: readonly Gender[];
|
|
154
|
+
/**
|
|
155
|
+
* Equipment class descriptions for documentation
|
|
156
|
+
*/
|
|
157
|
+
declare const EQUIPMENT_CLASS_DESCRIPTIONS: Record<EquipmentClass, string>;
|
|
158
|
+
/**
|
|
159
|
+
* Age group descriptions for documentation
|
|
160
|
+
*/
|
|
161
|
+
declare const AGE_GROUP_DESCRIPTIONS: Record<AgeGroup, string>;
|
|
162
|
+
/**
|
|
163
|
+
* Gender descriptions for documentation
|
|
164
|
+
*/
|
|
165
|
+
declare const GENDER_DESCRIPTIONS: Record<Gender, string>;
|
|
166
|
+
/**
|
|
167
|
+
* Checks if a value is a valid equipment class
|
|
168
|
+
*/
|
|
169
|
+
declare function isValidEquipmentClass(value: string, allowed?: readonly string[]): boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Checks if a value is a valid age group
|
|
172
|
+
*/
|
|
173
|
+
declare function isValidAgeGroup(value: string, allowed?: readonly string[]): boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Checks if a value is a valid gender
|
|
176
|
+
*/
|
|
177
|
+
declare function isValidGender(value: string, allowed?: readonly string[]): boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Gets the age group order for comparison (lower number = younger)
|
|
180
|
+
*/
|
|
181
|
+
declare function getAgeGroupOrder(ageGroup: AgeGroup): number;
|
|
182
|
+
/**
|
|
183
|
+
* Compares two age groups (-1 if a is younger, 0 if same, 1 if a is older)
|
|
184
|
+
*/
|
|
185
|
+
declare function compareAgeGroups(a: AgeGroup, b: AgeGroup): number;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Mixed Integer Programming approach for competitor partitioning
|
|
189
|
+
*
|
|
190
|
+
* This algorithm formulates the partitioning problem as a MIP problem where:
|
|
191
|
+
* - Binary variables x[i][j] indicate if competitor i is in subset j
|
|
192
|
+
* - Objective function maximizes the quality score based on preferences
|
|
193
|
+
* - Constraints ensure valid partitioning
|
|
194
|
+
*/
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Solves the competitor partitioning problem using Mixed Integer Programming
|
|
198
|
+
*/
|
|
199
|
+
declare function solveMIP(competitors: Competitor[], options?: Partial<PartitionOptions>): AlgorithmResult;
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Mixed Integer Programming approach using HiGHS solver
|
|
203
|
+
*
|
|
204
|
+
* This algorithm formulates the partitioning problem as a proper MIP problem where:
|
|
205
|
+
* - Binary variables x[i][j] indicate if competitor i is in subset j
|
|
206
|
+
* - Objective function maximizes the quality score based on preferences
|
|
207
|
+
* - Constraints ensure valid partitioning and guardian relationships
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Solves the competitor partitioning problem using HiGHS MIP solver
|
|
212
|
+
*/
|
|
213
|
+
declare function solveMIPWithLP(competitors: Competitor[], options?: Partial<PartitionOptions>): Promise<AlgorithmResult>;
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clustering approach for competitor partitioning
|
|
217
|
+
*
|
|
218
|
+
* This algorithm uses a modified K-means clustering approach where:
|
|
219
|
+
* - Competitors are represented as feature vectors
|
|
220
|
+
* - Distance metric considers gender, age category, and equipment class
|
|
221
|
+
* - Clusters are formed and then adjusted to meet size constraints
|
|
222
|
+
* - Post-processing ensures all constraints are satisfied
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Solves the competitor partitioning problem using clustering with guardian constraints
|
|
227
|
+
*/
|
|
228
|
+
declare function solveClustering(competitors: Competitor[], options?: Partial<PartitionOptions>): AlgorithmResult;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Group Planner Library
|
|
232
|
+
*
|
|
233
|
+
* A TypeScript library for competitor partitioning algorithms.
|
|
234
|
+
* Provides three different approaches: Mixed Integer Programming,
|
|
235
|
+
* K-means Clustering, and Branch-and-bound.
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Main solver function - single API entry point for all algorithms
|
|
240
|
+
*
|
|
241
|
+
* @param competitors - Array of competitor objects to partition
|
|
242
|
+
* @param options - Configuration options including algorithm selection
|
|
243
|
+
* @returns Algorithm result with partitioned subsets
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* // Use specific algorithm
|
|
248
|
+
* const result = solve(competitors, { algorithm: 'mip' });
|
|
249
|
+
*
|
|
250
|
+
* // Use auto-selection (runs all algorithms, returns best)
|
|
251
|
+
* const result = solve(competitors, { algorithm: 'auto' });
|
|
252
|
+
*
|
|
253
|
+
* // Use default algorithm (auto)
|
|
254
|
+
* const result = solve(competitors);
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
declare function solve(competitors: Competitor[], options?: Partial<PartitionOptions>): Promise<AlgorithmResult>;
|
|
258
|
+
/**
|
|
259
|
+
* Runs all algorithms and returns detailed results for comparison
|
|
260
|
+
*
|
|
261
|
+
* @param competitors - Array of competitor objects to partition
|
|
262
|
+
* @param options - Configuration options (algorithm option is ignored)
|
|
263
|
+
* @returns Object with best result and all algorithm results
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```typescript
|
|
267
|
+
* const { bestResult, allResults } = compareAlgorithms(competitors);
|
|
268
|
+
* console.log('Best:', bestResult.algorithmUsed);
|
|
269
|
+
* allResults.forEach(r => console.log(`${r.algorithmUsed}: ${r.result.score}`));
|
|
270
|
+
* ```
|
|
271
|
+
*/
|
|
272
|
+
declare function compareAlgorithms(competitors: Competitor[], options?: Partial<PartitionOptions>): Promise<{
|
|
273
|
+
bestResult: AlgorithmResult;
|
|
274
|
+
allResults: AlgorithmResult[];
|
|
275
|
+
}>;
|
|
276
|
+
declare const version = "0.1.0";
|
|
277
|
+
|
|
278
|
+
export { AGE_GROUPS, AGE_GROUP_DESCRIPTIONS, type AgeGroup, type Algorithm, type AlgorithmResult, type Competitor, DEFAULT_OPTIONS, EQUIPMENT_CLASSES, EQUIPMENT_CLASS_DESCRIPTIONS, type EquipmentClass, GENDERS, GENDER_DESCRIPTIONS, type Gender, MINIMUM_SUBSET_SIZE, type PartitionOptions, type PartitionResult, type SolutionSpace, calculateScore, compareAgeGroups, compareAlgorithms, createGuardianFamilies, createPartitionResult, getAgeGroupOrder, getGuardianGroups, isValidAgeGroup, isValidCompetitor, isValidEquipmentClass, isValidGender, retainLegalSubsets, smallestAllowedSubset, solve, solveClustering, solveMIP, solveMIPWithLP, validateCompetitors, validateGuardianConstraints, validatePartition, version };
|