Practice the small contracts that support a larger transformation pipeline. Curried calculations, composition, result mapping, memoized lookups, encoded routes, typed field extraction, line framing, and batch sizing all need consistent rules at their edges. You have already used every language feature required here.
Keep intermediate types visible while you work. If one stage produces a number, the next stage must accept a number. If a helper promises to preserve row order, sorting its output changes the contract even when the values are correct. For chunked text, reconstruct the text before interpreting newline boundaries. For batching, an exact multiple must not create an extra empty batch. Runtime checks and compiler assertions test these requirements separately so a shortcut can't hide behind a broad annotation.
function curry3<A,B,C,R>(fn:(a:A,b:B,c:C)=>R):(a:A)=>(b:B)=>(c:C)=>R {return a=>b=>c=>fn(a,b,c);}
function solve(values:number[]):number {return curry3((base:number,rate:number,offset:number)=>base*rate+offset)(values[0])(values[1])(values[2]);}
A complete implementation for the first scenario; trace its input before editing.