+
+
+
+
+
+
+
Input (JSON)
+
+
+
+
+
Diagnostics
+
No issues
+
+
+
+
+
+
+
diff --git a/playground/package-lock.json b/playground/package-lock.json
new file mode 100644
index 0000000..e0297fd
--- /dev/null
+++ b/playground/package-lock.json
@@ -0,0 +1,1128 @@
+{
+ "name": "axiom-playground",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "axiom-playground",
+ "version": "0.1.0",
+ "dependencies": {
+ "monaco-editor": "^0.52.2"
+ },
+ "devDependencies": {
+ "typescript": "^5.7.0",
+ "vite": "^6.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
+ "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
+ "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
+ "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
+ "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
+ "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
+ "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
+ "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
+ "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
+ "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
+ "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
+ "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
+ "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
+ "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
+ "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
+ "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
+ "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
+ "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
+ "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
+ "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
+ "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
+ "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
+ "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
+ "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
+ "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.25.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.25.12",
+ "@esbuild/android-arm": "0.25.12",
+ "@esbuild/android-arm64": "0.25.12",
+ "@esbuild/android-x64": "0.25.12",
+ "@esbuild/darwin-arm64": "0.25.12",
+ "@esbuild/darwin-x64": "0.25.12",
+ "@esbuild/freebsd-arm64": "0.25.12",
+ "@esbuild/freebsd-x64": "0.25.12",
+ "@esbuild/linux-arm": "0.25.12",
+ "@esbuild/linux-arm64": "0.25.12",
+ "@esbuild/linux-ia32": "0.25.12",
+ "@esbuild/linux-loong64": "0.25.12",
+ "@esbuild/linux-mips64el": "0.25.12",
+ "@esbuild/linux-ppc64": "0.25.12",
+ "@esbuild/linux-riscv64": "0.25.12",
+ "@esbuild/linux-s390x": "0.25.12",
+ "@esbuild/linux-x64": "0.25.12",
+ "@esbuild/netbsd-arm64": "0.25.12",
+ "@esbuild/netbsd-x64": "0.25.12",
+ "@esbuild/openbsd-arm64": "0.25.12",
+ "@esbuild/openbsd-x64": "0.25.12",
+ "@esbuild/openharmony-arm64": "0.25.12",
+ "@esbuild/sunos-x64": "0.25.12",
+ "@esbuild/win32-arm64": "0.25.12",
+ "@esbuild/win32-ia32": "0.25.12",
+ "@esbuild/win32-x64": "0.25.12"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/monaco-editor": {
+ "version": "0.52.2",
+ "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz",
+ "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
+ "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.1",
+ "@rollup/rollup-android-arm64": "4.60.1",
+ "@rollup/rollup-darwin-arm64": "4.60.1",
+ "@rollup/rollup-darwin-x64": "4.60.1",
+ "@rollup/rollup-freebsd-arm64": "4.60.1",
+ "@rollup/rollup-freebsd-x64": "4.60.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.1",
+ "@rollup/rollup-linux-arm64-musl": "4.60.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.1",
+ "@rollup/rollup-linux-loong64-musl": "4.60.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-gnu": "4.60.1",
+ "@rollup/rollup-linux-x64-musl": "4.60.1",
+ "@rollup/rollup-openbsd-x64": "4.60.1",
+ "@rollup/rollup-openharmony-arm64": "4.60.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.1",
+ "@rollup/rollup-win32-x64-gnu": "4.60.1",
+ "@rollup/rollup-win32-x64-msvc": "4.60.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "6.4.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz",
+ "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "fdir": "^6.4.4",
+ "picomatch": "^4.0.2",
+ "postcss": "^8.5.3",
+ "rollup": "^4.34.9",
+ "tinyglobby": "^0.2.13"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+ "jiti": ">=1.21.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/playground/package.json b/playground/package.json
new file mode 100644
index 0000000..0232601
--- /dev/null
+++ b/playground/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "axiom-playground",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "monaco-editor": "^0.52.2"
+ },
+ "devDependencies": {
+ "typescript": "^5.7.0",
+ "vite": "^6.0.0"
+ }
+}
diff --git a/playground/src/editor/language.ts b/playground/src/editor/language.ts
new file mode 100644
index 0000000..cc0d900
--- /dev/null
+++ b/playground/src/editor/language.ts
@@ -0,0 +1,115 @@
+import * as monaco from 'monaco-editor';
+
+export function registerAxiomLanguage() {
+ monaco.languages.register({ id: 'axiom' });
+
+ monaco.languages.setLanguageConfiguration('axiom', {
+ comments: { lineComment: '//' },
+ brackets: [
+ ['{', '}'],
+ ['[', ']'],
+ ['(', ')'],
+ ],
+ autoClosingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"', notIn: ['string'] },
+ ],
+ surroundingPairs: [
+ { open: '{', close: '}' },
+ { open: '[', close: ']' },
+ { open: '(', close: ')' },
+ { open: '"', close: '"' },
+ ],
+ indentationRules: {
+ increaseIndentPattern: /[{([].*$/,
+ decreaseIndentPattern: /^\s*[})\]]/,
+ },
+ });
+
+ monaco.languages.setMonarchTokensProvider('axiom', {
+ keywords: [
+ 'type', 'namespace', 'source', 'table', 'if', 'then', 'else', 'match',
+ 'not', 'in', 'as', 'any', 'all', 'collect', 'where',
+ ],
+
+ typeKeywords: [
+ 'number', 'string', 'bool', 'list', 'dict', 'money',
+ ],
+
+ constants: ['true', 'false'],
+
+ operators: [
+ '=', '==', '!=', '<', '>', '<=', '>=',
+ '&&', '||', '!', '+', '-', '*', '/', '%', '**',
+ '=>', '|', '..', '...',
+ ],
+
+ symbols: /[=>/, 'delimiter.arrow'],
+ [/[=> match limit { 500000 => £1125, 1000000 => £1500, 2000000 => £1950, 3000000 => £2175, 4000000 => £2400, 5000000 => £2475, _ => £0 },
+ "DRI-129" => match limit { 500000 => £750, 1000000 => £1000, 2000000 => £1300, 3000000 => £1450, 4000000 => £1600, 5000000 => £1650, _ => £0 },
+ "DRI-138" => match limit { 500000 => £225, 1000000 => £300, 2000000 => £390, 3000000 => £435, 4000000 => £480, 5000000 => £495, _ => £0 },
+ "DRI-139" => match limit { 500000 => £263, 1000000 => £350, 2000000 => £455, 3000000 => £508, 4000000 => £560, 5000000 => £578, _ => £0 },
+ "DRI-140" => match limit { 500000 => £225, 1000000 => £300, 2000000 => £390, 3000000 => £435, 4000000 => £480, 5000000 => £495, _ => £0 },
+ "DRI-170" => match limit { 500000 => £75, 1000000 => £100, 2000000 => £130, 3000000 => £145, 4000000 => £160, 5000000 => £165, _ => £0 },
+ "DRI-198" => match limit { 500000 => £938, 1000000 => £1250, 2000000 => £1625, 3000000 => £1813, 4000000 => £2000, 5000000 => £2063, _ => £0 },
+ "DRI-236" => match limit { 500000 => £45, 1000000 => £60, 2000000 => £78, 3000000 => £87, 4000000 => £96, 5000000 => £99, _ => £0 },
+ "DRI-247" => match limit { 500000 => £563, 1000000 => £750, 2000000 => £975, 3000000 => £1088, 4000000 => £1200, 5000000 => £1238, _ => £0 },
+ "DRI-253" => match limit { 500000 => £900, 1000000 => £1200, 2000000 => £1560, 3000000 => £1740, 4000000 => £1920, 5000000 => £1980, _ => £0 },
+ "DRI-263" => match limit { 500000 => £750, 1000000 => £1000, 2000000 => £1300, 3000000 => £1450, 4000000 => £1600, 5000000 => £1650, _ => £0 },
+ "DRI-295" => match limit { 500000 => £900, 1000000 => £1200, 2000000 => £1560, 3000000 => £1740, 4000000 => £1920, 5000000 => £1980, _ => £0 },
+ "DRI-318" => match limit { 500000 => £563, 1000000 => £750, 2000000 => £975, 3000000 => £1088, 4000000 => £1200, 5000000 => £1238, _ => £0 },
+ "DRI-319" => match limit { 500000 => £49, 1000000 => £65, 2000000 => £85, 3000000 => £94, 4000000 => £104, 5000000 => £107, _ => £0 },
+ _ => £0,
+ }
+}
+
+// Inline excess lookup table (from excess.csv)
+ExcessLookup(industry: string): money(GBP) {
+ match industry {
+ "DRI-106" => £2000,
+ "DRI-129" => £1000,
+ "DRI-138" => £500,
+ "DRI-139" => £250,
+ "DRI-140" => £500,
+ "DRI-170" => £150,
+ "DRI-198" => £1000,
+ "DRI-236" => £500,
+ "DRI-247" => £1000,
+ "DRI-253" => £2000,
+ "DRI-263" => £1000,
+ "DRI-295" => £2000,
+ "DRI-318" => £1000,
+ "DRI-319" => £250,
+ _ => £0,
+ }
+}
+
+// Industry-specific endorsements
+IndustryEndorsements(industry: string): list(Endorsement) {
+ match industry {
+ "DRI-129" => [
+ applied { code: "END-01", title: "Chiropodist / Podiatrist Exclusion" },
+ ],
+ "DRI-138" | "DRI-139" | "DRI-140" => [
+ applied { code: "END-03", title: "Direct Access Extension" },
+ applied { code: "END-12", title: "Teeth Whitening Condition" },
+ ],
+ "DRI-247" => [
+ applied { code: "END-08", title: "Opticians / Optical Exclusion" },
+ ],
+ "DRI-263" => [
+ applied { code: "END-10", title: "Professional Sports Exclusion" },
+ applied { code: "END-11", title: "Spinal Joint Manipulation Exclusion" },
+ ],
+ "DRI-318" => [
+ applied { code: "END-02", title: "Diagnostic and Interpretation Exclusion" },
+ ],
+ _ => [],
+ }
+}
+
+// Cover calculation
+HealthcareProfessional(
+ industry: string,
+ limit: number,
+ commission_rate: number,
+): CoverOutcome {
+ if PremiumLookup(industry: industry, limit: limit) == £0
+ then referred { reason: "Industry not rated" }
+ else rated {
+ gross_premium: PremiumLookup(industry: industry, limit: limit),
+ net_premium: round(PremiumLookup(industry: industry, limit: limit) * (1 - commission_rate), 2),
+ commission: round(PremiumLookup(industry: industry, limit: limit) * commission_rate, 2),
+ excess: ExcessLookup(industry: industry),
+ limit: limit,
+ inquest_costs: £25000,
+ endorsements: IndustryEndorsements(industry: industry),
+ }
+}
+
+// Product assembly — wraps cover outcome with product-level metadata
+// Propagates cover referrals as product declines
+BuildProduct(cover: CoverOutcome): ProductOutcome {
+ match cover {
+ referred { reason } => declined { reasons: [reason] },
+ _ => offered {
+ cover: cover,
+ umr: "B1792SPR2500004A",
+ currency: "GBP",
+ jurisdiction: "Great Britain, Northern Ireland, Isle of Man and Channel Islands",
+ },
+ }
+}
+
+// Product entry point — validates exposure constraints, then rates
+Product(
+ industry: string,
+ limit: number,
+ number_of_employees: number,
+): ProductOutcome {
+ if number_of_employees > 1
+ then declined { reasons: ["Maximum 1 employee allowed"] }
+ else BuildProduct(cover: HealthcareProfessional(
+ industry: industry,
+ limit: limit,
+ commission_rate: 0.33,
+ ))
+}
+`;
+
+// Scenario: Dental Hygienist (DRI-138) at £1,000,000 limit, sole trader
+export const HEALTHCARE_INPUT = {
+ industry: "DRI-138",
+ limit: 1000000,
+ number_of_employees: 1,
+};
diff --git a/playground/src/examples/hospitality.axiom.ts b/playground/src/examples/hospitality.axiom.ts
new file mode 100644
index 0000000..ada1343
--- /dev/null
+++ b/playground/src/examples/hospitality.axiom.ts
@@ -0,0 +1,816 @@
+import industryConfigCSV from './industry_config.csv?raw';
+import { parseCSV } from '../utils/csv';
+
+export const HOSPITALITY_EXAMPLE = `// AIG Hospitality V2
+// Translated from Abacus ProductSchemeBuilder + 6 CoverSchemeBuilders
+// Subset: 4 industries, simplified BC (no alarm/construction rules)
+
+// --- Input record types ---
+
+type Exposure = {
+ industry: string,
+ number_of_employees: number,
+ turnover: number,
+ is_sole_trader: bool,
+ years_experience: number,
+}
+
+type ClaimsHistory = {
+ number_of_claims: number,
+ total_claims_value: number,
+}
+
+type RiskScores = {
+ flood_risk: number,
+ theft_risk: number,
+ terrorism_risk: number,
+}
+
+type BCConfig = {
+ buildings_limit: number,
+ contents_limit: number,
+ stock_limit: number,
+ listed_type: string,
+ has_outdoor_play: bool,
+ has_functions: bool,
+ number_of_beds: string,
+ has_heavy_deep_frying: bool,
+}
+
+type BIConfig = {
+ basis_of_cover: string,
+ basis_of_cover_limit: number,
+ indemnity_months: number,
+ rent_receivable_limit: number,
+ loss_of_licence_limit: number,
+}
+
+// --- Outcome types ---
+
+type CoverOutcome =
+ rated {
+ key: string,
+ name: string,
+ base_premium: number,
+ limit: number,
+ excess: number,
+ }
+ | not_available { reason: string }
+
+type ProductOutcome =
+ offered {
+ covers: list(CoverOutcome),
+ subtotal: number,
+ minimum_premium: number,
+ total_gross_premium: number,
+ total_net_premium: number,
+ commission_rate: number,
+ currency: string,
+ }
+ | declined { reasons: list(string) }
+ | referred { reasons: list(string) }
+
+type MultiIndustrySummary = {
+ pl_classes: list(string),
+ el_classes: list(string),
+ deep_frying_rates: list(number),
+ minimum_premiums: list(number),
+}
+
+// --- Industry configuration (from industry_config.csv, 28 columns x 8 industries) ---
+// Table declaration — typed companion data, loaded from CSV artifact at deploy time
+
+table industry_config: list({
+ code: string,
+ buildings_fire_class: string,
+ contents_fire_class: string,
+ contents_theft_class: string,
+ stock_theft_class: string,
+ bi_fire_class: string,
+ pl_class: string,
+ pl_severity: number,
+ el_class: string,
+ el_severity: number,
+ flood_banding: string,
+ loss_of_licence_class: string,
+ deep_frying_rate: number,
+ min_premium_claims_free: number,
+ min_premium_default: number,
+})
+
+// Each lookup is a pure expression that searches the table
+namespace Industry {
+ BuildingsFireClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.buildings_fire_class, _ => "" }
+ }
+ ContentsFireClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.contents_fire_class, _ => "" }
+ }
+ ContentsTheftClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.contents_theft_class, _ => "" }
+ }
+ StockTheftClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.stock_theft_class, _ => "" }
+ }
+ BIFireClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.bi_fire_class, _ => "" }
+ }
+ PLClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.pl_class, _ => "" }
+ }
+ ELClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.el_class, _ => "" }
+ }
+ FloodBanding(industry: string): string {
+ match row in industry_config { row.code == industry => row.flood_banding, _ => "" }
+ }
+ LossOfLicenceClass(industry: string): string {
+ match row in industry_config { row.code == industry => row.loss_of_licence_class, _ => "" }
+ }
+ DeepFryingRate(industry: string): number {
+ match row in industry_config { row.code == industry => row.deep_frying_rate, _ => 0 }
+ }
+ MinPremiumClaimsFree(industry: string): number {
+ match row in industry_config { row.code == industry => row.min_premium_claims_free, _ => 0 }
+ }
+ MinPremiumDefault(industry: string): number {
+ match row in industry_config { row.code == industry => row.min_premium_default, _ => 0 }
+ }
+
+ // --- Multi-industry lookups ---
+ // Core v1 examples use collection queries directly rather than max/min aggregates.
+}
+
+// --- Claims loading system (product-level, shared across all covers) ---
+
+namespace Claims {
+ // Years trading coefficient and loading (from years_trading_coefficient_and_loads.csv)
+ // Years of experience are capped at 5.
+ YearsTradingLoading(is_sole_trader: bool, years_experience: number): number {
+ match (is_sole_trader, capped_years) {
+ (false, [0..1]) => 1,
+ (false, 2) => 0.5,
+ (false, 3) => 0.25,
+ (false, 4) => 0,
+ (false, 5) => -0.25,
+ (true, [0..2]) => 1,
+ (true, 3) => 0.25,
+ (true, 4) => 0,
+ (true, 5) => -0.25,
+ _ => 0,
+ }
+ where capped_years = if years_experience > 5 then 5 else years_experience
+ }
+
+ // Coefficient letter determines which column to use in claims cross-lookup
+ Coefficient(is_sole_trader: bool, years_experience: number): string {
+ match (is_sole_trader, capped_years) {
+ (false, [0..1]) | (true, [0..1]) => "A",
+ (false, 2) | (true, 2) => "B",
+ (false, 3) | (true, 3) => "C",
+ (false, 4) | (true, 4) => "D",
+ (false, 5) | (true, 5) => "E",
+ _ => "A",
+ }
+ where capped_years = if years_experience > 5 then 5 else years_experience
+ }
+
+ // Claims x years-trading cross-lookup (from claims_years_trading_loadings.csv)
+ // Row = number_of_claims, column = coefficient letter
+ ClaimsYearsTradingLoading(number_of_claims: number, coefficient: string): number {
+ match (number_of_claims, coefficient) {
+ (0, "A") | (0, "B") => 0,
+ (0, "C") => -0.1,
+ (0, "D") | (0, "E") => -0.2,
+ (1, "A") => 1,
+ (1, "B") => 0.1,
+ (1, "C") => 0.1,
+ (1, "D") | (1, "E") => 0.05,
+ (2, "A") => 5,
+ (2, "B") => 0.675,
+ (2, "C") => 0.2,
+ (2, "D") | (2, "E") => 0.125,
+ (3, "A") => 6,
+ (3, "B") => 1.75,
+ (3, "C") => 0.675,
+ (3, "D") | (3, "E") => 0.3,
+ (4, "A") | (4, "B") => 10,
+ (4, "C") => 1.75,
+ (4, "D") => 0.68,
+ (4, "E") => 0.675,
+ (5, "A") | (5, "B") => 25,
+ (5, "C") => 10,
+ (5, "D") | (5, "E") => 1.75,
+ _ => 100,
+ }
+ }
+
+ // Claims value loading (from claims_value_loadings.csv, range-based)
+ ClaimsValueLoading(total_claims_value: number): number {
+ match total_claims_value {
+ [0..400) => 0,
+ [400..600) => 0.1,
+ [600..700) => 0.2,
+ [700..800) => 0.3,
+ [800..900) => 0.4,
+ [900..1000) => 0.5,
+ [1000..2000) => 0.75,
+ [2000..3000) => 1.25,
+ [3000..4000) => 2,
+ [4000..5000) => 3.25,
+ [5000..6000) => 5.25,
+ [6000..8000) => 8.5,
+ [8000..9000) => 9.5,
+ [9000..10000) => 10,
+ [10000..) => 20,
+ _ => 0,
+ }
+ }
+
+ // Total claims loading: sum of all three components
+ TotalLoading(exposure: Exposure, claims: ClaimsHistory): number {
+ YearsTradingLoading(exposure.is_sole_trader, exposure.years_experience)
+ + ClaimsYearsTradingLoading(claims.number_of_claims, coefficient)
+ + ClaimsValueLoading(claims.total_claims_value)
+ where coefficient = Coefficient(exposure.is_sole_trader, exposure.years_experience)
+ }
+}
+
+// --- Minimum premium logic ---
+
+MinimumPremium(exposure: Exposure, claims: ClaimsHistory, bc: BCConfig): number {
+ if exposure.years_experience >= 2 && claims.number_of_claims == 0 && not bc.has_heavy_deep_frying
+ then Industry.MinPremiumClaimsFree(exposure.industry)
+ else Industry.MinPremiumDefault(exposure.industry)
+}
+
+// --- Public Liability (PL) — mandatory ---
+
+namespace PublicLiability {
+ // Base rate by PL classification (from base_rates.csv)
+ BaseRate(classification: string): number {
+ match classification {
+ "A" | "B" => 0.01,
+ "C" => 0.0175,
+ _ => 0.01,
+ }
+ }
+
+ // Turnover size discount (from discounts.csv)
+ TurnoverDiscount(turnover: number): number {
+ match turnover {
+ [0..100000) => 0,
+ [100000..150000) => -0.05,
+ [150000..200000) => -0.075,
+ [200000..250000) => -0.1,
+ _ => 0,
+ }
+ }
+
+ // Limit loading (from loadings.csv)
+ LimitLoading(limit: number): number {
+ match limit {
+ 1000000 => -0.1,
+ 2000000 => 0,
+ 5000000 => 0.25,
+ _ => 0,
+ }
+ }
+
+ Rate(exposure: Exposure, limit: number, total_claims_loading: number): CoverOutcome {
+ rated {
+ key: "PL",
+ name: "Public and products liability",
+ base_premium: (exposure.turnover / 100) * (base_rate * (1 + total_loads)),
+ limit: limit,
+ excess: 250,
+ }
+ where classification = Industry.PLClass(exposure.industry),
+ base_rate = BaseRate(classification),
+ total_loads = LimitLoading(limit) + total_claims_loading + TurnoverDiscount(exposure.turnover)
+ }
+}
+
+// --- Buildings, Contents and Stock (BC) — mandatory ---
+
+namespace BuildingsContentsStock {
+ // Buildings rate by fire class (from buildingsRates.csv, 25 classes A-Y)
+ BuildingsRate(classification: string): number {
+ match classification {
+ "B" => 0.15,
+ "Y" => 0.21,
+ _ => 0.1,
+ }
+ }
+
+ // Material damage (contents fire) rate (from materialDamageRates.csv)
+ MaterialDamageRate(classification: string): number {
+ match classification {
+ "B" => 0.0658,
+ "Y" => 0.15,
+ _ => 0.07,
+ }
+ }
+
+ // Contents theft rate (from contentsAndStockTheftRates.csv)
+ ContentsTheftRate(classification: string): number {
+ match classification {
+ "B" => 0.05625,
+ "F" => 0.06066,
+ _ => 0.06,
+ }
+ }
+
+ // Stock theft rate (from contentsAndStockTheftRates.csv)
+ StockTheftRate(classification: string): number {
+ match classification {
+ "C" => 0.065,
+ "V" => 0.5,
+ _ => 0.08,
+ }
+ }
+
+ // Theft area rate adjustment by risk score (from contentsAndStockTheftAreaRates.csv)
+ TheftAreaRate(theft_risk: number): number {
+ match theft_risk {
+ 1 => 0.25, 2 => 0.2, 3 => 0.15, 4 => 0.1,
+ 5 | 6 => 0,
+ 7 => -0.1, 8 => -0.15, 9 => -0.2, 10 => -0.25,
+ _ => 0,
+ }
+ }
+
+ // Buildings size discount (from buildingsSizeDiscounts.csv)
+ BuildingsSizeDiscount(sum_insured: number): number {
+ match sum_insured {
+ 0 => 0,
+ [1..125000) => -0.025,
+ [125000..250000) => -0.05,
+ [250000..375000) => -0.0625,
+ [375000..500000) => -0.075,
+ [500000..625000) => -0.0875,
+ [625000..750000) => -0.1,
+ [750000..875000) => -0.125,
+ [875000..) => -0.15,
+ _ => 0,
+ }
+ }
+
+ // Contents and stock size discount (from contentsAndStockSizeDiscounts.csv)
+ ContentsSizeDiscount(sum_insured: number): number {
+ match sum_insured {
+ 0 => 0,
+ [1..125000) => -0.025,
+ [125000..250000) => -0.05,
+ [250000..375000) => -0.0625,
+ [375000..500000) => -0.075,
+ [500000..625000) => -0.0875,
+ [625000..750000) => -0.1,
+ [750000..875000) => -0.125,
+ [875000..) => -0.15,
+ _ => 0,
+ }
+ }
+
+ // Flood rate adjustment (from floodTable.csv, by risk score and banding)
+ FloodAdjustment(flood_risk: number, flood_banding: string): number {
+ match flood_risk {
+ 7 | 8 => 0.1,
+ _ => 0,
+ }
+ }
+
+ // Listed building loading (from listedBuildingTable.csv)
+ ListedBuildingLoading(listed_type: string): number {
+ match listed_type {
+ "notListed" => 0,
+ "grade2Listed" | "gradeB2Listed" | "gradeCsListed" => 0.4,
+ _ => 0,
+ }
+ }
+
+ // Facility type loading (from facilitiesTable.csv, summed for selected types)
+ FacilityLoading(has_outdoor_play: bool, has_functions: bool): number {
+ (if has_outdoor_play then 1 else 0)
+ + (if has_functions then 0.25 else 0)
+ }
+
+ // Number of beds discount (from numberOfBedsTable.csv)
+ BedsDiscount(number_of_beds: string): number {
+ match number_of_beds {
+ "upTo5" => -0.05,
+ "upTo10" => 0,
+ "upTo20" => 1,
+ _ => 0,
+ }
+ }
+
+ // Premises factor: combined loading from building characteristics
+ PremisesFactor(
+ listed_type: string,
+ flood_risk: number,
+ flood_banding: string,
+ has_outdoor_play: bool,
+ has_functions: bool,
+ number_of_beds: string,
+ ): number {
+ ListedBuildingLoading(listed_type)
+ + FloodAdjustment(flood_risk, flood_banding)
+ + FacilityLoading(has_outdoor_play, has_functions)
+ + BedsDiscount(number_of_beds)
+ }
+
+ // Buildings section premium
+ BuildingsPremium(
+ sum_insured: number,
+ classification: string,
+ premises_factor: number,
+ total_claims_loading: number,
+ ): number {
+ (sum_insured / 100) * (rate * (1 + total_loads))
+ where rate = BuildingsRate(classification),
+ total_loads = premises_factor
+ + BuildingsSizeDiscount(sum_insured)
+ + total_claims_loading
+ }
+
+ // Contents section premium
+ ContentsPremium(
+ sum_insured: number,
+ fire_class: string,
+ theft_class: string,
+ theft_risk: number,
+ premises_factor: number,
+ total_claims_loading: number,
+ ): number {
+ (sum_insured / 100) * (rate * (1 + total_loads))
+ where rate = MaterialDamageRate(classification: fire_class) + ContentsTheftRate(classification: theft_class),
+ total_loads = premises_factor
+ + ContentsSizeDiscount(sum_insured)
+ + TheftAreaRate(theft_risk)
+ + total_claims_loading
+ }
+
+ // Stock section premium
+ StockPremium(
+ sum_insured: number,
+ fire_class: string,
+ stock_theft_class: string,
+ theft_risk: number,
+ premises_factor: number,
+ total_claims_loading: number,
+ ): number {
+ (sum_insured / 100) * (rate * (1 + total_loads))
+ where rate = MaterialDamageRate(classification: fire_class) + StockTheftRate(classification: stock_theft_class),
+ total_loads = premises_factor
+ + ContentsSizeDiscount(sum_insured)
+ + TheftAreaRate(theft_risk)
+ + total_claims_loading
+ }
+
+ Rate(exposure: Exposure, bc: BCConfig, risks: RiskScores, total_claims_loading: number): CoverOutcome {
+ rated {
+ key: "BC",
+ name: "Buildings, contents and stock",
+ base_premium: buildings_prem + contents_prem + stock_prem,
+ limit: bc.buildings_limit + bc.contents_limit + bc.stock_limit,
+ excess: 400,
+ }
+ where flood_banding = Industry.FloodBanding(exposure.industry),
+ fire_class = Industry.BuildingsFireClass(exposure.industry),
+ contents_fire = Industry.ContentsFireClass(exposure.industry),
+ theft_class = Industry.ContentsTheftClass(exposure.industry),
+ stock_theft_class = Industry.StockTheftClass(exposure.industry),
+ pf = BuildingsContentsStock.PremisesFactor(
+ listed_type: bc.listed_type,
+ flood_risk: risks.flood_risk,
+ flood_banding,
+ has_outdoor_play: bc.has_outdoor_play,
+ has_functions: bc.has_functions,
+ number_of_beds: bc.number_of_beds,
+ ),
+ buildings_prem = BuildingsPremium(
+ sum_insured: bc.buildings_limit,
+ classification: fire_class,
+ premises_factor: pf,
+ total_claims_loading,
+ ),
+ contents_prem = ContentsPremium(
+ sum_insured: bc.contents_limit,
+ fire_class: contents_fire,
+ theft_class, theft_risk: risks.theft_risk,
+ premises_factor: pf,
+ total_claims_loading,
+ ),
+ stock_prem = StockPremium(
+ sum_insured: bc.stock_limit,
+ fire_class: contents_fire,
+ stock_theft_class, theft_risk: risks.theft_risk,
+ premises_factor: pf,
+ total_claims_loading,
+ )
+ }
+}
+
+// --- Business Interruption (BI) — optional ---
+
+namespace BusinessInterruption {
+ // BI fire rate (from bi_fire_rates.csv)
+ BIFireRate(classification: string): number {
+ match classification {
+ "C" => 0.15,
+ "I" => 0.154,
+ "Y" => 0.5,
+ _ => 0.08,
+ }
+ }
+
+ // Basis of cover discount (from basis_of_cover_rates.csv)
+ BasisOfCoverDiscount(basis_of_cover: string): number {
+ match basis_of_cover {
+ "Gross Profit" => 0,
+ "Gross Revenue" => -0.5,
+ "Increased Cost of Working" => 0.5,
+ _ => 0,
+ }
+ }
+
+ // Indemnity period discount and months (from indemnity_period_rates.csv)
+ IndemnityDiscount(indemnity_months: number): number {
+ match indemnity_months {
+ 12 => 0,
+ 18 => -0.1,
+ 24 => -0.2,
+ 36 => -0.3,
+ _ => 0,
+ }
+ }
+
+ // Sum insured discount (from sum_insured_discounts.csv)
+ SumInsuredDiscount(sum_insured: number): number {
+ match sum_insured {
+ [1..125000) => -0.025,
+ [125000..250000) => -0.05,
+ [250000..375000) => -0.0625,
+ [375000..500000) => -0.075,
+ [500000..625000) => -0.0875,
+ [625000..750000) => -0.1,
+ [750000..875000) => -0.125,
+ [875000..1000000) => -0.15,
+ [1000000..1125000) => -0.1675,
+ [1125000..1250000) => -0.175,
+ [1250000..1375000) => -0.1875,
+ [1375000..1500001) => -0.2,
+ _ => 0,
+ }
+ }
+
+ // Loss of licence rate (from loss_of_license_rates.csv)
+ LossOfLicenceDiscount(lol_class: string): number {
+ match lol_class {
+ "Low" => 0.1,
+ "Medium" => 0.125,
+ "High" => 0.15,
+ _ => 0.15,
+ }
+ }
+
+ Rate(exposure: Exposure, bi: BIConfig, total_claims_loading: number): CoverOutcome {
+ rated {
+ key: "BI",
+ name: "Business interruption",
+ base_premium: bi_premium + lol_premium,
+ limit: sum_insured,
+ excess: 0,
+ }
+ where basis_si = bi.basis_of_cover_limit * (bi.indemnity_months / 12),
+ sum_insured = basis_si + bi.rent_receivable_limit,
+ bi_rate = BIFireRate(classification: Industry.BIFireClass(exposure.industry)),
+ bi_loads = SumInsuredDiscount(sum_insured)
+ + IndemnityDiscount(indemnity_months: bi.indemnity_months)
+ + BasisOfCoverDiscount(basis_of_cover: bi.basis_of_cover)
+ + total_claims_loading,
+ bi_premium = (sum_insured / 100) * (bi_rate * (1 + bi_loads)),
+ lol_class = Industry.LossOfLicenceClass(exposure.industry),
+ lol_rate = 0.0125,
+ lol_loads = LossOfLicenceDiscount(lol_class) + total_claims_loading,
+ lol_premium = if bi.loss_of_licence_limit > 0
+ then (bi.loss_of_licence_limit / 100) * (lol_rate * (1 + lol_loads))
+ else 0
+ }
+}
+
+// --- Employers Liability (EL) ---
+
+namespace EmployersLiability {
+ // Base rate by EL classification (from base_rates.csv)
+ BaseRate(classification: string): number {
+ match classification {
+ "A" => 0.005524,
+ "B" => 0.008825,
+ "C" => 0.017,
+ _ => 0.006825,
+ }
+ }
+
+ // Turnover size discount (from discounts.csv)
+ TurnoverDiscount(turnover: number): number {
+ match turnover {
+ [0..100000) => 0,
+ [100000..150000) => -0.025,
+ [150000..200000) => -0.05,
+ [200000..250000) => -0.1,
+ [250000..500000) => -0.125,
+ [500000..750000) => -0.15,
+ [750000..) => -0.2,
+ _ => 0,
+ }
+ }
+
+ Rate(exposure: Exposure, total_claims_loading: number): CoverOutcome {
+ rated {
+ key: "EL",
+ name: "Employers liability",
+ base_premium: (exposure.turnover / 100) * (base_rate * (1 + total_loads)),
+ limit: 10000000,
+ excess: 0,
+ }
+ where classification = Industry.ELClass(exposure.industry),
+ base_rate = BaseRate(classification),
+ total_loads = TurnoverDiscount(exposure.turnover) + total_claims_loading
+ }
+}
+
+// --- Portable Business Equipment (PBE) ---
+
+namespace PortableEquipment {
+ Rate(limit: number, total_claims_loading: number): CoverOutcome {
+ if limit == 0
+ then not_available { reason: "PBE not selected" }
+ else rated {
+ key: "EPE",
+ name: "Portable business equipment",
+ base_premium: (limit / 100) * (2.5 * (1 + total_claims_loading)),
+ limit: limit,
+ excess: 400,
+ }
+ }
+}
+
+// --- Terrorism (TER) — optional, excluded from minimum premium floor ---
+
+namespace Terrorism {
+ // Postcode zone from terrorism risk score (from postcode_zone.csv + postcode_rates.csv)
+ PostcodeRate(terrorism_risk: number): number {
+ match terrorism_risk {
+ 1 => 0.00033,
+ 2 => 0.00029,
+ 3 | 4 => 0.00006,
+ _ => 0,
+ }
+ }
+
+ Rate(risks: RiskScores, bc: BCConfig, bi: BIConfig): CoverOutcome {
+ if risks.terrorism_risk == 0
+ then not_available { reason: "Terrorism risk zone unavailable" }
+ else if md_si == 0
+ then not_available { reason: "No material damage sum insured" }
+ else rated {
+ key: "TER",
+ name: "Terrorism",
+ base_premium: md_si * PostcodeRate(risks.terrorism_risk) * 1.15,
+ limit: md_si + bi_si,
+ excess: 0,
+ }
+ where md_si = bc.buildings_limit + bc.contents_limit + bc.stock_limit,
+ bi_si = bi.basis_of_cover_limit * (bi.indemnity_months / 12)
+ }
+}
+
+// --- Product entry point ---
+// Validates exposure, rates all covers, applies claims loading, enforces minimum premium
+
+Product(
+ exposure: Exposure,
+ claims: ClaimsHistory,
+ risks: RiskScores,
+ bc: BCConfig,
+ bi: BIConfig,
+ pl_limit: number,
+ pbe_limit: number,
+): ProductOutcome {
+ // Exposure validation
+ if exposure.number_of_employees > 49
+ then declined { reasons: ["Maximum 49 employees allowed"] }
+ else if exposure.turnover > 5000000
+ then declined { reasons: ["Maximum turnover 5,000,000"] }
+ else if claims.number_of_claims > 5
+ then declined { reasons: ["Too many claims in history"] }
+ else if bc.number_of_beds == "over20"
+ then declined { reasons: ["Maximum 20 beds allowed"] }
+ // Rate all covers, check for failures, assemble product
+ else if any not_available in covers
+ then referred {
+ reasons: collect not_available { reason } in covers => reason,
+ }
+ else offered {
+ covers,
+ subtotal,
+ minimum_premium: min_prem,
+ total_gross_premium: round(total, 2),
+ total_net_premium: round(total * (1 - 0.35), 2),
+ commission_rate: 0.35,
+ currency: "GBP",
+ }
+ where total_claims_loading = Claims.TotalLoading(exposure, claims),
+ pl_cover = PublicLiability.Rate(exposure, limit: pl_limit, total_claims_loading),
+ bc_cover = BuildingsContentsStock.Rate(exposure, bc, risks, total_claims_loading),
+ bi_cover = BusinessInterruption.Rate(exposure, bi, total_claims_loading),
+ el_cover = EmployersLiability.Rate(exposure, total_claims_loading),
+ pbe_cover = PortableEquipment.Rate(limit: pbe_limit, total_claims_loading),
+ ter_cover = Terrorism.Rate(risks, bc, bi),
+ covers = [
+ pl_cover,
+ bc_cover,
+ bi_cover,
+ el_cover,
+ pbe_cover,
+ ter_cover,
+ ],
+ base_sum = sum(collect rated { base_premium } in covers => base_premium),
+ ter_premium = match ter_cover {
+ rated { base_premium } => base_premium,
+ _ => 0,
+ },
+ subtotal = base_sum - ter_premium,
+ min_prem = MinimumPremium(exposure, claims, bc),
+ floored_subtotal = if min_prem > subtotal then min_prem else subtotal,
+ total = floored_subtotal + ter_premium
+}
+
+// --- Multi-industry demonstration ---
+// Shows how a product can query multiple rows across selected industries.
+
+MultiIndustryDemo(industries: list(string)): MultiIndustrySummary {
+ {
+ pl_classes: collect row in industry_config {
+ row.code in industries => row.pl_class,
+ },
+ el_classes: collect row in industry_config {
+ row.code in industries => row.el_class,
+ },
+ deep_frying_rates: collect row in industry_config {
+ row.code in industries => row.deep_frying_rate,
+ },
+ minimum_premiums: collect row in industry_config {
+ row.code in industries => row.min_premium_default,
+ },
+ }
+}
+`;
+
+// Scenario: Café (DRI-945), 5 employees, £300k turnover, sole trader, 3 years exp, no claims
+// Own premises: buildings £500k, contents £100k, stock £50k, not listed
+// BI: Gross Profit £200k, 18 months indemnity, no loss of licence
+// PBE: £5,000 | Terrorism risk zone 3
+export const HOSPITALITY_INPUT = {
+ exposure: {
+ industry: "DRI-945",
+ number_of_employees: 5,
+ turnover: 300000,
+ is_sole_trader: true,
+ years_experience: 3,
+ },
+ claims: {
+ number_of_claims: 0,
+ total_claims_value: 0,
+ },
+ risks: {
+ flood_risk: 4,
+ theft_risk: 6,
+ terrorism_risk: 3,
+ },
+ bc: {
+ buildings_limit: 500000,
+ contents_limit: 100000,
+ stock_limit: 50000,
+ listed_type: "notListed",
+ has_outdoor_play: false,
+ has_functions: true,
+ number_of_beds: "none",
+ has_heavy_deep_frying: false,
+ },
+ bi: {
+ basis_of_cover: "Gross Profit",
+ basis_of_cover_limit: 200000,
+ indemnity_months: 18,
+ rent_receivable_limit: 0,
+ loss_of_licence_limit: 0,
+ },
+ pl_limit: 2000000,
+ pbe_limit: 5000,
+ // Table data loaded from CSV artifact (industry_config.csv)
+ _tables: {
+ industry_config: parseCSV(industryConfigCSV),
+ },
+};
diff --git a/playground/src/examples/industry_config.csv b/playground/src/examples/industry_config.csv
new file mode 100644
index 0000000..4e8096a
--- /dev/null
+++ b/playground/src/examples/industry_config.csv
@@ -0,0 +1,9 @@
+code,buildings_fire_class,contents_fire_class,contents_theft_class,stock_theft_class,bi_fire_class,pl_class,pl_severity,el_class,el_severity,flood_banding,loss_of_licence_class,deep_frying_rate,min_premium_claims_free,min_premium_default
+DRI-945,B,B,B,C,C,A,1,A,1,A,High,0.2,400,500
+DRI-946,B,B,B,C,C,A,1,A,1,A,High,0.2,400,500
+DRI-1955,Y,Y,F,V,I,B,2,C,3,C,High,0.35,600,750
+DRI-1956,Y,Y,F,V,I,B,2,C,3,C,High,0.35,600,750
+DRI-1957,Y,Y,F,V,I,C,3,C,3,D,High,0.35,600,750
+DRI-1958,Y,Y,F,V,I,C,3,C,3,D,High,0.35,350,500
+DRI-2857,Y,Y,F,V,I,B,2,C,3,C,High,0.35,600,750
+DRI-2858,Y,Y,F,V,I,B,2,B,2,B,High,0.2,600,750
diff --git a/playground/src/examples/insurance.axiom.ts b/playground/src/examples/insurance.axiom.ts
new file mode 100644
index 0000000..a35bfc7
--- /dev/null
+++ b/playground/src/examples/insurance.axiom.ts
@@ -0,0 +1,155 @@
+export const INSURANCE_EXAMPLE = `// Axiom v1 — Insurance Rating Example
+// Try editing this code and see the output update live!
+
+type Endorsement =
+ required { code: string, title: string }
+ | waived { code: string, reason: string }
+
+type RuleResult =
+ ok { factor: number, notes: list(string), endorsements: list(Endorsement) }
+ | referred { message: string }
+
+type CoverOutcome =
+ not_selected
+ | rated { premium: number, loading: number, notes: list(string), endorsements: list(Endorsement) }
+ | referred { reasons: list(string) }
+
+type ProductOutcome =
+ offered { total: number, covers: list(CoverOutcome) }
+ | referred { reasons: list(string) }
+
+BuildingsConstructionRule(quote: { construction: string }): RuleResult {
+ match quote.construction {
+ "brick" => ok { factor: 1.00, notes: [], endorsements: [] },
+ "stone" => ok {
+ factor: 1.05,
+ notes: ["stone_loading"],
+ endorsements: [
+ required { code: "END-ST01", title: "Structural survey within 5 years" },
+ ],
+ },
+ "timber" => referred { message: "timber_construction" },
+ _ => referred { message: "unknown_construction" }
+ }
+}
+
+BuildingsClaimsRule(quote: { claims_count: number }): RuleResult {
+ match {
+ quote.claims_count == 0 => ok { factor: 0.95, notes: ["claims_free"], endorsements: [] },
+ quote.claims_count <= 2 => ok { factor: 1.00, notes: [], endorsements: [] },
+ quote.claims_count == 3 => ok {
+ factor: 1.20,
+ notes: ["claims_3"],
+ endorsements: [
+ required { code: "END-CL01", title: "Claims history disclosure" },
+ ],
+ },
+ _ => referred { message: "claims_too_high" }
+ }
+}
+
+AggregateRules(
+ rules: list(RuleResult),
+ base_premium: number,
+): CoverOutcome {
+ if any referred in rules
+ then referred {
+ reasons: collect referred { message: m } in rules => m,
+ }
+ else rated {
+ premium: base_premium * product collect in rules {
+ ok { factor } => factor,
+ _ => 1.00,
+ },
+ loading: product collect in rules {
+ ok { factor } => factor,
+ _ => 1.00,
+ },
+ notes: flatten(collect ok { notes: n } in rules => n),
+ endorsements: flatten(collect ok { endorsements: e } in rules => e),
+ }
+}
+
+BuildingsCover(
+ quote: {
+ has_buildings: bool,
+ construction: string,
+ claims_count: number,
+ buildings_sum_insured: number,
+ },
+ base_rate: number,
+): CoverOutcome {
+ if not quote.has_buildings
+ then not_selected
+ else AggregateRules(
+ rules: [
+ BuildingsConstructionRule(quote: quote),
+ BuildingsClaimsRule(quote: quote),
+ ],
+ base_premium: quote.buildings_sum_insured / 1000 * base_rate,
+ )
+}
+
+ContentsCover(
+ quote: {
+ has_contents: bool,
+ contents_sum_insured: number,
+ },
+ base_rate: number,
+): CoverOutcome {
+ if not quote.has_contents
+ then not_selected
+ else rated {
+ premium: quote.contents_sum_insured / 1000 * base_rate,
+ loading: 1.00,
+ notes: [],
+ endorsements: [],
+ }
+}
+
+AggregateCovers(covers: list(CoverOutcome)): ProductOutcome {
+ if any referred in covers
+ then referred {
+ reasons: flatten(collect referred { reasons: rs } in covers => rs),
+ }
+ else offered {
+ total: sum(collect rated { premium: p } in covers => p),
+ covers: covers,
+ }
+}
+
+Product(
+ quote: {
+ has_buildings: bool,
+ construction: string,
+ claims_count: number,
+ buildings_sum_insured: number,
+ has_contents: bool,
+ contents_sum_insured: number,
+ },
+ rates: {
+ buildings_rate: number,
+ contents_rate: number,
+ },
+): ProductOutcome {
+ AggregateCovers(covers: [
+ BuildingsCover(quote: quote, base_rate: rates.buildings_rate),
+ ContentsCover(quote: quote, base_rate: rates.contents_rate),
+ ])
+}
+`;
+
+export const INSURANCE_INPUT = {
+ quote: {
+ has_buildings: true,
+ construction: "brick",
+ claims_count: 1,
+ buildings_sum_insured: 500000,
+ has_contents: true,
+ contents_sum_insured: 50000,
+ },
+ rates: {
+ buildings_rate: 0.50,
+ contents_rate: 0.75,
+ },
+};
diff --git a/playground/src/examples/landlords.axiom.ts b/playground/src/examples/landlords.axiom.ts
new file mode 100644
index 0000000..6edf5da
--- /dev/null
+++ b/playground/src/examples/landlords.axiom.ts
@@ -0,0 +1,349 @@
+import propertyTypeConfigCSV from './property_type_config.csv?raw';
+import { parseCSV } from '../utils/csv';
+
+export const LANDLORDS_EXAMPLE = `// Landlords Property Owners
+// Demonstrates nested exposures — each property is rated independently
+// then aggregated with a multi-property discount
+
+// --- Input types ---
+
+type Property = {
+ address: string,
+ property_type: string,
+ buildings_sum_insured: number,
+ contents_sum_insured: number,
+ annual_rent: number,
+ year_built: number,
+ listed_type: string,
+ number_of_units: number,
+ flood_risk: number,
+ subsidence_risk: number,
+}
+
+type LandlordExposure = {
+ number_of_employees: number,
+ turnover: number,
+ is_portfolio: bool,
+ properties: list(Property),
+}
+
+type ClaimsHistory = {
+ number_of_claims: number,
+ total_claims_value: number,
+}
+
+// --- Outcome types ---
+
+type PropertyBreakdown = {
+ address: string,
+ buildings_premium: number,
+ contents_premium: number,
+ rent_premium: number,
+ pol_premium: number,
+ property_total: number,
+}
+
+type ProductOutcome =
+ offered {
+ property_details: list(PropertyBreakdown),
+ property_subtotal: number,
+ discount_rate: number,
+ property_net: number,
+ el_premium: number,
+ legal_premium: number,
+ total_gross: number,
+ total_net: number,
+ commission_rate: number,
+ currency: string,
+ }
+ | declined { reasons: list(string) }
+
+// --- Property type configuration (from property_type_config.csv) ---
+// Rates and factors vary by property type: residential, commercial, HMO, mixed-use
+
+table property_type_config: list({
+ property_type: string,
+ buildings_rate: number,
+ contents_rate: number,
+ pol_base: number,
+ flood_factor: number,
+ subsidence_factor: number,
+})
+
+namespace PropertyConfig {
+ BuildingsRate(property_type: string): number {
+ match row in property_type_config { row.property_type == property_type => row.buildings_rate, _ => 0.04 }
+ }
+ ContentsRate(property_type: string): number {
+ match row in property_type_config { row.property_type == property_type => row.contents_rate, _ => 0.05 }
+ }
+ POLBase(property_type: string): number {
+ match row in property_type_config { row.property_type == property_type => row.pol_base, _ => 50 }
+ }
+ FloodFactor(property_type: string): number {
+ match row in property_type_config { row.property_type == property_type => row.flood_factor, _ => 0.15 }
+ }
+ SubsidenceFactor(property_type: string): number {
+ match row in property_type_config { row.property_type == property_type => row.subsidence_factor, _ => 0.10 }
+ }
+}
+
+// --- Per-property rating ---
+// Each property is rated independently — buildings, contents, rent, property owners liability.
+// Loadings are applied based on property characteristics (age, listed status, flood/subsidence risk).
+
+namespace PropertyRating {
+ // Listed building loading
+ ListedLoading(listed_type: string): number {
+ match listed_type {
+ "not_listed" => 0,
+ "grade_2" => 0.35,
+ "grade_1" => 0.75,
+ _ => 0,
+ }
+ }
+
+ // Building age loading — older properties attract higher rates
+ AgeLoading(year_built: number): number {
+ match year_built {
+ [0..1900] => 0.25,
+ [1901..1945) => 0.15,
+ [1945..1980) => 0.05,
+ [1980..9999] => 0,
+ _ => 0,
+ }
+ }
+
+ // Flood risk loading — scaled by property type flood factor
+ FloodRiskLoading(flood_risk: number, property_type: string): number {
+ if flood_risk <= 3 then 0
+ else if flood_risk <= 6 then PropertyConfig.FloodFactor(property_type) * 0.5
+ else PropertyConfig.FloodFactor(property_type)
+ }
+
+ // Subsidence risk loading
+ SubsidenceRiskLoading(subsidence_risk: number, property_type: string): number {
+ if subsidence_risk <= 3 then 0
+ else if subsidence_risk <= 6 then PropertyConfig.SubsidenceFactor(property_type) * 0.5
+ else PropertyConfig.SubsidenceFactor(property_type)
+ }
+
+ // Combined property loadings
+ TotalLoadings(prop: Property): number {
+ ListedLoading(prop.listed_type)
+ + AgeLoading(prop.year_built)
+ + FloodRiskLoading(prop.flood_risk, prop.property_type)
+ + SubsidenceRiskLoading(prop.subsidence_risk, prop.property_type)
+ }
+
+ // Buildings section premium
+ BuildingsPremium(prop: Property): number {
+ (prop.buildings_sum_insured / 1000) * PropertyConfig.BuildingsRate(prop.property_type) * (1 + TotalLoadings(prop))
+ }
+
+ // Contents section premium
+ ContentsPremium(prop: Property): number {
+ (prop.contents_sum_insured / 1000) * PropertyConfig.ContentsRate(prop.property_type) * (1 + TotalLoadings(prop))
+ }
+
+ // Rent receivable premium
+ RentPremium(prop: Property): number {
+ if prop.annual_rent == 0 then 0
+ else (prop.annual_rent / 1000) * 0.025 * (1 + TotalLoadings(prop))
+ }
+
+ // Property owners liability — per property, scaled by number of units
+ POLPremium(prop: Property): number {
+ PropertyConfig.POLBase(prop.property_type) * (1 + units_loading)
+ where units_loading = if prop.number_of_units > 4 then 0.25
+ else if prop.number_of_units > 2 then 0.10
+ else 0
+ }
+
+ // Total premium for a single property
+ Total(prop: Property): number {
+ BuildingsPremium(prop) + ContentsPremium(prop) + RentPremium(prop) + POLPremium(prop)
+ }
+
+ // Per-property breakdown with rounded values
+ Breakdown(prop: Property): PropertyBreakdown {
+ {
+ address: prop.address,
+ buildings_premium: round(BuildingsPremium(prop), 2),
+ contents_premium: round(ContentsPremium(prop), 2),
+ rent_premium: round(RentPremium(prop), 2),
+ pol_premium: round(POLPremium(prop), 2),
+ property_total: round(Total(prop), 2),
+ }
+ }
+}
+
+// --- Multi-property discount ---
+// Portfolio customers (via broker arrangement) get enhanced discounts
+
+MultiPropertyDiscount(num_properties: number, is_portfolio: bool): number {
+ if is_portfolio then portfolio_discount
+ else standard_discount
+ where standard_discount = match num_properties {
+ 1 => 0,
+ 2 => 0.05,
+ 3 => 0.075,
+ [4..6] => 0.10,
+ [7..10] => 0.125,
+ [11..99] => 0.15,
+ _ => 0,
+ },
+ portfolio_discount = match num_properties {
+ [1..3] => 0.10,
+ [4..6] => 0.15,
+ [7..10] => 0.175,
+ [11..99] => 0.20,
+ _ => 0,
+ }
+}
+
+// --- Employers Liability (landlord-level, not per-property) ---
+
+namespace EmployersLiability {
+ BaseRate(number_of_employees: number): number {
+ match number_of_employees {
+ 0 => 0,
+ [1..5] => 0.008,
+ [6..10] => 0.007,
+ [11..25] => 0.0065,
+ [26..99] => 0.006,
+ _ => 0.008,
+ }
+ }
+
+ Rate(turnover: number, number_of_employees: number): number {
+ if number_of_employees == 0 then 0
+ else round((turnover / 100) * BaseRate(number_of_employees), 2)
+ }
+}
+
+// --- Legal Expenses (flat rate by portfolio size) ---
+
+LegalExpensesPremium(num_properties: number): number {
+ match num_properties {
+ [1..3] => 95,
+ [4..6] => 150,
+ [7..10] => 225,
+ [11..99] => 350,
+ _ => 95,
+ }
+}
+
+// --- Claims loading (applied to property subtotal) ---
+
+ClaimsLoading(claims: ClaimsHistory): number {
+ match claims.number_of_claims {
+ 0 => -0.10,
+ 1 => 0,
+ 2 => 0.15,
+ 3 => 0.35,
+ 4 => 0.60,
+ [5..99] => 1.00,
+ _ => 0,
+ }
+}
+
+// --- Worst-case lookups across all properties ---
+// Useful for underwriting rules that check the riskiest property
+
+TotalBuildingsSI(properties: list(Property)): number {
+ sum collect prop in properties => prop.buildings_sum_insured
+}
+
+// --- Product entry point ---
+// Validates exposure, rates each property, aggregates with discount,
+// adds landlord-level covers (EL, legal expenses)
+
+Product(exposure: LandlordExposure, claims: ClaimsHistory): ProductOutcome {
+ if len(exposure.properties) == 0
+ then declined { reasons: ["At least one property is required"] }
+ else if len(exposure.properties) > 25
+ then declined { reasons: ["Maximum 25 properties per policy"] }
+ else if claims.number_of_claims > 5
+ then declined { reasons: ["Too many claims — manual review required"] }
+ else if TotalBuildingsSI(exposure.properties) > 10000000
+ then declined { reasons: ["Total buildings sum insured exceeds 10M limit"] }
+ else offered {
+ property_details: collect prop in exposure.properties => PropertyRating.Breakdown(prop),
+ property_subtotal: round(prop_subtotal, 2),
+ discount_rate: disc_rate,
+ property_net: round(prop_net, 2),
+ el_premium: el_prem,
+ legal_premium: legal_prem,
+ total_gross: round(total, 2),
+ total_net: round(total * (1 - 0.30), 2),
+ commission_rate: 0.30,
+ currency: "GBP",
+ }
+ where claims_load = ClaimsLoading(claims),
+ raw_property_total = sum collect prop in exposure.properties => PropertyRating.Total(prop),
+ prop_subtotal = raw_property_total * (1 + claims_load),
+ disc_rate = MultiPropertyDiscount(len(exposure.properties), exposure.is_portfolio),
+ prop_net = prop_subtotal * (1 - disc_rate),
+ el_prem = EmployersLiability.Rate(exposure.turnover, exposure.number_of_employees),
+ legal_prem = LegalExpensesPremium(len(exposure.properties)),
+ total = prop_net + el_prem + legal_prem
+}
+`;
+
+// Scenario: Small landlord with 3 properties
+// - Residential flat in London (1960s build, 1 unit)
+// - Commercial unit in Manchester (2005 build, 1 unit)
+// - HMO in Bristol (Victorian, 6 units, grade 2 listed, higher flood risk)
+export const LANDLORDS_INPUT = {
+ exposure: {
+ number_of_employees: 2,
+ turnover: 50000,
+ is_portfolio: false,
+ properties: [
+ {
+ address: "Flat 4, 23 Camden Road, London NW1",
+ property_type: "residential",
+ buildings_sum_insured: 250000,
+ contents_sum_insured: 15000,
+ annual_rent: 14400,
+ year_built: 1962,
+ listed_type: "not_listed",
+ number_of_units: 1,
+ flood_risk: 2,
+ subsidence_risk: 3,
+ },
+ {
+ address: "Unit 7, Enterprise Park, Manchester M4",
+ property_type: "commercial",
+ buildings_sum_insured: 400000,
+ contents_sum_insured: 30000,
+ annual_rent: 28000,
+ year_built: 2005,
+ listed_type: "not_listed",
+ number_of_units: 1,
+ flood_risk: 4,
+ subsidence_risk: 2,
+ },
+ {
+ address: "12 Clifton Gardens, Bristol BS8",
+ property_type: "hmo",
+ buildings_sum_insured: 350000,
+ contents_sum_insured: 20000,
+ annual_rent: 42000,
+ year_built: 1885,
+ listed_type: "grade_2",
+ number_of_units: 6,
+ flood_risk: 7,
+ subsidence_risk: 5,
+ },
+ ],
+ },
+ claims: {
+ number_of_claims: 0,
+ total_claims_value: 0,
+ },
+ _tables: {
+ property_type_config: parseCSV(propertyTypeConfigCSV),
+ },
+};
diff --git a/playground/src/examples/money.axiom.ts b/playground/src/examples/money.axiom.ts
new file mode 100644
index 0000000..5a0eeed
--- /dev/null
+++ b/playground/src/examples/money.axiom.ts
@@ -0,0 +1,87 @@
+export const MONEY_EXAMPLE = `// Money Type Plugin Demo
+// Demonstrates: money literals, type-safe arithmetic, currency enforcement
+
+// --- Premium calculation with money types ---
+
+type MoneyBreakdown = {
+ base_premium: money(GBP),
+ discount: money(GBP),
+ admin_fee: money(GBP),
+ ipt: money(GBP),
+ total: money(GBP),
+ affordable: bool,
+}
+
+BasePremium(risk_score: number): money(GBP) {
+ match risk_score {
+ [1..3] => £500,
+ [4..6] => £750,
+ [7..10] => £1250,
+ _ => £350,
+ }
+}
+
+AdminFee(): money(GBP) {
+ £35
+}
+
+// Insurance premium tax (12% of premium)
+IPT(premium: money(GBP)): money(GBP) {
+ premium * 0.12
+}
+
+// Multi-property discount
+PropertyDiscount(num_properties: number, subtotal: money(GBP)): money(GBP) {
+ subtotal * discount_rate
+ where discount_rate = match num_properties {
+ 1 => 0,
+ 2 => 0.05,
+ 3 => 0.10,
+ [4..99] => 0.15,
+ _ => 0,
+ }
+}
+
+// Minimum premium floor
+MinPremium(): money(GBP) {
+ £250
+}
+
+// Full product calculation with money throughout
+Product(risk_score: number, num_properties: number): money(GBP) {
+ round(total, 2)
+ where base = BasePremium(risk_score) * num_properties,
+ discount = PropertyDiscount(num_properties, base),
+ net = base - discount,
+ floor = if net > MinPremium() then net else MinPremium(),
+ ipt = IPT(floor),
+ total = floor + ipt + AdminFee()
+}
+
+// ISO code form works too
+EuroExample(): money(EUR) {
+ EUR1000 * 1.15
+}
+
+// Comparison operators
+IsAffordable(premium: money(GBP)): bool {
+ premium <= £2000
+}
+
+// Full breakdown
+Breakdown(risk_score: number, num_properties: number): MoneyBreakdown {
+ {
+ base_premium: BasePremium(risk_score) * num_properties,
+ discount: PropertyDiscount(num_properties, BasePremium(risk_score) * num_properties),
+ admin_fee: AdminFee(),
+ ipt: IPT(Product(risk_score, num_properties) - AdminFee()),
+ total: Product(risk_score, num_properties),
+ affordable: IsAffordable(Product(risk_score, num_properties)),
+ }
+}
+`;
+
+export const MONEY_INPUT = {
+ risk_score: 5,
+ num_properties: 3,
+};
diff --git a/playground/src/examples/property_type_config.csv b/playground/src/examples/property_type_config.csv
new file mode 100644
index 0000000..d722439
--- /dev/null
+++ b/playground/src/examples/property_type_config.csv
@@ -0,0 +1,5 @@
+property_type,buildings_rate,contents_rate,pol_base,flood_factor,subsidence_factor
+residential,0.035,0.045,35,0.15,0.10
+commercial,0.055,0.060,75,0.20,0.15
+hmo,0.065,0.055,95,0.15,0.12
+mixed_use,0.060,0.058,85,0.18,0.13
diff --git a/playground/src/examples/tradespeople.axiom.ts b/playground/src/examples/tradespeople.axiom.ts
new file mode 100644
index 0000000..2da0141
--- /dev/null
+++ b/playground/src/examples/tradespeople.axiom.ts
@@ -0,0 +1,373 @@
+export const TRADESPEOPLE_EXAMPLE = `// Covea Tradespeople Platinum V2
+// Translated from Abacus ProductSchemeBuilder + 6 CoverSchemeBuilders
+// Subset: 2 industries (DRI-103, DRI-284), employees 1-3
+
+type CoverOutcome =
+ rated {
+ key: string,
+ name: string,
+ base_premium: money(GBP),
+ limit: number,
+ excess: money(GBP),
+ }
+ | not_available { reason: string }
+
+type ProductOutcome =
+ offered {
+ covers: list(CoverOutcome),
+ total_gross_premium: money(GBP),
+ total_net_premium: money(GBP),
+ currency: string,
+ }
+ | declined { reasons: list(string) }
+ | referred { reasons: list(string) }
+
+// --- Product-level adjustment factors (from CSV lookup tables) ---
+
+namespace Adjustments {
+ // Postcode group relativity (from group_relativity.csv, 50 groups)
+ GroupRelativity(postcode_group: number): number {
+ match postcode_group {
+ 1 => 0.85, 5 => 0.885, 10 => 0.924, 15 => 0.962,
+ 20 => 0.992, 22 => 1, 25 => 1.012, 30 => 1.032,
+ 35 => 1.055, 40 => 1.08, 45 => 1.108, 50 => 1.15,
+ _ => 1,
+ }
+ }
+
+ // Years of experience relativity (from years_experience_relativities.csv)
+ YearsExperienceRelativity(years_experience: number): number {
+ match years_experience {
+ [0..1] => 0.92,
+ 2 => 0.98,
+ 3 => 1.02,
+ 4 => 1.06,
+ 5 => 1.1,
+ 6 => 1.09,
+ 7 => 1.08,
+ 8 => 1.07,
+ 9 => 1.06,
+ 10 => 1.05,
+ 11 => 1.04,
+ 12 => 1.02,
+ 13 | 14 => 1.01,
+ [15..] => 1,
+ _ => 1,
+ }
+ }
+
+ // Claims loading — applies when policyholder has prior claims
+ ClaimsLoading(number_of_claims: number, years_since_last_claim: number): number {
+ if number_of_claims == 0
+ then 1
+ else match years_since_last_claim {
+ [0..2) => 1.1,
+ [2..3) => 1.075,
+ [3..4) => 1.05,
+ [4..5) => 1.025,
+ _ => 1,
+ }
+ }
+
+ // No-claims discount — applies when policyholder has zero claims
+ NoClaimsLoading(number_of_claims: number, years_experience: number): number {
+ if number_of_claims > 0
+ then 1
+ else match years_experience {
+ [0..1) => 1,
+ [1..2) => 0.95,
+ [2..3) => 0.9,
+ [3..4) => 0.85,
+ [4..5) => 0.8,
+ [5..] => 0.75,
+ _ => 1,
+ }
+ }
+
+ // Combined adjustment factor: product of all relativities
+ Factor(
+ postcode_group: number,
+ years_experience: number,
+ number_of_claims: number,
+ years_since_last_claim: number,
+ ): number {
+ GroupRelativity(postcode_group)
+ * YearsExperienceRelativity(years_experience)
+ * ClaimsLoading(number_of_claims, years_since_last_claim)
+ * NoClaimsLoading(number_of_claims, years_experience)
+ }
+}
+
+// --- Public Liability (PL) ---
+
+namespace PublicLiability {
+ // 3-dimensional lookup: industry x limit x employees (from premium.csv, ~5700 rows)
+ BasePremium(industry: string, limit: number, employees: number): money(GBP) {
+ match (industry, limit, employees) {
+ ("DRI-103", 1000000, 1) => £163, ("DRI-103", 1000000, 2) => £256, ("DRI-103", 1000000, 3) => £398,
+ ("DRI-103", 2000000, 1) => £200, ("DRI-103", 2000000, 2) => £313, ("DRI-103", 2000000, 3) => £485,
+ ("DRI-103", 5000000, 1) => £251, ("DRI-103", 5000000, 2) => £391, ("DRI-103", 5000000, 3) => £609,
+ ("DRI-284", 1000000, 1) => £275, ("DRI-284", 1000000, 2) => £428, ("DRI-284", 1000000, 3) => £666,
+ ("DRI-284", 2000000, 1) => £336, ("DRI-284", 2000000, 2) => £518, ("DRI-284", 2000000, 3) => £812,
+ ("DRI-284", 5000000, 1) => £479, ("DRI-284", 5000000, 2) => £910, ("DRI-284", 5000000, 3) => £1323,
+ _ => £0,
+ }
+ }
+
+ Excess(industry: string): money(GBP) {
+ match industry {
+ "DRI-103" => £100,
+ "DRI-284" => £250,
+ _ => £100,
+ }
+ }
+
+ Rate(industry: string, limit: number, employees: number): CoverOutcome {
+ if bp == £0
+ then not_available { reason: "No PL rate for industry" }
+ else rated {
+ key: "PL",
+ name: "Public Liability",
+ base_premium: bp,
+ limit: limit,
+ excess: Excess(industry),
+ }
+ where bp = BasePremium(industry, limit, employees)
+ }
+}
+
+// --- Employers Liability (EL) ---
+
+namespace EmployersLiability {
+ // Fixed limit 10M. Premium per industry. Sole traders exclude the proprietor.
+ BasePremium(industry: string): money(GBP) {
+ match industry {
+ "DRI-103" => £137,
+ "DRI-284" => £1023,
+ _ => £0,
+ }
+ }
+
+ InsurableManualWorkers(manual_workers: number, business_type: string): number {
+ if business_type == "sole_trader"
+ then if manual_workers > 1 then manual_workers - 1 else 0
+ else manual_workers
+ }
+
+ Rate(industry: string, manual_workers: number, business_type: string): CoverOutcome {
+ if bp == £0
+ then not_available { reason: "No EL rate for industry" }
+ else rated {
+ key: "EL",
+ name: "Employers Liability",
+ base_premium: bp * InsurableManualWorkers(manual_workers, business_type),
+ limit: 10000000,
+ excess: £0,
+ }
+ where bp = BasePremium(industry)
+ }
+}
+
+// --- Portable Tools and Equipment (PTE) ---
+
+namespace PortableTools {
+ // Simple limit-based premium, no industry dependency (from premium.csv, 5 rows)
+ BasePremium(limit: number): money(GBP) {
+ match limit {
+ 1000 => £59.70,
+ 2500 => £126.35,
+ 5000 => £192.92,
+ 7500 => £244.86,
+ 10000 => £296.80,
+ _ => £0,
+ }
+ }
+
+ Rate(limit: number): CoverOutcome {
+ if bp == £0
+ then not_available { reason: "Invalid PTE limit" }
+ else rated {
+ key: "PTE",
+ name: "Portable Tools and Equipment",
+ base_premium: bp,
+ limit: limit,
+ excess: £60,
+ }
+ where bp = BasePremium(limit)
+ }
+}
+
+// --- Own Plant and Machinery (OPM) ---
+
+namespace OwnPlant {
+ // Only available for eligible industries. Premium by limit x manual workers.
+ BasePremium(limit: number, manual_workers: number): money(GBP) {
+ match (limit, manual_workers) {
+ (5000, 1) => £78.11, (5000, 2) => £103.75, (5000, 3) => £128.20,
+ (10000, 1) => £104.15, (10000, 2) => £138.33, (10000, 3) => £170.93,
+ (25000, 1) => £115.28, (25000, 2) => £153.43, (25000, 3) => £190.00,
+ _ => £0,
+ }
+ }
+
+ Rate(industry: string, limit: number, manual_workers: number): CoverOutcome {
+ if industry not in ["DRI-284"]
+ then not_available { reason: "Industry not eligible for OPM" }
+ else if bp == £0
+ then not_available { reason: "Invalid OPM limit/workers combination" }
+ else rated {
+ key: "OPM",
+ name: "Own Plant and Machinery",
+ base_premium: bp,
+ limit: limit,
+ excess: £250,
+ }
+ where bp = BasePremium(limit, manual_workers)
+ }
+}
+
+// --- Hired In Plant and Machinery (HPM) ---
+
+namespace HiredPlant {
+ // Only available for eligible industries. Premium by limit x manual workers.
+ BasePremium(limit: number, manual_workers: number): money(GBP) {
+ match (limit, manual_workers) {
+ (10000, 1) => £111.30, (10000, 2) => £145.48, (10000, 3) => £179.67,
+ (25000, 1) => £123.23, (25000, 2) => £162.18, (25000, 3) => £199.55,
+ (50000, 1) => £154.23, (50000, 2) => £202.73, (50000, 3) => £249.63,
+ _ => £0,
+ }
+ }
+
+ Rate(industry: string, limit: number, manual_workers: number): CoverOutcome {
+ if industry not in ["DRI-284"]
+ then not_available { reason: "Industry not eligible for HPM" }
+ else if bp == £0
+ then not_available { reason: "Invalid HPM limit/workers combination" }
+ else rated {
+ key: "HPM",
+ name: "Hired In Plant and Machinery",
+ base_premium: bp,
+ limit: limit,
+ excess: £250,
+ }
+ where bp = BasePremium(limit, manual_workers)
+ }
+}
+
+// --- Contract Works (CW) ---
+
+namespace ContractWorks {
+ // Industry band determines premium tier (from industry_bands.csv)
+ IndustryBand(industry: string): number {
+ match industry {
+ "DRI-284" => 4,
+ _ => 0,
+ }
+ }
+
+ // Premium by limit x employees x industry band (from premium.csv)
+ BasePremium(limit: number, employees: number, band: number): money(GBP) {
+ match (limit, band, employees) {
+ (100000, 1, 1) => £120.05, (100000, 1, 2) => £155.82, (100000, 1, 3) => £186.03,
+ (100000, 2, 1) => £141.51, (100000, 2, 2) => £183.65, (100000, 2, 3) => £218.63,
+ (100000, 3, 1) => £148.67, (100000, 3, 2) => £193.18, (100000, 3, 3) => £229.75,
+ (100000, 4, 1) => £162.98, (100000, 4, 2) => £211.47, (100000, 4, 3) => £251.22,
+ (250000, 1, 1) => £133.56, (250000, 1, 2) => £173.31, (250000, 1, 3) => £206.70,
+ (250000, 2, 1) => £157.41, (250000, 2, 2) => £204.32, (250000, 2, 3) => £243.27,
+ (250000, 3, 1) => £165.36, (250000, 3, 2) => £214.65, (250000, 3, 3) => £255.20,
+ (250000, 4, 1) => £181.26, (250000, 4, 2) => £235.32, (250000, 4, 3) => £279.84,
+ _ => £0,
+ }
+ }
+
+ Rate(industry: string, limit: number, employees: number): CoverOutcome {
+ if band == 0
+ then not_available { reason: "Industry not eligible for Contract Works" }
+ else if bp == £0
+ then not_available { reason: "Invalid CW limit/employees combination" }
+ else rated {
+ key: "CW",
+ name: "Contract Works",
+ base_premium: bp,
+ limit: limit,
+ excess: £250,
+ }
+ where band = IndustryBand(industry),
+ bp = BasePremium(limit, employees, band)
+ }
+}
+
+// --- Product entry point ---
+// Validates exposure constraints, rates all covers, applies shared adjustments
+
+Product(
+ industry: string,
+ number_of_employees: number,
+ manual_workers: number,
+ business_type: string,
+ turnover: number,
+ postcode_group: number,
+ years_experience: number,
+ number_of_claims: number,
+ years_since_last_claim: number,
+ pl_limit: number,
+ pte_limit: number,
+ opm_limit: number,
+ hpm_limit: number,
+ cw_limit: number,
+): ProductOutcome {
+ if number_of_employees > 10
+ then declined { reasons: ["Maximum 10 employees allowed"] }
+ else if turnover > 2000000
+ then declined { reasons: ["Maximum turnover 2,000,000"] }
+ else if manual_workers > number_of_employees
+ then declined { reasons: ["Manual workers cannot exceed total employees"] }
+ else if number_of_claims > 1
+ then declined { reasons: ["Maximum 1 claim in last 5 years"] }
+ else if any not_available in covers
+ then referred {
+ reasons: collect not_available { reason } in covers => reason,
+ }
+ else offered {
+ covers: covers,
+ total_gross_premium: round(base_sum * adj, 2),
+ total_net_premium: round(base_sum * adj * 0.65, 2),
+ currency: "GBP",
+ }
+ where pl_cover = PublicLiability.Rate(industry, limit: pl_limit, employees: number_of_employees),
+ el_cover = EmployersLiability.Rate(industry, manual_workers, business_type),
+ pte_cover = PortableTools.Rate(limit: pte_limit),
+ opm_cover = OwnPlant.Rate(industry, limit: opm_limit, manual_workers),
+ hpm_cover = HiredPlant.Rate(industry, limit: hpm_limit, manual_workers),
+ cw_cover = ContractWorks.Rate(industry, limit: cw_limit, employees: number_of_employees),
+ covers = [
+ pl_cover,
+ el_cover,
+ pte_cover,
+ opm_cover,
+ hpm_cover,
+ cw_cover,
+ ],
+ adj = Adjustments.Factor(postcode_group, years_experience, number_of_claims, years_since_last_claim),
+ base_sum = sum(collect rated { base_premium } in covers => base_premium)
+}
+`;
+
+// Scenario: Roofer (DRI-284), 2 employees, limited company, no claims, 5 years experience
+export const TRADESPEOPLE_INPUT = {
+ industry: "DRI-284",
+ number_of_employees: 2,
+ manual_workers: 2,
+ business_type: "limited_company",
+ turnover: 500000,
+ postcode_group: 22,
+ years_experience: 5,
+ number_of_claims: 0,
+ years_since_last_claim: 0,
+ pl_limit: 2000000,
+ pte_limit: 5000,
+ opm_limit: 10000,
+ hpm_limit: 25000,
+ cw_limit: 100000,
+};
diff --git a/playground/src/lang/ast.ts b/playground/src/lang/ast.ts
new file mode 100644
index 0000000..e0ab7a7
--- /dev/null
+++ b/playground/src/lang/ast.ts
@@ -0,0 +1,314 @@
+import { Location } from './diagnostics';
+
+// --- Top-level declarations ---
+
+export interface ProgramNode {
+ kind: 'Program';
+ body: Declaration[];
+}
+
+export type Declaration = TypeDeclaration | ExpressionDeclaration | NamespaceDeclaration | SourceDeclaration | TableDeclaration;
+
+export interface TypeDeclaration {
+ kind: 'TypeDeclaration';
+ name: string;
+ alternatives: VariantAlternative[];
+ shape?: Record