seo-testing-tool 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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +228 -0
  3. package/dist/auth/GoogleOAuthService.d.ts +31 -0
  4. package/dist/auth/GoogleOAuthService.d.ts.map +1 -0
  5. package/dist/auth/GoogleOAuthService.js +69 -0
  6. package/dist/auth/GoogleOAuthService.js.map +1 -0
  7. package/dist/auth/TokenManager.d.ts +56 -0
  8. package/dist/auth/TokenManager.d.ts.map +1 -0
  9. package/dist/auth/TokenManager.js +190 -0
  10. package/dist/auth/TokenManager.js.map +1 -0
  11. package/dist/cli/commands.d.ts +36 -0
  12. package/dist/cli/commands.d.ts.map +1 -0
  13. package/dist/cli/commands.js +471 -0
  14. package/dist/cli/commands.js.map +1 -0
  15. package/dist/cli/formatters.d.ts +24 -0
  16. package/dist/cli/formatters.d.ts.map +1 -0
  17. package/dist/cli/formatters.js +175 -0
  18. package/dist/cli/formatters.js.map +1 -0
  19. package/dist/cli.d.ts +15 -0
  20. package/dist/cli.d.ts.map +1 -0
  21. package/dist/cli.js +62 -0
  22. package/dist/cli.js.map +1 -0
  23. package/dist/config/AnalysisConfig.d.ts +13 -0
  24. package/dist/config/AnalysisConfig.d.ts.map +1 -0
  25. package/dist/config/AnalysisConfig.js +10 -0
  26. package/dist/config/AnalysisConfig.js.map +1 -0
  27. package/dist/config/env.d.ts +30 -0
  28. package/dist/config/env.d.ts.map +1 -0
  29. package/dist/config/env.js +77 -0
  30. package/dist/config/env.js.map +1 -0
  31. package/dist/database/DatabaseService.d.ts +106 -0
  32. package/dist/database/DatabaseService.d.ts.map +1 -0
  33. package/dist/database/DatabaseService.js +180 -0
  34. package/dist/database/DatabaseService.js.map +1 -0
  35. package/dist/database/TimeSeriesService.d.ts +53 -0
  36. package/dist/database/TimeSeriesService.d.ts.map +1 -0
  37. package/dist/database/TimeSeriesService.js +122 -0
  38. package/dist/database/TimeSeriesService.js.map +1 -0
  39. package/dist/database/db.d.ts +20 -0
  40. package/dist/database/db.d.ts.map +1 -0
  41. package/dist/database/db.js +60 -0
  42. package/dist/database/db.js.map +1 -0
  43. package/dist/database/schema.d.ts +687 -0
  44. package/dist/database/schema.d.ts.map +1 -0
  45. package/dist/database/schema.js +62 -0
  46. package/dist/database/schema.js.map +1 -0
  47. package/dist/demo.d.ts +13 -0
  48. package/dist/demo.d.ts.map +1 -0
  49. package/dist/demo.js +149 -0
  50. package/dist/demo.js.map +1 -0
  51. package/dist/gsc/GSCDataFetcher.d.ts +100 -0
  52. package/dist/gsc/GSCDataFetcher.d.ts.map +1 -0
  53. package/dist/gsc/GSCDataFetcher.js +398 -0
  54. package/dist/gsc/GSCDataFetcher.js.map +1 -0
  55. package/dist/gsc/GSCPermissionService.d.ts +20 -0
  56. package/dist/gsc/GSCPermissionService.d.ts.map +1 -0
  57. package/dist/gsc/GSCPermissionService.js +84 -0
  58. package/dist/gsc/GSCPermissionService.js.map +1 -0
  59. package/dist/index.d.ts +12 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +41 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/notifications/NotificationService.d.ts +64 -0
  64. package/dist/notifications/NotificationService.d.ts.map +1 -0
  65. package/dist/notifications/NotificationService.js +115 -0
  66. package/dist/notifications/NotificationService.js.map +1 -0
  67. package/dist/orchestrator/SEOExperimentOrchestrator.d.ts +69 -0
  68. package/dist/orchestrator/SEOExperimentOrchestrator.d.ts.map +1 -0
  69. package/dist/orchestrator/SEOExperimentOrchestrator.js +199 -0
  70. package/dist/orchestrator/SEOExperimentOrchestrator.js.map +1 -0
  71. package/dist/services/ExportService.d.ts +52 -0
  72. package/dist/services/ExportService.d.ts.map +1 -0
  73. package/dist/services/ExportService.js +238 -0
  74. package/dist/services/ExportService.js.map +1 -0
  75. package/dist/smoke-test.d.ts +10 -0
  76. package/dist/smoke-test.d.ts.map +1 -0
  77. package/dist/smoke-test.js +73 -0
  78. package/dist/smoke-test.js.map +1 -0
  79. package/dist/stats/StatisticalEngine.d.ts +48 -0
  80. package/dist/stats/StatisticalEngine.d.ts.map +1 -0
  81. package/dist/stats/StatisticalEngine.js +205 -0
  82. package/dist/stats/StatisticalEngine.js.map +1 -0
  83. package/dist/stats/TDistribution.d.ts +28 -0
  84. package/dist/stats/TDistribution.d.ts.map +1 -0
  85. package/dist/stats/TDistribution.js +120 -0
  86. package/dist/stats/TDistribution.js.map +1 -0
  87. package/drizzle/0000_hot_whiplash.sql +51 -0
  88. package/drizzle/0001_open_photon.sql +9 -0
  89. package/drizzle/0002_faulty_the_watchers.sql +1 -0
  90. package/drizzle/meta/0000_snapshot.json +360 -0
  91. package/drizzle/meta/0001_snapshot.json +428 -0
  92. package/drizzle/meta/0002_snapshot.json +420 -0
  93. package/drizzle/meta/_journal.json +27 -0
  94. package/package.json +89 -0
