etter 0.1.1__tar.gz

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 (36) hide show
  1. etter-0.1.1/LICENSE +29 -0
  2. etter-0.1.1/PKG-INFO +293 -0
  3. etter-0.1.1/README.md +258 -0
  4. etter-0.1.1/etter/__init__.py +72 -0
  5. etter-0.1.1/etter/datasources/__init__.py +20 -0
  6. etter-0.1.1/etter/datasources/composite.py +93 -0
  7. etter-0.1.1/etter/datasources/ign_bdcarto.py +521 -0
  8. etter-0.1.1/etter/datasources/location_types.py +261 -0
  9. etter-0.1.1/etter/datasources/postgis.py +533 -0
  10. etter-0.1.1/etter/datasources/protocol.py +88 -0
  11. etter-0.1.1/etter/datasources/swissnames3d.py +463 -0
  12. etter-0.1.1/etter/examples.py +297 -0
  13. etter-0.1.1/etter/exceptions.py +90 -0
  14. etter-0.1.1/etter/models.py +155 -0
  15. etter-0.1.1/etter/parser.py +362 -0
  16. etter-0.1.1/etter/prompts.py +245 -0
  17. etter-0.1.1/etter/spatial.py +254 -0
  18. etter-0.1.1/etter/spatial_config.py +288 -0
  19. etter-0.1.1/etter/validators.py +199 -0
  20. etter-0.1.1/etter.egg-info/PKG-INFO +293 -0
  21. etter-0.1.1/etter.egg-info/SOURCES.txt +34 -0
  22. etter-0.1.1/etter.egg-info/dependency_links.txt +1 -0
  23. etter-0.1.1/etter.egg-info/requires.txt +26 -0
  24. etter-0.1.1/etter.egg-info/top_level.txt +1 -0
  25. etter-0.1.1/pyproject.toml +63 -0
  26. etter-0.1.1/setup.cfg +4 -0
  27. etter-0.1.1/tests/test_context_aware_distances.py +241 -0
  28. etter-0.1.1/tests/test_contextual_distances.py +207 -0
  29. etter-0.1.1/tests/test_ign_bdcarto.py +212 -0
  30. etter-0.1.1/tests/test_models.py +206 -0
  31. etter-0.1.1/tests/test_parser_streaming.py +171 -0
  32. etter-0.1.1/tests/test_postgis_datasource.py +405 -0
  33. etter-0.1.1/tests/test_spatial.py +144 -0
  34. etter-0.1.1/tests/test_spatial_config.py +133 -0
  35. etter-0.1.1/tests/test_swissnames3d.py +273 -0
  36. etter-0.1.1/tests/test_validators.py +240 -0
