core-lens 0.1.dev136__tar.gz → 0.1.dev140__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.
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/PKG-INFO +1 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_aoi.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_export.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_polars_utils.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_result.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_schema.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_season.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_spatial.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/bench_view.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/export.md +8 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/intro.md +3 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/queries.md +3 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/quickstart.md +4 -4
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/examples/demo_mws.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/examples/demo_tehsil.py +1 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/aoi.py +17 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/entity.py +18 -3
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/namespaces/plot.py +12 -5
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/namespaces/stats.py +15 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/result.py +9 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/view.py +8 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/entities/mws.py +25 -10
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/entities/tehsil.py +20 -8
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/entities/waterbody.py +20 -8
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/export/formats.py +5 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/schema/detection.py +10 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/schema/profile.py +2 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/utils/paths.py +4 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/utils/polars_utils.py +3 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/utils/season.py +7 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/utils/spatial.py +25 -4
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/conftest.py +4 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_aoi.py +3 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_export.py +2 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_result.py +1 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_schema_detection.py +1 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_schema_profile.py +1 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_view.py +3 -1
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/ISSUE_TEMPLATE/blank-proposal.yaml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/ISSUE_TEMPLATE/feature-request.yaml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.gitignore +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.gitmessage +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/.python-version +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/LICENSE +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/README.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/SKILLS.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/README.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/benchmarks/run_all.sh +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/Makefile +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/make.bat +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/logging.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/plots.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/docs/source/stats.md +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/pyproject.toml +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/entities/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_plot.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_spatial.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/tests/unit/test_stats.py +0 -0
- {core_lens-0.1.dev136 → core_lens-0.1.dev140}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-lens
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.dev140
|
|
4
4
|
Summary: Query, analyse, and visualise CoreStack's microwatershed and Earth science data through a clean, composable Python API.
|
|
5
5
|
Project-URL: Homepage, https://github.com/ApoorvaKashyap/core-lens
|
|
6
6
|
Project-URL: Issues, https://github.com/ApoorvaKashyap/core-lens/issues
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
bench_polars_utils.py — Benchmark for core_lens.utils.polars_utils
|
|
1
|
+
"""bench_polars_utils.py — Benchmark for core_lens.utils.polars_utils
|
|
3
2
|
===================================================================
|
|
4
3
|
Targets:
|
|
5
4
|
- _gpu_available() module-level probe + caching
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
bench_spatial.py — Benchmark for core_lens.utils.spatial
|
|
1
|
+
"""bench_spatial.py — Benchmark for core_lens.utils.spatial
|
|
3
2
|
=========================================================
|
|
4
3
|
Targets:
|
|
5
4
|
- build_bbox_index() cold build (Shapely decode path) vs pre-computed bbox path
|
|
@@ -41,7 +41,13 @@ geoparquet(
|
|
|
41
41
|
|
|
42
42
|
# Export to GeoJSON
|
|
43
43
|
geojson(spatial_res, "output.json")
|
|
44
|
+
|
|
45
|
+
# Export to newline-delimited GeoJSON (GeoJSONSeq)
|
|
46
|
+
geojson(spatial_res, "output.ndjson", driver="GeoJSONSeq")
|
|
44
47
|
```
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
```{note}
|
|
50
|
+
Exporting to **GeoParquet** uses DuckDB to cast WKB geometries to proper spatial types via the `spatial` extension. CoreLens handles the installation and loading of the extension seamlessly under the hood.
|
|
51
|
+
|
|
52
|
+
Exporting to **GeoJSON** is fully natively streamed directly from Polars and Shapely without invoking GDAL or DuckDB, making it extremely fast for large datasets. Driver-specific kwargs for GeoJSON are ignored.
|
|
53
|
+
```
|
|
@@ -13,4 +13,6 @@ CoreLens provides a unified interface over microwatersheds, administrative bound
|
|
|
13
13
|
- **Temporal & Seasonal Awareness**: Native support for agronomic seasons (Kharif, Rabi, Zaid) and time-range filtering.
|
|
14
14
|
- **Spatial Statistics & Analysis**: Built-in methods for anomaly detection, spatial similarity, temporal correlation, and hypothesis testing.
|
|
15
15
|
- **Interactive Visualisation**: Generate interactive maps using Lonboard with zero-copy GeoArrow rendering directly from Polars, and timeseries/scatter plots using Bokeh.
|
|
16
|
-
- **
|
|
16
|
+
- **Cloud & Local Data**: Native support for reading Parquet data from local filesystems or directly from cloud storage (e.g., `s3://`).
|
|
17
|
+
- **High-Performance Exports**: Export results directly to Parquet, JSON, CSV, or spatially-enabled GeoParquet and GeoJSON (with a fast streaming writer).
|
|
18
|
+
- **Advanced Caching**: Transparently caches boundaries and pre-computes spatial indices (via on-disk sidecars) to achieve sub-second query initialisation times.
|
|
@@ -58,5 +58,6 @@ joined_view = aoi.mws.spatial_join(
|
|
|
58
58
|
# res = joined_view.annual
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
```{note}
|
|
62
|
+
Cross-entity spatial join execution (materialisation) is currently under development and will be added in a subsequent release. Calling materialisation methods (like `.annual`, `.static`, or `.fortnightly`) on a joined view will raise a `NotImplementedError` in the current version.
|
|
63
|
+
```
|
|
@@ -25,18 +25,18 @@ AoI.register(MWSEntity)
|
|
|
25
25
|
AoI.register(TehsilEntity)
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Create an `AoI` (Area of Interest) by specifying the `data_root` and your boundary (either by name, bounding box, or Shapely geometry):
|
|
28
|
+
Create an `AoI` (Area of Interest) by specifying the `data_root` (which can be a local path or a cloud URI like `s3://bucket/data`) and your boundary (either by name, bounding box, or Shapely geometry):
|
|
29
29
|
|
|
30
30
|
```python
|
|
31
|
-
# Create an AoI scoped to a specific Tehsil
|
|
31
|
+
# Create an AoI scoped to a specific Tehsil using a local path
|
|
32
32
|
aoi = AoI(
|
|
33
33
|
data_root="/path/to/data",
|
|
34
34
|
tehsil="Pangi"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
# Or scope it directly to a specific list of entity IDs
|
|
37
|
+
# Or scope it directly to a specific list of entity IDs using an S3 bucket
|
|
38
38
|
aoi_specific = AoI(
|
|
39
|
-
data_root="/
|
|
39
|
+
data_root="s3://my-cloud-bucket/core-lens-data",
|
|
40
40
|
mws_id=["13_001", "13_002"]
|
|
41
41
|
)
|
|
42
42
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
demo_mws.py — core_lens MWSEntity capability demonstration
|
|
1
|
+
"""demo_mws.py — core_lens MWSEntity capability demonstration
|
|
3
2
|
=========================================================
|
|
4
3
|
Walks through every meaningful operation available on a spatiotemporal
|
|
5
4
|
entity: registration, AoI construction, filtering, materialisation,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
"""
|
|
2
|
-
demo_tehsil.py — core_lens TehsilEntity capability demonstration
|
|
1
|
+
"""demo_tehsil.py — core_lens TehsilEntity capability demonstration
|
|
3
2
|
=================================================================
|
|
4
3
|
Walks through every meaningful operation available on a static-only
|
|
5
4
|
entity: registration, AoI construction, filtering, materialisation,
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1, '
|
|
21
|
+
__version__ = version = '0.1.dev140'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev140')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -47,6 +47,7 @@ class SeasonConfig:
|
|
|
47
47
|
kharif: Kharif (monsoon) season range as ``(start_MM-DD, end_MM-DD)``.
|
|
48
48
|
rabi: Rabi (winter) season range.
|
|
49
49
|
zaid: Zaid (summer) season range.
|
|
50
|
+
|
|
50
51
|
"""
|
|
51
52
|
|
|
52
53
|
kharif: tuple[str, str] = ("07-01", "10-31")
|
|
@@ -76,6 +77,7 @@ class SeasonConfig:
|
|
|
76
77
|
|
|
77
78
|
Returns:
|
|
78
79
|
str: ``"kharif"``, ``"rabi"``, or ``"zaid"``.
|
|
80
|
+
|
|
79
81
|
"""
|
|
80
82
|
md = f"{d.month:02d}-{d.day:02d}"
|
|
81
83
|
for name in ("kharif", "rabi", "zaid"):
|
|
@@ -115,6 +117,7 @@ def _default_season_config() -> SeasonConfig:
|
|
|
115
117
|
|
|
116
118
|
Returns:
|
|
117
119
|
SeasonConfig: The shared default instance.
|
|
120
|
+
|
|
118
121
|
"""
|
|
119
122
|
global _DEFAULT_SEASON_CONFIG
|
|
120
123
|
if _DEFAULT_SEASON_CONFIG is None:
|
|
@@ -167,6 +170,7 @@ def _cached_resolve_boundary(
|
|
|
167
170
|
|
|
168
171
|
Returns:
|
|
169
172
|
tuple: ``(geometry_wkb, entity_name, key_rows)``.
|
|
173
|
+
|
|
170
174
|
"""
|
|
171
175
|
from core_lens.base.entity import _entity_name as _ename
|
|
172
176
|
|
|
@@ -284,6 +288,7 @@ class AoI:
|
|
|
284
288
|
data_root: Resolved path to the data directory.
|
|
285
289
|
geometry: Shapely polygon representing the AoI boundary.
|
|
286
290
|
seasons: :class:`SeasonConfig` in effect for this AoI.
|
|
291
|
+
|
|
287
292
|
"""
|
|
288
293
|
|
|
289
294
|
def __init__(
|
|
@@ -331,6 +336,7 @@ class AoI:
|
|
|
331
336
|
one boundary mode is used simultaneously.
|
|
332
337
|
:class:`~core_lens.base.EntityValidationError`: If a boundary entity referenced in
|
|
333
338
|
``entity_kwargs`` is not registered.
|
|
339
|
+
|
|
334
340
|
"""
|
|
335
341
|
# Preserve cloud URIs as-is; resolve local paths to absolute.
|
|
336
342
|
data_root_str = str(data_root)
|
|
@@ -395,6 +401,7 @@ class AoI:
|
|
|
395
401
|
|
|
396
402
|
Returns:
|
|
397
403
|
str: ``"kharif"``, ``"rabi"``, or ``"zaid"``.
|
|
404
|
+
|
|
398
405
|
"""
|
|
399
406
|
return self.seasons.season_for(date.today())
|
|
400
407
|
|
|
@@ -404,6 +411,7 @@ class AoI:
|
|
|
404
411
|
|
|
405
412
|
Returns:
|
|
406
413
|
int: Current year as an integer.
|
|
414
|
+
|
|
407
415
|
"""
|
|
408
416
|
return date.today().year
|
|
409
417
|
|
|
@@ -416,6 +424,7 @@ class AoI:
|
|
|
416
424
|
|
|
417
425
|
Returns:
|
|
418
426
|
Any: A Lonboard Map object.
|
|
427
|
+
|
|
419
428
|
"""
|
|
420
429
|
import lonboard
|
|
421
430
|
import geopandas as gpd
|
|
@@ -476,6 +485,7 @@ class AoI:
|
|
|
476
485
|
Raises:
|
|
477
486
|
KeyError: If *name* is not in :data:`_REGISTRY`.
|
|
478
487
|
:class:`~core_lens.base.EntityValidationError`: If validation fails.
|
|
488
|
+
|
|
479
489
|
"""
|
|
480
490
|
if name not in self._entity_instances:
|
|
481
491
|
entity_cls = _REGISTRY[name]
|
|
@@ -506,6 +516,7 @@ class AoI:
|
|
|
506
516
|
Raises:
|
|
507
517
|
:class:`~core_lens.base.EntityValidationError`: If any registered
|
|
508
518
|
entity fails validation.
|
|
519
|
+
|
|
509
520
|
"""
|
|
510
521
|
for name in _REGISTRY:
|
|
511
522
|
self._get_entity(name)
|
|
@@ -562,6 +573,7 @@ class AoI:
|
|
|
562
573
|
Raises:
|
|
563
574
|
:class:`~core_lens.base.EntityValidationError`: If no registered entity can satisfy the filters.
|
|
564
575
|
ValueError: If the filters match zero rows.
|
|
576
|
+
|
|
565
577
|
"""
|
|
566
578
|
logger.debug("Resolving named boundary using kwargs: {}", entity_kwargs)
|
|
567
579
|
|
|
@@ -625,6 +637,7 @@ class AoI:
|
|
|
625
637
|
Raises:
|
|
626
638
|
:class:`~core_lens.base.EntityValidationError`: If any validation check
|
|
627
639
|
fails (absolute-path entities only at register time).
|
|
640
|
+
|
|
628
641
|
"""
|
|
629
642
|
import pathlib as _pathlib
|
|
630
643
|
|
|
@@ -647,6 +660,7 @@ class AoI:
|
|
|
647
660
|
|
|
648
661
|
Args:
|
|
649
662
|
entity_cls (type[BaseEntity]): The entity class to remove.
|
|
663
|
+
|
|
650
664
|
"""
|
|
651
665
|
name = _entity_name(entity_cls)
|
|
652
666
|
logger.info("Deregistering entity class: {}", name)
|
|
@@ -658,6 +672,7 @@ class AoI:
|
|
|
658
672
|
|
|
659
673
|
Returns:
|
|
660
674
|
list[str]: A sorted list of entity name strings.
|
|
675
|
+
|
|
661
676
|
"""
|
|
662
677
|
return sorted(_REGISTRY)
|
|
663
678
|
|
|
@@ -695,6 +710,7 @@ def _validate_entity_paths(entity: BaseEntity, name: str) -> None:
|
|
|
695
710
|
|
|
696
711
|
Raises:
|
|
697
712
|
EntityValidationError: If any path does not exist.
|
|
713
|
+
|
|
698
714
|
"""
|
|
699
715
|
# --- Static path existence check ----------------------------------------
|
|
700
716
|
try:
|
|
@@ -760,6 +776,7 @@ def _validate_entity(entity: BaseEntity, name: str) -> None:
|
|
|
760
776
|
|
|
761
777
|
Raises:
|
|
762
778
|
EntityValidationError: If any validation check fails.
|
|
779
|
+
|
|
763
780
|
"""
|
|
764
781
|
# Fast path-only check first.
|
|
765
782
|
_validate_entity_paths(entity, name)
|
|
@@ -65,6 +65,7 @@ def _so_key(storage_options: dict[str, Any]) -> tuple[tuple[str, Any], ...]:
|
|
|
65
65
|
|
|
66
66
|
Returns:
|
|
67
67
|
tuple[tuple[str, Any], ...]: A sorted tuple of ``(key, value)`` pairs.
|
|
68
|
+
|
|
68
69
|
"""
|
|
69
70
|
return tuple(sorted(storage_options.items()))
|
|
70
71
|
|
|
@@ -85,6 +86,7 @@ def _cached_detect(
|
|
|
85
86
|
|
|
86
87
|
Returns:
|
|
87
88
|
SchemaProfile: Detected schema profile for the given entity paths.
|
|
89
|
+
|
|
88
90
|
"""
|
|
89
91
|
from core_lens.schema.detection import detect
|
|
90
92
|
|
|
@@ -114,6 +116,7 @@ def _cached_build_index(
|
|
|
114
116
|
|
|
115
117
|
Returns:
|
|
116
118
|
pl.DataFrame: The bounding-box index DataFrame.
|
|
119
|
+
|
|
117
120
|
"""
|
|
118
121
|
logger.debug("Building bbox index for {} (cache miss)", static_path)
|
|
119
122
|
return build_bbox_index(
|
|
@@ -127,7 +130,7 @@ def _cached_build_index(
|
|
|
127
130
|
|
|
128
131
|
|
|
129
132
|
class BaseEntity(ABC):
|
|
130
|
-
"""Abstract base class for every entity in the core_lens plugin system.
|
|
133
|
+
r"""Abstract base class for every entity in the core_lens plugin system.
|
|
131
134
|
|
|
132
135
|
An entity represents a geospatial primitive (e.g. microwatershed, village,
|
|
133
136
|
district) backed by one or more Parquet/GeoParquet files. Entities are
|
|
@@ -195,6 +198,7 @@ class BaseEntity(ABC):
|
|
|
195
198
|
``polars.scan_parquet``. For S3 the common keys are
|
|
196
199
|
``"region"``, ``"access_key"``, and ``"secret_key"``.
|
|
197
200
|
``None`` uses ambient credentials (env-vars / ``~/.aws/``).
|
|
201
|
+
|
|
198
202
|
"""
|
|
199
203
|
# Store as str so cloud URIs (s3://…) are never coerced through pathlib.
|
|
200
204
|
self._data_root: str | None = str(data_root) if data_root is not None else None
|
|
@@ -219,6 +223,7 @@ class BaseEntity(ABC):
|
|
|
219
223
|
|
|
220
224
|
Raises:
|
|
221
225
|
FileNotFoundError: For *local* paths that do not exist after resolution.
|
|
226
|
+
|
|
222
227
|
"""
|
|
223
228
|
# Fully-qualified cloud URI — return as-is.
|
|
224
229
|
if is_cloud_uri(path):
|
|
@@ -255,13 +260,14 @@ class BaseEntity(ABC):
|
|
|
255
260
|
@property
|
|
256
261
|
@abstractmethod
|
|
257
262
|
def key_cols(self) -> list[str]:
|
|
258
|
-
"""Columns that uniquely identify one instance of this entity.
|
|
263
|
+
r"""Columns that uniquely identify one instance of this entity.
|
|
259
264
|
|
|
260
265
|
For built-in entities this is always a single-element list (e.g.
|
|
261
266
|
``[\"mws_id\"]``), but the contract allows composite keys for plugins.
|
|
262
267
|
|
|
263
268
|
Returns:
|
|
264
269
|
list[str]: A list of column name strings present in the static file.
|
|
270
|
+
|
|
265
271
|
"""
|
|
266
272
|
|
|
267
273
|
@property
|
|
@@ -274,6 +280,7 @@ class BaseEntity(ABC):
|
|
|
274
280
|
|
|
275
281
|
Returns:
|
|
276
282
|
str: The column name as a string.
|
|
283
|
+
|
|
277
284
|
"""
|
|
278
285
|
|
|
279
286
|
@property
|
|
@@ -287,6 +294,7 @@ class BaseEntity(ABC):
|
|
|
287
294
|
|
|
288
295
|
Returns:
|
|
289
296
|
str: A path string.
|
|
297
|
+
|
|
290
298
|
"""
|
|
291
299
|
|
|
292
300
|
@property
|
|
@@ -299,6 +307,7 @@ class BaseEntity(ABC):
|
|
|
299
307
|
|
|
300
308
|
Returns:
|
|
301
309
|
str | None: A path string, or ``None`` if the entity has no annual data.
|
|
310
|
+
|
|
302
311
|
"""
|
|
303
312
|
return None
|
|
304
313
|
|
|
@@ -312,6 +321,7 @@ class BaseEntity(ABC):
|
|
|
312
321
|
|
|
313
322
|
Returns:
|
|
314
323
|
str | None: A path string, or ``None`` if the entity has no fortnightly data.
|
|
324
|
+
|
|
315
325
|
"""
|
|
316
326
|
return None
|
|
317
327
|
|
|
@@ -336,6 +346,7 @@ class BaseEntity(ABC):
|
|
|
336
346
|
|
|
337
347
|
Returns:
|
|
338
348
|
SchemaProfile: A fully-validated :class:`~core_lens.schema.profile.SchemaProfile`.
|
|
349
|
+
|
|
339
350
|
"""
|
|
340
351
|
if not hasattr(self, "_schema_profile"):
|
|
341
352
|
_so = self._storage_options or {}
|
|
@@ -372,6 +383,7 @@ class BaseEntity(ABC):
|
|
|
372
383
|
|
|
373
384
|
Returns:
|
|
374
385
|
pl.DataFrame: DataFrame with columns ``(*key_cols, minx, miny, maxx, maxy)``.
|
|
386
|
+
|
|
375
387
|
"""
|
|
376
388
|
if not hasattr(self, "_cached_index"):
|
|
377
389
|
profile = self.schema_profile # already cached — no extra I/O
|
|
@@ -409,6 +421,7 @@ class BaseEntity(ABC):
|
|
|
409
421
|
Raises:
|
|
410
422
|
ValueError: If a kwarg cannot be resolved as either an attribute
|
|
411
423
|
column or a registered entity name.
|
|
424
|
+
|
|
412
425
|
"""
|
|
413
426
|
from core_lens.aoi import _REGISTRY
|
|
414
427
|
from core_lens.base.view import View
|
|
@@ -544,6 +557,7 @@ class BaseEntity(ABC):
|
|
|
544
557
|
|
|
545
558
|
Raises:
|
|
546
559
|
ValueError: If neither ``geometry`` nor ``bbox`` is provided.
|
|
560
|
+
|
|
547
561
|
"""
|
|
548
562
|
import shapely.geometry as sgeom
|
|
549
563
|
|
|
@@ -584,7 +598,7 @@ class BaseEntity(ABC):
|
|
|
584
598
|
return View(keys=keys, entity=self, entity_name=entity_name)
|
|
585
599
|
|
|
586
600
|
def spatial_join(self, other: "BaseEntity", agg: dict[str, str]) -> "View":
|
|
587
|
-
"""Return a lazy :class:`~core_lens.base.view.View` with a cross-entity join pending.
|
|
601
|
+
r"""Return a lazy :class:`~core_lens.base.view.View` with a cross-entity join pending.
|
|
588
602
|
|
|
589
603
|
The join is recorded in the View's ``join_spec`` and computed only at
|
|
590
604
|
materialisation time (``.static``, ``.annual``, or ``.fortnightly``).
|
|
@@ -601,6 +615,7 @@ class BaseEntity(ABC):
|
|
|
601
615
|
Returns:
|
|
602
616
|
View: A lazy :class:`~core_lens.base.view.View` with the join spec
|
|
603
617
|
recorded for deferred execution.
|
|
618
|
+
|
|
604
619
|
"""
|
|
605
620
|
from core_lens.base.view import View
|
|
606
621
|
|
|
@@ -17,6 +17,7 @@ class SubplotOn(Enum):
|
|
|
17
17
|
MONTH: Split data by month.
|
|
18
18
|
SEASON: Split data by meteorological season.
|
|
19
19
|
SEASON_YEAR: Split data by season and year.
|
|
20
|
+
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
YEAR = "year"
|
|
@@ -71,6 +72,7 @@ def _wkb_to_arrow_table(
|
|
|
71
72
|
|
|
72
73
|
Returns:
|
|
73
74
|
An ``arro3.core.Table`` whose geometry column is GeoArrow-typed.
|
|
75
|
+
|
|
74
76
|
"""
|
|
75
77
|
import geoarrow.rust.core as ga
|
|
76
78
|
import polars as pl
|
|
@@ -182,6 +184,7 @@ def _color_for(index: int) -> str:
|
|
|
182
184
|
|
|
183
185
|
Returns:
|
|
184
186
|
The hex color string.
|
|
187
|
+
|
|
185
188
|
"""
|
|
186
189
|
return _PALETTE[index % len(_PALETTE)]
|
|
187
190
|
|
|
@@ -193,6 +196,7 @@ def _apply_theme(fig: Any, result: "Result", title: str) -> None:
|
|
|
193
196
|
fig: The Bokeh figure.
|
|
194
197
|
result: The parent Result object.
|
|
195
198
|
title: The title text.
|
|
199
|
+
|
|
196
200
|
"""
|
|
197
201
|
entity_name = getattr(result.entity, "__name__", "Unknown")
|
|
198
202
|
if hasattr(result, "entity_name"):
|
|
@@ -202,7 +206,6 @@ def _apply_theme(fig: Any, result: "Result", title: str) -> None:
|
|
|
202
206
|
fig.title.text_font = "Inter, sans-serif"
|
|
203
207
|
fig.title.text_font_size = "14pt"
|
|
204
208
|
|
|
205
|
-
# Subtitle-style annotation in the bottom-right corner.
|
|
206
209
|
from bokeh.models.annotations import Label
|
|
207
210
|
|
|
208
211
|
subtitle = Label(
|
|
@@ -227,6 +230,7 @@ class PlotNamespace:
|
|
|
227
230
|
|
|
228
231
|
Attributes:
|
|
229
232
|
result: The parent Result object.
|
|
233
|
+
|
|
230
234
|
"""
|
|
231
235
|
|
|
232
236
|
def __init__(self, result: "Result") -> None:
|
|
@@ -253,6 +257,7 @@ class PlotNamespace:
|
|
|
253
257
|
ValueError: If ``column`` or ``subplot_on`` column not found in data.
|
|
254
258
|
NotImplementedError: Lonboard does not support native subplot grids;
|
|
255
259
|
only single-value ``subplot_on`` is rendered when specified.
|
|
260
|
+
|
|
256
261
|
"""
|
|
257
262
|
import lonboard
|
|
258
263
|
from lonboard.colormap import apply_continuous_cmap
|
|
@@ -347,6 +352,7 @@ class PlotNamespace:
|
|
|
347
352
|
|
|
348
353
|
Returns:
|
|
349
354
|
Any: A Bokeh Tabs object (or a single Figure when ``aggregate=True``).
|
|
355
|
+
|
|
350
356
|
"""
|
|
351
357
|
import polars as pl
|
|
352
358
|
import polars.selectors as cs
|
|
@@ -474,7 +480,6 @@ class PlotNamespace:
|
|
|
474
480
|
_apply_theme(fig, self.result, title)
|
|
475
481
|
return fig
|
|
476
482
|
|
|
477
|
-
# --- Aggregated view --------------------------------------------------
|
|
478
483
|
group_cols = [x, subplot_col] if subplot_col else [x]
|
|
479
484
|
agg_pdf = (
|
|
480
485
|
df.group_by(group_cols)
|
|
@@ -486,7 +491,6 @@ class PlotNamespace:
|
|
|
486
491
|
if aggregate:
|
|
487
492
|
return _make_fig(agg_pdf, "Timeseries (Aggregated Mean)")
|
|
488
493
|
|
|
489
|
-
# --- Per-entity view --------------------------------------------------
|
|
490
494
|
entity_df = df.sort(x)
|
|
491
495
|
unique_entities = entity_df[key_col].unique()
|
|
492
496
|
if len(unique_entities) > top_n:
|
|
@@ -519,6 +523,7 @@ class PlotNamespace:
|
|
|
519
523
|
|
|
520
524
|
Returns:
|
|
521
525
|
Any: A Bokeh Figure or Tabs object.
|
|
526
|
+
|
|
522
527
|
"""
|
|
523
528
|
import polars as pl
|
|
524
529
|
import polars.selectors as cs
|
|
@@ -595,6 +600,7 @@ class PlotNamespace:
|
|
|
595
600
|
|
|
596
601
|
Returns:
|
|
597
602
|
Any: A Bokeh Figure or Tabs object.
|
|
603
|
+
|
|
598
604
|
"""
|
|
599
605
|
import numpy as np
|
|
600
606
|
import pandas as pd
|
|
@@ -665,6 +671,7 @@ class PlotNamespace:
|
|
|
665
671
|
|
|
666
672
|
Returns:
|
|
667
673
|
Any: A Bokeh Figure object.
|
|
674
|
+
|
|
668
675
|
"""
|
|
669
676
|
import polars.selectors as cs
|
|
670
677
|
from bokeh.models import BasicTicker, ColumnDataSource, LinearColorMapper
|
|
@@ -684,7 +691,6 @@ class PlotNamespace:
|
|
|
684
691
|
corr = df.to_pandas().corr()
|
|
685
692
|
cols = list(corr.columns)
|
|
686
693
|
|
|
687
|
-
# Build long-form data for rect glyphs.
|
|
688
694
|
xs: list[str] = []
|
|
689
695
|
ys: list[str] = []
|
|
690
696
|
vals: list[float] = []
|
|
@@ -744,6 +750,7 @@ class PlotNamespace:
|
|
|
744
750
|
|
|
745
751
|
Returns:
|
|
746
752
|
Any: A Bokeh Figure object.
|
|
753
|
+
|
|
747
754
|
"""
|
|
748
755
|
from bokeh.models import (
|
|
749
756
|
BasicTicker,
|
|
@@ -769,7 +776,6 @@ class PlotNamespace:
|
|
|
769
776
|
x_vals = [str(v) for v in pdf.columns.tolist()]
|
|
770
777
|
y_vals = [str(v) for v in pdf.index.tolist()]
|
|
771
778
|
|
|
772
|
-
# Long-form
|
|
773
779
|
xs2: list[str] = []
|
|
774
780
|
ys2: list[str] = []
|
|
775
781
|
vals2: list[float] = []
|
|
@@ -819,6 +825,7 @@ class PlotNamespace:
|
|
|
819
825
|
|
|
820
826
|
Returns:
|
|
821
827
|
Any: A Bokeh ``gridplot`` object.
|
|
828
|
+
|
|
822
829
|
"""
|
|
823
830
|
import polars.selectors as cs
|
|
824
831
|
from bokeh.layouts import gridplot
|
|
@@ -29,6 +29,7 @@ def _sf(x: object) -> float:
|
|
|
29
29
|
|
|
30
30
|
Returns:
|
|
31
31
|
float: The numeric value as a float, or NaN if the input is None.
|
|
32
|
+
|
|
32
33
|
"""
|
|
33
34
|
if x is None:
|
|
34
35
|
return float("nan")
|
|
@@ -42,6 +43,7 @@ class CorrelateMethod(Enum):
|
|
|
42
43
|
PEARSON: Pearson correlation coefficient.
|
|
43
44
|
SPEARMAN: Spearman rank correlation.
|
|
44
45
|
KENDALL: Kendall Tau correlation.
|
|
46
|
+
|
|
45
47
|
"""
|
|
46
48
|
|
|
47
49
|
PEARSON = "pearson"
|
|
@@ -58,6 +60,7 @@ class TestMethod(Enum):
|
|
|
58
60
|
WILCOXON: Wilcoxon signed-rank test.
|
|
59
61
|
KS: Kolmogorov-Smirnov test.
|
|
60
62
|
CHI_SQUARE: Chi-square test.
|
|
63
|
+
|
|
61
64
|
"""
|
|
62
65
|
|
|
63
66
|
__test__ = False
|
|
@@ -75,6 +78,7 @@ class ChangeMethod(Enum):
|
|
|
75
78
|
ABSOLUTE: Absolute difference between periods.
|
|
76
79
|
PERCENTAGE: Percentage change between periods.
|
|
77
80
|
TREND: Linear trend over time.
|
|
81
|
+
|
|
78
82
|
"""
|
|
79
83
|
|
|
80
84
|
ABSOLUTE = "absolute"
|
|
@@ -90,6 +94,7 @@ class AnomalyCrossMethod(Enum):
|
|
|
90
94
|
IQR: Interquartile range method.
|
|
91
95
|
PERCENTILE: Percentile-based method.
|
|
92
96
|
THRESHOLD: Fixed threshold method.
|
|
97
|
+
|
|
93
98
|
"""
|
|
94
99
|
|
|
95
100
|
ZSCORE = "zscore"
|
|
@@ -105,6 +110,7 @@ class AnomalyTsMethod(Enum):
|
|
|
105
110
|
STL: Seasonal-Trend decomposition using LOESS.
|
|
106
111
|
CUSUM: Cumulative sum control chart.
|
|
107
112
|
MAD: Median Absolute Deviation.
|
|
113
|
+
|
|
108
114
|
"""
|
|
109
115
|
|
|
110
116
|
STL = "stl"
|
|
@@ -120,6 +126,7 @@ class SimilarityMethod(Enum):
|
|
|
120
126
|
COSINE: Cosine similarity.
|
|
121
127
|
MAHALANOBIS: Mahalanobis distance.
|
|
122
128
|
MANHATTAN: Manhattan distance.
|
|
129
|
+
|
|
123
130
|
"""
|
|
124
131
|
|
|
125
132
|
EUCLIDEAN = "euclidean"
|
|
@@ -167,7 +174,8 @@ class StatsNamespace:
|
|
|
167
174
|
columns: list[str] | None = None,
|
|
168
175
|
by: str = "column",
|
|
169
176
|
) -> "Result":
|
|
170
|
-
"""Per-column or per-entity descriptive statistics.
|
|
177
|
+
r"""Per-column or per-entity descriptive statistics.
|
|
178
|
+
|
|
171
179
|
Uses polars' in-built methods for mean, std, min, max, quantiles etc.
|
|
172
180
|
|
|
173
181
|
Args:
|
|
@@ -180,6 +188,7 @@ class StatsNamespace:
|
|
|
180
188
|
|
|
181
189
|
Raises:
|
|
182
190
|
ValueError: If ``by`` is not ``\"column\"`` or ``\"entity\"``.
|
|
191
|
+
|
|
183
192
|
"""
|
|
184
193
|
df = self._r.data
|
|
185
194
|
cols = columns if columns is not None else self._numeric_cols(df)
|
|
@@ -239,6 +248,7 @@ class StatsNamespace:
|
|
|
239
248
|
Raises:
|
|
240
249
|
CorrelationError: If fewer than 2 columns supplied.
|
|
241
250
|
ValueError: If ``method`` is not recognised.
|
|
251
|
+
|
|
242
252
|
"""
|
|
243
253
|
if len(columns) < 2:
|
|
244
254
|
raise CorrelationError(
|
|
@@ -313,6 +323,7 @@ class StatsNamespace:
|
|
|
313
323
|
Raises:
|
|
314
324
|
ValueError: If none of ``groups``, ``periods``, ``against`` supplied,
|
|
315
325
|
or if ``method`` is not recognised.
|
|
326
|
+
|
|
316
327
|
"""
|
|
317
328
|
import scipy.stats as sp
|
|
318
329
|
|
|
@@ -460,6 +471,7 @@ class StatsNamespace:
|
|
|
460
471
|
|
|
461
472
|
Raises:
|
|
462
473
|
ValueError: If ``method`` is not recognised or year column absent.
|
|
474
|
+
|
|
463
475
|
"""
|
|
464
476
|
if not isinstance(method, ChangeMethod):
|
|
465
477
|
raise ValueError(
|
|
@@ -566,6 +578,7 @@ class StatsNamespace:
|
|
|
566
578
|
|
|
567
579
|
Raises:
|
|
568
580
|
ValueError: If ``mode``, ``method``, or observation count invalid.
|
|
581
|
+
|
|
569
582
|
"""
|
|
570
583
|
df = self._r.data
|
|
571
584
|
key = self._r.key_cols[0]
|
|
@@ -835,6 +848,7 @@ class StatsNamespace:
|
|
|
835
848
|
Raises:
|
|
836
849
|
ValueError: If ``method`` is invalid, no columns can be resolved,
|
|
837
850
|
or ``target`` is not found.
|
|
851
|
+
|
|
838
852
|
"""
|
|
839
853
|
if not isinstance(method, SimilarityMethod):
|
|
840
854
|
raise ValueError(
|