Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 18 additions & 36 deletions src/lib/analyzer.ts
Original file line number Diff line number Diff line change
@@ -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<string, number>; // { "popularity": 42 }
readonly signalScores: Record<string, Record<string, number>>; // { "popularity": { "weeklyDownloads": 4, "githubStars": 2 } }
readonly signalWeights: Record<string, Record<string, number>>; // { "popularity": { "weeklyDownloads": 3, "githubStars": 2 } }
}
import type { ScoreResult, Config, PackageData, SignalWeights, PillarScores } from './types';

export class ConstructAnalyzer {
private config: Config;
Expand All @@ -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<PillarScores>;
const signalWeights = this.getSignalWeights(weights);

return {
Expand All @@ -41,25 +29,25 @@ export class ConstructAnalyzer {

private async calculateSignalScores(packageData: PackageData, weights?: SignalWeights) {
const signalScores: Record<string, Record<string, number>> = {};
const pillarScores: Record<string, number> = {};
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;
}
}

Expand All @@ -84,24 +72,18 @@ export class ConstructAnalyzer {
return (level - 1) * 25;
}

private updateSignalScore(signalScores: Record<string, Record<string, number>>, pillar: string, signalName: string, starRating: number): void {
(signalScores[pillar] ??= {})[signalName] = starRating;
}

private updatePillarScore(pillarScores: Record<string, number>, pillar: string, points: number, weight: number): void {
const weightedScore = points * weight;
pillarScores[pillar] = (pillarScores[pillar] ?? 0) + weightedScore;
}

private normalizePillarScores(pillarScores: Record<string, number>, weights?: SignalWeights): Record<string, number> {
const normalizedScores: Record<string, number> = {};
private normalizePillarScores(pillarScores: Record<string, number>, 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;
}

Expand Down
15 changes: 12 additions & 3 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -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: [
{
Expand Down Expand Up @@ -39,7 +48,7 @@ export const CONFIG: Config = {
],
},
{
name: 'QUALITY',
name: Pillar.QUALITY,
description: 'Measures the overall quality and reliability of the package',
signals: [
{
Expand Down Expand Up @@ -92,7 +101,7 @@ export const CONFIG: Config = {
],
},
{
name: 'POPULARITY',
name: Pillar.POPULARITY,
description: 'Measures how widely adopted and used the package is',
signals: [
{
Expand Down
21 changes: 20 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PillarScores>; // { "MAINTENANCE": 42, "QUALITY": 85, "POPULARITY": 67 }
readonly signalScores: Record<string, Record<string, number>>; // { "popularity": { "weeklyDownloads": 4, "githubStars": 2 } }
readonly signalWeights: Record<string, Record<string, number>>; // { "popularity": { "weeklyDownloads": 3, "githubStars": 2 } }
}

/**
* Benchmark function type for converting raw values to quality levels (1-5)
*/
Expand Down Expand Up @@ -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<string, number>;

/**
Expand Down
Loading