feat: reviewer chooses the dates — data as-of and the analysis window

Run review keeps the one-click default; a Custom run card takes 'data as of',
'window from' and 'window to' (all optional) and previews the window Ava will
use. Case page shows the chosen window. Pairs with 12_review_window.sql
(review_from / review_to on Run Daily Review) and the charter's THE WINDOW rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-16 18:49:26 +05:30
parent e8741f6333
commit 5e0649fb7e
2 changed files with 108 additions and 35 deletions

View File

@ -240,9 +240,16 @@ export function CaseScreen() {
<Facts
cols={3}
rows={[
{ label: 'Trigger', value: str(row.review_as_of) ? 'Manual run (historical date)' : 'Daily review' },
{ label: 'Data as of', value: str(row.review_as_of) ? str(row.review_as_of).slice(0, 10) : 'Latest available' },
{ label: 'Look-back', value: row.period_weeks ? `${str(row.period_weeks)} weeks` : '6 weeks' },
{ label: 'Trigger', value: str(row.review_as_of) || str(row.review_from) || str(row.review_to) ? 'Custom run (chosen dates)' : 'Daily review' },
{ label: 'Data as of', value: str(row.review_to || row.review_as_of) ? str(row.review_to || row.review_as_of).slice(0, 10) : 'Latest available' },
{
label: 'Analysis window',
value: str(row.review_from)
? `${str(row.review_from).slice(0, 10)} – ${str(row.review_to || row.review_as_of).slice(0, 10) || 'latest'}`
: row.period_weeks
? `${str(row.period_weeks)} weeks`
: '6 weeks',
},
...(str(row.review_note) ? [{ label: 'Reviewer note', value: str(row.review_note), wide: true }] : []),
...(str(row.approval_note) ? [{ label: 'Approval note', value: str(row.approval_note), wide: true }] : []),
]}

View File

@ -1,9 +1,10 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Brain, Database, Moon, Radar } from 'lucide-react';
import { Brain, CalendarRange, Database, Radar } from 'lucide-react';
import { useZino } from '../api/provider';
import { ACT, LAKEHOUSE, QUIET_MORNING_AS_OF, WORKFLOW } from '../api/config';
import { Button, Card, ErrorNote } from '../components/core';
import { Button, Card, ErrorNote, Input, Label } from '../components/core';
import { shortDate } from '../format';
/**
* Run the daily review, now.
@ -12,28 +13,43 @@ import { Button, Card, ErrorNote } from '../components/core';
* the button exists so a room does not have to wait until 6am. Nothing is
* asked of Ava — she decides what, if anything, is worth a person's time.
*
* The second button anchors the sweep on a date before the August spike. The
* demo data is static, so without it every run flags the same thing; with it,
* one run can genuinely come back "nothing to report" — which is what most
* mornings look like, and the contrast is the point.
* The custom run lets the reviewer choose the dates: what the data should be
* treated as "as of" (a replayed morning), and the window Ava compares against
* its baseline. The demo data is static, so without a date every run flags
* the same thing; anchored before the August spike, a run can genuinely come
* back "nothing to report" — which is what most mornings look like.
*/
export function RunReviewScreen() {
const navigate = useNavigate();
const { client } = useZino();
const [busy, setBusy] = useState<'now' | 'quiet' | null>(null);
const [busy, setBusy] = useState<'now' | 'custom' | null>(null);
const [err, setErr] = useState<string | null>(null);
const [asOf, setAsOf] = useState('');
const [from, setFrom] = useState('');
const [to, setTo] = useState('');
async function run(asOf?: string) {
setBusy(asOf ? 'quiet' : 'now');
// The window Ava will actually use, so the reviewer sees it before pressing Run.
const effectiveTo = to || asOf;
const dateErr =
from && effectiveTo && from > effectiveTo
? 'The window must start before it ends.'
: to && asOf && to > asOf
? '"Window to" cannot be after "data as of".'
: null;
async function run(custom: boolean) {
setBusy(custom ? 'custom' : 'now');
setErr(null);
try {
// The activity's one field is optional; omit it entirely for a live run
// so the workflow sees a genuinely empty submission.
const res = await client.startInstance(
WORKFLOW,
ACT.REVIEW,
asOf ? { review_as_of__review: asOf } : {},
);
// Every field on the activity is optional; send only the ones the
// reviewer set, so a plain run is a genuinely empty submission.
const data: Record<string, string> = {};
if (custom) {
if (asOf) data.review_as_of__review = asOf;
if (from) data.review_from__review = from;
if (to) data.review_to__review = to;
}
const res = await client.startInstance(WORKFLOW, ACT.REVIEW, data);
if (res.instance_id) navigate(`/c/${res.instance_id}`);
else navigate('/q/investigating');
} catch (e) {
@ -83,26 +99,76 @@ export function RunReviewScreen() {
</div>
</div>
<div className="mt-5 flex flex-wrap items-start gap-3 border-t border-line pt-4">
<div>
<Button kind="primary" onClick={() => run()} busy={busy === 'now'} disabled={busy !== null}>
<div className="mt-5 border-t border-line pt-4">
<Button kind="primary" onClick={() => run(false)} busy={busy === 'now'} disabled={busy !== null}>
<Radar className="h-4 w-4" />
Run review
</Button>
<p className="mt-1 max-w-[16rem] text-[11px] text-faint">
Scheduled daily at 06:00. This runs the same activity immediately.
<p className="mt-1 text-[11px] text-faint">
Latest data, last 6 weeks against the 8 weeks before. Scheduled daily at 06:00; this runs
the same activity immediately.
</p>
</div>
</Card>
<Card className="px-4 py-4">
<div className="flex items-start gap-3">
<span className="rounded-full bg-panel2 p-2 text-muted ring-1 ring-line ring-inset">
<CalendarRange className="h-4 w-4" />
</span>
<div className="min-w-0 flex-1">
<p className="font-semibold text-ink">Custom run</p>
<p className="mt-0.5 text-sm text-muted">
Replay a past morning, or point the review at a specific period. Leave a date blank to
use the default.
</p>
</div>
</div>
<div className="mt-4 grid gap-3 sm:grid-cols-3">
<div>
<Label>Data as of</Label>
<Input type="date" value={asOf} onChange={setAsOf} />
<p className="mt-1 text-[11px] text-faint">Ignore anything after this date. Try {shortDate(QUIET_MORNING_AS_OF)} for a quiet morning.</p>
</div>
<div>
<Button kind="secondary" onClick={() => run(QUIET_MORNING_AS_OF)} busy={busy === 'quiet'} disabled={busy !== null}>
<Moon className="h-4 w-4" />
Run as of {QUIET_MORNING_AS_OF}
</Button>
<p className="mt-1 max-w-[16rem] text-[11px] text-faint">
Runs the review against data as of this date — a period with no material change,
to show a no-findings outcome.
</p>
<Label>Window from</Label>
<Input type="date" value={from} onChange={setFrom} />
<p className="mt-1 text-[11px] text-faint">Start of the period Ava analyses.</p>
</div>
<div>
<Label>Window to</Label>
<Input type="date" value={to} onChange={setTo} />
<p className="mt-1 text-[11px] text-faint">End of the period. Defaults to "data as of".</p>
</div>
</div>
<p className="mt-3 text-xs text-muted">
{dateErr ? (
<span className="text-bad">{dateErr}</span>
) : (
<>
Ava will compare{' '}
<span className="font-semibold text-ink">
{from ? shortDate(from) : effectiveTo ? '6 weeks before' : 'the last 6 weeks'}
{from || effectiveTo ? ` – ${effectiveTo ? shortDate(effectiveTo) : 'latest data'}` : ''}
</span>{' '}
against the 8 weeks before it
{asOf ? `, treating ${shortDate(asOf)} as the latest date` : ''}.
</>
)}
</p>
<div className="mt-4">
<Button
kind="secondary"
onClick={() => run(true)}
busy={busy === 'custom'}
disabled={busy !== null || !!dateErr || (!asOf && !from && !to)}
>
<CalendarRange className="h-4 w-4" />
Run with these dates
</Button>
</div>
</Card>
</div>