Two plans for the investor: Plan A ~180M, Plan B ~225M THB; Pheenix at OMS +20 %
Deploy test site / deploy (push) Successful in 53s

User (October 2026): price the Pheenix galvanics line at OMS's reference
plus 20 %, and work on a new business plan in two versions, one at
roughly 180M THB and one at +25 %; the plans do not include contingency;
Plan A keeps every machine and uses cost levers only.

- total-investment.md: Pheenix block 324k to 445k USD (OMS 4-cell wet
  room with know-how transfer, EUR 234k to 321k, plus 20 %; was 320k to
  650k estimate); launch plant now ~161M to 285M THB with contingency,
  midpoint ~223M. New section "Two plans": Plan A = launch plant with
  solar PPA, metrology launch tier, corridor later, cassette and CD-R
  later, rented crane, Chinese boilers and compressor: subtotal ~161.7M
  + ramp-up reserve 10 to 20M = ~177M THB (127M to 226M). Plan B = second
  Duplex at launch (five presses), everything in: subtotal ~209.2M +
  reserve = ~224M THB (162M to 286M). Contingency listed as not in
  either price (~24M / ~31M at 15 %). The 180M / 216M answer marked
  superseded.
- revenue-model.md: operating pictures of both plans at 60 % specials.
  Plan A central EBITDA 1.73M USD, operating profit 1.34M, payback ~3.1
  yr on the plan price; adverse loses money. Plan B (1.0 / 1.25 / 1.5M
  records) central EBITDA 3.14M, operating profit 2.63M, payback ~2.2 yr;
  adverse just covers depreciation; needs ~1.25M records sold.
- Calculator: Pheenix price basis "OMS +20 %" and "OMS directly"; new
  contingency switch (in the total / shown separately); presets Plan A
  and Plan B (replace "investor levers" and "five-press"); budget card
  against 180M and 225M.
- galvanics.md price note, README, roadmap.

Checks: calculator regression updated and passing (0 fails, no page
errors), node --check on all scripts, site build (no dead links), no em
dashes added.
This commit is contained in:
grabowski committed 2026-10-17 14:47:43 +07:00
1 parent f7e20e1015
commit f2ff1eac1f
6 files changed
+204 -44

No files matched your search

