@ansvar/ch-livestock-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.
Files changed (113) hide show
  1. package/.github/workflows/check-freshness.yml +52 -0
  2. package/.github/workflows/ci.yml +21 -0
  3. package/.github/workflows/codeql.yml +31 -0
  4. package/.github/workflows/ghcr-build.yml +45 -0
  5. package/.github/workflows/gitleaks.yml +23 -0
  6. package/.github/workflows/ingest.yml +58 -0
  7. package/.github/workflows/publish.yml +24 -0
  8. package/CHANGELOG.md +24 -0
  9. package/CODEOWNERS +1 -0
  10. package/COVERAGE.md +62 -0
  11. package/DISCLAIMER.md +41 -0
  12. package/Dockerfile +26 -0
  13. package/LICENSE +17 -0
  14. package/PRIVACY.md +28 -0
  15. package/README.md +145 -0
  16. package/SECURITY.md +33 -0
  17. package/TOOLS.md +261 -0
  18. package/data/coverage.json +23 -0
  19. package/data/database.db +0 -0
  20. package/data/sources.yml +29 -0
  21. package/dist/db.d.ts +26 -0
  22. package/dist/db.d.ts.map +1 -0
  23. package/dist/db.js +220 -0
  24. package/dist/db.js.map +1 -0
  25. package/dist/http-server.d.ts +2 -0
  26. package/dist/http-server.d.ts.map +1 -0
  27. package/dist/http-server.js +294 -0
  28. package/dist/http-server.js.map +1 -0
  29. package/dist/jurisdiction.d.ts +18 -0
  30. package/dist/jurisdiction.d.ts.map +1 -0
  31. package/dist/jurisdiction.js +16 -0
  32. package/dist/jurisdiction.js.map +1 -0
  33. package/dist/metadata.d.ts +10 -0
  34. package/dist/metadata.d.ts.map +1 -0
  35. package/dist/metadata.js +21 -0
  36. package/dist/metadata.js.map +1 -0
  37. package/dist/server.d.ts +3 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +240 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/tools/about.d.ts +15 -0
  42. package/dist/tools/about.d.ts.map +1 -0
  43. package/dist/tools/about.js +27 -0
  44. package/dist/tools/about.js.map +1 -0
  45. package/dist/tools/check-freshness.d.ts +15 -0
  46. package/dist/tools/check-freshness.d.ts.map +1 -0
  47. package/dist/tools/check-freshness.js +26 -0
  48. package/dist/tools/check-freshness.js.map +1 -0
  49. package/dist/tools/get-breeding-guidance.d.ts +37 -0
  50. package/dist/tools/get-breeding-guidance.d.ts.map +1 -0
  51. package/dist/tools/get-breeding-guidance.js +39 -0
  52. package/dist/tools/get-breeding-guidance.js.map +1 -0
  53. package/dist/tools/get-feed-requirements.d.ts +40 -0
  54. package/dist/tools/get-feed-requirements.d.ts.map +1 -0
  55. package/dist/tools/get-feed-requirements.js +35 -0
  56. package/dist/tools/get-feed-requirements.js.map +1 -0
  57. package/dist/tools/get-housing-requirements.d.ts +39 -0
  58. package/dist/tools/get-housing-requirements.d.ts.map +1 -0
  59. package/dist/tools/get-housing-requirements.js +35 -0
  60. package/dist/tools/get-housing-requirements.js.map +1 -0
  61. package/dist/tools/get-movement-rules.d.ts +36 -0
  62. package/dist/tools/get-movement-rules.d.ts.map +1 -0
  63. package/dist/tools/get-movement-rules.js +31 -0
  64. package/dist/tools/get-movement-rules.js.map +1 -0
  65. package/dist/tools/get-stocking-density.d.ts +37 -0
  66. package/dist/tools/get-stocking-density.d.ts.map +1 -0
  67. package/dist/tools/get-stocking-density.js +35 -0
  68. package/dist/tools/get-stocking-density.js.map +1 -0
  69. package/dist/tools/get-welfare-standards.d.ts +38 -0
  70. package/dist/tools/get-welfare-standards.d.ts.map +1 -0
  71. package/dist/tools/get-welfare-standards.js +32 -0
  72. package/dist/tools/get-welfare-standards.js.map +1 -0
  73. package/dist/tools/list-sources.d.ts +18 -0
  74. package/dist/tools/list-sources.d.ts.map +1 -0
  75. package/dist/tools/list-sources.js +51 -0
  76. package/dist/tools/list-sources.js.map +1 -0
  77. package/dist/tools/search-animal-health.d.ts +28 -0
  78. package/dist/tools/search-animal-health.d.ts.map +1 -0
  79. package/dist/tools/search-animal-health.js +33 -0
  80. package/dist/tools/search-animal-health.js.map +1 -0
  81. package/dist/tools/search-livestock-guidance.d.ts +27 -0
  82. package/dist/tools/search-livestock-guidance.d.ts.map +1 -0
  83. package/dist/tools/search-livestock-guidance.js +25 -0
  84. package/dist/tools/search-livestock-guidance.js.map +1 -0
  85. package/docker-compose.yml +12 -0
  86. package/eslint.config.js +27 -0
  87. package/package.json +54 -0
  88. package/scripts/ingest.ts +553 -0
  89. package/server.json +10 -0
  90. package/src/db.ts +268 -0
  91. package/src/http-server.ts +327 -0
  92. package/src/jurisdiction.ts +30 -0
  93. package/src/metadata.ts +31 -0
  94. package/src/server.ts +264 -0
  95. package/src/tools/about.ts +28 -0
  96. package/src/tools/check-freshness.ts +42 -0
  97. package/src/tools/get-breeding-guidance.ts +53 -0
  98. package/src/tools/get-feed-requirements.ts +53 -0
  99. package/src/tools/get-housing-requirements.ts +52 -0
  100. package/src/tools/get-movement-rules.ts +45 -0
  101. package/src/tools/get-stocking-density.ts +52 -0
  102. package/src/tools/get-welfare-standards.ts +47 -0
  103. package/src/tools/list-sources.ts +65 -0
  104. package/src/tools/search-animal-health.ts +48 -0
  105. package/src/tools/search-livestock-guidance.ts +33 -0
  106. package/tests/db.test.ts +96 -0
  107. package/tests/helpers/seed-db.ts +97 -0
  108. package/tests/jurisdiction.test.ts +41 -0
  109. package/tests/tools/about.test.ts +32 -0
  110. package/tests/tools/check-freshness.test.ts +55 -0
  111. package/tests/tools/list-sources.test.ts +56 -0
  112. package/tests/tools/search-livestock-guidance.test.ts +63 -0
  113. package/tsconfig.json +19 -0
