diff --git a/src/lib/analyzer.ts b/src/lib/analyzer.ts index 5ca427b..a1b6969 100644 --- a/src/lib/analyzer.ts +++ b/src/lib/analyzer.ts @@ -1,18 +1,6 @@ -import { CONFIG } from './config'; +import { CONFIG, Pillar } from './config'; import { collectPackageData } from './data/collect'; -import type { Config, PackageData, SignalWeights } from './types'; - -/** - * Properties analyzer result - */ -export interface ScoreResult { - readonly packageName: string; // "aws-cdk" - readonly version: string; // "1.2.3" - readonly totalScore: number; // 85 - readonly pillarScores: Record; // { "popularity": 42 } - readonly signalScores: Record>; // { "popularity": { "weeklyDownloads": 4, "githubStars": 2 } } - readonly signalWeights: Record>; // { "popularity": { "weeklyDownloads": 3, "githubStars": 2 } } -} +import type { ScoreResult, Config, PackageData, SignalWeights, PillarScores } from './types'; export class ConstructAnalyzer { private config: Config; @@ -26,7 +14,7 @@ export class ConstructAnalyzer { const version = packageData.version; const { signalScores, pillarScores, totalScore } = await this.calculateSignalScores(packageData, weights); - const normalizedPillarScores = this.normalizePillarScores(pillarScores, weights); + const normalizedPillarScores = this.normalizePillarScores(pillarScores, weights) as Readonly; const signalWeights = this.getSignalWeights(weights); return { @@ -41,25 +29,25 @@ export class ConstructAnalyzer { private async calculateSignalScores(packageData: PackageData, weights?: SignalWeights) { const signalScores: Record> = {}; - const pillarScores: Record = {}; + const pillarScores = Object.fromEntries( + Object.values(Pillar).map(pillar => [pillar, 0]), + ) as PillarScores; + let totalWeightedSum = 0; let totalWeight = 0; for (const pillar of this.config.pillars) { for (const signal of pillar.signals) { - const rawValue = packageData[signal.name]; - - const level = signal.benchmarks(rawValue); + const level = signal.benchmarks(packageData[signal.name]); const points = this.convertLevelToPoints(level, signal.name); - // Use custom weight if provided, otherwise use default weight const weight = weights?.[signal.name] ?? signal.defaultWeight; - this.updateSignalScore(signalScores, pillar.name, signal.name, level ?? 1); - this.updatePillarScore(pillarScores, pillar.name, points, weight); + (signalScores[pillar.name] ??= {})[signal.name] = level ?? 1; + pillarScores[pillar.name as Pillar] = (pillarScores[pillar.name as Pillar] || 0) + points * weight; - totalWeightedSum += points * signal.defaultWeight; - totalWeight += signal.defaultWeight; + totalWeightedSum += points * weight; + totalWeight += weight; } } @@ -84,24 +72,18 @@ export class ConstructAnalyzer { return (level - 1) * 25; } - private updateSignalScore(signalScores: Record>, pillar: string, signalName: string, starRating: number): void { - (signalScores[pillar] ??= {})[signalName] = starRating; - } - - private updatePillarScore(pillarScores: Record, pillar: string, points: number, weight: number): void { - const weightedScore = points * weight; - pillarScores[pillar] = (pillarScores[pillar] ?? 0) + weightedScore; - } - - private normalizePillarScores(pillarScores: Record, weights?: SignalWeights): Record { - const normalizedScores: Record = {}; + private normalizePillarScores(pillarScores: Record, weights?: SignalWeights): PillarScores { + const normalizedScores = Object.fromEntries( + Object.values(Pillar).map(pillar => [pillar, 0]), + ) as PillarScores; const pillarEntries = Object.entries(pillarScores); for (const [pillar, weightedSum] of pillarEntries) { const totalWeight = this.getTotalWeightForPillar(pillar, weights); const normalizedScore = totalWeight > 0 ? Math.min(100, weightedSum / totalWeight) : 0; - normalizedScores[pillar] = Math.round(normalizedScore); + normalizedScores[pillar as keyof PillarScores] = Math.round(normalizedScore); } + return normalizedScores; } diff --git a/src/lib/config.ts b/src/lib/config.ts index c5a6d3a..1a11004 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,13 +1,22 @@ import { categorizeByChecklist, categorizeHigherIsBetter, categorizeLowerIsBetter } from './scoring'; import type { Config, DocumentationCompleteness, VersionStability, TestsData, ReleaseNotesData } from './types'; +/** + * Pillar enum - single source of truth for pillar names + */ +export enum Pillar { + MAINTENANCE = 'MAINTENANCE', + QUALITY = 'QUALITY', + POPULARITY = 'POPULARITY', +} + /** * Main configuration object with all signals and their benchmarks */ export const CONFIG: Config = { pillars: [ { - name: 'MAINTENANCE', + name: Pillar.MAINTENANCE, description: 'Measures how actively maintained and updated the package is', signals: [ { @@ -39,7 +48,7 @@ export const CONFIG: Config = { ], }, { - name: 'QUALITY', + name: Pillar.QUALITY, description: 'Measures the overall quality and reliability of the package', signals: [ { @@ -92,7 +101,7 @@ export const CONFIG: Config = { ], }, { - name: 'POPULARITY', + name: Pillar.POPULARITY, description: 'Measures how widely adopted and used the package is', signals: [ { diff --git a/src/lib/types.ts b/src/lib/types.ts index a391959..7cc3aa3 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -2,6 +2,20 @@ * Configuration type definitions for the CDK Construct Analyzer */ +import type { Pillar } from './config'; + +/** + * Properties analyzer result + */ +export interface ScoreResult { + readonly packageName: string; // "aws-cdk" + readonly version: string; // "1.2.3" + readonly totalScore: number; // 85 + readonly pillarScores: Readonly; // { "MAINTENANCE": 42, "QUALITY": 85, "POPULARITY": 67 } + readonly signalScores: Record>; // { "popularity": { "weeklyDownloads": 4, "githubStars": 2 } } + readonly signalWeights: Record>; // { "popularity": { "weeklyDownloads": 3, "githubStars": 2 } } +} + /** * Benchmark function type for converting raw values to quality levels (1-5) */ @@ -34,8 +48,13 @@ export interface Config { } /** - * Custom signal weights map: signal name -> weight + * Pillar scores for the three main evaluation pillars + * Dynamically generated from Pillar enum */ +export type PillarScores = { + [K in Pillar]: number; +}; + export type SignalWeights = Record; /**