raylu 3 日 前
コミット
5138ec0d4f
9 ファイル変更193 行追加3 行削除
  1. 2 0
      .gitignore
  2. 28 0
      bun.lock
  3. 14 0
      package.json
  4. 5 3
      roi.py
  5. 31 0
      ts/roi.ts
  6. 18 0
      tsconfig.json
  7. 29 0
      www/index.html
  8. BIN
      www/metrophobic-v23-latin-regular.woff2
  9. 66 0
      www/style.css

+ 2 - 0
.gitignore

@@ -1,4 +1,6 @@
 __pycache__/
 cache/
 config.toml
+node_modules/
 uv.lock
+www/roi.js*

+ 28 - 0
bun.lock

@@ -0,0 +1,28 @@
+{
+  "lockfileVersion": 1,
+  "workspaces": {
+    "": {
+      "name": "prun-calc",
+      "dependencies": {
+        "@typescript/native-preview": "^7.0.0-dev",
+      },
+    },
+  },
+  "packages": {
+    "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251211.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251211.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251211.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251211.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-RXuRj/zn2tWrria1eea1mzOVmUjNHdOZsxlcnXLy2BjXil+ncgdMFARWryeXP2+NPmGTwC+ERJ5YAuwU8n4nlg=="],
+
+    "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251211.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0TSLj8s2M1eQXnQV0+DMFCnJF4vqNobTaeKzMpR8oHOsD2az93knOUixsZk0Nyf3jYzgszDakNXhp0K3fzWWAw=="],
+
+    "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20251211.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Nv6+4H1SxjkbOLQZjbAjivTtFmPpeGiXKXh/8/UwkQW3Bom/L+owCfJNffsMKalKdDa/eYRW0uaoAk0dcU4c4A=="],
+
+    "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20251211.1", "", { "os": "linux", "cpu": "arm" }, "sha512-6Y6VqTMfgvt8f1P5FHNIT/O/L0lSw5MHOcVOnrZEfvOAUXE+cCmC5VvtFh1Paa1Zu+oTBnCu5EvIwemHl4V73A=="],
+
+    "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20251211.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-sJapYFFiJnTeK/d2LmoQaTQKzMm8zquQAaAJaual+yu4sVKbP6sZRZ1GVS4FDISJvUrlajpLWxtfQTHR4dlgeQ=="],
+
+    "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20251211.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bJfieFUeJRGsjByfeShQYXbFRbJ4k14/Sp1HQ2Eu8yOd+hUpOj7fsax7b5B2XvE4cSuC+dRV2FVOCZQNaL4u2Q=="],
+
+    "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20251211.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-0vZ1NQmLyRf1bLaBjHFem1pLsJJnPMOCfScgM616cJpkbqqGEAgXfOnxKOUxiZ6X+hfx09gXn+5wq7zjaV0aag=="],
+
+    "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251211.1", "", { "os": "win32", "cpu": "x64" }, "sha512-OUB0nNmzZeCl0KjxeG7R+Ey1gq8iaVoJJRJpwKiTj6Ws5voKOb6PxNoM2jMNqJV3R/d3PXfR7Y39/IINioa/CQ=="],
+  }
+}

+ 14 - 0
package.json

@@ -0,0 +1,14 @@
+{
+	"name": "prun-calc",
+	"version": "0",
+	"description": "GitHub Pages site with TypeScript",
+	"scripts": {
+		"build": "bun build ts/roi.ts --outdir www --target browser --sourcemap=external",
+		"typecheck": "tsgo --noEmit",
+		"serve": "python3 -m http.server -d www 8000"
+	},
+	"devDependencies": {},
+	"dependencies": {
+		"@typescript/native-preview": "^7.0.0-dev"
+	}
+}

+ 5 - 3
roi.py

@@ -1,6 +1,7 @@
 from __future__ import annotations
 
 import dataclasses
+import json
 import typing
 
 import cache
@@ -29,7 +30,7 @@ def main() -> None:
 			warnings.append('low volume')
 		if p.logistics_per_area > 1.5:
 			warnings.append('heavy logistics')
-		if p.high_opex:
+		if p.cost_per_day > 50_000:
 			warnings.append('high opex')
 		if len(warnings) > 0:
 			print('              \033[31m' + ' '.join(warnings).ljust(43), end='')
@@ -37,6 +38,9 @@ def main() -> None:
 			print(' ' * 14 + '\033[0;53m' + ' ' * 43, end='')
 		print(f'\n\033[0;30m{p.recipe:30} \033[0m{p.expertise:19} \033[33m{p.capex:7,.0f} \033[35m{p.cost_per_day:9,.0f} \033[34m{p.logistics_per_area:5.2f}\033[0m')
 
