// src/constants.ts
var PLANT = {
  id: "site-001",
  name: "\u3042\u304A\u305E\u3089\u84C4\u96FB\u6240\uFF08\u30C7\u30E2\uFF09",
  lat: 34.74,
  lon: 138.22,
  capacityKwh: 4e4,
  maxPowerKw: 1e4,
  contractGfKw: 1e3,
  roundTripEfficiency: 0.92,
  auxLossKw: 60
};
var TARIFF = {
  balancingDeltaKwYenPerKwH: 1.2,
  capacityYenPerKwYear: 5200,
  imbalanceYenPerKwh: 15,
  wheelingYenPerKwh: 3,
  wheelingYenPerKwMonth: 0
};
var CAPACITY_ALLOCATION = {
  totalCapacityMw: 10,
  allocations: [
    { product: "JEPX", allocatedMw: 3, color: "#0078b4" },
    { product: "\u4E09\u6B21\u2461", allocatedMw: 2.5, color: "#7c3aed" },
    { product: "\u4E09\u6B21\u2460", allocatedMw: 2, color: "#059669" },
    { product: "\u4E8C\u6B21\u2461", allocatedMw: 1.5, color: "#d97706" },
    { product: "\u4E88\u5099", allocatedMw: 1, color: "#666688" }
  ]
};

// src/time.ts
var TZ = "Asia/Tokyo";
var fmtParts = new Intl.DateTimeFormat("ja-JP", {
  timeZone: TZ,
  hour12: false,
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
  second: "2-digit"
});
function jst(d = /* @__PURE__ */ new Date()) {
  const p = Object.fromEntries(
    fmtParts.formatToParts(d).filter((x) => x.type !== "literal").map((x) => [x.type, Number(x.value)])
  );
  return { y: p.year, m: p.month, d: p.day, H: p.hour === 24 ? 0 : p.hour, M: p.minute, S: p.second };
}
function ymd(d = /* @__PURE__ */ new Date()) {
  const j = jst(d);
  return `${j.y}-${String(j.m).padStart(2, "0")}-${String(j.d).padStart(2, "0")}`;
}
function ymdSlash(d = /* @__PURE__ */ new Date()) {
  return ymd(d).replaceAll("-", "/");
}
function slotOf(d = /* @__PURE__ */ new Date()) {
  const j = jst(d instanceof Date ? d : new Date(d));
  return j.H * 2 + (j.M >= 30 ? 1 : 0);
}
function hhmm(slot) {
  return `${String(Math.floor(slot / 2)).padStart(2, "0")}:${slot % 2 ? "30" : "00"}`;
}
function slotLabel(slot) {
  return `${hhmm(slot)}-${hhmm((slot + 1) % 48)}`;
}
function isoJst(d = /* @__PURE__ */ new Date()) {
  const j = jst(d);
  return `${j.y}-${String(j.m).padStart(2, "0")}-${String(j.d).padStart(2, "0")}T${String(j.H).padStart(
    2,
    "0"
  )}:${String(j.M).padStart(2, "0")}:${String(j.S).padStart(2, "0")}+09:00`;
}
function jstDateTime(ymdStr, H = 0, M = 0) {
  return (/* @__PURE__ */ new Date(`${ymdStr}T${String(H).padStart(2, "0")}:${String(M).padStart(2, "0")}:00+09:00`)).getTime();
}
function addDays(ymdStr, n) {
  const t = jstDateTime(ymdStr, 12) + n * 864e5;
  return ymd(new Date(t));
}
function fiscalYear(d = /* @__PURE__ */ new Date()) {
  const j = jst(d);
  return j.m >= 4 ? j.y : j.y - 1;
}

