budgetFor.mjs

import get from 'lodash-es/get.js'
import isarr from 'wsemi/src/isarr.mjs'
import castPintOr from './castPintOr.mjs'
import dfTimeoutMs from './dfTimeoutMs.mjs'


// budgetFor.mjs — 由供應商鏈推導走滿全鏈之時間預算
//
// 【為何屬本套件】「逾時型失敗每組只燒一次timeout即跳組, 故走滿全鏈的上限=各組
//   timeout相加」是dispatchAiFallback失敗分流模型的直接推論。消費端手算此值,
//   會在改動fallback鏈時忘記同步而再度budget exhausted(使用端殷鑑: 2026-08-14
//   revise名額之預算未跟上鏈變動而失敗); 或於設定檔維護一大段人工反推註解。
//
// 【與外部排程上限的關係】本函數給的是「保證能遞補到鏈尾」的值; 呼叫端之外層
//   常另有硬上限(如Windows排程ExecutionTimeLimit), 兩者取小者交budgetMs即可——
//   截短的取捨(可能走不完全鏈)由呼叫端自行決定。


/**
 * 計算遞補鏈走滿所需之時間預算(=各條目timeoutMs之總和, 未帶者以套件統一預設計)
 *
 * 特點:
 * 條目timeoutMs取正整數者計入,未帶或無效者以dfTimeoutMs(全套件統一預設300000)計——
 * 與dispatchAiFallback執行時之實際取值一致,故總和即為走滿全鏈之上限;
 * 輸入非陣列回傳0
 *
 * @param {Array} providers 輸入供應商條目物件陣列(dispatchAiFallback之providers)
 * @returns {Number} 回傳時間預算毫秒整數
 * @example
 *
 * import budgetFor from './src/budgetFor.mjs'
 *
 * console.log(budgetFor([{ timeoutMs: 180000 }, { timeoutMs: 240000 }]))
 * // => 420000
 *
 * console.log(budgetFor([{ timeoutMs: 180000 }, {}])) //未帶者以套件統一預設300000計
 * // => 480000
 *
 */
function budgetFor(providers) {
    if (!isarr(providers)) {
        return 0
    }
    let n = 0
    for (let p of providers) {
        n += castPintOr(get(p, 'timeoutMs', null), dfTimeoutMs)
    }
    return n
}


export default budgetFor