Add hourly generated-text report shell
This commit is contained in:
21
docs/cli.md
21
docs/cli.md
@@ -35,14 +35,14 @@ weatherreporter inspect prior [--config PATH] RUN_ID
|
||||
weatherreporter inspect sources [--config PATH] RUN_ID
|
||||
```
|
||||
|
||||
`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
|
||||
hourly` uses the current generation time, 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`.
|
||||
Implemented Markdown `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 hourly` is parsed and resolved, covers the next six hours in
|
||||
the effective report timezone, and does not accept date or event window flags;
|
||||
its rendering workflow is not implemented yet. `generate storm` requires
|
||||
explicit event-window bounds with `--start` and `--end`.
|
||||
|
||||
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
|
||||
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch
|
||||
@@ -54,8 +54,8 @@ notification is enabled, batch summaries and status lines include notification
|
||||
status, accepted distributor run ID, or notification error fields for each
|
||||
attempted report.
|
||||
|
||||
Hourly Report generation is explicit only; it is not included in `run
|
||||
morning` or `run evening`.
|
||||
Hourly Report is explicit only; it is not included in `run morning` or `run
|
||||
evening`.
|
||||
|
||||
`inspect` commands read existing workspace artifacts and emit JSON to stdout.
|
||||
They do not fetch weather data or invoke `scriptorium`.
|
||||
@@ -83,7 +83,6 @@ are no distributor-specific CLI flags.
|
||||
|
||||
```sh
|
||||
weatherreporter generate tomorrow --out ./tomorrow.md
|
||||
weatherreporter generate hourly --out ./hourly.md
|
||||
weatherreporter generate three-day --out ./three-day.md
|
||||
weatherreporter generate weekend --out ./weekend.md
|
||||
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md
|
||||
|
||||
@@ -68,11 +68,12 @@ Prompt-facing module intervals use local `period_begins` and `period_ends`
|
||||
labels; canonical report metadata and source timestamps remain structured
|
||||
timestamps where applicable.
|
||||
|
||||
Hourly Report uses the same package schema and categories. Its default
|
||||
package includes `precip_timing` under `derived_summaries`, alert and SPC
|
||||
outlooks under `applicable_risk_products`, AFD/SPC discussion/weather story
|
||||
under `narrative_products`, and current/hourly data under `raw_data`. It does
|
||||
not include daily or daypart summary stanzas.
|
||||
Hourly Report module snapshots use the same package schema and categories when
|
||||
converted into prompt input. The default hourly module list places
|
||||
`precip_timing` under `derived_summaries`, alert and SPC outlooks under
|
||||
`applicable_risk_products`, AFD/SPC discussion/weather story under
|
||||
`narrative_products`, and current/hourly data under `raw_data`. It does not
|
||||
include daily or daypart summary stanzas.
|
||||
|
||||
Current categories are:
|
||||
|
||||
@@ -112,7 +113,6 @@ None.
|
||||
## Skip And Resume Behavior
|
||||
|
||||
None. Recent Changes is always present as an `items` list and may be empty.
|
||||
Hourly Report currently writes an empty `items` list.
|
||||
|
||||
## Failure Behavior
|
||||
|
||||
|
||||
@@ -26,20 +26,21 @@ Each report definition declares:
|
||||
- morning or evening batch membership
|
||||
- default ordered module composition
|
||||
|
||||
Current report definitions use the `scriptorium_markdown` generation mode.
|
||||
Template and structured-text schema identifiers are empty for these Markdown
|
||||
reports.
|
||||
Current Markdown report definitions use the `scriptorium_markdown` generation
|
||||
mode. Their template and structured-text schema identifiers are empty. Hourly
|
||||
Report declares `generated_text_template` with template ID `hourly` and schema
|
||||
ID `hourly`; the app rendering workflow for that mode is not implemented yet.
|
||||
|
||||
## Reports
|
||||
|
||||
| Report | ID | Prompt | Artifact group | Batch copy | Prior compatibility |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| Daily Today | `daily_today` | `weather.daily_report` | `daily` | `daily.md` | Daily Today, Daily Tomorrow |
|
||||
| Daily Tomorrow | `daily_tomorrow` | `weather.daily_report` | `daily` | `tomorrow.md` | Daily Today, Daily Tomorrow |
|
||||
| Hourly Report | `hourly` | `weather.hourly_report` | `hourly` | `hourly.md` | Hourly Report |
|
||||
| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `three-day` | `three-day.md` | 3-Day Outlook |
|
||||
| Weekend Outlook | `weekend` | `weather.weekend_outlook` | `weekend` | `weekend.md` | Weekend Outlook |
|
||||
| Storm Report | `storm` | `weather.storm_report` | `storm` | `storm.md` | Storm Report |
|
||||
| Report | ID | Prompt | Generation mode | Artifact group | Batch copy | Prior compatibility |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| Daily Today | `daily_today` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `daily.md` | Daily Today, Daily Tomorrow |
|
||||
| Daily Tomorrow | `daily_tomorrow` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `tomorrow.md` | Daily Today, Daily Tomorrow |
|
||||
| Hourly Report | `hourly` | `weather.hourly_generated_text` | `generated_text_template` | `hourly` | `hourly.md` | Hourly Report |
|
||||
| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `scriptorium_markdown` | `three-day` | `three-day.md` | 3-Day Outlook |
|
||||
| Weekend Outlook | `weekend` | `weather.weekend_outlook` | `scriptorium_markdown` | `weekend` | `weekend.md` | Weekend Outlook |
|
||||
| Storm Report | `storm` | `weather.storm_report` | `scriptorium_markdown` | `storm` | `storm.md` | Storm Report |
|
||||
|
||||
All report definitions are eligible for generation.
|
||||
|
||||
|
||||
@@ -11,24 +11,24 @@ Generation commands:
|
||||
```text
|
||||
weatherreporter generate daily --date 2026-05-29
|
||||
weatherreporter generate tomorrow
|
||||
weatherreporter generate hourly
|
||||
weatherreporter generate three-day
|
||||
weatherreporter generate weekend
|
||||
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00
|
||||
```
|
||||
|
||||
Each command resolves a report period, fetches a Weather API bundle, builds a
|
||||
JSON module snapshot, builds a YAML prompt input data package, runs
|
||||
`scriptorium render`, runs `scriptorium run`, and writes managed artifacts under
|
||||
the configured workspace.
|
||||
Each implemented Markdown generation command resolves a report period, fetches
|
||||
a Weather API bundle, builds a JSON module snapshot, builds a YAML prompt input
|
||||
data package, runs `scriptorium render`, runs `scriptorium run`, and writes
|
||||
managed artifacts under the configured workspace.
|
||||
When distributor notification is enabled, weatherreporter uploads the managed
|
||||
Markdown report after `scriptorium run` succeeds and final metadata is saved.
|
||||
`--out PATH` writes an extra Markdown copy for the current generated report; it
|
||||
is not used as the distributor upload source.
|
||||
|
||||
`generate hourly` is an explicit generation command. It 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 hourly` is an explicit command shell. It covers the six-hour rolling
|
||||
period from generation time in the effective report timezone and is not part of
|
||||
scheduled morning or evening batches, but its rendering workflow is not
|
||||
implemented yet.
|
||||
|
||||
Batch commands:
|
||||
|
||||
|
||||
@@ -438,6 +438,9 @@ func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*weatherda
|
||||
}
|
||||
|
||||
func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, error) {
|
||||
if req.Resolved.Definition.GenerationMode != report.GenerationModeScriptoriumMarkdown {
|
||||
return nil, fmt.Errorf("generation mode %q is not implemented for report %q", req.Resolved.Definition.GenerationMode, req.Resolved.Definition.ID)
|
||||
}
|
||||
store := req.Store
|
||||
if store == nil {
|
||||
defaultStore, err := defaultStore(req.Config)
|
||||
|
||||
@@ -349,9 +349,8 @@ func TestGenerateReportOmitsSPCConvectiveDiscussionBelowThreshold(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateHourlyReportWritesExpectedArtifacts(t *testing.T) {
|
||||
server := hourlyBundleServer(t)
|
||||
cfg := dailyTestConfig(t, server)
|
||||
func TestGenerateHourlyReportModeNotImplemented(t *testing.T) {
|
||||
cfg := config.Defaults()
|
||||
cfg.Workspace.Root = t.TempDir()
|
||||
resolved, err := ResolveGenerate(GenerateRequest{
|
||||
Config: cfg,
|
||||
@@ -360,174 +359,18 @@ func TestGenerateHourlyReportWritesExpectedArtifacts(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("ResolveGenerate() error = %v", err)
|
||||
}
|
||||
outputPath := filepath.Join(t.TempDir(), "hourly.md")
|
||||
renderer := successfulRenderer("# Hourly Report\n")
|
||||
if resolved.Definition.GenerationMode != report.GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("GenerationMode = %q, want generated text template", resolved.Definition.GenerationMode)
|
||||
}
|
||||
|
||||
result, err := GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
Resolved: resolved,
|
||||
OutputPath: outputPath,
|
||||
Renderer: renderer,
|
||||
_, err = GenerateReport(context.Background(), ReportRequest{
|
||||
Config: cfg,
|
||||
Resolved: resolved,
|
||||
Renderer: successfulRenderer("# Hourly Report\n"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateReport() error = %v", err)
|
||||
if err == nil || !strings.Contains(err.Error(), `generation mode "generated_text_template" is not implemented for report "hourly"`) {
|
||||
t.Fatalf("GenerateReport() error = %v, want generation mode not implemented", err)
|
||||
}
|
||||
|
||||
if result.Metadata.ReportID != report.Hourly || result.Metadata.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("metadata report/prompt = %q/%q, want hourly", result.Metadata.ReportID, result.Metadata.PromptID)
|
||||
}
|
||||
if got := result.Metadata.ValidPeriod.Start.Format(time.RFC3339); got != "2026-05-29T08:30:00-05:00" {
|
||||
t.Fatalf("valid period start = %s, want rolling window start", got)
|
||||
}
|
||||
if got := result.Metadata.ValidPeriod.End.Format(time.RFC3339); got != "2026-05-29T14:30:00-05:00" {
|
||||
t.Fatalf("valid period end = %s, want six-hour rolling window end", got)
|
||||
}
|
||||
if renderer.renderRequest.PromptID != "weather.hourly_report" || renderer.runRequest.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("renderer prompt IDs = %q/%q, want hourly prompt", renderer.renderRequest.PromptID, renderer.runRequest.PromptID)
|
||||
}
|
||||
if !strings.Contains(result.ReportPath, filepath.Join("reports", "hourly")) {
|
||||
t.Fatalf("ReportPath = %q, want hourly artifact group", result.ReportPath)
|
||||
}
|
||||
if !strings.Contains(result.DataPackagePath, filepath.Join("data-packages", "hourly", "2026-05-29")) {
|
||||
t.Fatalf("DataPackagePath = %q, want hourly artifact group", result.DataPackagePath)
|
||||
}
|
||||
assertPathsExist(t, result.ModuleSnapshotPath, result.DataPackagePath, result.PreflightPath, result.ReportPath, result.MetadataPath, outputPath)
|
||||
if result.OutputPath != outputPath {
|
||||
t.Fatalf("OutputPath = %q, want requested output copy %q", result.OutputPath, outputPath)
|
||||
}
|
||||
copiedReport, err := os.ReadFile(outputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read output copy: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(copiedReport), "# Hourly Report") {
|
||||
t.Fatalf("output copy missing rendered report:\n%s", string(copiedReport))
|
||||
}
|
||||
|
||||
wantModules := []module.ID{
|
||||
module.Metadata,
|
||||
module.CurrentConditions,
|
||||
module.HourlyForecast,
|
||||
module.PrecipTiming,
|
||||
module.AlertDigest,
|
||||
module.SPCConvectiveOutlooks,
|
||||
module.AreaForecastDiscussion,
|
||||
module.SPCConvectiveDiscussion,
|
||||
module.WeatherStory,
|
||||
}
|
||||
if got := snapshotModuleIDs(result.ModuleSnapshot); strings.Join(moduleIDsForTest(got), ",") != strings.Join(moduleIDsForTest(wantModules), ",") {
|
||||
t.Fatalf("module snapshot IDs = %#v, want %#v", got, wantModules)
|
||||
}
|
||||
|
||||
hourly, ok, err := module.StanzaValue[briefing.HourlyForecastModule](result.ModuleSnapshot, "hourly_forecast")
|
||||
if err != nil {
|
||||
t.Fatalf("decode hourly forecast: %v", err)
|
||||
}
|
||||
if !ok || len(hourly.Periods) != 5 {
|
||||
t.Fatalf("hourly forecast = %#v, want five overlapping hourly periods", hourly)
|
||||
}
|
||||
hourlyJSON := mustMarshalString(t, hourly)
|
||||
if !strings.Contains(hourlyJSON, "Showers entering the area") || !strings.Contains(hourlyJSON, "Heavy rain") {
|
||||
t.Fatalf("hourly forecast missing selected hourly periods:\n%s", hourlyJSON)
|
||||
}
|
||||
if strings.Contains(hourlyJSON, "Before-window storms") || strings.Contains(hourlyJSON, "After-window rain") {
|
||||
t.Fatalf("hourly forecast contains periods outside valid window:\n%s", hourlyJSON)
|
||||
}
|
||||
|
||||
precip, ok, err := module.StanzaValue[briefing.PrecipTimingModule](result.ModuleSnapshot, "precip_timing")
|
||||
if err != nil {
|
||||
t.Fatalf("decode precip timing: %v", err)
|
||||
}
|
||||
if !ok || precip.MaxPopPercent == nil || *precip.MaxPopPercent != 80 || len(precip.PrecipitationWindows) != 2 {
|
||||
t.Fatalf("precip timing = %#v, want hourly precipitation windows", precip)
|
||||
}
|
||||
if precip.PrecipitationWindows[0].PeriodBegins != "2026-05-29 at 8:00 AM" || precip.PrecipitationWindows[0].PeriodEnds != "2026-05-29 at 9:00 AM" {
|
||||
t.Fatalf("first precip window = %#v, want first selected hour only", precip.PrecipitationWindows[0])
|
||||
}
|
||||
if precip.PrecipitationWindows[1].PeriodBegins != "2026-05-29 at 10:00 AM" || precip.PrecipitationWindows[1].PeriodEnds != "2026-05-29 at 12:00 PM" {
|
||||
t.Fatalf("second precip window = %#v, want late-morning hourly rain", precip.PrecipitationWindows[1])
|
||||
}
|
||||
|
||||
alerts, ok, err := module.StanzaValue[briefing.AlertDigestModule](result.ModuleSnapshot, "alert_digest")
|
||||
if err != nil {
|
||||
t.Fatalf("decode alert digest: %v", err)
|
||||
}
|
||||
if !ok || !alerts.Checked || alerts.ActiveCount != 3 || alerts.RelevantCount != 1 || len(alerts.Relevant) != 1 || alerts.Relevant[0].Event != "Flood Watch" {
|
||||
t.Fatalf("alert digest = %#v, want only hourly alert overlap relevant", alerts)
|
||||
}
|
||||
|
||||
outlooks, ok, err := module.StanzaValue[briefing.SPCConvectiveOutlooksModule](result.ModuleSnapshot, "spc_convective_outlooks")
|
||||
if err != nil {
|
||||
t.Fatalf("decode SPC outlooks: %v", err)
|
||||
}
|
||||
if !ok || !outlooks.Checked || outlooks.OutlookCount != 1 || len(outlooks.Outlooks) != 1 || outlooks.Outlooks[0].Label != "SLGT" {
|
||||
t.Fatalf("SPC outlooks = %#v, want one overlapping hourly outlook", outlooks)
|
||||
}
|
||||
discussion, ok, err := module.StanzaValue[briefing.SPCConvectiveDiscussionModule](result.ModuleSnapshot, "spc_convective_discussion")
|
||||
if err != nil {
|
||||
t.Fatalf("decode SPC discussion: %v", err)
|
||||
}
|
||||
if !ok || len(discussion.Discussions) != 1 || discussion.Discussions[0].Headline != "hourly severe storms" {
|
||||
t.Fatalf("SPC discussion = %#v, want discussion for retained overlapping outlook", discussion)
|
||||
}
|
||||
|
||||
afd, ok, err := module.StanzaValue[briefing.AreaForecastDiscussionModule](result.ModuleSnapshot, "area_forecast_discussion")
|
||||
if err != nil {
|
||||
t.Fatalf("decode AFD: %v", err)
|
||||
}
|
||||
if !ok || len(afd.KeyMessages) != 1 || afd.ShortTerm != "Short-term AFD narrative for hourly report." {
|
||||
t.Fatalf("AFD = %#v, want key messages and short term", afd)
|
||||
}
|
||||
if afd.Product != "" || afd.LongTerm != "" {
|
||||
t.Fatalf("AFD = %#v, want hourly defaults to omit product and long term", afd)
|
||||
}
|
||||
|
||||
if result.PriorSnapshot != nil || len(result.RecentChanges) != 0 || len(result.DataPackage.RecentChanges.Items) != 0 {
|
||||
t.Fatalf("prior=%#v recent=%#v package=%#v, want no rolling-window comparison output", result.PriorSnapshot, result.RecentChanges, result.DataPackage.RecentChanges.Items)
|
||||
}
|
||||
data := readDataPackageForTest(t, result)
|
||||
text := string(data)
|
||||
for _, want := range []string{
|
||||
"id: hourly",
|
||||
"prompt_id: weather.hourly_report",
|
||||
"valid_period:",
|
||||
"recent_changes:",
|
||||
" items: []",
|
||||
" alert_digest:",
|
||||
" spc_convective_outlooks:",
|
||||
" precip_timing:",
|
||||
" area_forecast_discussion:",
|
||||
" spc_convective_discussion:",
|
||||
" weather_story:",
|
||||
" current_conditions:",
|
||||
" hourly_forecast:",
|
||||
} {
|
||||
if !strings.Contains(text, want) {
|
||||
t.Fatalf("data package missing %q:\n%s", want, text)
|
||||
}
|
||||
}
|
||||
for _, omitted := range []string{"validPeriod:", "Before-window storms", "After-window rain", "Evening Advisory", "Day 2 outlook", "Long-term AFD narrative for hourly report."} {
|
||||
if strings.Contains(text, omitted) {
|
||||
t.Fatalf("data package contains %q, want hourly filtered/canonical output:\n%s", omitted, text)
|
||||
}
|
||||
}
|
||||
riskIndex := strings.Index(text, " applicable_risk_products:")
|
||||
derivedIndex := strings.Index(text, " derived_summaries:")
|
||||
narrativeIndex := strings.Index(text, " narrative_products:")
|
||||
rawIndex := strings.Index(text, " raw_data:")
|
||||
alertIndex := strings.Index(text, " alert_digest:")
|
||||
precipIndex := strings.Index(text, " precip_timing:")
|
||||
afdIndex := strings.Index(text, " area_forecast_discussion:")
|
||||
currentIndex := strings.Index(text, " current_conditions:")
|
||||
hourlyIndex := strings.Index(text, " hourly_forecast:")
|
||||
if riskIndex < 0 || derivedIndex < 0 || narrativeIndex < 0 || rawIndex < 0 || alertIndex < 0 || precipIndex < 0 || afdIndex < 0 || currentIndex < 0 || hourlyIndex < 0 ||
|
||||
!(riskIndex < alertIndex && alertIndex < derivedIndex && derivedIndex < precipIndex && precipIndex < narrativeIndex && narrativeIndex < afdIndex && afdIndex < rawIndex && rawIndex < currentIndex && currentIndex < hourlyIndex) {
|
||||
t.Fatalf("data package category order is wrong:\n%s", text)
|
||||
}
|
||||
loaded, err := promptinput.LoadYAML(data)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadYAML() error = %v", err)
|
||||
}
|
||||
assertNoStaleModuleIntervalKeys(t, loaded.Briefing.Values)
|
||||
}
|
||||
|
||||
func TestGenerateReportDisabledNotificationDoesNotCallNotifier(t *testing.T) {
|
||||
@@ -1367,7 +1210,7 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
|
||||
name: "hourly",
|
||||
kind: ReportHourly,
|
||||
wantID: report.Hourly,
|
||||
wantPrompt: "weather.hourly_report",
|
||||
wantPrompt: "weather.hourly_generated_text",
|
||||
wantStart: "2026-05-29T08:00:00-05:00",
|
||||
wantEnd: "2026-05-29T14:00:00-05:00",
|
||||
},
|
||||
|
||||
@@ -910,6 +910,19 @@ func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateRejectsRetiredHourlyCommand(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
retired := strings.Join([]string{"near", "term"}, "-")
|
||||
|
||||
_, err := runner.resolveGenerate([]string{retired})
|
||||
if err == nil {
|
||||
t.Fatal("resolveGenerate() error = nil, want unknown report")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "unknown generate report") {
|
||||
t.Fatalf("error = %q, want unknown generate report", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGenerateStormRequiresStartAndEnd(t *testing.T) {
|
||||
runner := Runner{Clock: fixedClock()}
|
||||
|
||||
|
||||
@@ -307,6 +307,28 @@ reports:
|
||||
}
|
||||
}
|
||||
|
||||
func TestReportModuleOverrideRejectsRetiredHourlyKeys(t *testing.T) {
|
||||
for _, key := range []string{
|
||||
strings.Join([]string{"near", "term"}, "_"),
|
||||
strings.Join([]string{"near", "term"}, "-"),
|
||||
} {
|
||||
t.Run(key, func(t *testing.T) {
|
||||
_, err := LoadFile(writeConfig(t, `
|
||||
reports:
|
||||
`+key+`:
|
||||
deterministic_modules:
|
||||
- metadata
|
||||
`))
|
||||
if err == nil {
|
||||
t.Fatal("LoadFile() error = nil, want unknown report")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "is not a known report") {
|
||||
t.Fatalf("error = %q, want unknown report", err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitMissingConfigReturnsError(t *testing.T) {
|
||||
_, err := LoadFile(filepath.Join(t.TempDir(), "missing.yml"))
|
||||
if err == nil {
|
||||
|
||||
@@ -7,21 +7,23 @@ import (
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||
)
|
||||
|
||||
const hourlyHours = 6
|
||||
const hourlyReportHours = 6
|
||||
|
||||
func hourlyDefinition() Definition {
|
||||
return Definition{
|
||||
ID: Hourly,
|
||||
Name: "Hourly Report",
|
||||
PromptID: "weather.hourly_report",
|
||||
GenerationMode: GenerationModeScriptoriumMarkdown,
|
||||
ComparisonStrategy: CompareRollingWindow,
|
||||
ArtifactGroup: "hourly",
|
||||
BatchOutputName: "hourly.md",
|
||||
Generated: true,
|
||||
CompatiblePriorIDs: []ID{Hourly},
|
||||
Modules: hourlyModules(),
|
||||
resolve: resolveHourly,
|
||||
ID: Hourly,
|
||||
Name: "Hourly Report",
|
||||
PromptID: "weather.hourly_generated_text",
|
||||
GenerationMode: GenerationModeGeneratedTextTemplate,
|
||||
TemplateID: "hourly",
|
||||
GeneratedTextSchemaID: "hourly",
|
||||
ComparisonStrategy: CompareRollingWindow,
|
||||
ArtifactGroup: "hourly",
|
||||
BatchOutputName: "hourly.md",
|
||||
Generated: true,
|
||||
CompatiblePriorIDs: []ID{Hourly},
|
||||
Modules: hourlyModules(),
|
||||
resolve: resolveHourly,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +50,6 @@ func resolveHourly(req ResolveRequest) (timeutil.Period, error) {
|
||||
localNow := req.Now.In(req.Location)
|
||||
return timeutil.Period{
|
||||
Start: localNow,
|
||||
End: localNow.Add(hourlyHours * time.Hour),
|
||||
End: localNow.Add(hourlyReportHours * time.Hour),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -69,8 +69,17 @@ func TestHourlyLookupAndPeriodCalculation(t *testing.T) {
|
||||
if resolved.Definition.ID != Hourly {
|
||||
t.Fatalf("ID = %q, want hourly", resolved.Definition.ID)
|
||||
}
|
||||
if resolved.Definition.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.hourly_report", resolved.Definition.PromptID)
|
||||
if resolved.Definition.PromptID != "weather.hourly_generated_text" {
|
||||
t.Fatalf("PromptID = %q, want weather.hourly_generated_text", resolved.Definition.PromptID)
|
||||
}
|
||||
if resolved.Definition.GenerationMode != GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("GenerationMode = %q, want generated_text_template", resolved.Definition.GenerationMode)
|
||||
}
|
||||
if resolved.Definition.TemplateID != "hourly" {
|
||||
t.Fatalf("TemplateID = %q, want hourly", resolved.Definition.TemplateID)
|
||||
}
|
||||
if resolved.Definition.GeneratedTextSchemaID != "hourly" {
|
||||
t.Fatalf("GeneratedTextSchemaID = %q, want hourly", resolved.Definition.GeneratedTextSchemaID)
|
||||
}
|
||||
if resolved.Definition.ComparisonStrategy != CompareRollingWindow {
|
||||
t.Fatalf("ComparisonStrategy = %q, want rolling_window", resolved.Definition.ComparisonStrategy)
|
||||
@@ -259,6 +268,18 @@ func TestRegistryDefinitionsDeclareGenerationMetadata(t *testing.T) {
|
||||
if !definition.Generated {
|
||||
continue
|
||||
}
|
||||
if definition.ID == Hourly {
|
||||
if definition.GenerationMode != GenerationModeGeneratedTextTemplate {
|
||||
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, definition.GenerationMode, GenerationModeGeneratedTextTemplate)
|
||||
}
|
||||
if definition.TemplateID != "hourly" {
|
||||
t.Fatalf("%s TemplateID = %q, want hourly", definition.ID, definition.TemplateID)
|
||||
}
|
||||
if definition.GeneratedTextSchemaID != "hourly" {
|
||||
t.Fatalf("%s GeneratedTextSchemaID = %q, want hourly", definition.ID, definition.GeneratedTextSchemaID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if definition.GenerationMode != GenerationModeScriptoriumMarkdown {
|
||||
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, definition.GenerationMode, GenerationModeScriptoriumMarkdown)
|
||||
}
|
||||
@@ -545,8 +566,8 @@ func TestHourlyMetadataRunIDIncludesReportID(t *testing.T) {
|
||||
if metadata.ReportID != Hourly {
|
||||
t.Fatalf("ReportID = %q, want hourly", metadata.ReportID)
|
||||
}
|
||||
if metadata.PromptID != "weather.hourly_report" {
|
||||
t.Fatalf("PromptID = %q, want weather.hourly_report", metadata.PromptID)
|
||||
if metadata.PromptID != "weather.hourly_generated_text" {
|
||||
t.Fatalf("PromptID = %q, want weather.hourly_generated_text", metadata.PromptID)
|
||||
}
|
||||
if !strings.Contains(metadata.RunID, "hourly") {
|
||||
t.Fatalf("RunID = %q, want report id", metadata.RunID)
|
||||
|
||||
Reference in New Issue
Block a user