NAME

Database::BI::Ask - Natural language to analysis spec

SYNOPSIS

use Database::BI::Ask;

my $typed = Database::BI::Ask::infer_column_types(\@records, \@columns);

my $result = Database::BI::Ask::ask(
    question  => 'show me sales by region as a pie chart',
    spec_key  => 'table:sales',
    label     => 'sales',
    columns   => $typed,
    ai_config => { provider => 'anthropic', api_key => $key, model => 'claude-haiku-5-5' },
);
# $result = { spec => {...}, url => '/pie?l=...&cat=region', valid => 1 }

DESCRIPTION

Translates a plain-English question into an analysis spec by calling the Anthropic Messages API. The model receives only column names and types -- never raw row data, file paths, or credentials.

The returned spec is validated before being returned to the caller: every column name in the encode section must appear in the supplied column list, and the kind must be a supported chart type. An invalid spec is returned as { error => 'ask_invalid_spec:...' } and never executed.

The feature is entirely absent (returns ask_unavailable) when no api_key is supplied, keeping the app functional with no AI configured.

FUNCTIONS

ask(%args)

Returns { spec, url, valid => 1 } on success, or { error => 'code' } on failure. Error codes: ask_no_question, ask_unavailable, ask_provider_error, ask_unparseable, ask_invalid_spec:col.

API SPECIFICATION

INPUT

{
    question  => 'string',     # required; empty returns ask_no_question
    spec_key  => 'string',     # l= spec the URL is built on, e.g. "table:sales"
    label     => 'string',     # optional; dataset name shown to the model (default "dataset")
    columns   => 'arrayref',   # [ { name, type } ... ], as infer_column_types returns
    ai_config => 'hashref',    # { api_key, model, provider }; no api_key returns ask_unavailable
}

A flat key/value list. ask does not croak: every failure, including no arguments, comes back as an error code.

OUTPUT

{ spec => 'hashref', url => 'string', valid => 1 }   # success
{ error => 'string' }                              # one of the codes above

The model sees only the question, the label and columns. No row data, file path or spec_key is sent.

infer_column_types($records, $columns)

Infers each column's type ('number', 'date', 'string') from a sample of records. A column is 'number' when more than half its non-empty values pass Scalar::Util::looks_like_number. It is 'date' when more than half match YYYY-MM-DD or M/D/YYYY. Otherwise 'string'.

API SPECIFICATION

INPUT

$records   # arrayref of hashrefs (rows)
$columns   # arrayref of column names to classify

OUTPUT

[ { name => 'string', type => 'number' | 'date' | 'string' }, ... ]

One entry per name in $columns, in that order. Either argument not an array reference returns []. A column with no non-empty values is 'string'.

AUTHOR

Nigel Horne <njh@nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.