vantuz 3.4.2 → 3.5.1
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/.env.example +21 -0
- package/.openclaw/completions/openclaw.bash +227 -0
- package/.openclaw/completions/openclaw.fish +1552 -0
- package/.openclaw/completions/openclaw.ps1 +1966 -0
- package/.openclaw/completions/openclaw.zsh +3571 -0
- package/.openclaw/gateway.cmd +10 -0
- package/.openclaw/identity/device.json +7 -0
- package/.openclaw/openclaw.json +40 -0
- package/.windsurf/workflows/vantuz-dev.md +31 -0
- package/DOCS_TR.md +80 -0
- package/LICENSE +45 -45
- package/README.md +52 -21
- package/cli.js +685 -585
- package/config.js +733 -733
- package/core/agent-loop.js +190 -190
- package/core/ai-provider.js +298 -261
- package/core/automation.js +523 -523
- package/core/brand-analyst.js +101 -0
- package/core/channels.js +167 -167
- package/core/dashboard.js +230 -230
- package/core/database.js +135 -37
- package/core/eia-monitor.js +3 -1
- package/core/engine.js +648 -636
- package/core/gateway.js +447 -447
- package/core/learning.js +214 -214
- package/core/license.js +113 -0
- package/core/marketplace-adapter.js +168 -168
- package/core/memory.js +190 -190
- package/core/migrations/001-initial-schema.sql +1 -1
- package/core/queue.js +120 -120
- package/core/self-healer.js +314 -314
- package/core/unified-product.js +214 -214
- package/core/vision-service.js +113 -113
- package/index.js +217 -174
- package/modules/crm/sentiment-crm.js +231 -231
- package/modules/healer/listing-healer.js +201 -201
- package/modules/oracle/predictor.js +214 -214
- package/modules/researcher/agent.js +169 -169
- package/modules/team/agents/base.js +92 -92
- package/modules/team/agents/dev.js +33 -33
- package/modules/team/agents/josh.js +40 -40
- package/modules/team/agents/marketing.js +33 -33
- package/modules/team/agents/milo.js +36 -36
- package/modules/team/index.js +78 -78
- package/modules/team/shared-memory.js +87 -87
- package/modules/war-room/competitor-tracker.js +250 -250
- package/modules/war-room/pricing-engine.js +308 -308
- package/n11docs.md +1680 -0
- package/nodes/warehouse.js +238 -238
- package/onboard.js +1 -1
- package/openclawdocs.md +3 -0
- package/package.json +7 -5
- package/platforms/pttavm.js +14 -14
- package/plugins/vantuz/index.js +528 -528
- package/plugins/vantuz/memory/hippocampus.js +465 -465
- package/plugins/vantuz/package.json +20 -20
- package/plugins/vantuz/platforms/_template.js +118 -118
- package/plugins/vantuz/platforms/amazon.js +236 -236
- package/plugins/vantuz/platforms/ciceksepeti.js +166 -166
- package/plugins/vantuz/platforms/hepsiburada.js +180 -180
- package/plugins/vantuz/platforms/index.js +165 -165
- package/plugins/vantuz/platforms/n11.js +229 -229
- package/plugins/vantuz/platforms/pazarama.js +154 -154
- package/plugins/vantuz/platforms/pttavm.js +127 -127
- package/plugins/vantuz/platforms/trendyol.js +326 -326
- package/plugins/vantuz/services/alerts.js +253 -253
- package/plugins/vantuz/services/license.js +34 -34
- package/plugins/vantuz/services/scheduler.js +232 -232
- package/plugins/vantuz/tools/analytics.js +152 -152
- package/plugins/vantuz/tools/crossborder.js +187 -187
- package/plugins/vantuz/tools/nl-parser.js +211 -211
- package/plugins/vantuz/tools/product.js +110 -110
- package/plugins/vantuz/tools/quick-report.js +175 -175
- package/plugins/vantuz/tools/repricer.js +314 -314
- package/plugins/vantuz/tools/sentiment.js +115 -115
- package/plugins/vantuz/tools/vision.js +257 -257
- package/public.pem +9 -0
- package/server/app.js +260 -260
- package/server/public/index.html +514 -514
- package/start.bat +33 -33
- package/vantuz.sqlite +0 -0
- package/workspace/AGENTS.md +73 -0
- package/workspace/BRAND.md +29 -0
- package/workspace/SOUL.md +72 -0
- package/workspace/team/DECISIONS.md +3 -0
- package/workspace/team/GOALS.md +3 -0
- package/workspace/team/PROJECT_STATUS.md +3 -0
- package/workspace/team/agents/dev/SOUL.md +12 -0
- package/workspace/team/agents/josh/SOUL.md +12 -0
- package/workspace/team/agents/marketing/SOUL.md +12 -0
- package/workspace/team/agents/milo/SOUL.md +12 -0
- package/vantuz-3.3.4.tgz +0 -0
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
// modules/oracle/predictor.js
|
|
2
|
-
// Oracle — Predictive Analytics for Vantuz OS V2
|
|
3
|
-
// Predicts stockout dates and generates reorder alerts.
|
|
4
|
-
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import os from 'os';
|
|
8
|
-
import { log } from '../../core/ai-provider.js';
|
|
9
|
-
|
|
10
|
-
const SALES_HISTORY_FILE = path.join(os.homedir(), '.vantuz', 'memory', 'sales-velocity.json');
|
|
11
|
-
|
|
12
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
-
// SALES VELOCITY TRACKER
|
|
14
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
-
|
|
16
|
-
function loadVelocityData() {
|
|
17
|
-
try {
|
|
18
|
-
if (fs.existsSync(SALES_HISTORY_FILE)) {
|
|
19
|
-
return JSON.parse(fs.readFileSync(SALES_HISTORY_FILE, 'utf-8'));
|
|
20
|
-
}
|
|
21
|
-
} catch (e) { /* ignore */ }
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function saveVelocityData(data) {
|
|
26
|
-
const dir = path.dirname(SALES_HISTORY_FILE);
|
|
27
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
28
|
-
const tmp = SALES_HISTORY_FILE + '.tmp';
|
|
29
|
-
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
|
|
30
|
-
fs.renameSync(tmp, SALES_HISTORY_FILE);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
34
|
-
// ORACLE
|
|
35
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
-
|
|
37
|
-
class Oracle {
|
|
38
|
-
constructor() {
|
|
39
|
-
this.velocityData = loadVelocityData(); // barcode -> { snapshots: [{date, stock}], velocity }
|
|
40
|
-
log('INFO', 'Oracle initialized', { trackedProducts: Object.keys(this.velocityData).length });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Record a stock snapshot for velocity calculation.
|
|
45
|
-
* @param {string} barcode
|
|
46
|
-
* @param {number} stock - Current stock level.
|
|
47
|
-
*/
|
|
48
|
-
recordSnapshot(barcode, stock) {
|
|
49
|
-
if (!this.velocityData[barcode]) {
|
|
50
|
-
this.velocityData[barcode] = { snapshots: [], velocity: 0 };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const entry = this.velocityData[barcode];
|
|
54
|
-
entry.snapshots.push({
|
|
55
|
-
date: new Date().toISOString(),
|
|
56
|
-
stock: parseInt(stock, 10)
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Keep max 30 snapshots per product
|
|
60
|
-
if (entry.snapshots.length > 30) {
|
|
61
|
-
entry.snapshots = entry.snapshots.slice(-30);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Recalculate velocity
|
|
65
|
-
entry.velocity = this._calculateVelocity(entry.snapshots);
|
|
66
|
-
|
|
67
|
-
saveVelocityData(this.velocityData);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Record snapshots for multiple products.
|
|
72
|
-
*/
|
|
73
|
-
recordBatch(products) {
|
|
74
|
-
for (const p of products) {
|
|
75
|
-
const barcode = p.barcode || p.sku;
|
|
76
|
-
const stock = p.stock || p.quantity || 0;
|
|
77
|
-
if (barcode) this.recordSnapshot(barcode, stock);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Predict stockout date for a product.
|
|
83
|
-
* @param {string} barcode
|
|
84
|
-
* @param {number} currentStock - Current stock (if not in history).
|
|
85
|
-
* @returns {{ barcode, velocity, daysLeft, stockoutDate, severity }}
|
|
86
|
-
*/
|
|
87
|
-
predictStockout(barcode, currentStock = null) {
|
|
88
|
-
const entry = this.velocityData[barcode];
|
|
89
|
-
if (!entry || entry.snapshots.length < 2) {
|
|
90
|
-
return {
|
|
91
|
-
barcode,
|
|
92
|
-
velocity: 0,
|
|
93
|
-
daysLeft: null,
|
|
94
|
-
stockoutDate: null,
|
|
95
|
-
severity: 'unknown',
|
|
96
|
-
message: 'Yeterli veri yok — en az 2 snapshot gerekli'
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const stock = currentStock ?? entry.snapshots[entry.snapshots.length - 1].stock;
|
|
101
|
-
const velocity = entry.velocity;
|
|
102
|
-
|
|
103
|
-
if (velocity <= 0) {
|
|
104
|
-
return {
|
|
105
|
-
barcode, velocity: 0, daysLeft: Infinity,
|
|
106
|
-
stockoutDate: null, severity: 'safe',
|
|
107
|
-
message: 'Satış hızı sıfır veya negatif — stok azalmıyor'
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const daysLeft = Math.floor(stock / velocity);
|
|
112
|
-
const stockoutDate = new Date();
|
|
113
|
-
stockoutDate.setDate(stockoutDate.getDate() + daysLeft);
|
|
114
|
-
|
|
115
|
-
let severity = 'safe';
|
|
116
|
-
if (daysLeft <= 3) severity = 'critical';
|
|
117
|
-
else if (daysLeft <= 7) severity = 'warning';
|
|
118
|
-
else if (daysLeft <= 14) severity = 'attention';
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
barcode,
|
|
122
|
-
currentStock: stock,
|
|
123
|
-
velocity: Math.round(velocity * 100) / 100,
|
|
124
|
-
daysLeft,
|
|
125
|
-
stockoutDate: stockoutDate.toISOString().split('T')[0],
|
|
126
|
-
severity,
|
|
127
|
-
message: this._formatMessage(barcode, daysLeft, velocity, severity)
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Generate reorder report for all tracked products.
|
|
133
|
-
* @param {number} criticalDays - Days threshold for "critical" (default: 7).
|
|
134
|
-
* @returns {{ critical: array, warning: array, safe: number }}
|
|
135
|
-
*/
|
|
136
|
-
generateReorderReport(criticalDays = 7) {
|
|
137
|
-
const critical = [];
|
|
138
|
-
const warning = [];
|
|
139
|
-
let safe = 0;
|
|
140
|
-
|
|
141
|
-
for (const barcode of Object.keys(this.velocityData)) {
|
|
142
|
-
const prediction = this.predictStockout(barcode);
|
|
143
|
-
|
|
144
|
-
if (prediction.daysLeft !== null && prediction.daysLeft <= criticalDays) {
|
|
145
|
-
const reorderQty = Math.ceil(prediction.velocity * 30); // 30 days supply
|
|
146
|
-
critical.push({
|
|
147
|
-
...prediction,
|
|
148
|
-
reorderQty,
|
|
149
|
-
reorderMessage: `⚠️ ${barcode}: ${prediction.daysLeft} gün kaldı! ${reorderQty} adet sipariş ver.`
|
|
150
|
-
});
|
|
151
|
-
} else if (prediction.severity === 'attention') {
|
|
152
|
-
warning.push(prediction);
|
|
153
|
-
} else {
|
|
154
|
-
safe++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Sort by urgency
|
|
159
|
-
critical.sort((a, b) => a.daysLeft - b.daysLeft);
|
|
160
|
-
|
|
161
|
-
const report = { critical, warning, safe, generatedAt: new Date().toISOString() };
|
|
162
|
-
log('INFO', 'Reorder report generated', {
|
|
163
|
-
critical: critical.length, warning: warning.length, safe
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
return report;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
getStatus() {
|
|
170
|
-
return {
|
|
171
|
-
trackedProducts: Object.keys(this.velocityData).length,
|
|
172
|
-
critical: this.generateReorderReport().critical.length
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// ─────────────────────────────────────────────────────────────────────
|
|
177
|
-
|
|
178
|
-
_calculateVelocity(snapshots) {
|
|
179
|
-
if (snapshots.length < 2) return 0;
|
|
180
|
-
|
|
181
|
-
const first = snapshots[0];
|
|
182
|
-
const last = snapshots[snapshots.length - 1];
|
|
183
|
-
const daysDiff = (new Date(last.date) - new Date(first.date)) / (1000 * 60 * 60 * 24);
|
|
184
|
-
|
|
185
|
-
if (daysDiff <= 0) return 0;
|
|
186
|
-
|
|
187
|
-
const stockConsumed = first.stock - last.stock;
|
|
188
|
-
return stockConsumed > 0 ? stockConsumed / daysDiff : 0;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
_formatMessage(barcode, daysLeft, velocity, severity) {
|
|
192
|
-
if (severity === 'critical') {
|
|
193
|
-
return `🔴 KRİTİK: ${barcode} — ${daysLeft} gün içinde stok bitecek! (Günlük ${velocity.toFixed(1)} adet satılıyor)`;
|
|
194
|
-
}
|
|
195
|
-
if (severity === 'warning') {
|
|
196
|
-
return `🟡 UYARI: ${barcode} — ${daysLeft} gün stok kaldı. Sipariş planla.`;
|
|
197
|
-
}
|
|
198
|
-
if (severity === 'attention') {
|
|
199
|
-
return `🟠 DİKKAT: ${barcode} — ${daysLeft} gün stok kaldı.`;
|
|
200
|
-
}
|
|
201
|
-
return `🟢 ${barcode} — Stok yeterli (${daysLeft} gün)`;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
let oracleInstance = null;
|
|
206
|
-
|
|
207
|
-
export function getOracle() {
|
|
208
|
-
if (!oracleInstance) {
|
|
209
|
-
oracleInstance = new Oracle();
|
|
210
|
-
}
|
|
211
|
-
return oracleInstance;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export default Oracle;
|
|
1
|
+
// modules/oracle/predictor.js
|
|
2
|
+
// Oracle — Predictive Analytics for Vantuz OS V2
|
|
3
|
+
// Predicts stockout dates and generates reorder alerts.
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { log } from '../../core/ai-provider.js';
|
|
9
|
+
|
|
10
|
+
const SALES_HISTORY_FILE = path.join(os.homedir(), '.vantuz', 'memory', 'sales-velocity.json');
|
|
11
|
+
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
13
|
+
// SALES VELOCITY TRACKER
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
function loadVelocityData() {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(SALES_HISTORY_FILE)) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(SALES_HISTORY_FILE, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
} catch (e) { /* ignore */ }
|
|
22
|
+
return {};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function saveVelocityData(data) {
|
|
26
|
+
const dir = path.dirname(SALES_HISTORY_FILE);
|
|
27
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
28
|
+
const tmp = SALES_HISTORY_FILE + '.tmp';
|
|
29
|
+
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');
|
|
30
|
+
fs.renameSync(tmp, SALES_HISTORY_FILE);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
34
|
+
// ORACLE
|
|
35
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
36
|
+
|
|
37
|
+
class Oracle {
|
|
38
|
+
constructor() {
|
|
39
|
+
this.velocityData = loadVelocityData(); // barcode -> { snapshots: [{date, stock}], velocity }
|
|
40
|
+
log('INFO', 'Oracle initialized', { trackedProducts: Object.keys(this.velocityData).length });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Record a stock snapshot for velocity calculation.
|
|
45
|
+
* @param {string} barcode
|
|
46
|
+
* @param {number} stock - Current stock level.
|
|
47
|
+
*/
|
|
48
|
+
recordSnapshot(barcode, stock) {
|
|
49
|
+
if (!this.velocityData[barcode]) {
|
|
50
|
+
this.velocityData[barcode] = { snapshots: [], velocity: 0 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const entry = this.velocityData[barcode];
|
|
54
|
+
entry.snapshots.push({
|
|
55
|
+
date: new Date().toISOString(),
|
|
56
|
+
stock: parseInt(stock, 10)
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Keep max 30 snapshots per product
|
|
60
|
+
if (entry.snapshots.length > 30) {
|
|
61
|
+
entry.snapshots = entry.snapshots.slice(-30);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Recalculate velocity
|
|
65
|
+
entry.velocity = this._calculateVelocity(entry.snapshots);
|
|
66
|
+
|
|
67
|
+
saveVelocityData(this.velocityData);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Record snapshots for multiple products.
|
|
72
|
+
*/
|
|
73
|
+
recordBatch(products) {
|
|
74
|
+
for (const p of products) {
|
|
75
|
+
const barcode = p.barcode || p.sku;
|
|
76
|
+
const stock = p.stock || p.quantity || 0;
|
|
77
|
+
if (barcode) this.recordSnapshot(barcode, stock);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Predict stockout date for a product.
|
|
83
|
+
* @param {string} barcode
|
|
84
|
+
* @param {number} currentStock - Current stock (if not in history).
|
|
85
|
+
* @returns {{ barcode, velocity, daysLeft, stockoutDate, severity }}
|
|
86
|
+
*/
|
|
87
|
+
predictStockout(barcode, currentStock = null) {
|
|
88
|
+
const entry = this.velocityData[barcode];
|
|
89
|
+
if (!entry || entry.snapshots.length < 2) {
|
|
90
|
+
return {
|
|
91
|
+
barcode,
|
|
92
|
+
velocity: 0,
|
|
93
|
+
daysLeft: null,
|
|
94
|
+
stockoutDate: null,
|
|
95
|
+
severity: 'unknown',
|
|
96
|
+
message: 'Yeterli veri yok — en az 2 snapshot gerekli'
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const stock = currentStock ?? entry.snapshots[entry.snapshots.length - 1].stock;
|
|
101
|
+
const velocity = entry.velocity;
|
|
102
|
+
|
|
103
|
+
if (velocity <= 0) {
|
|
104
|
+
return {
|
|
105
|
+
barcode, velocity: 0, daysLeft: Infinity,
|
|
106
|
+
stockoutDate: null, severity: 'safe',
|
|
107
|
+
message: 'Satış hızı sıfır veya negatif — stok azalmıyor'
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const daysLeft = Math.floor(stock / velocity);
|
|
112
|
+
const stockoutDate = new Date();
|
|
113
|
+
stockoutDate.setDate(stockoutDate.getDate() + daysLeft);
|
|
114
|
+
|
|
115
|
+
let severity = 'safe';
|
|
116
|
+
if (daysLeft <= 3) severity = 'critical';
|
|
117
|
+
else if (daysLeft <= 7) severity = 'warning';
|
|
118
|
+
else if (daysLeft <= 14) severity = 'attention';
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
barcode,
|
|
122
|
+
currentStock: stock,
|
|
123
|
+
velocity: Math.round(velocity * 100) / 100,
|
|
124
|
+
daysLeft,
|
|
125
|
+
stockoutDate: stockoutDate.toISOString().split('T')[0],
|
|
126
|
+
severity,
|
|
127
|
+
message: this._formatMessage(barcode, daysLeft, velocity, severity)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Generate reorder report for all tracked products.
|
|
133
|
+
* @param {number} criticalDays - Days threshold for "critical" (default: 7).
|
|
134
|
+
* @returns {{ critical: array, warning: array, safe: number }}
|
|
135
|
+
*/
|
|
136
|
+
generateReorderReport(criticalDays = 7) {
|
|
137
|
+
const critical = [];
|
|
138
|
+
const warning = [];
|
|
139
|
+
let safe = 0;
|
|
140
|
+
|
|
141
|
+
for (const barcode of Object.keys(this.velocityData)) {
|
|
142
|
+
const prediction = this.predictStockout(barcode);
|
|
143
|
+
|
|
144
|
+
if (prediction.daysLeft !== null && prediction.daysLeft <= criticalDays) {
|
|
145
|
+
const reorderQty = Math.ceil(prediction.velocity * 30); // 30 days supply
|
|
146
|
+
critical.push({
|
|
147
|
+
...prediction,
|
|
148
|
+
reorderQty,
|
|
149
|
+
reorderMessage: `⚠️ ${barcode}: ${prediction.daysLeft} gün kaldı! ${reorderQty} adet sipariş ver.`
|
|
150
|
+
});
|
|
151
|
+
} else if (prediction.severity === 'attention') {
|
|
152
|
+
warning.push(prediction);
|
|
153
|
+
} else {
|
|
154
|
+
safe++;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Sort by urgency
|
|
159
|
+
critical.sort((a, b) => a.daysLeft - b.daysLeft);
|
|
160
|
+
|
|
161
|
+
const report = { critical, warning, safe, generatedAt: new Date().toISOString() };
|
|
162
|
+
log('INFO', 'Reorder report generated', {
|
|
163
|
+
critical: critical.length, warning: warning.length, safe
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return report;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
getStatus() {
|
|
170
|
+
return {
|
|
171
|
+
trackedProducts: Object.keys(this.velocityData).length,
|
|
172
|
+
critical: this.generateReorderReport().critical.length
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
_calculateVelocity(snapshots) {
|
|
179
|
+
if (snapshots.length < 2) return 0;
|
|
180
|
+
|
|
181
|
+
const first = snapshots[0];
|
|
182
|
+
const last = snapshots[snapshots.length - 1];
|
|
183
|
+
const daysDiff = (new Date(last.date) - new Date(first.date)) / (1000 * 60 * 60 * 24);
|
|
184
|
+
|
|
185
|
+
if (daysDiff <= 0) return 0;
|
|
186
|
+
|
|
187
|
+
const stockConsumed = first.stock - last.stock;
|
|
188
|
+
return stockConsumed > 0 ? stockConsumed / daysDiff : 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_formatMessage(barcode, daysLeft, velocity, severity) {
|
|
192
|
+
if (severity === 'critical') {
|
|
193
|
+
return `🔴 KRİTİK: ${barcode} — ${daysLeft} gün içinde stok bitecek! (Günlük ${velocity.toFixed(1)} adet satılıyor)`;
|
|
194
|
+
}
|
|
195
|
+
if (severity === 'warning') {
|
|
196
|
+
return `🟡 UYARI: ${barcode} — ${daysLeft} gün stok kaldı. Sipariş planla.`;
|
|
197
|
+
}
|
|
198
|
+
if (severity === 'attention') {
|
|
199
|
+
return `🟠 DİKKAT: ${barcode} — ${daysLeft} gün stok kaldı.`;
|
|
200
|
+
}
|
|
201
|
+
return `🟢 ${barcode} — Stok yeterli (${daysLeft} gün)`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let oracleInstance = null;
|
|
206
|
+
|
|
207
|
+
export function getOracle() {
|
|
208
|
+
if (!oracleInstance) {
|
|
209
|
+
oracleInstance = new Oracle();
|
|
210
|
+
}
|
|
211
|
+
return oracleInstance;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default Oracle;
|