Require explicit dates for daily generation
This commit is contained in:
23
docs/cli.md
23
docs/cli.md
@@ -20,7 +20,7 @@ Markdown report after final metadata is saved.
|
||||
|
||||
```text
|
||||
weatherreporter --help
|
||||
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
@@ -40,17 +40,14 @@ weatherreporter inspect sources [--config PATH] RUN_ID
|
||||
Implemented `generate` commands write a JSON module snapshot, YAML data package,
|
||||
preflight artifact, managed Markdown report, and metadata under the configured
|
||||
workspace. `--out` writes an extra Markdown copy for the operator; distributor
|
||||
notification uses the managed report path, not the extra copy. `generate today`,
|
||||
`generate tomorrow`, and `generate hourly` write managed generated-text artifacts,
|
||||
validate structured text from Scriptorium, and render the managed Markdown
|
||||
report from embedded templates. `generate hourly` covers the next six hours in
|
||||
the effective report timezone and does not accept date or event window flags.
|
||||
`generate storm` requires explicit event-window bounds with `--start` and
|
||||
`--end`.
|
||||
|
||||
`generate daily` remains the existing Daily Today report. It is separate from
|
||||
`generate today`; the commands use different report IDs, artifact groups,
|
||||
prompts, and config override keys.
|
||||
notification uses the managed report path, not the extra copy. `generate daily`,
|
||||
`generate today`, `generate tomorrow`, and `generate hourly` write managed
|
||||
generated-text artifacts, validate structured text from Scriptorium, and render
|
||||
the managed Markdown report from embedded templates. `generate daily` requires
|
||||
`--date YYYY-MM-DD` for the selected local civil day. `generate hourly` covers
|
||||
the next six hours in the effective report timezone and does not accept date or
|
||||
event window flags. `generate storm` requires explicit event-window bounds with
|
||||
`--start` and `--end`.
|
||||
|
||||
`run morning` generates Today Report and the 3-Day Outlook, plus Weekend Outlook
|
||||
except on Sunday. `run evening` generates the Tomorrow Report. Batch
|
||||
@@ -76,7 +73,7 @@ They do not fetch weather data or invoke `scriptorium`.
|
||||
- `--tz NAME`: override configured Weather API timezone for `generate` and `run`.
|
||||
- `--out PATH`: write an extra Markdown report copy where supported by the `generate` command.
|
||||
- `--out-dir PATH`: write extra Markdown report copies for `run morning` and `run evening`.
|
||||
- `--date YYYY-MM-DD`: optional date for `generate daily` and `generate today`; defaults to the current local date in the configured timezone.
|
||||
- `--date YYYY-MM-DD`: required date for `generate daily`; optional date for `generate today`, defaulting to the current local date in the configured timezone.
|
||||
- `--start TIME`: required start time for `generate storm`.
|
||||
- `--end TIME`: required end time for `generate storm`.
|
||||
- `--limit N`: maximum records for `inspect reports`; defaults to `20`, and `0` means no limit.
|
||||
|
||||
@@ -91,7 +91,7 @@ Prior snapshot lookup reads stored metadata through the shared lookup path and
|
||||
selects the latest earlier snapshot whose report ID is compatible with the
|
||||
current report definition.
|
||||
|
||||
- Daily Today compares with prior Daily Today snapshots for the same valid
|
||||
- Daily Report compares with prior Daily Report snapshots for the same valid
|
||||
local date.
|
||||
- Today Report compares with prior Today Report snapshots for the same valid
|
||||
local date.
|
||||
|
||||
@@ -24,16 +24,15 @@ a JSON module snapshot, build a YAML prompt input data package, run
|
||||
workspace. Markdown-path reports then run `scriptorium run` directly to the
|
||||
managed Markdown report path.
|
||||
|
||||
`generate today`, `generate tomorrow`, and `generate hourly` use the
|
||||
generated-text-template workflow. They run structured `scriptorium run` to raw
|
||||
GeneratedText JSON, validate the structured text, save a render context, and
|
||||
render the managed Markdown report from embedded templates. `generate today`
|
||||
covers the selected or current local civil day. `generate hourly` covers the
|
||||
six-hour rolling period from generation time in the effective report timezone
|
||||
and is not part of scheduled morning or evening batches.
|
||||
|
||||
`generate daily` remains the existing Daily Today report and is independent of
|
||||
`generate today`.
|
||||
`generate daily`, `generate today`, `generate tomorrow`, and `generate hourly`
|
||||
use the generated-text-template workflow. They run structured `scriptorium run`
|
||||
to raw GeneratedText JSON, validate the structured text, save a render context,
|
||||
and render the managed Markdown report from embedded templates. `generate
|
||||
daily` requires `--date YYYY-MM-DD` for the selected local civil day.
|
||||
`generate today` covers the selected or current local civil day. `generate
|
||||
hourly` covers the six-hour rolling period from generation time in the
|
||||
effective report timezone and is not part of scheduled morning or evening
|
||||
batches.
|
||||
|
||||
When distributor notification is enabled, weatherreporter uploads the managed
|
||||
Markdown report after report rendering succeeds and final metadata is saved.
|
||||
@@ -293,13 +292,13 @@ source provenance and warnings without dumping full weather payloads.
|
||||
Recent Changes are computed from structured module snapshots, not rendered
|
||||
Markdown or YAML text.
|
||||
|
||||
Daily Today compares with prior Daily Today snapshots for the same valid local
|
||||
date. Today Report compares with prior Today Report snapshots for the same
|
||||
valid local date. Tomorrow Report compares with prior Tomorrow Report snapshots
|
||||
for the same valid local date. 3-Day Outlook compares with prior compatible
|
||||
3-Day snapshots for the same valid local date. Weekend Outlook compares with
|
||||
prior compatible Weekend snapshots for the same weekend window. Hourly Report
|
||||
and Storm Report leave Recent Changes empty.
|
||||
Daily Report compares with prior Daily Report snapshots for the same valid
|
||||
local date. Today Report compares with prior Today Report snapshots for the
|
||||
same valid local date. Tomorrow Report compares with prior Tomorrow Report
|
||||
snapshots for the same valid local date. 3-Day Outlook compares with prior
|
||||
compatible 3-Day snapshots for the same valid local date. Weekend Outlook
|
||||
compares with prior compatible Weekend snapshots for the same weekend window.
|
||||
Hourly Report and Storm Report leave Recent Changes empty.
|
||||
|
||||
When no prior comparable snapshot exists, or no configured threshold is crossed,
|
||||
`recentChanges.items` is empty.
|
||||
|
||||
@@ -2061,6 +2061,22 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateDailyRequiresDate(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
cfg.WeatherAPI.Timezone = "America/Chicago"
|
||||
|
||||
_, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
Report: ReportDaily,
|
||||
}, mustParse("2026-05-29T08:00:00-05:00"))
|
||||
if err == nil {
|
||||
t.Fatal("ResolveGenerate() error = nil, want required date error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "requires an explicit date") {
|
||||
t.Fatalf("ResolveGenerate() error = %q, want required date context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateUsesConfiguredReportModules(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.yml")
|
||||
if err := os.WriteFile(path, []byte(`
|
||||
|
||||
@@ -17,7 +17,7 @@ const helpText = `weatherreporter prepares weather reports from normalized forec
|
||||
|
||||
Usage:
|
||||
weatherreporter --help
|
||||
weatherreporter generate daily [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate daily --date YYYY-MM-DD [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate today [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] [--date YYYY-MM-DD]
|
||||
weatherreporter generate tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
weatherreporter generate hourly [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||
@@ -217,7 +217,15 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
|
||||
}
|
||||
|
||||
switch reportKind {
|
||||
case app.ReportDaily, app.ReportToday:
|
||||
case app.ReportDaily:
|
||||
if opts.Date == "" {
|
||||
return app.GenerateRequest{}, fmt.Errorf("generate daily requires --date YYYY-MM-DD")
|
||||
}
|
||||
req.Date, err = timeutil.ParseLocalDate(opts.Date, location)
|
||||
if err != nil {
|
||||
return app.GenerateRequest{}, err
|
||||
}
|
||||
case app.ReportToday:
|
||||
if opts.Date == "" {
|
||||
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ func TestRunHelpLongFlag(t *testing.T) {
|
||||
t.Fatalf("Run() error = %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(output.stdout, "generate daily") {
|
||||
if !strings.Contains(output.stdout, "weatherreporter generate daily --date YYYY-MM-DD") {
|
||||
t.Fatalf("help output missing generate command:\n%s", output.stdout)
|
||||
}
|
||||
if !strings.Contains(output.stdout, "generate today") {
|
||||
@@ -571,6 +571,14 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
||||
if len(reportMatches) != 1 {
|
||||
t.Fatalf("managed reports = %#v, want one", reportMatches)
|
||||
}
|
||||
rawGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "daily", "2026-05-29", "*.generated_text.raw.json")
|
||||
validatedGeneratedTextPath := oneArtifact(t, workspaceRoot, "snapshots", "daily", "2026-05-29", "*.generated_text.json")
|
||||
renderContextPath := oneArtifact(t, workspaceRoot, "snapshots", "daily", "2026-05-29", "*.render_context.json")
|
||||
metadataPath := oneArtifact(t, workspaceRoot, "snapshots", "daily", "2026-05-29", "*.metadata.json")
|
||||
assertFileContains(t, rawGeneratedTextPath, `"summary": "Showers are possible during the selected day."`)
|
||||
assertFileContains(t, validatedGeneratedTextPath, `"summary":"Showers are possible during the selected day."`)
|
||||
assertFileContains(t, renderContextPath, `"Title": "Friday's Weather"`)
|
||||
assertFileContains(t, metadataPath, `"generatedTextSchemaId": "daily"`)
|
||||
}
|
||||
|
||||
func TestRunGenerateTodayWritesGeneratedTextReport(t *testing.T) {
|
||||
@@ -975,14 +983,44 @@ func TestResolveGenerateHourlyRejectsDateAndStormBounds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateDailyDefaultsDateInConfiguredTimezone(t *testing.T) {
|
||||
func TestResolveGenerateDailyRequiresDate(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
req, err := runner.resolveGenerate([]string{"daily"})
|
||||
if err == nil {
|
||||
t.Fatal("resolveGenerate() error = nil, want required date error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "generate daily requires --date YYYY-MM-DD") {
|
||||
t.Fatalf("resolveGenerate() error = %q, want required date context", err.Error())
|
||||
}
|
||||
if !req.Date.IsZero() {
|
||||
t.Fatalf("Date = %s, want unset on error", req.Date)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateDailyRejectsMalformedDate(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
_, err := runner.resolveGenerate([]string{"daily", "--date", "bad-date"})
|
||||
if err == nil {
|
||||
t.Fatal("resolveGenerate() error = nil, want date parse error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `parse date "bad-date" as YYYY-MM-DD`) {
|
||||
t.Fatalf("resolveGenerate() error = %q, want date parse context", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateDailyParsesDate(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
req, err := runner.resolveGenerate([]string{"daily", "--date", "2026-05-29"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGenerate() error = %v", err)
|
||||
}
|
||||
|
||||
if req.Report != app.ReportDaily {
|
||||
t.Fatalf("Report = %q, want daily", req.Report)
|
||||
}
|
||||
if got := req.Date.Format(timeutil.DateLayout); got != "2026-05-29" {
|
||||
t.Fatalf("Date = %s, want 2026-05-29", got)
|
||||
}
|
||||
@@ -1024,7 +1062,7 @@ func TestResolveGenerateTodayDate(t *testing.T) {
|
||||
func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
req, err := runner.resolveGenerate([]string{"daily", "--units", "metric", "--tz", "UTC", "--out", "./daily.md"})
|
||||
req, err := runner.resolveGenerate([]string{"daily", "--date", "2026-05-29", "--units", "metric", "--tz", "UTC", "--out", "./daily.md"})
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGenerate() error = %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user