Add hourly generated-text report shell

This commit is contained in:
2026-06-14 04:35:52 +00:00
parent dd92e9b061
commit b39ec1e4d3
10 changed files with 126 additions and 222 deletions

View File

@@ -35,14 +35,14 @@ weatherreporter inspect prior [--config PATH] RUN_ID
weatherreporter inspect sources [--config PATH] RUN_ID weatherreporter inspect sources [--config PATH] RUN_ID
``` ```
`generate` commands write a JSON module snapshot, YAML data package, preflight Implemented Markdown `generate` commands write a JSON module snapshot, YAML
artifact, managed Markdown report, and metadata under the configured workspace. data package, preflight artifact, managed Markdown report, and metadata under
`--out` writes an extra Markdown copy for the operator; distributor the configured workspace. `--out` writes an extra Markdown copy for the
notification uses the managed report path, not the extra copy. `generate operator; distributor notification uses the managed report path, not the extra
hourly` uses the current generation time, covers the next six hours in the copy. `generate hourly` is parsed and resolved, covers the next six hours in
effective report timezone, and does not accept date or event window flags. the effective report timezone, and does not accept date or event window flags;
`generate storm` requires explicit event-window bounds with `--start` and its rendering workflow is not implemented yet. `generate storm` requires
`--end`. explicit event-window bounds with `--start` and `--end`.
`run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook `run morning` generates Daily Today and the 3-Day Outlook, plus Weekend Outlook
except on Sunday. `run evening` generates the Tomorrow Planning Brief. Batch 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 status, accepted distributor run ID, or notification error fields for each
attempted report. attempted report.
Hourly Report generation is explicit only; it is not included in `run Hourly Report is explicit only; it is not included in `run morning` or `run
morning` or `run evening`. evening`.
`inspect` commands read existing workspace artifacts and emit JSON to stdout. `inspect` commands read existing workspace artifacts and emit JSON to stdout.
They do not fetch weather data or invoke `scriptorium`. They do not fetch weather data or invoke `scriptorium`.
@@ -83,7 +83,6 @@ are no distributor-specific CLI flags.
```sh ```sh
weatherreporter generate tomorrow --out ./tomorrow.md weatherreporter generate tomorrow --out ./tomorrow.md
weatherreporter generate hourly --out ./hourly.md
weatherreporter generate three-day --out ./three-day.md weatherreporter generate three-day --out ./three-day.md
weatherreporter generate weekend --out ./weekend.md weatherreporter generate weekend --out ./weekend.md
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 --out ./storm.md

View File

@@ -68,11 +68,12 @@ Prompt-facing module intervals use local `period_begins` and `period_ends`
labels; canonical report metadata and source timestamps remain structured labels; canonical report metadata and source timestamps remain structured
timestamps where applicable. timestamps where applicable.
Hourly Report uses the same package schema and categories. Its default Hourly Report module snapshots use the same package schema and categories when
package includes `precip_timing` under `derived_summaries`, alert and SPC converted into prompt input. The default hourly module list places
outlooks under `applicable_risk_products`, AFD/SPC discussion/weather story `precip_timing` under `derived_summaries`, alert and SPC outlooks under
under `narrative_products`, and current/hourly data under `raw_data`. It does `applicable_risk_products`, AFD/SPC discussion/weather story under
not include daily or daypart summary stanzas. `narrative_products`, and current/hourly data under `raw_data`. It does not
include daily or daypart summary stanzas.
Current categories are: Current categories are:
@@ -112,7 +113,6 @@ None.
## Skip And Resume Behavior ## Skip And Resume Behavior
None. Recent Changes is always present as an `items` list and may be empty. None. Recent Changes is always present as an `items` list and may be empty.
Hourly Report currently writes an empty `items` list.
## Failure Behavior ## Failure Behavior

View File

@@ -26,20 +26,21 @@ Each report definition declares:
- morning or evening batch membership - morning or evening batch membership
- default ordered module composition - default ordered module composition
Current report definitions use the `scriptorium_markdown` generation mode. Current Markdown report definitions use the `scriptorium_markdown` generation
Template and structured-text schema identifiers are empty for these Markdown mode. Their template and structured-text schema identifiers are empty. Hourly
reports. 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 ## Reports
| Report | ID | Prompt | Artifact group | Batch copy | Prior compatibility | | Report | ID | Prompt | Generation mode | Artifact group | Batch copy | Prior compatibility |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| Daily Today | `daily_today` | `weather.daily_report` | `daily` | `daily.md` | Daily Today, Daily Tomorrow | | Daily Today | `daily_today` | `weather.daily_report` | `scriptorium_markdown` | `daily` | `daily.md` | Daily Today, Daily Tomorrow |
| Daily Tomorrow | `daily_tomorrow` | `weather.daily_report` | `daily` | `tomorrow.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_report` | `hourly` | `hourly.md` | Hourly Report | | Hourly Report | `hourly` | `weather.hourly_generated_text` | `generated_text_template` | `hourly` | `hourly.md` | Hourly Report |
| 3-Day Outlook | `three_day` | `weather.three_day_outlook` | `three-day` | `three-day.md` | 3-Day Outlook | | 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` | `weekend` | `weekend.md` | Weekend Outlook | | Weekend Outlook | `weekend` | `weather.weekend_outlook` | `scriptorium_markdown` | `weekend` | `weekend.md` | Weekend Outlook |
| Storm Report | `storm` | `weather.storm_report` | `storm` | `storm.md` | Storm Report | | Storm Report | `storm` | `weather.storm_report` | `scriptorium_markdown` | `storm` | `storm.md` | Storm Report |
All report definitions are eligible for generation. All report definitions are eligible for generation.

