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
|
```text
|
||||||
weatherreporter --help
|
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 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 tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||||
weatherreporter generate hourly [--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,
|
Implemented `generate` commands write a JSON module snapshot, YAML data package,
|
||||||
preflight artifact, managed Markdown report, and metadata under the configured
|
preflight artifact, managed Markdown report, and metadata under the configured
|
||||||
workspace. `--out` writes an extra Markdown copy for the operator; distributor
|
workspace. `--out` writes an extra Markdown copy for the operator; distributor
|
||||||
notification uses the managed report path, not the extra copy. `generate today`,
|
notification uses the managed report path, not the extra copy. `generate daily`,
|
||||||
`generate tomorrow`, and `generate hourly` write managed generated-text artifacts,
|
`generate today`, `generate tomorrow`, and `generate hourly` write managed
|
||||||
validate structured text from Scriptorium, and render the managed Markdown
|
generated-text artifacts, validate structured text from Scriptorium, and render
|
||||||
report from embedded templates. `generate hourly` covers the next six hours in
|
the managed Markdown report from embedded templates. `generate daily` requires
|
||||||
the effective report timezone and does not accept date or event window flags.
|
`--date YYYY-MM-DD` for the selected local civil day. `generate hourly` covers
|
||||||
`generate storm` requires explicit event-window bounds with `--start` and
|
the next six hours in the effective report timezone and does not accept date or
|
||||||
`--end`.
|
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.
|
|
||||||
|
|
||||||
`run morning` generates Today Report and the 3-Day Outlook, plus Weekend Outlook
|
`run morning` generates Today Report and the 3-Day Outlook, plus Weekend Outlook
|
||||||
except on Sunday. `run evening` generates the Tomorrow Report. Batch
|
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`.
|
- `--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 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`.
|
- `--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`.
|
- `--start TIME`: required start time for `generate storm`.
|
||||||
- `--end TIME`: required end 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.
|
- `--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
|
selects the latest earlier snapshot whose report ID is compatible with the
|
||||||
current report definition.
|
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.
|
local date.
|
||||||
- Today Report compares with prior Today Report snapshots for the same valid
|
- Today Report compares with prior Today Report snapshots for the same valid
|
||||||
local date.
|
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
|
workspace. Markdown-path reports then run `scriptorium run` directly to the
|
||||||
managed Markdown report path.
|
managed Markdown report path.
|
||||||
|
|
||||||
`generate today`, `generate tomorrow`, and `generate hourly` use the
|
`generate daily`, `generate today`, `generate tomorrow`, and `generate hourly`
|
||||||
generated-text-template workflow. They run structured `scriptorium run` to raw
|
use the generated-text-template workflow. They run structured `scriptorium run`
|
||||||
GeneratedText JSON, validate the structured text, save a render context, and
|
to raw GeneratedText JSON, validate the structured text, save a render context,
|
||||||
render the managed Markdown report from embedded templates. `generate today`
|
and render the managed Markdown report from embedded templates. `generate
|
||||||
covers the selected or current local civil day. `generate hourly` covers the
|
daily` requires `--date YYYY-MM-DD` for the selected local civil day.
|
||||||
six-hour rolling period from generation time in the effective report timezone
|
`generate today` covers the selected or current local civil day. `generate
|
||||||
and is not part of scheduled morning or evening batches.
|
hourly` covers the six-hour rolling period from generation time in the
|
||||||
|
effective report timezone and is not part of scheduled morning or evening
|
||||||
`generate daily` remains the existing Daily Today report and is independent of
|
batches.
|
||||||
`generate today`.
|
|
||||||
|
|
||||||
When distributor notification is enabled, weatherreporter uploads the managed
|
When distributor notification is enabled, weatherreporter uploads the managed
|
||||||
Markdown report after report rendering succeeds and final metadata is saved.
|
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
|
Recent Changes are computed from structured module snapshots, not rendered
|
||||||
Markdown or YAML text.
|
Markdown or YAML text.
|
||||||
|
|
||||||
Daily Today compares with prior Daily Today snapshots for the same valid local
|
Daily Report compares with prior Daily Report snapshots for the same valid
|
||||||
date. Today Report compares with prior Today Report snapshots for the same
|
local date. Today Report compares with prior Today Report snapshots for the
|
||||||
valid local date. Tomorrow Report compares with prior Tomorrow Report snapshots
|
same valid local date. Tomorrow Report compares with prior Tomorrow Report
|
||||||
for the same valid local date. 3-Day Outlook compares with prior compatible
|
snapshots for the same valid local date. 3-Day Outlook compares with prior
|
||||||
3-Day snapshots for the same valid local date. Weekend Outlook compares with
|
compatible 3-Day snapshots for the same valid local date. Weekend Outlook
|
||||||
prior compatible Weekend snapshots for the same weekend window. Hourly Report
|
compares with prior compatible Weekend snapshots for the same weekend window.
|
||||||
and Storm Report leave Recent Changes empty.
|
Hourly Report and Storm Report leave Recent Changes empty.
|
||||||
|
|
||||||
When no prior comparable snapshot exists, or no configured threshold is crossed,
|
When no prior comparable snapshot exists, or no configured threshold is crossed,
|
||||||
`recentChanges.items` is empty.
|
`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) {
|
func TestResolveGenerateUsesConfiguredReportModules(t *testing.T) {
|
||||||
path := filepath.Join(t.TempDir(), "config.yml")
|
path := filepath.Join(t.TempDir(), "config.yml")
|
||||||
if err := os.WriteFile(path, []byte(`
|
if err := os.WriteFile(path, []byte(`
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const helpText = `weatherreporter prepares weather reports from normalized forec
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
weatherreporter --help
|
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 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 tomorrow [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
|
||||||
weatherreporter generate hourly [--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 {
|
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 == "" {
|
if opts.Date == "" {
|
||||||
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestRunHelpLongFlag(t *testing.T) {
|
|||||||
t.Fatalf("Run() error = %v", err)
|
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)
|
t.Fatalf("help output missing generate command:\n%s", output.stdout)
|
||||||
}
|
}
|
||||||
if !strings.Contains(output.stdout, "generate today") {
|
if !strings.Contains(output.stdout, "generate today") {
|
||||||
@@ -571,6 +571,14 @@ func TestRunGenerateDailyWritesMarkdownReport(t *testing.T) {
|
|||||||
if len(reportMatches) != 1 {
|
if len(reportMatches) != 1 {
|
||||||
t.Fatalf("managed reports = %#v, want one", reportMatches)
|
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) {
|
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()}
|
runner := Runner{Clock: fixedClock()}
|
||||||
|
|
||||||
req, err := runner.resolveGenerate([]string{"daily"})
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("resolveGenerate() error = %v", err)
|
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" {
|
if got := req.Date.Format(timeutil.DateLayout); got != "2026-05-29" {
|
||||||
t.Fatalf("Date = %s, want 2026-05-29", got)
|
t.Fatalf("Date = %s, want 2026-05-29", got)
|
||||||
}
|
}
|
||||||
@@ -1024,7 +1062,7 @@ func TestResolveGenerateTodayDate(t *testing.T) {
|
|||||||
func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
||||||
runner := Runner{Clock: fixedClock()}
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("resolveGenerate() error = %v", err)
|
t.Fatalf("resolveGenerate() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user