// src/engine.ts
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
var round = (v, d = 0) => Number((v ?? 0).toFixed(d));
function socStep(soc, powerKw, dtSec, {
  eta = PLANT.roundTripEfficiency,
  capacityKwh = PLANT.capacityKwh,
  auxKw = PLANT.auxLossKw
} = {}) {
  const sq = Math.sqrt(eta);
  const kwh = powerKw * dtSec / 3600;
  const stored = kwh > 0 ? kwh * sq : kwh / sq;
  const aux = auxKw * dtSec / 3600;
  return clamp(soc + (stored - aux) / capacityKwh * 100, 0, 100);
}
var FLOW_EPS = 1e-9;
function solveArbitrage(prices, opts) {
  const n = prices.length;
  const { slotKwhMax, s, capacityKwh, minSpreadYen, budgetKwh } = opts;
  const storedNow = opts.soc0 / 100 * capacityKwh;
  const headUp = Math.max(0, opts.socHi / 100 * capacityKwh - storedNow);
  const headDown = Math.max(0, storedNow - opts.socLo / 100 * capacityKwh);
  const SS = 0;
  const SRC = 1;
  const SNK = 2;
  const node = (t) => 3 + t;
  const V = n + 3;
  const to = [];
  const cap = [];
  const cost = [];
  const head = new Array(V).fill(-1);
  const next = [];
  const addEdge = (u, v, c1, c2, w) => {
    const id = to.length;
    to.push(v, u);
    cap.push(c1, c2);
    cost.push(w, -w);
    next.push(head[u], head[v]);
    head[u] = id;
    head[v] = id + 1;
    return id;
  };
  addEdge(SS, SRC, budgetKwh * s, 0, 0);
  const chargeEdge = [];
  const dischargeEdge = [];
  for (let t = 0; t < n; t++) {
    chargeEdge.push(addEdge(SRC, node(t), slotKwhMax * s, 0, prices[t] / s));
    dischargeEdge.push(addEdge(node(t), SNK, slotKwhMax / s, 0, -prices[t] * s));
  }
  for (let t = 0; t + 1 < n; t++) addEdge(node(t), node(t + 1), headUp, headDown, 0);
  const threshold = -minSpreadYen / s + 1e-12;
  const dist = new Array(V);
  const parentEdge = new Array(V);
  const inQueue = new Array(V);
  for (; ; ) {
    dist.fill(Number.POSITIVE_INFINITY);
    parentEdge.fill(-1);
    inQueue.fill(false);
    dist[SS] = 0;
    const queue = [SS];
    let qi = 0;
    while (qi < queue.length) {
      const u = queue[qi++];
      inQueue[u] = false;
      for (let e = head[u]; e !== -1; e = next[e]) {
        if (cap[e] <= FLOW_EPS) continue;
        const v = to[e];
        const nd = dist[u] + cost[e];
        if (nd < dist[v] - 1e-12) {
          dist[v] = nd;
          parentEdge[v] = e;
          if (!inQueue[v]) {
            inQueue[v] = true;
            queue.push(v);
          }
        }
      }
    }
    if (dist[SNK] > threshold) break;
    let f = Number.POSITIVE_INFINITY;
    for (let v = SNK; v !== SS; v = to[parentEdge[v] ^ 1]) f = Math.min(f, cap[parentEdge[v]]);
    if (!(f > FLOW_EPS)) break;
    for (let v = SNK; v !== SS; v = to[parentEdge[v] ^ 1]) {
      const e = parentEdge[v];
      cap[e] -= f;
      if (cap[e] < FLOW_EPS) cap[e] = 0;
      cap[e ^ 1] += f;
    }
  }
  const chargeKwh = new Array(n).fill(0);
  const dischargeKwh = new Array(n).fill(0);
  for (let t = 0; t < n; t++) {
    const qc = cap[chargeEdge[t] ^ 1];
    const qd = cap[dischargeEdge[t] ^ 1];
    chargeKwh[t] = qc <= FLOW_EPS ? 0 : cap[chargeEdge[t]] <= FLOW_EPS ? slotKwhMax : qc / s;
    dischargeKwh[t] = qd <= FLOW_EPS ? 0 : cap[dischargeEdge[t]] <= FLOW_EPS ? slotKwhMax : qd * s;
  }
  return { chargeKwh, dischargeKwh };
}
function buildDayPlan(prices, options = {}) {
  const {
    soc0 = 50,
    capKw = PLANT.maxPowerKw,
    eta = PLANT.roundTripEfficiency,
    socLo = 10,
    socHi = 90,
    capacityKwh = PLANT.capacityKwh,
    minSpreadYen = 0.5,
    maxCyclesPerDay = Number.POSITIVE_INFINITY,
    reservedKwForEprx = 0
  } = options;
  const useKw = Math.max(0, capKw - reservedKwForEprx);
  const n = prices.length;
  const plan = new Array(n).fill(0);
  if (useKw > 0 && n > 0 && capacityKwh > 0) {
    const { chargeKwh, dischargeKwh } = solveArbitrage(prices, {
      slotKwhMax: useKw * 0.5,
      s: Math.sqrt(eta),
      soc0,
      socLo,
      socHi,
      capacityKwh,
      minSpreadYen,
      budgetKwh: maxCyclesPerDay * capacityKwh
    });
    for (let i = 0; i < n; i++) plan[i] = (chargeKwh[i] - dischargeKwh[i]) / 0.5;
  }
  let soc = soc0;
  const traj = [];
  for (let i = 0; i < n; i++) {
    soc = socStep(soc, plan[i], 1800, { eta, capacityKwh, auxKw: 0 });
    traj.push(soc);
  }
  return prices.map((p, i) => ({
    time: hhmm(i),
    plannedKw: plan[i],
    actualKw: 0,
    socPercent: round(traj[i], 1),
    jepxPrice: round(p, 2)
  }));
}
function computePnl(slots, { balancingContractKw = 0, capacityKw = PLANT.maxPowerKw, tariff = TARIFF } = {}) {
  let jepx = 0;
  let chargedKwh = 0;
  let dischargedKwh = 0;
  let imbalanceKwh = 0;
  for (const s of slots) {
    const kwh = s.actualKw * 0.5;
    jepx += -kwh * s.jepxPrice;
    if (kwh > 0) chargedKwh += kwh;
    else dischargedKwh += -kwh;
    imbalanceKwh += Math.abs(s.actualKw - s.plannedKw) * 0.5;
  }
  const balancing = balancingContractKw * tariff.balancingDeltaKwYenPerKwH * 0.5 * slots.length;
  const capacity = capacityKw * tariff.capacityYenPerKwYear / 365 * (slots.length / 48);
  const imbalance = imbalanceKwh * tariff.imbalanceYenPerKwh;
  const wheeling = chargedKwh * tariff.wheelingYenPerKwh;
  return {
    jepxRevenue: Math.round(jepx),
    balancingRevenue: Math.round(balancing),
    capacityRevenue: Math.round(capacity),
    imbalanceCost: -Math.round(imbalance),
    transmissionCost: -Math.round(wheeling),
    netRevenue: Math.round(jepx + balancing + capacity - imbalance - wheeling),
    chargedKwh: Math.round(chargedKwh),
    dischargedKwh: Math.round(dischargedKwh),
    imbalanceKwh: Math.round(imbalanceKwh)
  };
}

