File size: 4,742 Bytes
9c1c0ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from __future__ import annotations

import json
from typing import Any

from datapilot.config import Settings
from datapilot.schemas import Evidence


def deterministic_insights(state: dict[str, Any]) -> tuple[list[str], list[str]]:
    profile = state["profile"]
    best = state["model_bundle"].results[0]
    quality = state["quality_issues"]
    explainability = state["explainability"]
    important = list(explainability.feature_importance)[:3]
    summary = [
        (
            f"The analysis used {profile.rows:,} rows and {profile.columns:,} columns for a "
            f"{profile.task_type.value} task targeting '{profile.target}'."
        ),
        (
            f"{best.name} ranked first with training CV {best.primary_metric} {best.primary_score:.3f}; "
            f"its one-time test score was {best.final_test_score:.3f}."
        ),
        (
            f"{len(quality)} data-quality observations were recorded; "
            f"{sum(issue.severity.value == 'critical' for issue in quality)} are critical."
        ),
    ]
    if important:
        summary.append(
            f"The strongest predictive signals were {', '.join(important)} "
            f"according to {explainability.method.lower()}."
        )
    recommendations = [
        "Validate performance on fresh, out-of-time data before production deployment.",
        "Review suspected leakage and identifier columns with a domain owner.",
        "Monitor input drift and the primary metric after deployment.",
    ]
    if profile.missing_rate > 0.1:
        recommendations.insert(0, "Investigate upstream causes of missing data before retraining.")
    return summary, recommendations


def optional_llm_narrative(

    state: dict[str, Any], evidence: list[Evidence], settings: Settings

) -> list[str] | None:
    """Generate narrative only from bounded evidence; calculations remain deterministic."""
    if not settings.gemini_api_key:
        return None
    try:
        from google import genai

        client = genai.Client(api_key=settings.gemini_api_key)
        payload = {
            "profile": state["profile"].model_dump(),
            "best_model": state["model_bundle"].results[0].model_dump(),
            "critic": state["critic"].model_dump(),
            "evidence": [item.model_dump() for item in evidence[:25]],
        }
        prompt = (
            "You are a senior data scientist. Return exactly four concise markdown bullet points. "
            "Use only the JSON evidence below. Cite supporting evidence IDs in square brackets. "
            "Do not add numbers, causal claims, or facts absent from the payload.\n"
            + json.dumps(payload, default=str)
        )
        response = client.models.generate_content(model=settings.gemini_model, contents=prompt)
        lines = [line.strip("- ").strip() for line in response.text.splitlines() if line.strip()]
        return lines[:4] or None
    except Exception:
        return None


def answer_follow_up(run: dict[str, Any], question: str) -> str:
    lowered = question.lower()
    if any(token in lowered for token in {"best model", "which model", "winner"}):
        top = run["model_results"][0]
        return (
            f"The best model was **{top['name']}**, with {top['primary_metric']} "
            f"training-CV **{top['selection_score']:.3f}** and one-time test "
            f"**{top['final_test_score']:.3f}**."
        )
    if any(token in lowered for token in {"feature", "important", "driver"}):
        importance = run["explainability"]["feature_importance"]
        top = list(importance.items())[:5]
        return (
            "Top predictive features: "
            + ", ".join(f"**{name}** ({value:.4f})" for name, value in top)
            + ". These are associations, not causal effects."
        )
    if any(token in lowered for token in {"quality", "missing", "leak", "risk"}):
        issues = run["quality_issues"]
        if not issues:
            return "No material quality flags were detected by the configured checks."
        return "Quality observations: " + "; ".join(item["message"] for item in issues[:6])
    if any(token in lowered for token in {"metric", "performance", "score"}):
        top = run["model_results"][0]
        formatted = ", ".join(
            f"{key}={value:.3f}" for key, value in top["final_test_metrics"].items()
        )
        return f"Selected-model one-time test metrics: {formatted}."
    return (
        "I can answer evidence-backed questions about the best model, performance metrics, "
        "data quality, leakage risk, and feature importance for this run."
    )