149 lines
5.3 KiB
Go
149 lines
5.3 KiB
Go
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/facts"
|
|
"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
|
|
derived facts.DerivedFacts
|
|
moduleSnapshot module.Snapshot
|
|
identity briefing.PreparedIdentity
|
|
sourceWarnings []weatherdata.SourceWarning
|
|
dataPackage []byte
|
|
handler generatedtext.Handler
|
|
}
|
|
|
|
type prepareReportRequest struct {
|
|
Config config.Config
|
|
Resolved report.Resolved
|
|
Collection collect.Result
|
|
handler generatedtext.Handler
|
|
}
|
|
|
|
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}
|
|
}
|
|
buildContext := briefingBuildContext(req.Config, req.Resolved, reportFacts.Collected)
|
|
identity := briefing.BuildPreparedIdentity(buildContext)
|
|
moduleSnapshot, err := BuildModuleSnapshotFromFacts(ModuleSnapshotRequest{Config: req.Config, Resolved: req.Resolved, Identity: identity}, reportFacts)
|
|
if err != nil {
|
|
return preparedReport{}, &preparationError{operation: "build module snapshot", err: err}
|
|
}
|
|
dataPackage, err := promptinput.Build(promptinput.BuildRequest{Metadata: promptMetadata(identity), 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}
|
|
}
|
|
clonedDerived, err := clonePreparedValue(reportFacts.Derived)
|
|
if err != nil {
|
|
return preparedReport{}, &preparationError{operation: "copy prepared derived facts", err: err}
|
|
}
|
|
clonedSnapshot, err := clonePreparedValue(moduleSnapshot)
|
|
if err != nil {
|
|
return preparedReport{}, &preparationError{operation: "copy prepared module snapshot", err: err}
|
|
}
|
|
clonedIdentity, err := clonePreparedValue(identity)
|
|
if err != nil {
|
|
return preparedReport{}, &preparationError{operation: "copy prepared identity", err: err}
|
|
}
|
|
prepared := preparedReport{
|
|
resolved: cloneResolved(req.Resolved),
|
|
derived: clonedDerived,
|
|
moduleSnapshot: clonedSnapshot,
|
|
identity: clonedIdentity,
|
|
sourceWarnings: append([]weatherdata.SourceWarning(nil), clonedIdentity.SourceWarnings...),
|
|
dataPackage: append([]byte(nil), serializedDataPackage...),
|
|
handler: req.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.PreparedIdentity, module.Snapshot, facts.DerivedFacts, error) {
|
|
identity, err := clonePreparedValue(p.identity)
|
|
if err != nil {
|
|
return briefing.PreparedIdentity{}, module.Snapshot{}, facts.DerivedFacts{}, err
|
|
}
|
|
snapshot, err := clonePreparedValue(p.moduleSnapshot)
|
|
if err != nil {
|
|
return briefing.PreparedIdentity{}, module.Snapshot{}, facts.DerivedFacts{}, err
|
|
}
|
|
derived, err := clonePreparedValue(p.derived)
|
|
if err != nil {
|
|
return briefing.PreparedIdentity{}, module.Snapshot{}, facts.DerivedFacts{}, err
|
|
}
|
|
return identity, snapshot, derived, 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
|
|
}
|