Files
fuel-price/resources/js/components/PredictionFull.vue
Ovidiu U 775e076bb7
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled
Add current_period tracking to subscriptions, document prediction engine, and refactor station list UI
Add current_period_start, current_period_end, and stripe_data columns to subscriptions table via migration. Extend Subscription model with datetime casts for new fields. Create comprehensive prediction engine documentation covering signals, aggregation, confidence calibration, and weekly summary logic. Add PredictionFull Vue component displaying action label, reasoning, and 7-day context. Refactor StationList to collapse outdated stations behind expandable section. Add UpsellBanner component with station count formatting. Create .claude/settings.json denying .env file access. Add todo.md tracking Stripe dashboard setup, production deployment steps, and E2E QA checklist. Update .env.example with fuel-finder credentials, Anthropic config, and complete Stripe price IDs.
2026-04-29 18:14:03 +01:00

94 lines
3.5 KiB
Vue

<template>
<div
v-if="prediction"
class="p-4 sm:p-5 bg-white rounded-2xl border border-zinc-300"
>
<div class="grid gap-4 lg:grid-cols-2 lg:gap-5">
<div class="space-y-1.5">
<p class="font-mono text-[10px] uppercase tracking-widest text-zinc-500 mb-1 truncate">Price Prediction</p>
<h3 class="text-sm font-semibold text-zinc-800 leading-snug">{{ actionLabel }}</h3>
<p class="text-sm text-zinc-500 leading-snug">{{ prediction.reasoning }}</p>
<p class="text-sm text-zinc-500 leading-snug">
<span>Avg {{ prediction.current_avg }}p</span>
<span class="text-zinc-400"> · </span>
<span>Confidence {{ prediction.confidence_label }}</span>
<template v-if="prediction.predicted_change_pence">
<span class="text-zinc-400"> · </span>
<span>{{ prediction.predicted_change_pence > 0 ? '+' : '' }}{{ prediction.predicted_change_pence.toFixed(1) }}p expected</span>
</template>
</p>
</div>
<div
v-if="weeklyHeadline || todayContext"
class="space-y-1.5 pt-3 border-t border-zinc-200 lg:pt-0 lg:border-t-0 lg:border-l lg:pl-5"
>
<p class="font-mono text-[10px] uppercase tracking-widest text-zinc-500 mb-1 truncate">Last 7 days</p>
<p v-if="weeklyHeadline" class="text-sm font-semibold text-zinc-800 leading-snug">{{ weeklyHeadline }}</p>
<p v-if="todayContext" class="text-sm text-zinc-500 leading-snug">{{ todayContext }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
prediction: { type: Object, default: null },
})
const actionLabel = computed(() => {
if (!props.prediction) return ''
return {
fill_now: 'Fill up now',
wait: 'Wait — prices falling',
no_signal: 'No clear signal',
}[props.prediction.action] ?? 'Check local prices'
})
const weekly = computed(() => props.prediction?.weekly_summary ?? null)
function formatPence(value) {
if (value === null || value === undefined) return null
return Number(value).toFixed(1) + 'p'
}
function formatDateShort(iso) {
if (!iso) return ''
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return iso
return d.toLocaleDateString('en-GB', { weekday: 'short', day: 'numeric' })
}
const weeklyHeadline = computed(() => {
const w = weekly.value
if (!w || !w.cheapest_day || !w.priciest_day || w.last_7_days_change_pence === null) {
return null
}
const change = w.last_7_days_change_pence
const lead = change > 0.05
? `Avg rose ${change.toFixed(1)}p`
: change < -0.05
? `Avg fell ${Math.abs(change).toFixed(1)}p`
: 'Avg held steady'
return `${lead} — cheapest ${formatDateShort(w.cheapest_day.date)} (${formatPence(w.cheapest_day.avg)}), priciest ${formatDateShort(w.priciest_day.date)} (${formatPence(w.priciest_day.avg)}).`
})
const todayContext = computed(() => {
const w = weekly.value
if (!w) return null
const today = formatPence(w.today_avg)
const tomorrow = formatPence(w.tomorrow_estimated_avg)
if (today && tomorrow) {
return `Today ${today}; tomorrow ≈ ${tomorrow}.`
}
if (today) {
return `Today ${today}.`
}
return null
})
</script>