powerball-quantum 1.0.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/src/cli.ts ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { predict, updateData, formatPick } from './index';
5
+
6
+ const program = new Command();
7
+
8
+ program
9
+ .name('powerball-quantum')
10
+ .description('Powerball number predictor using quantum-inspired algorithm')
11
+ .version('1.0.0');
12
+
13
+ program
14
+ .command('predict')
15
+ .description('Generate predicted Powerball numbers')
16
+ .option('-c, --count <number>', 'Number of picks to generate', '5')
17
+ .option('-a, --analysis', 'Show signal analysis', false)
18
+ .action(async (options) => {
19
+ console.log('');
20
+ console.log('šŸŽ± POWERBALL QUANTUM PREDICTOR');
21
+ console.log('═'.repeat(50));
22
+
23
+ try {
24
+ const picks = await predict({
25
+ count: parseInt(options.count),
26
+ showAnalysis: options.analysis
27
+ });
28
+
29
+ console.log('\n⭐ RECOMMENDED PICKS:\n');
30
+
31
+ picks.forEach((pick, i) => {
32
+ const score = pick.score?.toFixed(1) || '?';
33
+ console.log(` #${i + 1} ${formatPick(pick)} (score: ${score})`);
34
+ });
35
+
36
+ console.log('\n' + '═'.repeat(50));
37
+ console.log('Good luck! šŸ€\n');
38
+ } catch (error) {
39
+ console.error('Error:', error);
40
+ process.exit(1);
41
+ }
42
+ });
43
+
44
+ program
45
+ .command('update')
46
+ .description('Update Powerball data from NY Lottery API')
47
+ .action(async () => {
48
+ console.log('');
49
+ console.log('šŸ“„ Updating Powerball data...');
50
+ console.log('═'.repeat(50));
51
+
52
+ try {
53
+ await updateData();
54
+ console.log('āœ… Data updated successfully!\n');
55
+ } catch (error) {
56
+ console.error('Error updating data:', error);
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ program
62
+ .command('quick')
63
+ .description('Get one quick pick')
64
+ .action(async () => {
65
+ try {
66
+ const picks = await predict({ count: 1 });
67
+ if (picks[0]) {
68
+ console.log('\nšŸŽ« Your lucky numbers:');
69
+ console.log(`\n ${formatPick(picks[0])}\n`);
70
+ }
71
+ } catch (error) {
72
+ console.error('Error:', error);
73
+ process.exit(1);
74
+ }
75
+ });
76
+
77
+ // Default action (no command)
78
+ if (process.argv.length === 2) {
79
+ program.parse(['node', 'cli', 'predict']);
80
+ } else {
81
+ program.parse();
82
+ }
package/src/data.ts ADDED
@@ -0,0 +1,83 @@
1
+ import axios from 'axios';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { Draw, DATA_URL } from './types';
5
+
6
+ const DATA_FILE = path.join(__dirname, '..', 'powerball_data.csv');
7
+
8
+ /**
9
+ * Download latest Powerball data from NY Lottery API
10
+ */
11
+ export async function downloadData(): Promise<string> {
12
+ console.log('Downloading latest Powerball data...');
13
+ const response = await axios.get(DATA_URL);
14
+ fs.writeFileSync(DATA_FILE, response.data);
15
+ console.log('Download complete!');
16
+ return DATA_FILE;
17
+ }
18
+
19
+ /**
20
+ * Parse CSV data into Draw objects
21
+ */
22
+ export function parseCSV(csvContent: string): Draw[] {
23
+ const lines = csvContent.trim().split('\n');
24
+ const draws: Draw[] = [];
25
+
26
+ // Skip header
27
+ for (let i = 1; i < lines.length; i++) {
28
+ const line = lines[i].trim();
29
+ if (!line) continue;
30
+
31
+ const parts = line.split(',');
32
+ if (parts.length < 3) continue;
33
+
34
+ const dateStr = parts[0];
35
+ const numbersStr = parts[1];
36
+ const multiplier = parseInt(parts[2]) || 2;
37
+
38
+ const numbers = numbersStr.split(' ').map(n => parseInt(n.trim()));
39
+ if (numbers.length < 6) continue;
40
+
41
+ const date = new Date(dateStr);
42
+ // Filter for current rules (post Oct 7, 2015)
43
+ if (date < new Date('2015-10-07')) continue;
44
+
45
+ draws.push({
46
+ date,
47
+ whiteBalls: numbers.slice(0, 5),
48
+ powerball: numbers[5],
49
+ multiplier
50
+ });
51
+ }
52
+
53
+ return draws;
54
+ }
55
+
56
+ /**
57
+ * Load data from file or download if not exists
58
+ */
59
+ export async function loadData(): Promise<Draw[]> {
60
+ let csvContent: string;
61
+
62
+ if (fs.existsSync(DATA_FILE)) {
63
+ csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
64
+ } else {
65
+ await downloadData();
66
+ csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
67
+ }
68
+
69
+ const draws = parseCSV(csvContent);
70
+ console.log(`Loaded ${draws.length} draws (current rules since 2015-10-07)`);
71
+ return draws;
72
+ }
73
+
74
+ /**
75
+ * Update data (force re-download)
76
+ */
77
+ export async function updateData(): Promise<Draw[]> {
78
+ await downloadData();
79
+ const csvContent = fs.readFileSync(DATA_FILE, 'utf-8');
80
+ const draws = parseCSV(csvContent);
81
+ console.log(`Updated! ${draws.length} draws loaded.`);
82
+ return draws;
83
+ }
package/src/index.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { Draw, Pick, PredictOptions } from './types';
2
+ import { loadData, updateData } from './data';
3
+ import {
4
+ calculateMomentum,
5
+ calculateZScores,
6
+ calculateRecentMomentum,
7
+ analyzePairFrequency,
8
+ quantumSelect,
9
+ checkSumRange,
10
+ checkOddEvenRatio,
11
+ checkHighLowBalance,
12
+ checkDecadeBalance,
13
+ checkEndingDiversity,
14
+ checkNoTripleConsecutive,
15
+ isInHistory,
16
+ buildHistorySet
17
+ } from './quantum';
18
+
19
+ export { Draw, Pick, PredictOptions } from './types';
20
+ export { loadData, updateData } from './data';
21
+
22
+ /**
23
+ * Generate a single quantum-powered pick
24
+ */
25
+ export function generatePick(
26
+ draws: Draw[],
27
+ historySet: Set<string>,
28
+ whiteMomentum: Record<number, number>,
29
+ redMomentum: Record<number, number>,
30
+ whiteZ: Record<number, number>,
31
+ redZ: Record<number, number>,
32
+ whiteRecent: Record<number, number>,
33
+ redRecent: Record<number, number>,
34
+ pairBonus: Record<number, number>
35
+ ): Pick | null {
36
+ const maxAttempts = 150000;
37
+
38
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
39
+ const whiteBalls = quantumSelect(whiteMomentum, whiteZ, whiteRecent, pairBonus, 5).sort((a, b) => a - b);
40
+ const powerball = quantumSelect(redMomentum, redZ, redRecent, {}, 1)[0];
41
+
42
+ // Apply all filters
43
+ if (isInHistory(whiteBalls, powerball, historySet)) continue;
44
+ if (!checkSumRange(whiteBalls)) continue;
45
+ if (!checkOddEvenRatio(whiteBalls)) continue;
46
+ if (!checkHighLowBalance(whiteBalls)) continue;
47
+ if (!checkDecadeBalance(whiteBalls)) continue;
48
+ if (!checkEndingDiversity(whiteBalls)) continue;
49
+ if (!checkNoTripleConsecutive(whiteBalls)) continue;
50
+
51
+ // Calculate score
52
+ let score = 0;
53
+ whiteBalls.forEach(n => {
54
+ score += (whiteMomentum[n] || 0) * 1.0;
55
+ score += Math.max(0, whiteZ[n] || 0) * 3.0;
56
+ score += (whiteRecent[n] || 0) * 2.0;
57
+ score += (pairBonus[n] || 0) * 0.5;
58
+ });
59
+ score += (redMomentum[powerball] || 0) * 2.0;
60
+ score += Math.max(0, redZ[powerball] || 0) * 4.0;
61
+
62
+ return { whiteBalls, powerball, score };
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * Main prediction function - call this to get picks!
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * import { predict } from 'powerball-quantum';
74
+ *
75
+ * const picks = await predict({ count: 5 });
76
+ * console.log(picks);
77
+ * ```
78
+ */
79
+ export async function predict(options: PredictOptions = {}): Promise<Pick[]> {
80
+ const { count = 5, showAnalysis = false } = options;
81
+
82
+ // Load data
83
+ const draws = await loadData();
84
+ const historySet = buildHistorySet(draws);
85
+
86
+ // Calculate all signals
87
+ const { white: wMom, red: rMom } = calculateMomentum(draws);
88
+ const { white: wZ, red: rZ } = calculateZScores(draws);
89
+ const { white: wRecent, red: rRecent } = calculateRecentMomentum(draws, 15);
90
+ const pairBonus = analyzePairFrequency(draws, 50);
91
+
92
+ if (showAnalysis) {
93
+ console.log('\nšŸ“Š Signal Analysis:');
94
+ const topMom = Object.entries(wMom).sort((a, b) => b[1] - a[1]).slice(0, 10);
95
+ console.log('šŸ”„ Hot Numbers:', topMom.map(([n]) => n));
96
+ const topZ = Object.entries(wZ).sort((a, b) => b[1] - a[1]).slice(0, 10);
97
+ console.log('ā° Overdue Numbers:', topZ.filter(([, z]) => z > 1).map(([n]) => n));
98
+ }
99
+
100
+ // Generate picks
101
+ const picks: Pick[] = [];
102
+ const seen = new Set<string>();
103
+
104
+ for (let i = 0; i < count * 10 && picks.length < count; i++) {
105
+ const pick = generatePick(
106
+ draws, historySet,
107
+ wMom, rMom, wZ, rZ, wRecent, rRecent, pairBonus
108
+ );
109
+
110
+ if (pick) {
111
+ const key = pick.whiteBalls.join(',') + '-' + pick.powerball;
112
+ if (!seen.has(key)) {
113
+ seen.add(key);
114
+ picks.push(pick);
115
+ }
116
+ }
117
+ }
118
+
119
+ // Sort by score
120
+ picks.sort((a, b) => (b.score || 0) - (a.score || 0));
121
+
122
+ return picks;
123
+ }
124
+
125
+ /**
126
+ * Quick function to get a single best pick
127
+ */
128
+ export async function quickPick(): Promise<Pick | null> {
129
+ const picks = await predict({ count: 1 });
130
+ return picks[0] || null;
131
+ }
132
+
133
+ /**
134
+ * Format pick for display
135
+ */
136
+ export function formatPick(pick: Pick): string {
137
+ const whites = pick.whiteBalls.map(n => n.toString().padStart(2, ' ')).join(' - ');
138
+ return `${whites} šŸ”“ ${pick.powerball}`;
139
+ }
140
+
141
+ // Default export
142
+ export default { predict, quickPick, updateData, loadData, formatPick };
package/src/quantum.ts ADDED
@@ -0,0 +1,277 @@
1
+ import { Draw, Pick, Scores, WHITE_BALL_RANGE, RED_BALL_RANGE } from './types';
2
+
3
+ /**
4
+ * Calculate exponential decay momentum scores
5
+ */
6
+ export function calculateMomentum(draws: Draw[], decayAlpha = 0.03): { white: Scores; red: Scores } {
7
+ const whiteScores: Scores = {};
8
+ const redScores: Scores = {};
9
+
10
+ // Initialize scores
11
+ for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
12
+ whiteScores[n] = 0.1;
13
+ }
14
+ for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
15
+ redScores[n] = 0.1;
16
+ }
17
+
18
+ // Sort by date ascending
19
+ const sorted = [...draws].sort((a, b) => a.date.getTime() - b.date.getTime());
20
+ const total = sorted.length;
21
+
22
+ sorted.forEach((draw, idx) => {
23
+ const t = total - 1 - idx; // t=0 for most recent
24
+ const weight = Math.exp(-decayAlpha * t);
25
+
26
+ draw.whiteBalls.forEach(w => {
27
+ if (whiteScores[w] !== undefined) {
28
+ whiteScores[w] += weight;
29
+ }
30
+ });
31
+
32
+ if (redScores[draw.powerball] !== undefined) {
33
+ redScores[draw.powerball] += weight;
34
+ }
35
+ });
36
+
37
+ return { white: whiteScores, red: redScores };
38
+ }
39
+
40
+ /**
41
+ * Calculate Z-scores for gap analysis (mean reversion)
42
+ */
43
+ export function calculateZScores(draws: Draw[]): { white: Scores; red: Scores } {
44
+ const whiteGaps: { [key: number]: number[] } = {};
45
+ const redGaps: { [key: number]: number[] } = {};
46
+ const lastSeenWhite: { [key: number]: number } = {};
47
+ const lastSeenRed: { [key: number]: number } = {};
48
+
49
+ // Initialize
50
+ for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
51
+ whiteGaps[n] = [];
52
+ lastSeenWhite[n] = -1;
53
+ }
54
+ for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
55
+ redGaps[n] = [];
56
+ lastSeenRed[n] = -1;
57
+ }
58
+
59
+ const sorted = [...draws].sort((a, b) => a.date.getTime() - b.date.getTime());
60
+
61
+ sorted.forEach((draw, idx) => {
62
+ // White balls
63
+ draw.whiteBalls.forEach(w => {
64
+ if (lastSeenWhite[w] !== -1) {
65
+ whiteGaps[w].push(idx - lastSeenWhite[w]);
66
+ }
67
+ lastSeenWhite[w] = idx;
68
+ });
69
+
70
+ // Red ball
71
+ if (lastSeenRed[draw.powerball] !== -1) {
72
+ redGaps[draw.powerball].push(idx - lastSeenRed[draw.powerball]);
73
+ }
74
+ lastSeenRed[draw.powerball] = idx;
75
+ });
76
+
77
+ const total = sorted.length;
78
+ const whiteZ: Scores = {};
79
+ const redZ: Scores = {};
80
+
81
+ // Calculate Z-scores
82
+ for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
83
+ const currentGap = total - 1 - lastSeenWhite[n];
84
+ const gaps = whiteGaps[n];
85
+
86
+ if (gaps.length === 0) {
87
+ whiteZ[n] = 0;
88
+ continue;
89
+ }
90
+
91
+ const mean = gaps.reduce((a, b) => a + b, 0) / gaps.length;
92
+ const variance = gaps.reduce((sum, g) => sum + Math.pow(g - mean, 2), 0) / gaps.length;
93
+ const stdev = Math.sqrt(variance) || 1;
94
+
95
+ whiteZ[n] = (currentGap - mean) / stdev;
96
+ }
97
+
98
+ for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
99
+ const currentGap = total - 1 - lastSeenRed[n];
100
+ const gaps = redGaps[n];
101
+
102
+ if (gaps.length === 0) {
103
+ redZ[n] = 0;
104
+ continue;
105
+ }
106
+
107
+ const mean = gaps.reduce((a, b) => a + b, 0) / gaps.length;
108
+ const variance = gaps.reduce((sum, g) => sum + Math.pow(g - mean, 2), 0) / gaps.length;
109
+ const stdev = Math.sqrt(variance) || 1;
110
+
111
+ redZ[n] = (currentGap - mean) / stdev;
112
+ }
113
+
114
+ return { white: whiteZ, red: redZ };
115
+ }
116
+
117
+ /**
118
+ * Calculate recent momentum (last N draws)
119
+ */
120
+ export function calculateRecentMomentum(draws: Draw[], nRecent = 15, decay = 0.15): { white: Scores; red: Scores } {
121
+ const whiteScores: Scores = {};
122
+ const redScores: Scores = {};
123
+
124
+ for (let n = WHITE_BALL_RANGE.min; n <= WHITE_BALL_RANGE.max; n++) {
125
+ whiteScores[n] = 0.1;
126
+ }
127
+ for (let n = RED_BALL_RANGE.min; n <= RED_BALL_RANGE.max; n++) {
128
+ redScores[n] = 0.1;
129
+ }
130
+
131
+ const sorted = [...draws].sort((a, b) => b.date.getTime() - a.date.getTime()).slice(0, nRecent);
132
+
133
+ sorted.forEach((draw, idx) => {
134
+ const weight = Math.exp(-decay * idx) * 2;
135
+
136
+ draw.whiteBalls.forEach(w => {
137
+ if (whiteScores[w] !== undefined) {
138
+ whiteScores[w] += weight;
139
+ }
140
+ });
141
+
142
+ if (redScores[draw.powerball] !== undefined) {
143
+ redScores[draw.powerball] += weight;
144
+ }
145
+ });
146
+
147
+ return { white: whiteScores, red: redScores };
148
+ }
149
+
150
+ /**
151
+ * Analyze pair frequency
152
+ */
153
+ export function analyzePairFrequency(draws: Draw[], topN = 50): Scores {
154
+ const pairCounts: Map<string, number> = new Map();
155
+
156
+ draws.forEach(draw => {
157
+ const whites = [...draw.whiteBalls].sort((a, b) => a - b);
158
+ for (let i = 0; i < whites.length; i++) {
159
+ for (let j = i + 1; j < whites.length; j++) {
160
+ const key = `${whites[i]}-${whites[j]}`;
161
+ pairCounts.set(key, (pairCounts.get(key) || 0) + 1);
162
+ }
163
+ }
164
+ });
165
+
166
+ const pairBonus: Scores = {};
167
+ const sorted = [...pairCounts.entries()]
168
+ .sort((a, b) => b[1] - a[1])
169
+ .slice(0, topN);
170
+
171
+ sorted.forEach(([pair, count]) => {
172
+ const [a, b] = pair.split('-').map(Number);
173
+ pairBonus[a] = (pairBonus[a] || 0) + count * 0.1;
174
+ pairBonus[b] = (pairBonus[b] || 0) + count * 0.1;
175
+ });
176
+
177
+ return pairBonus;
178
+ }
179
+
180
+ /**
181
+ * Weighted random selection
182
+ */
183
+ export function weightedSample(candidates: number[], weights: number[], k: number): number[] {
184
+ const selected = new Set<number>();
185
+ const totalWeight = weights.reduce((a, b) => a + b, 0);
186
+
187
+ while (selected.size < k) {
188
+ let r = Math.random() * totalWeight;
189
+ for (let i = 0; i < candidates.length; i++) {
190
+ r -= weights[i];
191
+ if (r <= 0) {
192
+ selected.add(candidates[i]);
193
+ break;
194
+ }
195
+ }
196
+ }
197
+
198
+ return [...selected];
199
+ }
200
+
201
+ /**
202
+ * Quantum selector combining all signals
203
+ */
204
+ export function quantumSelect(
205
+ momentum: Scores,
206
+ zScores: Scores,
207
+ recentMomentum: Scores,
208
+ pairBonus: Scores,
209
+ nPicks: number
210
+ ): number[] {
211
+ const candidates = Object.keys(momentum).map(Number);
212
+ const weights = candidates.map(n => {
213
+ const base = momentum[n] || 0;
214
+ const zBoost = Math.max(0, zScores[n] || 0) * 5;
215
+ const recentBoost = (recentMomentum[n] || 0) * 1.5;
216
+ const pairB = (pairBonus[n] || 0) * 0.5;
217
+ return Math.max(0.1, base + zBoost + recentBoost + pairB);
218
+ });
219
+
220
+ return weightedSample(candidates, weights, nPicks);
221
+ }
222
+
223
+ // --- FILTERS ---
224
+
225
+ export function checkSumRange(whiteBalls: number[], min = 130, max = 220): boolean {
226
+ const sum = whiteBalls.reduce((a, b) => a + b, 0);
227
+ return sum >= min && sum <= max;
228
+ }
229
+
230
+ export function checkOddEvenRatio(whiteBalls: number[]): boolean {
231
+ const odds = whiteBalls.filter(x => x % 2 !== 0).length;
232
+ const evens = 5 - odds;
233
+ return (odds === 3 && evens === 2) || (odds === 2 && evens === 3);
234
+ }
235
+
236
+ export function checkHighLowBalance(whiteBalls: number[], threshold = 35): boolean {
237
+ const lows = whiteBalls.filter(x => x < threshold).length;
238
+ const highs = 5 - lows;
239
+ return (lows === 3 && highs === 2) || (lows === 2 && highs === 3);
240
+ }
241
+
242
+ export function checkDecadeBalance(whiteBalls: number[]): boolean {
243
+ const decades = new Set(whiteBalls.map(n => Math.floor(n / 10)));
244
+ return decades.size >= 3;
245
+ }
246
+
247
+ export function checkEndingDiversity(whiteBalls: number[]): boolean {
248
+ const endings = new Set(whiteBalls.map(n => n % 10));
249
+ return endings.size >= 4;
250
+ }
251
+
252
+ export function checkNoTripleConsecutive(whiteBalls: number[]): boolean {
253
+ const sorted = [...whiteBalls].sort((a, b) => a - b);
254
+ const diffs = [];
255
+ for (let i = 0; i < sorted.length - 1; i++) {
256
+ diffs.push(sorted[i + 1] - sorted[i]);
257
+ }
258
+ const consecutiveOnes = diffs.filter(d => d === 1).length;
259
+ return consecutiveOnes < 2;
260
+ }
261
+
262
+ /**
263
+ * Check if combination exists in history
264
+ */
265
+ export function isInHistory(whiteBalls: number[], powerball: number, history: Set<string>): boolean {
266
+ const key = [...whiteBalls].sort((a, b) => a - b).join(',') + '-' + powerball;
267
+ return history.has(key);
268
+ }
269
+
270
+ export function buildHistorySet(draws: Draw[]): Set<string> {
271
+ const history = new Set<string>();
272
+ draws.forEach(draw => {
273
+ const key = [...draw.whiteBalls].sort((a, b) => a - b).join(',') + '-' + draw.powerball;
274
+ history.add(key);
275
+ });
276
+ return history;
277
+ }
package/src/types.ts ADDED
@@ -0,0 +1,25 @@
1
+ export interface Draw {
2
+ date: Date;
3
+ whiteBalls: number[];
4
+ powerball: number;
5
+ multiplier: number;
6
+ }
7
+
8
+ export interface Pick {
9
+ whiteBalls: number[];
10
+ powerball: number;
11
+ score?: number;
12
+ }
13
+
14
+ export interface Scores {
15
+ [key: number]: number;
16
+ }
17
+
18
+ export interface PredictOptions {
19
+ count?: number;
20
+ showAnalysis?: boolean;
21
+ }
22
+
23
+ export const WHITE_BALL_RANGE = { min: 1, max: 69 };
24
+ export const RED_BALL_RANGE = { min: 1, max: 26 };
25
+ export const DATA_URL = "https://data.ny.gov/api/views/d6yy-54nr/rows.csv?accessType=DOWNLOAD";
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "resolveJsonModule": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }