Skip to content

Commit ee1f67c

Browse files
committed
feat: add explainable guidance generation based on numeric behavior analysis
on the Insight Generation Layer Fixes #9
1 parent 6345b19 commit ee1f67c

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

client/features/insights/index.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Heatmap from '../../components/Heatmap';
55
export default function Insights() {
66
const [matrix, setMatrix] = useState<number[][]>([]);
77
const [weeklyRecs, setWeeklyRecs] = useState<any[]>([]);
8+
const [guidance, setGuidance] = useState<any[]>([]);
89

910
useEffect(() => {
1011
// build a small demo heatmap (7 days x 24 hours) or try fetch from server
@@ -13,6 +14,7 @@ export default function Insights() {
1314
const res = await fetch('/api/insights/simple');
1415
const payload = await res.json();
1516
if (Array.isArray(payload.weeklyRecommendations)) setWeeklyRecs(payload.weeklyRecommendations);
17+
if (Array.isArray(payload.explainableGuidance)) setGuidance(payload.explainableGuidance);
1618
// payload may include logs: try to build hourly counts over last 7 days
1719
const logs = Array.isArray(payload.logs) ? payload.logs : Array.isArray(payload) ? payload : [];
1820
const now = new Date();
@@ -49,7 +51,19 @@ export default function Insights() {
4951
<Heatmap matrix={matrix} xLabels={Array.from({ length: 24 }).map((_,i)=>String(i))} yLabels={['6d','5d','4d','3d','2d','1d','today']} title="Hourly activity (recent days)" />
5052
</div>
5153
<div style={{ marginTop: 24 }}>
52-
<h3>Weekly Recommendations</h3>
54+
<h3>Explainable Guidance</h3>
55+
{guidance.length === 0 ? <p>No guidance available.</p> : (
56+
<ul>
57+
{guidance.map((g, idx) => (
58+
<li key={`g-${idx}`} style={{ marginBottom: 8 }}>
59+
<div><strong>{g.message}</strong></div>
60+
{g.recommendation ? <div style={{ fontSize: 13 }}>{g.recommendation}</div> : null}
61+
</li>
62+
))}
63+
</ul>
64+
)}
65+
66+
<h3 style={{ marginTop: 18 }}>Weekly Recommendations</h3>
5367
{weeklyRecs.length === 0 ? <p>No recommendations yet.</p> : (
5468
<ul>
5569
{weeklyRecs.map((w, idx) => (

internal/intelligence/insightGenerator.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,55 @@ export function generateInsightsFromLogs(logs: any[]) {
118118
}
119119
}
120120

121+
// Generic numeric-behavior guidance for other numeric logs
122+
const looksLikeNumeric = values.length > 0 && values.every((v) => typeof v === 'number' && !Number.isNaN(v));
123+
if (!looksLikeEnergy && looksLikeNumeric) {
124+
try {
125+
// time-of-day buckets
126+
const buckets: { label: string; start: number; end: number; count: number; values: number[] }[] = [
127+
{ label: 'Night', start: 0, end: 6, count: 0, values: [] },
128+
{ label: 'Morning', start: 6, end: 12, count: 0, values: [] },
129+
{ label: 'Afternoon', start: 12, end: 17, count: 0, values: [] },
130+
{ label: 'Evening', start: 17, end: 21, count: 0, values: [] },
131+
{ label: 'Late', start: 21, end: 24, count: 0, values: [] },
132+
];
133+
points.forEach((p) => {
134+
const h = p.date.getHours();
135+
const b = buckets.find((bk) => h >= bk.start && h < bk.end) || buckets[0];
136+
b.count += 1;
137+
b.values.push(p.value);
138+
});
139+
const total = values.length;
140+
const top = buckets.slice().sort((a, b) => b.count - a.count)[0];
141+
const behaviorLabel = (logs[0] && (logs[0].behaviorType || logs[0].type || logs[0].name)) || 'this behavior';
142+
const guidance: Array<{ message: string; recommendation?: string }> = [];
143+
if (top && top.count / total >= 0.55) {
144+
const pct = Math.round((top.count / total) * 100);
145+
guidance.push({
146+
message: `You report ${behaviorLabel} ${pct}% of the time ${top.label.toLowerCase()}.`,
147+
recommendation: `If possible, schedule or expect ${behaviorLabel} ${top.label.toLowerCase()}.`,
148+
});
149+
}
150+
151+
// Average-based guidance
152+
if (avg !== null) {
153+
if (avg >= 8) {
154+
guidance.push({ message: `Your average ${behaviorLabel} is high (${avg.toFixed(1)}).`, recommendation: 'Consider whether this reflects a sustained pattern and adjust planning accordingly.' });
155+
} else if (avg <= 3) {
156+
guidance.push({ message: `Your average ${behaviorLabel} is low (${avg.toFixed(1)}).`, recommendation: 'Consider small, consistent actions to raise this metric if desired.' });
157+
}
158+
}
159+
160+
if (trend === 'upward' || trend === 'downward') {
161+
guidance.push({ message: `Your recent ${behaviorLabel} trend is ${trend}.`, recommendation: trend === 'upward' ? 'Leverage this momentum.' : 'Consider interventions to reverse the decline.' });
162+
}
163+
164+
if (guidance.length) report.explainableGuidance = (report.explainableGuidance || []).concat(guidance);
165+
} catch (e) {
166+
// ignore
167+
}
168+
}
169+
121170
return report;
122171
}
123172

0 commit comments

Comments
 (0)