@@ -0,0 +1,62 @@
1
+ import { sqliteTable, text, integer, real, uniqueIndex, index } from 'drizzle-orm/sqlite-core';
2
+ import { sql } from 'drizzle-orm';
3
+ // ── Users ───────────────────────────────────────────────────────────────────
4
+ export const users = sqliteTable('User', {
5
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
6
+ email: text('email').notNull().unique(),
7
+ createdAt: text('createdAt').notNull().default(sql `(datetime('now'))`),
8
+ });
9
+ // ── Tests ───────────────────────────────────────────────────────────────────
10
+ export const tests = sqliteTable('Test', {
11
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
12
+ name: text('name').notNull(),
13
+ urls: text('urls').notNull().default('[]'),
14
+ siteUrl: text('siteUrl').notNull(),
15
+ startDate: text('startDate').notNull(),
16
+ splitDate: text('splitDate').notNull(),
17
+ status: text('status').notNull().default('running'),
18
+ lastSyncAt: text('lastSyncAt'),
19
+ lastPValue: real('lastPValue'),
20
+ lastImprovement: real('lastImprovement'),
21
+ userId: text('userId').notNull().references(() => users.id),
22
+ createdAt: text('createdAt').notNull().default(sql `(datetime('now'))`),
23
+ updatedAt: text('updatedAt').notNull().default(sql `(datetime('now'))`),
24
+ }, (table) => [
25
+ index('Test_userId_idx').on(table.userId),
26
+ index('Test_status_idx').on(table.status),
27
+ ]);
28
+ // ── Metrics ─────────────────────────────────────────────────────────────────
29
+ export const metrics = sqliteTable('Metric', {
30
+ id: integer('id').primaryKey({ autoIncrement: true }),
31
+ testId: text('testId').notNull().references(() => tests.id, { onDelete: 'cascade' }),
32
+ date: text('date').notNull(),
33
+ clicks: integer('clicks').notNull(),
34
+ impressions: integer('impressions').notNull(),
35
+ gapFilled: integer('gapFilled', { mode: 'boolean' }).notNull().default(false),
36
+ filledAt: text('filledAt'),
37
+ createdAt: text('createdAt').notNull().default(sql `(datetime('now'))`),
38
+ }, (table) => [
39
+ uniqueIndex('Metric_testId_date_key').on(table.testId, table.date),
40
+ index('Metric_testId_date_idx').on(table.testId, table.date),
41
+ ]);
42
+ // ── OAuth Tokens ─────────────────────────────────────────────────────────────
43
+ export const oauthTokens = sqliteTable('OAuthToken', {
44
+ userId: text('userId').primaryKey().references(() => users.id),
45
+ accessToken: text('accessToken').notNull(),
46
+ refreshToken: text('refreshToken').notNull(),
47
+ expiresAt: integer('expiresAt').notNull(), // Unix timestamp in ms
48
+ createdAt: text('createdAt').notNull().default(sql `(datetime('now'))`),
49
+ updatedAt: text('updatedAt').notNull().default(sql `(datetime('now'))`),
50
+ });
51
+ // ── Audit Log ───────────────────────────────────────────────────────────────
52
+ export const auditLogs = sqliteTable('AuditLog', {
53
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
54
+ testId: text('testId').notNull(),
55
+ action: text('action').notNull(),
56
+ userId: text('userId').notNull(),
57
+ timestamp: text('timestamp').notNull().default(sql `(datetime('now'))`),
58
+ }, (table) => [
59
+ index('AuditLog_testId_idx').on(table.testId),
60
+ index('AuditLog_userId_idx').on(table.userId),
61
+ ]);
62
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/database/schema.ts"],"names":[],"mappings":"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"}
package/dist/demo.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Demo — Popola il database con dati finti per provare il tool
3
+ *
4
+ * Crea due esperimenti di esempio:
5
+ * 1. "Esperimento Positivo" — incremento reale di click (+50%) nel periodo post
6
+ * 2. "Esperimento Neutro" — nessuna differenza significativa tra pre e post
7
+ *
8
+ * Ogni esperimento ha 35 giorni di metriche pre-split e 35 giorni post-split.
9
+ *
10
+ * Uso: npx tsx src/demo.ts oppure npm run demo
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.d.ts","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
package/dist/demo.js ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Demo — Popola il database con dati finti per provare il tool
3
+ *
4
+ * Crea due esperimenti di esempio:
5
+ * 1. "Esperimento Positivo" — incremento reale di click (+50%) nel periodo post
6
+ * 2. "Esperimento Neutro" — nessuna differenza significativa tra pre e post
7
+ *
8
+ * Ogni esperimento ha 35 giorni di metriche pre-split e 35 giorni post-split.
9
+ *
10
+ * Uso: npx tsx src/demo.ts oppure npm run demo
11
+ */
12
+ import { config } from 'dotenv';
13
+ config();
14
+ import { eq } from 'drizzle-orm';
15
+ import { db, migrateDB, closeDb } from './database/db.js';
16
+ import { users, tests, metrics } from './database/schema.js';
17
+ // ── Generatore pseudo-random deterministico (seeded LCG) ─────────────────
18
+ class SeededRng {
19
+ state;
20
+ constructor(seed) {
21
+ this.state = seed;
22
+ }
23
+ /** Restituisce un float in [0, 1) */
24
+ next() {
25
+ this.state = (this.state * 1664525 + 1013904223) & 0x7fffffff;
26
+ return this.state / 0x7fffffff;
27
+ }
28
+ /** Distribuzione normale approssimata (Box-Muller) */
29
+ normal(mean, stdDev) {
30
+ const u1 = this.next() || 0.0001;
31
+ const u2 = this.next();
32
+ const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
33
+ return mean + z * stdDev;
34
+ }
35
+ }
36
+ // ── Helpers ──────────────────────────────────────────────────────────────────
37
+ /** Genera una data ISO (YYYY-MM-DD) a partire da un offset in giorni da oggi */
38
+ function dateOffset(daysFromToday) {
39
+ const d = new Date();
40
+ d.setDate(d.getDate() + daysFromToday);
41
+ return d.toISOString().slice(0, 10);
42
+ }
43
+ /** Genera N giorni di metriche (clicks/impressions) con rumore realistico */
44
+ function generateMetrics(rng, testId, startOffset, days, clickMean, clickStdDev, impressionMultiplier) {
45
+ const rows = [];
46
+ for (let i = 0; i < days; i++) {
47
+ const clicks = Math.max(0, Math.round(rng.normal(clickMean, clickStdDev)));
48
+ const impressions = Math.max(clicks, Math.round(rng.normal(clicks * impressionMultiplier, clicks * impressionMultiplier * 0.15)));
49
+ rows.push({
50
+ testId,
51
+ date: dateOffset(startOffset + i),
52
+ clicks,
53
+ impressions,
54
+ });
55
+ }
56
+ return rows;
57
+ }
58
+ // ── Main ─────────────────────────────────────────────────────────────────────
59
+ function demo() {
60
+ console.log('');
61
+ console.log('╔══════════════════════════════════════════╗');
62
+ console.log('║ SEO Testing Tool — Modalità Demo ║');
63
+ console.log('╚══════════════════════════════════════════╝');
64
+ console.log('');
65
+ try {
66
+ migrateDB();
67
+ console.log('[OK] Database pronto');
68
+ // ── Utente demo ──────────────────────────────────────────────────────
69
+ const DEMO_USER_ID = 'demo-user';
70
+ const existing = db.select().from(users).where(eq(users.id, DEMO_USER_ID)).get();
71
+ if (!existing) {
72
+ db.insert(users).values({ id: DEMO_USER_ID, email: 'demo@seo-tool.local' }).run();
73
+ }
74
+ console.log('[OK] Utente demo pronto');
75
+ // ── Configurazione esperimenti ───────────────────────────────────────
76
+ const DAYS_PRE = 35;
77
+ const DAYS_POST = 35;
78
+ const START_OFFSET = -(DAYS_PRE + DAYS_POST); // inizio = 70 giorni fa
79
+ const SPLIT_OFFSET = START_OFFSET + DAYS_PRE; // split = 35 giorni fa
80
+ const startDate = dateOffset(START_OFFSET);
81
+ const splitDate = dateOffset(SPLIT_OFFSET);
82
+ // ── 1. Esperimento Positivo ──────────────────────────────────────────
83
+ const rngPositive = new SeededRng(42);
84
+ const testPositive = db.insert(tests).values({
85
+ name: 'Esperimento Positivo',
86
+ siteUrl: 'sc-domain:esempio-positivo.it',
87
+ urls: JSON.stringify(['https://esempio-positivo.it/landing-page']),
88
+ startDate,
89
+ splitDate,
90
+ status: 'completed',
91
+ lastPValue: 0.001,
92
+ lastImprovement: 46.6,
93
+ userId: DEMO_USER_ID,
94
+ }).returning().get();
95
+ // Pre-split: ~50 click/giorno, post-split: ~75 click/giorno (+50%)
96
+ const prePositive = generateMetrics(rngPositive, testPositive.id, START_OFFSET, DAYS_PRE, 1500, 100, 10);
97
+ const postPositive = generateMetrics(rngPositive, testPositive.id, SPLIT_OFFSET, DAYS_POST, 2200, 150, 10);
98
+ for (const row of [...prePositive, ...postPositive]) {
99
+ db.insert(metrics).values(row).run();
100
+ }
101
+ console.log(`[OK] Esperimento Positivo creato — ID: ${testPositive.id.slice(0, 8)}...`);
102
+ console.log(` Pre: ${DAYS_PRE} giorni (~1500 click/giorno)`);
103
+ console.log(` Post: ${DAYS_POST} giorni (~2200 click/giorno, +46%)`);
104
+ // ── 2. Esperimento Neutro ────────────────────────────────────────────
105
+ const rngNeutral = new SeededRng(123);
106
+ const testNeutral = db.insert(tests).values({
107
+ name: 'Esperimento Neutro',
108
+ siteUrl: 'sc-domain:esempio-neutro.it',
109
+ urls: JSON.stringify(['https://esempio-neutro.it/pagina-test']),
110
+ startDate,
111
+ splitDate,
112
+ status: 'completed',
113
+ lastPValue: 0.72,
114
+ lastImprovement: 1.2,
115
+ userId: DEMO_USER_ID,
116
+ }).returning().get();
117
+ // Pre-split: ~50 click/giorno, post-split: ~51 click/giorno (nessun cambiamento reale)
118
+ const preNeutral = generateMetrics(rngNeutral, testNeutral.id, START_OFFSET, DAYS_PRE, 50, 8, 10);
119
+ const postNeutral = generateMetrics(rngNeutral, testNeutral.id, SPLIT_OFFSET, DAYS_POST, 51, 8, 10);
120
+ for (const row of [...preNeutral, ...postNeutral]) {
121
+ db.insert(metrics).values(row).run();
122
+ }
123
+ console.log(`[OK] Esperimento Neutro creato — ID: ${testNeutral.id.slice(0, 8)}...`);
124
+ console.log(` Pre: ${DAYS_PRE} giorni (~50 click/giorno)`);
125
+ console.log(` Post: ${DAYS_POST} giorni (~51 click/giorno, nessun effetto)`);
126
+ // ── Riepilogo ────────────────────────────────────────────────────────
127
+ console.log('');
128
+ console.log('════════════════════════════════════════════');
129
+ console.log(' Demo completata! Prova questi comandi:');
130
+ console.log('');
131
+ console.log(` npx tsx src/cli.ts list`);
132
+ console.log(` npx tsx src/cli.ts status ${testPositive.id.slice(0, 8)}`);
133
+ console.log(` npx tsx src/cli.ts status ${testNeutral.id.slice(0, 8)}`);
134
+ console.log(` npx tsx src/cli.ts export ${testPositive.id.slice(0, 8)}`);
135
+ console.log('════════════════════════════════════════════');
136
+ console.log('');
137
+ closeDb();
138
+ process.exit(0);
139
+ }
140
+ catch (error) {
141
+ console.error('');
142
+ console.error('[ERRORE] Demo fallita:');
143
+ console.error(error instanceof Error ? error.message : error);
144
+ closeDb();
145
+ process.exit(1);
146
+ }
147
+ }
148
+ demo();
149
+ //# sourceMappingURL=demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"demo.js","sourceRoot":"","sources":["../src/demo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * GSCDataFetcher - Fetch dati da Google Search Console
3
+ */
4
+ import { GSCPermissionService } from './GSCPermissionService.js';
5
+ interface SearchAnalyticsParams {
6
+ siteUrl: string;
7
+ startDate: string;
8
+ endDate: string;
9
+ timezone?: string;
10
+ dimensions?: string[];
11
+ startRow?: number;
12
+ concurrentRequests?: number;
13
+ onProgress?: (progress: number) => void;
14
+ maxMemoryMB?: number;
15
+ accessToken?: string;
16
+ }
17
+ interface SearchAnalyticsRow {
18
+ keys: string[];
19
+ clicks: number;
20
+ impressions: number;
21
+ ctr?: number;
22
+ position?: number;
23
+ date?: string;
24
+ }
25
+ interface SearchAnalyticsData {
26
+ rows: SearchAnalyticsRow[];
27
+ hasDataGap?: boolean;
28
+ gapDays?: number;
29
+ message?: string;
30
+ lastAvailableDate?: string;
31
+ timezone?: string;
32
+ timezoneWarning?: string;
33
+ dstHandling?: string;
34
+ totalRows?: number;
35
+ stats?: {
36
+ averageClicks: number;
37
+ totalClicks: number;
38
+ totalImpressions: number;
39
+ daysWithData: number;
40
+ daysRequested: number;
41
+ };
42
+ processingInfo?: {
43
+ totalPages: number;
44
+ pageSize: number;
45
+ retriesPerformed: number;
46
+ progressCallbackInvoked: boolean;
47
+ peakMemoryUsageMB: number;
48
+ memoryLimitReached: boolean;
49
+ };
50
+ warnings?: Array<{
51
+ type: string;
52
+ message?: string;
53
+ startRow?: number;
54
+ }>;
55
+ }
56
+ export declare class GSCDataFetcher {
57
+ private readonly MAX_RETRIES;
58
+ private readonly INITIAL_DELAY;
59
+ private permissionService;
60
+ private retryCount;
61
+ private retryWarnings;
62
+ constructor(permissionService?: GSCPermissionService);
63
+ private resetRetryTracking;
64
+ /**
65
+ * Metodo interno per fare richieste alle API Google Search Console
66
+ */
67
+ private makeRequest;
68
+ /**
69
+ * Implementa exponential backoff per gestire rate limiting
70
+ */
71
+ private delay;
72
+ /**
73
+ * Fetch con retry e exponential backoff
74
+ */
75
+ private fetchWithRetry;
76
+ /**
77
+ * Calcola i giorni tra due date
78
+ */
79
+ private daysBetween;
80
+ /**
81
+ * Rileva gap temporali nei dati
82
+ */
83
+ private detectDataGap;
84
+ /**
85
+ * Calcola statistiche dai dati
86
+ */
87
+ private calculateStats;
88
+ fetchSearchAnalytics(accessToken: string, propertyUrl: string, options: {
89
+ startDate: string;
90
+ endDate: string;
91
+ }): Promise<SearchAnalyticsData>;
92
+ fetchSearchAnalytics(params: SearchAnalyticsParams): Promise<SearchAnalyticsData>;
93
+ private fetchSearchAnalyticsInternal;
94
+ /**
95
+ * Fetch con richieste parallele per migliorare le performance
96
+ */
97
+ private fetchWithConcurrency;
98
+ }
99
+ export {};
100
+ //# sourceMappingURL=GSCDataFetcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GSCDataFetcher.d.ts","sourceRoot":"","sources":["../../src/gsc/GSCDataFetcher.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,mBAAmB;IAC3B,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE;QACN,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,gBAAgB,EAAE,MAAM,CAAC;QACzB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,cAAc,CAAC,EAAE;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;QACzB,uBAAuB,EAAE,OAAO,CAAC;QACjC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC,CAAC;CACJ;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAK;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAQ;IACtC,OAAO,CAAC,iBAAiB,CAAuB;IAChD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAoE;gBAE7E,iBAAiB,CAAC,EAAE,oBAAoB;IAIpD,OAAO,CAAC,kBAAkB;IAK1B;;OAEG;YACW,WAAW;IA2CzB;;OAEG;YACW,KAAK;IAInB;;OAEG;YACW,cAAc;IA+B5B;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA2BrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAuBhB,oBAAoB,CACxB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAC9C,OAAO,CAAC,mBAAmB,CAAC;IACzB,oBAAoB,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC;YA+BzE,4BAA4B;IAwJ1C;;OAEG;YACW,oBAAoB;CA4HnC"}