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.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/auth/GoogleOAuthService.d.ts +31 -0
- package/dist/auth/GoogleOAuthService.d.ts.map +1 -0
- package/dist/auth/GoogleOAuthService.js +69 -0
- package/dist/auth/GoogleOAuthService.js.map +1 -0
- package/dist/auth/TokenManager.d.ts +56 -0
- package/dist/auth/TokenManager.d.ts.map +1 -0
- package/dist/auth/TokenManager.js +190 -0
- package/dist/auth/TokenManager.js.map +1 -0
- package/dist/cli/commands.d.ts +36 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +471 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/formatters.d.ts +24 -0
- package/dist/cli/formatters.d.ts.map +1 -0
- package/dist/cli/formatters.js +175 -0
- package/dist/cli/formatters.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +62 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/AnalysisConfig.d.ts +13 -0
- package/dist/config/AnalysisConfig.d.ts.map +1 -0
- package/dist/config/AnalysisConfig.js +10 -0
- package/dist/config/AnalysisConfig.js.map +1 -0
- package/dist/config/env.d.ts +30 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +77 -0
- package/dist/config/env.js.map +1 -0
- package/dist/database/DatabaseService.d.ts +106 -0
- package/dist/database/DatabaseService.d.ts.map +1 -0
- package/dist/database/DatabaseService.js +180 -0
- package/dist/database/DatabaseService.js.map +1 -0
- package/dist/database/TimeSeriesService.d.ts +53 -0
- package/dist/database/TimeSeriesService.d.ts.map +1 -0
- package/dist/database/TimeSeriesService.js +122 -0
- package/dist/database/TimeSeriesService.js.map +1 -0
- package/dist/database/db.d.ts +20 -0
- package/dist/database/db.d.ts.map +1 -0
- package/dist/database/db.js +60 -0
- package/dist/database/db.js.map +1 -0
- package/dist/database/schema.d.ts +687 -0
- package/dist/database/schema.d.ts.map +1 -0
- package/dist/database/schema.js +62 -0
- package/dist/database/schema.js.map +1 -0
- package/dist/demo.d.ts +13 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +149 -0
- package/dist/demo.js.map +1 -0
- package/dist/gsc/GSCDataFetcher.d.ts +100 -0
- package/dist/gsc/GSCDataFetcher.d.ts.map +1 -0
- package/dist/gsc/GSCDataFetcher.js +398 -0
- package/dist/gsc/GSCDataFetcher.js.map +1 -0
- package/dist/gsc/GSCPermissionService.d.ts +20 -0
- package/dist/gsc/GSCPermissionService.d.ts.map +1 -0
- package/dist/gsc/GSCPermissionService.js +84 -0
- package/dist/gsc/GSCPermissionService.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/NotificationService.d.ts +64 -0
- package/dist/notifications/NotificationService.d.ts.map +1 -0
- package/dist/notifications/NotificationService.js +115 -0
- package/dist/notifications/NotificationService.js.map +1 -0
- package/dist/orchestrator/SEOExperimentOrchestrator.d.ts +69 -0
- package/dist/orchestrator/SEOExperimentOrchestrator.d.ts.map +1 -0
- package/dist/orchestrator/SEOExperimentOrchestrator.js +199 -0
- package/dist/orchestrator/SEOExperimentOrchestrator.js.map +1 -0
- package/dist/services/ExportService.d.ts +52 -0
- package/dist/services/ExportService.d.ts.map +1 -0
- package/dist/services/ExportService.js +238 -0
- package/dist/services/ExportService.js.map +1 -0
- package/dist/smoke-test.d.ts +10 -0
- package/dist/smoke-test.d.ts.map +1 -0
- package/dist/smoke-test.js +73 -0
- package/dist/smoke-test.js.map +1 -0
- package/dist/stats/StatisticalEngine.d.ts +48 -0
- package/dist/stats/StatisticalEngine.d.ts.map +1 -0
- package/dist/stats/StatisticalEngine.js +205 -0
- package/dist/stats/StatisticalEngine.js.map +1 -0
- package/dist/stats/TDistribution.d.ts +28 -0
- package/dist/stats/TDistribution.d.ts.map +1 -0
- package/dist/stats/TDistribution.js +120 -0
- package/dist/stats/TDistribution.js.map +1 -0
- package/drizzle/0000_hot_whiplash.sql +51 -0
- package/drizzle/0001_open_photon.sql +9 -0
- package/drizzle/0002_faulty_the_watchers.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +360 -0
- package/drizzle/meta/0001_snapshot.json +428 -0
- package/drizzle/meta/0002_snapshot.json +420 -0
- package/drizzle/meta/_journal.json +27 -0
- package/package.json +89 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationService.d.ts","sourceRoot":"","sources":["../../src/notifications/NotificationService.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,UAAU;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE;QACf,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,iBAAiB,CAA0B;IAEnD;;OAEG;IACG,sBAAsB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;IActE;;OAEG;IACG,gBAAgB,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAgCnE;;OAEG;IACG,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAiC7F;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuBzB;;OAEG;IACG,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAI3F"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationService
|
|
3
|
+
*
|
|
4
|
+
* Gestisce l'invio di notifiche agli utenti:
|
|
5
|
+
* - Alert per test statisticamente significativi
|
|
6
|
+
* - Digest settimanali per utenti con test multipli
|
|
7
|
+
* - Prevenzione alert fatigue
|
|
8
|
+
*/
|
|
9
|
+
export class NotificationService {
|
|
10
|
+
notificationsSent = new Set();
|
|
11
|
+
/**
|
|
12
|
+
* Test 6.1 - Determina se inviare alert vittoria
|
|
13
|
+
*/
|
|
14
|
+
async shouldSendVictoryAlert(testResult) {
|
|
15
|
+
// Regola 1: p-value deve essere < 0.05 (statisticamente significativo)
|
|
16
|
+
if (testResult.pValue >= 0.05) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Regola 2: Miglioramento deve essere >= 5% (evita alert fatigue per miglioramenti minimi)
|
|
20
|
+
if (testResult.improvement < 0.05) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Test 6.1 - Invia alert vittoria
|
|
27
|
+
*/
|
|
28
|
+
async sendVictoryAlert(testResult) {
|
|
29
|
+
// Previeni invio duplicati
|
|
30
|
+
const notificationKey = `victory-${testResult.testId}`;
|
|
31
|
+
if (this.notificationsSent.has(notificationKey)) {
|
|
32
|
+
return {
|
|
33
|
+
sent: false,
|
|
34
|
+
reason: 'already_notified',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Verifica se deve inviare
|
|
38
|
+
const shouldSend = await this.shouldSendVictoryAlert(testResult);
|
|
39
|
+
if (!shouldSend) {
|
|
40
|
+
return {
|
|
41
|
+
sent: false,
|
|
42
|
+
reason: 'not_significant',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// Marca come inviata
|
|
46
|
+
this.notificationsSent.add(notificationKey);
|
|
47
|
+
// Qui invieremmo realmente l'email
|
|
48
|
+
// await emailService.send(...)
|
|
49
|
+
return {
|
|
50
|
+
sent: true,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Test 6.2 - Genera digest settimanale
|
|
55
|
+
*/
|
|
56
|
+
async generateWeeklyDigest(_userId, activeTests) {
|
|
57
|
+
// Ordina test per priorità (significativi prima)
|
|
58
|
+
const sortedTests = [...activeTests].sort((a, b) => {
|
|
59
|
+
// Test con p-value < 0.05 hanno priorità
|
|
60
|
+
const aSignificant = a.currentProgress.pValue < 0.05;
|
|
61
|
+
const bSignificant = b.currentProgress.pValue < 0.05;
|
|
62
|
+
if (aSignificant && !bSignificant)
|
|
63
|
+
return -1;
|
|
64
|
+
if (!aSignificant && bSignificant)
|
|
65
|
+
return 1;
|
|
66
|
+
// Altrimenti ordina per improvement
|
|
67
|
+
return b.currentProgress.improvement - a.currentProgress.improvement;
|
|
68
|
+
});
|
|
69
|
+
// Genera riepilogo complessivo
|
|
70
|
+
const significantTests = activeTests.filter(t => t.currentProgress.pValue < 0.05);
|
|
71
|
+
const summary = `${activeTests.length} test attivi. ${significantTests.length} statisticamente significativi.`;
|
|
72
|
+
// Genera body con sezioni per ogni test
|
|
73
|
+
let body = `Riepilogo Settimanale\n\n${summary}\n\n`;
|
|
74
|
+
for (const test of sortedTests) {
|
|
75
|
+
body += this.formatTestSection(test);
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
tests: sortedTests,
|
|
79
|
+
userEmail: 'user@example.com', // TODO: recuperare email reale da userId
|
|
80
|
+
subject: 'Report Settimanale - I Tuoi Test SEO',
|
|
81
|
+
body,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Formatta sezione test nel digest
|
|
86
|
+
*/
|
|
87
|
+
formatTestSection(test) {
|
|
88
|
+
const improvement = Math.round(test.currentProgress.improvement * 100);
|
|
89
|
+
const confidence = Math.round(test.currentProgress.confidenceLevel * 100);
|
|
90
|
+
let section = `\n--- ${test.testName} ---\n`;
|
|
91
|
+
section += `Durata: ${test.daysRunning} giorni\n`;
|
|
92
|
+
section += `Miglioramento: +${improvement}%\n`;
|
|
93
|
+
section += `Confidenza: ${confidence}%\n`;
|
|
94
|
+
// Azioni consigliate
|
|
95
|
+
if (test.currentProgress.pValue < 0.05) {
|
|
96
|
+
section += `✅ Azione: Pronto per applicare - Test statisticamente significativo!\n`;
|
|
97
|
+
}
|
|
98
|
+
else if (test.daysRunning < 10) {
|
|
99
|
+
section += `⏳ Azione: Continua a monitorare - Test ancora troppo giovane\n`;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
section += `👀 Azione: Continua a monitorare\n`;
|
|
103
|
+
}
|
|
104
|
+
section += '\n';
|
|
105
|
+
return section;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Test 6.2 - Determina se inviare digest settimanale
|
|
109
|
+
*/
|
|
110
|
+
async shouldSendWeeklyDigest(_userId, activeTests) {
|
|
111
|
+
// NON inviare se non ci sono test attivi
|
|
112
|
+
return activeTests.length > 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=NotificationService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationService.js","sourceRoot":"","sources":["../../src/notifications/NotificationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEOExperimentOrchestrator
|
|
3
|
+
*
|
|
4
|
+
* Orchestra il flusso completo di un esperimento SEO:
|
|
5
|
+
* - Fetch dati da GSC tramite GSCDataFetcher
|
|
6
|
+
* - Salvataggio dati tramite TimeSeriesService
|
|
7
|
+
* - Analisi statistica tramite StatisticalEngine
|
|
8
|
+
* - Aggiornamento stato test nel database
|
|
9
|
+
* - Notifiche tramite NotificationService
|
|
10
|
+
* - Audit logging per tracciabilità
|
|
11
|
+
*/
|
|
12
|
+
import { type DrizzleDB } from '../database/db.js';
|
|
13
|
+
import { GSCDataFetcher } from '../gsc/GSCDataFetcher.js';
|
|
14
|
+
import { StatisticalEngine } from '../stats/StatisticalEngine.js';
|
|
15
|
+
import { TimeSeriesService } from '../database/TimeSeriesService.js';
|
|
16
|
+
import { NotificationService } from '../notifications/NotificationService.js';
|
|
17
|
+
import { TokenManager } from '../auth/TokenManager.js';
|
|
18
|
+
export interface ExperimentResult {
|
|
19
|
+
testId: string;
|
|
20
|
+
status: 'updated' | 'completed' | 'insufficient_data' | 'error';
|
|
21
|
+
pValue: number | null;
|
|
22
|
+
percentageChange: number | null;
|
|
23
|
+
isSignificant: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
dataPointsBefore: number;
|
|
26
|
+
dataPointsAfter: number;
|
|
27
|
+
notificationSent: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface SyncResult {
|
|
30
|
+
totalTests: number;
|
|
31
|
+
successCount: number;
|
|
32
|
+
errorCount: number;
|
|
33
|
+
results: ExperimentResult[];
|
|
34
|
+
errors: Array<{
|
|
35
|
+
testId: string;
|
|
36
|
+
error: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
export interface OrchestratorDeps {
|
|
40
|
+
db?: DrizzleDB;
|
|
41
|
+
gscFetcher?: GSCDataFetcher;
|
|
42
|
+
statisticalEngine?: StatisticalEngine;
|
|
43
|
+
timeSeriesService?: TimeSeriesService;
|
|
44
|
+
notificationService?: NotificationService;
|
|
45
|
+
tokenManager?: TokenManager;
|
|
46
|
+
}
|
|
47
|
+
export declare class SEOExperimentOrchestrator {
|
|
48
|
+
private db;
|
|
49
|
+
private gscFetcher;
|
|
50
|
+
private statisticalEngine;
|
|
51
|
+
private timeSeriesService;
|
|
52
|
+
private notificationService;
|
|
53
|
+
private tokenManager;
|
|
54
|
+
constructor(deps?: OrchestratorDeps);
|
|
55
|
+
/**
|
|
56
|
+
* Esegue il flusso completo per un singolo esperimento SEO.
|
|
57
|
+
*/
|
|
58
|
+
runExperiment(testId: string): Promise<ExperimentResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Sincronizza tutti i test con status 'running'.
|
|
61
|
+
* Resiliente: se un test fallisce, logga l'errore e continua.
|
|
62
|
+
*/
|
|
63
|
+
syncAllActiveTests(): Promise<SyncResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Aggiorna lo stato di un test e registra nel log.
|
|
66
|
+
*/
|
|
67
|
+
private updateTestStatus;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=SEOExperimentOrchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SEOExperimentOrchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/SEOExperimentOrchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,mBAAmB,GAAG,OAAO,CAAC;IAChE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,aAAa,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,MAAM,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,EAAE,SAAS,CAAC;IACf,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,qBAAa,yBAAyB;IACpC,OAAO,CAAC,EAAE,CAAY;IACtB,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,YAAY,CAAe;gBAEvB,IAAI,CAAC,EAAE,gBAAgB;IASnC;;OAEG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsI9D;;;OAGG;IACG,kBAAkB,IAAI,OAAO,CAAC,UAAU,CAAC;IA6B/C;;OAEG;YACW,gBAAgB;CAgB/B"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEOExperimentOrchestrator
|
|
3
|
+
*
|
|
4
|
+
* Orchestra il flusso completo di un esperimento SEO:
|
|
5
|
+
* - Fetch dati da GSC tramite GSCDataFetcher
|
|
6
|
+
* - Salvataggio dati tramite TimeSeriesService
|
|
7
|
+
* - Analisi statistica tramite StatisticalEngine
|
|
8
|
+
* - Aggiornamento stato test nel database
|
|
9
|
+
* - Notifiche tramite NotificationService
|
|
10
|
+
* - Audit logging per tracciabilità
|
|
11
|
+
*/
|
|
12
|
+
import { eq } from 'drizzle-orm';
|
|
13
|
+
import { db as defaultDb } from '../database/db.js';
|
|
14
|
+
import { tests, metrics, auditLogs, users } from '../database/schema.js';
|
|
15
|
+
import { GSCDataFetcher } from '../gsc/GSCDataFetcher.js';
|
|
16
|
+
import { StatisticalEngine } from '../stats/StatisticalEngine.js';
|
|
17
|
+
import { TimeSeriesService } from '../database/TimeSeriesService.js';
|
|
18
|
+
import { NotificationService } from '../notifications/NotificationService.js';
|
|
19
|
+
import { TokenManager } from '../auth/TokenManager.js';
|
|
20
|
+
export class SEOExperimentOrchestrator {
|
|
21
|
+
db;
|
|
22
|
+
gscFetcher;
|
|
23
|
+
statisticalEngine;
|
|
24
|
+
timeSeriesService;
|
|
25
|
+
notificationService;
|
|
26
|
+
tokenManager;
|
|
27
|
+
constructor(deps) {
|
|
28
|
+
this.db = deps?.db ?? defaultDb;
|
|
29
|
+
this.gscFetcher = deps?.gscFetcher ?? new GSCDataFetcher();
|
|
30
|
+
this.statisticalEngine = deps?.statisticalEngine ?? new StatisticalEngine();
|
|
31
|
+
this.timeSeriesService = deps?.timeSeriesService ?? new TimeSeriesService(deps?.db);
|
|
32
|
+
this.notificationService = deps?.notificationService ?? new NotificationService();
|
|
33
|
+
this.tokenManager = deps?.tokenManager ?? new TokenManager({ clientId: '', clientSecret: '' });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Esegue il flusso completo per un singolo esperimento SEO.
|
|
37
|
+
*/
|
|
38
|
+
async runExperiment(testId) {
|
|
39
|
+
// 1. Carica test dal database
|
|
40
|
+
const test = this.db
|
|
41
|
+
.select()
|
|
42
|
+
.from(tests)
|
|
43
|
+
.where(eq(tests.id, testId))
|
|
44
|
+
.get();
|
|
45
|
+
if (!test) {
|
|
46
|
+
throw new Error(`Test ${testId} non trovato.`);
|
|
47
|
+
}
|
|
48
|
+
if (test.status !== 'running') {
|
|
49
|
+
throw new Error(`Test ${testId} non è in esecuzione (stato: ${test.status}).`);
|
|
50
|
+
}
|
|
51
|
+
// Carica user per email notifica
|
|
52
|
+
const user = this.db
|
|
53
|
+
.select()
|
|
54
|
+
.from(users)
|
|
55
|
+
.where(eq(users.id, test.userId))
|
|
56
|
+
.get();
|
|
57
|
+
// 2. Ottieni access token
|
|
58
|
+
const tokenResult = await this.tokenManager.getValidAccessToken(test.userId);
|
|
59
|
+
if (!tokenResult.success || !tokenResult.token) {
|
|
60
|
+
await this.updateTestStatus(testId, 'failed', test.userId);
|
|
61
|
+
throw new Error(`Token non valido per utente ${test.userId}: ${tokenResult.error}`);
|
|
62
|
+
}
|
|
63
|
+
// 3. Fetch nuovi dati da GSC
|
|
64
|
+
const today = new Date().toISOString().split('T')[0];
|
|
65
|
+
const startDateStr = test.startDate.split('T')[0];
|
|
66
|
+
const gscData = await this.gscFetcher.fetchSearchAnalytics(tokenResult.token, test.siteUrl, { startDate: startDateStr, endDate: today });
|
|
67
|
+
// 4. Salva nuovi dati nel database
|
|
68
|
+
if (gscData.rows && gscData.rows.length > 0) {
|
|
69
|
+
const timeSeriesData = gscData.rows.map((row) => ({
|
|
70
|
+
date: row.date || row.keys[0],
|
|
71
|
+
clicks: row.clicks,
|
|
72
|
+
impressions: row.impressions,
|
|
73
|
+
}));
|
|
74
|
+
await this.timeSeriesService.saveData(testId, timeSeriesData);
|
|
75
|
+
}
|
|
76
|
+
// 5. Ricarica tutte le metriche dal database (incluse le nuove)
|
|
77
|
+
const allMetrics = this.db
|
|
78
|
+
.select()
|
|
79
|
+
.from(metrics)
|
|
80
|
+
.where(eq(metrics.testId, testId))
|
|
81
|
+
.orderBy(metrics.date)
|
|
82
|
+
.all();
|
|
83
|
+
// 6. Dividi metriche in before/after alla splitDate
|
|
84
|
+
const splitDateStr = test.splitDate;
|
|
85
|
+
const beforeMetrics = allMetrics.filter((m) => m.date < splitDateStr);
|
|
86
|
+
const afterMetrics = allMetrics.filter((m) => m.date >= splitDateStr);
|
|
87
|
+
const beforeClicks = beforeMetrics.map((m) => m.clicks);
|
|
88
|
+
const afterClicks = afterMetrics.map((m) => m.clicks);
|
|
89
|
+
// 7. Esegui analisi statistica
|
|
90
|
+
const analysisResult = this.statisticalEngine.analyze({
|
|
91
|
+
before: beforeClicks,
|
|
92
|
+
after: afterClicks,
|
|
93
|
+
});
|
|
94
|
+
// 8. Determina nuovo stato
|
|
95
|
+
const newStatus = analysisResult.isSignificant ? 'completed' : 'running';
|
|
96
|
+
// 9. Aggiorna test nel database
|
|
97
|
+
this.db
|
|
98
|
+
.update(tests)
|
|
99
|
+
.set({
|
|
100
|
+
lastSyncAt: new Date().toISOString(),
|
|
101
|
+
lastPValue: analysisResult.pValue,
|
|
102
|
+
lastImprovement: analysisResult.percentageChange,
|
|
103
|
+
status: newStatus,
|
|
104
|
+
updatedAt: new Date().toISOString(),
|
|
105
|
+
})
|
|
106
|
+
.where(eq(tests.id, testId))
|
|
107
|
+
.run();
|
|
108
|
+
// 10. Registra nell'audit log
|
|
109
|
+
this.db
|
|
110
|
+
.insert(auditLogs)
|
|
111
|
+
.values({
|
|
112
|
+
testId,
|
|
113
|
+
action: 'experiment_synced',
|
|
114
|
+
userId: test.userId,
|
|
115
|
+
})
|
|
116
|
+
.run();
|
|
117
|
+
// 11. Gestisci notifiche (solo se significativo)
|
|
118
|
+
let notificationSent = false;
|
|
119
|
+
if (analysisResult.isSignificant && user?.email) {
|
|
120
|
+
const testResult = {
|
|
121
|
+
testId,
|
|
122
|
+
testName: test.name,
|
|
123
|
+
pValue: analysisResult.pValue,
|
|
124
|
+
improvement: analysisResult.percentageChange / 100,
|
|
125
|
+
confidenceLevel: 1 - analysisResult.pValue,
|
|
126
|
+
metricType: 'clicks',
|
|
127
|
+
userId: test.userId,
|
|
128
|
+
userEmail: user.email,
|
|
129
|
+
};
|
|
130
|
+
const sendResult = await this.notificationService.sendVictoryAlert(testResult);
|
|
131
|
+
notificationSent = sendResult.sent;
|
|
132
|
+
}
|
|
133
|
+
// 12. Restituisci risultato
|
|
134
|
+
return {
|
|
135
|
+
testId,
|
|
136
|
+
status: analysisResult.hasInsufficientData
|
|
137
|
+
? 'insufficient_data'
|
|
138
|
+
: analysisResult.isSignificant
|
|
139
|
+
? 'completed'
|
|
140
|
+
: 'updated',
|
|
141
|
+
pValue: analysisResult.pValue,
|
|
142
|
+
percentageChange: analysisResult.percentageChange,
|
|
143
|
+
isSignificant: analysisResult.isSignificant,
|
|
144
|
+
message: analysisResult.message,
|
|
145
|
+
dataPointsBefore: beforeClicks.length,
|
|
146
|
+
dataPointsAfter: afterClicks.length,
|
|
147
|
+
notificationSent,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Sincronizza tutti i test con status 'running'.
|
|
152
|
+
* Resiliente: se un test fallisce, logga l'errore e continua.
|
|
153
|
+
*/
|
|
154
|
+
async syncAllActiveTests() {
|
|
155
|
+
const activeTests = this.db
|
|
156
|
+
.select()
|
|
157
|
+
.from(tests)
|
|
158
|
+
.where(eq(tests.status, 'running'))
|
|
159
|
+
.all();
|
|
160
|
+
const results = [];
|
|
161
|
+
const errors = [];
|
|
162
|
+
for (const test of activeTests) {
|
|
163
|
+
try {
|
|
164
|
+
const result = await this.runExperiment(test.id);
|
|
165
|
+
results.push(result);
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
const errorMessage = error instanceof Error ? error.message : 'Errore sconosciuto';
|
|
169
|
+
errors.push({ testId: test.id, error: errorMessage });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
totalTests: activeTests.length,
|
|
174
|
+
successCount: results.length,
|
|
175
|
+
errorCount: errors.length,
|
|
176
|
+
results,
|
|
177
|
+
errors,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Aggiorna lo stato di un test e registra nel log.
|
|
182
|
+
*/
|
|
183
|
+
async updateTestStatus(testId, status, userId) {
|
|
184
|
+
this.db
|
|
185
|
+
.update(tests)
|
|
186
|
+
.set({ status, updatedAt: new Date().toISOString() })
|
|
187
|
+
.where(eq(tests.id, testId))
|
|
188
|
+
.run();
|
|
189
|
+
this.db
|
|
190
|
+
.insert(auditLogs)
|
|
191
|
+
.values({
|
|
192
|
+
testId,
|
|
193
|
+
action: `status_changed_to_${status}`,
|
|
194
|
+
userId,
|
|
195
|
+
})
|
|
196
|
+
.run();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=SEOExperimentOrchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SEOExperimentOrchestrator.js","sourceRoot":"","sources":["../../src/orchestrator/SEOExperimentOrchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExportService
|
|
3
|
+
*
|
|
4
|
+
* Genera report professionali in formato Excel (.xlsx) e CSV
|
|
5
|
+
* a partire dai dati di un test SEO (metadata + metriche giornaliere).
|
|
6
|
+
*/
|
|
7
|
+
export interface TestReportData {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string;
|
|
10
|
+
siteUrl: string;
|
|
11
|
+
urls: string[];
|
|
12
|
+
startDate: string;
|
|
13
|
+
splitDate: string;
|
|
14
|
+
status: string;
|
|
15
|
+
lastPValue: number | null;
|
|
16
|
+
lastImprovement: number | null;
|
|
17
|
+
}
|
|
18
|
+
export interface MetricRow {
|
|
19
|
+
date: string;
|
|
20
|
+
clicks: number;
|
|
21
|
+
impressions: number;
|
|
22
|
+
gapFilled: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface ExportInput {
|
|
25
|
+
test: TestReportData;
|
|
26
|
+
metrics: MetricRow[];
|
|
27
|
+
}
|
|
28
|
+
export interface ExportResult {
|
|
29
|
+
buffer: Buffer;
|
|
30
|
+
fileName: string;
|
|
31
|
+
format: 'xlsx' | 'csv';
|
|
32
|
+
rowCount: number;
|
|
33
|
+
}
|
|
34
|
+
export declare class ReportExportService {
|
|
35
|
+
/**
|
|
36
|
+
* Genera un file Excel professionale con due fogli:
|
|
37
|
+
* - Riepilogo (metadata + analisi)
|
|
38
|
+
* - Dati Giornalieri (tabella metriche)
|
|
39
|
+
*/
|
|
40
|
+
exportToExcel(input: ExportInput): Promise<ExportResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Genera un file CSV piatto con le metriche giornaliere.
|
|
43
|
+
*/
|
|
44
|
+
exportToCSV(input: ExportInput): Promise<ExportResult>;
|
|
45
|
+
private buildRiepilogoSheet;
|
|
46
|
+
private buildDatiSheet;
|
|
47
|
+
private addSectionHeader;
|
|
48
|
+
private addLabelValueRow;
|
|
49
|
+
buildFileName(testName: string, ext: 'xlsx' | 'csv'): string;
|
|
50
|
+
private formatDate;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=ExportService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ExportService.d.ts","sourceRoot":"","sources":["../../src/services/ExportService.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAmBD,qBAAa,mBAAmB;IAC9B;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAqB9D;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA0B5D,OAAO,CAAC,mBAAmB;IAqG3B,OAAO,CAAC,cAAc;IAyDtB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,gBAAgB;IAkBxB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM;IAM5D,OAAO,CAAC,UAAU;CAWnB"}
|