core-lens 0.1.dev101__tar.gz → 0.1.dev105__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 (78) hide show
  1. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/PKG-INFO +1 -1
  2. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/intro.md +1 -1
  3. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/plots.md +5 -3
  4. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/queries.md +1 -1
  5. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/quickstart.md +6 -0
  6. core_lens-0.1.dev105/plot_wb.py +29 -0
  7. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/_version.py +2 -2
  8. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/result.py +11 -4
  9. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/entities/__init__.py +2 -1
  10. core_lens-0.1.dev105/src/core_lens/entities/waterbody.py +32 -0
  11. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_result.py +20 -6
  12. core_lens-0.1.dev105/wb_annual.txt +11 -0
  13. core_lens-0.1.dev105/wb_static.txt +11 -0
  14. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.github/pull_request_template.md +0 -0
  15. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.github/workflows/ci.yml +0 -0
  16. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.github/workflows/gh-pages.yml +0 -0
  17. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.github/workflows/pre-release.yml +0 -0
  18. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.github/workflows/release.yml +0 -0
  19. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.gitignore +0 -0
  20. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.gitmessage +0 -0
  21. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.pre-commit-config.yaml +0 -0
  22. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/.python-version +0 -0
  23. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/CONTRIBUTING.md +0 -0
  24. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/LICENSE +0 -0
  25. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/README.md +0 -0
  26. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/SKILLS.md +0 -0
  27. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/Makefile +0 -0
  28. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/make.bat +0 -0
  29. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/concepts.md +0 -0
  30. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/conf.py +0 -0
  31. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/index.rst +0 -0
  32. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/plugins.md +0 -0
  33. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/docs/source/stats.md +0 -0
  34. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/examples/demo_mws.py +0 -0
  35. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/examples/demo_tehsil.py +0 -0
  36. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/hooks/mypy.sh +0 -0
  37. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/hooks/no-parquet-outside-fixtures.sh +0 -0
  38. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/hooks/pytest.sh +0 -0
  39. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/pyproject.toml +0 -0
  40. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/__init__.py +0 -0
  41. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/__main__.py +0 -0
  42. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/aoi.py +0 -0
  43. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/__init__.py +0 -0
  44. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/entity.py +0 -0
  45. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/namespaces/__init__.py +0 -0
  46. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/namespaces/plot.py +0 -0
  47. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/namespaces/stats.py +0 -0
  48. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/base/view.py +0 -0
  49. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/entities/mws.py +0 -0
  50. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/entities/tehsil.py +0 -0
  51. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/export/__init__.py +0 -0
  52. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/export/formats.py +0 -0
  53. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/py.typed +0 -0
  54. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/schema/__init__.py +0 -0
  55. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/schema/detection.py +0 -0
  56. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/schema/profile.py +0 -0
  57. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/utils/__init__.py +0 -0
  58. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/utils/polars_utils.py +0 -0
  59. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/utils/season.py +0 -0
  60. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/src/core_lens/utils/spatial.py +0 -0
  61. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/fixtures/generate_fixtures.py +0 -0
  62. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/conftest.py +0 -0
  63. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_aoi.py +0 -0
  64. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_entities.py +0 -0
  65. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_entity.py +0 -0
  66. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_export.py +0 -0
  67. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_main.py +0 -0
  68. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_plot.py +0 -0
  69. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_polars_utils.py +0 -0
  70. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_profile.py +0 -0
  71. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_schema_detection.py +0 -0
  72. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_schema_profile.py +0 -0
  73. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_season.py +0 -0
  74. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_season_config.py +0 -0
  75. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_spatial.py +0 -0
  76. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_stats.py +0 -0
  77. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/tests/unit/test_view.py +0 -0
  78. {core_lens-0.1.dev101 → core_lens-0.1.dev105}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: core-lens
3
- Version: 0.1.dev101
3
+ Version: 0.1.dev105
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
@@ -12,4 +12,4 @@ CoreLens provides a unified interface over microwatersheds, administrative bound
12
12
  - **Pluggable Entities**: Built-in support for standard units (MWS, Tehsil) with a simple plugin architecture for adding new domain entities.
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
- - **Interactive Visualisation**: Generate interactive maps using Lonboard and timeseries/scatter plots using Plotly directly from results.
15
+ - **Interactive Visualisation**: Generate interactive maps using Lonboard and timeseries/scatter plots using Bokeh directly from results.
@@ -1,8 +1,8 @@
1
1
  # Visualisation and Plots
2
2
 
3
- The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps, while charts and timeseries use **Plotly**.
3
+ The `result.plot` namespace provides integrated plotting capabilities. Spatial plots use **Lonboard** for high-performance interactive maps, while charts and timeseries use **Bokeh**.
4
4
 
5
- Plot methods return plot objects (like a Plotly Figure or Lonboard Map) which can be displayed in Jupyter notebooks or exported.
5
+ Plot methods return plot objects (like a Bokeh Figure or Lonboard Map) which can be displayed using `bokeh.io.show` or exported via `bokeh.io.save`.
6
6
 
