feat: align frontend with rusty-telemetry v0.5.0 API changes

- Update ShiftPointAnalysis types: gear_ranges → gear_curves (with
  acceleration data points), shift_events → crossover_points, add
  runs_analyzed field
- Add UseCaseInfo type for GET /api/use-cases (now returns objects
  with id, name, guidance instead of plain strings)
- Add guidance and recording_id fields to UseCaseSession
- Rewrite ShiftPointsView: scatter chart for gear acceleration curves,
  dual y-axis bar chart for crossover RPM/speed, 4-column summary
- Update SessionDetailView: show session guidance, crossover points
  and runs analyzed in analysis stats
- Extract shared type guard functions into src/utils/analysis.ts
- Type getAnalysis/reAnalyze as AnalysisResult instead of
  Record<string, unknown>
- Type getGames return as string[] to match backend
This commit is contained in:
Jan-Peter von Hunnius 2026-06-05 21:28:27 +02:00
parent aa6293d346
commit 87e5bcae5b
6 changed files with 156 additions and 114 deletions

View file

@ -3,6 +3,7 @@ import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useSessionsStore } from '@/stores/sessions'
import type { ShiftPointAnalysis, TrackMapAnalysis } from '@/types'
import { isShiftPointAnalysis, isTrackMapAnalysis } from '@/utils/analysis'
const props = defineProps<{
id: string
@ -34,19 +35,16 @@ const analysisData = computed(() => {
const shiftAnalysis = computed((): ShiftPointAnalysis | null => {
if (!analysisData.value) return null
// The analysis field is the full analysis object
const data = analysisData.value
if (data && typeof data === 'object' && 'gear_ranges' in data) {
return data as unknown as ShiftPointAnalysis
if (isShiftPointAnalysis(analysisData.value)) {
return analysisData.value
}
return null
})
const trackAnalysis = computed((): TrackMapAnalysis | null => {
if (!analysisData.value) return null
const data = analysisData.value
if (data && typeof data === 'object' && 'boundaries' in data) {
return data as unknown as TrackMapAnalysis
if (isTrackMapAnalysis(analysisData.value)) {
return analysisData.value
}
return null
})
@ -147,6 +145,10 @@ async function handleDelete() {
<span class="info-label">Completed</span>
<span>{{ formatTime(session.completed_at) }}</span>
</div>
<div v-if="session.guidance" class="info-item" style="grid-column: 1 / -1">
<span class="info-label">Guidance</span>
<span class="text-secondary" style="font-size: 0.85rem">{{ session.guidance }}</span>
</div>
</div>
</section>
@ -243,14 +245,18 @@ async function handleDelete() {
</div>
<!-- Quick stats for shift points -->
<div v-if="shiftAnalysis" class="analysis-stats grid grid-2 mb-2">
<div v-if="shiftAnalysis" class="analysis-stats grid grid-3 mb-2">
<div class="stat-card">
<span class="stat-label">Recommended Shift RPM</span>
<span class="stat-value mono">{{ Math.round(shiftAnalysis.recommended_shift_rpm ?? 0) }}</span>
</div>
<div class="stat-card">
<span class="stat-label">Shift Events Detected</span>
<span class="stat-value mono">{{ shiftAnalysis.shift_events.length }}</span>
<span class="stat-label">Crossover Points</span>
<span class="stat-value mono">{{ shiftAnalysis.crossover_points.length }}</span>
</div>
<div class="stat-card">
<span class="stat-label">Runs Analyzed</span>
<span class="stat-value mono">{{ shiftAnalysis.runs_analyzed }}</span>
</div>
</div>