package/TOOLS.md ADDED
@@ -0,0 +1,261 @@
1
+ # Tools Reference -- Switzerland Livestock MCP
2
+
3
+ 11 tools: 3 meta-tools + 8 domain tools. All tools return JSON with a `_meta` object containing disclaimer, data age, and source URL.
4
+
5
+ ---
6
+
7
+ ## Meta Tools
8
+
9
+ ### `about`
10
+
11
+ Get server metadata: name, version, coverage, data sources, and links.
12
+
13
+ **Parameters:** None
14
+
15
+ **Returns:**
16
+ ```json
17
+ {
18
+ "name": "Switzerland Livestock MCP",
19
+ "description": "Swiss livestock regulations...",
20
+ "version": "0.1.0",
21
+ "jurisdiction": ["CH"],
22
+ "data_sources": ["TSchV", "DZV", "TVD", "Zuchtorganisationen"],
23
+ "tools_count": 11,
24
+ "links": { "homepage": "...", "repository": "...", "mcp_network": "..." },
25
+ "_meta": { "disclaimer": "...", "data_age": "...", "source_url": "..." }
26
+ }
27
+ ```
28
+
29
+ ---
30
+
31
+ ### `list_sources`
32
+
33
+ List all data sources with authority, URL, license, and freshness info.
34
+
35
+ **Parameters:** None
36
+
37
+ **Returns:** Array of source objects, each with `name`, `authority`, `official_url`, `retrieval_method`, `update_frequency`, `license`, `coverage`, `last_retrieved`.
38
+
39
+ **Example response (truncated):**
40
+ ```json
41
+ {
42
+ "sources": [
43
+ {
44
+ "name": "Tierschutzverordnung (TSchV, SR 455.1)",
45
+ "authority": "Bundesamt fuer Lebensmittelsicherheit und Veterinaerwesen (BLV)",
46
+ "official_url": "https://www.fedlex.admin.ch/eli/cc/2008/416/de",
47
+ "retrieval_method": "PDF_EXTRACT",
48
+ "update_frequency": "periodic (amended as needed)",
49
+ "license": "Swiss Federal Administration -- free reuse",
50
+ "coverage": "Minimum welfare standards per species, space requirements, housing, transport, slaughter"
51
+ }
52
+ ],
53
+ "_meta": { ... }
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ### `check_data_freshness`
60
+
61
+ Check when data was last ingested, staleness status, and how to trigger a refresh.
62
+
63
+ **Parameters:** None
64
+
65
+ **Returns:**
66
+ ```json
67
+ {
68
+ "status": "fresh",
69
+ "last_ingest": "2026-04-05",
70
+ "build_date": null,
71
+ "schema_version": "1.0",
72
+ "days_since_ingest": 0,
73
+ "staleness_threshold_days": 90,
74
+ "refresh_command": "gh workflow run ingest.yml -R ansvar-systems/ch-livestock-mcp",
75
+ "_meta": { ... }
76
+ }
77
+ ```
78
+
79
+ **Staleness logic:** If `days_since_ingest > 90`, status is `stale`. If no ingest date recorded, status is `unknown`.
80
+
81
+ ---
82
+
83
+ ## Domain Tools
84
+
85
+ ### `search_livestock_guidance`
86
+
87
+ Full-text search across all Swiss livestock topics: welfare, housing, feeding, health, transport, breeds. Uses tiered FTS5 with automatic fallback (exact phrase, AND, prefix, stemmed, OR, LIKE).
88
+
89
+ **Parameters:**
90
+
91
+ | Name | Type | Required | Description |
92
+ |------|------|----------|-------------|
93
+ | `query` | string | Yes | Free-text search query (German or English) |
94
+ | `species` | string | No | Filter by species (e.g. Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde) |
95
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
96
+ | `limit` | number | No | Max results (default: 20, max: 50) |
97
+
98
+ **Example call:**
99
+ ```json
100
+ { "query": "Mindestflaeche Milchkuh Laufstall", "species": "Rinder", "limit": 10 }
101
+ ```
102
+
103
+ **Returns:** Array of results with `title`, `body`, `species`, `category`, `relevance_rank`.
104
+
105
+ **Limitations:** Search is text-based (FTS5), not semantic. German terms produce better results than English for Swiss-specific content.
106
+
107
+ ---
108
+
109
+ ### `get_welfare_standards`
110
+
111
+ Get legal minimum welfare requirements and RAUS/BTS programme standards for a species. Based on TSchV and DZV.
112
+
113
+ **Parameters:**
114
+
115
+ | Name | Type | Required | Description |
116
+ |------|------|----------|-------------|
117
+ | `species` | string | Yes | Species: Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde |
118
+ | `production_system` | string | No | Filter: TSchV-Minimum, RAUS, BTS |
119
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
120
+
121
+ **Example call:**
122
+ ```json
123
+ { "species": "Rinder", "production_system": "RAUS" }
124
+ ```
125
+
126
+ **Returns:** Array of records with `id`, `species`, `production_system`, `requirement`, `min_space_m2`, `details`.
127
+
128
+ **Limitations:** Does not cover cantonal extensions to TSchV. Production system names must match exactly (case-insensitive).
129
+
130
+ ---
131
+
132
+ ### `get_stocking_density`
133
+
134
+ Get animals per m2 and space requirements by species, age class, and housing type. Based on TSchV Anhang 1.
135
+
136
+ **Parameters:**
137
+
138
+ | Name | Type | Required | Description |
139
+ |------|------|----------|-------------|
140
+ | `species` | string | Yes | Species: Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde |
141
+ | `age_class` | string | No | Age/weight class (e.g. Milchkuh, Kalb, Mastschwein >60kg, Legehenne) |
142
+ | `housing_type` | string | No | Housing type (e.g. Laufstall, Anbindestall, Voliere, Freilandhaltung) |
143
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
144
+
145
+ **Example call:**
146
+ ```json
147
+ { "species": "Schweine", "age_class": "Mastschwein >60kg" }
148
+ ```
149
+
150
+ **Returns:** Array of records with `id`, `species`, `age_class`, `housing_type`, `animals_per_m2`, `regulatory_minimum`.
151
+
152
+ ---
153
+
154
+ ### `get_feed_requirements`
155
+
156
+ Get nutritional requirements per species and production stage. Includes GMF (graslandbasierte Milch- und Fleischproduktion) programme details.
157
+
158
+ **Parameters:**
159
+
160
+ | Name | Type | Required | Description |
161
+ |------|------|----------|-------------|
162
+ | `species` | string | Yes | Species: Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde |
163
+ | `age_class` | string | No | Age class (e.g. Milchkuh, Aufzuchtrind, Mastschwein, Legehenne) |
164
+ | `production_stage` | string | No | Production stage (e.g. Laktation, Trockenstehend, Mast, Aufzucht) |
165
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
166
+
167
+ **Example call:**
168
+ ```json
169
+ { "species": "Rinder", "age_class": "Milchkuh", "production_stage": "Laktation" }
170
+ ```
171
+
172
+ **Returns:** Array of records with `id`, `species`, `age_class`, `production_stage`, `feed_type`, `quantity_kg_day`, `energy_mj`, `protein_g`, `notes`.
173
+
174
+ ---
175
+
176
+ ### `search_animal_health`
177
+
178
+ Search animal health topics: diseases, symptoms, prevention, regulatory reporting requirements. Uses substring matching across all text fields.
179
+
180
+ **Parameters:**
181
+
182
+ | Name | Type | Required | Description |
183
+ |------|------|----------|-------------|
184
+ | `query` | string | Yes | Search query (e.g. Salmonellen, BVD, Moderhinke, Mastitis) |
185
+ | `species` | string | No | Filter by species |
186
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
187
+
188
+ **Example call:**
189
+ ```json
190
+ { "query": "BVD", "species": "Rinder" }
191
+ ```
192
+
193
+ **Returns:** Array of records with `id`, `species`, `condition`, `symptoms`, `prevention`, `regulatory_status`, `details`.
194
+
195
+ **Limitations:** Uses substring matching (not FTS5). Short queries (1-2 chars) are filtered out.
196
+
197
+ ---
198
+
199
+ ### `get_housing_requirements`
200
+
201
+ Get detailed housing specifications: space, ventilation, flooring, temperature. Compares TSchV minimum vs. BTS standard.
202
+
203
+ **Parameters:**
204
+
205
+ | Name | Type | Required | Description |
206
+ |------|------|----------|-------------|
207
+ | `species` | string | Yes | Species: Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde |
208
+ | `age_class` | string | No | Age class (e.g. Milchkuh, Mastschwein, Legehenne) |
209
+ | `system` | string | No | Housing system (e.g. Laufstall, Anbindestall, Voliere, BTS) |
210
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
211
+
212
+ **Example call:**
213
+ ```json
214
+ { "species": "Gefluegel", "system": "Voliere" }
215
+ ```
216
+
217
+ **Returns:** Array of records with `id`, `species`, `age_class`, `system`, `space`, `ventilation`, `flooring`, `temperature`.
218
+
219
+ ---
220
+
221
+ ### `get_movement_rules`
222
+
223
+ Get TVD registration, transport regulations, standstill rules, and Soemmerung/Alpung requirements per species.
224
+
225
+ **Parameters:**
226
+
227
+ | Name | Type | Required | Description |
228
+ |------|------|----------|-------------|
229
+ | `species` | string | Yes | Species: Rinder, Schweine, Gefluegel, Schafe, Ziegen, Pferde |
230
+ | `rule_type` | string | No | Rule type: TVD, Transport, Soemmerung, Schlachtung |
231
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
232
+
233
+ **Example call:**
234
+ ```json
235
+ { "species": "Rinder", "rule_type": "TVD" }
236
+ ```
237
+
238
+ **Returns:** Array of records with `id`, `species`, `rule_type`, `description`.
239
+
240
+ ---
241
+
242
+ ### `get_breeding_guidance`
243
+
244
+ Get Swiss breed information, breeding calendars, AI (kuenstliche Besamung), genetics, and Soemmerung guidance.
245
+
246
+ **Parameters:**
247
+
248
+ | Name | Type | Required | Description |
249
+ |------|------|----------|-------------|
250
+ | `species` | string | Yes | Species: Rinder, Schweine, Schafe, Ziegen, Pferde |
251
+ | `topic` | string | No | Topic filter (e.g. Zweinutzung, Milch, Fleisch, Alp) |
252
+ | `jurisdiction` | string | No | ISO 3166-1 alpha-2 code (default: CH) |
253
+
254
+ **Example call:**
255
+ ```json
256
+ { "species": "Rinder", "topic": "Milch" }
257
+ ```
258
+
259
+ **Returns:** Array of breed records with `id`, `species`, `name`, `purpose`, `notes`. If topic filter yields no results, all breeds for the species are returned.
260
+
261
+ **Limitations:** Topic filtering uses substring matching on breed name, purpose, and notes fields.
@@ -0,0 +1,23 @@
1
+ {
2
+ "server": "ch-livestock-mcp",
3
+ "jurisdiction": "CH",
4
+ "version": "0.1.0",
5
+ "last_ingest": "2026-04-05",
6
+ "data": {
7
+ "welfare_standards": 45,
8
+ "stocking_densities": 25,
9
+ "housing_requirements": 14,
10
+ "movement_rules": 21,
11
+ "breeds": 25,
12
+ "feed_requirements": 14,
13
+ "animal_health": 13,
14
+ "search_index": 157
15
+ },
16
+ "tools": 11,
17
+ "sources": [
18
+ "TSchV — Tierschutzverordnung (BLV)",
19
+ "DZV — RAUS/BTS-Programme (BLW)",
20
+ "TVD — Tierverkehrsdatenbank (Identitas)",
21
+ "Zuchtorganisationen (Braunvieh Schweiz, swissherdbook, Suisseporcs)"
22
+ ]
23
+ }
Binary file
@@ -0,0 +1,29 @@
1
+ # Data sources for ch-livestock-mcp
2
+ sources:
3
+ - name: Tierschutzverordnung (TSchV, SR 455.1)
4
+ authority: Bundesamt fuer Lebensmittelsicherheit und Veterinaerwesen (BLV)
5
+ url: https://www.fedlex.admin.ch/eli/cc/2008/416/de
6
+ license: Swiss Federal Administration — free reuse
7
+ update_frequency: periodic (amended as needed)
8
+ last_retrieved: "2026-04-05"
9
+
10
+ - name: Direktzahlungsverordnung (DZV) — RAUS/BTS
11
+ authority: Bundesamt fuer Landwirtschaft (BLW)
12
+ url: https://www.blw.admin.ch/blw/de/home/instrumente/direktzahlungen/produktionssystembeitraege/tierwohlbeitraege.html
13
+ license: Swiss Federal Administration — free reuse
14
+ update_frequency: annual (with DZV updates)
15
+ last_retrieved: "2026-04-05"
16
+
17
+ - name: Tierverkehrsdatenbank (TVD)
18
+ authority: Identitas AG / BLV
19
+ url: https://www.identitas.ch/tvd
20
+ license: Public regulatory information
21
+ update_frequency: continuous (rules updated periodically)
22
+ last_retrieved: "2026-04-05"
23
+
24
+ - name: Zuchtorganisationen
25
+ authority: Braunvieh Schweiz, swissherdbook, Mutterkuh Schweiz, Suisseporcs, SZZV
26
+ url: https://www.braunvieh.ch
27
+ license: Public breed information
28
+ update_frequency: annual
29
+ last_retrieved: "2026-04-05"
package/dist/db.d.ts ADDED
@@ -0,0 +1,26 @@
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, species?: string): {
11
+ title: string;
12
+ body: string;
13
+ species: string;
14
+ category: string;
15
+ jurisdiction: string;
16
+ rank: number;
17
+ }[];
18
+ /**
19
+ * Tiered FTS5 search with automatic fallback.
20
+ * Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
21
+ */
22
+ export declare function tieredFtsSearch(db: Database, table: string, columns: string[], query: string, limit?: number, species?: string): {
23
+ tier: string;
24
+ results: Record<string, unknown>[];
25
+ };
26
+ //# sourceMappingURL=db.d.ts.map
@@ -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;AAqGD,wBAAgB,SAAS,CACvB,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAW,EAClB,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAG1G;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,EAClB,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,CAoEtD"}
package/dist/db.js ADDED
@@ -0,0 +1,220 @@
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 welfare_standards (
32
+ id INTEGER PRIMARY KEY,
33
+ species TEXT NOT NULL,
34
+ production_system TEXT NOT NULL,
35
+ requirement TEXT NOT NULL,
36
+ min_space_m2 REAL,
37
+ details TEXT,
38
+ language TEXT NOT NULL DEFAULT 'DE',
39
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
40
+ );
41
+
42
+ CREATE TABLE IF NOT EXISTS stocking_densities (
43
+ id INTEGER PRIMARY KEY,
44
+ species TEXT NOT NULL,
45
+ age_class TEXT NOT NULL,
46
+ housing_type TEXT NOT NULL,
47
+ animals_per_m2 REAL,
48
+ regulatory_minimum TEXT,
49
+ language TEXT NOT NULL DEFAULT 'DE',
50
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
51
+ );
52
+
53
+ CREATE TABLE IF NOT EXISTS housing_requirements (
54
+ id INTEGER PRIMARY KEY,
55
+ species TEXT NOT NULL,
56
+ age_class TEXT NOT NULL,
57
+ system TEXT NOT NULL,
58
+ space TEXT,
59
+ ventilation TEXT,
60
+ flooring TEXT,
61
+ temperature TEXT,
62
+ language TEXT NOT NULL DEFAULT 'DE',
63
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
64
+ );
65
+
66
+ CREATE TABLE IF NOT EXISTS movement_rules (
67
+ id INTEGER PRIMARY KEY,
68
+ species TEXT NOT NULL,
69
+ rule_type TEXT NOT NULL,
70
+ description TEXT NOT NULL,
71
+ language TEXT NOT NULL DEFAULT 'DE',
72
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
73
+ );
74
+
75
+ CREATE TABLE IF NOT EXISTS breeds (
76
+ id INTEGER PRIMARY KEY,
77
+ species TEXT NOT NULL,
78
+ name TEXT NOT NULL,
79
+ purpose TEXT,
80
+ notes TEXT,
81
+ language TEXT NOT NULL DEFAULT 'DE',
82
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
83
+ );
84
+
85
+ CREATE TABLE IF NOT EXISTS feed_requirements (
86
+ id INTEGER PRIMARY KEY,
87
+ species TEXT NOT NULL,
88
+ age_class TEXT NOT NULL,
89
+ production_stage TEXT,
90
+ feed_type TEXT,
91
+ quantity_kg_day REAL,
92
+ energy_mj REAL,
93
+ protein_g REAL,
94
+ notes TEXT,
95
+ language TEXT NOT NULL DEFAULT 'DE',
96
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
97
+ );
98
+
99
+ CREATE TABLE IF NOT EXISTS animal_health (
100
+ id INTEGER PRIMARY KEY,
101
+ species TEXT NOT NULL,
102
+ condition TEXT NOT NULL,
103
+ symptoms TEXT,
104
+ prevention TEXT,
105
+ regulatory_status TEXT,
106
+ details TEXT,
107
+ language TEXT NOT NULL DEFAULT 'DE',
108
+ jurisdiction TEXT NOT NULL DEFAULT 'CH'
109
+ );
110
+
111
+ CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
112
+ title, body, species, category, jurisdiction
113
+ );
114
+
115
+ CREATE TABLE IF NOT EXISTS db_metadata (
116
+ key TEXT PRIMARY KEY,
117
+ value TEXT
118
+ );
119
+
120
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('schema_version', '1.0');
121
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('mcp_name', 'Switzerland Livestock MCP');
122
+ INSERT OR IGNORE INTO db_metadata (key, value) VALUES ('jurisdiction', 'CH');
123
+ `);
124
+ }
125
+ const FTS_COLUMNS = ['title', 'body', 'species', 'category', 'jurisdiction'];
126
+ export function ftsSearch(db, query, limit = 20, species) {
127
+ const { results } = tieredFtsSearch(db, 'search_index', FTS_COLUMNS, query, limit, species);
128
+ return results;
129
+ }
130
+ /**
131
+ * Tiered FTS5 search with automatic fallback.
132
+ * Tiers: exact phrase -> AND -> prefix -> stemmed prefix -> OR -> LIKE
133
+ */
134
+ export function tieredFtsSearch(db, table, columns, query, limit = 20, species) {
135
+ const sanitized = sanitizeFtsInput(query);
136
+ if (!sanitized.trim())
137
+ return { tier: 'empty', results: [] };
138
+ const columnList = columns.join(', ');
139
+ const select = `SELECT ${columnList}, rank FROM ${table}`;
140
+ const order = `ORDER BY rank LIMIT ?`;
141
+ // Tier 1: Exact phrase
142
+ const phrase = `"${sanitized}"`;
143
+ let results = tryFts(db, select, table, order, phrase, limit, species);
144
+ if (results.length > 0)
145
+ return { tier: 'phrase', results };
146
+ // Tier 2: AND
147
+ const words = sanitized.split(/\s+/).filter(w => w.length > 1);
148
+ if (words.length > 1) {
149
+ const andQuery = words.join(' AND ');
150
+ results = tryFts(db, select, table, order, andQuery, limit, species);
151
+ if (results.length > 0)
152
+ return { tier: 'and', results };
153
+ }
154
+ // Tier 3: Prefix
155
+ const prefixQuery = words.map(w => `${w}*`).join(' AND ');
156
+ results = tryFts(db, select, table, order, prefixQuery, limit, species);
157
+ if (results.length > 0)
158
+ return { tier: 'prefix', results };
159
+ // Tier 4: Stemmed prefix
160
+ const stemmed = words.map(w => stemWord(w) + '*');
161
+ const stemmedQuery = stemmed.join(' AND ');
162
+ if (stemmedQuery !== prefixQuery) {
163
+ results = tryFts(db, select, table, order, stemmedQuery, limit, species);
164
+ if (results.length > 0)
165
+ return { tier: 'stemmed', results };
166
+ }
167
+ // Tier 5: OR
168
+ if (words.length > 1) {
169
+ const orQuery = words.join(' OR ');
170
+ results = tryFts(db, select, table, order, orQuery, limit, species);
171
+ if (results.length > 0)
172
+ return { tier: 'or', results };
173
+ }
174
+ // Tier 6: LIKE fallback on search_index content
175
+ const likeConditions = words.map(() => `(title LIKE ? OR body LIKE ? OR species LIKE ?)`).join(' AND ');
176
+ const likeParams = words.flatMap(w => [`%${w}%`, `%${w}%`, `%${w}%`]);
177
+ if (species) {
178
+ try {
179
+ const likeResults = db.all(`SELECT title, body, species, category, jurisdiction FROM search_index WHERE ${likeConditions} AND species LIKE ? LIMIT ?`, [...likeParams, `%${species}%`, limit]);
180
+ if (likeResults.length > 0)
181
+ return { tier: 'like', results: likeResults };
182
+ }
183
+ catch {
184
+ // LIKE fallback failed
185
+ }
186
+ }
187
+ try {
188
+ const likeResults = db.all(`SELECT title, body, species, category, jurisdiction FROM search_index WHERE ${likeConditions} LIMIT ?`, [...likeParams, limit]);
189
+ if (likeResults.length > 0)
190
+ return { tier: 'like', results: likeResults };
191
+ }
192
+ catch {
193
+ // LIKE fallback failed
194
+ }
195
+ return { tier: 'none', results: [] };
196
+ }
197
+ function tryFts(db, select, table, order, matchExpr, limit, species) {
198
+ try {
199
+ if (species) {
200
+ const filtered = db.all(`${select} WHERE ${table} MATCH ? ${order}`, [matchExpr, limit * 3]);
201
+ return filtered.filter(r => (r.species || '').toLowerCase().includes(species.toLowerCase())).slice(0, limit);
202
+ }
203
+ return db.all(`${select} WHERE ${table} MATCH ? ${order}`, [matchExpr, limit]);
204
+ }
205
+ catch {
206
+ return [];
207
+ }
208
+ }
209
+ function sanitizeFtsInput(query) {
210
+ return query
211
+ .replace(/["\u201C\u201D\u2018\u2019\uFF0C\u3001\uFF1B\u3002]/g, '"')
212
+ .replace(/[^a-zA-Z0-9\s*"_\u00C0-\u024F-]/g, ' ')
213
+ .replace(/\s+/g, ' ')
214
+ .trim();
215
+ }
216
+ function stemWord(word) {
217
+ return word
218
+ .replace(/(ung|heit|keit|lich|isch|ieren|tion|ment|ness|able|ible|ous|ive|ing|ers|ed|es|er|en|ly|s)$/i, '');
219
+ }
220
+ //# 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":"AAAA,OAAO,aAAa,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAUpC,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,MAAM,YAAY,GAChB,MAAM;QACN,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IAC7E,MAAM,EAAE,GAAG,IAAI,aAAa,CAAC,YAAY,CAAC,CAAC;IAE3C,EAAE,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC;IACnC,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAE/B,UAAU,CAAC,EAAE,CAAC,CAAC;IAEf,OAAO;QACL,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;YACxC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAkB,CAAC;QACzD,CAAC;QACD,GAAG,CAAI,GAAW,EAAE,SAAoB,EAAE;YACxC,OAAO,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAQ,CAAC;QAC/C,CAAC;QACD,GAAG,CAAC,GAAW,EAAE,SAAoB,EAAE;YACrC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC;QACjC,CAAC;QACD,KAAK;YACH,EAAE,CAAC,KAAK,EAAE,CAAC;QACb,CAAC;QACD,IAAI,QAAQ;YACV,OAAO,EAAE,CAAC;QACZ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,EAA0B;IAC5C,EAAE,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6FP,CAAC,CAAC;AACL,CAAC;AAED,MAAM,WAAW,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,CAAC,CAAC;AAE7E,MAAM,UAAU,SAAS,CACvB,EAAY,EACZ,KAAa,EACb,QAAgB,EAAE,EAClB,OAAgB;IAEhB,MAAM,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5F,OAAO,OAAmH,CAAC;AAC7H,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,EAAY,EACZ,KAAa,EACb,OAAiB,EACjB,KAAa,EACb,QAAgB,EAAE,EAClB,OAAgB;IAEhB,MAAM,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1C,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAE7D,MAAM,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,UAAU,UAAU,eAAe,KAAK,EAAE,CAAC;IAC1D,MAAM,KAAK,GAAG,uBAAuB,CAAC;IAEtC,uBAAuB;IACvB,MAAM,MAAM,GAAG,IAAI,SAAS,GAAG,CAAC;IAChC,IAAI,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACvE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAE3D,cAAc;IACd,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC/D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACrC,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACrE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC1D,CAAC;IAED,iBAAiB;IACjB,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1D,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAE3D,yBAAyB;IACzB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,YAAY,KAAK,WAAW,EAAE,CAAC;QACjC,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACzE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC9D,CAAC;IAED,aAAa;IACb,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACnC,OAAO,GAAG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QACpE,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACzD,CAAC;IAED,gDAAgD;IAChD,MAAM,cAAc,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,CACpC,iDAAiD,CAClD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChB,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,IAAI,OAAO,EAAE,CAAC;QACZ,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,EAAE,CAAC,GAAG,CACxB,+EAA+E,cAAc,6BAA6B,EAC1H,CAAC,GAAG,UAAU,EAAE,IAAI,OAAO,GAAG,EAAE,KAAK,CAAC,CACvC,CAAC;YACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;gBAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,uBAAuB;QACzB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,MAAM,WAAW,GAAG,EAAE,CAAC,GAAG,CACxB,+EAA+E,cAAc,UAAU,EACvG,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,CACvB,CAAC;QACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAC5E,CAAC;IAAC,MAAM,CAAC;QACP,uBAAuB;IACzB,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;AACvC,CAAC;AAED,SAAS,MAAM,CACb,EAAY,EAAE,MAAc,EAAE,KAAa,EAC3C,KAAa,EAAE,SAAiB,EAAE,KAAa,EAC/C,OAAgB;IAEhB,IAAI,CAAC;QACH,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,EAAE,CAAC,GAAG,CACrB,GAAG,MAAM,UAAU,KAAK,YAAY,KAAK,EAAE,EAC3C,CAAC,SAAS,EAAE,KAAK,GAAG,CAAC,CAAC,CACvB,CAAC;YACF,OAAQ,QAAsC,CAAC,MAAM,CACnD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAiB,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAC/E,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,EAAE,CAAC,GAAG,CACX,GAAG,MAAM,UAAU,KAAK,YAAY,KAAK,EAAE,EAC3C,CAAC,SAAS,EAAE,KAAK,CAAC,CACnB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAa;IACrC,OAAO,KAAK;SACT,OAAO,CAAC,sDAAsD,EAAE,GAAG,CAAC;SACpE,OAAO,CAAC,kCAAkC,EAAE,GAAG,CAAC;SAChD,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,OAAO,IAAI;SACR,OAAO,CAAC,6FAA6F,EAAE,EAAE,CAAC,CAAC;AAChH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=http-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":""}