glutamatt HF Staff commited on
Commit
b10734b
·
verified ·
1 Parent(s): d3b483b
index.html CHANGED
@@ -45,16 +45,19 @@
45
  <section id="charts-section" class="hidden">
46
  <div class="chart-container">
47
  <h2>🗺️ Distance-based ACWR</h2>
 
48
  <canvas id="distance-chart"></canvas>
49
  </div>
50
 
51
  <div class="chart-container">
52
  <h2>⏱️ Duration-based ACWR</h2>
 
53
  <canvas id="duration-chart"></canvas>
54
  </div>
55
 
56
  <div class="chart-container">
57
  <h2>🔋 TSS-based ACWR</h2>
 
58
  <canvas id="tss-chart"></canvas>
59
  </div>
60
 
 
45
  <section id="charts-section" class="hidden">
46
  <div class="chart-container">
47
  <h2>🗺️ Distance-based ACWR</h2>
48
+ <div id="distance-target" class="target-info"></div>
49
  <canvas id="distance-chart"></canvas>
50
  </div>
51
 
52
  <div class="chart-container">
53
  <h2>⏱️ Duration-based ACWR</h2>
54
+ <div id="duration-target" class="target-info"></div>
55
  <canvas id="duration-chart"></canvas>
56
  </div>
57
 
58
  <div class="chart-container">
59
  <h2>🔋 TSS-based ACWR</h2>
60
+ <div id="tss-target" class="target-info"></div>
61
  <canvas id="tss-chart"></canvas>
62
  </div>
63
 
src/components/charts.ts CHANGED
@@ -296,6 +296,7 @@ export function createDistanceChart(data: MetricACWRData): void {
296
  '(km)',
297
  'rgba(234, 179, 8, 0.8)'
298
  );
 
299
  }
300
 
301
  export function createDurationChart(data: MetricACWRData): void {
@@ -309,6 +310,7 @@ export function createDurationChart(data: MetricACWRData): void {
309
  '(min)',
310
  'rgba(234, 179, 8, 0.8)'
311
  );
 
312
  }
313
 
314
  export function createTSSChart(data: MetricACWRData): void {
@@ -322,6 +324,21 @@ export function createTSSChart(data: MetricACWRData): void {
322
  '',
323
  'rgba(234, 179, 8, 0.8)'
324
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
  }
326
 
327
  export function destroyAllCharts(): void {
 
296
  '(km)',
297
  'rgba(234, 179, 8, 0.8)'
298
  );
299
+ updateTargetInfo('distance-target', data.targetTomorrowValue, 'km', data.targetACWR);
300
  }
301
 
302
  export function createDurationChart(data: MetricACWRData): void {
 
310
  '(min)',
311
  'rgba(234, 179, 8, 0.8)'
312
  );
313
+ updateTargetInfo('duration-target', data.targetTomorrowValue, 'minutes', data.targetACWR);
314
  }
315
 
316
  export function createTSSChart(data: MetricACWRData): void {
 
324
  '',
325
  'rgba(234, 179, 8, 0.8)'
326
  );
327
+ updateTargetInfo('tss-target', data.targetTomorrowValue, 'TSS', data.targetACWR);
328
+ }
329
+
330
+ function updateTargetInfo(elementId: string, targetValue: number | null | undefined, unit: string, targetACWR: number | undefined): void {
331
+ const element = document.getElementById(elementId);
332
+ if (!element) return;
333
+
334
+ if (targetValue !== null && targetValue !== undefined && targetACWR !== undefined) {
335
+ const formattedValue = targetValue.toFixed(1);
336
+ element.innerHTML = `<strong>💡 Target for tomorrow:</strong> <span class="target-value">${formattedValue} ${unit}</span> <span style="color: var(--secondary-color);">to reach ACWR of ${targetACWR}</span>`;
337
+ element.classList.add('visible');
338
+ } else {
339
+ element.innerHTML = `<strong>ℹ️ Target for tomorrow:</strong> <span style="color: var(--secondary-color);">Need at least 28 days of data to calculate</span>`;
340
+ element.classList.add('visible');
341
+ }
342
  }
343
 
