@atomist/sdm-pack-aspect
Version:
an Atomist SDM Extension Pack for visualizing drift across an organization
169 lines (142 loc) • 4.33 kB
text/typescript
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { logger } from "@atomist/automation-client";
import { Omit } from "../util/omit";
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Score value
*/
export type FiveStar = 0 | 1 | 2 | 3 | 4 | 5;
/**
* Scores with this category are always included in any score computation
*/
export const AlwaysIncludeCategory: "*" = "*";
/**
* Represents a quality ranking of a particular element of a project.
* The numeric score will be from from 1-5, where 1 is very bad and 5 is very good.
*/
export interface Score {
readonly name: string;
readonly description?: string;
/**
* Category this score belongs to, if any
*/
readonly category?: string;
/**
* Explanation for this score, if available
*/
readonly reason?: string;
/**
* Score for this project
*/
readonly score: FiveStar;
}
/**
* Structure representing a score on a particular aspect of a project.
* The key is the scorer name
*/
export type Scores = Record<string, Score>;
/**
* Weighting of a particular scorer
*/
export type Weighting = 1 | 2 | 3;
export interface Scored {
readonly scores: Scores;
}
/**
* Weighting to apply to this name score. Default is 1.
* Other values can be used to increase the weighting.
*/
export type ScoreWeightings = Record<string, Weighting>;
export type WeightedScores = Record<string, Score & { weighting: Weighting }>;
export interface WeightedScore {
/**
* Weighted score
*/
weightedScore: number;
/**
* Individual component scores
*/
weightedScores: WeightedScores;
}
/**
* Perform a weighted composite score for the given scores.
* Returns a real number from 0 to 5
*/
export function weightedCompositeScore(scored: Scored,
weightings: ScoreWeightings = {}): WeightedScore | undefined {
const keys = Object.getOwnPropertyNames(scored.scores);
if (keys.length === 0) {
return {
weightedScore: 3,
weightedScores: {},
};
}
const weightedScores: WeightedScores = {};
let compositeScore: number = 0.0;
let divideBy = 0;
const scores = keys.map(k => scored.scores[k]);
for (const score of scores) {
if (Number.isNaN(score.score)) {
logger.error("Invalid score: %j", score);
continue;
}
const weighting = weightings[score.name] || 1;
weightedScores[score.name] = {
...score,
weighting,
};
compositeScore += score.score * weighting;
divideBy += weighting;
}
const weightedScore = compositeScore / (divideBy || 1);
return {
weightedScore,
weightedScores,
};
}
/**
* Extended by types that can perform scoring
*/
export interface Scorer {
/**
* Name of the scorer. Will be included in all scores.
*/
readonly name: string;
readonly description?: string;
/**
* Category to include in scores, if any
*/
readonly category?: string;
}
/**
* Type returned by any scorer
*/
export type ScorerReturn = Omit<Score, "name" | "description" | "category"> | undefined;