+35 -27
View File
@@ -599,7 +599,7 @@ html[data-theme=light] .iso-svg .iso-glint { display: inline; }
presses, an EX 50 and an EX 35 colour extruder each) and Newbilt Classic
automatic presses, starting from the launch plant: one Duplex system and one
Classic. It builds the investment from the blocks in total-investment.md,
checks it against the investor's 180M and 216M THB, runs the P&L and a
checks it against Plan A's 180M and Plan B's 225M THB, runs the P&L and a
ten-year cash sketch, and lists what the plant needs: people, power, central
hydraulics, steam, cooling, compressed air, floor, compound and stampers.
Vendor figures are marked as such; the rest are planning figures and
@@ -609,9 +609,9 @@ html[data-theme=light] .iso-svg .iso-glint { display: inline; }
<div class="presets" id="presets">
<span>Presets:</span>
<button type="button" data-p="launch">Launch plant (decided)</button>
<button type="button" data-p="levers">Investor levers (216M)</button>
<button type="button" data-p="planA">Plan A (~180M)</button>
<button type="button" data-p="planB">Plan B (~225M, five presses)</button>
<button type="button" data-p="lean">Lean: bought stampers, outsourced cutting</button>
<button type="button" data-p="five">Five-press fleet (second Duplex)</button>
<button type="button" data-p="full">Full hall (5 D + 4 C)</button>
</div>
</header>
@@ -730,10 +730,12 @@ html[data-theme=light] .iso-svg .iso-glint { display: inline; }
</section>
<section class="card">
<h2>Budget: the investor's question</h2>
<p class="note" style="margin-top:0">The investor asked whether the plant can be built for 180M THB, possibly
20 % more (216M THB). Bars: the investment at the low end, the midpoint and the high end of every block
(contingency included), plus the ramp-up reserve when it is in the budget (hatched).</p>
<h2>Budget: Plan A and Plan B</h2>
<p class="note" style="margin-top:0">Two plans for the investor (October 2026): Plan A at ~180M THB keeps every
machine of the launch plant with six cost levers, Plan B at ~225M THB (+25 %) launches with the second Duplex
system and everything in; both priced without contingency and with the ramp-up reserve. Bars: the investment
at the low end, the midpoint and the high end of every block (contingency as set under Plant configuration),
plus the ramp-up reserve when it is in the budget (hatched).</p>
<div class="kpis" id="budK"></div>
<div class="bud" id="bud"></div>
<div id="budV"></div>
@@ -2842,7 +2844,7 @@ const S0 = {
priceStd: 3.8, premSpec: 1.15, titleFee: 1155, cur: "USD", boi: "on", exportShare: 70, capexEnd: "mid",
// plant configuration (defaults = the launch plant, total-investment.md October 2026)
galv: "in", galvPrice: "pheenix", cut: "in", metro: "full", cass: "on", cdr: "on", countCD: "off",
solar: "buy", steamFuel: "electric", sourcing: "thai", crane: "buy", corridor: "now", reserve: "in", stSupply: 0,
solar: "buy", steamFuel: "electric", sourcing: "thai", crane: "buy", corridor: "now", reserve: "in", cont: "in", stSupply: 0,
};
// allowed values of every switch, default first where it matters for readHash
const ENUM = {
@@ -2850,9 +2852,9 @@ const ENUM = {
capexEnd: ["low", "mid", "high"], galv: ["in", "bought"], galvPrice: ["pheenix", "oms"], cut: ["in", "out"],
metro: ["full", "launch"], cass: ["on", "off"], cdr: ["on", "off"], countCD: ["off", "on"],
solar: ["buy", "ppa", "none"], steamFuel: ["electric", "biomass"], sourcing: ["thai", "china"],
crane: ["buy", "rent"], corridor: ["now", "later"], reserve: ["in", "sep"],
crane: ["buy", "rent"], corridor: ["now", "later"], reserve: ["in", "sep"], cont: ["in", "sep"],
};
const CFGK = ["galv", "galvPrice", "cut", "metro", "cass", "cdr", "countCD", "solar", "steamFuel", "sourcing", "crane", "corridor", "reserve", "stSupply"];
const CFGK = ["galv", "galvPrice", "cut", "metro", "cass", "cdr", "countCD", "solar", "steamFuel", "sourcing", "crane", "corridor", "reserve", "cont", "stSupply"];
// [group, id, label, value, unit, kind, source]
const AS = [];
function grp(g, rows) { rows.forEach(r => AS.push([g].concat(r))); }
@@ -3008,8 +3010,8 @@ rng("Investment blocks (USD unless THB)", [
["dup1", "Newbilt Duplex system, the first", 350000, 370000, "USD", "doc", "total-investment.md: 2024 quote EUR 200,500 for the Single 2.1 plus the quoted second press and extruder ~USD 115k; expired, re-quote"],
["dupN", "Each further Duplex system", 230000, 300000, "USD", "est", "total-investment.md added scope: two presses at the quoted ~115k per press and extruder set, plus hydraulic unit and EX 35; no EX 35 price on file"],
["cla", "Each Newbilt Classic", 290000, 460000, "USD", "est", "total-investment.md: placeholder (the old AD12 band per press); no Newbilt price on file"],
["gPh", "Pheenix galvanics line (PASU, PPPU, PDPU, PMPB), Pheenix estimate", 320000, 650000, "USD", "est", "total-investment.md: no Pheenix price on file"],
["gOms", "Galvanics line priced near OMS's reference", 259000, 359000, "USD", "vendor", "OMS letter, 9 October 2026: 4-cell wet room EUR 224k basic to 311k advanced, at 1.155 USD; used as a price basis for the Pheenix line (investor lever)"],
["gPh", "Pheenix galvanics line (PASU, PPPU, PDPU, PMPB), OMS reference plus 20 %", 324000, 445000, "USD", "est", "total-investment.md (user, October 2026): OMS's 4-cell wet room with the know-how transfer, EUR 234k to 321k (letter, 9 October 2026), plus 20 %; no Pheenix price on file"],
["gOms", "Galvanics wet room bought from OMS directly", 270000, 371000, "USD", "vendor", "OMS letter, 9 October 2026: EUR 224k basic to 311k advanced plus the EUR 10k know-how transfer, at 1.155 USD; reference prices, not a quote"],
["gWat", "Galvanics water: ZLD evaporator and DI water", 3200000, 8500000, "THB", "est", "Utilities CSV rows ZLD 3.0 to 8.0M and RO/DI 0.2 to 0.5M THB, market estimates"],
["wtb", "Water-treatment building next to galvanics, ~12 × 10 m", 1700000, 2900000, "THB", "est", "total-investment.md: estimate at the building rate"],
["sib", "Sibert VSF, VSP, VSC", 250000, 250000, "USD", "vendor", "Sibert's budget confirmation, 5 October 2026; its time, spares, shipping and travel in the freight and working-capital lines"],
@@ -3048,8 +3050,8 @@ grp("Investment rules", [
["craneRent", "Crane rented for the installations, share of the crane block", 40, "%", "est", "Frame stays crane-ready; total-investment.md: saves ~3.7M THB at the midpoint"],
["wcRef", "Records a year the working capital is sized for", 840000, "", "doc", "revenue-model.md launch plant, central volume"],
["wcMin", "Working capital never below", 60, "%", "est", "Estimate"],
["budgetLo", "Investor's budget", 180000000, "THB", "doc", "total-investment.md: the investor's question, 180M THB"],
["budgetHi", "Investor's budget plus 20 %", 216000000, "THB", "doc", "total-investment.md: 216M THB"],
["budgetLo", "Plan A budget", 180000000, "THB", "doc", "total-investment.md: Plan A, ~180M THB without contingency, ramp-up reserve included"],
["budgetHi", "Plan B budget, Plan A plus 25 %", 225000000, "THB", "doc", "total-investment.md: Plan B, ~225M THB (user, October 2026)"],
]);
grp("Ten years of cash", [
["ramp1", "Sold output in year 1", 35, "% of the mature year", "est", "Estimate"],
@@ -3089,9 +3091,10 @@ const CFG0 = Object.fromEntries(CFGK.map(k => [k, S0[k]]));
const LAUNCH = { mode: "target", target: 850000, grow: "classic", dMin: 1, cMin: 1, dFleet: 1, cFleet: 1, ...CFG0 };
const PRESETS = {
launch: LAUNCH,
levers: { ...LAUNCH, solar: "ppa", metro: "launch", galvPrice: "oms", corridor: "later" },
// Plan A (~180M THB) and Plan B (~225M THB): total-investment.md "Two plans", priced without contingency
planA: { ...LAUNCH, solar: "ppa", metro: "launch", corridor: "later", cass: "off", cdr: "off", crane: "rent", sourcing: "china", cont: "sep" },
planB: { mode: "target", target: 1250000, grow: "classic", dMin: 2, cMin: 1, dFleet: 2, cFleet: 1, ...CFG0, cont: "sep" },
lean: { ...LAUNCH, galv: "bought", cut: "out" },
five: { mode: "fleet", dFleet: 2, cFleet: 1, ...CFG0 },
full: { mode: "fleet", dFleet: 5, cFleet: 4, ...CFG0 },
};
@@ -3215,7 +3218,7 @@ function capexOf(r, c = S, boiOn = c.boi !== "off") {
if (r.C > 0) blk("Presses", "cla", r.C === 1 ? "Newbilt Classic" : `Newbilt Classic: ${r.C} ×`, r.C, false, { imp: 1, frt: 1 });
if (galvIn) {
const oms = c.galvPrice === "oms";
blk("Galvanics and stamper prep", oms ? "gOms" : "gPh", `Pheenix galvanics line (PASU, PPPU, PDPU, PMPB), ${oms ? "priced near OMS's reference" : "Pheenix estimate"}`,
blk("Galvanics and stamper prep", oms ? "gOms" : "gPh", oms ? "Galvanics wet room bought from OMS directly (4 cells)" : "Pheenix galvanics line (PASU, PPPU, PDPU, PMPB), OMS reference plus 20 %",
1, false, { imp: 1, frt: 1, frtMid: (A.gPhLo + A.gPhHi) / 2 });
blk("Galvanics and stamper prep", "gWat", "Galvanics water: ZLD evaporator and DI water", 1, true, { imp: 1 });
blk("Galvanics and stamper prep", "wtb", "Water-treatment building next to galvanics, ~12 × 10 m", 1, true);
@@ -3263,15 +3266,16 @@ function capexOf(r, c = S, boiOn = c.boi !== "off") {
const sumOf = (f, k) => rows.filter(f).reduce((a, x) => a + x[k], 0), all = () => true;
const cont = Math.max(0, A.contPct) / 100;
const sub = tri(sumOf(all, "lo"), sumOf(all, "hi"));
const ctg = tri(sub.lo * cont, sub.hi * cont);
const tot = tri(sub.lo + ctg.lo, sub.hi + ctg.hi);
const ctg = tri(sub.lo * cont, sub.hi * cont), ctgIn = c.cont !== "sep";
// Plan A and Plan B are priced without contingency (user, October 2026): then it is shown, not added
const tot = ctgIn ? tri(sub.lo + ctg.lo, sub.hi + ctg.hi) : tri(sub.lo, sub.hi);
const imp = tri(sumOf(x => x.imp, "lo"), sumOf(x => x.imp, "hi"));
const dr = boiOn ? 0 : Math.max(0, A.dutyMach) / 100;
const duty = tri(imp.lo * dr, imp.hi * dr);
const vat = tri(boiOn ? 0 : (imp.lo + duty.lo) * A.vatRate / 100, boiOn ? 0 : (imp.hi + duty.hi) * A.vatRate / 100);
const res = tri(T(A.resLo), T(A.resHi));
const withDuty = tri(tot.lo + duty.lo, tot.hi + duty.hi);
return { rows, sub, ctg, cont, tot, imp, duty, vat, res, all: withDuty, fr, wf, boiOn };
return { rows, sub, ctg, ctgIn, cont, tot, imp, duty, vat, res, all: withDuty, fr, wf, boiOn };
}
// Money a year in USD. Lines: [group, label, USD, basis].
function econ(r, b, boiOn = S.boi !== "off") {
@@ -3607,9 +3611,9 @@ const CFG = [
h: { in: "Fathers, mothers and stampers made here; the plating share of the title fee earns its contribution.",
bought: "Stampers bought in and passed through at cost: no galvanics line, operators, ZLD or water-treatment building." } },
{ k: "galvPrice", t: "Galvanics price basis", sub: "only with galvanics in-house",
o: [["pheenix", "Pheenix estimate"], ["oms", "Near OMS reference"]],
h: { pheenix: () => `${kU("gPh")} USD; no Pheenix price on file.`,
oms: () => `${kU("gOms")} USD: OMS's reference for a 4-cell wet room, EUR 224k to 311k (letter, 9 October 2026).` },
o: [["pheenix", "Pheenix (OMS +20 %)"], ["oms", "OMS directly"]],
h: { pheenix: () => `${kU("gPh")} USD: OMS's reference for a 4-cell wet room plus 20 % (user, October 2026); no Pheenix price on file.`,
oms: () => `${kU("gOms")} USD: OMS's 4-cell wet room, EUR 234k to 321k with the know-how transfer (letter, 9 October 2026).` },
dis: () => S.galv === "bought" ? "Not applicable: the stampers are bought in." : "",
extra: `<label class="row" id="row_stSupply"><span>Stamper supply to other plants, titles a year<br><small>contribution 200 to 333 USD a title (estimate); galvanics in-house only</small></span><input type="number" id="stSupply" min="0" step="50"></label>` },
{ k: "cut", t: "Mastering and cutting room", sub: "Sillitoe SDMS lathe, console and chain, room",
@@ -3654,6 +3658,10 @@ const CFG = [
o: [["in", "In the budget"], ["sep", "Shown separately"]],
h: { in: "Added to the budget bars and to year 0 of the cash view; never to the investment total.",
sep: "Left out of the budget bars and year 0; shown as a line under the investment." } },
{ k: "cont", t: "Contingency", sub: "15 % of the subtotal (Assumptions)",
o: [["in", "In the total"], ["sep", "Shown separately"]],
h: { in: "Added to the investment total, as in the launch plant roll-up of total-investment.md.",
sep: "Left out of the total, as Plan A and Plan B are priced (user, October 2026); shown as a line under the subtotal." } },
];
function buildCfg() {
$("cfg").innerHTML = CFG.map(c => `<div class="cfg" id="cfg_${c.k}"><div class="ct">${c.t}<small>${c.sub}</small></div>` +
@@ -3700,8 +3708,8 @@ function renderInvest(e, r) {
const TR = (l, o, s, tot) => H.push(`<tr${tot ? ' class="tot"' : ""}><td>${l}</td><td class="n">${money(o.lo)}</td><td class="n">${money(o.mid)}</td><td class="n">${money(o.hi)}</td><td class="src">${s || ""}</td></tr>`);
H.push(`<tr class="grp"><td colspan="5">Totals</td></tr>`);
TR("Subtotal", cx.sub, "", true);
TR(`Contingency ${f1(A.contPct)} %`, cx.ctg, "total-investment.md");
TR("Total investment", cx.tot, "for reference, the launch plant in total-investment.md: ~161M to 292M THB, midpoint ~227M", true);
TR(cx.ctgIn ? `Contingency ${f1(A.contPct)} %` : `Contingency ${f1(A.contPct)} % (not in the total)`, cx.ctg, cx.ctgIn ? "total-investment.md" : "shown separately, as in Plan A and Plan B");
TR("Total investment", cx.tot, "for reference, total-investment.md: the launch plant ~161M to 285M THB with contingency (midpoint ~223M); Plan A ~162M and Plan B ~209M at the midpoint without it, plus the ramp-up reserve", true);
if (!cx.boiOn) {
TR(`Import duty without BOI, ${f1(A.dutyMach)} %`, cx.duty, `on ${money(cx.imp.lo)} to ${money(cx.imp.hi)} of imported machinery; import VAT ${money(cx.vat.mid)} at the midpoint, reclaimable`);
TR("Total with import duty", cx.all, "", true);
@@ -3709,7 +3717,7 @@ function renderInvest(e, r) {
TR("Ramp-up reserve (not in the total)", cx.res, S.reserve === "in" ? "in the budget bars and year 0 of the cash view" : "shown separately");
if (S.reserve === "in") TR("Total with the reserve", { lo: cx.all.lo + cx.res.lo, mid: cx.all.mid + cx.res.mid, hi: cx.all.hi + cx.res.hi }, "what the budget card compares", true);
$("ivT").innerHTML = `<thead><tr><th>Block</th><th class="n">Low</th><th class="n">Mid</th><th class="n">High</th><th>Basis</th></tr></thead><tbody>${H.join("")}</tbody>`;
$("ivK").innerHTML = kpi(money(cx.all.lo), "low end, contingency included") + kpi(money(cx.all.mid), "midpoint") +
$("ivK").innerHTML = kpi(money(cx.all.lo), cx.ctgIn ? "low end, contingency included" : "low end, without contingency") + kpi(money(cx.all.mid), "midpoint") +
kpi(money(cx.all.hi), "high end") + kpi(thbM(cx.all.mid), `midpoint in THB (${thbM(cx.all.lo)} to ${thbM(cx.all.hi)})`);
const fl = [];
if (r.nb > A.modRooms) fl.push(["warn", `${r.nb} boiler modules: beyond the boiler hall built for ${f0(A.modRooms)}; a second hall is not in the investment.`]);
@@ -3735,7 +3743,7 @@ function renderBudget(e) {
`<div class="bgap">against ${M(B1)}: ${gap(tot, B1)} · against ${M(B2)}: ${gap(tot, B2)}</div>`;
}).join("");
const mid = V[1], mt = mid.cap + mid.res, rLo = cx.res.lo * fx, rHi = cx.res.hi * fx;
$("budK").innerHTML = kpi(M(mid.cap) + " THB", "investment at the midpoint, contingency included") +
$("budK").innerHTML = kpi(M(mid.cap) + " THB", `investment at the midpoint, ${cx.ctgIn ? "contingency included" : "without contingency"}`) +
kpi(`${M(mid.cap + rLo)} to ${M(mid.cap + rHi)}`, `THB with the ramp-up reserve (${M(rLo)} to ${M(rHi)})`) +
kpi(mt <= B2 ? M(B2 - mt) : "+" + M(mt - B2), `${mt <= B2 ? "to spare under" : "over"} ${M(B2)} THB at the midpoint${inB ? ", reserve included" : ""}`);
const verdict = mt <= B2 ? `Mid fits ${M(B2)} with ${M(B2 - mt)} to spare` : `Mid is ${M(mt - B2)} over ${M(B2)}`;