feat: extend evidence passport to spots
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
---
|
||||
import Layout from "../../layouts/Layout.astro";
|
||||
import AtlasBreadcrumbs from "../../components/AtlasBreadcrumbs.astro";
|
||||
import SourceBadge from "../../components/SourceBadge.astro";
|
||||
import CoordinateRadar from "../../components/CoordinateRadar.astro";
|
||||
import TackleGlyph from "../../components/TackleGlyph.astro";
|
||||
import ActivityTimeline from "../../components/ActivityTimeline.astro";
|
||||
import CatchList from "../../components/CatchList.astro";
|
||||
import { activityLevel, api, ApiError, plural, type Activity, type Catch, type PaginatedActivity, type Spot } from "../../lib/api";
|
||||
import DataPassport from "../../components/DataPassport.astro";
|
||||
import { activityLevel, api, ApiError, type Activity, type Catch, type PaginatedActivity, type Spot } from "../../lib/api";
|
||||
const { id } = Astro.params;
|
||||
let spot: Spot | null = null, catches: Catch[] = [], activity: Activity | null = null, unavailable = false;
|
||||
let timeline: { start: string; end: string; count: number }[] = [];
|
||||
@@ -44,7 +44,7 @@ const breadcrumbs = spot ? { "@context": "https://schema.org", "@type": "Breadcr
|
||||
<div class="section-heading"><h2>Последние уловы</h2></div>
|
||||
{catches.length ? <CatchList catches={catches}/> : <div class="state compact-state"><h2>Уловов пока нет</h2><p>Для этой точки нет одобренных наблюдений.</p></div>}
|
||||
</div>
|
||||
<aside><span class="eyebrow">Оценка за 24 часа</span>{activity && level ? <><h2>{activity.activity_score} / 100</h2><strong data-activity-level={level.short}>{level.description}</strong><div class="source-strip">{activity.sources.map(source => <SourceBadge source={source}/>)}</div><p>{activity.explanation}</p><p class="note">Основано на {activity.catches} {plural(activity.catches, ["наблюдении", "наблюдениях", "наблюдениях"])} от {activity.unique_players} {plural(activity.unique_players, ["игрока", "игроков", "игроков"])}. Уверенность: {activity.confidence_score}%.</p></> : <p>За последние 24 часа данных для расчёта нет.</p>}<span class="eyebrow">Лучшие приманки</span>{spot.top_baits.length ? <ol>{spot.top_baits.map(name => <li><span class="tackle-label"><TackleGlyph name={name} size={24}/><span>{name}</span></span></li>)}</ol> : <p>Недостаточно данных.</p>}<p class="note">Учитываются только одобренные наблюдения.</p></aside>
|
||||
<aside><span class="eyebrow">Оценка за 24 часа</span>{activity && level ? <><h2>{activity.activity_score} / 100</h2><strong data-activity-level={level.short}>{level.description}</strong><p>{activity.explanation}</p><DataPassport sources={activity.sources} observedAt={activity.last_confirmed_at} confidence={activity.confidence_score} sampleSize={activity.catches} independentPlayers={activity.unique_players} coordinatePrecision={activity.coordinate_precision} status={activity.catches < 3 ? "incomplete" : "verified"}/></> : <p>За последние 24 часа данных для расчёта нет.</p>}<h3 class="detail-action-heading">Что взять</h3>{spot.top_baits.length ? <ol>{spot.top_baits.map(name => <li><span class="tackle-label"><TackleGlyph name={name} size={24}/><span>{name}</span></span></li>)}</ol> : <p>Недостаточно данных.</p>}<p class="note">Учитываются только одобренные наблюдения.</p></aside>
|
||||
</section>
|
||||
</>}
|
||||
</Layout>
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
.query-summary__filters{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}
|
||||
.query-summary__filters span{padding:6px 10px;border:1px solid #c8d3cb;border-radius:20px;background:#f8faf5;color:#526662;font-size:12px}
|
||||
.query-summary__filters a{margin-left:4px;color:#4d625e;font-size:13px;text-underline-offset:3px}
|
||||
.detail-action-heading{margin:27px 0 8px;font:400 24px Georgia,serif;color:var(--lime)}
|
||||
@media(max-width:720px){.query-summary{display:block;padding:16px 14px 0;width:100%}.query-summary h2{font-size:23px}.query-summary__filters{justify-content:flex-start;flex-wrap:nowrap;overflow-x:auto;padding-top:11px;scroll-snap-type:x proximity;scrollbar-width:none}.query-summary__filters::-webkit-scrollbar{display:none}.query-summary__filters span{flex:0 0 auto;scroll-snap-align:start}.query-summary__filters a{position:sticky;right:0;flex:0 0 auto;padding:6px 10px;background:var(--paper)}}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("spot detail exposes the same evidence passport as activity cards", async ({ page }) => {
|
||||
await page.goto("/spots/vyunok-6331x6332");
|
||||
|
||||
const passport = page.locator(".detail-grid aside .data-passport");
|
||||
await expect(passport).toBeVisible();
|
||||
await expect(passport).toContainText("Свежесть");
|
||||
await expect(passport).toContainText("Наблюдения");
|
||||
await expect(passport).toContainText("Игроки");
|
||||
await expect(passport).toContainText("Координаты");
|
||||
await expect(page.getByRole("heading", { name: "Что взять" })).toBeVisible();
|
||||
});
|
||||
+2
-2
@@ -94,10 +94,10 @@
|
||||
|
||||
- [ ] **U01 · UX-контракт и scorecard.** Зафиксировать task-based навигацию, смысловой порядок ответа, словарь статусов (`fresh`, `stale`, `insufficient_data`, `blocked`, `temporary_error`) и единый scorecard: time-to-first-useful-answer, filter-to-result steps, completion rate, overflow, axe и keyboard regression. Сверить каждый публичный маршрут с контрактом; не добавлять фичи, которые не улучшают решение игрока.
|
||||
- [ ] **U02 · Главный сценарий «рыба → водоём → точка → снасть».** Главная уже сохраняет рыбу, водоём, период и сортировку в shareable URL и после поиска явно показывает контекст запроса, включая мобильный режим; empty-state предлагает вернуться к полному набору данных. Осталось показать 3–5 вариантов с понятным CTA и добавить режим map. Acceptance: первый полезный вариант виден без регистрации, back/refresh сохраняют контекст, mobile не теряет фильтры.
|
||||
- [ ] **U03 · Evidence/trust card.** Для activity-карточек добавлен первый общий evidence-контракт: freshness, confidence, число наблюдений, независимые игроки, `coordinate_precision`, source badges и явный статус `Неполные данные` при малой выборке; targeted E2E проверяет эти поля. Осталось протянуть тот же контракт на spot, waterbody и tackle, добавить период выборки, completeness, конфликт источников и правило минимальной выборки для любых рекомендаций. Нельзя показывать «лучшую точку» или AI-like рекомендацию без объяснения и минимальной выборки.
|
||||
- [ ] **U03 · Evidence/trust card.** Общий evidence-контракт теперь используется и на activity-карточках, и на detail точки: freshness, confidence, число наблюдений, независимые игроки, `coordinate_precision`, source badges и явный статус `Неполные данные` при малой выборке; targeted E2E проверяет оба пути. Осталось протянуть его на waterbody и tackle, добавить период выборки, completeness, конфликт источников и правило минимальной выборки для любых рекомендаций. Нельзя показывать «лучшую точку» или AI-like рекомендацию без объяснения и минимальной выборки.
|
||||
- [ ] **U04 · List/map и progressive disclosure.** List остаётся честным базовым режимом: фильтры, сортировка, URL-состояние и evidence-карточки уже работают без имитации координатной карты. Следующий шаг — единый list/map-контракт после подтверждения геометрии; на mobile карта должна открываться отдельным действием. Вторичные raw/provenance-поля не исчезают и раскрываются по запросу.
|
||||
- [ ] **U05 · Mobile-first и сохранённый план рыбалки.** Добавить лёгкий сценарий сравнения и сохранения 3–5 вариантов без обязательного аккаунта: рыба, водоём, координаты, метод, наживка, свежесть, доверие и риск. Acceptance: shareable URL/print-карточка, 320/390 px, восстановление после reload, удаление локального плана без внешнего сервиса.
|
||||
- [ ] **U06 · Контентная и визуальная иерархия.** Сократить конкурирующие цифры на карточках, привести badges/source labels к одной семантике, сделать «что делать» главным текстом, а provenance — вторичным раскрываемым слоем. Провести ручной review на 5 ключевых маршрутах и проверить, что цвет не является единственным носителем статуса.
|
||||
- [ ] **U06 · Контентная и визуальная иерархия.** Для detail точки действие «Что взять» выделено отдельным заголовком, а provenance и качество собраны в общем паспорте данных; статусы дополнительно передаются текстом, не только цветом. Осталось сократить конкурирующие цифры на карточках и провести ручной review на 5 ключевых маршрутах.
|
||||
- [ ] **U07 · UX-приёмка и измерения.** Добавить Playwright-контракты для полного journey, list/map, evidence states, saved plan, stale/blocked/insufficient data и share/print. Зафиксировать baseline Lighthouse/axe/CLS/INP и time-to-first-useful-answer; закрывать U01–U06 только по evidence, screenshots и обратной связи пилота.
|
||||
|
||||
- [ ] **Q01 · Документы источников — реестр готов, нужны первичные подтверждения.** Создан единый production-gate с атрибуцией, общим лимитом 30 минут, хранением и процедурой отзыва для RF4DB, RF4-STAT, RF4MAP, RF4 Posts и официального RF4. До открытой публикации приложить устойчивые ссылки/копии первичных разрешений, контакты, даты и отдельно подтвердить право на изображения; пустое поле блокирует соответствующий источник.
|
||||
|
||||
Reference in New Issue
Block a user