Files
weatherreporter/internal/generatedtext/catalog.go

205 lines
7.2 KiB
Go

package generatedtext
import (
"fmt"
"gitea.maximumdirect.net/eric/weatherreporter/internal/briefing"
"gitea.maximumdirect.net/eric/weatherreporter/internal/facts"
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
"gitea.maximumdirect.net/eric/weatherreporter/internal/promptassets"
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
"gitea.maximumdirect.net/eric/weatherreporter/internal/reporttemplate"
)
const (
schemaIDHourly = "hourly"
schemaIDDaily = "daily"
schemaIDToday = "today"
schemaIDTomorrow = "tomorrow"
templateIDHourly = "hourly"
templateIDDaily = "daily"
templateIDToday = "today"
templateIDTomorrow = "tomorrow"
)
type validator func([]byte) (any, []byte, error)
type renderContextBuilder func(report.ID, string, briefing.PreparedIdentity, module.Snapshot, facts.CollectedFacts, facts.DerivedFacts, any) (any, error)
type catalogEntry struct {
reportID report.ID
schemaID string
templateID string
validate validator
renderContextBuilder renderContextBuilder
}
type Handler struct {
reportID report.ID
schemaID string
templateID string
validate validator
renderContextBuilder renderContextBuilder
}
var catalog = []catalogEntry{
{
reportID: report.Hourly,
schemaID: schemaIDHourly,
templateID: templateIDHourly,
validate: validateHourly,
renderContextBuilder: buildHourlyContext,
},
{
reportID: report.Daily,
schemaID: schemaIDDaily,
templateID: templateIDDaily,
validate: validateDaily,
renderContextBuilder: buildDailyContext,
},
{
reportID: report.Today,
schemaID: schemaIDToday,
templateID: templateIDToday,
validate: validateToday,
renderContextBuilder: buildTodayContext,
},
{
reportID: report.Tomorrow,
schemaID: schemaIDTomorrow,
templateID: templateIDTomorrow,
validate: validateTomorrow,
renderContextBuilder: buildTomorrowContext,
},
}
func LookupDefinition(definition report.Definition) (Handler, error) {
var schemaKnown, templateKnown, pairKnown bool
for _, entry := range catalog {
if entry.schemaID == definition.GeneratedTextSchemaID {
schemaKnown = true
}
if entry.templateID == definition.TemplateID {
templateKnown = true
}
if entry.schemaID == definition.GeneratedTextSchemaID && entry.templateID == definition.TemplateID {
pairKnown = true
if entry.reportID != definition.ID {
continue
}
return Handler{
reportID: entry.reportID,
schemaID: entry.schemaID,
templateID: entry.templateID,
validate: entry.validate,
renderContextBuilder: entry.renderContextBuilder,
}, nil
}
}
if !schemaKnown {
return Handler{}, fmt.Errorf("generated text schema %q is not supported for report %q", definition.GeneratedTextSchemaID, definition.ID)
}
if !templateKnown {
return Handler{}, fmt.Errorf("report template %q is not supported for report %q", definition.TemplateID, definition.ID)
}
if pairKnown {
return Handler{}, fmt.Errorf("generated text schema %q and report template %q do not belong to report %q", definition.GeneratedTextSchemaID, definition.TemplateID, definition.ID)
}
return Handler{}, fmt.Errorf("generated text schema %q and report template %q are not supported together for report %q", definition.GeneratedTextSchemaID, definition.TemplateID, definition.ID)
}
func (h Handler) SchemaID() string {
return h.schemaID
}
func (h Handler) TemplateID() string {
return h.templateID
}
func (h Handler) Schema() ([]byte, error) {
data, err := promptassets.Schema(h.schemaID)
if err != nil {
return nil, fmt.Errorf("load generated text schema %q for report %q: %w", h.schemaID, h.reportID, err)
}
return data, nil
}
func (h Handler) Template() (string, error) {
source, err := reporttemplate.Template(h.templateID)
if err != nil {
return "", fmt.Errorf("load report template %q for report %q: %w", h.templateID, h.reportID, err)
}
return source, nil
}
func (h Handler) Validate(data []byte) (any, []byte, error) {
if h.validate == nil {
return nil, nil, fmt.Errorf("generated text validator is not registered for schema %q on report %q", h.schemaID, h.reportID)
}
return h.validate(data)
}
func (h Handler) BuildRenderContext(identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
if h.renderContextBuilder == nil {
return nil, fmt.Errorf("render-context builder is not registered for template %q on report %q", h.templateID, h.reportID)
}
return h.renderContextBuilder(h.reportID, h.templateID, identity, snapshot, collected, derived, generated)
}
func (h Handler) Render(data any) ([]byte, error) {
rendered, err := reporttemplate.Render(h.templateID, data)
if err != nil {
return nil, fmt.Errorf("render report template %q for report %q: %w", h.templateID, h.reportID, err)
}
return rendered, nil
}
func validateHourly(data []byte) (any, []byte, error) {
return ValidateHourly(data)
}
func validateDaily(data []byte) (any, []byte, error) {
return ValidateDaily(data)
}
func validateToday(data []byte) (any, []byte, error) {
return ValidateToday(data)
}
func validateTomorrow(data []byte) (any, []byte, error) {
return ValidateTomorrow(data)
}
func buildHourlyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
hourly, ok := generated.(Hourly)
if !ok {
return nil, fmt.Errorf("report template %q requires hourly generated text for report %q", templateID, reportID)
}
return BuildHourlyRenderContext(identity, snapshot, hourly, collected, derived)
}
func buildDailyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
daily, ok := generated.(Daily)
if !ok {
return nil, fmt.Errorf("report template %q requires daily generated text for report %q", templateID, reportID)
}
return BuildDailyRenderContext(identity, snapshot, daily, collected, derived)
}
func buildTodayContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
today, ok := generated.(Today)
if !ok {
return nil, fmt.Errorf("report template %q requires today generated text for report %q", templateID, reportID)
}
return BuildTodayRenderContext(identity, snapshot, today, collected, derived)
}
func buildTomorrowContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, collected facts.CollectedFacts, derived facts.DerivedFacts, generated any) (any, error) {
tomorrow, ok := generated.(Tomorrow)
if !ok {
return nil, fmt.Errorf("report template %q requires tomorrow generated text for report %q", templateID, reportID)
}
return BuildTomorrowRenderContext(identity, snapshot, tomorrow, collected, derived)
}