feat: Profile health score system (scoring engine, API, frontend)

Health scoring engine with 4 dimensions: Gateway stability (40%), config
completeness (25%), skill consistency (20%), and Cron success rate (15%).
Grades A(80-100) B(60-79) C(40-59) D(0-39) with detailed deduction items
and fix suggestions per dimension.

API endpoints:
  - GET /health/scores — list all profiles with grade filter + sort
  - GET /health/scores/{profile} — detail with deductions + suggestions
  - POST /health/compare — multi-profile config/skill comparison
  - POST /health/calculate — manual recalculation (owner only)

Frontend HealthPage: grade distribution cards, score table with expandable
detail rows, batch compare view with diff highlighting, grade/select/sort
filters, and owner-only recalculation.

Data sources use placeholders (TODO: hermes CLI / file reads) but the full
scoring logic is implemented end-to-end.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
enzo
2026-07-22 17:14:30 +08:00
co-authored by Claude
parent 6d4a5c955b
commit 8bfefd199c
5 changed files with 893 additions and 3 deletions
+69
View File
@@ -0,0 +1,69 @@
import client from './client'
/* ── types ── */
export interface ScoreRow {
rank: number
name: string
total_score: number
grade: string
gateway_score: number
config_score: number
skill_score: number
cron_score: number
deduct_summary: string[]
}
export interface ScoreListResponse {
total: number
distribution: { A: number; B: number; C: number; D: number }
scores: ScoreRow[]
}
export interface DimensionDetail {
name: string
weight: string
score: number
max_score: number
deductions: string[]
suggestions: string[]
}
export interface ProfileScoreDetail {
profile_name: string
total_score: number
grade: string
calculated_at: string
dimensions: DimensionDetail[]
}
export interface CompareRow {
field: string
category: string
has_diff: boolean
[profileName: string]: unknown
}
export interface CompareResponse {
profiles: string[]
rows: CompareRow[]
}
export interface CalculateResponse {
message: string
count: number
}
/* ── API functions ── */
export const getHealthScores = (params?: { grade?: string; sort?: string }) =>
client.get<ScoreListResponse>('/health/scores', { params })
export const getProfileScore = (profile: string) =>
client.get<ProfileScoreDetail>(`/health/scores/${encodeURIComponent(profile)}`)
export const compareProfiles = (profile_names: string[]) =>
client.post<CompareResponse>('/health/compare', { profile_names })
export const recalculateScores = () =>
client.post<CalculateResponse>('/health/calculate')