core-lens 0.1.dev190__tar.gz → 0.1.dev191__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 (94) hide show
  1. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/PKG-INFO +1 -1
  2. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/_version.py +2 -2
  3. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/namespaces/stats.py +29 -16
  4. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/ISSUE_TEMPLATE/blank-proposal.yaml +0 -0
  5. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
  6. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/ISSUE_TEMPLATE/feature-request.yaml +0 -0
  7. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/pull_request_template.md +0 -0
  8. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/workflows/ci.yml +0 -0
  9. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/workflows/gh-pages.yml +0 -0
  10. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/workflows/pre-release.yml +0 -0
  11. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.github/workflows/release.yml +0 -0
  12. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.gitignore +0 -0
  13. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.gitmessage +0 -0
  14. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.pre-commit-config.yaml +0 -0
  15. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/.python-version +0 -0
  16. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/CONTRIBUTING.md +0 -0
  17. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/LICENSE +0 -0
  18. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/README.md +0 -0
  19. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/SKILLS.md +0 -0
  20. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/README.md +0 -0
  21. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_aoi.py +0 -0
  22. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_entity.py +0 -0
  23. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_export.py +0 -0
  24. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_polars_utils.py +0 -0
  25. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_result.py +0 -0
  26. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_schema.py +0 -0
  27. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_season.py +0 -0
  28. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_spatial.py +0 -0
  29. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/bench_view.py +0 -0
  30. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/benchmarks/run_all.sh +0 -0
  31. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/Makefile +0 -0
  32. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/make.bat +0 -0
  33. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/concepts.md +0 -0
  34. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/conf.py +0 -0
  35. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/export.md +0 -0
  36. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/index.rst +0 -0
  37. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/intro.md +0 -0
  38. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/logging.md +0 -0
  39. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/plots.md +0 -0
  40. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/plugins.md +0 -0
  41. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/queries.md +0 -0
  42. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/quickstart.md +0 -0
  43. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/docs/source/stats.md +0 -0
  44. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/examples/demo_mws.py +0 -0
  45. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/examples/demo_tehsil.py +0 -0
  46. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/hooks/mypy.sh +0 -0
  47. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/hooks/no-parquet-outside-fixtures.sh +0 -0
  48. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/hooks/pytest.sh +0 -0
  49. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/pyproject.toml +0 -0
  50. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/__init__.py +0 -0
  51. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/__main__.py +0 -0
  52. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/aoi.py +0 -0
  53. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/__init__.py +0 -0
  54. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/entity.py +0 -0
  55. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/namespaces/__init__.py +0 -0
  56. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/namespaces/plot.py +0 -0
  57. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/result.py +0 -0
  58. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/base/view.py +0 -0
  59. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/entities/__init__.py +0 -0
  60. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/entities/farms.py +0 -0
  61. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/entities/mws.py +0 -0
  62. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/entities/tehsil.py +0 -0
  63. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/entities/waterbody.py +0 -0
  64. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/export/__init__.py +0 -0
  65. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/export/formats.py +0 -0
  66. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/py.typed +0 -0
  67. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/schema/__init__.py +0 -0
  68. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/schema/detection.py +0 -0
  69. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/schema/profile.py +0 -0
  70. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/utils/__init__.py +0 -0
  71. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/utils/paths.py +0 -0
  72. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/utils/polars_utils.py +0 -0
  73. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/utils/season.py +0 -0
  74. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/src/core_lens/utils/spatial.py +0 -0
  75. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/fixtures/generate_fixtures.py +0 -0
  76. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/conftest.py +0 -0
  77. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_aoi.py +0 -0
  78. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_entities.py +0 -0
  79. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_entity.py +0 -0
  80. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_export.py +0 -0
  81. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_main.py +0 -0
  82. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_plot.py +0 -0
  83. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_polars_utils.py +0 -0
  84. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_profile.py +0 -0
  85. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_result.py +0 -0
  86. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_schema_detection.py +0 -0
  87. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_schema_profile.py +0 -0
  88. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_season.py +0 -0
  89. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_season_config.py +0 -0
  90. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_spatial.py +0 -0
  91. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_stats.py +0 -0
  92. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/tests/unit/test_view.py +0 -0
  93. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/usage.md +0 -0
  94. {core_lens-0.1.dev190 → core_lens-0.1.dev191}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: core-lens
3
- Version: 0.1.dev190
3
+ Version: 0.1.dev191
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.dev190'
22
- __version_tuple__ = version_tuple = (0, 1, 'dev190')
21
+ __version__ = version = '0.1.dev191'
22
+ __version_tuple__ = version_tuple = (0, 1, 'dev191')
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -777,6 +777,17 @@ class StatsNamespace:
777
777
  Raises:
778
778
  ValueError: If ``mode``/``method`` invalid, observation count too low, or
779
779
  ``baseline`` and ``target_years`` overlap.
