score API
パッケージ Luna-Flow/mooncake-impact-factor/score は、4 つの整数シグナルから MoonBit パッケージのインパクトスコアを計算し、スコアにランクとモメンタムの区分を付け、すべてを 1 つの ScoreSnapshot にまとめます。どの関数も純粋かつ全域的で、中断することも隠れた状態を持つこともありません。
ソース: src/score/impact_factor.mbt。式の背後にある数学は score の設計 にあります。
moon.pkg でパッケージをインポートします。
import {
"Luna-Flow/mooncake-impact-factor/score",
}
シグナル
どのスコア関数も、同じ 4 つのシグナルを次の順で受け取ります。
| パラメーター | 意味 |
|---|---|
dependents | このパッケージに依存しているパッケージの数。 |
recent_dependents | それらのうち、最近のウィンドウ内で初めて現れたものの数。 |
downloads | ダウンロード数。不明な場合は 0。 |
days_since_release | 最新リリースからの経過日数(整数)。 |
これらの関数はシグナル同士の整合性(たとえば recent_dependents <= dependents)を確認しません。負の値は 0 に切り上げられます。シグナルはインデックスビルダーがローカルのレジストリスナップショットから算出します。アーキテクチャガイド を参照してください。
スコア
compute_score
compute_score はパッケージのインパクトスコアを返します。
pub fn compute_score(Int, Int, Int, Int) -> Double
とし、被依存数を 、最近の被依存数を 、ダウンロード数を 、リリースからの日数を とすると、結果は次のとおりです。
ここで は activity_multiplier(t) です。スコアが になるのは 3 つのカウントがすべて 以下のときに限られ、どのカウントが増えてもスコアが減ることはありません。設計上スコアに上限はありませんが、カウントが 未満であればおよそ を下回ります。
test "compute_score" {
let score = @score.compute_score(20, 4, 300, 40)
inspect(score, content="301.7852882193072")
inspect(@score.compute_score(0, 0, 0, 10), content="0")
}
activity_multiplier
activity_multiplier は、スコア全体に掛かるリリース鮮度係数 を返します。
pub fn activity_multiplier(Int) -> Double
負の days_since_release は 0 として扱われます。
| リリースからの日数 | 乗数 |
|---|---|
0 〜 30 | 1.12 |
31 〜 90 | 1.06 |
91 〜 180 | 1.0 |
181 〜 365 | 0.94 |
366 以上 | 0.88 |
test "activity_multiplier" {
inspect(@score.activity_multiplier(-3), content="1.12")
inspect(@score.activity_multiplier(90), content="1.06")
inspect(@score.activity_multiplier(400), content="0.88")
}
clamp_non_negative
clamp_non_negative は value が非負ならそれを、そうでなければ 0 を返します。
pub fn clamp_non_negative(Int) -> Int
スコア関数はすべてのシグナルにこれを適用します。呼び出し側が同じ方法でシグナルを準備できるよう公開されています。
test "clamp_non_negative" {
inspect(@score.clamp_non_negative(-7), content="0")
inspect(@score.clamp_non_negative(12), content="12")
}
ラベル
rank_label
rank_label はスコアをランク区分 S、A、B、C、D のいずれかに対応付けます。
pub fn rank_label(Double) -> String
| ランク | 条件 |
|---|---|
S | score >= 260.0 |
A | 180.0 <= score < 260.0 |
B | 110.0 <= score < 180.0 |
C | 50.0 <= score < 110.0 |
D | score < 50.0、または score が NaN |
test "rank_label" {
inspect(@score.rank_label(260.0), content="S")
inspect(@score.rank_label(259.99), content="A")
inspect(@score.rank_label(12.0), content="D")
}
compute_momentum_label
compute_momentum_label はスコアの伸びの速さを Rising、Hot、Stable に分類します。
pub fn compute_momentum_label(Double, Double, Double, Int) -> String
引数は現在のスコア 、30 日前のスコア 、成長率 、最近の被依存数 です。成長量を とすると、次のようになります。
| ラベル | 条件 |
|---|---|
Rising | かつ かつ |
Hot | Rising ではなく、かつ 、、 |
Stable | それ以外(いずれかの引数が NaN の場合を含む) |
この関数は growth_ratio が 2 つのスコアと一致するかを確認しません。compute_score_snapshot を使えば自動的に計算されます。
test "compute_momentum_label" {
inspect(@score.compute_momentum_label(140.0, 100.0, 0.4, 3), content="Rising")
inspect(@score.compute_momentum_label(140.0, 100.0, 0.4, 2), content="Hot")
inspect(@score.compute_momentum_label(105.0, 100.0, 0.05, 1), content="Stable")
}
スナップショット
ScoreSnapshot
ScoreSnapshot は、ある時点での 1 つのパッケージの評価結果一式です。
pub struct ScoreSnapshot {
score : Double
score_30d_ago : Double
score_growth_30d : Double
score_growth_ratio_30d : Double
rank_label : String
momentum_label : String
activity_multiplier : Double
} derive(ToJson, @debug.Debug, @json.FromJson)
| フィールド | 意味 |
|---|---|
score | 現在のスコア 。 |
score_30d_ago | 過去のシグナルから計算したスコア 。 |
score_growth_30d | 。スコアが下がった場合は負になります。 |
score_growth_ratio_30d | のときは 。それ以外では なら 、 なら 。 |
rank_label | rank_label(score). |
momentum_label | compute_momentum_label(score, score_30d_ago, score_growth_ratio_30d, recent_dependents). |
activity_multiplier | 現在のシグナルに対する activity_multiplier(days_since_release)。 |
フィールドはパッケージ外からは読み取り専用です。スナップショットは compute_score_snapshot で作成してください。ToJson はフィールド名をキーとするオブジェクトを書き出し、これが cli コマンド の出力形式です。FromJson は同じ形式を読み戻し、フィールドが欠けていたり型が違ったりすると @json.JsonDecodeError を送出します。Debug により debug_inspect と Repr(...) が使えます。この構造体は Eq を実装していないため、スナップショットはフィールドか JSON 形式で比較してください。
test "ScoreSnapshot JSON round trip" {
let snapshot = @score.compute_score_snapshot(8, 2, 120, 12, 0, 0, 0, 0)
let json = Json(snapshot)
let back : @score.ScoreSnapshot = @json.from_json(json)
assert_eq(Json(back), json)
inspect(back.momentum_label, content="Hot")
}
compute_score_snapshot
compute_score_snapshot はパッケージの現在と 30 日前のスコアを計算し、成長量、2 つのラベル、乗数を 1 回の呼び出しで導出します。
pub fn compute_score_snapshot(Int, Int, Int, Int, Int, Int, Int, Int) -> ScoreSnapshot
最初の 4 つの引数は現在のシグナル、残りの 4 つは 30 日前のシグナルで、どちらもシグナル表の順に並べます。
compute_score_snapshot(
dependents, recent_dependents, downloads, days_since_release,
historical_dependents, historical_recent_dependents,
historical_downloads, historical_days_since_release,
)
モメンタムラベルには現在の recent_dependents が使われます。30 日前に存在しなかったパッケージは過去のシグナルをすべて 0 として表します。このとき score_30d_ago は 、成長率は になります。
test "compute_score_snapshot" {
let snapshot = @score.compute_score_snapshot(20, 4, 300, 40, 10, 2, 0, 10)
debug_inspect(
snapshot,
content=(
#|{
#| score: 301.7852882193072,
#| score_30d_ago: 135.2764584196223,
#| score_growth_30d: 166.5088297996849,
#| score_growth_ratio_30d: 1.2308780976744749,
#| rank_label: "S",
#| momentum_label: "Rising",
#| activity_multiplier: 1.06,
#|}
),
)
}
非推奨
ScoreSnapshot には、以前の MoonBit が派生トレイトから暗黙に生成していたメソッドが 3 つ残っています。これらはインターフェースファイルから隠されており、使うと警告が出ます。
| 非推奨 | 代わりに使うもの |
|---|---|
ScoreSnapshot::to_json(s) | Json(s) |
ScoreSnapshot::from_json(json, path) | @json.from_json(json) |
ScoreSnapshot::to_repr(s) | Repr(s)、debug_inspect、または @debug.to_string |