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.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, 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, 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, _ 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) } func buildDailyContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, 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, derived) } func buildTodayContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, 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, derived) } func buildTomorrowContext(reportID report.ID, templateID string, identity briefing.PreparedIdentity, snapshot module.Snapshot, 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, derived) }