780
+
781
+ Notes:
782
+ **STL fitting scope.** For ``AnomalyTsMethod.STL`` the decomposition
783
+ (trend + seasonal + remainder) is fit on the *entire* series of each entity,
784
+ baseline and target years together. ``baseline`` is used only to calibrate the scale (standard deviation) of the remainder; ``target_years`` selects which points are scored. Scores are ``remainder / baseline_remainder_std``.
785
+
786
+ Because target observations take part in the fit, the trend and seasonal components can partly absorb target-period anomalies, shrinking the remainder and lowering the score. The effect grows with the size and duration of the anomaly; a sustained shift can be absorbed almost entirely into the trend. Scores therefore measure deviation from a locally fitted trend and seasonal pattern, not from the baseline's "normal" behaviour, and the method is conservative: it may miss anomalies but is unlikely to over-flag them.
787
+
788
+ A stricter alternative is to fit on the baseline only and forecast the target period (e.g. ``statsmodels.tsa.forecasting.stl.STLForecast``), scoring forecast errors against the baseline error spread. This is not implemented.
789
+
790
+ For sub-annual data, residuals are matched to rows by the finer time column (``date``/``period``), not by year, so each observation gets its own score.
780
791
  """
781
792
  if baseline is not None and target_years is not None:
782
793
  b_lo, b_hi = baseline
@@ -976,7 +987,8 @@ class StatsNamespace:
976
987
  # ---- CUSUM / STL: inherently sequential/per-entity, partition_by instead of filter-loop ----
977
988
  else:
978
989
  rows: list[dict[str, Any]] = []
979
- for sub in df.sort(year_col).partition_by(key, maintain_order=True):
990
+ sort_cols = list(dict.fromkeys([year_col, output_time_col]))
991
+ for sub in df.sort(sort_cols).partition_by(key, maintain_order=True):
980
992
  eid = sub[key][0]
981
993
  base_vals = (
982
994
  sub.filter(
@@ -1027,6 +1039,7 @@ class StatsNamespace:
1027
1039
  full = sub.filter(pl.col(column).is_not_null())
1028
1040
  full_vals = full[column].to_numpy().astype(float)
1029
1041
  full_years = full[year_col].to_list()
1042
+ full_times = full[output_time_col].to_list()
1030
1043
 
1031
1044
  period = (
1032
1045
  26 if len(full_vals) >= 52 else max(2, len(full_vals) // 2)
@@ -1036,13 +1049,13 @@ class StatsNamespace:
1036
1049
  3 * period
1037
1050
  ) # want ~3 cycles minimum for a trustworthy seasonal fit, not just 2
1038
1051
 
1039
- if len(full_vals) < min_required:
1040
- warnings.warn(
1041
- f"StatsNamespace.anomaly: entity {eid!r} has {len(full_vals)} points, "
1042
- f"period={period} needs >= {min_reliable} for a reliable STL fit. Skipping."
1043
- )
1044
- n_dropped += 1
1045
- continue
1052
+ # if len(full_vals) < min_reliable:
1053
+ # warnings.warn(
1054
+ # f"StatsNamespace.anomaly: entity {eid!r} has {len(full_vals)} points, "
1055
+ # f"period={period} needs >= {min_reliable} for a reliable STL fit. Skipping."
1056
+ # )
1057
+ # n_dropped += 1
1058
+ # continue
1046
1059
 
1047
1060
  if len(full_vals) < min_required:
1048
1061
  warnings.warn(
@@ -1058,21 +1071,21 @@ class StatsNamespace:
1058
1071
  res = STL(full_vals, period=period).fit()
1059
1072
  resid = res.resid
1060
1073
 
1061
- # Match residuals to years EXPLICITLY, not positionally —
1062
- # avoids misalignment if there are gaps in the series.
1063
- resid_by_year = dict(zip(full_years, resid))
1074
+ # Key residuals by the fine-grained time column (date/period).
1075
+ # Keying by year collapses ~26 fortnights into one value.
1076
+ resid_by_time = dict(zip(full_times, resid))
1064
1077
  base_resid = [
1065
- resid_by_year[y]
1066
- for y in full_years
1078
+ r
1079
+ for y, r in zip(full_years, resid)
1067
1080
  if baseline[0] <= y <= baseline[1]
1068
1081
  ]
1069
1082
  std = _sf(pl.Series(base_resid).std(ddof=1)) or 1.0
1070
1083
 
1071
1084
  ts_scores = [
1072
- float(resid_by_year[y] / std)
1073
- if y in resid_by_year
1085
+ float(resid_by_time[t] / std)
1086
+ if t in resid_by_time
1074
1087
  else float("nan")
1075
- for y in eval_years_lookup
1088
+ for t in eval_times
1076
1089
  ]
1077
1090
  ts_flags = [
1078
1091
  abs(s) > threshold if s == s else False
File without changes
File without changes
File without changes
File without changes
File without changes