Extract immutable report preparation
This commit is contained in:
150
internal/app/prepared_report.go
Normal file
150
internal/app/prepared_report.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/collect"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/generatedtext"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptinput"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/weatherdata"
|
||||
)
|
||||
|
||||
// preparedReport contains the immutable deterministic inputs shared by prompt
|
||||
// executions for one resolved report.
|
||||
type preparedReport struct {
|
||||
resolved report.Resolved
|
||||
reportFacts ReportFacts
|
||||
moduleSnapshot module.Snapshot
|
||||
briefingMetadata briefing.Metadata
|
||||
sourceWarnings []weatherdata.SourceWarning
|
||||
dataPackage []byte
|
||||
handler generatedtext.Handler
|
||||
}
|
||||
|
||||
type prepareReportRequest struct {
|
||||
Config config.Config
|
||||
Resolved report.Resolved
|
||||
Collection collect.Result
|
||||
}
|
||||
|
||||
type preparationError struct {
|
||||
operation string
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *preparationError) Error() string {
|
||||
return e.operation + ": " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e *preparationError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func prepareReport(req prepareReportRequest) (preparedReport, error) {
|
||||
if req.Collection.Bundle == nil {
|
||||
return preparedReport{}, &preparationError{operation: "prepare report", err: fmt.Errorf("collected weather bundle is required")}
|
||||
}
|
||||
|
||||
reportFacts, err := BuildReportFacts(ModuleSnapshotRequest{Config: req.Config, Resolved: req.Resolved}, req.Collection.Bundle)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build report facts", err: err}
|
||||
}
|
||||
moduleSnapshot, err := BuildModuleSnapshotFromFacts(ModuleSnapshotRequest{Config: req.Config, Resolved: req.Resolved}, reportFacts)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build module snapshot", err: err}
|
||||
}
|
||||
metadata := briefing.BuildMetadata(briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected))
|
||||
dataPackage, err := promptinput.Build(promptinput.BuildRequest{Metadata: promptMetadata(metadata), Modules: moduleSnapshot})
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "build data package", err: err}
|
||||
}
|
||||
serializedDataPackage, err := promptinput.MarshalYAML(dataPackage)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "marshal data package", err: err}
|
||||
}
|
||||
handler, err := generatedtext.LookupDefinition(req.Resolved.Definition)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "lookup generated text catalog", err: err}
|
||||
}
|
||||
|
||||
clonedFacts, err := clonePreparedValue(reportFacts)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared report facts", err: err}
|
||||
}
|
||||
clonedSnapshot, err := clonePreparedValue(moduleSnapshot)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared module snapshot", err: err}
|
||||
}
|
||||
clonedMetadata, err := clonePreparedValue(metadata)
|
||||
if err != nil {
|
||||
return preparedReport{}, &preparationError{operation: "copy prepared briefing metadata", err: err}
|
||||
}
|
||||
prepared := preparedReport{
|
||||
resolved: cloneResolved(req.Resolved),
|
||||
reportFacts: clonedFacts,
|
||||
moduleSnapshot: clonedSnapshot,
|
||||
briefingMetadata: clonedMetadata,
|
||||
sourceWarnings: append([]weatherdata.SourceWarning(nil), clonedMetadata.SourceWarnings...),
|
||||
dataPackage: append([]byte(nil), serializedDataPackage...),
|
||||
handler: handler,
|
||||
}
|
||||
return prepared, nil
|
||||
}
|
||||
|
||||
func cloneResolved(value report.Resolved) report.Resolved {
|
||||
cloned := value
|
||||
cloned.Definition.DistributorPathTemplates = append([]string(nil), value.Definition.DistributorPathTemplates...)
|
||||
cloned.Definition.Modules = make([]module.ConfigItem, len(value.Definition.Modules))
|
||||
for i, item := range value.Definition.Modules {
|
||||
cloned.Definition.Modules[i] = item
|
||||
switch options := item.Options.(type) {
|
||||
case module.AreaForecastDiscussionOptions:
|
||||
options.Sections = append([]string(nil), options.Sections...)
|
||||
cloned.Definition.Modules[i].Options = options
|
||||
}
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
func (p preparedReport) dataPackageCopy() []byte {
|
||||
return append([]byte(nil), p.dataPackage...)
|
||||
}
|
||||
|
||||
func (p preparedReport) sourceWarningsCopy() []weatherdata.SourceWarning {
|
||||
return append([]weatherdata.SourceWarning(nil), p.sourceWarnings...)
|
||||
}
|
||||
|
||||
func (p preparedReport) renderInputs() (briefing.Metadata, module.Snapshot, ReportFacts, error) {
|
||||
metadata, err := clonePreparedValue(p.briefingMetadata)
|
||||
if err != nil {
|
||||
return briefing.Metadata{}, module.Snapshot{}, ReportFacts{}, err
|
||||
}
|
||||
snapshot, err := clonePreparedValue(p.moduleSnapshot)
|
||||
if err != nil {
|
||||
return briefing.Metadata{}, module.Snapshot{}, ReportFacts{}, err
|
||||
}
|
||||
reportFacts, err := clonePreparedValue(p.reportFacts)
|
||||
if err != nil {
|
||||
return briefing.Metadata{}, module.Snapshot{}, ReportFacts{}, err
|
||||
}
|
||||
return metadata, snapshot, reportFacts, nil
|
||||
}
|
||||
|
||||
func clonePreparedValue[T any](value T) (T, error) {
|
||||
encoded, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, fmt.Errorf("marshal immutable prepared value: %w", err)
|
||||
}
|
||||
var cloned T
|
||||
if err := json.Unmarshal(encoded, &cloned); err != nil {
|
||||
var zero T
|
||||
return zero, fmt.Errorf("unmarshal immutable prepared value: %w", err)
|
||||
}
|
||||
return cloned, nil
|
||||
}
|
||||
Reference in New Issue
Block a user