7
7
  ## Choropleth Maps
8
8
 
@@ -18,9 +18,11 @@ map_view = res.plot.choropleth(column="ndvi")
18
18
  Plot timeseries data. You can aggregate the data or plot the top N entities individually.
19
19
 
20
20
  ```python
21
+ from bokeh.io import show
22
+
21
23
  # Aggregate timeseries
22
24
  fig_agg = res.plot.timeseries(x="year", y="ndvi", aggregate=True)
23
- fig_agg.show()
25
+ show(fig_agg)
24
26
 
25
27
  # Timeseries for top entities
26
28
  fig_top = res.plot.timeseries(x="year", y=["ndvi", "rainfall"], top_n=5)
@@ -31,7 +31,7 @@ derived_res = res_annual.derive(
31
31
  pl.when(pl.col("rainfall") < 500).then(1).otherwise(0)
32
32
  )
33
33
 
34
- # 2. Aggregate (temporal grouping requires fortnightly resolution)
34
+ # 2. Aggregate (temporal grouping like "month" requires fortnightly, but "year" works on annual too)
35
35
  res_fortnightly = aoi.mws.fortnightly
36
36
  monthly_avg = res_fortnightly.aggregate(pl.mean("ndvi"), by="month")
37
37
  seasonal_avg = res_fortnightly.aggregate(pl.mean("ndvi"), by="season_year")
@@ -34,6 +34,12 @@ aoi = AoI(
34
34
  tehsil="Pangi"
35
35
  )
36
36
 
37
+ # Or scope it directly to a specific list of entity IDs
38
+ aoi_specific = AoI(
39
+ data_root="/path/to/data",
40
+ mws_id=["13_001", "13_002"]
41
+ )
42
+
37
43
  # You can now access entities scoped to this AoI:
38
44
  mws_view = aoi.mws
39
45
 
@@ -0,0 +1,29 @@
1
+ import polars as pl
2
+ from core_lens import AoI
3
+ from core_lens.base.entity import BaseEntity
4
+
5
+ from core_lens.entities.waterbody import WaterbodyEntity
6
+
7
+ def main():
8
+ # Register the entity
9
+ AoI.register(WaterbodyEntity)
10
+
11
+ # Init AoI with a bounding box for India (or you can adjust bbox)
12
+ DATA_ROOT = "data/"
13
+ aoi = AoI(DATA_ROOT, bbox=(68.0, 6.0, 98.0, 38.0))
14
+
15
+ # Materialise the static waterbodies data
16
+ print("Loading waterbodies...")
17
+ wb_result = aoi.waterbody.static
18
+
19
+ # Plot using the area column for choropleth colors
20
+ print("Plotting waterbodies...")
21
+ choropleth_map = wb_result.plot.choropleth("area_ha")
22
+
23
+ # Save the map to HTML
24
+ output_file = "wb_map.html"
25
+ choropleth_map.to_html(output_file)
26
+ print(f"Saved waterbodies map to {output_file}")
27
+
28
+ if __name__ == "__main__":
29
+ main()
@@ -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.dev101'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev101')
21
+ __version__ = version = '0.1.dev105'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev105')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -15,8 +15,9 @@ if TYPE_CHECKING:
15
15
  from core_lens.base.namespaces.stats import StatsNamespace
16
16
  from core_lens.base.namespaces.plot import PlotNamespace
17
17
 
18
- _FORTNIGHTLY_ONLY_BY = {"year", "month", "year_month", "season", "season_year"}
19
- _VALID_BY = {None} | _FORTNIGHTLY_ONLY_BY
18
+ _FORTNIGHTLY_ONLY_BY = {"month", "year_month", "season", "season_year"}
19
+ _ANNUAL_OR_FORTNIGHTLY_BY = {"year"}
20
+ _VALID_BY = {None} | _FORTNIGHTLY_ONLY_BY | _ANNUAL_OR_FORTNIGHTLY_BY
20
21
 
21
22
 
22
23
  class Result:
@@ -194,7 +195,11 @@ class Result:
194
195
  - ❌
195
196
  - ✅
196
197
  - ✅
197
- * - ``"year"`` / ``"month"`` / ``"year_month"`` / ``"season"`` / ``"season_year"``
198
+ * - ``"year"``
199
+ - ❌
200
+ - ✅
201
+ - ✅
202
+ * - ``"month"`` / ``"year_month"`` / ``"season"`` / ``"season_year"``
198
203
  - ❌
199
204
  - ❌
200
205
  - ✅
@@ -203,7 +208,9 @@ class Result:
203
208
  *exprs: One or more Polars aggregation expressions
204
209
  (e.g. ``pl.mean("ndvi")``, ``pl.max("rainfall")``).
205
210
  by: Grouping dimension. ``None`` collapses all rows to one.
206
- Temporal groupings (``"year"``, ``"month"``, ``"year_month"``,
211
+ ``"year"`` groups by entity + year and is valid for both
212
+ annual and fortnightly resolution.
213
+ Other temporal groupings (``"month"``, ``"year_month"``,
207
214
  ``"season"``, ``"season_year"``) require
208
215
  ``resolution="fortnightly"``.
209
216
 
@@ -2,5 +2,6 @@ __module__: str = "core_lens.entities"
2
2
 
3
3
  from core_lens.entities.tehsil import TehsilEntity
4
4
  from core_lens.entities.mws import MWSEntity
5
+ from core_lens.entities.waterbody import WaterbodyEntity
5
6
 
6
- __all__ = ["TehsilEntity", "MWSEntity"]
7
+ __all__ = ["TehsilEntity", "MWSEntity", "WaterbodyEntity"]
@@ -0,0 +1,32 @@
1
+ from core_lens.base.entity import BaseEntity
2
+
3
+
4
+ class WaterbodyEntity(BaseEntity):
5
+ """Waterbody entity.
6
+
7
+ Backed by:
8
+ - Static: waterbodies/static
9
+ - Annual: waterbodies/annual (partitioned by basin)
10
+
11
+ All paths are relative to the AoI ``data_root``.
12
+ """
13
+
14
+ @property
15
+ def key_cols(self) -> list[str]:
16
+ return ["wb_id"]
17
+
18
+ @property
19
+ def geometry_col(self) -> str:
20
+ return "geometry"
21
+
22
+ @property
23
+ def static_path(self) -> str:
24
+ return "waterbodies/static"
25
+
26
+ @property
27
+ def annual_path(self) -> str | None:
28
+ return "waterbodies/annual"
29
+
30
+ @property
31
+ def fortnightly_path(self) -> str | None:
32
+ return "waterbodies/fortnightly"
@@ -220,11 +220,27 @@ class TestResultAggregate:
220
220
  with pytest.raises(ValueError, match="not supported on static"):
221
221
  result.aggregate(pl.mean("ndvi_mean"))
222
222
 
223
- def test_aggregate_temporal_by_on_annual_raises(self, entity_cls: Any) -> None:
223
+ def test_aggregate_by_year_on_annual_succeeds(self, entity_cls: Any) -> None:
224
+ df = pl.DataFrame(
225
+ {
226
+ "mws_id": ["1", "1"],
227
+ "year": [2021, 2022],
228
+ "ndvi_mean": [0.4, 0.5],
229
+ }
230
+ )
231
+ result = _make_result(entity_cls(), resolution=Resolution.ANNUAL, data=df)
232
+ agg = result.aggregate(pl.mean("ndvi_mean"), by="year")
233
+
234
+ assert "year" in agg.df().columns
235
+ assert "mws_id" in agg.df().columns
236
+
237
+ def test_aggregate_fortnightly_only_by_on_annual_raises(
238
+ self, entity_cls: Any
239
+ ) -> None:
224
240
  result = _make_result(entity_cls(), resolution=Resolution.ANNUAL)
225
241
 
226
242
  with pytest.raises(ValueError, match="requires data at fortnightly resolution"):
227
- result.aggregate(pl.mean("ndvi_mean"), by="year")
243
+ result.aggregate(pl.mean("ndvi_mean"), by="month")
228
244
 
229
245
  def test_aggregate_unknown_by_raises(self, entity_cls: Any) -> None:
230
246
  result = _make_result(entity_cls())
@@ -232,10 +248,8 @@ class TestResultAggregate:
232
248
  with pytest.raises(ValueError, match="Unknown grouping"):
233
249
  result.aggregate(pl.mean("ndvi_mean"), by="quarter")
234
250
 
235
- @pytest.mark.parametrize(
236
- "by", ["year", "month", "year_month", "season", "season_year"]
237
- )
238
- def test_all_fortnightly_by_keys_accepted(self, entity_cls: Any, by: Any) -> None:
251
+ @pytest.mark.parametrize("by", ["month", "year_month", "season", "season_year"])
252
+ def test_fortnightly_only_by_keys_accepted(self, entity_cls: Any, by: Any) -> None:
239
253
  df = pl.DataFrame(
240
254
  {
241
255
  "mws_id": ["1"],
@@ -0,0 +1,11 @@
1
+ tehsil String
2
+ district String
3
+ state String
4
+ area_in_ha Float64
5
+ wb_id String
6
+ ba_name String
7
+ year Int32
8
+ area_ha Float64
9
+ kharif Float64
10
+ karif_rabi Float64
11
+ kharif_rabi_zaid Float64
@@ -0,0 +1,11 @@
1
+ tehsil String
2
+ district String
3
+ state String
4
+ area_in_ha Float64
5
+ wb_id String
6
+ ba_name String
7
+ year Int32
8
+ area_ha Float64
9
+ kharif Float64
10
+ karif_rabi Float64
11
+ kharif_rabi_zaid Float64
File without changes
File without changes
File without changes
File without changes