Labour from base salaries; health care with check-ups for all staff
Deploy test site / deploy (push) Successful in 34s

User, October 2026: option to enter base salaries; the company should
provide proper extra health care with check-ups for all staff.

- worker-safety.md: new "Health care for all staff" policy (decided
  October 2026): group health insurance (inpatient and outpatient) and a
  yearly check-up for everyone, office included, on top of social
  security; the occupational checks Thai law requires for risk-factor
  work (Ministerial Regulation B.E. 2547: within 30 days, then yearly, by
  an occupational physician, records kept 2 years after the job ends) for
  the press floor, galvanics and maintenance; planning costs with sources
  checked October 2026 (group insurance ~8,000 to 18,006 THB a head a
  year, plan ~15,000; Rajavej Chiang Mai check-ups 1,290 to 5,990 THB,
  plan ~2,500 plus ~1,500 risk-factor tests, estimates); social security
  5 % up to the 17,500 THB ceiling since 1 January 2026.
- revenue-model.md: cutting engineer 50,000 THB is base salary; health
  care on top of the x1.3 (~0.35M THB a year at the start fleet); what the
  x1.3 is made of.
- Fleet calculator: base salaries per role (the revenue model loaded
  rates / 1.3; cutting engineer 50,000), employer social security 5 %
  capped at 17,500, 25 % overtime, bonus and PPE, health insurance and
  check-ups for all staff incl. 4 office staff (scale.md 3 to 5), risk-
  factor tests for the press floor, galvanics and maintenance; all as
  editable assumptions; people table gains office and all-staff rows.
  Defaults: labour 5.25M THB (was 4.67M loaded), direct cost 1.90 USD a
  record, EBITDA 2.19M USD.
This commit is contained in:
grabowski committed 2026-10-10 20:17:39 +07:00
1 parent eac45eb234
commit 1a368287f8
4 files changed
+89 -19

No files matched your search

