Spaces:
Running
Running
Add 3 files
Browse files- README.md +7 -5
- index.html +350 -19
- prompts.txt +14 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 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 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
</
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|