File size: 1,746 Bytes
379bf78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8f1c2fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20abf65
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// Display formatting ONLY β€” no business arithmetic happens in the UI.
// Every number shown comes from the engine via the API.

export const fmtMillions = (v) =>
  v == null || Number.isNaN(Number(v)) ? 'β€”' : `$${Number(v).toFixed(1)}m`;

export const fmtPct = (v, dp = 2) =>
  v == null || Number.isNaN(Number(v)) ? 'β€”' : `${(Number(v) * 100).toFixed(dp)}%`;

export const fmtRatio = (v) =>
  v == null || Number.isNaN(Number(v)) ? 'β€”' : `${Number(v).toFixed(3)}Γ—`;

export const fmtSignedM = (v) =>
  v == null || Number.isNaN(Number(v))
    ? 'β€”'
    : `${Number(v) >= 0 ? '+' : 'βˆ’'}$${Math.abs(Number(v)).toFixed(1)}m`;

export const fmtTime = (ms) => {
  const d = new Date(ms);
  const p = (n) => String(n).padStart(2, '0');
  return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
};

// A "*_pct" field is already 0-100 scale (per the contract's convention) β€”
// never re-multiply it.
export const fmtPctScale = (v, dp = 1) =>
  v == null || Number.isNaN(Number(v)) ? 'β€”' : `${Number(v).toFixed(dp)}%`;

export const fmtNum = (v, dp = 0) =>
  v == null || Number.isNaN(Number(v))
    ? 'β€”'
    : Number(v).toLocaleString(undefined, {
        minimumFractionDigits: dp,
        maximumFractionDigits: dp,
      });

export const fmtHazard = (v) =>
  v == null || Number.isNaN(Number(v)) ? 'β€”' : Number(v).toFixed(4);

export const fmtSigned = (v, dp = 1) =>
  v == null || Number.isNaN(Number(v))
    ? 'β€”'
    : `${Number(v) >= 0 ? '+' : 'βˆ’'}${Math.abs(Number(v)).toFixed(dp)}`;

// Exhibit-footer "run <date>" stamp (FINAL_SPEC Β§5.2) β€” the client's own
// clock; the app has no server-side "as-of build" timestamp endpoint.
export const runDate = () => new Date().toISOString().slice(0, 10);