+40 -16
View File
@@ -334,8 +334,13 @@ document.documentElement.setAttribute("data-theme",t);})();
the plant. Lacquer blanks (70 EUR each) and the cutting engineer (50,000 THB a month) are team
figures; shipping cartons have no figure on file and are an estimate. Missing as the revenue model
lists: indirect labour, freight, inventory, effluent, commission, styli, extra galvanics units' capex.</p>
<p><b>Not scaled here:</b> admin, sales and logistics (in the overheads line); cover for leave
and sickness.</p>
<p><b>Labour.</b> Base salaries per role (revenue-model.md, its loaded rates divided by 1.3; the cutting
engineer a team figure), plus the employer's 5 % social security up to the 17,500 THB ceiling, 25 %
for overtime, bonus and PPE, and the company's health care for every member of staff, office
included: group health insurance, a yearly check-up, and the risk-factor tests Thai law requires
for the press floor, galvanics and maintenance (worker-safety.md).</p>
<p><b>Not scaled here:</b> admin, sales and logistics wages (in the overheads line); cover for
leave and sickness.</p>
<p>Sources in the CNX Vinyl planning repository: equipment/pressing-line.md,
equipment/newbilt/ (Duplex site requirements V14, offer 240620-04, 2026 flyers),
equipment/galvanics.md (cycle times per step; the stamper line counts each station from them,
@@ -450,15 +455,24 @@ grp("Energy", [
["heatPrice", "Steam heat from biomass", 0.7, "THB/kWh", "doc", "biomass.md: 0.5 to 0.9 at 1.35 to 2 THB/kg delivered"],
["steamKWh", "Heat per kg of steam", 0.68, "kWh/kg", "est", "Estimate: 10 bar saturated steam from ~80 °C feed water"],
]);
grp("Labour (THB a month a head, loaded ×1.3)", [
["wageOp", "Press and finishing operator", 19500, "THB", "doc", "revenue-model.md labour build-up"],
["wageQC", "QC", 26000, "THB", "doc", "revenue-model.md"],
["wageMaint", "Maintenance", 32500, "THB", "doc", "revenue-model.md"],
["wageGalv", "Galvanics", 32500, "THB", "doc", "revenue-model.md"],
["wageCut", "Cutting engineer", 50000, "THB", "user", "Team figure, October 2026 (revenue-model.md), taken as the full monthly cost"],
grp("Labour: base salaries, THB a month", [
["salOp", "Press and finishing operator", 15000, "THB", "doc", "revenue-model.md: ~19,500 loaded at ×1.3"],
["salQC", "QC", 20000, "THB", "doc", "revenue-model.md: ~26,000 loaded"],
["salMaint", "Maintenance", 25000, "THB", "doc", "revenue-model.md: ~32,500 loaded"],
["salGalv", "Galvanics", 25000, "THB", "doc", "revenue-model.md: ~32,500 loaded"],
["salCut", "Cutting engineer", 50000, "THB", "user", "Team figure, October 2026 (revenue-model.md)"],
["officeHeads", "Admin, sales and logistics staff", 4, "people", "doc", "scale.md: 3 to 5; their wages sit in the overheads, they count here for health care"],
["qcPerShift", "QC inspectors per shift", 1, "", "est", "Estimate; scale.md: 1 to 2 QC"],
["pressPerTech", "Presses per maintenance technician (at least 2)", 8, "", "est", "Estimate; scale.md: 2 in maintenance for the start fleet"],
]);
grp("Labour: on-costs and health care", [
["ssRate", "Employer social security", 5, "% of salary", "doc", "Social Security Act, section 33: 5 % up to the wage ceiling (worker-safety.md)"],
["ssCap", "Social security wage ceiling", 17500, "THB a month", "doc", "Raised from 15,000 on 1 January 2026, so at most 875 THB a month (worker-safety.md)"],
["otherOn", "Overtime, bonus, PPE and other on-costs", 25, "% of salary", "doc", "With social security this is the revenue model's ×1.3 at 15,000 THB"],
["healthIns", "Group health insurance (inpatient and outpatient)", 15000, "THB a head a year", "doc", "worker-safety.md: AXA SME plans 9,394 to 18,006 THB at 25 staff (CheckDi, October 2026); local plans 8,000 to 12,000"],
["checkup", "Annual health check-up", 2500, "THB a head a year", "est", "Estimate for an age mix; Rajavej Chiang Mai 2026 programmes 1,290 to 5,990 THB by age (worker-safety.md)"],
["occCheck", "Risk-factor tests (hearing, lungs, metals)", 1500, "THB a head a year", "est", "Estimate; required yearly for press floor, galvanics and maintenance (Ministerial Regulation B.E. 2547, worker-safety.md)"],
]);
grp("Planned plant, for the checks", [
["boiler", "Steam boiler", 400, "kg/h", "doc", "scale.md: sized for one Duplex system"],
["chiller", "Chiller", 250, "kW", "doc", "scale.md: sized for one Duplex system"],
@@ -608,12 +622,20 @@ function econ(r, b) {
add("energy", "Electricity", t2u((elecMWh - pvMWh) * 1000 * A.kwhPrice + demandKW * A.demandPrice * 12),
`${f0(elecMWh - pvMWh)} MWh from the grid after ${f0(pvMWh)} MWh roof PV; ~${f0(demandKW)} kW demand`);
add("energy", "Steam heat (biomass)", t2u(heatMWh * 1000 * A.heatPrice), `${f0(steamT)} t of steam, ${f0(heatMWh)} MWh of heat`);
const h = headsOf(r), wage = (n, w) => t2u(n * w * 12), ppl = n => n + (n === 1 ? " person" : " people");
add("lab", "Press operators and finishing", wage(h.ops, A.wageOp), ppl(h.ops));
add("lab", "QC", wage(h.qc, A.wageQC), ppl(h.qc));
add("lab", "Maintenance", wage(h.maint, A.wageMaint), ppl(h.maint));
add("lab", "Galvanics", wage(h.galv, A.wageGalv), ppl(h.galv));
add("lab", "Cutting engineers", wage(h.cut, A.wageCut), ppl(h.cut));
const h = headsOf(r), ppl = n => n + (n === 1 ? " person" : " people");
let base = 0, ss = 0;
for (const [l, n, sal] of [["Press operators and finishing", h.ops, A.salOp], ["QC", h.qc, A.salQC],
["Maintenance", h.maint, A.salMaint], ["Galvanics", h.galv, A.salGalv], ["Cutting engineers", h.cut, A.salCut]]) {
add("lab", l, t2u(n * sal * 12), `${ppl(n)} at ${f0(sal)} THB a month`);
base += n * sal * 12; ss += n * Math.min(sal, A.ssCap) * A.ssRate / 100 * 12;
}
add("lab", "Employer social security", t2u(ss), `${f1(A.ssRate)} % of salary up to ${f0(A.ssCap)} THB a month`);
add("lab", "Overtime, bonus, PPE", t2u(base * A.otherOn / 100), `${f1(A.otherOn)} % of base salaries`);
h.prod = h.ops + h.qc + h.maint + h.galv + h.cut; h.office = Math.max(0, Math.round(A.officeHeads));
h.all = h.prod + h.office; h.exposed = h.ops + h.galv + h.maint;
add("lab", "Health insurance, all staff", t2u(h.all * A.healthIns), `${ppl(h.all)}, office included, at ${f0(A.healthIns)} THB a year`);
add("lab", "Health check-ups, all staff", t2u(h.all * A.checkup + h.exposed * A.occCheck),
`yearly for everyone, plus risk-factor tests for ${ppl(h.exposed)} on the press floor, in galvanics and maintenance`);
const sum = gs => L.filter(l => gs.includes(l[0])).reduce((a, l) => a + l[2], 0);
const direct = L.reduce((a, l) => a + l[2], 0), contrib = rev - direct, ebitda = contrib - A.overheads;
const capex = A.capexBase + (r.D - 2) * A.capexD + (r.C - 1) * A.capexC, depr = capex / Math.max(1, A.deprYears);
@@ -735,7 +757,7 @@ function render() {
PR("Splatter and specials", e.revSpec, `${big(r.sp)} at ${unit(S.priceStd + S.premSpec)}`);
PR("Title services", e.revTitle, `${f0(r.titles)} titles at ${money(S.titleFee)}`);
PR("Revenue", e.rev, "", true);
const GRP = [["Materials, packaging, upkeep", ["mat", "pack", "maint"]], ["Stamper making", ["metal"]], ["Energy", ["energy"]], ["Labour, loaded wages", ["lab"]]];
const GRP = [["Materials, packaging, upkeep", ["mat", "pack", "maint"]], ["Stamper making", ["metal"]], ["Energy", ["energy"]], ["Labour and health care", ["lab"]]];
for (const [t, gs] of GRP) { PG(t); e.L.filter(l => gs.includes(l[0])).forEach(l => PR(l[1], l[2], l[3])); }
PG("Result");
PR("Direct cost", e.direct, "everything above", true);
@@ -758,7 +780,9 @@ function render() {
R("Maintenance technicians", e.heads.maint, `one per ${f0(A.pressPerTech)} presses, at least 2 (estimate)`);
R("Galvanics operators (day work)", r.galv, `${f1(r.partsDay)} parts a day at about ${f0(A.galvRate)} per person (OMS)`);
R("Cutting engineers (day work)", e.heads.cut, "one per lathe (estimate)");
R("Production headcount", e.heads.ops + e.heads.qc + e.heads.maint + e.heads.galv + e.heads.cut, "admin, sales and logistics sit in overheads; add cover for leave");
R("Production headcount", e.heads.prod, "add cover for leave");
R("Admin, sales and logistics", e.heads.office, "wages in the overheads (scale.md 3 to 5)");
R("All staff, with health insurance and yearly check-ups", e.heads.all, `risk-factor tests for ${e.heads.exposed} (press floor, galvanics, maintenance)`);
G("Machines");
R("HRP 12 presses (Duplex)", 2 * r.D, "");
R("Classic automatic presses", r.C, "extruder built in; trimming not stated by Newbilt");