+	with open('www/roi.json', 'w') as f:
+		json.dump([dataclasses.asdict(p) for p in profits], f, indent='\t')
+
 def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], materials: typing.Mapping[str, Material],
 		prices: typing.Mapping[str, Price]) -> Profit | None:
 	try:
@@ -139,11 +143,9 @@ class Profit:
 	cost_per_day: float
 	logistics_per_area: float
 	low_volume: bool
-	high_opex: bool = dataclasses.field(init=False)
 	score: float = dataclasses.field(init=False)
 
 	def __post_init__(self) -> None:
-		self.high_opex = self.cost_per_day > 50_000
 		self.score = self.profit_per_area
 		if self.low_volume:
 			self.score *= 0.2

+ 31 - 0
ts/roi.ts

@@ -0,0 +1,31 @@
+(async function () {
+	const response = await fetch('roi.json');
+	const profits: Profit[] = await response.json();
+
+	const format = new Intl.NumberFormat(undefined,
+			{maximumFractionDigits: 2, maximumSignificantDigits: 7, roundingPriority: 'lessPrecision'}).format;
+	const tbody = document.querySelector('tbody')!;
+	for (const p of profits) {
+		const tr = document.createElement('tr');
+		tr.innerHTML = `
+			<td>${p.output}</td>
+			<td>${p.expertise}</td>
+			<td>${format(p.profit_per_area)}</td>
+			<td>${format(p.capex)}</td>
+			<td>${format(p.cost_per_day)}</td>
+			<td>${format(p.logistics_per_area)}</td>
+		`;
+		tbody.appendChild(tr);
+	}
+})();
+
+interface Profit {
+	output: string
+	recipe: string
+	expertise: string
+	profit_per_area: number
+	capex: number
+	cost_per_day: number
+	logistics_per_area: number
+	low_volume: boolean
+}

+ 18 - 0
tsconfig.json

@@ -0,0 +1,18 @@
+{
+	"compilerOptions": {
+		"target": "esnext",
+		"module": "esnext",
+		"lib": ["ESNext", "DOM"],
+		"rootDir": "./ts",
+		"strict": true,
+		"esModuleInterop": true,
+		"skipLibCheck": true,
+		"forceConsistentCasingInFileNames": true,
+		"moduleResolution": "bundler",
+		"isolatedModules": true,
+		"sourceMap": true,
+	},
+	"include": ["ts/**/*"],
+	"exclude": ["node_modules"],
+}
+

+ 29 - 0
www/index.html

@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8">
+	<title>PrUn ROI</title>
+	<link rel="stylesheet" type="text/css" href="style.css">
+	<link rel="icon" href="https://www.raylu.net/hammer-man.svg" />
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<meta name="theme-color" content="#222">
+</head>
+<body>
+    <main>
+        <table>
+            <thead>
+                <tr>
+                    <th>wrought product</th>
+                    <th>expertise</th>
+                    <th>daily profit/area</th>
+                    <th>capex</th>
+                    <th>daily opex</th>
+                    <th>logistics</th>
+                </tr>
+            </thead>
+            <tbody></tbody>
+        </table>
+    </main>
+    <script src="roi.js"></script>
+</body>
+</html>

BIN
www/metrophobic-v23-latin-regular.woff2


+ 66 - 0
www/style.css

@@ -0,0 +1,66 @@
+@font-face {
+	font-family: 'Metrophobic';
+	font-style: normal;
+	font-weight: 400;
+	src: url('metrophobic-v23-latin-regular.woff2') format('woff2');
+}
+
+* {
+	box-sizing: border-box;
+}
+
+body {
+	background-color: #000;
+	color: #aaa;
+	font: 18px Metrophobic, sans-serif;
+}
+
+a:link, a:visited, a:active {
+	color: #58a;
+	text-decoration: none;
+}
+a:hover {
+	color: #5ad;
+}
+
+main {
+	width: 900px;
+	margin: 10px auto;
+	padding: 10px 50px 25px;
+	background-color: #111;
+	box-shadow: 0 0 5px #222;
+
+    table {
+        td, th {
+            padding: 0.25em 0.5em;
+        }
+        tbody {
+            td:nth-child(1),
+            td:nth-child(2) {
+                font-family: inherit;
+                text-align: inherit;
+            }
+            td {
+                font-family: monospace;
+                text-align: right;
+            }
+        }
+    }
+}
+
+@media (prefers-color-scheme: light) {
+	body {
+		background-color: #fff;
+		color: #555;
+	}
+	a:link, a:visited, a:active {
+		color: #358;
+	}
+	a:hover {
+		color: #38b;
+	}
+	main {
+		background-color: #eee;
+		box-shadow: 0 0 5px #ddd;
+	}
+}