etter-0.1.1/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026-present, camptocamp SA
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
etter-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,293 @@
1
+ Metadata-Version: 2.4
2
+ Name: etter
3
+ Version: 0.1.1
4
+ Summary: Natural language geographic query parsing using LLMs
5
+ Author: etter contributors
6
+ License: BSD-3-Clause
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: langchain>=1.2
11
+ Requires-Dist: pydantic>=2.12
12
+ Requires-Dist: shapely>=2.1
13
+ Requires-Dist: pyproj>=3.7
14
+ Requires-Dist: geopandas>=1.1
15
+ Requires-Dist: rapidfuzz>=3.14
16
+ Provides-Extra: postgis
17
+ Requires-Dist: sqlalchemy>=2.0; extra == "postgis"
18
+ Requires-Dist: geoalchemy2>=0.15; extra == "postgis"
19
+ Provides-Extra: dev
20
+ Requires-Dist: langchain-openai>=1.1.14; extra == "dev"
21
+ Requires-Dist: pytest>=9.0.3; extra == "dev"
22
+ Requires-Dist: pytest-cov>=7.0; extra == "dev"
23
+ Requires-Dist: ruff>=0.15; extra == "dev"
24
+ Requires-Dist: ty>=0.0.31; extra == "dev"
25
+ Requires-Dist: pdoc>=14.0; extra == "dev"
26
+ Requires-Dist: fastapi>=0.135; extra == "dev"
27
+ Requires-Dist: python-dotenv>=1.2; extra == "dev"
28
+ Requires-Dist: uvicorn>=0.44.0; extra == "dev"
29
+ Requires-Dist: mcp[cli]>=1.27.0; extra == "dev"
30
+ Requires-Dist: rich>=15.0.0; extra == "dev"
31
+ Requires-Dist: testcontainers[postgres]>=4.14.2; extra == "dev"
32
+ Requires-Dist: psycopg2-binary>=2.9; extra == "dev"
33
+ Requires-Dist: etter[postgis]; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ ![etter logo](./images/etter-logo.png)
37
+
38
+ > **[etter](https://en.wiktionary.org/wiki/Etter#German)** /ˈɛtɐ/ *n.* (Swiss German) — the boundary or enclosure marking the edge of a village or commune; a natural demarcation between settled and unsettled land.
39
+
40
+ Natural language geographic query parsing using LLMs.
41
+
42
+ ## Overview
43
+
44
+ etter transforms natural language location queries into structured geographic filters that can be used by search engines and spatial databases.
45
+ It uses Large Language Models (LLMs) to understand multilingual queries and extract spatial relationships.
46
+
47
+ **Key Principle:** etter's sole purpose is to extract the **geographic filter** from user queries. It does NOT handle feature/activity identification or search execution.
48
+
49
+ > [!TIP]
50
+ > Documentation available at [https://geoblocks.github.io/etter/](https://geoblocks.github.io/etter/)
51
+
52
+ ## Sponsorship
53
+
54
+ [![camptocamp logo](./images/camptocamp-logo.png)](https://www.camptocamp.com)
55
+
56
+ The development of this library is sponsored by [Camptocamp](https://www.camptocamp.com).
57
+
58
+ ## Features
59
+
60
+ - **Geographic Filters Only**: Extracts spatial relationships from queries, ignoring non-geographic content
61
+ - **Multilingual Support**: Parse queries in English, German, French, Italian, and more
62
+ - **Rich Spatial Relations**: Support for containment, buffer, and directional queries
63
+ - **Structured Output**: Pydantic models with full type safety
64
+ - **Streaming Support**: Real-time feedback with reasoning transparency for responsive UIs
65
+ - **Flexible Configuration**: Customizable spatial relations and confidence thresholds
66
+ - **LLM Provider Agnostic**: Works with OpenAI, Anthropic, or local models
67
+
68
+ ## What etter Does (and Doesn't Do)
69
+
70
+ **✅ etter extracts:**
71
+
72
+ - Spatial relations: "north of", "in", "near", etc.
73
+ - Reference locations: "Lausanne", "Lake Geneva", etc.
74
+ - Distance parameters: "within 5km", "around 2 miles", etc.
75
+
76
+ **❌ etter does NOT handle:**
77
+
78
+ - Feature/activity identification: "hiking", "restaurants", "hotels"
79
+ - Attribute filtering: "with children", "vegetarian", "4-star"
80
+ - Search execution or database queries
81
+
82
+ **Integration Pattern:**
83
+ Parent application handles feature/activity filtering and combines it with etter's geographic filter for complete search functionality.
84
+
85
+ ## Installation
86
+
87
+ This project uses [uv](https://docs.astral.sh/uv/) for dependency management.
88
+
89
+ ```bash
90
+ # Install dependencies
91
+ uv sync
92
+
93
+ # Or with development dependencies
94
+ uv sync --extra dev
95
+ ```
96
+
97
+ ## REPL
98
+
99
+ An interactive REPL is available for testing queries interactively:
100
+
101
+ Set your OpenAI API key before running:
102
+
103
+ ```bash
104
+ export OPENAI_API_KEY='sk-...'
105
+ uv run python repl.py
106
+ ```
107
+
108
+ ## Demo API Server
109
+
110
+ A FastAPI demo server is available that combines query parsing with geographic resolution using SwissNames3D data.
111
+
112
+ **Setup:**
113
+
114
+ Set `OPENAI_API_KEY` in your `.env` file:
115
+
116
+ ```bash
117
+ echo "OPENAI_API_KEY=sk-..." > .env
118
+ ```
119
+
120
+ **Running the server:**
121
+
122
+ ```bash
123
+ uv run uvicorn demo.main:app --port 8000 --reload
124
+ ```
125
+
126
+ The API will be available at `http://localhost:8000`.
127
+
128
+ **Making a query:**
129
+
130
+ ```bash
131
+ # Standard endpoint (returns complete result)
132
+ curl -X POST http://localhost:8000/api/query \
133
+ -H "Content-Type: application/json" \
134
+ -d '{"query": "north of Lausanne"}'
135
+
136
+ # Streaming endpoint (returns Server-Sent Events)
137
+ curl -X POST http://localhost:8000/api/query/stream \
138
+ -H "Content-Type: application/json" \
139
+ -d '{"query": "north of Lausanne"}' \
140
+ --no-buffer
141
+ ```
142
+
143
+ Response: A GeoJSON FeatureCollection containing the parsed geographic query, spatial relation, and computed search areas.
144
+
145
+ The web UI at `http://localhost:8000` includes a toggle to enable streaming mode with real-time reasoning display.
146
+
147
+ ## Quick Start
148
+
149
+ ```python
150
+ from langchain_openai import ChatOpenAI
151
+ from etter import GeoFilterParser
152
+ import os
153
+
154
+ # Initialize LLM
155
+ llm = ChatOpenAI(
156
+ model="gpt-4o",
157
+ temperature=0,
158
+ api_key=os.getenv("OPENAI_API_KEY")
159
+ )
160
+
161
+ # Initialize parser
162
+ parser = GeoFilterParser(
163
+ llm=llm,
164
+ confidence_threshold=0.6,
165
+ strict_mode=False
166
+ )
167
+
168
+ # Strict mode - raises error on low confidence
169
+ parser = GeoFilterParser(
170
+ llm=llm,
171
+ confidence_threshold=0.8,
172
+ strict_mode=True
173
+ )
174
+ ```
175
+
176
+ ### Custom Spatial Relations
177
+
178
+ ```python
179
+ from etter import SpatialRelationConfig, RelationConfig
180
+
181
+ config = SpatialRelationConfig()
182
+ config.register_relation(RelationConfig(
183
+ name="close_to",
184
+ category="buffer",
185
+ description="Very close proximity",
186
+ default_distance_m=1000,
187
+ buffer_from="center"
188
+ ))
189
+
190
+ parser = GeoFilterParser(spatial_config=config)
191
+ ```
192
+
193
+ ## API Reference
194
+
195
+ ### GeoFilterParser
196
+
197
+ Main class for parsing queries.
198
+
199
+ **Methods:**
200
+
201
+ - `parse(query: str) -> GeoQuery`: Parse a single query
202
+ - `parse_stream(query: str) -> AsyncGenerator[dict]`: Parse with streaming events
203
+ - `parse_batch(queries: List[str]) -> List[GeoQuery]`: Parse multiple queries
204
+ - `get_available_relations(category: Optional[str]) -> List[str]`: List available relations
205
+ - `describe_relation(name: str) -> str`: Get relation description
206
+
207
+ ### GeoQuery
208
+
209
+ Structured output model representing the parsed geographic filter.
210
+
211
+ **Attributes:**
212
+
213
+ - `query_type`: Type of query (simple, compound, split, boolean)
214
+ - `spatial_relation`: Spatial relationship (e.g., "north_of", "in", "near")
215
+ - `reference_location`: Reference location (e.g., "Lausanne")
216
+ - `buffer_config`: Buffer parameters (optional)
217
+ - `confidence_breakdown`: Confidence scores
218
+ - `original_query`: Original input text
219
+
220
+ **Note:** etter is fully implemented with three integrated layers: parsing, geographic resolution via datasources, and spatial operations. The demo API shows a complete end-to-end workflow that resolves locations and computes search areas.
221
+
222
+ ## Available Spatial Relations
223
+
224
+ ### Containment
225
+
226
+ - `in`: Exact boundary matching
227
+
228
+ ### Buffer/Proximity
229
+
230
+ - `near`: Proximity with context-aware distance (default 5km, LLM infers based on activity, feature scale, and intent)
231
+ - `on_shores_of`: 1km ring buffer (excludes water body)
232
+ - `along`: 500m buffer for linear features
233
+ - `left_bank`, `right_bank`: Buffer on one side of a linear feature (river, road) relative to its flow direction
234
+ - `in_the_heart_of`: Erosion for central areas (default -500m, LLM infers based on area size)
235
+
236
+ ### Directional
237
+
238
+ - **Cardinal**: `north_of`, `south_of`, `east_of`, `west_of`: 10km sector (90° each)
239
+ - **Diagonal**: `northeast_of`, `southeast_of`, `southwest_of`, `northwest_of`: 10km sector (90° each)
240
+
241
+ ## Error Handling
242
+
243
+ ```python
244
+ from etter import ParsingError, UnknownRelationError, LowConfidenceError
245
+
246
+ try:
247
+ result = parser.parse("some query")
248
+ except ParsingError as e:
249
+ print(f"Failed to parse: {e}")
250
+ print(f"Raw LLM response: {e.raw_response}")
251
+ except UnknownRelationError as e:
252
+ print(f"Unknown relation: {e.relation_name}")
253
+ except LowConfidenceError as e:
254
+ print(f"Low confidence: {e.confidence}")
255
+ print(f"Reasoning: {e.reasoning}")
256
+ ```
257
+
258
+ ## Demo Examples
259
+
260
+ Here are some good example queries to try with the demo application:
261
+
262
+ - `walk in the Gros-de-Vaud`
263
+ - `on the shores of the lac Morat`
264
+ - `near Lausanne`
265
+ - `south west of Lausanne`
266
+ - `5km north of Lausanne`
267
+ - `walking distance from Zurich main railway station`
268
+ - `15 min biking from Zurich main railway station`
269
+ - `along l'Orbe`
270
+ - `2km right bank of the Rhône`
271
+
272
+ ## Architecture
273
+
274
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed system design.
275
+
276
+ ## Development
277
+
278
+ ```bash
279
+ # Install dev dependencies
280
+ uv sync --extra dev
281
+
282
+ # Run tests
283
+ uv run pytest
284
+
285
+ # Format code
286
+ uv run ruff format
287
+
288
+ # Linting
289
+ uv run ruff check
290
+
291
+ # Type checking
292
+ uv run ty check
293
+ ```
etter-0.1.1/README.md ADDED
@@ -0,0 +1,258 @@
1
+ ![etter logo](./images/etter-logo.png)
2
+
3
+ > **[etter](https://en.wiktionary.org/wiki/Etter#German)** /ˈɛtɐ/ *n.* (Swiss German) — the boundary or enclosure marking the edge of a village or commune; a natural demarcation between settled and unsettled land.
4
+
5
+ Natural language geographic query parsing using LLMs.
6
+
7
+ ## Overview
8
+
9
+ etter transforms natural language location queries into structured geographic filters that can be used by search engines and spatial databases.
10
+ It uses Large Language Models (LLMs) to understand multilingual queries and extract spatial relationships.
11
+
12
+ **Key Principle:** etter's sole purpose is to extract the **geographic filter** from user queries. It does NOT handle feature/activity identification or search execution.
13
+
14
+ > [!TIP]
15
+ > Documentation available at [https://geoblocks.github.io/etter/](https://geoblocks.github.io/etter/)
16
+
17
+ ## Sponsorship
18
+
19
+ [![camptocamp logo](./images/camptocamp-logo.png)](https://www.camptocamp.com)
20
+
21
+ The development of this library is sponsored by [Camptocamp](https://www.camptocamp.com).
22
+
23
+ ## Features
24
+
25
+ - **Geographic Filters Only**: Extracts spatial relationships from queries, ignoring non-geographic content
26
+ - **Multilingual Support**: Parse queries in English, German, French, Italian, and more
27
+ - **Rich Spatial Relations**: Support for containment, buffer, and directional queries
28
+ - **Structured Output**: Pydantic models with full type safety
29
+ - **Streaming Support**: Real-time feedback with reasoning transparency for responsive UIs
30
+ - **Flexible Configuration**: Customizable spatial relations and confidence thresholds
31
+ - **LLM Provider Agnostic**: Works with OpenAI, Anthropic, or local models
32
+
33
+ ## What etter Does (and Doesn't Do)
34
+
35
+ **✅ etter extracts:**
36
+
37
+ - Spatial relations: "north of", "in", "near", etc.
38
+ - Reference locations: "Lausanne", "Lake Geneva", etc.
39
+ - Distance parameters: "within 5km", "around 2 miles", etc.
40
+
41
+ **❌ etter does NOT handle:**
42
+
43
+ - Feature/activity identification: "hiking", "restaurants", "hotels"
44
+ - Attribute filtering: "with children", "vegetarian", "4-star"
45
+ - Search execution or database queries
46
+
47
+ **Integration Pattern:**
48
+ Parent application handles feature/activity filtering and combines it with etter's geographic filter for complete search functionality.
49
+
50
+ ## Installation
51
+
52
+ This project uses [uv](https://docs.astral.sh/uv/) for dependency management.
53
+
54
+ ```bash
55
+ # Install dependencies
56
+ uv sync
57
+
58
+ # Or with development dependencies
59
+ uv sync --extra dev
60
+ ```
61
+
62
+ ## REPL
63
+
64
+ An interactive REPL is available for testing queries interactively:
65
+
66
+ Set your OpenAI API key before running:
67
+
68
+ ```bash
69
+ export OPENAI_API_KEY='sk-...'
70
+ uv run python repl.py
71
+ ```
72
+
73
+ ## Demo API Server
74
+
75
+ A FastAPI demo server is available that combines query parsing with geographic resolution using SwissNames3D data.
76
+
77
+ **Setup:**
78
+
79
+ Set `OPENAI_API_KEY` in your `.env` file:
80
+
81
+ ```bash
82
+ echo "OPENAI_API_KEY=sk-..." > .env
83
+ ```
84
+
85
+ **Running the server:**
86
+
87
+ ```bash
88
+ uv run uvicorn demo.main:app --port 8000 --reload
89
+ ```
90
+
91
+ The API will be available at `http://localhost:8000`.
92
+
93
+ **Making a query:**
94
+
95
+ ```bash
96
+ # Standard endpoint (returns complete result)
97
+ curl -X POST http://localhost:8000/api/query \
98
+ -H "Content-Type: application/json" \
99
+ -d '{"query": "north of Lausanne"}'
100
+
101
+ # Streaming endpoint (returns Server-Sent Events)
102
+ curl -X POST http://localhost:8000/api/query/stream \
103
+ -H "Content-Type: application/json" \
104
+ -d '{"query": "north of Lausanne"}' \
105
+ --no-buffer
106
+ ```
107
+
108
+ Response: A GeoJSON FeatureCollection containing the parsed geographic query, spatial relation, and computed search areas.
109
+
110
+ The web UI at `http://localhost:8000` includes a toggle to enable streaming mode with real-time reasoning display.
111
+
112
+ ## Quick Start
113
+
114
+ ```python
115
+ from langchain_openai import ChatOpenAI
116
+ from etter import GeoFilterParser
117
+ import os
118
+
119
+ # Initialize LLM
120
+ llm = ChatOpenAI(
121
+ model="gpt-4o",
122
+ temperature=0,
123
+ api_key=os.getenv("OPENAI_API_KEY")
124
+ )
125
+
126
+ # Initialize parser
127
+ parser = GeoFilterParser(
128
+ llm=llm,
129
+ confidence_threshold=0.6,
130
+ strict_mode=False
131
+ )
132
+
133
+ # Strict mode - raises error on low confidence
134
+ parser = GeoFilterParser(
135
+ llm=llm,
136
+ confidence_threshold=0.8,
137
+ strict_mode=True
138
+ )
139
+ ```
140
+
141
+ ### Custom Spatial Relations
142
+
143
+ ```python
144
+ from etter import SpatialRelationConfig, RelationConfig
145
+
146
+ config = SpatialRelationConfig()
147
+ config.register_relation(RelationConfig(
148
+ name="close_to",
149
+ category="buffer",
150
+ description="Very close proximity",
151
+ default_distance_m=1000,
152
+ buffer_from="center"
153
+ ))
154
+
155
+ parser = GeoFilterParser(spatial_config=config)
156
+ ```
157
+
158
+ ## API Reference
159
+
160
+ ### GeoFilterParser
161
+
162
+ Main class for parsing queries.
163
+
164
+ **Methods:**
165
+
166
+ - `parse(query: str) -> GeoQuery`: Parse a single query
167
+ - `parse_stream(query: str) -> AsyncGenerator[dict]`: Parse with streaming events
168
+ - `parse_batch(queries: List[str]) -> List[GeoQuery]`: Parse multiple queries
169
+ - `get_available_relations(category: Optional[str]) -> List[str]`: List available relations
170
+ - `describe_relation(name: str) -> str`: Get relation description
171
+
172
+ ### GeoQuery
173
+
174
+ Structured output model representing the parsed geographic filter.
175
+
176
+ **Attributes:**
177
+
178
+ - `query_type`: Type of query (simple, compound, split, boolean)
179
+ - `spatial_relation`: Spatial relationship (e.g., "north_of", "in", "near")
180
+ - `reference_location`: Reference location (e.g., "Lausanne")
181
+ - `buffer_config`: Buffer parameters (optional)
182
+ - `confidence_breakdown`: Confidence scores
183
+ - `original_query`: Original input text
184
+
185
+ **Note:** etter is fully implemented with three integrated layers: parsing, geographic resolution via datasources, and spatial operations. The demo API shows a complete end-to-end workflow that resolves locations and computes search areas.
186
+
187
+ ## Available Spatial Relations
188
+
189
+ ### Containment
190
+
191
+ - `in`: Exact boundary matching
192
+
193
+ ### Buffer/Proximity
194
+
195
+ - `near`: Proximity with context-aware distance (default 5km, LLM infers based on activity, feature scale, and intent)
196
+ - `on_shores_of`: 1km ring buffer (excludes water body)
197
+ - `along`: 500m buffer for linear features
198
+ - `left_bank`, `right_bank`: Buffer on one side of a linear feature (river, road) relative to its flow direction
199
+ - `in_the_heart_of`: Erosion for central areas (default -500m, LLM infers based on area size)
200
+
201
+ ### Directional
202
+
203
+ - **Cardinal**: `north_of`, `south_of`, `east_of`, `west_of`: 10km sector (90° each)
204
+ - **Diagonal**: `northeast_of`, `southeast_of`, `southwest_of`, `northwest_of`: 10km sector (90° each)
205
+
206
+ ## Error Handling
207
+
208
+ ```python
209
+ from etter import ParsingError, UnknownRelationError, LowConfidenceError
210
+
211
+ try:
212
+ result = parser.parse("some query")
213
+ except ParsingError as e:
214
+ print(f"Failed to parse: {e}")
215
+ print(f"Raw LLM response: {e.raw_response}")
216
+ except UnknownRelationError as e:
217
+ print(f"Unknown relation: {e.relation_name}")
218
+ except LowConfidenceError as e:
219
+ print(f"Low confidence: {e.confidence}")
220
+ print(f"Reasoning: {e.reasoning}")
221
+ ```
222
+
223
+ ## Demo Examples
224
+
225
+ Here are some good example queries to try with the demo application:
226
+
227
+ - `walk in the Gros-de-Vaud`
228
+ - `on the shores of the lac Morat`
229
+ - `near Lausanne`
230
+ - `south west of Lausanne`
231
+ - `5km north of Lausanne`
232
+ - `walking distance from Zurich main railway station`
233
+ - `15 min biking from Zurich main railway station`
234
+ - `along l'Orbe`
235
+ - `2km right bank of the Rhône`
236
+
237
+ ## Architecture
238
+
239
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed system design.
240
+
241
+ ## Development
242
+
243
+ ```bash
244
+ # Install dev dependencies
245
+ uv sync --extra dev
246
+
247
+ # Run tests
248
+ uv run pytest
249
+
250
+ # Format code
251
+ uv run ruff format
252
+
253
+ # Linting
254
+ uv run ruff check
255
+
256
+ # Type checking
257
+ uv run ty check
258
+ ```
@@ -0,0 +1,72 @@
1
+ """
2
+ etter - Natural language geographic query parsing using LLMs.
3
+
4
+ Parse location queries into structured geographic queries using LLM.
5
+ """
6
+
7
+ from importlib.metadata import PackageNotFoundError, version
8
+
9
+ try:
10
+ __version__ = version("etter")
11
+ except PackageNotFoundError: # running from source without install
12
+ __version__ = "unknown"
13
+
14
+ # Main API
15
+ # Exceptions
16
+ # Datasources
17
+ from .datasources import CompositeDataSource, GeoDataSource, IGNBDCartoSource, PostGISDataSource, SwissNames3DSource
18
+ from .exceptions import (
19
+ GeoFilterError,
20
+ LowConfidenceError,
21
+ LowConfidenceWarning,
22
+ ParsingError,
23
+ UnknownRelationError,
24
+ ValidationError,
25
+ )
26
+
27
+ # Models (for type hints and result access)
28
+ from .models import (
29
+ BufferConfig,
30
+ ConfidenceLevel,
31
+ ConfidenceScore,
32
+ GeoQuery,
33
+ ReferenceLocation,
34
+ SpatialRelation,
35
+ )
36
+ from .parser import GeoFilterParser
37
+
38
+ # Spatial operations
39
+ from .spatial import apply_spatial_relation
40
+
41
+ # Configuration
42
+ from .spatial_config import RelationConfig, SpatialRelationConfig
43
+
44
+ __all__ = [
45
+ # Main API
46
+ "GeoFilterParser",
47
+ # Models
48
+ "GeoQuery",
49
+ "SpatialRelation",
50
+ "ReferenceLocation",
51
+ "BufferConfig",
52
+ "ConfidenceScore",
53
+ "ConfidenceLevel",
54
+ # Configuration
55
+ "SpatialRelationConfig",
56
+ "RelationConfig",
57
+ # Exceptions
58
+ "GeoFilterError",
59
+ "ParsingError",
60
+ "ValidationError",
61
+ "UnknownRelationError",
62
+ "LowConfidenceError",
63
+ "LowConfidenceWarning",
64
+ # Datasources
65
+ "GeoDataSource",
66
+ "SwissNames3DSource",
67
+ "IGNBDCartoSource",
68
+ "CompositeDataSource",
69
+ "PostGISDataSource",
70
+ # Spatial
71
+ "apply_spatial_relation",
72
+ ]
@@ -0,0 +1,20 @@
1
+ """
2
+ Geographic data source layer for resolving location names to geometries.
3
+
4
+ Provides a Protocol-based interface (GeoDataSource) and concrete implementations:
5
+ SwissNames3DSource, IGNBDCartoSource, PostGISDataSource, and CompositeDataSource.
6
+ """
7
+
8
+ from .composite import CompositeDataSource
9
+ from .ign_bdcarto import IGNBDCartoSource
10
+ from .postgis import PostGISDataSource
11
+ from .protocol import GeoDataSource
12
+ from .swissnames3d import SwissNames3DSource
13
+
14
+ __all__ = [
15
+ "CompositeDataSource",
16
+ "GeoDataSource",
17
+ "IGNBDCartoSource",
18
+ "PostGISDataSource",
19
+ "SwissNames3DSource",
20
+ ]