NAME

Database::BI::Summary - Narrative summary generation for an analysis result

SYNOPSIS

use Database::BI::Summary;

my $result = Database::BI::Summary::generate(
    insights  => $insights_aref,   # from Database::BI::Insights::compute()
    label     => 'sales',
    row_count => 39,
);
# $result = { text => "...", source => "template", cached => 0 }

DESCRIPTION

Generates a 2-4 sentence plain-English summary of an analysis result. Two paths are available:

Template path (default, always offline)

Composes deterministic sentences from the insight kinds and headlines. No external service required.

LLM path (opt-in)

Sends only aggregate facts (insight kinds, headlines, and detail strings) to the Anthropic Messages API. Raw row data, file paths, and the spec_key are never included in the LLM prompt. The LLM path falls back silently to the template path if the provider call fails.

Results are cached in a package-level hash keyed by an MD5 fingerprint of the insight content. Cached results are served for up to one hour.

FUNCTIONS

generate(%args)

my $result = Database::BI::Summary::generate(
    insights  => \@insights,
    spec_key  => 'table:sales',
    label     => 'sales',
    row_count => 39,
    use_ai    => 0,
    ai_config => undef,
);

Returns a hashref with keys text, source ('template', 'template_fallback', or 'llm'), and cached (1 if served from cache, 0 if freshly generated).

use_ai is only honoured when ai_config is provided and ai_config->{api_key} is non-empty.

API SPECIFICATION

INPUT

{
    insights  => 'arrayref',  # optional; insight hashrefs from Database::BI::Insights::compute (default [])
    spec_key  => 'string',    # optional; accepted for the caller's bookkeeping, never sent to the LLM
    label     => 'string',    # optional; dataset name (default "dataset")
    row_count => 'integer',   # optional; rows analysed (default 0)
    use_ai    => 'boolean',   # optional; ask for LLM prose
    ai_config => 'hashref',   # optional; { api_key, model, provider }
}

OUTPUT

{
    text   => 'string',                                  # 2-4 sentences
    source => 'template' | 'template_fallback' | 'llm',
    cached => 0 | 1,
}

generate does not croak. A failed provider call gives the template text with source => 'template_fallback'. The same insights, label, row count and use_ai within an hour return the cached text with cached => 1.

AUTHOR

Nigel Horne <njh@nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.