quotacap 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import type { Quota } from "../adapters/types.js";
2
2
  import type { Advisory } from "./types.js";
3
- export declare function computeAdvisory(q: Quota, burnRate: number, now?: Date): Advisory;
3
+ export declare function computeAdvisory(q: Quota, burnRate: number, now?: Date, burnMeasured?: boolean): Advisory;
4
4
  export declare function recommend(quotas: Quota[], _task: string, burnByProvider?: Map<string, number>, now?: Date): {
5
5
  use: string;
6
6
  reason: string;
@@ -1,9 +1,12 @@
1
- export function computeAdvisory(q, burnRate, now = new Date()) {
1
+ export function computeAdvisory(q, burnRate, now = new Date(), burnMeasured = false) {
2
2
  const resets = new Date(q.resetsAt);
3
3
  const daysLeft = Math.max(0.1, (resets.getTime() - now.getTime()) / 86400000);
4
4
  const remaining = 100 - q.usedPct;
5
5
  const idealRate = remaining / daysLeft;
6
6
  const wastePct = Math.max(0, remaining - burnRate * daysLeft);
7
+ // burn > ideal is exactly "quota exhausts before reset": remaining/burn < daysLeft
8
+ const daysToExhaust = burnRate > 0 ? remaining / burnRate : Infinity;
9
+ const status = daysToExhaust < daysLeft ? "at risk" : "on track";
7
10
  let urgency = "on track";
8
11
  if (wastePct > 30 && daysLeft < 3)
9
12
  urgency = "burn now";
@@ -13,12 +16,12 @@ export function computeAdvisory(q, burnRate, now = new Date()) {
13
16
  urgency = "slow down";
14
17
  else if (wastePct > 10)
15
18
  urgency = "save";
16
- return { provider: q.provider, daysLeft, remaining, idealRate, burnRate, wastePct, urgency };
19
+ return { provider: q.provider, daysLeft, remaining, idealRate, burnRate, burnMeasured, daysToExhaust, status, wastePct, urgency };
17
20
  }
18
21
  export function recommend(quotas, _task, burnByProvider = new Map(), now = new Date()) {
19
22
  if (!quotas.length)
20
23
  return { use: "none", reason: "no quotas yet", wastePct: 0, idealRate: 0, alternatives: [], advisories: [] };
21
- const advisories = quotas.map(q => computeAdvisory(q, burnByProvider.get(q.provider) ?? 2, now));
24
+ const advisories = quotas.map(q => computeAdvisory(q, burnByProvider.get(q.provider) ?? 2, now, burnByProvider.has(q.provider)));
22
25
  const burnNow = advisories.filter(a => a.urgency === "burn now");
23
26
  const pool = burnNow.length ? burnNow : advisories;
24
27
  const use = pool.sort((a, b) => b.wastePct - a.wastePct)[0];
@@ -1,10 +1,14 @@
1
1
  export type Urgency = "burn now" | "use soon" | "slow down" | "save" | "on track";
2
+ export type BurnStatus = "at risk" | "on track";
2
3
  export interface Advisory {
3
4
  provider: string;
4
5
  daysLeft: number;
5
6
  remaining: number;
6
7
  idealRate: number;
7
8
  burnRate: number;
9
+ burnMeasured: boolean;
10
+ daysToExhaust: number;
11
+ status: BurnStatus;
8
12
  wastePct: number;
9
13
  urgency: Urgency;
10
14
  }
@@ -45,7 +45,9 @@ export function buildApp(db) {
45
45
  }
46
46
  try {
47
47
  const task = req.query?.task ?? "any";
48
- return recommend(quotas, task);
48
+ const { getBurnRates } = await import("../store/quotas.js");
49
+ const burnByProvider = getBurnRates(db);
50
+ return recommend(quotas, task, burnByProvider);
49
51
  }
50
52
  catch (e) {
51
53
  return { use: quotas[0]?.provider ?? "none", reason: `advisory error: ${e?.message ?? String(e)}`, alternatives: quotas };
@@ -23,11 +23,13 @@ export function recommendationTable(rec) {
23
23
  const resets = q?.resetsAt ? new Date(q.resetsAt).toLocaleString() : "—";
24
24
  const daysLeft = a.daysLeft != null ? a.daysLeft.toFixed(1) : "—";
25
25
  const ideal = a.idealRate != null ? `${Math.round(a.idealRate)}%/day` : "—";
26
- return `| ${a.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${Math.round(a.wastePct)}% |`;
26
+ const burn = a.burnMeasured ? `${a.burnRate.toFixed(1)}%/day` : "collecting…";
27
+ const icon = a.status === "at risk" ? "⚠️" : "✅";
28
+ return `| ${a.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${burn} | ${icon} | ${Math.round(a.wastePct)}% |`;
27
29
  });
28
30
  return [
29
- "| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Waste if unused |",
30
- "|---|---|---|---|---|---|---|",
31
+ "| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Status | Waste if unused |",
32
+ "|---|---|---|---|---|---|---|---|---|",
31
33
  ...rows,
32
34
  ].join("\n");
33
35
  }
@@ -1,6 +1,6 @@
1
1
  import type { Quota } from "../adapters/types.js";
2
2
  import type { Advisory } from "./types.js";
3
- export declare function computeAdvisory(q: Quota, burnRate: number, now?: Date): Advisory;
3
+ export declare function computeAdvisory(q: Quota, burnRate: number, now?: Date, burnMeasured?: boolean): Advisory;
4
4
  export declare function recommend(quotas: Quota[], _task: string, burnByProvider?: Map<string, number>, now?: Date): {
5
5
  use: string;
6
6
  reason: string;
@@ -1,9 +1,12 @@
1
- export function computeAdvisory(q, burnRate, now = new Date()) {
1
+ export function computeAdvisory(q, burnRate, now = new Date(), burnMeasured = false) {
2
2
  const resets = new Date(q.resetsAt);
3
3
  const daysLeft = Math.max(0.1, (resets.getTime() - now.getTime()) / 86400000);
4
4
  const remaining = 100 - q.usedPct;
5
5
  const idealRate = remaining / daysLeft;
6
6
  const wastePct = Math.max(0, remaining - burnRate * daysLeft);
7
+ // burn > ideal is exactly "quota exhausts before reset": remaining/burn < daysLeft
8
+ const daysToExhaust = burnRate > 0 ? remaining / burnRate : Infinity;
9
+ const status = daysToExhaust < daysLeft ? "at risk" : "on track";
7
10
  let urgency = "on track";
8
11
  if (wastePct > 30 && daysLeft < 3)
9
12
  urgency = "burn now";
@@ -13,12 +16,12 @@ export function computeAdvisory(q, burnRate, now = new Date()) {
13
16
  urgency = "slow down";
14
17
  else if (wastePct > 10)
15
18
  urgency = "save";
16
- return { provider: q.provider, daysLeft, remaining, idealRate, burnRate, wastePct, urgency };
19
+ return { provider: q.provider, daysLeft, remaining, idealRate, burnRate, burnMeasured, daysToExhaust, status, wastePct, urgency };
17
20
  }
18
21
  export function recommend(quotas, _task, burnByProvider = new Map(), now = new Date()) {
19
22
  if (!quotas.length)
20
23
  return { use: "none", reason: "no quotas yet", wastePct: 0, idealRate: 0, alternatives: [], advisories: [] };
21
- const advisories = quotas.map(q => computeAdvisory(q, burnByProvider.get(q.provider) ?? 2, now));
24
+ const advisories = quotas.map(q => computeAdvisory(q, burnByProvider.get(q.provider) ?? 2, now, burnByProvider.has(q.provider)));
22
25
  const burnNow = advisories.filter(a => a.urgency === "burn now");
23
26
  const pool = burnNow.length ? burnNow : advisories;
24
27
  const use = pool.sort((a, b) => b.wastePct - a.wastePct)[0];
@@ -1,10 +1,14 @@
1
1
  export type Urgency = "burn now" | "use soon" | "slow down" | "save" | "on track";
2
+ export type BurnStatus = "at risk" | "on track";
2
3
  export interface Advisory {
3
4
  provider: string;
4
5
  daysLeft: number;
5
6
  remaining: number;
6
7
  idealRate: number;
7
8
  burnRate: number;
9
+ burnMeasured: boolean;
10
+ daysToExhaust: number;
11
+ status: BurnStatus;
8
12
  wastePct: number;
9
13
  urgency: Urgency;
10
14
  }
@@ -45,7 +45,9 @@ export function buildApp(db) {
45
45
  }
46
46
  try {
47
47
  const task = req.query?.task ?? "any";
48
- return recommend(quotas, task);
48
+ const { getBurnRates } = await import("../store/quotas.js");
49
+ const burnByProvider = getBurnRates(db);
50
+ return recommend(quotas, task, burnByProvider);
49
51
  }
50
52
  catch (e) {
51
53
  return { use: quotas[0]?.provider ?? "none", reason: `advisory error: ${e?.message ?? String(e)}`, alternatives: quotas };
@@ -23,11 +23,13 @@ export function recommendationTable(rec) {
23
23
  const resets = q?.resetsAt ? new Date(q.resetsAt).toLocaleString() : "—";
24
24
  const daysLeft = a.daysLeft != null ? a.daysLeft.toFixed(1) : "—";
25
25
  const ideal = a.idealRate != null ? `${Math.round(a.idealRate)}%/day` : "—";
26
- return `| ${a.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${Math.round(a.wastePct)}% |`;
26
+ const burn = a.burnMeasured ? `${a.burnRate.toFixed(1)}%/day` : "collecting…";
27
+ const icon = a.status === "at risk" ? "⚠️" : "✅";
28
+ return `| ${a.provider} | ${used}% | ${100 - used}% | ${resets} | ${daysLeft} | ${ideal} | ${burn} | ${icon} | ${Math.round(a.wastePct)}% |`;
27
29
  });
28
30
  return [
29
- "| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Waste if unused |",
30
- "|---|---|---|---|---|---|---|",
31
+ "| Provider | Used | Remaining | Resets | Days left | Ideal daily burn | Burn rate | Status | Waste if unused |",
32
+ "|---|---|---|---|---|---|---|---|---|",
31
33
  ...rows,
32
34
  ].join("\n");
33
35
  }
@@ -3,3 +3,4 @@ export declare function getLatestByProvider(db: any, provider: string): any;
3
3
  export declare function getAllLatest(db: any): any;
4
4
  export declare const getQuotas: typeof getAllLatest;
5
5
  export declare function getSnapshots(db: any): any;
6
+ export declare function getBurnRates(db: any): Map<string, number>;
@@ -26,3 +26,27 @@ export function getAllLatest(db) {
26
26
  // alias for plan's getQuotas naming
27
27
  export const getQuotas = getAllLatest;
28
28
  export function getSnapshots(db) { return db.prepare(`SELECT * FROM snapshots ORDER BY day DESC`).all(); }
29
+ export function getBurnRates(db) {
30
+ const rows = db.prepare(`SELECT day, provider, used_pct FROM snapshots`).all();
31
+ const byProvider = new Map();
32
+ for (const r of rows) {
33
+ const pts = byProvider.get(r.provider) ?? [];
34
+ pts.push({ day: r.day, usedPct: r.used_pct });
35
+ byProvider.set(r.provider, pts);
36
+ }
37
+ const out = new Map();
38
+ for (const [provider, pts] of byProvider) {
39
+ if (pts.length < 2)
40
+ continue;
41
+ const sorted = [...pts].sort((a, b) => a.day.localeCompare(b.day));
42
+ const first = sorted[0];
43
+ const last = sorted[sorted.length - 1];
44
+ const days = (new Date(last.day).getTime() - new Date(first.day).getTime()) / 86400000;
45
+ if (days < 1)
46
+ continue;
47
+ const burn = (last.usedPct - first.usedPct) / days;
48
+ if (burn >= 0)
49
+ out.set(provider, burn);
50
+ }
51
+ return out;
52
+ }
@@ -1,2 +1,2 @@
1
1
  // generated by scripts/build-embed.mjs — do not edit
2
- export const VERSION = "0.0.7";
2
+ export const VERSION = "0.0.8";