Add investor business plan (draft 0.1) with its Python builder
Leased land instead of purchase, in-house cassette duplication, CD-R duplication with CD replication shown as a separate investor decision, Ploy Printing as packaging partner and intended co-investor, and a dedicated section on traceability, metrology, QC and efficiency.
This commit is contained in:
1 parent
1bbef055c4
commit
3934dc1dc4
7 files changed
+1860
No files matched your search
@@ -110,11 +110,21 @@ The detail lives in dedicated documents:
|
||||
- [Revenue model](docs/finance/revenue-model.md): rough full-capacity
|
||||
revenue (~3.7M to 6.8M USD/yr), direct cost and margin per record, and
|
||||
the levers and caveats behind the numbers
|
||||
- [Business plan](docs/business-plan/CNX-Vinyl-Business-Plan.docx): the
|
||||
investor-facing roll-up of everything above, built from
|
||||
`docs/business-plan/build/` (run `python build_plan.py` to regenerate
|
||||
after the numbers move). Draft 0.1 carries the land-lease decision, the
|
||||
cassette and CD scope, and Ploy Printing as packaging partner and
|
||||
intended co-investor
|
||||
|
||||
## Repository Layout
|
||||
|
||||
```
|
||||
docs/
|
||||
├── business-plan/
|
||||
│ ├── CNX-Vinyl-Business-Plan.docx Investor business plan (generated)
|
||||
│ └── build/ Python source of the plan; regenerate with
|
||||
│ build_plan.py
|
||||
├── consulting/ Consultancy proposals and overviews
|
||||
├── equipment/
|
||||
│ ├── newbilt/ Offers, site requirements, process schematic,
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
"""Build the CNX Vinyl business plan .docx.
|
||||
|
||||
python build_plan.py -> ../CNX-Vinyl-Business-Plan.docx
|
||||
python build_plan.py out.docx -> custom path
|
||||
|
||||
Images are resolved relative to docs/ (the parent of business-plan/).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
sys.path.insert(0, str(HERE))
|
||||
|
||||
from plan_lib import Renderer # noqa: E402
|
||||
import content_a, content_b, content_c # noqa: E402
|
||||
|
||||
DOCS = HERE.parent.parent # .../docs
|
||||
OUT = HERE.parent / "CNX-Vinyl-Business-Plan.docx"
|
||||
|
||||
|
||||
def main(out: Path = OUT) -> Path:
|
||||
r = Renderer(DOCS, header_text="CNX Vinyl / Poy Records: Business plan, draft 0.1, September 2026",
|
||||
footer_text="Confidential")
|
||||
r.render(content_a.blocks() + content_b.blocks() + content_c.blocks())
|
||||
r.save(out)
|
||||
return out
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
target = Path(sys.argv[1]) if len(sys.argv) > 1 else OUT
|
||||
print(main(target))
|
||||
@@ -0,0 +1,522 @@
|
||||
"""Part A: cover, reading guide, executive summary, opportunity, anatomy of the plant."""
|
||||
from plan_lib import H1, H2, H3, P, B, N, T, KV, IMG, CALL, PB, TOC, COVER
|
||||
|
||||
|
||||
def blocks():
|
||||
out = []
|
||||
|
||||
# ------------------------------------------------------------ cover
|
||||
out.append(COVER(
|
||||
kicker="CNX VINYL / POY RECORDS",
|
||||
title="What It Takes to Build a Top-Notch Vinyl Plant",
|
||||
subtitle="Business plan for a full-service vinyl, cassette and CD manufacturing plant "
|
||||
"in the Chiang Mai region, Northern Thailand",
|
||||
image="renders/aerial-hero.png",
|
||||
lines=[
|
||||
"**Prepared for:** the project's lead investor",
|
||||
"**Prepared by:** Simona Meesaiyati (Project Lead) and Alexander Grabowski (Technical Consultant), "
|
||||
"supported by buildfor.life, with Ploy Printing (Chiang Mai) as packaging partner and "
|
||||
"strategic co-investor",
|
||||
"**Version:** Draft 0.1, September 2026",
|
||||
],
|
||||
note="Confidential. All figures in USD and THB at 1 USD = 33 THB (August 2026). Vendor quotes are "
|
||||
"snapshots at their file dates, not commitments. Every number in this plan is labelled as quoted, "
|
||||
"researched, or estimated; see section 2.",
|
||||
))
|
||||
out.append(TOC())
|
||||
|
||||
# ------------------------------------------------ 1 executive summary
|
||||
out += [
|
||||
H1("1. Executive summary"),
|
||||
P("CNX Vinyl (label name Poy Records) is a planned record manufacturing plant for the Chiang Mai "
|
||||
"region of Northern Thailand. Its premise is simple: a label or artist in Asia-Pacific should be "
|
||||
"able to send an audio file to one place and receive finished, packaged, fully traceable records at "
|
||||
"their door, without shipping a fragile lacquer disc across the world and without waiting for metal "
|
||||
"stampers to come back from Europe. Today that customer presses in Europe, the United States or "
|
||||
"Japan. The one operating pressing shop in Thailand presses only: it buys its stampers abroad, runs "
|
||||
"without climate control, and cannot economically serve runs under 300 units."),
|
||||
P("This document explains, for a reader who is not a vinyl specialist, what a top-notch plant "
|
||||
"consists of, why each part matters, what it costs, what it can earn, how long it takes, and what "
|
||||
"could go wrong. It is built from the project's planning repository and vendor documents, and it "
|
||||
"keeps the honest parts: where a number is an estimate, it says so; where the evidence is thin, it "
|
||||
"says that too."),
|
||||
H2("What we will build"),
|
||||
B([
|
||||
"**The complete vinyl chain under one roof:** mastering, lacquer cutting, galvanics (making the "
|
||||
"nickel stampers), pressing, finishing, packaging with our Chiang Mai print partner Ploy "
|
||||
"Printing, and fulfilment including direct-to-fan dropshipping.",
|
||||
"**A mixed press fleet:** semi-automatic presses for colour, splatter and short runs, fully "
|
||||
"automatic presses for volume. Launch with one semi-automatic line (90,000 to 135,000 records a "
|
||||
"year); grow to four presses (1.0 to 1.5 million records a year).",
|
||||
"**Cassette duplication in-house** and **short-run CD duplication** from launch. Full CD "
|
||||
"replication (injection-moulded discs) is a separate, much larger decision; both CD options are "
|
||||
"priced in section 4.9 for the investor to decide.",
|
||||
"**A purpose-built 1,500 m² plant on long-term leased land**, with a detached utility yard, "
|
||||
"a rooftop solar array, and room for a second building. The land is now planned as a "
|
||||
"registered long lease rather than a purchase, which lowers upfront capital by roughly "
|
||||
"USD 0.4 to 1.0 million.",
|
||||
]),
|
||||
H2("What makes it top-notch"),
|
||||
P("Four things separate this plant from a pressing shop, and they are the reason it can charge "
|
||||
"export-grade prices in a region that currently has none:"),
|
||||
N([
|
||||
"**Traceability from audio file to shipment.** Every lacquer, father, mother, stamper, press "
|
||||
"cycle, compound lot and carton lot gets an identity that knows its parents. A customer sees "
|
||||
"their order's full chain; a complaint resolves to one stamper, one press, one shift and one "
|
||||
"material lot. Machine data comes from the machines, not from paperwork.",
|
||||
"**Metrology and layered quality control.** The nickel parts are measured without contact "
|
||||
"(laser confocal, wide-area profiler, XRF thickness) and logged per serial, which is ahead of "
|
||||
"industry practice. Every pressed record passes 100% inline optical inspection, every press "
|
||||
"cycle is logged, and sampled records are compared automatically against the source master.",
|
||||
"**Efficiency designed in.** Heat is recovered before energy is generated (the presses' own "
|
||||
"60 °C cooling return pre-heats the boiler feedwater), ~290 kWp of rooftop solar at phase 1 "
|
||||
"grows to ~500 kWp with the second building, a 271 kWh battery shaves peaks, cooling runs as a "
|
||||
"closed loop, no process water is discharged, and 20% of the feedstock is in-house regrind. One "
|
||||
"operator runs up to four automatic presses.",
|
||||
"**Climate control as product quality.** The whole plant runs at 20 to 26 °C, 35 to 60% "
|
||||
"relative humidity, with particulate filtration sized for Northern Thailand's burning season. "
|
||||
"Moisture and dust are the two classic causes of noisy records; the incumbent has neither "
|
||||
"control.",
|
||||
]),
|
||||
H2("The numbers"),
|
||||
KV([
|
||||
["Capacity", "Launch ~90k to 135k records/yr (1 semi-automatic line, 1 shift); mature ~1.0M to "
|
||||
"1.5M records/yr (2 semi-automatic + 2 automatic presses, 2 shifts)"],
|
||||
["Site", "~1,500 m² main building plus ~180 m² utility yard on ~5 to 8 rai of leased land; "
|
||||
"second 1,710 m² building as expansion"],
|
||||
["Investment, full vision", "~USD 4.4M to 8.0M (~THB 144M to 264M) including 15% contingency "
|
||||
"(was 4.7M to 8.9M before the decision to lease the land)"],
|
||||
["Investment, phase 1 launch", "~USD 2.2M to 3.9M (~THB 73M to 128M) including the building, "
|
||||
"one press line, climate control, rooftop solar, cassette and CD-R "
|
||||
"lines, working capital and contingency"],
|
||||
["Land lease", "Estimated THB 0.3M to 1.0M per year (USD 9k to 30k), to be replaced by the "
|
||||
"negotiated figure"],
|
||||
["Revenue at maturity", "~USD 3.7M to 6.8M per year (~THB 122M to 224M)"],
|
||||
["Operating result at maturity", "Central case EBITDA ~USD 2.6M, operating profit ~USD 2.0M, "
|
||||
"payback 2 to 4 years. Adverse case is EBITDA-positive but "
|
||||
"does not cover depreciation. Utilisation and price "
|
||||
"realisation decide the outcome, not upside."],
|
||||
["First paying release", "Q4 2028 to mid-2029 if the gates below start now; one regulatory "
|
||||
"question (ordering machinery before the BOI certificate) could pull "
|
||||
"that forward by 6 to 9 months"],
|
||||
["People", "~15 to 25 at maturity across shifts, fewer than half that at launch"],
|
||||
]),
|
||||
H2("Where we are, and what we ask"),
|
||||
P("The project is at the planning stage. Industry outreach has run for several years; vendor quotes "
|
||||
"exist for the pressing line, utilities have been costed with Thai suppliers, a parametric building "
|
||||
"model has produced quantities for a construction price request, and a candidate parcel in Mueang "
|
||||
"Chi, Lamphun has been tested against the design. Nothing has been ordered."),
|
||||
P("Three gates run now and in parallel: **market validation** with regional labels and at the Making "
|
||||
"Vinyl trade show in Haarlem (autumn 2026); the **BOI investment promotion** track with a "
|
||||
"registered consultant; and **land**: zoning verification, lease terms, and a soil test. Nothing "
|
||||
"large is spent until its gate clears."),
|
||||
CALL("What the investor is asked to fund now", [
|
||||
"**Phase 0 (next 3 to 6 months): ~USD 40k to 80k** for the BOI consultant, Thai legal work on "
|
||||
"the lease and zoning, a soil boring test, the trade-show trip, firm re-quotes from the press, "
|
||||
"galvanics and building vendors, and optional specialist consultancy.",
|
||||
"**Phase 1 (commitment after the BOI certificate and validated pricing): ~USD 2.2M to 3.9M** "
|
||||
"released in tranches against milestones: lease signed, press order, building contract, "
|
||||
"fit-out, commissioning.",
|
||||
"**Decisions needed from the investor:** CD scope (duplication only, or full replication), "
|
||||
"whether galvanics moves into phase 1, and the equity/debt mix (BOI requires debt-to-equity "
|
||||
"of at most 3:1).",
|
||||
"**Co-investment:** Ploy Printing, the Chiang Mai print and packaging house that will produce "
|
||||
"the plant's jackets and sleeves, has expressed the intention to invest alongside the lead "
|
||||
"investor. Terms are not yet discussed; the intended structure is described in section 12.",
|
||||
]),
|
||||
|
||||
# ----------------------------------------------- 2 reading guide
|
||||
H1("2. How to read the numbers"),
|
||||
P("Every figure in this plan carries one of three confidence levels. Where a table does not say, "
|
||||
"the basis column or caption does."),
|
||||
T(["Label", "Meaning", "Examples"], [
|
||||
["Quoted", "A vendor document exists, in its original currency. Quotes may have expired and "
|
||||
"need re-confirmation.", "Newbilt pressing line (EUR 200,500, June 2024); Newbilt "
|
||||
"service rates; Gobel battery list price"],
|
||||
["Researched", "Published or market data gathered by the team, including one set of real Thai "
|
||||
"invoices.", "Thai utility supplier ranges; Pheenix AD12 datasheet; domestic "
|
||||
"pressing prices, August 2026; BOI measures"],
|
||||
["Estimate", "Engineering ratio or industry norm with no vendor confirmation. Treat as "
|
||||
"plus or minus 30 to 50%.", "Building cost per m²; galvanics line; mastering "
|
||||
"room; metrology stack; land lease; all cassette and "
|
||||
"CD figures"],
|
||||
], widths_mm=[22, 70, 78]),
|
||||
P("Currency: USD and THB at 1 USD = 33 THB (5 August 2026). Vendor quotes issued in EUR or GBP are "
|
||||
"kept in their currency next to their source and converted at 1 EUR = 1.155 USD and "
|
||||
"1 GBP = 1.35 USD. Currency exposure is real and is discussed in section 8.5."),
|
||||
P("Deliberately **not** in this plan: a financing structure or terms (to be agreed with the investor); "
|
||||
"a signed customer; a signed lease; firm quotes for the automatic presses, the galvanics line, the "
|
||||
"mastering room or the building. Each of these is a named next step in section 14."),
|
||||
|
||||
# ------------------------------------------------- 3 opportunity
|
||||
H1("3. The opportunity"),
|
||||
H2("3.1 The product"),
|
||||
P("A vinyl record is made by cutting the audio into a soft lacquer disc, growing a set of nickel "
|
||||
"metal parts from that lacquer by electroplating (the 'stampers'), and pressing hot PVC between two "
|
||||
"stampers under about 150 tonnes of force with steam heating and water cooling. The record is then "
|
||||
"trimmed, labelled, sleeved, put into a printed jacket, shrink-wrapped and shipped. Each of these "
|
||||
"steps has its own equipment, skills and failure modes, and most plants in the world do only some "
|
||||
"of them, sending the rest out to specialists, often on other continents."),
|
||||
P("Demand for vinyl has grown for well over a decade from a small base. The 2021 to 2022 boom "
|
||||
"pushed lead times at European and US plants to six to nine months; they have since normalised to "
|
||||
"roughly 10 to 16 weeks for a standard black LP, and global capacity has been added. Demand is "
|
||||
"cyclical and this plan does not assume it only rises; it assumes the plant can sell what it "
|
||||
"presses, which is the single assumption most worth testing (section 3.3)."),
|
||||
H2("3.2 Where regional labels press today"),
|
||||
P("Labels and artists in Southeast Asia, Australia and much of East Asia ship their masters to "
|
||||
"Europe, the United States or Japan, wait in those plants' queues, and pay freight and time zones "
|
||||
"on top. There is no industrial-grade, climate-controlled, full-service plant in the region. That "
|
||||
"is the gap. It is also the plan's biggest assumption: that enough regional customers will pay for "
|
||||
"a closer, faster, more accountable plant at a price that works. That pricing power is unproven and "
|
||||
"the market validation gate exists to test it before capital is committed."),
|
||||
H2("3.3 What the Thai market shows: one real data point"),
|
||||
P("In August 2026 the team compared real invoices from the one operating pressing shop in Thailand, "
|
||||
"ResurRec in Bangkok (with whom a collaboration is also under discussion), and toured its plant. "
|
||||
"This is one buyer, one plant, one afternoon, and it is deliberately not used to re-base the "
|
||||
"financial model. But it is the only real market evidence so far, and it points in a useful "
|
||||
"direction."),
|
||||
T(["Item", "THB", "USD", "Note"], [
|
||||
["Audio mastering", "5,000 to 20,000 per song", "150 to 605", "Market range from several engineers"],
|
||||
["Stamper set (father, mother, silvering)", "up to 22,000 per title", "up to 665",
|
||||
"Residual between a full-service and a pressing-only invoice; an upper bound that also carries "
|
||||
"cutting, test pressings and margin"],
|
||||
["All-in per 12-inch record at 300 units", "650 to 950", "19.70 to 28.80",
|
||||
"Includes a printed jacket; discounts of 5% at 500, 15% at 1,000 to 3,000, 30% above 3,000, "
|
||||
"with 2 to 4 weeks added lead time above 3,000"],
|
||||
["Minimum run", "150 units, 'rarely accepted'", "", "Sub-300 runs are effectively unavailable "
|
||||
"in Thailand"],
|
||||
], widths_mm=[42, 36, 24, 68],
|
||||
caption="Researched: buy-side invoices and published discount ladder, August 2026."),
|
||||
P("What the tour showed: the incumbent operates from a small non-industrial premises; it does not "
|
||||
"plate (stampers are bought in from abroad and resold); there is no temperature, humidity or "
|
||||
"particulate control on the pressing floor; and it runs two Newbilt presses, one fully automatic "
|
||||
"and one semi-automatic, which is a real capital fleet that on paper out-presses our launch "
|
||||
"configuration two to three times over."),
|
||||
P("Four readings follow, each with its caveat:"),
|
||||
B([
|
||||
"**Domestic willingness to pay is higher than our model assumes.** The plan prices a record at "
|
||||
"USD 3.50 to 4.60 blended (Western export benchmark). The Thai market pays 535 to 780 THB on a "
|
||||
"comparable scope, several times more, even after allowing 100 to 150 THB for the jacket. The "
|
||||
"caveat: a sole domestic supplier can hold an import-parity price that a second entrant "
|
||||
"compresses. We kept the conservative price until a second data point exists.",
|
||||
"**The floor of the market is unserved.** Runs under 300 are uneconomic for a shop whose "
|
||||
"per-title cost is an imported stamper plus outsourced mastering. In-house cutting and plating "
|
||||
"collapse that fixed cost, which makes short runs viable for CNX in a way they structurally are "
|
||||
"not for the incumbent.",
|
||||
"**Lead time degrades with run size, probably because of metalwork.** Three thousand units is "
|
||||
"about two days of pressing on their fleet, yet they add two to four weeks. A stamper lasts "
|
||||
"1,000 to 2,000 pressings, so a 3,000-unit run needs two or three sets, each an overseas order. "
|
||||
"Jacket printing or floor space could produce the same symptom, and the cause should be "
|
||||
"confirmed before it is treated as a structural weakness.",
|
||||
"**The gap we close is capability, not capacity.** No plating, no climate control, no metrology, "
|
||||
"no traceability. The incumbent sells commercially from that room today, which bounds how much "
|
||||
"the Thai market currently pays for the difference. Our bet is on export-grade customers and on "
|
||||
"regional labels who currently ship to Europe.",
|
||||
]),
|
||||
CALL("Investor takeaway", "CNX should not fight a price war at 200-unit runs against an operator "
|
||||
"with low rent and no climate-control load. It competes on what a non-plating, unconditioned "
|
||||
"shop cannot offer at any run size: export-grade quality control and traceability, titles that "
|
||||
"need plating, consistent output through the burning season, repress in days from an archived "
|
||||
"mother, and short runs. A stamper-supply relationship with the incumbent is a plausible early "
|
||||
"revenue line for the galvanics room and is under discussion."),
|
||||
H2("3.4 Cassettes and CDs"),
|
||||
P("The investor has asked for cassette and CD capability on the site. Both fit the same customer: "
|
||||
"the independent label that presses 300 to 2,000 records also orders 100 to 500 cassettes and a "
|
||||
"short run of CDs for the same release, and prefers one supplier, one artwork approval and one "
|
||||
"shipment. Cassette sales have grown from a very low base in the US and UK over the last decade; "
|
||||
"volumes are small and the equipment market is mostly refurbished, but the format is a natural "
|
||||
"add-on with modest capital. CDs remain a large format in Asia (Japan and Korea in particular), "
|
||||
"but industrial CD replication is an established, price-competitive business with existing "
|
||||
"capacity across the region. Section 4.8 and 4.9 lay out both formats and the two very different "
|
||||
"CD routes."),
|
||||
|
||||
# ------------------------------------------ 4 anatomy of the plant
|
||||
H1("4. Anatomy of a top-notch plant: the full chain"),
|
||||
P("The chain from audio file to delivered record has nine stages and three points where the artist or "
|
||||
"label must approve before work continues. Every stage writes to the traceability database, and "
|
||||
"the instruments behind every check are specified in section 5. The chart on the next page shows "
|
||||
"the whole chain; the table after it is the checklist of what a top-notch plant needs and where "
|
||||
"this plan puts each item."),
|
||||
PB(),
|
||||
IMG("process-flow.png", 138, "Figure 1. Process flow from audio file to delivered product, with every "
|
||||
"check and the three artist approval gates. Source: project repository."),
|
||||
PB(),
|
||||
H2("4.0 The checklist"),
|
||||
T(["What a top-notch plant needs", "Why it matters", "This plan", "When"], [
|
||||
["A quiet, vibration-free cutting room with a modern lathe", "The groove is born here; every "
|
||||
"defect propagates to every record", "Agnew Analog Type 612 lathe and Type 602 head, "
|
||||
"in-house at maturity; brokered through a European partner at launch", "Phase 5 (earlier if "
|
||||
"demand shows)"],
|
||||
["In-house galvanics with zero-discharge water", "The regional bottleneck; enables repress in "
|
||||
"days and short runs", "Digital Matrix plating line, clean room, evaporator-based water "
|
||||
"recycling", "Phase 5; case for moving to phase 1 under review"],
|
||||
["Stamper preparation machines", "Turns a raw nickel shell into a press-ready part",
|
||||
"Sibert punch, back-finisher, coiner", "With galvanics"],
|
||||
["A mixed press fleet", "Automatic for volume, semi-automatic for colour and short runs",
|
||||
"Newbilt Single growing to Duplex; two automatic presses (Pheenix AD12 or Newbilt Classic)",
|
||||
"Phase 1, then 5"],
|
||||
["Steam, cooling and compressed air engineered locally", "The largest energy loads; efficiency "
|
||||
"lives here", "Thai-sourced packages with heat recovery designed in", "Phase 1"],
|
||||
["Whole-plant climate and particulate control", "Moisture and dust are the two classic record "
|
||||
"defects", "20 to 26 °C, 35 to 60% RH, F7 to F9 filtration, HEPA in tight zones", "Phase 1"],
|
||||
["Metrology for the metal parts", "Data-backed accept/reject before a stamper is mounted",
|
||||
"Laser confocal, wide-area profiler, XRF thickness, inline vision", "Phase 5; basic at launch"],
|
||||
["Layered QC for every record", "Catch defects at line speed without playing every record",
|
||||
"100% optical inspection, per-cycle telemetry, sampled audio comparison", "Basic at phase 1, "
|
||||
"full at phase 5"],
|
||||
["Traceability from file to shipment", "Complaints resolve backwards; customers see their "
|
||||
"order; machine data access is a purchasing condition", "Batch genealogy database, PLC data "
|
||||
"capture, customer portal", "Phase 1 (v1)"],
|
||||
["A materials lab", "PVC lots differ; a bad lot is a noisy run", "Per-lot gate: moisture, heat "
|
||||
"stability, colour, density, granulate; release by test pressing", "Phase 1"],
|
||||
["A packaging partner and packaging QC", "Jackets are the path that slips; the wrong "
|
||||
"record in the right jacket is the classic error", "Ploy Printing (Chiang Mai) for jackets, "
|
||||
"sleeves, J-cards; matrix code scanned "
|
||||
"against jacket barcode", "Phase 1"],
|
||||
["An archive for masters and mothers", "Irreplaceable metal; repress economics",
|
||||
"Fire-rated, climate-controlled room with clean-agent suppression", "Phase 1"],
|
||||
["Renewable energy and closed-loop water", "Cost, resilience, brand, BOI tax value",
|
||||
"~290 kWp PV, 271 kWh battery, closed cooling loop, zero process discharge", "Phase 1 to 2"],
|
||||
["Trained people and a safety program", "Steam at 10 bar, hydraulics at 180 bar, plating "
|
||||
"chemistry", "Vendor commissioning training, in-house program, PPE, clean rooms", "Phase 1"],
|
||||
["Cassette and CD lines", "Same customers, one order, one shipment", "In-house cassette "
|
||||
"duplication; CD-R duplication; CD replication as a separate option", "Phase 1 (replication "
|
||||
"if chosen: phase 2)"],
|
||||
], widths_mm=[40, 45, 55, 30], font_pt=8.5),
|
||||
|
||||
H2("4.1 Mastering and lacquer cutting"),
|
||||
P("**What happens.** A mastering engineer prepares the audio for vinyl (levels, side length, "
|
||||
"low-frequency handling), then a cutting lathe drives a heated stylus into a lacquer-coated "
|
||||
"aluminium disc, cutting the groove in real time. The lacquer is soft nitrocellulose: heat- and "
|
||||
"shock-sensitive, and it wants plating within one to three days. Lacquers do not travel well."),
|
||||
P("**What it needs.** A disk mastering lathe and stereo cutter head, matched cutting amplifiers, a "
|
||||
"mastering console and chain, a preview and pitch automation system, vacuum for platter hold-down "
|
||||
"and chip suction, a groove inspection microscope, and a low-vibration, temperature-controlled "
|
||||
"room away from the presses and compressor. Consumables are lacquer blanks and styli."),
|
||||
P("**Who supplies it.** Agnew Analog Reference Instruments (Greece) builds the Type 612 lathe, "
|
||||
"unveiled in 2023 and billed as the first all-new lacquer lathe since the 1960s, with the Type 602 "
|
||||
"cutter head; bespoke, low-volume manufacturing with long build times. Cutter amplifiers, console, "
|
||||
"preview automation and lacquer supply are open items."),
|
||||
P("**Cost and phasing.** Estimated USD 290k to 580k for the room (bespoke market, no public "
|
||||
"pricing). At launch, mastering is done by an external engineer and cutting is brokered through "
|
||||
"Stamper Discs' European partners, so that the lacquer is cut and plated in the same region and "
|
||||
"only nickel travels. Mastering is labour-bound: an engineer masters perhaps two to four songs a "
|
||||
"day, which caps the title-services line and is discussed in section 9."),
|
||||
|
||||
H2("4.2 Galvanics: silvering and electroforming"),
|
||||
P("**What happens.** The lacquer is cleaned and sprayed with a conductive silver layer, then "
|
||||
"electroplated with nickel to grow a negative metal copy, the 'father'. From the father a positive "
|
||||
"'mother' is grown (playable, and archived), and from the mother the working 'stampers' that go "
|
||||
"into the press. This three-step process means a fresh stamper can be pulled from an archived "
|
||||
"mother in a day or two; a cheaper one-step stamper is single-use and a repress restarts at "
|
||||
"cutting."),
|
||||
P("**Why it is the key to the region.** No plating capability has been found in Thailand; the one "
|
||||
"operating plant buys its stampers abroad. Every title in the country therefore waits on an "
|
||||
"overseas order, which is the most plausible cause of the incumbent's lead-time penalty on larger "
|
||||
"runs and its inability to take small ones. Bringing cutting and plating in-house takes an "
|
||||
"estimated 1.5 to 5 weeks out of the lead time and USD 175 to 665 per title out of the cost, and "
|
||||
"it is the one capability nobody in the country has."),
|
||||
P("**What it needs.** A lacquer cleaning and silvering station, a low-stress nickel pre-plate, "
|
||||
"production electroforming cells for the three generations, an electro-clean and passivation "
|
||||
"station, a clean room with controlled access, fume extraction and scrubbing, and a closed-loop "
|
||||
"rinse-water treatment and metal recovery system. The chemistry (silver nitrate, nickel salts) "
|
||||
"sets the safety and permitting requirements: decontamination procedures, PPE, locked chemical "
|
||||
"storage, eyewash and emergency shower, and hazardous-substance registration."),
|
||||
P("**Who supplies it.** Plating Power / Digital Matrix Corporation (New York) is the dominant "
|
||||
"turnkey vendor for vinyl galvanics (silvering, pre-plate, multi-cell electroforming, "
|
||||
"passivation). They supply no water treatment: the zero-discharge system comes from an industrial "
|
||||
"water vendor (Veolia or Hydrotek in Thailand integrating an imported evaporator), following the "
|
||||
"zero-discharge plating room at Gotta Groove Records in Ohio as the reference model."),
|
||||
P("**Cost and phasing.** Estimated USD 320k to 650k for the plating line plus USD 90k to 240k for "
|
||||
"the water plant, plus a clean room. At launch, press-ready stampers come from Stamper Discs "
|
||||
"(Sheffield, UK), a trade galvanics service that makes stampers to fit Newbilt moulds and ships to "
|
||||
"30+ countries, under a written turnaround agreement. The roadmap places in-house galvanics in "
|
||||
"phase 5, but the August 2026 market evidence argues for costing an earlier start: it addresses "
|
||||
"the market's actual bottleneck, it is a fraction of the full capital, and it could earn from the "
|
||||
"incumbent's own presses before CNX's press hall exists. Section 11 sets out that question."),
|
||||
|
||||
H2("4.3 Stamper preparation"),
|
||||
P("A raw electroformed shell is not yet a stamper. It is centre-punched to the press spindle, "
|
||||
"back-finished flat, and trimmed and formed ('coined') to the mould. Sibert Instruments (UK, in "
|
||||
"this niche since the early 1970s) makes the three machines: the VSP punch, VSF back-finisher and "
|
||||
"VSC coiner. Estimated USD 60k to 170k, pricing unpublished, bought together with the galvanics "
|
||||
"line. Concentricity and dimensional checks after each step feed the traceability record."),
|
||||
|
||||
H2("4.4 Pressing"),
|
||||
P("**What happens.** An extruder melts PVC compound into a 'patty', which is placed with two labels "
|
||||
"between two stampers in a steam-heated mould. The press closes at about 150 tonnes, the mould is "
|
||||
"then water-cooled, and the record is ejected and trimmed. A cycle takes about 25 to 30 seconds on "
|
||||
"an automatic press; on a semi-automatic press an operator handles the patty and the record, which "
|
||||
"is slower but is exactly what colour, splatter and special editions need."),
|
||||
P("**The fleet strategy: 50/50.** Half the fleet fully automatic for standard volume, half "
|
||||
"semi-automatic for effects, special editions and small runs, where an operator working the mould "
|
||||
"directly is an advantage rather than a cost."),
|
||||
T(["Machine", "Role", "Basis", "Cost"], [
|
||||
["Newbilt Single 2.1 system (Alsdorf, Germany)", "Launch line: VEX 50 extruder, HRP 12 "
|
||||
"hydraulic press (~150 t), 12-inch book mould set for 140/180 g, Vario trimmer (7/10-inch "
|
||||
"upgrade path), label dry oven, shrink wrapper, PVC grinder, label punch, spindles and plates. "
|
||||
"Hydraulics already sized for a second press.", "Quoted: EUR 200,500 (~USD 232k, ~THB 7.7M) "
|
||||
"including sea freight and packaging, June 2024, "
|
||||
"expired; re-quote required with build time and "
|
||||
"order-book position", "USD 230k to 260k"],
|
||||
["Duplex expansion", "Second press and extruder on the same hydraulic aggregate",
|
||||
"Quoted option", "~USD 115k"],
|
||||
["Two fully automatic presses", "Volume production, one operator per four presses. Candidates: "
|
||||
"Pheenix Alpha AD12 (Sweden, successor of Toolex Alpha, 24 to 26 s cycle, ~140/h, 25 kW, "
|
||||
"closed platen cooling) or Newbilt Classic 12-inch (28 s cycle, ~128/h, built-in extruder, "
|
||||
"Siemens PLC with OPC UA, vendor-claimed 95% yield and uptime)", "Estimate: ~USD 340k per "
|
||||
"press indicative, unverified, "
|
||||
"plus delivery and commissioning",
|
||||
"USD 580k to 920k"],
|
||||
], widths_mm=[36, 74, 40, 20], font_pt=8.5),
|
||||
P("**What Newbilt deliberately excludes**, and what will be engineered and sourced in Thailand: a "
|
||||
"~400 kg/h steam generator at 10 bar / 180 °C with water treatment, and a ~250 kW cooling system. "
|
||||
"These are the two largest energy systems in the plant and the reason the efficiency architecture "
|
||||
"in section 5.4 is ours to design."),
|
||||
P("**Site requirements.** Roughly 200 m² of production floor for the Duplex, 32 kVA connected load, "
|
||||
"saturated steam, compressed air and cooling circuits, and side rooms for materials, compressor, "
|
||||
"chiller and finished goods. A press weighs 1,350 kg on under one square metre, so the floor slab "
|
||||
"must carry several tonnes per square metre and is a building-selection criterion; the floor must "
|
||||
"be flat, non-slip and solvent-resistant."),
|
||||
T(["Phase", "Fleet", "Rough output per year"], [
|
||||
["Launch", "1 semi-automatic press, 1 shift", "~90,000 to 135,000 records"],
|
||||
["Duplex", "2 semi-automatic presses, 1 to 2 shifts", "~175,000 to 540,000 records"],
|
||||
["Mature", "2 semi-automatic + 2 automatic (50/50 fleet), 2 shifts", "~1.0 to 1.5 million "
|
||||
"records"],
|
||||
], widths_mm=[25, 80, 65],
|
||||
caption="Basis: AD12 ~140 records/h (datasheet); semi-automatic 60 to 80/h assumed; 16 h/day, "
|
||||
"300 days/yr, 60 to 70% overall equipment effectiveness. Theoretical ceiling of the mature "
|
||||
"fleet at 100% uptime is 1.9 to 2.1 million."),
|
||||
P("**On the 60 to 70% effectiveness figure.** Newbilt publishes 95% yield and 95% uptime for its "
|
||||
"automatic press. We have not adjusted the plan on that basis, deliberately: a vendor's machine "
|
||||
"uptime excludes stamper and mould changes, colour changeovers, compound lot changes, "
|
||||
"first-article QC and breaks, which are exactly the losses that dominate a short-run boutique "
|
||||
"plant. Both press vendors are being asked for the basis of their figures as part of the "
|
||||
"re-quote. If they hold, they argue for a higher effectiveness on the automatic half only."),
|
||||
|
||||
H2("4.5 Finishing, packaging and fulfilment"),
|
||||
P("After pressing, records are trimmed inline, labels have been pre-dried in an oven (moisture in a "
|
||||
"label causes bubbles), and records are sleeved, assembled into jackets, shrink-wrapped and packed. "
|
||||
"Jackets, sleeves, inserts, J-cards and CD packaging come from **Ploy Printing** (Ploy Karnpim "
|
||||
"Limited Partnership, Pa Daet, Mueang Chiang Mai), an established print house with offset, "
|
||||
"digital, risograph, UV inkjet and latex inkjet presses and vinyl packaging production. That "
|
||||
"keeps a labour-intensive, freight-heavy item within the province, gives the plant a partner who "
|
||||
"already knows record packaging, and, because Ploy Printing intends to co-invest (section 12), "
|
||||
"aligns the two businesses on the same schedule and quality. Risograph and UV print also open "
|
||||
"boutique packaging options (spot colours, special stocks, printed shells and J-cards) that suit "
|
||||
"the short-run and colour-vinyl market the semi-automatic line serves. Two points matter "
|
||||
"commercially:"),
|
||||
B([
|
||||
"**Packaging is the path that slips.** Printed jackets need 10 to 20 working days from approved "
|
||||
"artwork. The metal path and the packaging path run in parallel and converge at assembly; when "
|
||||
"a label delivers artwork late, jackets become the critical path and every day saved on "
|
||||
"metalwork is wasted. Artwork deadline is therefore contracted as a fourth approval gate.",
|
||||
"**The wrong record in the right jacket is the classic error**, and a misprinted barcode is a "
|
||||
"rejected delivery at the distributor. Each jacket is scanned against the runout matrix code of "
|
||||
"the record going into it, and every unit's barcode is verified.",
|
||||
]),
|
||||
P("**Fulfilment** covers bulk pallet delivery to labels and distributors, and dropshipping single "
|
||||
"parcels directly to end customers on behalf of labels and artists with carrier tracking recorded "
|
||||
"per shipment. Dropshipping fees are charged per parcel and are not in the revenue model."),
|
||||
|
||||
H2("4.6 Materials: PVC compound"),
|
||||
P("Records are pressed from a vinyl chloride / vinyl acetate copolymer compound, about 150 to 160 g "
|
||||
"per 12-inch record. The plant runs an 80% virgin / 20% regrind feed: trimmings and rejects go "
|
||||
"through an in-house grinder and back to the extruder, so scrap is feedstock, not landfill. The "
|
||||
"current supplier list is European (Netherlands, UK, Italy, France, Germany) with one US option; "
|
||||
"the preference is a qualified Thai or regional compound if it meets record specifications, "
|
||||
"which cuts freight, cost and footprint. The incumbent's floor was stacked with sacks marked "
|
||||
"'Made in Thailand', a lead worth following. Every incoming lot passes the material lab gate in "
|
||||
"section 5.3 before it is released to a press."),
|
||||
|
||||
H2("4.7 The order, end to end: what a customer is promised"),
|
||||
P("A defensible lead time is not the sum of process steps: it carries queue and buffer, and it is "
|
||||
"quoted from the last customer approval, because a plant cannot be held to a clock the customer "
|
||||
"keeps stopping. The plan's quotable lead times, from test-pressing approval:"),
|
||||
T(["Order type", "Quoted lead time", "Basis"], [
|
||||
["New title, full service, launch configuration (bridge stampers from the UK)", "10 to 14 "
|
||||
"weeks", "Stage sum plus the external metal queue plus buffer; at parity with the 10 to 16 "
|
||||
"weeks reported for EU/US plants, but closer and in the customer's time zone"],
|
||||
["New title, full service, mature configuration (cutting and plating in-house)", "7 to 9 "
|
||||
"weeks", "Removes 13 to 27 days of outsourced metal plus freight"],
|
||||
["Customer supplies jackets", "1 to 2 weeks less", "Removes the print path"],
|
||||
["Repress from an archived mother, no changes", "2 to 3 weeks", "Pull stamper (1 to 2 days), "
|
||||
"prep, press, finish; the highest-margin order the plant will take"],
|
||||
["Rush slot", "Premium, capped per month", "Only credible with reserved capacity"],
|
||||
], widths_mm=[62, 28, 80]),
|
||||
P("Lead time is a capacity decision, not a process decision. A plant running at 90% quotes 16 weeks "
|
||||
"with the same equipment that quotes 6 weeks at 50%. Any turnaround promise to the market is a "
|
||||
"promise to hold spare capacity, and the revenue model in section 9 is built on that basis."),
|
||||
|
||||
H2("4.8 Cassette duplication (new scope)"),
|
||||
P("**What happens.** A studio master is duplicated onto tape either in real time on a bank of decks, "
|
||||
"or at high speed on a loop-bin duplicator that copies a running master loop onto reels of blank "
|
||||
"tape ('pancakes') many times faster than real time. The tape is then loaded into empty cassette "
|
||||
"shells ('C-0s') by a loader that cuts to length and splices, shells are printed or labelled, "
|
||||
"J-cards are printed and folded, and the cassette is boxed and shrink-wrapped."),
|
||||
T(["", "Tier A: real-time bank", "Tier B: professional high-speed line"], [
|
||||
["Equipment", "10 to 20 duplicating decks, a cassette loader, shell printer or label printer, "
|
||||
"shrink wrapper", "Loop-bin high-speed duplicator (typically refurbished 1980s to 1990s "
|
||||
"Lyrec, Gauss or similar, or a current Chinese-built unit), 2 to 3 loaders, "
|
||||
"pad printer for on-shell print, imprinter, shrink wrapper"],
|
||||
["Throughput", "~100 to 300 cassettes per day", "~1,000 to 3,000 cassettes per day"],
|
||||
["Space", "~30 to 40 m²", "~60 to 80 m², climate-controlled (tape and PVC are both "
|
||||
"humidity-sensitive)"],
|
||||
["Staff", "1, part time", "1 to 2"],
|
||||
["Capital", "USD 15k to 40k", "USD 40k to 150k"],
|
||||
["Quality", "Adequate for short runs; audio quality is master-limited", "Industry standard; "
|
||||
"chrome or ferric tape"],
|
||||
], widths_mm=[24, 68, 78],
|
||||
caption="Estimate. The professional cassette equipment market is largely refurbished; new "
|
||||
"high-speed lines are rare. Prices to be confirmed with two or three vendors."),
|
||||
P("**Supply chain.** Blank tape pancakes and C-0 shells come from a handful of suppliers worldwide "
|
||||
"(National Audio Company in the US is the largest tape source; shell moulders exist in China and "
|
||||
"should be sought in Thailand). Supply risk is real and the plan is to hold stock. **Positioning:** "
|
||||
"cassettes are a boutique add-on for the same indie customers, with shells in colours that match "
|
||||
"the record. **Effect on the plan:** schedule-neutral; fits inside the building programme; add "
|
||||
"the activity to the BOI application; recommended to start at tier A or a small tier B in phase 1."),
|
||||
|
||||
H2("4.9 CD: two very different routes (investor decision)"),
|
||||
P("'CD manufacturing' can mean two things that differ by two orders of magnitude in capital, and the "
|
||||
"investor is asked to choose."),
|
||||
T(["", "Option A: CD-R duplication", "Option B: CD replication"], [
|
||||
["What it is", "Blank recordable discs are burned and printed by robotic duplicators. The "
|
||||
"result is a CD-R, playable in almost all players, with full-colour on-disc print.",
|
||||
"Discs are injection-moulded from polycarbonate against a nickel stamper (made by glass "
|
||||
"mastering and electroforming), metallised, lacquered, and printed. The result is a 'pressed' "
|
||||
"CD identical to major-label product."],
|
||||
["Equipment", "2 to 4 robotic autoloader duplicators with inkjet or thermal print (Rimage, "
|
||||
"Microboards, Primera or Epson class); packaging printed by Ploy Printing; "
|
||||
"shrink wrapper", "Laser beam recorder for glass mastering (or outsourced glass masters), "
|
||||
"electroforming line, one or two injection-moulding lines with inline "
|
||||
"metalliser, lacquer coater and scanner, screen or offset disc printer, "
|
||||
"packaging automation, ISO 7/8 clean room"],
|
||||
["Throughput", "~200 to 800 discs per day per unit", "~10,000 to 30,000 discs per day per line"],
|
||||
["Space", "~20 to 40 m²", "~300 to 600 m² plus clean room; realistically the phase 2 building"],
|
||||
["Capital", "USD 15k to 40k", "USD 1.5M to 4.0M (new-build lines); used lines from closed "
|
||||
"plants can halve that at higher risk"],
|
||||
["Staff", "Shared with cassette line", "6 to 10 across shifts, including a process engineer"],
|
||||
["Market fit", "Short runs of 50 to 500 for indie releases, merch and promo; same customers as "
|
||||
"vinyl and cassette", "Volume runs of 1,000+; competes with established replicators in "
|
||||
"Japan, Taiwan, Malaysia and elsewhere on thin margins"],
|
||||
["Regulatory", "No separate BOI activity expected (confirm)", "Separate BOI activity code, "
|
||||
"must be in the application from "
|
||||
"the start; larger factory "
|
||||
"licence scope"],
|
||||
["Effect on timeline", "None", "Substantial: a second commissioning programme and a bigger "
|
||||
"building; would push first CD output well past first vinyl"],
|
||||
["Synergy", "Shares packaging, fulfilment, artwork approval, traceability", "Shares the "
|
||||
"electroforming skill set and the clean-room discipline of the vinyl galvanics room, but not "
|
||||
"the equipment"],
|
||||
], widths_mm=[24, 68, 78], font_pt=8.5,
|
||||
caption="Estimate throughout. Option B figures are order-of-magnitude and would need a dedicated "
|
||||
"feasibility study."),
|
||||
P("**Recommendation.** Include Option A in phase 1: it is cheap, schedule-neutral, and serves the "
|
||||
"plant's customers. Treat Option B as a separate project with its own market study, because CD "
|
||||
"replication is a mature, competitive, declining business outside Asia's strongholds and would "
|
||||
"add USD 1.5M to 4.0M and a second building to a plan whose vinyl economics do not depend on it. "
|
||||
"If the investor wants Option B, the decision must be taken before the BOI application is filed "
|
||||
"(section 7), and the phase 2 building should be designed for it. This plan's totals include "
|
||||
"Option A only; Option B is shown as a separate line."),
|
||||
]
|
||||
return out
|
||||
@@ -0,0 +1,426 @@
|
||||
"""Part B: what makes it top-notch (traceability, metrology, QC, efficiency); site and building; BOI."""
|
||||
from plan_lib import H1, H2, H3, P, B, N, T, KV, IMG, CALL, PB
|
||||
|
||||
|
||||
def blocks():
|
||||
return [
|
||||
# ------------------------------------------------- 5 top-notch
|
||||
H1("5. What makes it top-notch: traceability, metrology, QC and efficiency"),
|
||||
P("A pressing plant makes records. A top-notch plant knows, for every record it has ever made, "
|
||||
"exactly how it was made, can prove it to the customer, and wastes as little energy, water and "
|
||||
"material as the physics allows. This section is the heart of the plan, because these four "
|
||||
"capabilities are what a regional customer cannot buy today and what justifies export-grade "
|
||||
"pricing. None of it is exotic: the instruments exist, the data interfaces exist, and the "
|
||||
"engineering is well understood. What is unusual is putting all of it in one plant from the "
|
||||
"start rather than bolting it on later."),
|
||||
|
||||
H2("5.1 Traceability: every record knows its history"),
|
||||
P("Every step of production is logged continuously, so that every record is traceable end to end "
|
||||
"for the customer. This is a fixed requirement, and it is the core specification for the factory "
|
||||
"software the project will build. The natural identity of a record is **stamper ID + press ID + "
|
||||
"cycle timestamp + side**, and everything else hangs off that key."),
|
||||
T(["Stage", "What is recorded"], [
|
||||
["Audio intake", "File received, checksums, versions, who handled it, approval trail"],
|
||||
["Mastering", "Session log, engineer, settings, customer approvals"],
|
||||
["Cutting", "Lacquer IDs, lathe parameters, stylus used, inspection result, dead-wax matrix "
|
||||
"scribe"],
|
||||
["Galvanics", "Silvering and plating batch IDs, bath chemistry logs, father/mother/stamper "
|
||||
"genealogy (which stamper came from which mother from which father), or the "
|
||||
"external supplier and batch if bridged"],
|
||||
["Pressing", "Press ID, operator, shift, mould and stamper IDs, per-cycle temperatures, "
|
||||
"pressures and cycle time, PVC compound lot and virgin/regrind mix, label lot"],
|
||||
["Quality control", "Test-pressing approvals, inspection results, reject counts and reasons"],
|
||||
["Finishing and packaging", "Trimming, sleeving, carton lot, packaging partner batch"],
|
||||
["Fulfilment", "Warehouse location, shipment or dropshipping consignment, carrier tracking per "
|
||||
"end customer"],
|
||||
], widths_mm=[38, 132]),
|
||||
P("**Principles.** Log at the source: machine data comes from the machine (press PLCs, plating "
|
||||
"rectifiers, the AD12's remote monitoring), never from manual transcription; manual steps get a "
|
||||
"simple operator check-in. Batch and genealogy first: every physical artefact (lacquer, father, "
|
||||
"mother, stamper, press run, box) gets an ID and knows its parents. Environment attached: the "
|
||||
"plant's temperature, humidity and particulate logs sit on the same timeline, so any production "
|
||||
"window can be correlated with plant conditions. Immutable and retained: logs are append-only "
|
||||
"and kept for the life of the customer relationship; stamper genealogy for as long as the archive "
|
||||
"holds the metal."),
|
||||
P("**Identity travels with the product.** The matrix code scribed into the lacquer's dead wax "
|
||||
"propagates through father, mother and stamper to every pressed record. A small angular fiducial "
|
||||
"scribed beside it makes each part's rotation camera-readable at any stage, and lets the packing "
|
||||
"station read the code on a finished record and match it to the jacket barcode. Nickel parts "
|
||||
"additionally get a laser-marked serial on the back, as the optical-disc industry marked its "
|
||||
"stampers; lacquers are never lasered (nitrocellulose is flammable) and records are never lasered "
|
||||
"(burning PVC releases hydrochloric acid)."),
|
||||
CALL("Why this is a product feature, not an internal file", [
|
||||
"**Customers see their order.** A per-order portal or report covers the full chain above, "
|
||||
"without exposing other customers' data. Labels ask about pre-release security and provenance "
|
||||
"before they send masters; this answers both.",
|
||||
"**A complaint resolves backwards.** One noisy record resolves to its stamper, press, cycle, "
|
||||
"operator, compound lot and packaging lot, and from the stamper back through mother and father "
|
||||
"to the lacquer and the approved master. Root cause takes minutes, not a week of guessing.",
|
||||
"**Stampers retire on data.** Audio scores trending down against a stamper's play count become "
|
||||
"an automatic retirement signal instead of a customer complaint.",
|
||||
"**Machine data access is a purchasing condition.** Newbilt, Pheenix Alpha and Digital Matrix "
|
||||
"are asked which PLC interfaces they expose before a purchase order is signed. That question "
|
||||
"is free now and expensive later.",
|
||||
]),
|
||||
P("**Cost and build.** The traceability and factory software is estimated at USD 60k to 170k and "
|
||||
"is a development project, not a purchase; no vendor sells a turnkey version. Version 1 (intake, "
|
||||
"batch genealogy, press telemetry capture) is built during commissioning so the first test "
|
||||
"pressings are already in the system. The project repository is the eventual home of this "
|
||||
"software. Open design work: the data model across all stages, retention and PDPA handling for "
|
||||
"operator-linked records, and whether test-pressing approval workflows live in the same system "
|
||||
"from day one."),
|
||||
|
||||
H2("5.2 Metrology: measuring the metal before it presses a single record"),
|
||||
P("A stamper is a 0.2 to 0.3 mm nickel disc carrying a spiral ridge 40 to 80 microns wide and about "
|
||||
"28 microns high, 200 to 350 turns per inch. Industry practice, even at respected stamper houses, "
|
||||
"is to check its thickness with a hand micrometer at ten points on sacrificial blanks, with "
|
||||
"nothing logged. A top-notch plant measures the metal without contact, per serial, and makes an "
|
||||
"accept or reject decision on data before the next plating generation or the press mounting."),
|
||||
T(["Instrument", "What it measures", "How it is used"], [
|
||||
["3D laser confocal microscope (Keyence VK-X class)", "Groove cross-section, wall roughness, "
|
||||
"micro-defects; 20 nm vertical, ~120 nm lateral resolution, non-contact", "Sampled radial "
|
||||
"zones (lead-in, outer, mid, inner) on fathers, mothers and stampers; scanning the full spiral "
|
||||
"is not feasible and not needed. The optical-disc industry verified its nickel stampers with "
|
||||
"this instrument class, so the technique transfers directly"],
|
||||
["Wide-area optical profiler (Keyence VR-6000 class)", "Form, flatness, gross defects; "
|
||||
"0.1 micron height resolution, ~1 s per field, HDR mode for mirror-bright nickel",
|
||||
"Fast whole-part mapping of every part"],
|
||||
["XRF thickness gauge (Fischer or Bowman class)", "Nickel deposit thickness, non-destructive",
|
||||
"Per serial, read on the outer land zones; replaces the ten-point micrometer. Target total "
|
||||
"thickness variation 10 to 20 microns"],
|
||||
["Stylus profilometer", "Traceable reference", "Calibration of the optical instruments"],
|
||||
["Inline machine vision", "Cosmetic screening", "100% of parts, fast"],
|
||||
], widths_mm=[42, 58, 70], font_pt=8.5),
|
||||
P("Integration is real but not turnkey: the instruments export data and support scripting, and "
|
||||
"the inline sensors speak standard industrial protocols, but there is no off-the-shelf connector "
|
||||
"to a factory database. A light middleware service stamps each measurement with the part serial "
|
||||
"and batch ID and writes it to the genealogy record. Budget for the full stack (metrology plus "
|
||||
"the record-QC layers below, plus the audio station) is USD 350k to 810k, phaseable; a basic "
|
||||
"tier (microscope, benchtop PVC lab, sampled listening) runs from launch."),
|
||||
|
||||
H2("5.3 Quality control: three layers for every record, one gate for every material"),
|
||||
P("Full automated audio checking of every record is physically impossible: a 12-inch record plays "
|
||||
"for about 40 minutes and a press makes a new one every 25 to 30 seconds, so one press outruns "
|
||||
"roughly 80 to 95 full-time listening stations. No playback-QC robot exists anywhere. The credible "
|
||||
"maximum-automation design is layered, and it should be described to customers exactly as it is: "
|
||||
"100% optical plus per-cycle telemetry plus sampled audio, with human ears kept in the loop for "
|
||||
"test-pressing approval."),
|
||||
T(["Layer", "What", "Catches", "Status"], [
|
||||
["1. 100% inline optical inspection", "Darkfield line-scan cameras at the press unloader "
|
||||
"inspect every record at line speed", "Non-fill (dull patches where PVC did not fill the "
|
||||
"stamper), stitching and flow lines, warp, off-centre spindle holes, surface debris, wrong or "
|
||||
"off-centre labels. Non-fill and stitching correlate with audible defects, so this is a strong "
|
||||
"proxy for audio quality without playing the record", "Custom integration of general machine "
|
||||
"vision (Keyence or Cognex class); no "
|
||||
"vinyl-specific product exists; CNX "
|
||||
"would be an early adopter"],
|
||||
["2. Press telemetry, every cycle", "Nozzle pressure, mould temperatures, cycle time, cake "
|
||||
"weight, yields and drift alerts streamed from the press PLC", "Process drift before it "
|
||||
"becomes a defect; the record-level context for every other check", "Proven and deployed "
|
||||
"in the industry "
|
||||
"(Viryl ADAPT model); "
|
||||
"AD12 and Newbilt "
|
||||
"Classic expose the "
|
||||
"data"],
|
||||
["3. Automated audio comparison, sampled", "A needle-drop capture station with automated "
|
||||
"analysis: spectral-envelope comparison against the source master, click and pop counting, "
|
||||
"noise floor from lead-in silence, channel balance; numeric score and pass/fail to the "
|
||||
"database", "Wrong master cut into good-looking grooves, EQ and mastering errors, speed and "
|
||||
"pitch problems; the things no camera can see", "Buildable from proven parts "
|
||||
"(Stamper Discs' Fonograph "
|
||||
"approach, iZotope-class "
|
||||
"algorithms); first-of-kind as a "
|
||||
"whole"],
|
||||
], widths_mm=[32, 48, 55, 35], font_pt=8.5),
|
||||
P("**Sampling rule for layer 3:** every test pressing and first-off per stamper, plus pulled "
|
||||
"samples densifying as the stamper ages (a stamper lasts roughly 1,000 to 2,000 records; the "
|
||||
"industry rule of thumb is a spot-play around every 50th)."),
|
||||
H3("The materials lab"),
|
||||
P("Every incoming compound lot passes a gate before it reaches a press, and its lot ID follows every "
|
||||
"record pressed from it. Per lot, in-house from day one, on benchtop instruments with data "
|
||||
"export: certificate of analysis against the agreed specification, moisture (the most common "
|
||||
"cause of noisy surfaces), heat stability (PVC degradation releases hydrochloric acid and shows as "
|
||||
"discoloration and noise), density, colour and gloss, granulate size and dust. New lots, and "
|
||||
"especially new suppliers, are released only by test pressing. As the lab matures, torque "
|
||||
"rheometry, DSC and FTIR are added; supplier qualification uses an external lab for K-value, "
|
||||
"stabiliser identification (legacy lead rejected) and heavy-metals compliance for export. The "
|
||||
"in-house regrind stream has its own contamination gate. This protocol is how Thai and regional "
|
||||
"compounds are qualified against the European references."),
|
||||
H3("Packaging QC"),
|
||||
P("Incoming cartons and sleeves are checked per delivery: dimensions (records must fit without "
|
||||
"force; spine width per title), board grammage, print quality against the customer-approved "
|
||||
"proof (spectrophotometer Delta E for colour-critical jobs), lamination and seam strength, inner "
|
||||
"sleeve scuff risk. At assembly, the runout matrix code is scanned against the jacket barcode and "
|
||||
"every unit's barcode is verified. Results go back to Ploy Printing per packaging lot, which is "
|
||||
"the practical advantage of a partner ten kilometres away over a supplier ten thousand."),
|
||||
|
||||
H2("5.4 Efficiency: recover before you generate"),
|
||||
P("The pressing machinery is a modest electrical load (32 kVA connected for a full semi-automatic "
|
||||
"line). The real energy demand sits in the utilities around it: the steam generator, the 250 kW "
|
||||
"cooling system, and, in a tropical climate, the whole-plant air conditioning. Because Newbilt "
|
||||
"excludes steam and cooling from its scope, these systems are ours to design, and the plan's "
|
||||
"architecture is built on one observation: the steam's job is to heat the moulds and the cooling "
|
||||
"water's job is to remove that same heat again. Roughly 98% of the cooling load is heat that the "
|
||||
"steam put into the moulds, and the press cooling return leaves at about 60 °C. That warm return "
|
||||
"is a premium heat source, and the plant reuses it before generating anything new."),
|
||||
T(["Measure", "What it does", "Effect"], [
|
||||
["Feedwater heat recovery", "A water-to-water heat pump lifts the 60 °C press cooling return "
|
||||
"into boiler feedwater at 90 to 95 °C", "Each kWh does double duty: cuts the boiler's work "
|
||||
"and removes load from the chiller. With condensate "
|
||||
"return, saves roughly 10 to 15% of steam energy and "
|
||||
"shrinks the cooling system"],
|
||||
["Condensate return", "Steam condensate goes back to the treated feedwater cycle", "80% "
|
||||
"return assumed; less water, less treatment, less fuel"],
|
||||
["Waste heat for low-grade consumers", "The label dry oven and the air conditioning's "
|
||||
"dehumidification reheat run on recovered press heat", "Displaces fresh electricity in two "
|
||||
"continuous loads"],
|
||||
["Solar-thermal feedwater preheat", "Flat-plate or evacuated-tube collectors reach 60 to 90 °C "
|
||||
"trivially in the Thai sun", "Cheap, proven, stackable with the heat pump"],
|
||||
["Steam accumulator", "Presses draw steam in bursts; an accumulator (a Newbilt option) lets a "
|
||||
"smaller boiler ride the peaks", "Smaller boiler, steadier firing"],
|
||||
["Closed-loop cooling", "Chiller or hybrid tower with plate heat exchanger; no open 'dirty "
|
||||
"water' loop as in the reference schematic", "No process water discharged; the largest "
|
||||
"water draw (tower evaporation) shrinks with "
|
||||
"every kW the heat pump removes"],
|
||||
["Regrind loop", "In-house grinder returns trimmings and rejects to the extruder at 20% of "
|
||||
"the feed", "Scrap is feedstock, not landfill; ~20% less virgin compound"],
|
||||
["One operator per four automatic presses", "Automatic patty, label and cooling-plate "
|
||||
"handling", "Labour per record on the automatic half is a fraction of the semi-automatic "
|
||||
"line's"],
|
||||
], widths_mm=[38, 68, 64], font_pt=8.5),
|
||||
P("Two things were checked and ruled out: heat pumps cannot make the final 10 bar / 180 °C steam "
|
||||
"(commercial units top out around 120 to 165 °C, and core utility reliability is the wrong place "
|
||||
"to pioneer), and ground-source heat exchange does not work in Thailand (the ground sits at "
|
||||
"28 to 30 °C, too cold for process heat and warmer than the cooling loop wants). The boiler only "
|
||||
"does the final lift, from already-hot feedwater."),
|
||||
H3("Renewable energy"),
|
||||
KV([
|
||||
["Rooftop PV, phase 1", "~247 kWp on the 1,523 m² monopitch roof (425 modules of the current "
|
||||
"580 W class) plus ~40 kWp on the parking canopies, ~290 kWp total; "
|
||||
"expected yield ~335 to 360 MWh/yr from the roof, ~25 to 35% of mature "
|
||||
"two-shift consumption"],
|
||||
["Rooftop PV, with phase 2 building", "~498 kWp across both roofs, which removes the need for "
|
||||
"ground-mount solar"],
|
||||
["Battery", "271 kWh Gobel Power LiFePO4 rack (15 stacked 18 kWh packs, 8,000+ cycles), "
|
||||
"USD 40k to 58k listed, 50 to 60 day lead. Peak shaving and ride-through, roughly "
|
||||
"two to three hours of the chiller's draw, not off-grid autonomy"],
|
||||
["Inverters", "Multiple Deye three-phase hybrid units (30 to 80 kW class), Thai distribution "
|
||||
"via Solar Nicolo; Deye is a supported protocol on the Gobel battery management"],
|
||||
["Roof design", "Monopitch at 10° falling toward ESE puts the whole roof on one plane at ~98% "
|
||||
"of ideal yield; at this latitude orientation barely matters (under 2% "
|
||||
"across a wide azimuth swing) and the building stays aligned with the plot. "
|
||||
"10° rather than shallower so the rains self-clean burning-season dust"],
|
||||
["Open checks", "Battery rack voltage vs the chosen Deye model's window; Gobel safety "
|
||||
"certifications (not published); EPC warranty on a mixed-vendor DC bus; "
|
||||
"confirm yield against PVGIS before committing"],
|
||||
]),
|
||||
H3("Steam: the open engineering choice"),
|
||||
P("There is no natural gas pipeline in Northern Thailand, so gas means a bulk LPG tank on site and "
|
||||
"diesel means a fuel tank with price exposure. The plan keeps two fossil-free routes open. An "
|
||||
"**electric boiler** (THB 2.5M to 4.0M installed, Miura or Bangkok Industrial Boilers class) is "
|
||||
"fast, clean to permit, and maximises the solar share, at a ~300 kW draw that pushes the grid "
|
||||
"connection toward 750 kVA. A **biomass boiler** (THB 4M to 7M, Getabec, Thai-fabricated) burns "
|
||||
"wood chips, longan orchard prunings (Lamphun is Thailand's longan heartland and prunings are a "
|
||||
"traded fuel), corn cobs and rice husk, roughly 350 to 400 tonnes a year at maturity; it makes "
|
||||
"the steam genuinely renewable at the cost of fuel logistics, ash handling and an emissions "
|
||||
"stack. The team's own 60 rai of forestry-classified land could supply 80 to 135 tonnes a year "
|
||||
"as short-rotation coppice, most of early operations' need, but only after its legal "
|
||||
"classification is verified. The staged path is the working assumption: **start electric, "
|
||||
"plant the coppice now, add biomass at the automatic-line expansion** when demand and fuel supply "
|
||||
"have both matured. The building already has a boiler hall and a fuel store in its utility yard."),
|
||||
H3("Water"),
|
||||
P("Estimated demand at mature two-shift operation is about 15 m³ per day, called 20 with margin: "
|
||||
"cooling tower evaporation and blowdown (over half), boiler makeup, galvanics rinse after "
|
||||
"recycling, and domestic use for 25 staff. Source is open (PWA mains or a licensed well) and "
|
||||
"drives part of the utilities budget. Storage is a dedicated ~200 m³ fire reserve plus a ~50 m³ "
|
||||
"raw-water tank feeding a softener or RO skid. **No process water is discharged:** cooling is a "
|
||||
"closed loop, condensate is returned, galvanics rinse water goes through closed-loop treatment "
|
||||
"and metal recovery (an evaporator, THB 3M to 8M, with the galvanics line), and solvents are "
|
||||
"contained and disposed of as controlled waste. This is a fixed design requirement, not an "
|
||||
"aspiration to be traded away during engineering."),
|
||||
H3("Climate control as a product"),
|
||||
P("The whole plant runs under continuous control: 20 to 26 °C, 35 to 60% relative humidity, and "
|
||||
"particulate filtration (F7 to F9 general, HEPA with positive pressure in the cutting room and "
|
||||
"galvanics clean room), with sensors logging in every zone. Moisture in PVC compound causes "
|
||||
"bubbles, non-fill and surface defects and drives cooling rate, hence flatness and warp; airborne "
|
||||
"particulate embeds in the record surface and is the classic source of clicks and pops. Northern "
|
||||
"Thailand's burning season (February to April) brings some of the world's worst PM2.5 episodes, "
|
||||
"so a plant without filtration would press different records in March than in September. This "
|
||||
"is the plant's largest utility line (THB 7.5M to 15M for ~1,500 m², plus THB 1.2M to 3.4M for "
|
||||
"the two clean zones) and it is treated here as part of the product, not as overhead. The 35% "
|
||||
"humidity floor is aggressive for the tropics and drives desiccant cost; if 45% proves "
|
||||
"acceptable, the cost drops materially, and that question is on the RFQ list."),
|
||||
CALL("Efficiency in one paragraph, for the investor", "Heat is recovered from the presses before "
|
||||
"any fuel is burned; a quarter to a third of electricity comes from the roof at phase 1 and "
|
||||
"up to half with the second building; a battery shaves the peaks; cooling water is never "
|
||||
"discharged; a fifth of the plastic is reclaimed; and one operator runs four automatic presses. "
|
||||
"The same choices cut the energy line in the cost model toward its low end, hedge Thai tariff "
|
||||
"rises, and convert into BOI tax value (section 7). They also make the plant's sustainability "
|
||||
"story true rather than claimed, which matters to the labels it sells to."),
|
||||
|
||||
# -------------------------------------------- 6 site and building
|
||||
H1("6. Site and building"),
|
||||
H2("6.1 Land: a long lease in Lamphun"),
|
||||
P("The project's site strategy has changed from purchase to **long-term lease of raw land**, on "
|
||||
"which the purpose-built plant is still constructed. This lowers upfront capital by roughly "
|
||||
"USD 0.4M to 1.0M (THB 12M to 32M at the 2 to 4 million THB per rai the plan carried for "
|
||||
"purchase), removes land from the balance sheet, and matches how most foreign-invested "
|
||||
"manufacturing in Thailand actually holds its site. It brings its own conditions, which Thai "
|
||||
"counsel will resolve before signature:"),
|
||||
B([
|
||||
"**Term and registration.** A lease over three years must be registered at the Land Office to "
|
||||
"be enforceable; the ordinary maximum is 30 years, and for industrial and commercial use the "
|
||||
"Hire of Immovable Property for Commerce and Industry Act allows up to 50 years for qualifying "
|
||||
"projects. The plan assumes a registered 30-year lease with a contractual renewal option, and "
|
||||
"will test the 50-year route with counsel.",
|
||||
"**Building ownership.** The lease must state clearly that the buildings, foundations, tanks "
|
||||
"and fixed plant belong to the company for the term, what happens to them at expiry, and that "
|
||||
"the company may mortgage or assign them. Without this, financing the building is difficult.",
|
||||
"**Lessor's title and encumbrances.** A clean Chanote title, no mortgage that could foreclose "
|
||||
"over the tenant, and consent from any co-owners. A lease of Sor Por Kor or forestry-classified "
|
||||
"land is not acceptable.",
|
||||
"**Zoning and factory permission.** The candidate parcel in Mueang Chi, Lamphun is agricultural "
|
||||
"land whose zoning for factory use is unverified; this gates everything and is the cheapest "
|
||||
"schedule insurance available. The Northern Region Industrial Estate in Lamphun (IEAT) is the "
|
||||
"fallback: leased plots, licensing handled by the estate, and shared central effluent treatment "
|
||||
"that eases the galvanics permitting.",
|
||||
"**BOI interaction.** BOI's land-ownership privilege for foreign-held promoted companies is "
|
||||
"simply not used; leasing does not affect the promotion.",
|
||||
"**Rent.** Not yet negotiated. The plan carries THB 0.3M to 1.0M per year (USD 9k to 30k) as "
|
||||
"an estimate for 5 to 8 rai of agricultural land converted to industrial use in the Lamphun "
|
||||
"valley; IEAT plots run higher. The negotiated figure replaces this.",
|
||||
]),
|
||||
P("Land requirements are unchanged: ~4 rai minimum, 6 to 8 rai comfortable with expansion, truck "
|
||||
"access and turning, parking, the outdoor cooling tower, a separated battery enclosure, and a "
|
||||
"500 to 1,000 kVA medium-voltage connection from PEA."),
|
||||
|
||||
H2("6.2 The building"),
|
||||
IMG("renders/site-plan.png", 120, "Figure 2. Site plan on the candidate Mueang Chi parcel: phase 1 "
|
||||
"main building, detached utility yard, fire and raw water "
|
||||
"tanks, parking canopies, and the phase 2 building footprint. "
|
||||
"Source: parametric model, project repository."),
|
||||
P("The phase 1 building is a single-storey steel portal frame of **60 × 25 m (1,500 m²)** with a "
|
||||
"10° monopitch roof carrying the PV array, low eave 7.0 m, high side ~11.4 m. The floor is a "
|
||||
"200 mm slab rated 30 kN/m² (3 t/m², the standard industrial class and what the presses and "
|
||||
"racking need) raised 1.2 m above ground so that two sealed loading docks work at trailer height "
|
||||
"and the docked trailer acts as the airlock. The envelope is insulated PIR sandwich panel "
|
||||
"(100 mm roof, 75 mm walls) on a 2.5 m masonry dado, taped and gasketed, because the building is "
|
||||
"air-conditioned and must be airtight against smoke-season particulate; mineral-wool panels "
|
||||
"where fire compartmentation (EI 60 or 120) or acoustics require them."),
|
||||
T(["Zone", "Area"], [
|
||||
["Semi-automatic press hall (Newbilt Duplex specification)", "~200 m²"],
|
||||
["Automatic press zone", "~100 to 150 m²"],
|
||||
["Galvanics clean room, water treatment, chemical storage", "~80 to 150 m²"],
|
||||
["Mastering and cutting room", "~40 to 60 m²"],
|
||||
["Stamper preparation", "~30 m²"],
|
||||
["Cassette duplication and CD-R duplication (new)", "~60 to 100 m²"],
|
||||
["Packaging, assembly and finished-goods warehouse", "~300 to 500 m²"],
|
||||
["Utility rooms inside (compressor, electrical, IT)", "~50 to 100 m²"],
|
||||
["Offices, QC and listening room, archive, entrance", "~150 to 200 m²"],
|
||||
["Circulation, walls, miscellaneous", "~150 to 250 m²"],
|
||||
["**Total under roof**", "**~1,200 to 1,700 m²**, against 1,500 m² built"],
|
||||
], widths_mm=[110, 60],
|
||||
caption="The boiler hall (66 m²), biomass fuel store (42 m²), chiller room (60 m²) and cooling "
|
||||
"tower pad sit in a detached utility yard, which frees the main floor for production and "
|
||||
"keeps the boiler in its own room as Thai regulations require."),
|
||||
P("Site works include a ~444 m perimeter wall, one 10 m sliding gate with a guard house, 4,387 m² "
|
||||
"of paving in two classes (heavy-duty for trucks, general for parking and yard), roofed parking "
|
||||
"for 10 cars and 30 scooters with ~40 kWp of PV on the canopies, foundations for a 9 m fire "
|
||||
"water tank and a 5 m raw-water tank, a fire ring main and hydrants, stormwater drainage "
|
||||
"(66% of the plot becomes sealed, so a detention pond may be required for the permit), and "
|
||||
"domestic sanitation."),
|
||||
P("**A second building is expansion, not a gap.** The whole zone table fits inside the phase 1 "
|
||||
"building. A 45 × 38 m (1,710 m²) phase 2 building fits the plot's narrowing southern end with a "
|
||||
"two-bay sawtooth roof carrying ~251 kWp, a 20 m yard between the two for fire separation and "
|
||||
"shared loading; total coverage stays at 39% of the deed area. It is where a CD replication line "
|
||||
"would go if that option is chosen (section 4.9), or where finished goods move to free production "
|
||||
"floor."),
|
||||
H3("What the building costs, and what is not known"),
|
||||
P("Building and civil works are estimated at **USD 650k to 1.1M** (THB 21M to 36M) at 12,000 to "
|
||||
"20,000 THB/m² for Thai pre-engineered steel construction to the insulated-panel specification, "
|
||||
"plus ~20% site works. A budget-level price request with quantities from the parametric model is "
|
||||
"ready to send to contractors. The single largest unknown is the ground: the parcel is likely "
|
||||
"soft former paddy, and a piled slab can add several hundred to 1,000+ THB/m² across the "
|
||||
"building, 4,387 m² of paving and a 1,711 m³ fill platform. A soil boring test (THB 30k to 50k) "
|
||||
"comes before any real number and is in the phase 0 budget."),
|
||||
IMG("renders/aerial-southwest.png", 150, "Figure 3. Massing render from the south-west: phase 1 "
|
||||
"building with rooftop PV, utility yard, tanks and parking "
|
||||
"canopies. Source: parametric model."),
|
||||
|
||||
H2("6.3 Safety, security and the irreplaceable"),
|
||||
P("Worker safety is a founding priority on equal footing with the environmental commitments. The "
|
||||
"pressing line combines hydraulics at ~180 bar, saturated steam at 10 bar / 180 °C and hot PVC; "
|
||||
"the galvanics room handles silver nitrate and nickel salts. The plan provides vendor-led "
|
||||
"commissioning training extending into an in-house program, mandatory PPE per area, controlled "
|
||||
"access and decontamination for the clean rooms, eyewash and emergency shower at the plating "
|
||||
"line, a licensed boiler operator, and fume-extraction interlocks (no extraction, no plating)."),
|
||||
P("Physical security is zoned: production, galvanics, chemical storage, the master and stamper "
|
||||
"archive, IT, and shipping each get badge or biometric access with logs; CCTV at entrances, "
|
||||
"perimeter, dock, production, chemicals and archive under a PDPA-compliant retention policy. Two "
|
||||
"things are irreplaceable and get their own protection: **the metal archive** (a destroyed father "
|
||||
"means re-cutting and re-plating a release; fire-rated room, climate control, access logging, "
|
||||
"clean-agent gas suppression because sprinkler water would destroy lacquers and paper and corrode "
|
||||
"the metal) and **client audio** (pre-release music is a leak target; isolated encrypted storage, "
|
||||
"need-to-know access, and a written policy the sales team can show labels)."),
|
||||
P("Fire suppression is zoned, not full-building gas: clean agent only in small sealed high-value "
|
||||
"rooms (archive, IT, switchrooms, control cabinets), water mist or sprinklers in production and "
|
||||
"the carton-heavy warehouse, detection and containment rather than suppression for the battery "
|
||||
"enclosure. The site carries its own ~200 m³ fire reserve and pump set because mains flow in "
|
||||
"Mueang Chi cannot be assumed. The property insurer is involved before the design is fixed."),
|
||||
|
||||
# ----------------------------------------------------- 7 BOI
|
||||
H1("7. Regulatory: BOI promotion and licences"),
|
||||
P("The project will apply for promotion by the Thailand Board of Investment. In BOI terms vinyl "
|
||||
"pressing is basic plastic consumer-goods manufacturing (incentive Group B): no corporate income "
|
||||
"tax holiday on the core activity, but the full non-tax package and machinery duty exemption. The "
|
||||
"tax value is engineered on top through measures where this project happens to be strong, so the "
|
||||
"application is pitched on solar, automation and technology content, not on pressing plastic "
|
||||
"discs."),
|
||||
T(["What BOI gives", "Why it matters here"], [
|
||||
["Machinery import duty exemption", "Almost the entire equipment list is imported (Germany, "
|
||||
"Sweden, USA, UK, Japan). Significant across every equipment block."],
|
||||
["100% foreign ownership and land privileges", "Overrides Foreign Business Act restrictions "
|
||||
"for the promoted activity. Land ownership privilege is unused under the lease strategy."],
|
||||
["Visas and work permits via One Stop Service", "Foreign commissioning engineers and the "
|
||||
"technical consultant, without the four-Thai-employees-per-permit rule."],
|
||||
["Raw material duty relief", "Exemption for export production; up to 90% reduction for "
|
||||
"domestic sales. Relevant for imported PVC compound until a Thai source is qualified."],
|
||||
["Solar and battery measure (alternative-energy, 2025)", "CIT exemption based on the solar "
|
||||
"investment; PV modules, inverters, battery and mounting explicitly eligible. The planned "
|
||||
"installation converts directly into tax-exemption headroom."],
|
||||
["Smart and Sustainable Industry upgrade measure", "Rewards automation, smart machinery and "
|
||||
"energy efficiency, typically 3 years of CIT exemption capped around 50% of the qualifying "
|
||||
"spend. The automatic presses, inline inspection, metrology and heat recovery are the natural "
|
||||
"fit."],
|
||||
["R&D merit", "At least 1% of revenue in Thai R&D adds 1 to 3 exemption years. Galvanics "
|
||||
"process development, recycled-PVC work and the audio-QC system could be framed as "
|
||||
"qualifying development."],
|
||||
], widths_mm=[58, 112]),
|
||||
P("**Requirements:** minimum THB 1M investment excluding land and working capital (cleared by the "
|
||||
"first press line alone); debt-to-equity no higher than 3:1 for the life of the privileges, which "
|
||||
"shapes the financing mix; modern machinery and process; progress reporting. BOI promotion does "
|
||||
"not replace the factory licence (Ror.Ngor. 4), boiler registration, hazardous-substance "
|
||||
"registration for the plating chemistry, or wastewater permitting. The galvanics effluent is the "
|
||||
"most likely regulatory friction point regardless of BOI, which is one argument for the "
|
||||
"industrial estate's shared treatment."),
|
||||
P("**Process:** file via the e-Investment system and present at an analyst meeting; decision in "
|
||||
"about 40 working days for projects under THB 200M, roughly 4 to 6 months end to end to the "
|
||||
"certificate; incorporate the company under it; import machinery within a window commonly 30 "
|
||||
"months from issuance; reach full operation by about 36 months. The Northern Economic Corridor "
|
||||
"(Chiang Mai, Lamphun, Lampang) is promoted as a creative-economy base, which a music "
|
||||
"manufacturing story fits; current area add-ons must be confirmed with BOI directly."),
|
||||
CALL("Two things to settle before filing", [
|
||||
"**Scope.** Cassette duplication and CD-R duplication should be in the application from the "
|
||||
"start. CD replication is a separate activity code and cannot be added casually afterwards; "
|
||||
"the investor's decision on section 4.9 is needed before the filing date.",
|
||||
"**Sequencing.** The plan's rule is that no machinery purchase order precedes the certificate, "
|
||||
"because duty exemption is not retroactive. But the exemption attaches at importation, not "
|
||||
"at contract signature. If a press can be ordered with a deposit while the application is in "
|
||||
"review, provided it clears customs after issuance, the launch pulls forward by 6 to 9 "
|
||||
"months. This is the first question for the BOI consultant and the only item in the plan "
|
||||
"worth half a year.",
|
||||
]),
|
||||
P("Keep any future label, content or retail arm in a separate entity: media and service activities "
|
||||
"carry different, sometimes Thai-ownership, conditions and would muddy a clean manufacturing "
|
||||
"application. Poy Records as a label brand is therefore a separate question from the plant."),
|
||||
]
|
||||
@@ -0,0 +1,549 @@
|
||||
"""Part C: investment, revenue and operating model, timeline, phasing, team and ownership, risks, next steps."""
|
||||
from plan_lib import H1, H2, H3, P, B, N, T, KV, IMG, CALL, PB
|
||||
|
||||
|
||||
def blocks():
|
||||
return [
|
||||
# ------------------------------------------------- 8 investment
|
||||
H1("8. Investment"),
|
||||
P("Everything priced or estimated so far, rolled into one view. Blocks marked 'estimate' have no "
|
||||
"vendor confirmation; the rest trace to quotes or researched ranges. Treat all figures as "
|
||||
"budgetary with a wide margin (plus or minus 30 to 50% on the estimates) until requests for "
|
||||
"quotation replace them. Land is now a lease and is removed from capital; cassette and CD-R "
|
||||
"duplication are added."),
|
||||
H2("8.1 Full vision, all blocks"),
|
||||
T(["Block", "USD", "THB", "Basis"], [
|
||||
["Newbilt semi-automatic line, full Duplex", "350k to 370k", "11.5M to 12.2M", "Quoted: 2024 "
|
||||
"offer EUR 200,500 (~USD 232k) plus quoted second press and extruder ~USD 115k; expired, "
|
||||
"re-quote required"],
|
||||
["Two fully automatic presses", "580k to 920k", "19.1M to 30.5M", "Estimate: ~USD 340k per "
|
||||
"press indicative, unverified, plus delivery and commissioning"],
|
||||
["Galvanics line (Digital Matrix)", "320k to 650k", "10.7M to 21.4M", "Estimate: six-figure "
|
||||
"turnkey inference from vendor research"],
|
||||
["Zero-discharge water treatment", "90k to 240k", "3.0M to 8.0M", "Researched: utilities "
|
||||
"option row"],
|
||||
["Stamper preparation (Sibert)", "60k to 170k", "1.9M to 5.7M", "Estimate, pricing unpublished"],
|
||||
["Mastering and cutting room", "290k to 580k", "9.6M to 19.1M", "Estimate, bespoke market"],
|
||||
["Metrology and QC stack", "350k to 810k", "11.5M to 26.7M", "Estimate, phaseable"],
|
||||
["Utilities, solar, battery, HVAC, racking baseline", "730k to 1,270k", "24.0M to 41.6M",
|
||||
"Researched: Thai supplier ranges, THB-native, plus or minus 30%"],
|
||||
["Building ~1,500 m² plus civil and site works", "650k to 1,100k", "21.4M to 36.2M",
|
||||
"Estimate at 12,000 to 20,000 THB/m² plus ~20% site works; RFQ ready"],
|
||||
["Land", "0 (leased)", "0", "Was 360k to 970k for purchase; rent moves to operating cost"],
|
||||
["Cassette duplication line", "40k to 150k", "1.3M to 5.0M", "Estimate, section 4.8"],
|
||||
["CD-R duplication", "15k to 40k", "0.5M to 1.3M", "Estimate, section 4.9 option A"],
|
||||
["Traceability and factory software", "60k to 170k", "1.9M to 5.7M", "Estimate, development "
|
||||
"project"],
|
||||
["Freight, installation, training (non-Newbilt)", "120k to 230k", "3.8M to 7.6M", "Estimate"],
|
||||
["Working capital, PVC stock, spares, lease deposit", "135k to 290k", "4.5M to 9.6M",
|
||||
"Estimate; includes lease deposit and first-year rent"],
|
||||
["Subtotal", "~3.8M to 7.0M", "~125M to 231M", ""],
|
||||
["Contingency 15%", "~0.6M to 1.0M", "~19M to 35M", ""],
|
||||
["**Total, full vision**", "**~4.4M to 8.0M**", "**~144M to 264M**", "Before the lease "
|
||||
"decision: 4.7M to 8.9M"],
|
||||
["Not included: CD replication (option B)", "1.5M to 4.0M", "50M to 132M", "Separate "
|
||||
"decision, separate feasibility study, phase 2 building"],
|
||||
["Not included: phase 2 building", "~750k to 1,250k", "~25M to 41M", "Same THB/m² basis on "
|
||||
"1,710 m²; expansion "
|
||||
"beyond the current "
|
||||
"programme"],
|
||||
], widths_mm=[52, 30, 30, 58], font_pt=8.5),
|
||||
|
||||
H2("8.2 Phase 1: what launch actually costs"),
|
||||
P("The launch configuration is far smaller than the full vision, and it is the number the "
|
||||
"investor is asked to commit to after the gates clear. It buys a purpose-built, climate-controlled "
|
||||
"plant with one semi-automatic press line, bridge stampers from the UK, basic QC, first-version "
|
||||
"traceability, the rooftop solar array and battery, and the cassette and CD-R lines."),
|
||||
T(["Block", "USD low", "USD high", "Note"], [
|
||||
["Phase 0: consultants, legal, soil test, trade show, re-quotes", "40k", "80k", "Section 14"],
|
||||
["Newbilt Single 2.1 system incl. freight", "230k", "260k", "Re-quote drift on the 2024 EUR "
|
||||
"figure"],
|
||||
["Building, civil and site works", "650k", "1,100k", "Soil result is the main variable"],
|
||||
["Utilities: electric steam, compressed air, chiller, water treatment, racking, whole-plant "
|
||||
"HVAC, ~290 kWp PV, 271 kWh battery", "690k", "1,160k", "Clean-room adder deferred to the "
|
||||
"galvanics phase"],
|
||||
["Basic QC, PVC lab, traceability v1", "70k", "180k", "Microscope, benchtop lab, sampled "
|
||||
"listening, software v1"],
|
||||
["Cassette duplication and CD-R lines", "60k", "190k", "Tier A or small tier B cassette"],
|
||||
["Freight, installation, commissioning beyond Newbilt's 5 days, training", "40k", "100k",
|
||||
"Newbilt service EUR 84/h plus travel"],
|
||||
["Working capital, PVC stock, spares, lease deposit, first-year rent", "130k", "290k", ""],
|
||||
["Subtotal", "~1.9M", "~3.4M", ""],
|
||||
["Contingency 15%", "~0.3M", "~0.5M", ""],
|
||||
["**Phase 1 total**", "**~2.2M**", "**~3.9M**", "**~THB 73M to 128M**"],
|
||||
], widths_mm=[80, 22, 22, 46], font_pt=8.5),
|
||||
P("Galvanics, the automatic presses, the mastering room, the full metrology stack and the biomass "
|
||||
"boiler follow as revenue-funded or second-round expansions; the remainder between phase 1 and "
|
||||
"the full vision is roughly USD 2.2M to 4.1M. The planning already supports the split: the "
|
||||
"Duplex-ready hydraulics, the bridge role for Stamper Discs, the staged electric-to-biomass "
|
||||
"steam path and the phaseable metrology stack are all documented for exactly this reason."),
|
||||
|
||||
H2("8.3 Tranches"),
|
||||
P("Phase 1 capital is released against milestones rather than up front, so that each tranche is "
|
||||
"spent only once the previous gate has cleared:"),
|
||||
T(["Tranche", "Trigger", "Covers", "Approx. share"], [
|
||||
["0", "Now", "Phase 0 work", "2 to 3%"],
|
||||
["1", "BOI certificate issued; lease signed; zoning and soil confirmed; pricing validated",
|
||||
"Press order deposit, building design and permit, long-lead utility orders", "25 to 30%"],
|
||||
["2", "Building permit; press build confirmed", "Construction, utilities, solar", "40 to 45%"],
|
||||
["3", "Building watertight; machinery shipped", "Fit-out, HVAC commissioning, QC and software, "
|
||||
"cassette and CD-R lines, hiring", "15 to 20%"],
|
||||
["4", "Commissioning complete", "Working capital, stock, first stamper orders", "8 to 10%"],
|
||||
], widths_mm=[16, 62, 66, 26]),
|
||||
|
||||
H2("8.4 What moves the number"),
|
||||
B([
|
||||
"**BOI** machinery duty exemption applies across most of the equipment blocks, and the solar "
|
||||
"and automation spend converts into tax-exemption headroom; the application precedes every "
|
||||
"purchase order.",
|
||||
"**The ground.** Soft paddy soil under the building, paving and fill platform is the single "
|
||||
"largest cost variable in the building package, and the cheapest to resolve.",
|
||||
"**Biggest uncertainty bands:** metrology (partly a choice of ambition), the mastering room "
|
||||
"(bespoke vendor, no public pricing), the automatic presses (one unverified price point), "
|
||||
"the galvanics line, and every cassette and CD figure.",
|
||||
"**Every band narrows only with quotes.** The supplier map in the project repository lists who "
|
||||
"to ask, and the re-quotes are phase 0 work.",
|
||||
]),
|
||||
H2("8.5 Currency exposure"),
|
||||
P("Revenue will be mostly USD and EUR from export customers; the cost base is mostly THB; the "
|
||||
"largest single equipment block is EUR-denominated and unhedged against an order that will not "
|
||||
"be placed for roughly a year. A 10% move in EUR/THB between now and the Newbilt order is worth "
|
||||
"USD 25k to 35k on that line alone. Operationally, roughly two-thirds of direct cost (labour, "
|
||||
"energy, cartons, maintenance) is THB, so a 10% baht appreciation takes 0.09 to 0.17 USD off the "
|
||||
"gross margin per record without any operational change. Two mitigations are planned: ask every "
|
||||
"re-quote to state its currency and validity period, and decide early whether to quote regional "
|
||||
"customers in THB. Hedging the press order once the BOI timeline is known is a decision for the "
|
||||
"investor."),
|
||||
|
||||
# ------------------------------------- 9 revenue and operating model
|
||||
H1("9. Revenue and operating model"),
|
||||
P("A first-pass economic model at mature capacity. Most numbers are estimates from industry pricing "
|
||||
"norms and the project's own cost research; the point is the shape of the economics and which "
|
||||
"levers matter, not precision. The pricing assumptions are deliberately held at the conservative "
|
||||
"Western export benchmark even though the one set of Thai market data (section 3.3) says "
|
||||
"domestic prices are several times higher: re-basing the whole model on one supplier's price "
|
||||
"list in a market with one supplier would be worse than leaving it low."),
|
||||
H2("9.1 Pricing assumptions"),
|
||||
B([
|
||||
"**Blended pressing price: USD 3.50 to 4.60 per record** (THB 115 to 150), across run sizes "
|
||||
"and the standard/colour mix, covering pressing, labels, inner sleeve and assembly into the "
|
||||
"jacket; deluxe packaging passed through at cost plus margin. Western plants charge roughly "
|
||||
"USD 2.90 to 5.80 for standard black at volume, with premiums of 0.60 to 1.70 for colour and "
|
||||
"splatter, 180 g and small runs.",
|
||||
"**Per-title services: USD 580 to 1,730 per title** (THB 19k to 57k) for mastering, cutting, "
|
||||
"stampers and test pressings, at roughly 300 to 600 titles a year at full capacity. Observed "
|
||||
"Thai rates suggest this is understated by up to three to four times for a full album, but "
|
||||
"mastering is labour-bound (an engineer masters perhaps two to four songs a day) and the plan "
|
||||
"staffs one to two engineers, which caps this line.",
|
||||
"**Cassettes and CD-Rs** are add-on lines, not in the totals: illustratively 20k to 60k "
|
||||
"cassettes a year at USD 2.50 to 4.00 (USD 50k to 240k) and 10k to 40k CD-Rs at USD 1.50 to "
|
||||
"3.00 (USD 15k to 120k). Estimates with no market data behind them yet.",
|
||||
"**Dropshipping fees** per parcel are on top and not modelled.",
|
||||
]),
|
||||
H2("9.2 Revenue at full capacity"),
|
||||
T(["Scenario", "Records/yr", "Pressing revenue", "Title services", "Total USD", "Total THB"], [
|
||||
["Mature, low", "1.0M", "~3.5M", "~0.17M", "**~3.7M/yr**", "**~122M/yr**"],
|
||||
["Mature, high", "1.5M", "~6.1M", "~0.69M", "**~6.8M/yr**", "**~224M/yr**"],
|
||||
], widths_mm=[26, 22, 30, 28, 32, 32],
|
||||
caption="Low: 1.0M × USD 3.50 plus 300 titles × 580. High: 1.5M × 4.05 plus 600 titles × 1,155. "
|
||||
"A pure-volume plant runs at the low per-title end; a boutique-heavy mix pushes titles up "
|
||||
"and volume down."),
|
||||
H2("9.3 Direct cost per record"),
|
||||
T(["Cost element", "USD/record", "THB/record", "Basis"], [
|
||||
["PVC compound", "0.29 to 0.46", "9.6 to 15.2", "~150 to 160 g effective virgin at 1.75 to "
|
||||
"2.90 USD/kg; regrind loop covers scrap"],
|
||||
["Labels", "0.03 to 0.09", "1.1 to 3.0", "Printed pairs, volume"],
|
||||
["Inner sleeve", "0.09 to 0.17", "3.0 to 5.7", "Poly-lined"],
|
||||
["Jacket (standard)", "0.40 to 0.92", "13.3 to 30.4", "Ploy Printing; largely pass-through"],
|
||||
["Shrink wrap", "0.02 to 0.03", "0.8 to 1.1", "Film"],
|
||||
["Energy", "0.12 to 0.23", "3.8 to 7.6", "~1.5 to 2 kWh blended per record at grid/solar mix"],
|
||||
["Production labour", "0.08 to 0.16", "2.6 to 5.4", "13 to 19 direct staff at northern Thai "
|
||||
"rates, loaded ×1.3, over 1.0 to 1.5M "
|
||||
"records"],
|
||||
["Stamper amortisation", "0.03 to 0.12", "1.0 to 4.0", "In-house three-generation set at "
|
||||
"~USD 60 to 150 marginal over 1,000 "
|
||||
"to 2,000 pressings"],
|
||||
["Maintenance, spares, consumables", "0.12 to 0.23", "3.8 to 7.6", "Estimate"],
|
||||
["Reject allowance", "0.12 to 0.17", "3.8 to 5.7", "5 to 10% reject rate carried by good units"],
|
||||
["**Total direct cost**", "**~1.30 to 2.58**", "**~43 to 85**", ""],
|
||||
], widths_mm=[42, 26, 26, 76], font_pt=8.5),
|
||||
P("Blended price minus direct cost gives a gross margin of about **USD 2.11 per record at the "
|
||||
"midpoint (~52%)**. The spread is wide and worth stating: the adverse corner (low price meeting "
|
||||
"high cost) leaves USD 0.92 (26%), the favourable corner USD 3.30 (72%). Price realisation and "
|
||||
"cost control each move the margin by more than a third on their own."),
|
||||
H2("9.4 Operating picture at full capacity"),
|
||||
P("Built from the components above. Three scenarios, each internally consistent: adverse pairs low "
|
||||
"volume with the low price and high cost; favourable does the opposite; central takes midpoints. "
|
||||
"Cash overheads (admin, sales, insurance, facility running, IT, professional fees, marketing, and "
|
||||
"now the land lease) are estimated at USD 210k to 480k a year and paired high-to-adverse. "
|
||||
"Depreciation is the 4.4M to 8.0M capital over 10 to 15 years."),
|
||||
T(["USD per year", "Adverse", "Central", "Favourable"], [
|
||||
["Records", "1.0M", "1.25M", "1.5M"],
|
||||
["Blended price", "3.50", "4.05", "4.60"],
|
||||
["Direct cost", "2.58", "1.94", "1.30"],
|
||||
["Contribution per record", "0.92", "2.11", "3.30"],
|
||||
["Pressing contribution", "0.92M", "2.64M", "4.95M"],
|
||||
["Title-services contribution", "0.10M", "0.28M", "0.48M"],
|
||||
["**Total contribution**", "**1.02M**", "**2.92M**", "**5.43M**"],
|
||||
["Cash overheads incl. lease", "0.48M", "0.35M", "0.21M"],
|
||||
["**EBITDA**", "**0.54M**", "**2.57M**", "**5.22M**"],
|
||||
["Depreciation", "0.80M", "0.50M", "0.29M"],
|
||||
["**Operating profit**", "**−0.26M**", "**2.07M**", "**4.93M**"],
|
||||
["Payback on 4.4M to 8.0M capital", "never", "2.1 to 3.9 yr", "0.9 to 1.6 yr"],
|
||||
], widths_mm=[60, 36, 36, 38]),
|
||||
CALL("Reading this table honestly", [
|
||||
"**The adverse case loses money.** At 1.0M records, the low price, the high cost and the high "
|
||||
"capital, the plant is EBITDA-positive but does not cover depreciation. Utilisation and price "
|
||||
"realisation are not upside levers; they decide solvency.",
|
||||
"**The favourable case is not credible**, and that is a finding about the cost table, not the "
|
||||
"business. A 69% EBITDA margin is roughly triple what pressing plants earn; even the central "
|
||||
"case at ~47% is well above industry norms of 15 to 25%. The direct cost table counts only "
|
||||
"per-record consumables and direct payroll and almost certainly understates: missing at "
|
||||
"minimum are indirect production labour, consumables beyond those listed, scrap above the "
|
||||
"reject allowance, water and effluent, inbound freight, inventory carrying cost and sales "
|
||||
"commission. Treat the adverse column as the honest one until those lines exist.",
|
||||
"**This is a sensitivity, not a forecast.** BOI tax exemptions shield part of early-year "
|
||||
"profits where there are any. The mix lever is real: colour, splatter and boutique runs carry "
|
||||
"20 to 50% premiums and suit the semi-automatic line, and the title-services line has "
|
||||
"software-like margins once the rooms exist.",
|
||||
]),
|
||||
H2("9.5 The ramp"),
|
||||
P("The operating picture above is the mature plant. The launch plant (one line, one shift) presses "
|
||||
"90k to 135k records a year, which at the same blended price is USD 0.3M to 0.6M of pressing "
|
||||
"revenue plus title services, cassette and CD-R lines, and possibly stamper supply to the "
|
||||
"incumbent if galvanics comes early. It will not cover a full overhead base in year one, and "
|
||||
"early production runs slower and rejects more than any of these figures while the process "
|
||||
"settles. The Duplex expansion (second press on the existing hydraulics, ~USD 115k) is the "
|
||||
"first revenue-funded step and roughly doubles capacity for a small sum; it should be planned "
|
||||
"for year two. A year-by-year cash model belongs to the financing discussion and is a next "
|
||||
"step, not a claim this plan makes."),
|
||||
|
||||
# ----------------------------------------------- 10 timeline
|
||||
H1("10. Timeline: today to first paying release"),
|
||||
P("Anchored at September 2026. Every duration is an estimate; one of them is not documented "
|
||||
"anywhere and governs the whole schedule. **First paying release lands around Q4 2028 to "
|
||||
"mid-2029, roughly 27 to 33 months from today.** The critical path is not construction:"),
|
||||
CALL("The critical path", "BOI certificate → press order → press build → sea freight → "
|
||||
"commissioning. Everything else, including the entire building, has float against that chain. "
|
||||
"The two things that move the date are the BOI filing date and the press vendor's build time, "
|
||||
"and the second is not documented anywhere: 9 to 12 months is an industry-typical figure for "
|
||||
"a built-to-order system from a small German manufacturer and it carries about 40% of the "
|
||||
"schedule. If the real figure is 15 to 18 months, launch moves to mid-2029. The re-quote now "
|
||||
"being requested asks for quoted build time and order-book position; it is one line in an "
|
||||
"email and the single highest-value missing fact in the project."),
|
||||
T(["#", "Step", "Duration", "Source"], [
|
||||
["1", "Engage BOI consultant, prepare application", "4 to 5 months", "Estimate"],
|
||||
["2", "BOI review to certificate", "4 to 6 months", "BOI: ~40 working days decision, 4 to 6 "
|
||||
"months in practice"],
|
||||
["3", "Company incorporation under the certificate", "1 to 2 months", "Roadmap"],
|
||||
["4", "Press order to vendor-ready", "**9 to 12 months**", "**Assumed; not yet quoted**"],
|
||||
["5", "Sea freight Germany to Thailand", "6 to 8 weeks", "Newbilt quote includes freight"],
|
||||
["6", "Install, commission, train", "2 to 3 months", "Newbilt includes 5 days; rest at "
|
||||
"~USD 97/h plus travel"],
|
||||
["7", "Test pressings, QC baseline, reject shakedown", "1.5 to 2 months", "Roadmap"],
|
||||
["", "**Total**", "**~27 to 33 months**", ""],
|
||||
], widths_mm=[8, 70, 34, 58]),
|
||||
T(["Track (in parallel)", "Needed by", "Float", "Becomes critical if"], [
|
||||
["Land lease, zoning, soil test", "Design start, ~Q1 2027", "~4 months", "Zoning fails and a "
|
||||
"new site is needed (~6 months reset)"],
|
||||
["Phase 1 construction", "Machinery arrival, ~Q3 2028", "~3 to 5 months", "Soft soil forces "
|
||||
"piling, or permits stall"],
|
||||
["Factory licence (Ror.Ngor. 4)", "First production, Q4 2028", "~4 months", "Galvanics "
|
||||
"effluent scope is raised early; keep it out of the phase 1 licence scope unless galvanics "
|
||||
"moves forward"],
|
||||
["Solar and battery", "Fit-out, ~Q2 2028", "Large", "Not a risk; battery lead is 50 to 60 "
|
||||
"days"],
|
||||
["Cassette and CD-R lines", "Fit-out, ~Q2 2028", "Large", "Not a risk; short lead times"],
|
||||
["Hiring and training", "Commissioning, ~Q3 2028", "~2 months", "Galvanics expertise if "
|
||||
"galvanics moves forward: the "
|
||||
"hardest skill to hire"],
|
||||
], widths_mm=[40, 40, 24, 66]),
|
||||
P("**BOI windows are comfortable** on this schedule: machinery clears customs ~Q3 2028 against a "
|
||||
"30-month import window ending ~Q4 2029, and first production ~Q4 2028 against a ~36-month "
|
||||
"full-operation deadline. The margin is consumed by exactly one scenario: filing early to start "
|
||||
"the clock and then discovering the site does not work. Verify zoning before filing."),
|
||||
P("**Leasing does not by itself pull the launch forward.** Construction has only 3 to 5 months of "
|
||||
"float against the press chain; removing purchase money from the plan does not move the end date. "
|
||||
"Only the press order moves it, which is the BOI sequencing question in section 7. The two are "
|
||||
"one lever, not two."),
|
||||
P("**What a delay costs.** A month lost engaging the BOI consultant costs a month of launch. A "
|
||||
"month lost on land or construction costs nothing until the float is gone. A month lost getting "
|
||||
"the press lead time in writing costs nothing today and may cost six months of surprise later."),
|
||||
|
||||
# ------------------------------------ 11 phasing and the galvanics question
|
||||
H1("11. Phasing, and the one sequencing question that matters"),
|
||||
P("Nothing in phases 2 to 5 starts before its gate clears: no machinery order before the BOI "
|
||||
"certificate, no large spending before the market validates the price, no coppice planning "
|
||||
"before the land-title check. The phased entry is the default path; the full vision is reached "
|
||||
"through it, not instead of it."),
|
||||
T(["Phase", "Content", "Gate"], [
|
||||
["0. Validate and clear the gates (now)", "Market validation at Making Vinyl and with regional "
|
||||
"labels; BOI consultant, activity code, application; lease terms, zoning, soil test; 60 rai "
|
||||
"title check; re-quotes from Newbilt, Pheenix, Digital Matrix, Stamper Discs, building "
|
||||
"contractors, Thai utility vendors; solar and battery engineering checks", "Runs in parallel; "
|
||||
"gates everything"],
|
||||
["1. Quotes and site (during the BOI window)", "Firm quotes; site selection between the Mueang "
|
||||
"Chi lease and the industrial estate; resolve the 35% vs 45% humidity floor", "BOI filed"],
|
||||
["2. Commit", "Incorporate under the certificate; sign the press order; sign the lease; order "
|
||||
"long-lead items; written stamper turnaround agreement with Stamper Discs", "BOI certificate "
|
||||
"and validated "
|
||||
"pricing"],
|
||||
["3. Build and commission (~6 to 12 months after commitment)", "Construction, utilities, HVAC, "
|
||||
"solar, battery, security; machinery installation and vendor commissioning; traceability v1; "
|
||||
"cassette and CD-R lines; hire and train", "Building permit, press build confirmed"],
|
||||
["4. Launch (pressing with bridge stampers)", "Test pressings, QC baselines, reject shakedown; "
|
||||
"first paying releases; per-lot PVC gate live; qualify Thai compound", "Commissioning complete"],
|
||||
["5. Expand (revenue-gated)", "Duplex second press; galvanics line with zero-discharge water; "
|
||||
"automatic presses; full metrology and the audio QC station; biomass boiler; mastering and "
|
||||
"cutting room; phase 2 building; CD replication if chosen", "Revenue and second-round "
|
||||
"capital"],
|
||||
], widths_mm=[42, 96, 32], font_pt=8.5),
|
||||
H2("Should galvanics come first rather than fifth?"),
|
||||
P("The roadmap places the plating line in phase 5. The August 2026 market evidence argues for "
|
||||
"costing an earlier start, and the investor should decide with the following in view:"),
|
||||
T(["For moving galvanics to phase 1", "Against"], [
|
||||
["It addresses the market's actual bottleneck: imported metalwork, not press capacity. The "
|
||||
"incumbent turns away small runs and adds weeks above 3,000 units, most plausibly because "
|
||||
"every stamper set is an overseas order.", "It still needs a clean room, zero-discharge water "
|
||||
"treatment and hazardous-substance permitting, "
|
||||
"which is the most likely regulatory friction "
|
||||
"point in the whole project."],
|
||||
["It is a fraction of the full capital (USD 320k to 650k plus 90k to 240k water) and is "
|
||||
"driven by title count, not record volume: payback at 300 titles a year is one to five "
|
||||
"years depending on which end of the saving range holds.", "It is the hardest skill to hire. "
|
||||
"No plating expertise exists in "
|
||||
"the country; the process "
|
||||
"engineer comes from abroad or is "
|
||||
"trained abroad."],
|
||||
["It could earn before CNX's own press hall exists, by supplying stampers into the incumbent's "
|
||||
"spare press capacity: 300 titles a year at wholesale might net USD 60k to 100k of "
|
||||
"contribution, a useful offset against the capital.", "That offset is not a business case: "
|
||||
"it assumes a competitor moves its "
|
||||
"entire stamper supply to a new "
|
||||
"entrant who is also its rival, and "
|
||||
"the incumbent's actual title count "
|
||||
"is unknown."],
|
||||
["It removes the launch plan's dependence on a single small UK supplier with no documented "
|
||||
"Asian customers and two international freight legs per job.", "The saving is measured "
|
||||
"against the wrong supplier: "
|
||||
"CNX's avoided cost at launch "
|
||||
"is the Stamper Discs price, "
|
||||
"which has not been quoted "
|
||||
"yet."],
|
||||
], widths_mm=[85, 85], font_pt=8.5),
|
||||
P("**Recommendation:** get the Stamper Discs quote and a line-itemised full-service invoice from "
|
||||
"the incumbent in phase 0, cost the galvanics room properly with Digital Matrix and a Thai water "
|
||||
"vendor, and put the decision to the investor with real numbers before the BOI application is "
|
||||
"finalised, because the galvanics effluent changes the licence scope."),
|
||||
|
||||
# --------------------------------------- 12 team, ownership, governance
|
||||
H1("12. Team, ownership and governance"),
|
||||
H2("12.1 The people"),
|
||||
T(["Who", "Role"], [
|
||||
["Simona Meesaiyati", "Project Lead. Owns the commercial side: market validation, label "
|
||||
"relationships, the Making Vinyl outreach, the BOI track and the site."],
|
||||
["Alexander Grabowski", "Technical Consultant. Owns the plant: equipment selection and "
|
||||
"vendor negotiation, the utility and efficiency architecture, the parametric building model, "
|
||||
"the traceability and QC specification, and the factory software."],
|
||||
["buildfor.life", "Supports the project in a consulting capacity: engineering, sustainability "
|
||||
"and project management."],
|
||||
["Ploy Printing (Ploy Karnpim Limited Partnership, Chiang Mai)", "Packaging partner and "
|
||||
"intended strategic co-investor: jackets, sleeves, inserts, J-cards and CD packaging on "
|
||||
"offset, digital, risograph, UV and latex presses, with existing vinyl packaging production."],
|
||||
["To be hired", "Plant manager; galvanics process engineer (when galvanics is built); "
|
||||
"mastering and cutting engineer (when the room is built); QC lead; maintenance; press and "
|
||||
"finishing operators; sales and customer service for the label relationship and the "
|
||||
"traceability portal."],
|
||||
["External", "BOI-registered consultant; Thai counsel for the lease, company and shareholder "
|
||||
"agreement; optional vinyl manufacturing consultancy for layout, installation and training "
|
||||
"(GBP 10k to 20k packages); Newbilt commissioning; property insurer."],
|
||||
], widths_mm=[48, 122]),
|
||||
P("Headcount at maturity is roughly 15 to 25 across shifts (4 to 6 press and finishing operators "
|
||||
"per shift, 2 to 3 galvanics, 1 to 2 mastering, 1 to 2 QC, 2 maintenance, 3 to 5 admin, sales "
|
||||
"and logistics), fewer than half that at launch, plus 1 to 2 for the cassette and CD-R lines."),
|
||||
H2("12.2 Ownership and structure"),
|
||||
P("Amounts and percentages are not yet agreed and are deliberately left open here. The intended "
|
||||
"structure is:"),
|
||||
B([
|
||||
"**One Thai limited company** holds the plant, the lease, the equipment and the BOI promotion. "
|
||||
"BOI promotion permits 100% foreign ownership of the promoted manufacturing activity, so the "
|
||||
"shareholding can be set on commercial terms rather than to satisfy the Foreign Business Act.",
|
||||
"**Shareholders:** the lead investor; Ploy Printing as strategic co-investor; and the founders. "
|
||||
"Ploy Printing's participation, whether in cash, in kind (print capacity, premises, working "
|
||||
"capital in packaging) or both, is to be discussed; its strategic value is a packaging partner "
|
||||
"whose incentives are aligned with the plant's schedule and quality, within the same province.",
|
||||
"**Debt-to-equity no higher than 3:1**, maintained for the life of the BOI privileges, which "
|
||||
"sets the floor on equity in the financing mix.",
|
||||
"**A shareholders' agreement** covering board composition, reserved matters (capital calls, "
|
||||
"phase 5 expansions, CD replication), pre-emption and transfer, information rights, and the "
|
||||
"founders' roles and remuneration.",
|
||||
"**Related-party discipline.** Ploy Printing will be both a shareholder and the plant's main "
|
||||
"packaging supplier. The supply agreement is priced at arm's length, benchmarked periodically "
|
||||
"against other printers, with quality and lead-time terms written down, and Ploy Printing "
|
||||
"abstains from board decisions on its own contract. This protects both the lead investor and "
|
||||
"Ploy Printing, and it is what a BOI or bank reviewer will look for.",
|
||||
"**Any label, content or retail activity (Poy Records as a brand) sits in a separate entity**, "
|
||||
"so the manufacturing company's BOI application stays clean.",
|
||||
]),
|
||||
|
||||
# ------------------------------------------------------ 13 risks
|
||||
H1("13. Risks and open questions"),
|
||||
P("Every plan has risks; the point of listing them is to say which are being retired, by what, and "
|
||||
"when. The first column is the plan's own honest severity."),
|
||||
T(["Severity", "Risk", "What it would do", "Mitigation and status"], [
|
||||
["High", "Pricing power in the region is unproven", "The single biggest assumption in the "
|
||||
"revenue model; a turnaround advantage nobody pays a premium for is a cost, not a moat",
|
||||
"Market validation gate: Making Vinyl, regional label conversations, a second Thai data "
|
||||
"point; no big-block spending before it clears"],
|
||||
["High", "Utilisation", "The cost structure is mostly fixed; 60% utilisation at mature size "
|
||||
"cuts operating profit by more than half; the adverse case is loss-making at full volume",
|
||||
"Phase capacity behind demand: one line, then Duplex, then automatic presses, each "
|
||||
"revenue-gated; hold spare capacity as the turnaround promise requires"],
|
||||
["High", "Press build time unknown", "Carries ~40% of the schedule; 15 to 18 months would "
|
||||
"push launch to mid-2029", "Required field in the Newbilt and Pheenix re-quotes, now"],
|
||||
["High", "Zoning of the candidate parcel unverified", "Gates phase 1 as much as phase 2; a "
|
||||
"site reset costs ~6 months", "Verify before the BOI filing; industrial estate as fallback"],
|
||||
["Medium", "Lease terms and building ownership", "A weak lease makes the building hard to "
|
||||
"finance and exposes it at expiry", "Registered 30 to 50 year lease with renewal, explicit "
|
||||
"building ownership and assignment rights; Thai counsel "
|
||||
"in phase 0"],
|
||||
["Medium", "Soil conditions", "Piling could add several hundred to 1,000+ THB/m² across "
|
||||
"building, paving and fill", "Soil boring test in phase 0 (THB 30k to 50k)"],
|
||||
["Medium", "Bridge stamper supplier", "Launch lead time rests on one small UK supplier with "
|
||||
"no Asian customers; the 10 to 20 day external metal block is unmeasured", "Written "
|
||||
"turnaround "
|
||||
"agreement; cost "
|
||||
"the galvanics-first "
|
||||
"option (section 11)"],
|
||||
["Medium", "Galvanics permitting and skills", "Effluent permitting is the most likely "
|
||||
"regulatory friction; plating expertise is the hardest hire", "Zero-discharge design from the "
|
||||
"start; industrial estate "
|
||||
"option; recruit or train abroad; "
|
||||
"keep out of the phase 1 licence "
|
||||
"unless brought forward"],
|
||||
["Medium", "Currency", "EUR equipment, THB costs, USD/EUR revenue; 10% moves are worth tens "
|
||||
"of thousands on the press order and 0.09 to 0.17 USD per record on margin", "State currency "
|
||||
"and validity "
|
||||
"in quotes; "
|
||||
"consider THB "
|
||||
"pricing "
|
||||
"regionally; "
|
||||
"hedge the press "
|
||||
"order"],
|
||||
["Medium", "Vinyl demand is cyclical", "Global capacity has been added; the model assumes the "
|
||||
"plant sells what it presses", "Mixed fleet suits boutique work that is less cyclical; "
|
||||
"cassette and CD-R lines diversify; phase behind demand"],
|
||||
["Medium", "Reject rate in early production", "Early runs will be worse than the 5 to 10% "
|
||||
"allowance while the process settles", "Vendor commissioning, materials lab from day one, "
|
||||
"QC layers, conservative launch promises"],
|
||||
["Medium", "Competitive response", "The incumbent may add plating, or a second entrant may "
|
||||
"appear; the domestic price ceiling may compress on our arrival", "Compete on capability "
|
||||
"(traceability, QC, "
|
||||
"climate), export "
|
||||
"customers, and the "
|
||||
"collaboration under "
|
||||
"discussion"],
|
||||
["Low", "Solar and battery integration", "Rack voltage vs inverter window; unpublished "
|
||||
"certifications; mixed-vendor warranty", "Engineering checks and certificate requests in "
|
||||
"phase 0; large float"],
|
||||
["Low", "Cassette supply chain", "Blank tape and shells come from few suppliers", "Hold "
|
||||
"stock; qualify two sources"],
|
||||
["Low", "CD replication scope creep", "Adds USD 1.5M to 4.0M and a second commissioning "
|
||||
"programme", "Decide before BOI filing; treat as a separate project if chosen"],
|
||||
], widths_mm=[16, 40, 52, 62], font_pt=8),
|
||||
P("**Open questions with owners and dates** are in the next section. Two standing caveats on the "
|
||||
"evidence base: the Thai market data is one buyer, one plant and one afternoon, and the "
|
||||
"underlying invoices and tour notes should be archived with this plan before it goes in front "
|
||||
"of a bank; and the incumbent is a competitor, so every collaboration scenario assumes they would "
|
||||
"buy a critical input from a new entrant, which they may decline to do."),
|
||||
|
||||
# ------------------------------------------------- 14 next steps
|
||||
H1("14. Next steps and the ask"),
|
||||
H2("14.1 Phase 0: the next three to six months"),
|
||||
T(["#", "Action", "Owner", "Cost", "Retires"], [
|
||||
["1", "Making Vinyl (Haarlem) and regional label conversations; test the 3.50 to 4.60 USD "
|
||||
"blended price, run sizes, and whether 7 weeks instead of 14 plus freight is worth a premium",
|
||||
"Simona, Alexander", "Travel", "Pricing risk"],
|
||||
["2", "Engage a BOI-registered consultant; confirm the activity sub-code; put the "
|
||||
"order-before-certificate question first; add cassette and CD-R; decide CD replication",
|
||||
"Simona", "USD 5k to 15k", "Sequencing, scope"],
|
||||
["3", "Thai counsel: lease heads of terms with the Mueang Chi lessor (term, renewal, building "
|
||||
"ownership, assignment), zoning verification, company and shareholder structure including "
|
||||
"Ploy Printing", "Simona, counsel", "USD 5k to 15k", "Zoning, lease, ownership"],
|
||||
["4", "Soil boring test on the candidate parcel", "Alexander", "THB 30k to 50k", "Ground risk"],
|
||||
["5", "Newbilt re-quote with build time, order-book position, PLC data access, currency and "
|
||||
"validity; same questions to Pheenix Alpha for the AD12 and AD12 Manual", "Alexander",
|
||||
"Free", "Schedule, price"],
|
||||
["6", "Stamper Discs quote and written turnaround terms; line-itemised full-service invoice "
|
||||
"from the incumbent; Digital Matrix galvanics quote and a Thai water-treatment vendor",
|
||||
"Alexander", "Free", "Galvanics decision"],
|
||||
["7", "Send the construction price request to two or three Thai PEB contractors", "Alexander",
|
||||
"Free", "Building estimate"],
|
||||
["8", "Thai utility RFQs: boiler, chiller, compressed air, HVAC (35% vs 45% humidity floor), "
|
||||
"solar EPC; Deye and Gobel engineering checks", "Alexander", "Free", "Utilities band"],
|
||||
["9", "Cassette and CD-R equipment quotes from two vendors each; tape and shell supply",
|
||||
"Alexander", "Free", "New-scope estimates"],
|
||||
["10", "60 rai title check for the coppice plan", "Simona", "Small", "Biomass path"],
|
||||
["11", "Optional: vinyl manufacturing consultancy engagement for layout and installation "
|
||||
"review", "Alexander", "GBP 10k to 20k", "Layout risk"],
|
||||
["12", "Archive the Thai invoices and tour notes with this plan", "Simona", "None", "Evidence "
|
||||
"base"],
|
||||
], widths_mm=[8, 84, 26, 24, 28], font_pt=8),
|
||||
H2("14.2 What the investor is asked for"),
|
||||
KV([
|
||||
["Now", "Approval of Phase 0 spending of ~USD 40k to 80k, and a decision on CD scope (section "
|
||||
"4.9) before the BOI application is finalised."],
|
||||
["On the gates clearing", "Commitment in principle to phase 1 at ~USD 2.2M to 3.9M, released "
|
||||
"in the tranches of section 8.3, with the financing mix set to hold "
|
||||
"debt-to-equity at or below 3:1."],
|
||||
["Decisions", "Whether galvanics moves into phase 1 (section 11), once the phase 0 quotes are "
|
||||
"in; the shareholder structure with Ploy Printing and the founders; hedging of "
|
||||
"the press order."],
|
||||
["Later", "Revenue-gated expansion to the full vision (~USD 4.4M to 8.0M in total) as the "
|
||||
"launch plant proves the market: Duplex, galvanics, automatic presses, full "
|
||||
"metrology, mastering room, biomass, and the phase 2 building."],
|
||||
], widths_mm=[38, 132]),
|
||||
P("The next version of this plan should carry: firm re-quotes in place of the expired and "
|
||||
"estimated blocks, the negotiated lease figure, a second market data point, the investor's CD "
|
||||
"decision, a year-by-year cash model for the ramp, and the shareholder terms. Each of those is "
|
||||
"a phase 0 output."),
|
||||
|
||||
# --------------------------------------------------- 15 appendix
|
||||
H1("15. Appendix: sources and planning documents"),
|
||||
P("This plan is compiled from the project's planning repository (cnx_vinyl, GitLab), which is the "
|
||||
"source of truth and holds the detail behind every section:"),
|
||||
T(["Document", "Content"], [
|
||||
["roadmap.md", "Next steps in dependency order, from the three current gates through launch "
|
||||
"and expansion"],
|
||||
["scale.md", "Project sizing on one page: capacity, building, land, electrical, solar, steam, "
|
||||
"people"],
|
||||
["finance/total-investment.md", "Full-vision and phased-entry capital roll-up with the basis "
|
||||
"of every block"],
|
||||
["finance/revenue-model.md", "Pricing, direct cost, margin, operating scenarios, and the "
|
||||
"August 2026 Thai market data and factory tour"],
|
||||
["finance/utilities-investment.csv", "Thai supplier candidates and budgetary ranges for "
|
||||
"steam, cooling, air, water, HVAC, solar and battery"],
|
||||
["finance/phase1-construction-rfq.md", "Contractor price request with quantities from the "
|
||||
"parametric model"],
|
||||
["equipment/pressing-line.md and equipment/newbilt/", "Press fleet strategy; Newbilt "
|
||||
"budgetary offer, site requirements, "
|
||||
"process schematic, service rates"],
|
||||
["suppliers/vendor-landscape.md and equipment-suppliers.md", "Per-vendor narrative and the "
|
||||
"full needs list with contacts"],
|
||||
["process-flow.md, production-timeline.md, project-timeline.md", "The chain and its checks; "
|
||||
"order turnaround stage by "
|
||||
"stage; the calendar and "
|
||||
"critical path"],
|
||||
["traceability.md, quality-automation.md", "The traceability specification; metrology, "
|
||||
"layered record QC, the materials lab, packaging QC"],
|
||||
["sustainability.md, biomass.md", "Solar, heat recovery, water, zero discharge, materials; "
|
||||
"the biomass fuel strategy and the 60 rai coppice"],
|
||||
["facility-security.md, worker-safety.md", "Climate control, security, fire and life safety, "
|
||||
"compliance anchors; safety program"],
|
||||
["boi.md", "BOI promotion strategy, requirements, process, open items"],
|
||||
["cnx_vinyl_parametric.FCStd and renders/", "The parametric building and site model on the "
|
||||
"candidate parcel, and its renders"],
|
||||
], widths_mm=[62, 108], font_pt=8.5),
|
||||
P("Vendor PDFs and spreadsheets are primary sources and reflect the state of negotiations at "
|
||||
"their file dates. Treat prices and lead times as snapshots, not commitments."),
|
||||
]
|
||||
@@ -0,0 +1,320 @@
|
||||
"""Tiny document DSL and renderer for the CNX Vinyl business plan.
|
||||
|
||||
Blocks are plain tuples so the content files stay readable:
|
||||
|
||||
H1("text") / H2 / H3 headings
|
||||
P("text with **bold** runs") paragraph
|
||||
B([...]) bullet list
|
||||
N([...]) numbered list (manual numbers, restart per list)
|
||||
T(header, rows, widths_mm=None, caption=None, font_pt=9)
|
||||
KV([[k, v], ...], widths_mm) two-column table without header
|
||||
IMG(path, width_mm, caption) picture
|
||||
CALL(title, body) shaded call-out box; body is str or list
|
||||
PB() page break
|
||||
TOC() table-of-contents field
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.table import WD_TABLE_ALIGNMENT
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Mm, Pt, RGBColor
|
||||
|
||||
NAVY = RGBColor(0x1F, 0x38, 0x64)
|
||||
BLUE = RGBColor(0x2E, 0x55, 0x97)
|
||||
GREY = RGBColor(0x59, 0x59, 0x59)
|
||||
HEADER_FILL = "1F3864"
|
||||
CALLOUT_FILL = "DEEAF6"
|
||||
ALT_FILL = "F2F5FA"
|
||||
BODY_FONT = "Calibri"
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- DSL ---
|
||||
def H1(t): return ("h", 1, t)
|
||||
def H2(t): return ("h", 2, t)
|
||||
def H3(t): return ("h", 3, t)
|
||||
def P(t, style=None): return ("p", t, style)
|
||||
def B(items): return ("bullets", items)
|
||||
def N(items): return ("numbers", items)
|
||||
def T(header, rows, widths_mm=None, caption=None, font_pt=9):
|
||||
return ("table", header, rows, widths_mm, caption, font_pt)
|
||||
def KV(rows, widths_mm=(50, 115), font_pt=9.5): return ("kv", rows, widths_mm, font_pt)
|
||||
def IMG(path, width_mm, caption=None): return ("img", path, width_mm, caption)
|
||||
def CALL(title, body): return ("callout", title, body)
|
||||
def PB(): return ("pb",)
|
||||
def TOC(): return ("toc",)
|
||||
def COVER(**kw): return ("cover", kw)
|
||||
|
||||
|
||||
# ------------------------------------------------------------ helpers ---
|
||||
def _shade(cell, fill):
|
||||
tcPr = cell._tc.get_or_add_tcPr()
|
||||
shd = OxmlElement("w:shd")
|
||||
shd.set(qn("w:val"), "clear")
|
||||
shd.set(qn("w:color"), "auto")
|
||||
shd.set(qn("w:fill"), fill)
|
||||
tcPr.append(shd)
|
||||
|
||||
|
||||
def _field(paragraph, instr, placeholder):
|
||||
run = paragraph.add_run()
|
||||
b = OxmlElement("w:fldChar"); b.set(qn("w:fldCharType"), "begin")
|
||||
i = OxmlElement("w:instrText"); i.set(qn("xml:space"), "preserve"); i.text = instr
|
||||
s = OxmlElement("w:fldChar"); s.set(qn("w:fldCharType"), "separate")
|
||||
t = OxmlElement("w:t"); t.text = placeholder
|
||||
e = OxmlElement("w:fldChar"); e.set(qn("w:fldCharType"), "end")
|
||||
for el in (b, i, s, t, e):
|
||||
run._r.append(el)
|
||||
return run
|
||||
|
||||
|
||||
def _set_font(style, name, size=None, bold=None, color=None, italic=None):
|
||||
f = style.font
|
||||
f.name = name
|
||||
rpr = style.element.get_or_add_rPr()
|
||||
rfonts = rpr.find(qn("w:rFonts"))
|
||||
if rfonts is None:
|
||||
rfonts = OxmlElement("w:rFonts"); rpr.append(rfonts)
|
||||
for k in ("w:ascii", "w:hAnsi", "w:eastAsia", "w:cs"):
|
||||
rfonts.set(qn(k), name)
|
||||
if size is not None: f.size = Pt(size)
|
||||
if bold is not None: f.bold = bold
|
||||
if italic is not None: f.italic = italic
|
||||
if color is not None: f.color.rgb = color
|
||||
|
||||
|
||||
_INLINE = re.compile(r"(\*\*.+?\*\*)")
|
||||
|
||||
|
||||
def _runs(paragraph, text, size=None, color=None):
|
||||
for part in _INLINE.split(text):
|
||||
if not part:
|
||||
continue
|
||||
bold = part.startswith("**") and part.endswith("**")
|
||||
run = paragraph.add_run(part[2:-2] if bold else part)
|
||||
run.bold = bold or None
|
||||
if size is not None: run.font.size = Pt(size)
|
||||
if color is not None: run.font.color.rgb = color
|
||||
return paragraph
|
||||
|
||||
|
||||
def _cell_text(cell, text, size, bold=False, color=None, align=None):
|
||||
cell.text = ""
|
||||
para = cell.paragraphs[0]
|
||||
para.paragraph_format.space_after = Pt(1)
|
||||
para.paragraph_format.space_before = Pt(1)
|
||||
if align: para.alignment = align
|
||||
for j, line in enumerate(str(text).split("\n")):
|
||||
if j:
|
||||
para = cell.add_paragraph()
|
||||
para.paragraph_format.space_after = Pt(1)
|
||||
_runs(para, line, size=size, color=color)
|
||||
if bold:
|
||||
for r in para.runs: r.bold = True
|
||||
|
||||
|
||||
def _caption(doc, text):
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(2)
|
||||
p.paragraph_format.space_after = Pt(8)
|
||||
r = p.add_run(text)
|
||||
r.italic = True
|
||||
r.font.size = Pt(8.5)
|
||||
r.font.color.rgb = GREY
|
||||
return p
|
||||
|
||||
|
||||
def _keep_rows_together(table):
|
||||
for row in table.rows:
|
||||
trPr = row._tr.get_or_add_trPr()
|
||||
cs = OxmlElement("w:cantSplit"); trPr.append(cs)
|
||||
|
||||
|
||||
# ----------------------------------------------------------- renderer ---
|
||||
class Renderer:
|
||||
def __init__(self, base_dir: Path, header_text: str, footer_text: str):
|
||||
self.base = Path(base_dir)
|
||||
self.doc = Document()
|
||||
self._setup_page()
|
||||
self._setup_styles()
|
||||
self._setup_header_footer(header_text, footer_text)
|
||||
self._update_fields_on_open()
|
||||
|
||||
# -- setup
|
||||
def _setup_page(self):
|
||||
s = self.doc.sections[0]
|
||||
s.page_width, s.page_height = Mm(210), Mm(297)
|
||||
s.top_margin = s.bottom_margin = Mm(22)
|
||||
s.left_margin = s.right_margin = Mm(20)
|
||||
s.header_distance = s.footer_distance = Mm(10)
|
||||
|
||||
def _setup_styles(self):
|
||||
st = self.doc.styles
|
||||
_set_font(st["Normal"], BODY_FONT, 10.5)
|
||||
pf = st["Normal"].paragraph_format
|
||||
pf.space_after = Pt(6); pf.line_spacing = 1.08
|
||||
_set_font(st["Heading 1"], BODY_FONT, 17, True, NAVY)
|
||||
st["Heading 1"].paragraph_format.space_before = Pt(18)
|
||||
st["Heading 1"].paragraph_format.space_after = Pt(8)
|
||||
_set_font(st["Heading 2"], BODY_FONT, 13, True, BLUE)
|
||||
st["Heading 2"].paragraph_format.space_before = Pt(12)
|
||||
st["Heading 2"].paragraph_format.space_after = Pt(4)
|
||||
_set_font(st["Heading 3"], BODY_FONT, 11, True, RGBColor(0x40, 0x40, 0x40))
|
||||
st["Heading 3"].paragraph_format.space_before = Pt(8)
|
||||
st["Heading 3"].paragraph_format.space_after = Pt(3)
|
||||
for name in ("List Bullet", "List Number"):
|
||||
_set_font(st[name], BODY_FONT, 10.5)
|
||||
st[name].paragraph_format.space_after = Pt(3)
|
||||
|
||||
def _setup_header_footer(self, header_text, footer_text):
|
||||
s = self.doc.sections[0]
|
||||
hp = s.header.paragraphs[0]
|
||||
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
r = hp.add_run(header_text); r.font.size = Pt(8.5); r.font.color.rgb = GREY
|
||||
fp = s.footer.paragraphs[0]
|
||||
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
r = fp.add_run(footer_text + " | Page "); r.font.size = Pt(8.5); r.font.color.rgb = GREY
|
||||
r = _field(fp, "PAGE", "1"); r.font.size = Pt(8.5); r.font.color.rgb = GREY
|
||||
r = fp.add_run(" of "); r.font.size = Pt(8.5); r.font.color.rgb = GREY
|
||||
r = _field(fp, "NUMPAGES", "1"); r.font.size = Pt(8.5); r.font.color.rgb = GREY
|
||||
|
||||
def _update_fields_on_open(self):
|
||||
el = OxmlElement("w:updateFields"); el.set(qn("w:val"), "true")
|
||||
self.doc.settings.element.append(el)
|
||||
|
||||
# -- blocks
|
||||
def render(self, blocks):
|
||||
for blk in blocks:
|
||||
kind = blk[0]
|
||||
getattr(self, "_b_" + kind)(*blk[1:])
|
||||
return self.doc
|
||||
|
||||
def _b_cover(self, kw):
|
||||
d = self.doc
|
||||
for _ in range(3): d.add_paragraph()
|
||||
p = d.add_paragraph(); p.alignment = WD_ALIGN_PARAGRAPH.LEFT
|
||||
r = p.add_run(kw["kicker"]); r.font.size = Pt(12); r.font.color.rgb = BLUE; r.bold = True
|
||||
p = d.add_paragraph()
|
||||
r = p.add_run(kw["title"]); r.font.size = Pt(30); r.font.color.rgb = NAVY; r.bold = True
|
||||
p.paragraph_format.space_after = Pt(6)
|
||||
p = d.add_paragraph()
|
||||
r = p.add_run(kw["subtitle"]); r.font.size = Pt(13); r.font.color.rgb = GREY
|
||||
p.paragraph_format.space_after = Pt(14)
|
||||
if kw.get("image"):
|
||||
d.add_picture(str(self.base / kw["image"]), width=Mm(170))
|
||||
p = d.add_paragraph(); p.paragraph_format.space_before = Pt(10)
|
||||
for line in kw["lines"]:
|
||||
_runs(p, line, size=10.5); p.add_run().add_break()
|
||||
p = d.add_paragraph()
|
||||
r = p.add_run(kw["note"]); r.font.size = Pt(8.5); r.italic = True; r.font.color.rgb = GREY
|
||||
d.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
|
||||
|
||||
def _b_toc(self):
|
||||
d = self.doc
|
||||
p = d.add_paragraph(); r = p.add_run("Contents"); r.font.size = Pt(17); r.bold = True; r.font.color.rgb = NAVY
|
||||
p = d.add_paragraph()
|
||||
_field(p, 'TOC \\o "1-2" \\h \\z \\u',
|
||||
"Table of contents: open in Word and press F9 (or accept the update prompt) to populate.")
|
||||
d.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
|
||||
|
||||
def _b_h(self, level, text):
|
||||
self.doc.add_heading(text, level=level)
|
||||
|
||||
def _b_p(self, text, style):
|
||||
p = self.doc.add_paragraph(style=style) if style else self.doc.add_paragraph()
|
||||
_runs(p, text)
|
||||
|
||||
def _b_bullets(self, items):
|
||||
for it in items:
|
||||
_runs(self.doc.add_paragraph(style="List Bullet"), it)
|
||||
|
||||
def _b_numbers(self, items):
|
||||
for i, it in enumerate(items, 1):
|
||||
p = self.doc.add_paragraph()
|
||||
p.paragraph_format.left_indent = Mm(8)
|
||||
p.paragraph_format.first_line_indent = Mm(-6)
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
_runs(p, f"{i}. {it}")
|
||||
|
||||
def _b_table(self, header, rows, widths, caption, font_pt):
|
||||
d = self.doc
|
||||
n = len(header)
|
||||
t = d.add_table(rows=1, cols=n)
|
||||
t.style = d.styles["Table Grid"]
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
for j, h in enumerate(header):
|
||||
c = t.rows[0].cells[j]
|
||||
_shade(c, HEADER_FILL)
|
||||
_cell_text(c, h, font_pt, bold=True, color=RGBColor(0xFF, 0xFF, 0xFF))
|
||||
for i, row in enumerate(rows):
|
||||
cells = t.add_row().cells
|
||||
for j in range(n):
|
||||
val = row[j] if j < len(row) else ""
|
||||
_cell_text(cells[j], val, font_pt)
|
||||
if i % 2 == 1: _shade(cells[j], ALT_FILL)
|
||||
if widths:
|
||||
for row in t.rows:
|
||||
for j, w in enumerate(widths):
|
||||
row.cells[j].width = Mm(w)
|
||||
# repeat header row
|
||||
trPr = t.rows[0]._tr.get_or_add_trPr()
|
||||
th = OxmlElement("w:tblHeader"); th.set(qn("w:val"), "true"); trPr.append(th)
|
||||
_keep_rows_together(t)
|
||||
if caption: _caption(d, caption)
|
||||
else: d.add_paragraph().paragraph_format.space_after = Pt(2)
|
||||
|
||||
def _b_kv(self, rows, widths, font_pt):
|
||||
d = self.doc
|
||||
t = d.add_table(rows=0, cols=2)
|
||||
t.style = d.styles["Table Grid"]
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
for k, v in rows:
|
||||
cells = t.add_row().cells
|
||||
_shade(cells[0], ALT_FILL)
|
||||
_cell_text(cells[0], k, font_pt, bold=True)
|
||||
_cell_text(cells[1], v, font_pt)
|
||||
for row in t.rows:
|
||||
for j, w in enumerate(widths):
|
||||
row.cells[j].width = Mm(w)
|
||||
_keep_rows_together(t)
|
||||
d.add_paragraph().paragraph_format.space_after = Pt(2)
|
||||
|
||||
def _b_img(self, path, width_mm, caption):
|
||||
self.doc.add_picture(str(self.base / path), width=Mm(width_mm))
|
||||
self.doc.paragraphs[-1].alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
if caption: _caption(self.doc, caption)
|
||||
|
||||
def _b_callout(self, title, body):
|
||||
d = self.doc
|
||||
t = d.add_table(rows=1, cols=1)
|
||||
t.style = d.styles["Table Grid"]
|
||||
t.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
c = t.rows[0].cells[0]
|
||||
_shade(c, CALLOUT_FILL)
|
||||
c.width = Mm(170)
|
||||
c.text = ""
|
||||
p = c.paragraphs[0]
|
||||
p.paragraph_format.space_before = Pt(3); p.paragraph_format.space_after = Pt(3)
|
||||
r = p.add_run(title); r.bold = True; r.font.color.rgb = NAVY; r.font.size = Pt(10.5)
|
||||
items = body if isinstance(body, list) else [body]
|
||||
for it in items:
|
||||
q = c.add_paragraph()
|
||||
q.paragraph_format.space_after = Pt(3)
|
||||
if isinstance(body, list):
|
||||
q.paragraph_format.left_indent = Mm(4)
|
||||
_runs(q, "\u2022 " + it, size=10)
|
||||
else:
|
||||
_runs(q, it, size=10)
|
||||
_keep_rows_together(t)
|
||||
d.add_paragraph().paragraph_format.space_after = Pt(2)
|
||||
|
||||
def _b_pb(self):
|
||||
self.doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)
|
||||
|
||||
def save(self, path):
|
||||
self.doc.save(str(path))
|
||||
Reference in new issue
Block a user