Kaliman-1981 commited on
Commit
6f4eb51
·
verified ·
1 Parent(s): 91f5d67

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +350 -19
  3. prompts.txt +14 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Curvemaster
3
- emoji: 🦀
4
- colorFrom: red
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: curvemaster
3
+ emoji: ⚛️
4
+ colorFrom: pink
5
+ colorTo: green
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - QwenSite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,350 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="bg-slate-900 text-white">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Curve Master S Curve Analysis</title>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.4.1/papaparse.min.js"></script>
9
+ <script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
10
+ </head>
11
+ <body class="p-6 font-sans bg-slate-900 text-white">
12
+
13
+ <div class="max-w-7xl mx-auto">
14
+ <h1 class="text-4xl font-extrabold mb-6 text-center text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-amber-400">
15
+ <i class="fas fa-chart-line mr-2"></i>Curve Master – S Curve Analysis
16
+ </h1>
17
+
18
+ <!-- Status Messages -->
19
+ <div id="statusMessage" class="mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800"></div>
20
+
21
+ <!-- File Upload -->
22
+ <div class="mb-6 bg-slate-800 p-4 rounded shadow">
23
+ <label class="block font-medium mb-2">Upload CSV File(s):</label>
24
+ <input type="file" id="fileInput" multiple accept=".csv" class="text-white file:bg-amber-500 file:text-slate-900 file:border-0 file:py-2 file:px-4 file:rounded file:mr-2" />
25
+ </div>
26
+
27
+ <!-- Productivity Factor -->
28
+ <div class="mb-6 bg-slate-800 p-4 rounded shadow">
29
+ <label for="pfInput" class="block font-medium mb-2">Productivity Factor (PF):</label>
30
+ <input type="number" id="pfInput" value="1" step="0.01" min="0.01" class="bg-slate-700 text-white p-2 rounded w-full focus:ring-2 focus:ring-teal-400 focus:outline-none" />
31
+ </div>
32
+
33
+ <!-- Chart -->
34
+ <canvas id="curveChart" class="bg-slate-800 rounded shadow-lg mb-6 h-96 border border-slate-700"></canvas>
35
+
36
+ <!-- Dataset Toggle -->
37
+ <div class="mb-6 bg-slate-800 p-4 rounded shadow">
38
+ <h3 class="font-semibold mb-2">Toggle Data Series:</h3>
39
+ <div id="datasetToggles" class="space-y-2 text-sm"></div>
40
+ </div>
41
+
42
+ <!-- Export & Reset Buttons -->
43
+ <div class="flex gap-4 mb-8">
44
+ <button id="exportCsvBtn" class="bg-teal-600 hover:bg-teal-700 px-4 py-2 rounded flex items-center transition-all">
45
+ <i class="fas fa-file-csv mr-1"></i> Export CSV
46
+ </button>
47
+ <button id="exportPngBtn" class="bg-amber-600 hover:bg-amber-700 px-4 py-2 rounded flex items-center transition-all">
48
+ <i class="fas fa-image mr-1"></i> Export PNG
49
+ </button>
50
+ <button id="resetBtn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded flex items-center transition-all">
51
+ <i class="fas fa-undo mr-1"></i> Reset
52
+ </button>
53
+ </div>
54
+
55
+ <!-- Data Table -->
56
+ <div id="dataTable" class="mt-8 overflow-auto max-h-64 bg-slate-800 p-4 rounded shadow border border-slate-700"></div>
57
+ </div>
58
+
59
+ <script>
60
+ const ctx = document.getElementById('curveChart').getContext('2d');
61
+ let curveChart = null;
62
+ let datasets = [];
63
+ let allDatasetLabels = [];
64
+
65
+ const requiredHeaders = {
66
+ units: [
67
+ "Budgeted Units", "Actual Units", "Cum Actual Units",
68
+ "Remaining Early Units", "Remaining Late Units",
69
+ "Cum Remaining Early Units", "Cum Remaining Late Units",
70
+ "Cum Baseline"
71
+ ],
72
+ cost: [
73
+ "Budgeted Cost", "Actual Cost", "Cum Actual Cost",
74
+ "Remaining Early Cost", "Remaining Late Cost",
75
+ "Cum Remaining Early Cost", "Cum Remaining Late Cost",
76
+ "Cum Baseline"
77
+ ]
78
+ };
79
+
80
+ const fileInput = document.getElementById('fileInput');
81
+ const pfInput = document.getElementById('pfInput');
82
+ const exportCsvBtn = document.getElementById('exportCsvBtn');
83
+ const exportPngBtn = document.getElementById('exportPngBtn');
84
+ const resetBtn = document.getElementById('resetBtn');
85
+ const datasetToggles = document.getElementById('datasetToggles');
86
+ const statusMessage = document.getElementById('statusMessage');
87
+
88
+ fileInput.addEventListener('change', handleFileUpload);
89
+ pfInput.addEventListener('input', updateChart);
90
+ exportCsvBtn.addEventListener('click', exportCSV);
91
+ exportPngBtn.addEventListener('click', exportPNG);
92
+ resetBtn.addEventListener('click', resetAll);
93
+
94
+ function resetAll() {
95
+ datasets = [];
96
+ allDatasetLabels = [];
97
+ if (curveChart) {
98
+ curveChart.destroy();
99
+ }
100
+ document.getElementById('dataTable').innerHTML = '';
101
+ document.getElementById('datasetToggles').innerHTML = '';
102
+ fileInput.value = '';
103
+ pfInput.value = 1;
104
+ statusMessage.textContent = "All data has been reset.";
105
+ statusMessage.className = 'mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800 text-blue-300';
106
+ }
107
+
108
+ function handleFileUpload(event) {
109
+ const files = Array.from(event.target.files);
110
+ if (files.length === 0) return;
111
+
112
+ statusMessage.textContent = `Processing ${files.length} file(s)...`;
113
+ statusMessage.className = 'mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800 text-yellow-300';
114
+
115
+ files.forEach(file => {
116
+ Papa.parse(file, {
117
+ header: true,
118
+ skipEmptyLines: true,
119
+ complete: (results) => {
120
+ try {
121
+ processDataset(results.data, results.meta.fields);
122
+ statusMessage.innerHTML = `<i class="fas fa-check-circle text-green-400 mr-1"></i> Successfully processed ${file.name}`;
123
+ statusMessage.className = 'mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800 text-green-400';
124
+ } catch (e) {
125
+ statusMessage.innerHTML = `<i class="fas fa-exclamation-circle text-red-400 mr-1"></i> Failed to process ${file.name}: ${e.message}`;
126
+ statusMessage.className = 'mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800 text-red-400';
127
+ }
128
+ },
129
+ error: (err) => {
130
+ statusMessage.innerHTML = `<i class="fas fa-exclamation-triangle text-red-400 mr-1"></i> CSV Parse Error: ${err.message}`;
131
+ statusMessage.className = 'mb-4 text-center text-sm p-3 rounded shadow-md bg-slate-800 text-red-400';
132
+ }
133
+ });
134
+ });
135
+ }
136
+
137
+ function processDataset(data, headers) {
138
+ const detectedType = detectType(headers);
139
+ if (!detectedType) throw new Error("Missing required headers. Expected: " + requiredHeaders.units.join(","));
140
+
141
+ const dates = data.map(row => row[headers[0]]); // First column is date
142
+
143
+ const series = requiredHeaders[detectedType].map(key => ({
144
+ label: key,
145
+ data: data.map(row => parseFloat(row[key]) || 0),
146
+ borderColor: getRandomColor(1),
147
+ backgroundColor: getRandomColor(0.7),
148
+ fill: key.includes("Cum"),
149
+ tension: key.includes("Cum") ? 0.3 : 0,
150
+ type: key.includes("Cum") ? 'line' : 'bar'
151
+ }));
152
+
153
+ datasets.push({ dates, series, type: detectedType });
154
+ allDatasetLabels = [...new Set([...allDatasetLabels, ...series.map(s => s.label)])];
155
+ updateChart();
156
+ renderDatasetToggles();
157
+ renderDataTable(data);
158
+ }
159
+
160
+ function detectType(headers) {
161
+ if (requiredHeaders.units.every(h => headers.includes(h))) return "units";
162
+ if (requiredHeaders.cost.every(h => headers.includes(h))) return "cost";
163
+ return null;
164
+ }
165
+
166
+ function updateChart() {
167
+ if (curveChart) curveChart.destroy();
168
+
169
+ const pf = parseFloat(pfInput.value) || 1;
170
+
171
+ const allLabels = new Set();
172
+ const allSeries = [];
173
+
174
+ datasets.forEach(dataset => {
175
+ const { dates, series } = dataset;
176
+ series.forEach(s => {
177
+ const adjustedData = [...s.data];
178
+ if (s.label.includes("Remaining")) adjustedData.forEach((v, i) => adjustedData[i] = v * pf);
179
+
180
+ const isCumulative = s.label.includes("Cum");
181
+ let yAxisID = isCumulative ? 'rightY' : 'leftY';
182
+
183
+ allSeries.push({
184
+ label: s.label + (pf !== 1 && s.label.includes("Remaining") ? ` (PF=${pf})` : ""),
185
+ data: adjustedData,
186
+ borderColor: s.borderColor,
187
+ backgroundColor: s.backgroundColor,
188
+ borderDash: s.label.includes("Remaining") && pf !== 1 ? [5, 5] : [],
189
+ tension: isCumulative ? 0.3 : 0,
190
+ fill: isCumulative,
191
+ type: isCumulative ? 'line' : 'bar',
192
+ pointRadius: isCumulative ? 3 : 0,
193
+ yAxisID
194
+ });
195
+ });
196
+
197
+ dates.forEach(d => allLabels.add(d));
198
+ });
199
+
200
+ curveChart = new Chart(ctx, {
201
+ type: 'bar',
202
+ data: {
203
+ labels: Array.from(allLabels).sort(),
204
+ datasets: allSeries
205
+ },
206
+ options: {
207
+ responsive: true,
208
+ plugins: {
209
+ legend: {
210
+ labels: { color: 'white' },
211
+ onClick: (e, legendItem) => {
212
+ const index = legendItem.datasetIndex;
213
+ const meta = curveChart.getDatasetMeta(index);
214
+ meta.hidden = meta.hidden === null ? true : !meta.hidden;
215
+ curveChart.update();
216
+ }
217
+ },
218
+ tooltip: {
219
+ mode: 'index',
220
+ intersect: false,
221
+ backgroundColor: 'rgba(0,0,0,0.8)',
222
+ titleColor: 'white',
223
+ bodyColor: 'white'
224
+ },
225
+ title: { display: true, text: "S-Curve Analysis", color: 'white' }
226
+ },
227
+ interaction: { mode: 'index', axis: 'x' },
228
+ scales: {
229
+ x: {
230
+ ticks: { color: 'white' },
231
+ grid: { color: 'rgba(255,255,255,0.1)' }
232
+ },
233
+ leftY: {
234
+ type: 'linear',
235
+ position: 'left',
236
+ title: { display: true, text: 'Units', color: '#6EE7B7' },
237
+ ticks: { color: '#6EE7B7' },
238
+ grid: { color: 'rgba(100,255,180,0.1)' }
239
+ },
240
+ rightY: {
241
+ type: 'linear',
242
+ position: 'right',
243
+ title: { display: true, text: 'Cumulative Units / Cost', color: '#FBBF24' },
244
+ ticks: { color: '#FBBF24' },
245
+ grid: { color: 'rgba(255,200,0,0.1)' }
246
+ }
247
+ }
248
+ }
249
+ });
250
+ }
251
+
252
+ function renderDatasetToggles() {
253
+ datasetToggles.innerHTML = '';
254
+ allDatasetLabels.forEach((label, index) => {
255
+ const div = document.createElement('div');
256
+ const id = `toggle-${index}`;
257
+ div.innerHTML = `
258
+ <label class="inline-flex items-center cursor-pointer">
259
+ <input type="checkbox" id="${id}" checked class="form-checkbox h-4 w-4 text-teal-500 rounded focus:ring-teal-500" />
260
+ <span class="ml-2">${label}</span>
261
+ </label>
262
+ `;
263
+ datasetToggles.appendChild(div);
264
+
265
+ const checkbox = document.getElementById(id);
266
+ checkbox.addEventListener('change', () => {
267
+ const meta = curveChart.getDatasetMeta(index);
268
+ meta.hidden = !checkbox.checked;
269
+ curveChart.update();
270
+ });
271
+ });
272
+ }
273
+
274
+ function getRandomColor(alpha = 1) {
275
+ const colors = [
276
+ 'rgba(20, 184, 166, 1)', // teal-500
277
+ 'rgba(245, 158, 11, 1)', // amber-500
278
+ 'rgba(236, 72, 153, 1)', // rose-500
279
+ 'rgba(59, 130, 246, 1)', // blue-500
280
+ 'rgba(16, 185, 129, 1)', // green-500
281
+ 'rgba(239, 68, 68, 1)', // red-500
282
+ 'rgba(147, 51, 234, 1)', // purple-500
283
+ ];
284
+ const color = colors[Math.floor(Math.random() * colors.length)];
285
+ return color.replace('1)', alpha + ')');
286
+ }
287
+
288
+ function renderDataTable(data) {
289
+ const table = document.createElement('table');
290
+ table.className = 'min-w-full bg-slate-800 text-sm rounded overflow-hidden divide-y divide-slate-700';
291
+
292
+ const thead = document.createElement('thead');
293
+ const headerRow = document.createElement('tr');
294
+ Object.keys(data[0]).forEach(key => {
295
+ const th = document.createElement('th');
296
+ th.textContent = key;
297
+ th.className = 'p-2 bg-slate-700';
298
+ headerRow.appendChild(th);
299
+ });
300
+ thead.appendChild(headerRow);
301
+ table.appendChild(thead);
302
+
303
+ const tbody = document.createElement('tbody');
304
+ data.forEach(row => {
305
+ const tr = document.createElement('tr');
306
+ Object.values(row).forEach(val => {
307
+ const td = document.createElement('td');
308
+ td.textContent = val;
309
+ td.className = 'p-2';
310
+ tr.appendChild(td);
311
+ });
312
+ tbody.appendChild(tr);
313
+ });
314
+ table.appendChild(tbody);
315
+
316
+ const dataTable = document.getElementById('dataTable');
317
+ dataTable.innerHTML = '';
318
+ dataTable.appendChild(table);
319
+ }
320
+
321
+ function exportCSV() {
322
+ let csv = "Date," + datasets.flatMap(d => d.series.map(s => s.label)).join(",") + "\n";
323
+ const labels = curveChart.data.labels;
324
+
325
+ labels.forEach((label, i) => {
326
+ let row = label;
327
+ curveChart.data.datasets.forEach(ds => {
328
+ row += "," + (ds.data[i] || 0);
329
+ });
330
+ csv += row + "\n";
331
+ });
332
+
333
+ const blob = new Blob([csv], { type: 'text/csv' });
334
+ const url = URL.createObjectURL(blob);
335
+ const a = document.createElement('a');
336
+ a.href = url;
337
+ a.download = "s_curve_data.csv";
338
+ a.click();
339
+ URL.revokeObjectURL(url);
340
+ }
341
+
342
+ function exportPNG() {
343
+ const link = document.createElement('a');
344
+ link.download = 's_curve_chart.png';
345
+ link.href = curveChart.toBase64Image();
346
+ link.click();
347
+ }
348
+ </script>
349
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=Kaliman-1981/curvemaster" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
350
+ </html>
prompts.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are Curve Master, an S-Curve generator and analyzer. You take Excel or CSV input in vertical format where: - The first row = dates (time axis). - Each following row = one metric. ### Import Feature - User uploads an Excel (.xlsx) or CSV (.csv). - Auto-detect columns (Date, Budgeted, Actual, Remaining, etc.). - Validate headers against the required fields: (Budgeted Units, Actual Units, Cum Actual Units, Remaining Early Units, Remaining Late Units, Cum Remaining Early Units, Cum Remaining Late Units) OR (Budgeted Cost, Actual Cost, Cum Actual Cost, Remaining Early Cost, Remaining Late Cost, Cum Remaining Early Cost, Cum Remaining Late Cost). - If headers are missing, prompt user to map columns manually. - Allow multiple file uploads (compare Unit vs Cost curves in one session). ### Inputs (Labor Hours / Units) - Budgeted Units - Actual Units - Cum Actual Units - Remaining Early Units - Remaining Late Units - Cum Remaining Early Units - Cum Remaining Late Units ### Inputs (Cost) - Budgeted Cost - Actual Cost - Cum Actual Cost - Remaining Early Cost - Remaining Late Cost - Cum Remaining Early Cost - Cum Remaining Late Cost ### Curve Controls 1. Plot S-curves for both **units** and **costs**. - X-axis = Date - Y-axis = Units or Cost - Show multiple lines on one chart (Budget, Actual, Remaining Early, Remaining Late, Cumulative, etc.). - Clear labels + legend. 2. Add **Productivity Factor (PF) control**: - User sets PF (e.g., 0.75, 1.2). - Remaining Early/Late curves are multiplied by PF. - Show adjusted curves in **dashed lines**. 3. Provide two views: - **Cumulative S-Curve** (long-term). - **Period S-Curve** (weekly/monthly). ### Output - Interactive charts (hover shows values). - Export options: - PNG (charts) - CSV (adjusted dataset) - XLSX (with both raw + adjusted curves). - Title = “Curve Master – S Curve Analysis” - Clean professional style: dark theme, bold lines, milestone markers. ### Example Productivity Adjustments - PF = 0.75 → Remaining Early/Late Units × 0.75 - PF = 1.25 → Remaining Early/Late Cost × 1.25
2
+ Budgeted Units, Actual Units, Remaining Early Units, Remaining Late Units, Need to be vertical bars like a histogram, the cumulative data makes the curves. Need option to filter on chart
3
+ cumulative needs to remain lines, and i cant see the color they are faint
4
+ need to fix the scaling , maybe use a combo style chart.
5
+ Need to Add, one more line going to call it cum baseline
6
+ NOT DOING ANYTHING WHEN CHART IS UPLOADED
7
+ perfect, i dont like the color scheme
8
+ Cum Actual Units - I need the data stop at the last actaul reported it shows a drop in the data cause actuals dont go past a certain data
9
+ Set the left Y-axis to show Units (Budgeted, Actual, Remaining) and the right Y-axis to show Cumulative values (Cum Actual, Cum Remaining, Cum Baseline — the tail of the S-Curve).
10
+ units dont need to be in period values
11
+ Budgeted units, Actual units, Remaining Early units , Remaining Late units, should be read left y axis
12
+ right side is cumulative units, left side is not cumalitve
13
+ Make sure Units are only plotted on the left Y-axis and Cumulative values on the right Y-axis — do not stack or overlay them; keep each axis independent with clear titles (‘Units’ left, ‘Cumulative Units/Cost’ right)
14
+ lets add reset button