"""Analytics tool registry The real registry of the `analyze_*` family, built on the canonical `ToolSpec` (src/tools/contracts.py) and the prompt-style `DESCRIPTION` constants the Planner reads to choose a tool (KM-625). This replaces the agent team's local stub in `src/agents/planner/registry.py` for the analytics slice. Conventions (decided with the agent team, KM-465): - **Pattern A** — `analyze_*` tools do NOT self-fetch by `source_id`. Each takes a `data` argument that is a `"${t}"` placeholder pointing at an upstream `retrieve_data` table output, resolved to a DataFrame at execution time. Column arguments reference the aliases that upstream query produced. - `input_schema` is the lightweight JSON-schema-ish dict the planner validator consumes: `required` (arg names with no default) + `properties` (allowed args). `required` mirrors each compute function's no-default parameters; value-typing of placeholder args is deferred to execution time. - `output_kind` is the `ToolOutput.kind` each tool returns: stats (labelled-metric dict) | table (rows×cols) | series (ordered periods). The four data-access tools (retrieve_data / retrieve_knowledge / check_data / check_knowledge) are registered separately once their wrappers land (KM-465 #4); `default_registry()` composes both slices. """ from __future__ import annotations from src.tools.analytics import ( ab_test, aggregation, anomaly, cluster, data_quality, descriptive, forecast, importance, merge, pareto, relationship, segmentation, temporal, visualization, ) from src.tools.contracts import ToolRegistry, ToolSpec from src.tools.spec_builder import spec_from_model # Active this round — the analytics (+ render_chart, SPINE_V2_PLAN §4.1) tools # the Planner may select. ACTIVE_ANALYTICS_TOOLS: list[ToolSpec] = [ spec_from_model( name="analyze_descriptive", category="analytics.descriptive", output_kind="stats", description=descriptive.DESCRIPTION, input_model=descriptive.DescriptiveInput, phase="P1", ), spec_from_model( name="analyze_aggregate", category="analytics.aggregation", output_kind="table", description=aggregation.DESCRIPTION, input_model=aggregation.AggregateInput, phase="P1", ), spec_from_model( name="analyze_correlation", category="analytics.relationship", output_kind="stats", description=relationship.DESCRIPTION, input_model=relationship.CorrelationInput, phase="P1", ), spec_from_model( name="analyze_trend", category="analytics.timeseries", output_kind="series", description=temporal.DESCRIPTION, input_model=temporal.TrendInput, phase="P1", ), spec_from_model( name="analyze_merge", category="analytics.combine", output_kind="table", description=merge.DESCRIPTION, input_model=merge.MergeInput, phase="P1", ), spec_from_model( name="render_chart", category="analytics.visualization", output_kind="chart", description=visualization.DESCRIPTION, input_model=visualization.RenderChartInput, phase="P1", ), # --- Activated 2026-09-17 (A15): the 8 new/renamed tools, moved from DEFERRED to # ACTIVE. All-active + grouping (A17) decision — grouping keeps the planner accurate # at 18 tools. NOTE: their planner few-shots (examples.py, A18 — Rifqi) land in sync. # Renamed from analyze_comparison (A11): absorbs the significance test (Welch t-test). spec_from_model( name="analyze_ab_test", category="analytics.comparison", output_kind="stats", description=ab_test.DESCRIPTION, input_model=ab_test.AbTestInput, phase="P1", ), # Renamed from analyze_contribution (A12): reframed to Pareto 80/20 + auto-chart. # Spec derived from its pydantic InputModel via spec_builder. spec_from_model( name="analyze_pareto", category="analytics.pareto", output_kind="table", description=pareto.DESCRIPTION, input_model=pareto.ParetoInput, phase="P1", ), # Renamed from analyze_profile (A14): activate + missing-rate bar chart. # Spec derived from its pydantic InputModel via spec_builder. spec_from_model( name="analyze_data_quality", category="analytics.quality", output_kind="stats", description=data_quality.DESCRIPTION, input_model=data_quality.DataQualityInput, phase="P1", ), # Activated + renamed from analyze_segment (A13): + bar chart, spec from InputModel. spec_from_model( name="analyze_segmentation", category="analytics.segmentation", output_kind="table", description=segmentation.DESCRIPTION, input_model=segmentation.SegmentationInput, phase="P1", ), # First ML tool (§0.9). Spec derived from its pydantic InputModel via # spec_builder, not hand-written. DEFERRED until the demo scenario locks P1 and # its planner few-shot lands (examples.py) — see the ACTIVE/DEFERRED note above. spec_from_model( name="analyze_forecast", category="analytics.timeseries", output_kind="series", description=forecast.DESCRIPTION, input_model=forecast.ForecastInput, phase="P1", ), # Second ML tool (§0.9). Same spec_builder + DEFERRED pattern as analyze_forecast. spec_from_model( name="analyze_anomaly", category="analytics.anomaly", output_kind="stats", description=anomaly.DESCRIPTION, input_model=anomaly.AnomalyInput, phase="P1", ), # A8 + A10 (Rifqi wrote the tool files; Sofhia registers per the ownership split). spec_from_model( name="analyze_importance", category="analytics.relationship", output_kind="stats", description=importance.DESCRIPTION, input_model=importance.ImportanceInput, phase="P1", ), spec_from_model( name="analyze_cluster", category="analytics.clustering", output_kind="stats", description=cluster.DESCRIPTION, input_model=cluster.ClusterInput, phase="P1", ), ] # All analytical tools are ACTIVE this round (A15, 2026-09-17) — nothing deferred. # The mechanism is kept so a future tool can still ship dark (move a spec here). DEFERRED_ANALYTICS_TOOLS: list[ToolSpec] = [] # Full set (active + deferred) — kept for callers that need every spec, e.g. tests # or the invoker's name checks. The Planner-visible registry uses ACTIVE only. ANALYTICS_TOOLS: list[ToolSpec] = [*ACTIVE_ANALYTICS_TOOLS, *DEFERRED_ANALYTICS_TOOLS] def analytics_registry() -> ToolRegistry: """The analytics (`analyze_*`) slice of the tool registry (fresh instance). Exposes only `ACTIVE_ANALYTICS_TOOLS`; deferred specs are withheld from the Planner (see `DEFERRED_ANALYTICS_TOOLS`). """ return ToolRegistry(tools=list(ACTIVE_ANALYTICS_TOOLS))