// src/eprx.ts
var EPRX_PRODUCTS = ["\u4E00\u6B21", "\u4E8C\u6B21\u2460", "\u4E8C\u6B21\u2461", "\u4E09\u6B21\u2460", "\u4E09\u6B21\u2461", "\u8907\u5408"];
var EPRX_CAP_PRICES_BEFORE_2026_09 = {
  \u4E00\u6B21: 15,
  "\u4E8C\u6B21\u2460": 15,
  \u8907\u5408: 15,
  "\u4E8C\u6B21\u2461": 7.21,
  "\u4E09\u6B21\u2460": 7.21
};
var EPRX_CAP_PRICES_FROM_2026_09 = {
  \u4E00\u6B21: 10,
  "\u4E8C\u6B21\u2460": 10,
  \u8907\u5408: 10,
  "\u4E8C\u6B21\u2461": 7.21,
  "\u4E09\u6B21\u2460": 7.21
};
var EPRX_CAP_CHANGE_PRODUCTS = ["\u4E00\u6B21", "\u4E8C\u6B21\u2460", "\u8907\u5408"];
var capFor = (cap, product) => typeof cap === "number" ? cap : cap[product] ?? Number.POSITIVE_INFINITY;
function calcEprxRevenue(slots, { productMix, capKw = Number.POSITIVE_INFINITY, capPriceYen, awardRate = 1 }) {
  if (!(awardRate >= 0 && awardRate <= 1)) throw new Error("awardRate \u306F 0..1 \u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044");
  let remainingKw = capKw;
  const effective = productMix.map((a) => {
    const kw = Math.max(0, Math.min(a.allocatedKw, remainingKw));
    remainingKw -= kw;
    return { product: a.product, kw, cap: capFor(capPriceYen, a.product) };
  });
  const byProduct = {
    \u4E00\u6B21: 0,
    "\u4E8C\u6B21\u2460": 0,
    "\u4E8C\u6B21\u2461": 0,
    "\u4E09\u6B21\u2460": 0,
    "\u4E09\u6B21\u2461": 0,
    \u8907\u5408: 0
  };
  let cappedCount = 0;
  for (const s of slots) {
    for (const { product, kw, cap } of effective) {
      if (kw <= 0) continue;
      const raw = typeof s.expectedPriceYen === "number" ? s.expectedPriceYen : s.expectedPriceYen[product] ?? 0;
      const price = Math.max(0, raw);
      if (price > cap) cappedCount++;
      byProduct[product] += Math.min(price, cap) * kw * awardRate;
    }
  }
  const total = EPRX_PRODUCTS.reduce((s, p) => s + byProduct[p], 0);
  const totalAllocatedKw = effective.reduce((s, e) => s + e.kw, 0);
  return { revenueYen: Math.round(total), byProduct, totalAllocatedKw, cappedCount, awardRate };
}
export {
  CAPACITY_ALLOCATION,
  EPRX_CAP_CHANGE_PRODUCTS,
  EPRX_CAP_PRICES_BEFORE_2026_09,
  EPRX_CAP_PRICES_FROM_2026_09,
  EPRX_PRODUCTS,
  PLANT,
  TARIFF,
  TZ,
  addDays,
  buildDayPlan,
  calcEprxRevenue,
  clamp,
  computePnl,
  fiscalYear,
  hhmm,
  isoJst,
  jst,
  jstDateTime,
  round,
  slotLabel,
  slotOf,
  socStep,
  ymd,
  ymdSlash
};
//# sourceMappingURL=index.js.map