View File

@@ -11,24 +11,24 @@ Generation commands:
```text ```text
weatherreporter generate daily --date 2026-05-29 weatherreporter generate daily --date 2026-05-29
weatherreporter generate tomorrow weatherreporter generate tomorrow
weatherreporter generate hourly
weatherreporter generate three-day weatherreporter generate three-day
weatherreporter generate weekend weatherreporter generate weekend
weatherreporter generate storm --start 2026-05-29T18:00 --end 2026-05-30T06:00 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 Each implemented Markdown generation command resolves a report period, fetches
JSON module snapshot, builds a YAML prompt input data package, runs a Weather API bundle, builds a JSON module snapshot, builds a YAML prompt input
`scriptorium render`, runs `scriptorium run`, and writes managed artifacts under data package, runs `scriptorium render`, runs `scriptorium run`, and writes
the configured workspace. managed artifacts under the configured workspace.
When distributor notification is enabled, weatherreporter uploads the managed When distributor notification is enabled, weatherreporter uploads the managed
Markdown report after `scriptorium run` succeeds and final metadata is saved. Markdown report after `scriptorium run` succeeds and final metadata is saved.
`--out PATH` writes an extra Markdown copy for the current generated report; it `--out PATH` writes an extra Markdown copy for the current generated report; it
is not used as the distributor upload source. is not used as the distributor upload source.
`generate hourly` is an explicit generation command. It covers the six-hour `generate hourly` is an explicit command shell. It covers the six-hour rolling
rolling period from generation time in the effective report timezone and is not period from generation time in the effective report timezone and is not part of
part of scheduled morning or evening batches. scheduled morning or evening batches, but its rendering workflow is not
implemented yet.
Batch commands: Batch commands:

View File

