feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command
Add /ultraresearch-local for structured research combining local codebase analysis with external knowledge via parallel agent swarms. Produces research briefs with triangulation, confidence ratings, and source quality assessment. New command: /ultraresearch-local with modes --quick, --local, --external, --fg. New agents: research-orchestrator (opus), docs-researcher, community-researcher, security-researcher, contrarian-researcher, gemini-bridge (all sonnet). New template: research-brief-template.md. Integration: --research flag in /ultraplan-local accepts pre-built research briefs (up to 3), enriches the interview and exploration phases. Planning orchestrator cross-references brief findings during synthesis. Design principle: Context Engineering — right information to right agent at right time. Research briefs are structured artifacts in the pipeline: ultraresearch → brief → ultraplan --research → plan → ultraexecute. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
4dc868ea62
151 changed files with 34374 additions and 0 deletions
85
scripts/analytics/src/reports/heatmap.ts
Normal file
85
scripts/analytics/src/reports/heatmap.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import type { PostAnalytics, DayOfWeekMetrics, HeatmapReport } from "../models/types.js";
|
||||
|
||||
const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
|
||||
// Convert JS getDay() (0=Sun) to ISO weekday (1=Mon, 7=Sun)
|
||||
function toISOWeekday(jsDay: number): number {
|
||||
return jsDay === 0 ? 7 : jsDay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a day-of-week performance heatmap from post analytics data.
|
||||
* Groups posts by day of week and calculates average metrics per day.
|
||||
*/
|
||||
export function generateHeatmap(posts: PostAnalytics[]): HeatmapReport {
|
||||
// Initialize buckets for all 7 days (ISO: 1=Mon to 7=Sun)
|
||||
const buckets: Map<number, PostAnalytics[]> = new Map();
|
||||
for (let i = 1; i <= 7; i++) {
|
||||
buckets.set(i, []);
|
||||
}
|
||||
|
||||
// Group posts by ISO weekday
|
||||
for (const post of posts) {
|
||||
const jsDay = new Date(post.publishedDate).getUTCDay();
|
||||
const isoDay = toISOWeekday(jsDay);
|
||||
buckets.get(isoDay)!.push(post);
|
||||
}
|
||||
|
||||
// Build metrics per day
|
||||
const byDayOfWeek: DayOfWeekMetrics[] = [];
|
||||
for (let isoDay = 1; isoDay <= 7; isoDay++) {
|
||||
const dayPosts = buckets.get(isoDay)!;
|
||||
const jsDay = isoDay === 7 ? 0 : isoDay;
|
||||
const dayName = DAY_NAMES[jsDay];
|
||||
|
||||
if (dayPosts.length === 0) {
|
||||
byDayOfWeek.push({
|
||||
dayName,
|
||||
dayIndex: isoDay,
|
||||
postCount: 0,
|
||||
avgImpressions: 0,
|
||||
avgEngagementRate: 0,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const totalImpressions = dayPosts.reduce((sum, p) => sum + p.metrics.impressions, 0);
|
||||
const totalEngagement = dayPosts.reduce((sum, p) => sum + p.metrics.engagementRate, 0);
|
||||
const bestPost = dayPosts.reduce((best, p) =>
|
||||
p.metrics.impressions > best.metrics.impressions ? p : best
|
||||
);
|
||||
|
||||
byDayOfWeek.push({
|
||||
dayName,
|
||||
dayIndex: isoDay,
|
||||
postCount: dayPosts.length,
|
||||
avgImpressions: Math.round(totalImpressions / dayPosts.length),
|
||||
avgEngagementRate: parseFloat((totalEngagement / dayPosts.length).toFixed(1)),
|
||||
bestPost,
|
||||
});
|
||||
}
|
||||
|
||||
// Find best days
|
||||
const daysWithPosts = byDayOfWeek.filter(d => d.postCount > 0);
|
||||
const bestDayImpressions = daysWithPosts.length > 0
|
||||
? daysWithPosts.reduce((best, d) => d.avgImpressions > best.avgImpressions ? d : best).dayName
|
||||
: "N/A";
|
||||
const bestDayEngagement = daysWithPosts.length > 0
|
||||
? daysWithPosts.reduce((best, d) => d.avgEngagementRate > best.avgEngagementRate ? d : best).dayName
|
||||
: "N/A";
|
||||
|
||||
// Date range
|
||||
const sortedDates = posts.map(p => p.publishedDate).sort();
|
||||
const dateRange = posts.length > 0
|
||||
? { from: sortedDates[0], to: sortedDates[sortedDates.length - 1] }
|
||||
: { from: "", to: "" };
|
||||
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
postsAnalyzed: posts.length,
|
||||
dateRange,
|
||||
byDayOfWeek,
|
||||
bestDayImpressions,
|
||||
bestDayEngagement,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue