Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/astrxnomo/financial-analytics-agent/llms.txt

Use this file to discover all available pages before exploring further.

Every analytic in the Financial Analytics Agent follows a seven-step pattern: types in finance.types.ts, a SQL function in finance.ts, an agent tool in agent/tools/, a REST route in app/api/finance/, a chart component in app/_components/tool-result/, registration in tool-result/index.tsx, and a tool description added to agent/instructions.md. The SQL logic lives once — both the agent tool and the REST route call the same shared library function, so there is no duplication and the agent works standalone under eve dev --no-ui with no Next.js process running.
1

Define types in agent/lib/finance.types.ts

Add input and output interfaces for your new analytic. Keep output types as plain objects with number fields — never expose raw Postgres numeric strings to tools or charts.
agent/lib/finance.types.ts
// Input for the new analytic
export interface ExampleInput {
  from: string;   // YYYY-MM-DD
  to:   string;   // YYYY-MM-DD
}

// One row of output
export interface ExampleRow {
  period:     string;   // YYYY-MM-DD (first of month)
  department: string;
  value:      number;   // always a JS number, never a raw Postgres numeric string
}
If your analytic shares concepts with existing types (such as Metric or GroupBy), import and reuse them rather than redefining them.
2

Write the SQL function in agent/lib/finance.ts

Export an async function that calls db() and maps rows to your output type. Use the num() helper (const num = (v: unknown) => Number(v)) to convert Postgres numeric columns to JS number at the lib boundary, and use the day() helper to normalize Date objects to YYYY-MM-DD strings.
agent/lib/finance.ts
import { db } from "./db";
import type { ExampleRow } from "./finance.types";

const num = (v: unknown) => Number(v);
const day = (v: unknown) => (v instanceof Date ? v.toISOString().slice(0, 10) : String(v));

export async function getExample(input: {
  from: string;
  to: string;
}): Promise<ExampleRow[]> {
  const sql = db();
  const rows = await sql<{ period: Date; department: string; value: string }[]>`
    SELECT date_trunc('month', t.date)::date AS period,
           d.name AS department,
           COALESCE(SUM(t.amount), 0) AS value
    FROM transactions t
    JOIN departments d ON d.id = t.department_id
    WHERE t.date >= ${input.from} AND t.date <= ${input.to}
    GROUP BY period, d.name
    ORDER BY period, d.name
  `;
  return rows.map((r) => ({
    period:     day(r.period),
    department: r.department,
    value:      num(r.value),
  }));
}
Compute num() and day() at the return boundary — never pass raw numeric strings or Date objects up to callers.
3

Create the agent tool in agent/tools/<name>.ts

Create a thin defineTool wrapper with a Zod inputSchema. The tool’s execute method should call your finance function directly and return its output verbatim — no transformation, no extra logic.
agent/tools/get_example.ts
import { defineTool } from "eve/tools";
import { z } from "zod";
import { getExample } from "#lib/finance.js";

export default defineTool({
  description: "Describe when the agent should call this tool.",
  inputSchema: z.object({
    from: z.string().describe("Start date, YYYY-MM-DD"),
    to:   z.string().describe("End date, YYYY-MM-DD"),
  }),
  async execute(input) {
    return await getExample(input);
  },
});
Note the #lib/finance.js import alias (defined in package.json’s imports field) — use this form inside agent/ rather than a relative path.
4

Add the REST route in app/api/finance/<name-kebab>/route.ts

Create a Next.js route handler that parses and validates query string parameters using the parseQuery helper from agent/lib/api-route.ts, then delegates to your finance function. Each route is a thin two-liner on top of the shared lib.
app/api/finance/example/route.ts
import { z } from "zod";
import { parseQuery } from "@/agent/lib/api-route";
import { getExample } from "@/agent/lib/finance";

const Q = z.object({ from: z.string(), to: z.string() });

export async function GET(req: Request) {
  const { data, error } = parseQuery(Q, req);
  if (error) return error;
  return Response.json(await getExample(data));
}
The parseQuery helper in agent/lib/api-route.ts handles all the boilerplate of parsing and validating query string params against a Zod schema — every REST route is a thin two-liner on top of it. If validation fails, parseQuery returns a ready-to-return 400 Response with the Zod error details serialized as JSON; otherwise it returns the typed data object ready to pass directly into your finance function.
5

Create the chart component in app/_components/tool-result/<name>.tsx

Create a React component that accepts the tool result JSON and renders a Recharts chart. Use the shared Panel, ChartHeader, and ChartPanel primitives from panel.tsx, the ChartTooltip from chart-tooltip.tsx, and the palette and formatters from app/_components/charts.ts (fmtMoney, fmtDate, the CSS custom property colors, etc.).
app/_components/tool-result/example-chart.tsx
"use client";

import type { ExampleRow } from "@/agent/lib/finance.types";
import { ChartPanel, ChartHeader, EmptyState } from "./panel";
import { fmtMoney } from "@/app/_components/charts";

interface Props {
  data: ExampleRow[];
}

export function ExampleChart({ data }: Props) {
  if (!data.length) return <EmptyState />;
  return (
    <ChartPanel>
      <ChartHeader title="Example Analytic" />
      {/* Recharts chart using data */}
    </ChartPanel>
  );
}
Handle edge cases gracefully: a single-period result should render a bar chart rather than a one-dot line, and an empty result should render <EmptyState />.
6

Register in app/_components/tool-result/index.tsx

Add the tool name to the TOOL_NAMES set and add a case to the ToolResult switch statement. This is the only file that anything outside the tool-result/ folder imports from.
app/_components/tool-result/index.tsx
// Add to the TOOL_NAMES set:
export const TOOL_NAMES = new Set([
  // ... existing tool names ...
  "get_example",
]);

// Add a case to the ToolResult switch:
case "get_example":
  return <ExampleChart data={result as ExampleRow[]} />;
The isFinanceTool helper and the ToolResultSkeleton are already exported from this file and will automatically cover your new tool once it is added to TOOL_NAMES.
7

Update agent/instructions.md

Add your new tool to the tool list in agent/instructions.md with a clear description of when the agent should call it. Deliberately write the description in terms of user intent, not SQL mechanics — the model uses this to decide which tool fits the question.
- **get_example** — use when the user asks about [describe the question type].
  Requires `from` and `to` dates in YYYY-MM-DD format.
Keep the instructions free of hardcoded dates. If the tool has session-dependent defaults (such as defaulting to the current month), add that logic to agent/instructions/dates.ts as an extension of the existing defineDynamic resolver rather than writing a literal date into instructions.md.

Verify

After adding your analytic, run the following to confirm everything compiles and the existing shared lib tests still pass:
pnpm typecheck
pnpm test
pnpm typecheck catches any type mismatches between your new interfaces, the Zod schema, and the tool’s execute return type. pnpm test runs vitest run against the full unit and integration suite — the integration tests in agent/lib/finance.test.ts require DATABASE_URL in .env.local and will validate that your new SQL function returns well-formed data against the seeded database. If you want to verify the agent picks up and calls the new tool correctly, run:
pnpm exec eve dev --no-ui
This starts the agent in headless mode so you can send messages and inspect tool call outputs without needing the full Next.js dev server.

Build docs developers (and LLMs) love