@@ -438,6 +438,9 @@ func FetchAndSaveBundle(ctx context.Context, req FetchBundleRequest) (*weatherda
} }
func GenerateReport(ctx context.Context, req ReportRequest) (*ReportResult, error) { 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 store := req.Store
if store == nil { if store == nil {
defaultStore, err := defaultStore(req.Config) defaultStore, err := defaultStore(req.Config)

View File

@@ -349,9 +349,8 @@ func TestGenerateReportOmitsSPCConvectiveDiscussionBelowThreshold(t *testing.T)
} }
} }
func TestGenerateHourlyReportWritesExpectedArtifacts(t *testing.T) { func TestGenerateHourlyReportModeNotImplemented(t *testing.T) {
server := hourlyBundleServer(t) cfg := config.Defaults()
cfg := dailyTestConfig(t, server)
cfg.Workspace.Root = t.TempDir() cfg.Workspace.Root = t.TempDir()
resolved, err := ResolveGenerate(GenerateRequest{ resolved, err := ResolveGenerate(GenerateRequest{
Config: cfg, Config: cfg,
@@ -360,174 +359,18 @@ func TestGenerateHourlyReportWritesExpectedArtifacts(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("ResolveGenerate() error = %v", err) t.Fatalf("ResolveGenerate() error = %v", err)
} }
outputPath := filepath.Join(t.TempDir(), "hourly.md") if resolved.Definition.GenerationMode != report.GenerationModeGeneratedTextTemplate {
renderer := successfulRenderer("# Hourly Report\n") t.Fatalf("GenerationMode = %q, want generated text template", resolved.Definition.GenerationMode)
}
result, err := GenerateReport(context.Background(), ReportRequest{ _, err = GenerateReport(context.Background(), ReportRequest{
Config: cfg, Config: cfg,
Resolved: resolved, Resolved: resolved,
OutputPath: outputPath, Renderer: successfulRenderer("# Hourly Report\n"),
Renderer: renderer,
}) })
if err != nil { if err == nil || !strings.Contains(err.Error(), `generation mode "generated_text_template" is not implemented for report "hourly"`) {
t.Fatalf("GenerateReport() error = %v", err) 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) { func TestGenerateReportDisabledNotificationDoesNotCallNotifier(t *testing.T) {
@@ -1367,7 +1210,7 @@ func TestResolveGenerateMapsCommandToReportDefinition(t *testing.T) {
name: "hourly", name: "hourly",
kind: ReportHourly, kind: ReportHourly,
wantID: report.Hourly, wantID: report.Hourly,
wantPrompt: "weather.hourly_report", wantPrompt: "weather.hourly_generated_text",
wantStart: "2026-05-29T08:00:00-05:00", wantStart: "2026-05-29T08:00:00-05:00",
wantEnd: "2026-05-29T14:00:00-05:00", wantEnd: "2026-05-29T14:00:00-05:00",
}, },

View File

@@ -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) { func TestResolveGenerateStormRequiresStartAndEnd(t *testing.T) {
runner := Runner{Clock: fixedClock()} runner := Runner{Clock: fixedClock()}

View File

@@ -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) { func TestExplicitMissingConfigReturnsError(t *testing.T) {
_, err := LoadFile(filepath.Join(t.TempDir(), "missing.yml")) _, err := LoadFile(filepath.Join(t.TempDir(), "missing.yml"))
if err == nil { if err == nil {

View File

@@ -7,21 +7,23 @@ import (
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil" "gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
) )
const hourlyHours = 6 const hourlyReportHours = 6
func hourlyDefinition() Definition { func hourlyDefinition() Definition {
return Definition{ return Definition{
ID: Hourly, ID: Hourly,
Name: "Hourly Report", Name: "Hourly Report",
PromptID: "weather.hourly_report", PromptID: "weather.hourly_generated_text",
GenerationMode: GenerationModeScriptoriumMarkdown, GenerationMode: GenerationModeGeneratedTextTemplate,
ComparisonStrategy: CompareRollingWindow, TemplateID: "hourly",
ArtifactGroup: "hourly", GeneratedTextSchemaID: "hourly",
BatchOutputName: "hourly.md", ComparisonStrategy: CompareRollingWindow,
Generated: true, ArtifactGroup: "hourly",
CompatiblePriorIDs: []ID{Hourly}, BatchOutputName: "hourly.md",
Modules: hourlyModules(), Generated: true,
resolve: resolveHourly, CompatiblePriorIDs: []ID{Hourly},
Modules: hourlyModules(),
resolve: resolveHourly,
} }
} }
@@ -48,6 +50,6 @@ func resolveHourly(req ResolveRequest) (timeutil.Period, error) {
localNow := req.Now.In(req.Location) localNow := req.Now.In(req.Location)
return timeutil.Period{ return timeutil.Period{
Start: localNow, Start: localNow,
End: localNow.Add(hourlyHours * time.Hour), End: localNow.Add(hourlyReportHours * time.Hour),
}, nil }, nil
} }

View File

@@ -69,8 +69,17 @@ func TestHourlyLookupAndPeriodCalculation(t *testing.T) {
if resolved.Definition.ID != Hourly { if resolved.Definition.ID != Hourly {
t.Fatalf("ID = %q, want hourly", resolved.Definition.ID) t.Fatalf("ID = %q, want hourly", resolved.Definition.ID)
} }
if resolved.Definition.PromptID != "weather.hourly_report" { if resolved.Definition.PromptID != "weather.hourly_generated_text" {
t.Fatalf("PromptID = %q, want weather.hourly_report", resolved.Definition.PromptID) 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 { if resolved.Definition.ComparisonStrategy != CompareRollingWindow {
t.Fatalf("ComparisonStrategy = %q, want rolling_window", resolved.Definition.ComparisonStrategy) t.Fatalf("ComparisonStrategy = %q, want rolling_window", resolved.Definition.ComparisonStrategy)
@@ -259,6 +268,18 @@ func TestRegistryDefinitionsDeclareGenerationMetadata(t *testing.T) {
if !definition.Generated { if !definition.Generated {
continue 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 { if definition.GenerationMode != GenerationModeScriptoriumMarkdown {
t.Fatalf("%s GenerationMode = %q, want %q", definition.ID, 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 { if metadata.ReportID != Hourly {
t.Fatalf("ReportID = %q, want hourly", metadata.ReportID) t.Fatalf("ReportID = %q, want hourly", metadata.ReportID)
} }
if metadata.PromptID != "weather.hourly_report" { if metadata.PromptID != "weather.hourly_generated_text" {
t.Fatalf("PromptID = %q, want weather.hourly_report", metadata.PromptID) t.Fatalf("PromptID = %q, want weather.hourly_generated_text", metadata.PromptID)
} }
if !strings.Contains(metadata.RunID, "hourly") { if !strings.Contains(metadata.RunID, "hourly") {
t.Fatalf("RunID = %q, want report id", metadata.RunID) t.Fatalf("RunID = %q, want report id", metadata.RunID)