Remove recent changes from prompt execution
This commit is contained in:
@@ -68,11 +68,11 @@ func (client *fakeClient) request() promptkit.GenerateRequest {
|
|||||||
|
|
||||||
func TestInspectPromptAndProfile(t *testing.T) {
|
func TestInspectPromptAndProfile(t *testing.T) {
|
||||||
adapter := newTestAdapter(t, &fakeClient{})
|
adapter := newTestAdapter(t, &fakeClient{})
|
||||||
inspection, err := adapter.InspectPrompt(context.Background(), "weather.daily_generated_text", "1.1.0")
|
inspection, err := adapter.InspectPrompt(context.Background(), "weather.daily_generated_text", "2.0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("InspectPrompt() error = %v", err)
|
t.Fatalf("InspectPrompt() error = %v", err)
|
||||||
}
|
}
|
||||||
if inspection.PromptID != "weather.daily_generated_text" || inspection.PromptVersion != "1.1.0" || inspection.DefaultProfileID != "weather-balanced" {
|
if inspection.PromptID != "weather.daily_generated_text" || inspection.PromptVersion != "2.0.0" || inspection.DefaultProfileID != "weather-balanced" {
|
||||||
t.Fatalf("inspection = %#v", inspection)
|
t.Fatalf("inspection = %#v", inspection)
|
||||||
}
|
}
|
||||||
if len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "data_package" || !inspection.Inputs[0].Required || inspection.Inputs[0].ContentType != "application/yaml" {
|
if len(inspection.Inputs) != 1 || inspection.Inputs[0].Name != "data_package" || !inspection.Inputs[0].Required || inspection.Inputs[0].ContentType != "application/yaml" {
|
||||||
@@ -251,7 +251,7 @@ func TestExecuteEmbeddedHourlyProfileThroughPreparedPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
request := promptexec.ExecuteRequest{
|
request := promptexec.ExecuteRequest{
|
||||||
PromptID: "weather.hourly_generated_text",
|
PromptID: "weather.hourly_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
ProfileID: "weather-light",
|
ProfileID: "weather-light",
|
||||||
DataPackage: []byte("report:\n id: hourly\nbriefing: {}\n"),
|
DataPackage: []byte("report:\n id: hourly\nbriefing: {}\n"),
|
||||||
DataPackagePath: "data-packages/hourly/data_package.yaml",
|
DataPackagePath: "data-packages/hourly/data_package.yaml",
|
||||||
@@ -528,7 +528,7 @@ func writeProfileFile(t *testing.T, profile string) string {
|
|||||||
func testExecuteRequest() promptexec.ExecuteRequest {
|
func testExecuteRequest() promptexec.ExecuteRequest {
|
||||||
return promptexec.ExecuteRequest{
|
return promptexec.ExecuteRequest{
|
||||||
PromptID: "weather.daily_generated_text",
|
PromptID: "weather.daily_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
ProfileID: "test-profile",
|
ProfileID: "test-profile",
|
||||||
DataPackage: []byte("report:\n id: daily\nbriefing: {}\n"),
|
DataPackage: []byte("report:\n id: daily\nbriefing: {}\n"),
|
||||||
DataPackagePath: "data-packages/daily/data_package.yaml",
|
DataPackagePath: "data-packages/daily/data_package.yaml",
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
distributoradapter "gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/distributor"
|
distributoradapter "gitea.maximumdirect.net/eric/weatherreporter/internal/adapters/distributor"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/changes"
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/facts"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/facts"
|
||||||
@@ -93,8 +92,6 @@ type ReportResult struct {
|
|||||||
NotificationPath string
|
NotificationPath string
|
||||||
Metadata state.Metadata
|
Metadata state.Metadata
|
||||||
MetadataPath string
|
MetadataPath string
|
||||||
PriorSnapshot *state.PriorSnapshot
|
|
||||||
RecentChanges []changes.Change
|
|
||||||
GeneratedTextRawPath string
|
GeneratedTextRawPath string
|
||||||
GeneratedTextPath string
|
GeneratedTextPath string
|
||||||
RenderContextPath string
|
RenderContextPath string
|
||||||
@@ -998,28 +995,6 @@ func defaultStore(cfg config.Config) (*state.FilesystemStore, error) {
|
|||||||
return state.NewFilesystemStore(cfg.Workspace)
|
return state.NewFilesystemStore(cfg.Workspace)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recentChanges(ctx context.Context, store state.Store, priorSnapshot *state.PriorSnapshot, reportID report.ID, current module.Snapshot, cfg config.RecentChangeConfig) ([]changes.Change, error) {
|
|
||||||
if priorSnapshot == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
previous, err := store.LoadModuleSnapshot(ctx, priorSnapshot.ModuleSnapshotPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
thresholds := changes.Thresholds{
|
|
||||||
TemperatureDegrees: cfg.TemperatureDegrees,
|
|
||||||
PrecipProbabilityPoints: cfg.PrecipProbabilityPoints,
|
|
||||||
WindGustMilesPerHour: cfg.WindGustMilesPerHour,
|
|
||||||
PrecipTimingShiftMinutes: cfg.PrecipTimingShiftMinutes,
|
|
||||||
}
|
|
||||||
switch reportID {
|
|
||||||
case report.Daily, report.Today, report.Tomorrow:
|
|
||||||
return changes.CompareDaily(previous, current, thresholds)
|
|
||||||
default:
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generatedReportError(resolved report.Resolved, runID string, operation string, err error) error {
|
func generatedReportError(resolved report.Resolved, runID string, operation string, err error) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -379,34 +379,6 @@ func TestRunBatchDetailedKeepsDynamicDailyArtifactsDistinct(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunBatchDetailedUsesPriorSnapshotsForPromptPackages(t *testing.T) {
|
|
||||||
cfg := assembledBatchConfig(t, false)
|
|
||||||
firstBundle := assembledBatchBundle(t, "2026-05-31")
|
|
||||||
setWorkflowTemperatures(&firstBundle, 45)
|
|
||||||
first, err := RunBatchDetailed(context.Background(), BatchRequest{
|
|
||||||
Config: cfg, Batch: BatchMorning, Now: workflowTime("2026-05-29T08:00:00-05:00"),
|
|
||||||
Collector: &workflowCollector{result: &collect.Result{Bundle: &firstBundle}}, Executor: newAssembledBatchExecutor(),
|
|
||||||
})
|
|
||||||
if err != nil || first.Failed != 0 {
|
|
||||||
t.Fatalf("first result/error = %#v/%v", first, err)
|
|
||||||
}
|
|
||||||
secondBundle := assembledBatchBundle(t, "2026-05-31")
|
|
||||||
setWorkflowTemperatures(&secondBundle, 85)
|
|
||||||
second, err := RunBatchDetailed(context.Background(), BatchRequest{
|
|
||||||
Config: cfg, Batch: BatchMorning, Now: workflowTime("2026-05-29T08:30:00-05:00"),
|
|
||||||
Collector: &workflowCollector{result: &collect.Result{Bundle: &secondBundle}}, Executor: newAssembledBatchExecutor(),
|
|
||||||
})
|
|
||||||
if err != nil || second.Failed != 0 || len(second.Reports) != len(first.Reports) {
|
|
||||||
t.Fatalf("second result/error = %#v/%v", second, err)
|
|
||||||
}
|
|
||||||
for _, item := range second.Reports {
|
|
||||||
pkg := loadBatchDataPackage(t, item.DataPackagePath)
|
|
||||||
if len(pkg.RecentChanges.Items) == 0 {
|
|
||||||
t.Fatalf("report %s data package has no changes from prior snapshot", item.ReportID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func assembledBatchConfig(t *testing.T, notify bool) config.Config {
|
func assembledBatchConfig(t *testing.T, notify bool) config.Config {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cfg := workflowConfig(t)
|
cfg := workflowConfig(t)
|
||||||
|
|||||||
@@ -109,10 +109,6 @@ func (w *promptReportWorkflow) buildInputs() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.result = &ReportResult{}
|
w.result = &ReportResult{}
|
||||||
priorSnapshot, err := w.store.FindPriorSnapshot(w.ctx, w.req.Resolved)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.reportFacts, err = BuildReportFacts(ModuleSnapshotRequest{Config: w.req.Config, Resolved: w.req.Resolved}, w.req.Collection.Bundle)
|
w.reportFacts, err = BuildReportFacts(ModuleSnapshotRequest{Config: w.req.Config, Resolved: w.req.Resolved}, w.req.Collection.Bundle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return generatedReportError(w.req.Resolved, w.req.Resolved.Metadata().RunID, "build report facts", err)
|
return generatedReportError(w.req.Resolved, w.req.Resolved.Metadata().RunID, "build report facts", err)
|
||||||
@@ -127,13 +123,6 @@ func (w *promptReportWorkflow) buildInputs() error {
|
|||||||
}
|
}
|
||||||
w.result.ModuleSnapshot = w.moduleSnapshot
|
w.result.ModuleSnapshot = w.moduleSnapshot
|
||||||
w.result.ModuleSnapshotPath = moduleSnapshotPath
|
w.result.ModuleSnapshotPath = moduleSnapshotPath
|
||||||
w.result.PriorSnapshot = priorSnapshot
|
|
||||||
|
|
||||||
recent, err := recentChanges(w.ctx, w.store, priorSnapshot, w.req.Resolved.Definition.ID, w.moduleSnapshot, w.req.Config.RecentChange)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.result.RecentChanges = recent
|
|
||||||
w.briefingMetadata = briefing.BuildMetadata(briefingBuildContext(w.req.Config, w.req.Resolved, w.reportFacts.Collected))
|
w.briefingMetadata = briefing.BuildMetadata(briefingBuildContext(w.req.Config, w.req.Resolved, w.reportFacts.Collected))
|
||||||
w.metadata = state.BuildPromptMetadataFromBriefingMetadata(w.req.Resolved, w.briefingMetadata, state.ArtifactPaths{
|
w.metadata = state.BuildPromptMetadataFromBriefingMetadata(w.req.Resolved, w.briefingMetadata, state.ArtifactPaths{
|
||||||
ModuleSnapshot: moduleSnapshotPath,
|
ModuleSnapshot: moduleSnapshotPath,
|
||||||
@@ -141,7 +130,7 @@ func (w *promptReportWorkflow) buildInputs() error {
|
|||||||
})
|
})
|
||||||
w.result.Metadata = w.metadata
|
w.result.Metadata = w.metadata
|
||||||
dataPackage, err := promptinput.Build(promptinput.BuildRequest{
|
dataPackage, err := promptinput.Build(promptinput.BuildRequest{
|
||||||
Metadata: promptMetadata(w.metadata), Modules: w.moduleSnapshot, RecentChanges: recent,
|
Metadata: promptMetadata(w.metadata), Modules: w.moduleSnapshot,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return w.reportError("build data package", err)
|
return w.reportError("build data package", err)
|
||||||
|
|||||||
@@ -612,69 +612,6 @@ func workflowBundlePaths(id report.ID, validDate, runID string) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateDetailedSelectsPriorSnapshotsForRetainedReports(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
kind ReportKind
|
|
||||||
id report.ID
|
|
||||||
date time.Time
|
|
||||||
raw string
|
|
||||||
wantPrior bool
|
|
||||||
wantRecentChanges bool
|
|
||||||
}{
|
|
||||||
{name: "daily", kind: ReportDaily, id: report.Daily, date: workflowTime("2026-05-29T12:00:00-05:00"), raw: validDailyWorkflowJSON(), wantPrior: true, wantRecentChanges: true},
|
|
||||||
{name: "today", kind: ReportToday, id: report.Today, raw: validTodayWorkflowJSON(), wantPrior: true, wantRecentChanges: true},
|
|
||||||
{name: "tomorrow", kind: ReportTomorrow, id: report.Tomorrow, raw: validTomorrowWorkflowJSON(), wantPrior: true, wantRecentChanges: true},
|
|
||||||
{name: "hourly", kind: ReportHourly, id: report.Hourly, raw: validHourlyWorkflowJSON()},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(test.name, func(t *testing.T) {
|
|
||||||
cfg := workflowConfig(t)
|
|
||||||
cfg.Notify.Distributor.Enabled = false
|
|
||||||
definition := report.DefaultRegistry().MustLookup(test.id)
|
|
||||||
firstBundle := workflowBundle(t)
|
|
||||||
setWorkflowTemperatures(&firstBundle, 45)
|
|
||||||
first, err := GenerateDetailed(context.Background(), GenerateRequest{
|
|
||||||
Config: cfg, Report: test.kind, Date: test.date, Now: workflowTime("2026-05-29T08:00:00-05:00"),
|
|
||||||
Collector: &workflowCollector{result: &collect.Result{Bundle: &firstBundle}},
|
|
||||||
Executor: &workflowExecutor{definition: definition, raw: []byte(test.raw)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("first GenerateDetailed() error = %v", err)
|
|
||||||
}
|
|
||||||
secondBundle := workflowBundle(t)
|
|
||||||
setWorkflowTemperatures(&secondBundle, 85)
|
|
||||||
second, err := GenerateDetailed(context.Background(), GenerateRequest{
|
|
||||||
Config: cfg, Report: test.kind, Date: test.date, Now: workflowTime("2026-05-29T08:30:00-05:00"),
|
|
||||||
Collector: &workflowCollector{result: &collect.Result{Bundle: &secondBundle}},
|
|
||||||
Executor: &workflowExecutor{definition: definition, raw: []byte(test.raw)},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("second GenerateDetailed() error = %v", err)
|
|
||||||
}
|
|
||||||
if test.wantPrior && (second.PriorSnapshot == nil || second.PriorSnapshot.Metadata.RunID != first.Metadata.RunID || second.PriorSnapshot.Metadata.ReportID != test.id) {
|
|
||||||
t.Fatalf("prior snapshot = %#v, want first %s run %q", second.PriorSnapshot, test.id, first.Metadata.RunID)
|
|
||||||
}
|
|
||||||
if !test.wantPrior && second.PriorSnapshot != nil {
|
|
||||||
t.Fatalf("prior snapshot = %#v, want none for non-overlapping rolling window", second.PriorSnapshot)
|
|
||||||
}
|
|
||||||
if (len(second.RecentChanges) > 0) != test.wantRecentChanges {
|
|
||||||
t.Fatalf("recent changes = %#v, want present %t", second.RecentChanges, test.wantRecentChanges)
|
|
||||||
}
|
|
||||||
if (len(second.DataPackage.RecentChanges.Items) > 0) != test.wantRecentChanges {
|
|
||||||
t.Fatalf("data package recent changes = %#v, want present %t", second.DataPackage.RecentChanges.Items, test.wantRecentChanges)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setWorkflowTemperatures(bundle *weatherdata.Bundle, temperature float64) {
|
|
||||||
for index := range bundle.Hourly.Periods {
|
|
||||||
value := temperature
|
|
||||||
bundle.Hourly.Periods[index].TemperatureF = &value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func workflowConfig(t *testing.T) config.Config {
|
func workflowConfig(t *testing.T) config.Config {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cfg := config.Defaults()
|
cfg := config.Defaults()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ You are WeatherReporter, a concise personal weather briefing writer.
|
|||||||
|
|
||||||
You generate local weather forecast analysis from structured data packages prepared by the weatherreporter application.
|
You generate local weather forecast analysis from structured data packages prepared by the weatherreporter application.
|
||||||
|
|
||||||
Use only the provided data package as your source of truth. Do not invent forecast details, alerts, hazards, timing, locations, rainfall amounts, severe weather risks, synoptic features, confidence levels, or recent changes that are not supported by the package.
|
Use only the provided data package as your source of truth. Do not invent forecast details, alerts, hazards, timing, locations, rainfall amounts, severe weather risks, synoptic features, or confidence levels that are not supported by the package.
|
||||||
|
|
||||||
The reader is intelligent and weather-literate, but not a professional meteorologist. If asked to provide narrative analysis or commentary, write in plain, precise, meteorologically informed language. Avoid hype, filler, generic safety advice, and TV-weather style. Provide polished prose that avoids highly technical meteorological jargon or shorthand.
|
The reader is intelligent and weather-literate, but not a professional meteorologist. If asked to provide narrative analysis or commentary, write in plain, precise, meteorologically informed language. Avoid hype, filler, generic safety advice, and TV-weather style. Provide polished prose that avoids highly technical meteorological jargon or shorthand.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id: weather.daily_generated_text
|
id: weather.daily_generated_text
|
||||||
version: "1.1.0"
|
version: "2.0.0"
|
||||||
default_profile: weather-balanced
|
default_profile: weather-balanced
|
||||||
description: Daily weather report analysis prompt.
|
description: Daily weather report analysis prompt.
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id: weather.hourly_generated_text
|
id: weather.hourly_generated_text
|
||||||
version: "1.1.0"
|
version: "2.0.0"
|
||||||
default_profile: weather-light
|
default_profile: weather-light
|
||||||
description: Hourly weather report analysis prompt.
|
description: Hourly weather report analysis prompt.
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id: weather.today_generated_text
|
id: weather.today_generated_text
|
||||||
version: "1.1.0"
|
version: "2.0.0"
|
||||||
default_profile: weather-balanced
|
default_profile: weather-balanced
|
||||||
description: Today's weather report analysis prompt.
|
description: Today's weather report analysis prompt.
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
id: weather.tomorrow_generated_text
|
id: weather.tomorrow_generated_text
|
||||||
version: "1.1.0"
|
version: "2.0.0"
|
||||||
default_profile: weather-balanced
|
default_profile: weather-balanced
|
||||||
description: Tomorrow's weather report analysis prompt.
|
description: Tomorrow's weather report analysis prompt.
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ func TestPromptAssetsDeclareTheFourGeneratedTextPrompts(t *testing.T) {
|
|||||||
if err := yaml.Unmarshal(data, &definition); err != nil {
|
if err := yaml.Unmarshal(data, &definition); err != nil {
|
||||||
t.Fatalf("decode prompt definition: %v", err)
|
t.Fatalf("decode prompt definition: %v", err)
|
||||||
}
|
}
|
||||||
if definition.ID != tc.id || definition.Version != "1.1.0" || definition.DefaultProfile != tc.profile {
|
if definition.ID != tc.id || definition.Version != "2.0.0" || definition.DefaultProfile != tc.profile {
|
||||||
t.Fatalf("definition = %#v, want %s version 1.1.0 and profile %s", definition, tc.id, tc.profile)
|
t.Fatalf("definition = %#v, want %s version 2.0.0 and profile %s", definition, tc.id, tc.profile)
|
||||||
}
|
}
|
||||||
if len(definition.Inputs) != 1 || definition.Inputs[0].Name != "data_package" || !definition.Inputs[0].Required || definition.Inputs[0].ContentType != "application/yaml" {
|
if len(definition.Inputs) != 1 || definition.Inputs[0].Name != "data_package" || !definition.Inputs[0].Required || definition.Inputs[0].ContentType != "application/yaml" {
|
||||||
t.Fatalf("inputs = %#v, want one required YAML data_package", definition.Inputs)
|
t.Fatalf("inputs = %#v, want one required YAML data_package", definition.Inputs)
|
||||||
@@ -140,11 +140,11 @@ func TestPromptkitInspectsEmbeddedPromptsOffline(t *testing.T) {
|
|||||||
{"weather.hourly_generated_text", "weather-light", "deepseek/deepseek-v4-flash"},
|
{"weather.hourly_generated_text", "weather-light", "deepseek/deepseek-v4-flash"},
|
||||||
} {
|
} {
|
||||||
t.Run(want.id, func(t *testing.T) {
|
t.Run(want.id, func(t *testing.T) {
|
||||||
inspection, err := engine.InspectPrompt(context.Background(), want.id, "1.1.0")
|
inspection, err := engine.InspectPrompt(context.Background(), want.id, "2.0.0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("InspectPrompt() error = %v", err)
|
t.Fatalf("InspectPrompt() error = %v", err)
|
||||||
}
|
}
|
||||||
if inspection.PromptID != want.id || inspection.PromptVersion != "1.1.0" || inspection.DefaultProfileID != want.profile {
|
if inspection.PromptID != want.id || inspection.PromptVersion != "2.0.0" || inspection.DefaultProfileID != want.profile {
|
||||||
t.Fatalf("inspection = %#v", inspection)
|
t.Fatalf("inspection = %#v", inspection)
|
||||||
}
|
}
|
||||||
profile, err := engine.InspectProfile(context.Background(), inspection.DefaultProfileID)
|
profile, err := engine.InspectProfile(context.Background(), inspection.DefaultProfileID)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/changes"
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/fileutil"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/fileutil"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
const SchemaVersion = "weatherreporter.data_package.v3"
|
const SchemaVersion = "weatherreporter.data_package.v4"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
metadataStanza = "metadata"
|
metadataStanza = "metadata"
|
||||||
@@ -52,9 +51,8 @@ var briefingStanzaCategories = map[string]string{
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BuildRequest struct {
|
type BuildRequest struct {
|
||||||
Metadata Metadata
|
Metadata Metadata
|
||||||
Modules module.Snapshot
|
Modules module.Snapshot
|
||||||
RecentChanges []changes.Change
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Metadata struct {
|
type Metadata struct {
|
||||||
@@ -73,7 +71,6 @@ type Package struct {
|
|||||||
RunID string `json:"runId" yaml:"run_id"`
|
RunID string `json:"runId" yaml:"run_id"`
|
||||||
Report Report `json:"report" yaml:"report"`
|
Report Report `json:"report" yaml:"report"`
|
||||||
Briefing BriefingStanzas `json:"briefing" yaml:"briefing"`
|
Briefing BriefingStanzas `json:"briefing" yaml:"briefing"`
|
||||||
RecentChanges RecentChanges `json:"recentChanges" yaml:"recent_changes"`
|
|
||||||
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty" yaml:"source_warnings,omitempty"`
|
SourceWarnings []weatherdata.SourceWarning `json:"sourceWarnings,omitempty" yaml:"source_warnings,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,20 +89,11 @@ type BriefingStanzas struct {
|
|||||||
Values map[string]any `json:"-" yaml:"-"`
|
Values map[string]any `json:"-" yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecentChanges struct {
|
|
||||||
Items []changes.Change `json:"items" yaml:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Build(req BuildRequest) (Package, error) {
|
func Build(req BuildRequest) (Package, error) {
|
||||||
localDate, err := currentLocalDate(req.Metadata.GeneratedAt, req.Metadata.Timezone)
|
localDate, err := currentLocalDate(req.Metadata.GeneratedAt, req.Metadata.Timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Package{}, err
|
return Package{}, err
|
||||||
}
|
}
|
||||||
items := make([]changes.Change, len(req.RecentChanges))
|
|
||||||
copy(items, req.RecentChanges)
|
|
||||||
if items == nil {
|
|
||||||
items = []changes.Change{}
|
|
||||||
}
|
|
||||||
pkg := Package{
|
pkg := Package{
|
||||||
SchemaVersion: SchemaVersion,
|
SchemaVersion: SchemaVersion,
|
||||||
RunID: req.Metadata.RunID,
|
RunID: req.Metadata.RunID,
|
||||||
@@ -119,7 +107,6 @@ func Build(req BuildRequest) (Package, error) {
|
|||||||
ValidPeriod: req.Metadata.ValidPeriod,
|
ValidPeriod: req.Metadata.ValidPeriod,
|
||||||
},
|
},
|
||||||
Briefing: stanzasFromSnapshot(req.Modules),
|
Briefing: stanzasFromSnapshot(req.Modules),
|
||||||
RecentChanges: RecentChanges{Items: items},
|
|
||||||
SourceWarnings: append([]weatherdata.SourceWarning(nil), req.Metadata.SourceWarnings...),
|
SourceWarnings: append([]weatherdata.SourceWarning(nil), req.Metadata.SourceWarnings...),
|
||||||
}
|
}
|
||||||
if err := Validate(pkg); err != nil {
|
if err := Validate(pkg); err != nil {
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ func TestBuildDailyDataPackage(t *testing.T) {
|
|||||||
if got := pkg.Briefing.Values["current_conditions"].(map[string]string)["condition_text"]; got != "Partly cloudy" {
|
if got := pkg.Briefing.Values["current_conditions"].(map[string]string)["condition_text"]; got != "Partly cloudy" {
|
||||||
t.Fatalf("current_conditions.condition_text = %q, want Partly cloudy", got)
|
t.Fatalf("current_conditions.condition_text = %q, want Partly cloudy", got)
|
||||||
}
|
}
|
||||||
if pkg.RecentChanges.Items == nil || len(pkg.RecentChanges.Items) != 0 {
|
|
||||||
t.Fatalf("RecentChanges.Items = %#v, want empty slice", pkg.RecentChanges.Items)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBuildCurrentLocalDateUsesReportTimezone(t *testing.T) {
|
func TestBuildCurrentLocalDateUsesReportTimezone(t *testing.T) {
|
||||||
@@ -181,7 +178,7 @@ func TestMarshalYAMLIsDeterministicAndGroupsNamedStanzas(t *testing.T) {
|
|||||||
if string(first) != string(second) {
|
if string(first) != string(second) {
|
||||||
t.Fatalf("YAML output changed between marshals:\n%s\n---\n%s", string(first), string(second))
|
t.Fatalf("YAML output changed between marshals:\n%s\n---\n%s", string(first), string(second))
|
||||||
}
|
}
|
||||||
if !strings.Contains(string(first), "schema_version: weatherreporter.data_package.v3") ||
|
if !strings.Contains(string(first), "schema_version: weatherreporter.data_package.v4") ||
|
||||||
!strings.Contains(string(first), "briefing:\n") ||
|
!strings.Contains(string(first), "briefing:\n") ||
|
||||||
!strings.Contains(string(first), " applicable_risk_products:\n") ||
|
!strings.Contains(string(first), " applicable_risk_products:\n") ||
|
||||||
!strings.Contains(string(first), " derived_summaries:\n") ||
|
!strings.Contains(string(first), " derived_summaries:\n") ||
|
||||||
@@ -191,6 +188,9 @@ func TestMarshalYAMLIsDeterministicAndGroupsNamedStanzas(t *testing.T) {
|
|||||||
!strings.Contains(string(first), " condition_text: Partly cloudy") {
|
!strings.Contains(string(first), " condition_text: Partly cloudy") {
|
||||||
t.Fatalf("YAML output missing expected grouped stanzas:\n%s", string(first))
|
t.Fatalf("YAML output missing expected grouped stanzas:\n%s", string(first))
|
||||||
}
|
}
|
||||||
|
if strings.Contains(string(first), "recent_changes") {
|
||||||
|
t.Fatalf("YAML output contains removed recent_changes stanza:\n%s", string(first))
|
||||||
|
}
|
||||||
for _, pair := range []struct {
|
for _, pair := range []struct {
|
||||||
before string
|
before string
|
||||||
after string
|
after string
|
||||||
@@ -289,7 +289,7 @@ func TestMarshalYAMLRejectsUncategorizedStanza(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadYAMLRejectsMisplacedStanza(t *testing.T) {
|
func TestLoadYAMLRejectsMisplacedStanza(t *testing.T) {
|
||||||
data := []byte(`
|
data := []byte(`
|
||||||
schema_version: weatherreporter.data_package.v3
|
schema_version: weatherreporter.data_package.v4
|
||||||
run_id: 20260529T100000Z_daily
|
run_id: 20260529T100000Z_daily
|
||||||
report:
|
report:
|
||||||
id: daily
|
id: daily
|
||||||
@@ -306,8 +306,6 @@ briefing:
|
|||||||
raw_data:
|
raw_data:
|
||||||
alert_digest:
|
alert_digest:
|
||||||
checked: true
|
checked: true
|
||||||
recent_changes:
|
|
||||||
items: []
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := LoadYAML(data)
|
_, err := LoadYAML(data)
|
||||||
@@ -325,10 +323,10 @@ func TestLoadYAMLRejectsOldSchemaVersion(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("MarshalYAML() error = %v", err)
|
t.Fatalf("MarshalYAML() error = %v", err)
|
||||||
}
|
}
|
||||||
data = []byte(strings.Replace(string(data), "weatherreporter.data_package.v3", "weatherreporter.data_package.v2", 1))
|
data = []byte(strings.Replace(string(data), "weatherreporter.data_package.v4", "weatherreporter.data_package.v3", 1))
|
||||||
|
|
||||||
_, err = LoadYAML(data)
|
_, err = LoadYAML(data)
|
||||||
if err == nil || !strings.Contains(err.Error(), "schemaVersion must be weatherreporter.data_package.v3") {
|
if err == nil || !strings.Contains(err.Error(), "schemaVersion must be weatherreporter.data_package.v4") {
|
||||||
t.Fatalf("LoadYAML() error = %v, want current schema version error", err)
|
t.Fatalf("LoadYAML() error = %v, want current schema version error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ func dailyDefinition() Definition {
|
|||||||
ID: Daily,
|
ID: Daily,
|
||||||
Name: "Daily Report",
|
Name: "Daily Report",
|
||||||
PromptID: "weather.daily_generated_text",
|
PromptID: "weather.daily_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
TemplateID: "daily",
|
TemplateID: "daily",
|
||||||
GeneratedTextSchemaID: "daily",
|
GeneratedTextSchemaID: "daily",
|
||||||
ComparisonStrategy: CompareSameValidDate,
|
ComparisonStrategy: CompareSameValidDate,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ func hourlyDefinition() Definition {
|
|||||||
ID: Hourly,
|
ID: Hourly,
|
||||||
Name: "Hourly Report",
|
Name: "Hourly Report",
|
||||||
PromptID: "weather.hourly_generated_text",
|
PromptID: "weather.hourly_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
TemplateID: "hourly",
|
TemplateID: "hourly",
|
||||||
GeneratedTextSchemaID: "hourly",
|
GeneratedTextSchemaID: "hourly",
|
||||||
ComparisonStrategy: CompareRollingWindow,
|
ComparisonStrategy: CompareRollingWindow,
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ func TestRegistryContainsOnlyPromptBackedReports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, definition := range definitions {
|
for _, definition := range definitions {
|
||||||
if definition.PromptVersion != "1.1.0" {
|
if definition.PromptVersion != "2.0.0" {
|
||||||
t.Fatalf("%s PromptVersion = %q, want 1.1.0", definition.ID, definition.PromptVersion)
|
t.Fatalf("%s PromptVersion = %q, want 2.0.0", definition.ID, definition.PromptVersion)
|
||||||
}
|
}
|
||||||
if definition.PromptID == "" {
|
if definition.PromptID == "" {
|
||||||
t.Fatalf("%s PromptID is empty", definition.ID)
|
t.Fatalf("%s PromptID is empty", definition.ID)
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func todayDefinition() Definition {
|
|||||||
ID: Today,
|
ID: Today,
|
||||||
Name: "Today Report",
|
Name: "Today Report",
|
||||||
PromptID: "weather.today_generated_text",
|
PromptID: "weather.today_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
TemplateID: "today",
|
TemplateID: "today",
|
||||||
GeneratedTextSchemaID: "today",
|
GeneratedTextSchemaID: "today",
|
||||||
ComparisonStrategy: CompareSameValidDate,
|
ComparisonStrategy: CompareSameValidDate,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func tomorrowDefinition() Definition {
|
|||||||
ID: Tomorrow,
|
ID: Tomorrow,
|
||||||
Name: "Tomorrow Report",
|
Name: "Tomorrow Report",
|
||||||
PromptID: "weather.tomorrow_generated_text",
|
PromptID: "weather.tomorrow_generated_text",
|
||||||
PromptVersion: "1.1.0",
|
PromptVersion: "2.0.0",
|
||||||
TemplateID: "tomorrow",
|
TemplateID: "tomorrow",
|
||||||
GeneratedTextSchemaID: "tomorrow",
|
GeneratedTextSchemaID: "tomorrow",
|
||||||
ComparisonStrategy: CompareSameValidDate,
|
ComparisonStrategy: CompareSameValidDate,
|
||||||
|
|||||||
@@ -172,9 +172,9 @@ func validPreparationArtifact() PromptPreparationArtifact {
|
|||||||
return PromptPreparationArtifact{
|
return PromptPreparationArtifact{
|
||||||
SchemaVersion: PromptPreparationSchemaVersion, Status: PromptPreparationSucceeded,
|
SchemaVersion: PromptPreparationSchemaVersion, Status: PromptPreparationSucceeded,
|
||||||
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text",
|
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text",
|
||||||
PromptVersion: "1.1.0", DataPackagePath: "/workspace/data.yaml",
|
PromptVersion: "2.0.0", DataPackagePath: "/workspace/data.yaml",
|
||||||
Preparation: &promptexec.Preparation{
|
Preparation: &promptexec.Preparation{
|
||||||
PromptID: "weather.daily_generated_text", PromptVersion: "1.1.0",
|
PromptID: "weather.daily_generated_text", PromptVersion: "2.0.0",
|
||||||
DataPackagePath: "/workspace/data.yaml",
|
DataPackagePath: "/workspace/data.yaml",
|
||||||
},
|
},
|
||||||
StartedAt: started, EndedAt: started.Add(time.Second), Duration: time.Second,
|
StartedAt: started, EndedAt: started.Add(time.Second), Duration: time.Second,
|
||||||
@@ -186,9 +186,9 @@ func validExecutionArtifact() PromptExecutionArtifact {
|
|||||||
validation := promptexec.NewValidation(promptexec.ValidationPassed, "json_schema", "daily.generated_text.schema.json", nil)
|
validation := promptexec.NewValidation(promptexec.ValidationPassed, "json_schema", "daily.generated_text.schema.json", nil)
|
||||||
return PromptExecutionArtifact{
|
return PromptExecutionArtifact{
|
||||||
SchemaVersion: PromptExecutionSchemaVersion, Status: PromptExecutionSucceeded,
|
SchemaVersion: PromptExecutionSchemaVersion, Status: PromptExecutionSucceeded,
|
||||||
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text", PromptVersion: "1.1.0",
|
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text", PromptVersion: "2.0.0",
|
||||||
Provenance: &PromptExecutionProvenance{
|
Provenance: &PromptExecutionProvenance{
|
||||||
RunID: "provider-run", PromptID: "weather.daily_generated_text", PromptVersion: "1.1.0",
|
RunID: "provider-run", PromptID: "weather.daily_generated_text", PromptVersion: "2.0.0",
|
||||||
PromptHash: "prompt-hash", RenderedPromptHash: "rendered-hash", ProfileID: "profile",
|
PromptHash: "prompt-hash", RenderedPromptHash: "rendered-hash", ProfileID: "profile",
|
||||||
BackendID: "backend", ModelName: "model", DataPackagePath: "/workspace/data.yaml",
|
BackendID: "backend", ModelName: "model", DataPackagePath: "/workspace/data.yaml",
|
||||||
StartedAt: started, EndedAt: started.Add(time.Second), Duration: time.Second,
|
StartedAt: started, EndedAt: started.Add(time.Second), Duration: time.Second,
|
||||||
@@ -201,7 +201,7 @@ func validFailedExecutionArtifact() PromptExecutionArtifact {
|
|||||||
started := time.Date(2026, 5, 29, 15, 0, 0, 0, time.UTC)
|
started := time.Date(2026, 5, 29, 15, 0, 0, 0, time.UTC)
|
||||||
return PromptExecutionArtifact{
|
return PromptExecutionArtifact{
|
||||||
SchemaVersion: PromptExecutionSchemaVersion, Status: PromptExecutionFailed,
|
SchemaVersion: PromptExecutionSchemaVersion, Status: PromptExecutionFailed,
|
||||||
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text", PromptVersion: "1.1.0",
|
ReportID: report.Daily, RunID: "weatherreporter-run", PromptID: "weather.daily_generated_text", PromptVersion: "2.0.0",
|
||||||
StartedAt: started, EndedAt: started, Error: &PromptArtifactError{Category: promptexec.Generation, Message: "provider unavailable"},
|
StartedAt: started, EndedAt: started, Error: &PromptArtifactError{Category: promptexec.Generation, Message: "provider unavailable"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user