@ansvar/ch-farm-safety-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/check-freshness.yml +53 -0
- package/.github/workflows/ci.yml +21 -0
- package/.github/workflows/codeql.yml +30 -0
- package/.github/workflows/ghcr-build.yml +45 -0
- package/.github/workflows/gitleaks.yml +24 -0
- package/.github/workflows/ingest.yml +67 -0
- package/.github/workflows/publish.yml +24 -0
- package/CHANGELOG.md +23 -0
- package/CODEOWNERS +1 -0
- package/COVERAGE.md +54 -0
- package/DISCLAIMER.md +51 -0
- package/Dockerfile +26 -0
- package/LICENSE +17 -0
- package/PRIVACY.md +25 -0
- package/README.md +129 -0
- package/SECURITY.md +25 -0
- package/TOOLS.md +140 -0
- package/data/coverage.json +22 -0
- package/data/database.db +0 -0
- package/data/sources.yml +36 -0
- package/dist/db.d.ts +25 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +200 -0
- package/dist/db.js.map +1 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +250 -0
- package/dist/http-server.js.map +1 -0
- package/dist/jurisdiction.d.ts +18 -0
- package/dist/jurisdiction.d.ts.map +1 -0
- package/dist/jurisdiction.js +16 -0
- package/dist/jurisdiction.js.map +1 -0
- package/dist/metadata.d.ts +10 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +20 -0
- package/dist/metadata.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +196 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/about.d.ts +15 -0
- package/dist/tools/about.d.ts.map +1 -0
- package/dist/tools/about.js +27 -0
- package/dist/tools/about.js.map +1 -0
- package/dist/tools/check-freshness.d.ts +15 -0
- package/dist/tools/check-freshness.d.ts.map +1 -0
- package/dist/tools/check-freshness.js +26 -0
- package/dist/tools/check-freshness.js.map +1 -0
- package/dist/tools/get-accident-reporting.d.ts +49 -0
- package/dist/tools/get-accident-reporting.d.ts.map +1 -0
- package/dist/tools/get-accident-reporting.js +31 -0
- package/dist/tools/get-accident-reporting.js.map +1 -0
- package/dist/tools/get-chemical-safety.d.ts +47 -0
- package/dist/tools/get-chemical-safety.d.ts.map +1 -0
- package/dist/tools/get-chemical-safety.js +31 -0
- package/dist/tools/get-chemical-safety.js.map +1 -0
- package/dist/tools/get-machinery-safety.d.ts +46 -0
- package/dist/tools/get-machinery-safety.d.ts.map +1 -0
- package/dist/tools/get-machinery-safety.js +31 -0
- package/dist/tools/get-machinery-safety.js.map +1 -0
- package/dist/tools/get-risk-assessment-requirements.d.ts +47 -0
- package/dist/tools/get-risk-assessment-requirements.d.ts.map +1 -0
- package/dist/tools/get-risk-assessment-requirements.js +31 -0
- package/dist/tools/get-risk-assessment-requirements.js.map +1 -0
- package/dist/tools/get-young-worker-rules.d.ts +47 -0
- package/dist/tools/get-young-worker-rules.d.ts.map +1 -0
- package/dist/tools/get-young-worker-rules.js +31 -0
- package/dist/tools/get-young-worker-rules.js.map +1 -0
- package/dist/tools/list-sources.d.ts +18 -0
- package/dist/tools/list-sources.d.ts.map +1 -0
- package/dist/tools/list-sources.js +51 -0
- package/dist/tools/list-sources.js.map +1 -0
- package/dist/tools/search-bul-guidance.d.ts +23 -0
- package/dist/tools/search-bul-guidance.d.ts.map +1 -0
- package/dist/tools/search-bul-guidance.js +35 -0
- package/dist/tools/search-bul-guidance.js.map +1 -0
- package/dist/tools/search-safety-rules.d.ts +25 -0
- package/dist/tools/search-safety-rules.d.ts.map +1 -0
- package/dist/tools/search-safety-rules.js +26 -0
- package/dist/tools/search-safety-rules.js.map +1 -0
- package/docker-compose.yml +12 -0
- package/eslint.config.js +27 -0
- package/package.json +54 -0
- package/scripts/ingest.ts +879 -0
- package/server.json +31 -0
- package/src/db.ts +241 -0
- package/src/http-server.ts +282 -0
- package/src/jurisdiction.ts +30 -0
- package/src/metadata.ts +30 -0
- package/src/server.ts +219 -0
- package/src/tools/about.ts +28 -0
- package/src/tools/check-freshness.ts +42 -0
- package/src/tools/get-accident-reporting.ts +52 -0
- package/src/tools/get-chemical-safety.ts +52 -0
- package/src/tools/get-machinery-safety.ts +52 -0
- package/src/tools/get-risk-assessment-requirements.ts +52 -0
- package/src/tools/get-young-worker-rules.ts +52 -0
- package/src/tools/list-sources.ts +65 -0
- package/src/tools/search-bul-guidance.ts +51 -0
- package/src/tools/search-safety-rules.ts +35 -0
- package/tests/db.test.ts +121 -0
- package/tests/helpers/seed-db.ts +134 -0
- package/tests/jurisdiction.test.ts +54 -0
- package/tests/tools/about.test.ts +44 -0
- package/tests/tools/check-freshness.test.ts +68 -0
- package/tests/tools/list-sources.test.ts +75 -0
- package/tests/tools/search-safety-rules.test.ts +75 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +9 -0
package/TOOLS.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Tools Reference — ch-farm-safety-mcp
|
|
2
|
+
|
|
3
|
+
10 tools for Swiss farm workplace safety data.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## about
|
|
8
|
+
|
|
9
|
+
Get server metadata: name, version, coverage, data sources, and links.
|
|
10
|
+
|
|
11
|
+
**Parameters:** none
|
|
12
|
+
|
|
13
|
+
**Returns:** Server name, description, version, supported jurisdictions, data source list, tool count, links.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## list_sources
|
|
18
|
+
|
|
19
|
+
List all data sources with authority, URL, license, and freshness info.
|
|
20
|
+
|
|
21
|
+
**Parameters:** none
|
|
22
|
+
|
|
23
|
+
**Returns:** Array of sources, each with `name`, `authority`, `official_url`, `retrieval_method`, `update_frequency`, `license`, `coverage`, `last_retrieved`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## check_data_freshness
|
|
28
|
+
|
|
29
|
+
Check when data was last ingested, staleness status, and how to trigger a refresh.
|
|
30
|
+
|
|
31
|
+
**Parameters:** none
|
|
32
|
+
|
|
33
|
+
**Returns:** `status` (fresh/stale/unknown), `last_ingest`, `build_date`, `schema_version`, `days_since_ingest`, `staleness_threshold_days` (90), `refresh_command`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## search_safety_rules
|
|
38
|
+
|
|
39
|
+
Search Swiss farm workplace safety rules using FTS5 with tiered fallback (exact phrase, AND, prefix, stemmed, OR, LIKE).
|
|
40
|
+
|
|
41
|
+
**Parameters:**
|
|
42
|
+
|
|
43
|
+
| Name | Type | Required | Description |
|
|
44
|
+
|------|------|----------|-------------|
|
|
45
|
+
| `query` | string | yes | Free-text search query (German or English) |
|
|
46
|
+
| `topic` | string | no | Filter by safety area: `maschinen`, `tiere`, `wald`, `hoehe`, `chemie`, `silogas`, `allgemein` |
|
|
47
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
48
|
+
| `limit` | number | no | Max results (default: 20, max: 50) |
|
|
49
|
+
|
|
50
|
+
**Returns:** `query`, `jurisdiction`, `results_count`, array of `{ title, body, area, relevance_rank }`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## get_risk_assessment_requirements
|
|
55
|
+
|
|
56
|
+
Get risk assessment (Risikobeurteilung) obligations for farm activities. Based on EKAS Branchenloesung Landwirtschaft and VUV.
|
|
57
|
+
|
|
58
|
+
**Parameters:**
|
|
59
|
+
|
|
60
|
+
| Name | Type | Required | Description |
|
|
61
|
+
|------|------|----------|-------------|
|
|
62
|
+
| `activity_type` | string | no | Activity type (e.g. Alleinarbeit, Siloarbeiten, Holzerei, Tierhaltung). Omit for all. |
|
|
63
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
64
|
+
|
|
65
|
+
**Returns:** `jurisdiction`, `results_count`, array of `{ activity_type, requirement, content, update_trigger, legal_basis }`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## get_machinery_safety
|
|
70
|
+
|
|
71
|
+
Get safety requirements for agricultural machinery. Covers ROPS, Gurtpflicht, MFK, speed limits, PTO guards.
|
|
72
|
+
|
|
73
|
+
**Parameters:**
|
|
74
|
+
|
|
75
|
+
| Name | Type | Required | Description |
|
|
76
|
+
|------|------|----------|-------------|
|
|
77
|
+
| `machine_type` | string | no | Machine type (e.g. Traktor, Maehdrescher, Motorsaege, Kreiselmaeher). Omit for all. |
|
|
78
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
79
|
+
|
|
80
|
+
**Returns:** `jurisdiction`, `results_count`, array of `{ machine_type, requirement, certification, notes }`.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## get_chemical_safety
|
|
85
|
+
|
|
86
|
+
Get chemical safety data: exposure limits (MAK), required PPE, and storage rules. Covers PSM (Pflanzenschutzmittel), Silogas, Duenger.
|
|
87
|
+
|
|
88
|
+
**Parameters:**
|
|
89
|
+
|
|
90
|
+
| Name | Type | Required | Description |
|
|
91
|
+
|------|------|----------|-------------|
|
|
92
|
+
| `substance_type` | string | no | Substance type (e.g. Pflanzenschutzmittel, Silogas, Ammoniak, Dieselkraftstoff). Omit for all. |
|
|
93
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
94
|
+
|
|
95
|
+
**Returns:** `jurisdiction`, `results_count`, array of `{ substance_type, exposure_limit, ppe_required, storage_rule }`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## get_young_worker_rules
|
|
100
|
+
|
|
101
|
+
Get restrictions for young workers (Jugendarbeitsschutz) on Swiss farms. Covers age thresholds, allowed/forbidden tasks, exceptions for family farms.
|
|
102
|
+
|
|
103
|
+
**Parameters:**
|
|
104
|
+
|
|
105
|
+
| Name | Type | Required | Description |
|
|
106
|
+
|------|------|----------|-------------|
|
|
107
|
+
| `age_group` | string | no | Age group (e.g. unter-13, 13-15, 15-18). Omit for all. |
|
|
108
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
109
|
+
|
|
110
|
+
**Returns:** `jurisdiction`, `results_count`, array of `{ age_group, restriction, exception, legal_basis }`.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## get_accident_reporting
|
|
115
|
+
|
|
116
|
+
Get accident reporting obligations: deadlines, forms, and responsible insurer (Suva for employees, private for self-employed).
|
|
117
|
+
|
|
118
|
+
**Parameters:**
|
|
119
|
+
|
|
120
|
+
| Name | Type | Required | Description |
|
|
121
|
+
|------|------|----------|-------------|
|
|
122
|
+
| `severity` | string | no | Accident severity (e.g. toedlich, schwer, leicht, Berufskrankheit). Omit for all. |
|
|
123
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
124
|
+
|
|
125
|
+
**Returns:** `jurisdiction`, `results_count`, array of `{ severity, obligation, deadline, form, insurer }`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## search_bul_guidance
|
|
130
|
+
|
|
131
|
+
Search BUL/SPAA safety guidance documents, fact sheets, and campaigns.
|
|
132
|
+
|
|
133
|
+
**Parameters:**
|
|
134
|
+
|
|
135
|
+
| Name | Type | Required | Description |
|
|
136
|
+
|------|------|----------|-------------|
|
|
137
|
+
| `query` | string | yes | Free-text search query (German or English) |
|
|
138
|
+
| `jurisdiction` | string | no | ISO 3166-1 alpha-2 code (default: CH) |
|
|
139
|
+
|
|
140
|
+
**Returns:** `query`, `jurisdiction`, `results_count`, array of `{ topic, title, description, url }`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"server": "ch-farm-safety-mcp",
|
|
3
|
+
"jurisdiction": "CH",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"last_ingest": "2026-04-05",
|
|
6
|
+
"data": {
|
|
7
|
+
"safety_rules": 30,
|
|
8
|
+
"risk_assessments": 7,
|
|
9
|
+
"machinery_safety": 10,
|
|
10
|
+
"chemical_safety": 7,
|
|
11
|
+
"young_worker_rules": 5,
|
|
12
|
+
"accident_reporting": 5,
|
|
13
|
+
"bul_guidance": 15
|
|
14
|
+
},
|
|
15
|
+
"tools": 10,
|
|
16
|
+
"sources": [
|
|
17
|
+
"BUL/SPAA — Sicherheitsregeln, Merkblaetter",
|
|
18
|
+
"Suva — Arbeitssicherheit Landwirtschaft",
|
|
19
|
+
"EKAS — Branchenloesung Landwirtschaft",
|
|
20
|
+
"Agriss — Unfallstatistik"
|
|
21
|
+
]
|
|
22
|
+
}
|
package/data/database.db
ADDED
|
Binary file
|
package/data/sources.yml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Data sources for ch-farm-safety-mcp
|
|
2
|
+
sources:
|
|
3
|
+
- name: BUL/SPAA — Sicherheitsregeln und Merkblaetter
|
|
4
|
+
authority: Beratungsstelle fuer Unfallverhuetung in der Landwirtschaft (BUL)
|
|
5
|
+
url: https://www.bul.ch
|
|
6
|
+
license: Swiss public-sector information — free reuse
|
|
7
|
+
update_frequency: periodic (rules updated as needed)
|
|
8
|
+
last_retrieved: "2026-04-05"
|
|
9
|
+
|
|
10
|
+
- name: Suva — Arbeitssicherheit Landwirtschaft
|
|
11
|
+
authority: Schweizerische Unfallversicherungsanstalt (Suva)
|
|
12
|
+
url: https://www.suva.ch/de-ch/praevention/nach-branche/landwirtschaft
|
|
13
|
+
license: Swiss public-sector information — free reuse
|
|
14
|
+
update_frequency: periodic
|
|
15
|
+
last_retrieved: "2026-04-05"
|
|
16
|
+
|
|
17
|
+
- name: EKAS — Branchenloesung Landwirtschaft
|
|
18
|
+
authority: Eidgenoessische Koordinationskommission fuer Arbeitssicherheit (EKAS)
|
|
19
|
+
url: https://www.ekas.admin.ch
|
|
20
|
+
license: Swiss Federal Administration — free reuse
|
|
21
|
+
update_frequency: periodic
|
|
22
|
+
last_retrieved: "2026-04-05"
|
|
23
|
+
|
|
24
|
+
- name: Agriss — Unfallstatistik Schweizer Landwirtschaft
|
|
25
|
+
authority: BUL / Agroscope
|
|
26
|
+
url: https://www.bul.ch/de/themen/unfallstatistik
|
|
27
|
+
license: Swiss public-sector information — free reuse
|
|
28
|
+
update_frequency: annual
|
|
29
|
+
last_retrieved: "2026-04-05"
|
|
30
|
+
|
|
31
|
+
- name: SECO — Jugendarbeitsschutz
|
|
32
|
+
authority: Staatssekretariat fuer Wirtschaft (SECO)
|
|
33
|
+
url: https://www.seco.admin.ch/seco/de/home/Arbeit/Arbeitsbedingungen/jugendarbeitsschutz.html
|
|
34
|
+
license: Swiss Federal Administration — free reuse
|
|
35
|
+
update_frequency: periodic
|
|
36
|
+
last_retrieved: "2026-04-05"
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
export interface Database {
|
|
3
|
+
get<T>(sql: string, params?: unknown[]): T | undefined;
|
|
4
|
+
all<T>(sql: string, params?: unknown[]): T[];
|
|
5
|
+
run(sql: string, params?: unknown[]): void;
|
|
6
|
+
close(): void;
|
|
7
|
+
readonly instance: BetterSqlite3.Database;
|
|
8
|
+
}
|
|
9
|
+
export declare function createDatabase(dbPath?: string): Database;
|
|
10
|
+
export declare function ftsSearch(db: Database, query: string, limit?: number): {
|
|
11
|
+
title: string;
|
|
12
|
+
body: string;
|
|
13
|
+
area: string;
|
|
14
|
+
jurisdiction: string;
|
|
15
|
+
rank: number;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Tiered FTS5 search with automatic fallback.
|
|
19
|
+
* Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
|
|
20
|
+
*/
|
|
21
|
+
export declare function tieredFtsSearch(db: Database, table: string, columns: string[], query: string, limit?: number): {
|
|
22
|
+
tier: string;
|
|
23
|
+
results: Record<string, unknown>[];
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAI3C,MAAM,WAAW,QAAQ;IACvB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC;IACvD,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC3C,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,QAAQ,CAAC;CAC3C;AAED,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CA4BxD;AA8FD,wBAAgB,SAAS,CACvB,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAGrF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,CA4DtD"}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import BetterSqlite3 from 'better-sqlite3';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
export function createDatabase(dbPath) {
|
|
5
|
+
const resolvedPath = dbPath ??
|
|
6
|
+
join(dirname(fileURLToPath(import.meta.url)), '..', 'data', 'database.db');
|
|
7
|
+
const db = new BetterSqlite3(resolvedPath);
|
|
8
|
+
db.pragma('journal_mode = DELETE');
|
|
9
|
+
db.pragma('foreign_keys = ON');
|
|
10
|
+
initSchema(db);
|
|
11
|
+
return {
|
|
12
|
+
get(sql, params = []) {
|
|
13
|
+
return db.prepare(sql).get(...params);
|
|
14
|
+
},
|
|
15
|
+
all(sql, params = []) {
|
|
16
|
+
return db.prepare(sql).all(...params);
|
|
17
|
+
},
|
|
18
|
+
run(sql, params = []) {
|
|
19
|
+
db.prepare(sql).run(...params);
|
|
20
|
+
},
|
|
21
|
+
close() {
|
|
22
|
+
db.close();
|
|
23
|
+
},
|
|
24
|
+
get instance() {
|
|
25
|
+
return db;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function initSchema(db) {
|
|
30
|
+
db.exec(`
|
|
31
|
+
CREATE TABLE IF NOT EXISTS safety_rules (
|
|
32
|
+
id INTEGER PRIMARY KEY,
|
|
33
|
+
topic TEXT NOT NULL,
|
|
34
|
+
rule TEXT NOT NULL,
|
|
35
|
+
area TEXT NOT NULL CHECK(area IN ('maschinen','tiere','wald','hoehe','chemie','silogas','allgemein')),
|
|
36
|
+
description TEXT,
|
|
37
|
+
source TEXT,
|
|
38
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
39
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE TABLE IF NOT EXISTS risk_assessments (
|
|
43
|
+
id INTEGER PRIMARY KEY,
|
|
44
|
+
activity_type TEXT NOT NULL,
|
|
45
|
+
requirement TEXT NOT NULL,
|
|
46
|
+
content TEXT,
|
|
47
|
+
update_trigger TEXT,
|
|
48
|
+
legal_basis TEXT,
|
|
49
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
50
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE IF NOT EXISTS machinery_safety (
|
|
54
|
+
id INTEGER PRIMARY KEY,
|
|
55
|
+
machine_type TEXT NOT NULL,
|
|
56
|
+
requirement TEXT NOT NULL,
|
|
57
|
+
certification TEXT,
|
|
58
|
+
notes TEXT,
|
|
59
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
60
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE TABLE IF NOT EXISTS chemical_safety (
|
|
64
|
+
id INTEGER PRIMARY KEY,
|
|
65
|
+
substance_type TEXT NOT NULL,
|
|
66
|
+
exposure_limit TEXT,
|
|
67
|
+
ppe_required TEXT,
|
|
68
|
+
storage_rule TEXT,
|
|
69
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
70
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TABLE IF NOT EXISTS young_worker_rules (
|
|
74
|
+
id INTEGER PRIMARY KEY,
|
|
75
|
+
age_group TEXT NOT NULL,
|
|
76
|
+
restriction TEXT NOT NULL,
|
|
77
|
+
exception TEXT,
|
|
78
|
+
legal_basis TEXT,
|
|
79
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
80
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS accident_reporting (
|
|
84
|
+
id INTEGER PRIMARY KEY,
|
|
85
|
+
severity TEXT NOT NULL,
|
|
86
|
+
obligation TEXT NOT NULL,
|
|
87
|
+
deadline TEXT,
|
|
88
|
+
form TEXT,
|
|
89
|
+
insurer TEXT,
|
|
90
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
91
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS bul_guidance (
|
|
95
|
+
id INTEGER PRIMARY KEY,
|
|
96
|
+
topic TEXT NOT NULL,
|
|
97
|
+
title TEXT NOT NULL,
|
|
98
|
+
description TEXT,
|
|
99
|
+
url TEXT,
|
|
100
|
+
language TEXT NOT NULL DEFAULT 'DE',
|
|
101
|
+
jurisdiction TEXT NOT NULL DEFAULT 'CH'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
|
|
105
|
+
title, body, area, jurisdiction
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS db_metadata (
|
|
109
|
+
key TEXT PRIMARY KEY,
|
|
110
|
+
value TEXT
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('schema_version', '1.0');
|
|
114
|
+
INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('mcp_name', 'Switzerland Farm Safety MCP');
|
|
115
|
+
INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('jurisdiction', 'CH');
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
const FTS_COLUMNS = ['title', 'body', 'area', 'jurisdiction'];
|
|
119
|
+
export function ftsSearch(db, query, limit = 20) {
|
|
120
|
+
const { results } = tieredFtsSearch(db, 'search_index', FTS_COLUMNS, query, limit);
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Tiered FTS5 search with automatic fallback.
|
|
125
|
+
* Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
|
|
126
|
+
*/
|
|
127
|
+
export function tieredFtsSearch(db, table, columns, query, limit = 20) {
|
|
128
|
+
const sanitized = sanitizeFtsInput(query);
|
|
129
|
+
if (!sanitized.trim())
|
|
130
|
+
return { tier: 'empty', results: [] };
|
|
131
|
+
const columnList = columns.join(', ');
|
|
132
|
+
const select = `SELECT ${columnList}, rank FROM ${table}`;
|
|
133
|
+
const order = `ORDER BY rank LIMIT ?`;
|
|
134
|
+
// Tier 1: Exact phrase
|
|
135
|
+
const phrase = `"${sanitized}"`;
|
|
136
|
+
let results = tryFts(db, select, table, order, phrase, limit);
|
|
137
|
+
if (results.length > 0)
|
|
138
|
+
return { tier: 'phrase', results };
|
|
139
|
+
// Tier 2: AND
|
|
140
|
+
const words = sanitized.split(/\s+/).filter(w => w.length > 1);
|
|
141
|
+
if (words.length > 1) {
|
|
142
|
+
const andQuery = words.join(' AND ');
|
|
143
|
+
results = tryFts(db, select, table, order, andQuery, limit);
|
|
144
|
+
if (results.length > 0)
|
|
145
|
+
return { tier: 'and', results };
|
|
146
|
+
}
|
|
147
|
+
// Tier 3: Prefix
|
|
148
|
+
const prefixQuery = words.map(w => `${w}*`).join(' AND ');
|
|
149
|
+
results = tryFts(db, select, table, order, prefixQuery, limit);
|
|
150
|
+
if (results.length > 0)
|
|
151
|
+
return { tier: 'prefix', results };
|
|
152
|
+
// Tier 4: Stemmed prefix
|
|
153
|
+
const stemmed = words.map(w => stemWord(w) + '*');
|
|
154
|
+
const stemmedQuery = stemmed.join(' AND ');
|
|
155
|
+
if (stemmedQuery !== prefixQuery) {
|
|
156
|
+
results = tryFts(db, select, table, order, stemmedQuery, limit);
|
|
157
|
+
if (results.length > 0)
|
|
158
|
+
return { tier: 'stemmed', results };
|
|
159
|
+
}
|
|
160
|
+
// Tier 5: OR
|
|
161
|
+
if (words.length > 1) {
|
|
162
|
+
const orQuery = words.join(' OR ');
|
|
163
|
+
results = tryFts(db, select, table, order, orQuery, limit);
|
|
164
|
+
if (results.length > 0)
|
|
165
|
+
return { tier: 'or', results };
|
|
166
|
+
}
|
|
167
|
+
// Tier 6: LIKE fallback
|
|
168
|
+
const baseCols = ['topic', 'rule'];
|
|
169
|
+
const likeConditions = words.map(() => `(${baseCols.map(c => `${c} LIKE ?`).join(' OR ')})`).join(' AND ');
|
|
170
|
+
const likeParams = words.flatMap(w => baseCols.map(() => `%${w}%`));
|
|
171
|
+
try {
|
|
172
|
+
const likeResults = db.all(`SELECT topic as title, COALESCE(description, rule) as body, area, jurisdiction FROM safety_rules WHERE ${likeConditions} LIMIT ?`, [...likeParams, limit]);
|
|
173
|
+
if (likeResults.length > 0)
|
|
174
|
+
return { tier: 'like', results: likeResults };
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// LIKE fallback failed
|
|
178
|
+
}
|
|
179
|
+
return { tier: 'none', results: [] };
|
|
180
|
+
}
|
|
181
|
+
function tryFts(db, select, table, order, matchExpr, limit) {
|
|
182
|
+
try {
|
|
183
|
+
return db.all(`${select} WHERE ${table} MATCH ? ${order}`, [matchExpr, limit]);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function sanitizeFtsInput(query) {
|
|
190
|
+
return query
|
|
191
|
+
.replace(/["""'',,,,]/g, '"')
|
|
192
|
+
.replace(/[^a-zA-Z0-9\s*"_\u00C0-\u024F-]/g, ' ')
|
|
193
|
+
.replace(/\s+/g, ' ')
|
|
194
|
+
.trim();
|
|
195
|
+
}
|
|
196
|
+
function stemWord(word) {
|
|
197
|
+
return word
|
|
198
|
+
.replace(/(ung|heit|keit|lich|isch|ieren|tion|ment|ness|able|ible|ous|ive|ing|ers|ed|es|er|en|ly|s)$/i, '');
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":""}
|