core-lens 0.1.dev113__tar.gz → 0.1.dev114__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.dev113 → core_lens-0.1.dev114}/PKG-INFO +1 -1
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/_version.py +2 -2
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/view.py +21 -1
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/utils/season.py +68 -28
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.github/pull_request_template.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.github/workflows/ci.yml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.github/workflows/gh-pages.yml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.github/workflows/pre-release.yml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.github/workflows/release.yml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.gitignore +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.gitmessage +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.pre-commit-config.yaml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/.python-version +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/CONTRIBUTING.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/LICENSE +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/README.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/SKILLS.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/Makefile +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/make.bat +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/concepts.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/conf.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/index.rst +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/intro.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/plots.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/plugins.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/queries.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/quickstart.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/docs/source/stats.md +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/examples/demo_mws.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/examples/demo_tehsil.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/hooks/mypy.sh +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/hooks/no-parquet-outside-fixtures.sh +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/hooks/pytest.sh +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/pyproject.toml +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/__main__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/aoi.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/entity.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/namespaces/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/namespaces/plot.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/namespaces/stats.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/base/result.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/entities/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/entities/mws.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/entities/tehsil.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/entities/waterbody.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/export/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/export/formats.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/py.typed +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/schema/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/schema/detection.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/schema/profile.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/utils/__init__.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/utils/polars_utils.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/src/core_lens/utils/spatial.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/fixtures/generate_fixtures.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/conftest.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_aoi.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_entities.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_entity.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_export.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_main.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_plot.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_polars_utils.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_profile.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_result.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_schema_detection.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_schema_profile.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_season.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_season_config.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_spatial.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_stats.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/tests/unit/test_view.py +0 -0
- {core_lens-0.1.dev113 → core_lens-0.1.dev114}/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.dev114
|
|
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
|
|
@@ -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.dev114'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 'dev114')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -376,7 +376,27 @@ class View:
|
|
|
376
376
|
from core_lens.aoi import SeasonConfig
|
|
377
377
|
|
|
378
378
|
season_cfg = self._season_config or SeasonConfig()
|
|
379
|
-
|
|
379
|
+
|
|
380
|
+
# Detect whether the time column is an integer year column so
|
|
381
|
+
# _date_range_expr can branch at expression-build time (avoids
|
|
382
|
+
# the cast-to-string + runtime when/then triple-copy).
|
|
383
|
+
try:
|
|
384
|
+
pq_schema = pl.read_parquet_schema(abs_path)
|
|
385
|
+
_time_dtype = pq_schema.get(time_col)
|
|
386
|
+
_is_year_col = _time_dtype is not None and _time_dtype in (
|
|
387
|
+
pl.Int32,
|
|
388
|
+
pl.Int64,
|
|
389
|
+
pl.UInt32,
|
|
390
|
+
pl.UInt16,
|
|
391
|
+
)
|
|
392
|
+
except Exception:
|
|
393
|
+
_is_year_col = False
|
|
394
|
+
|
|
395
|
+
# Pass as a private hint inside the filter dict (non-mutating copy).
|
|
396
|
+
_tf = dict(self.time_filter)
|
|
397
|
+
if "start" in _tf and "end" in _tf:
|
|
398
|
+
_tf["_is_year_col"] = _is_year_col
|
|
399
|
+
time_expr = resolve_time_filter(_tf, time_col, season_cfg)
|
|
380
400
|
|
|
381
401
|
lf = scan_with_key_filter(
|
|
382
402
|
path=abs_path,
|
|
@@ -47,7 +47,12 @@ def resolve_time_filter(
|
|
|
47
47
|
end = time_filter["end"]
|
|
48
48
|
assert isinstance(start, str)
|
|
49
49
|
assert isinstance(end, str)
|
|
50
|
-
|
|
50
|
+
# _is_year_col is injected by _materialise when the parquet schema is
|
|
51
|
+
# available. When absent (e.g. direct test calls) pass None so that
|
|
52
|
+
# _date_range_expr uses its runtime when/then fallback.
|
|
53
|
+
_raw = time_filter.get("_is_year_col")
|
|
54
|
+
is_year_col: bool | None = bool(_raw) if _raw is not None else None
|
|
55
|
+
return _date_range_expr(time_col, start, end, is_year_col=is_year_col)
|
|
51
56
|
|
|
52
57
|
if "season" in time_filter:
|
|
53
58
|
season_name = time_filter["season"]
|
|
@@ -64,16 +69,25 @@ def resolve_time_filter(
|
|
|
64
69
|
)
|
|
65
70
|
|
|
66
71
|
|
|
67
|
-
def _date_range_expr(
|
|
72
|
+
def _date_range_expr(
|
|
73
|
+
time_col: str,
|
|
74
|
+
start: str,
|
|
75
|
+
end: str,
|
|
76
|
+
is_year_col: bool | None = None,
|
|
77
|
+
) -> pl.Expr:
|
|
68
78
|
"""Return a Polars expression for an inclusive ISO-8601 date range.
|
|
69
79
|
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
When ``is_year_col`` is known (``True`` / ``False``) branches at *build
|
|
81
|
+
time* — no runtime copies of the column. When ``None`` (caller has no
|
|
82
|
+
schema info) falls back to a ``when/then`` that handles both integer-year
|
|
83
|
+
and Date columns at runtime.
|
|
72
84
|
|
|
73
85
|
Args:
|
|
74
86
|
time_col: Name of the time column.
|
|
75
87
|
start: ISO-8601 start date string (``"YYYY-MM-DD"``).
|
|
76
88
|
end: ISO-8601 end date string (``"YYYY-MM-DD"``).
|
|
89
|
+
is_year_col: ``True`` → integer calendar-year column; ``False`` →
|
|
90
|
+
``pl.Date`` column; ``None`` → unknown, use runtime detection.
|
|
77
91
|
|
|
78
92
|
Returns:
|
|
79
93
|
A Polars expression.
|
|
@@ -82,25 +96,25 @@ def _date_range_expr(time_col: str, start: str, end: str) -> pl.Expr:
|
|
|
82
96
|
end_date = datetime.date.fromisoformat(end)
|
|
83
97
|
col = pl.col(time_col)
|
|
84
98
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# For date columns, cast to string yields "2020-01-01".
|
|
89
|
-
# So we compare strings directly! Lexicographical string comparison works perfectly for ISO-8601 dates and 4-digit years.
|
|
90
|
-
# For year integers, "2020" >= "2020-01-01" is False.
|
|
91
|
-
# Wait, "2020" < "2020-01-01". So if it's an integer, "2020" will not be between "2020-01-01" and "2023-12-31" because "2020" is less than "2020-01-01".
|
|
92
|
-
# To fix this, we can extract the year for the integer comparison using a regex or length check.
|
|
93
|
-
# Better: check the length of the casted string. If 4, it's a year.
|
|
94
|
-
s_col = col.cast(pl.String)
|
|
95
|
-
is_year_int = s_col.str.len_bytes() == 4
|
|
99
|
+
if is_year_col is True:
|
|
100
|
+
# Integer year column — compare year integers directly, no string cast.
|
|
101
|
+
return col.is_between(start_date.year, end_date.year)
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
start_date
|
|
100
|
-
)
|
|
101
|
-
date_expr = s_col.is_between(pl.lit(start), pl.lit(end))
|
|
103
|
+
if is_year_col is False:
|
|
104
|
+
# Date column — compare against date literals directly.
|
|
105
|
+
return col.is_between(pl.lit(start_date), pl.lit(end_date))
|
|
102
106
|
|
|
103
|
-
|
|
107
|
+
# is_year_col is None — dtype unknown (e.g. direct test call without schema).
|
|
108
|
+
# Use a runtime when/then that handles both representations.
|
|
109
|
+
s_col = col.cast(pl.String)
|
|
110
|
+
is_year_like = s_col.str.len_bytes() == 4
|
|
111
|
+
return (
|
|
112
|
+
pl.when(is_year_like)
|
|
113
|
+
.then(
|
|
114
|
+
col.cast(pl.Int32, strict=False).is_between(start_date.year, end_date.year)
|
|
115
|
+
)
|
|
116
|
+
.otherwise(col.is_between(pl.lit(start_date), pl.lit(end_date)))
|
|
117
|
+
)
|
|
104
118
|
|
|
105
119
|
|
|
106
120
|
def _season_expr(
|
|
@@ -227,17 +241,43 @@ def add_temporal_columns(
|
|
|
227
241
|
df = df.with_columns(exprs)
|
|
228
242
|
|
|
229
243
|
# --- season / season_year -----------------------------------------------
|
|
230
|
-
#
|
|
231
|
-
#
|
|
244
|
+
# Vectorized via Polars when/then on MM-DD string — no Python loop,
|
|
245
|
+
# no .to_list(), no Python date objects per row.
|
|
232
246
|
if "season" not in existing or "season_year" not in existing:
|
|
233
|
-
|
|
247
|
+
date_col = pl.col(time_col)
|
|
248
|
+
md = (
|
|
249
|
+
date_col.dt.month().cast(pl.String).str.pad_start(2, "0")
|
|
250
|
+
+ pl.lit("-")
|
|
251
|
+
+ date_col.dt.day().cast(pl.String).str.pad_start(2, "0")
|
|
252
|
+
)
|
|
253
|
+
k_start, k_end = season_config.kharif
|
|
254
|
+
r_start, r_end = season_config.rabi
|
|
255
|
+
z_start, z_end = season_config.zaid
|
|
256
|
+
|
|
257
|
+
def _md_between(expr: pl.Expr, start: str, end: str) -> pl.Expr:
|
|
258
|
+
"""True when *expr* (MM-DD string) falls within [start, end], handling year rollover."""
|
|
259
|
+
if start <= end:
|
|
260
|
+
# is_between with bare strings → column-name lookup; use pl.lit().
|
|
261
|
+
return expr.is_between(pl.lit(start), pl.lit(end))
|
|
262
|
+
# Year-crossing: e.g. rabi 11-01 → 03-31
|
|
263
|
+
return (expr >= pl.lit(start)) | (expr <= pl.lit(end))
|
|
264
|
+
|
|
265
|
+
season_expr = (
|
|
266
|
+
pl.when(_md_between(md, k_start, k_end))
|
|
267
|
+
.then(pl.lit("kharif"))
|
|
268
|
+
.when(_md_between(md, r_start, r_end))
|
|
269
|
+
.then(pl.lit("rabi"))
|
|
270
|
+
.otherwise(pl.lit("zaid"))
|
|
271
|
+
.alias("season")
|
|
272
|
+
)
|
|
273
|
+
|
|
234
274
|
if "season" not in existing:
|
|
235
|
-
df = df.with_columns(
|
|
275
|
+
df = df.with_columns(season_expr)
|
|
236
276
|
if "season_year" not in existing:
|
|
237
|
-
years = df["year"].to_list()
|
|
238
|
-
season_year_labels = [f"{s}_{y}" for s, y in zip(season_labels, years)]
|
|
239
277
|
df = df.with_columns(
|
|
240
|
-
pl.
|
|
278
|
+
(pl.col("season") + pl.lit("_") + pl.col("year").cast(pl.String)).alias(
|
|
279
|
+
"season_year"
|
|
280
|
+
)
|
|
241
281
|
)
|
|
242
282
|
|
|
243
283
|
return df
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|