344
  export function destroyAllCharts(): void {
src/style.css CHANGED
@@ -348,6 +348,32 @@ h2 {
348
  width: 100%;
349
  }
350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  /* Legend */
352
  .legend {
353
  margin-top: 1rem;
 
348
  width: 100%;
349
  }
350
 
351
+ .target-info {
352
+ background-color: var(--bg-color);
353
+ border-left: 4px solid var(--primary-color);
354
+ padding: 0.75rem 1rem;
355
+ margin-bottom: 1rem;
356
+ border-radius: 4px;
357
+ font-size: 0.875rem;
358
+ color: var(--text-color);
359
+ display: none;
360
+ }
361
+
362
+ .target-info.visible {
363
+ display: block;
364
+ }
365
+
366
+ .target-info strong {
367
+ color: var(--primary-color);
368
+ font-weight: 600;
369
+ }
370
+
371
+ .target-info .target-value {
372
+ font-size: 1.125rem;
373
+ font-weight: 700;
374
+ color: var(--primary-color);
375
+ }
376
+
377
  /* Legend */
378
  .legend {
379
  margin-top: 1rem;
src/types/index.ts CHANGED
@@ -20,4 +20,6 @@ export interface MetricACWRData {
20
  average7d: (number | null)[]; // 7-day rolling average
21
  average28d: (number | null)[]; // 28-day rolling average
22
  acwr: (number | null)[]; // ACWR based on this metric
 
 
23
  }
 
20
  average7d: (number | null)[]; // 7-day rolling average
21
  average28d: (number | null)[]; // 28-day rolling average
22
  acwr: (number | null)[]; // ACWR based on this metric
23
+ targetTomorrowValue?: number | null; // Value needed tomorrow to reach target ACWR of 1.3
24
+ targetACWR?: number; // The target ACWR value used for calculation
25
  }
src/utils/metricAcwr.ts CHANGED
@@ -122,11 +122,62 @@ export function calculateMetricACWR(
122
  }
123
  });
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  return {
126
  dates,
127
  values,
128
  average7d,
129
  average28d,
130
  acwr,
 
 
131
  };
132
  }
 
122
  }
123
  });
124
 
125
+ // Calculate tomorrow's required value to reach ACWR
126
+ let targetTomorrowValue: number | null = null;
127
+ const targetACWR = 1.3;
128
+
129
+ if (allDates.length >= 28) {
130
+
131
+ // Calculate tomorrow's 7-day acute sum (last 6 days including zeros)
132
+ let tomorrowAcuteSum = 0;
133
+ for (let i = allDates.length - 6; i < allDates.length; i++) {
134
+ const checkDateStr = formatDateLocal(allDates[i]);
135
+ const val = dailyValues.get(checkDateStr) || 0; // Include 0 for rest days
136
+ tomorrowAcuteSum += val;
137
+ }
138
+
139
+ // Calculate tomorrow's 28-day chronic sum (last 27 days including zeros)
140
+ let tomorrowChronicSum = 0;
141
+ for (let i = allDates.length - 27; i < allDates.length; i++) {
142
+ const checkDateStr = formatDateLocal(allDates[i]);
143
+ const val = dailyValues.get(checkDateStr) || 0; // Include 0 for rest days
144
+ tomorrowChronicSum += val;
145
+ }
146
+
147
+ // Solve for tomorrow's value (X):
148
+ // Tomorrow's ACWR = [(tomorrowAcuteSum + X) / 7] / [(tomorrowChronicSum + X) / 28]
149
+ // Simplifies to: targetACWR = (tomorrowAcuteSum + X) / (tomorrowChronicSum + X) * 28 / 7
150
+ // targetACWR = (tomorrowAcuteSum + X) / (tomorrowChronicSum + X) * 4
151
+ // targetACWR * (tomorrowChronicSum + X) = 4 * (tomorrowAcuteSum + X)
152
+ // targetACWR * tomorrowChronicSum + targetACWR * X = 4 * tomorrowAcuteSum + 4 * X
153
+ // targetACWR * X - 4 * X = 4 * tomorrowAcuteSum - targetACWR * tomorrowChronicSum
154
+ // X * (targetACWR - 4) = 4 * tomorrowAcuteSum - targetACWR * tomorrowChronicSum
155
+ // X = (4 * tomorrowAcuteSum - targetACWR * tomorrowChronicSum) / (targetACWR - 4)
156
+
157
+ const numerator = 4 * tomorrowAcuteSum - targetACWR * tomorrowChronicSum;
158
+ const denominator = targetACWR - 4;
159
+
160
+ if (Math.abs(denominator) > 0.001) {
161
+ targetTomorrowValue = numerator / denominator;
162
+
163
+ // Set to 0 if negative (meaning you should rest)
164
+ if (targetTomorrowValue < 0) {
165
+ targetTomorrowValue = 0;
166
+ }
167
+ } else {
168
+ // Special case: targetACWR ≈ 4, need different approach
169
+ // If ACWR = 4, then acute = 4 * chronic, which means you need massive increase
170
+ targetTomorrowValue = null;
171
+ }
172
+ }
173
+
174
  return {
175
  dates,
176
  values,
177
  average7d,
178
  average28d,
179
  acwr,
180
+ targetTomorrowValue,
181
+ targetACWR: allDates.length >= 28 ? targetACWR : undefined,
182
  };
183
  }