pvclipping 0.1.0__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.
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: pvclipping
3
+ Version: 0.1.0
4
+ Summary: Clipping detection helpers for the solar plant rule engine
5
+ Requires-Python: >=3.7
6
+ Requires-Dist: numpy>=1.20
7
+ Requires-Dist: pandas>=1.3
8
+ Description-Content-Type: text/markdown
9
+
10
+ # pvclipping
11
+
12
+ Inverter clipping detection helper for the solar plant rule engine.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install pvclipping
18
+ ```
19
+
20
+ ## Usage with RuleParser
21
+
22
+ ```python
23
+ from pvclipping import get_custom_funcs
24
+
25
+ parser = RuleParser(
26
+ plant_id=...,
27
+ meter_dim=meter_dim,
28
+ postgres_engine=engine,
29
+ influx_engine=clickhouse,
30
+ reading_dim=reading_dim,
31
+ custom_func=get_custom_funcs(),
32
+ )
33
+ ```
34
+
35
+ ## Rule JSON
36
+
37
+ ```json
38
+ {
39
+ "steps": [
40
+ {
41
+ "name": "irr_filter#GLOBAL",
42
+ "type": "filter",
43
+ "value": "$$Tilt_Irr#GLOBAL$$ > 400"
44
+ },
45
+ {
46
+ "name": "Power_Active#INV",
47
+ "value": "$$Power_Active#INV$$.round(0)",
48
+ "filter": "irr_filter#GLOBAL",
49
+ "all_ticks": true
50
+ },
51
+ {
52
+ "name": "AC_Capacity#INV",
53
+ "value": "$$AC_Capacity#INV$$",
54
+ "all_ticks": true,
55
+ "fill_missing": true
56
+ },
57
+ {
58
+ "name": "Clipping_status#INV",
59
+ "value": "detect_clipping(df['Power_Active#INV'], df['AC_Capacity#INV'])",
60
+ "eval_meter": true
61
+ },
62
+ {
63
+ "name": "Clipping_status_F#INV",
64
+ "type": "filter",
65
+ "value": "$$Clipping_status#INV$$ > 0"
66
+ }
67
+ ],
68
+ "output": ["Clipping_status_F#INV"]
69
+ }
70
+ ```
71
+
72
+ ## Function signature
73
+
74
+ ```python
75
+ detect_clipping(
76
+ power, # pd.Series — active power, pre-filtered to evaluation window
77
+ capacity, # pd.Series — AC capacity, forward-filled
78
+ window=5, # rolling window size in ticks
79
+ std_threshold=1.0, # max rolling std (kW) to qualify as a flat plateau
80
+ min_power_fraction=0.2, # min power fraction of capacity to exclude standby
81
+ )
82
+ ```
@@ -0,0 +1,73 @@
1
+ # pvclipping
2
+
3
+ Inverter clipping detection helper for the solar plant rule engine.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install pvclipping
9
+ ```
10
+
11
+ ## Usage with RuleParser
12
+
13
+ ```python
14
+ from pvclipping import get_custom_funcs
15
+
16
+ parser = RuleParser(
17
+ plant_id=...,
18
+ meter_dim=meter_dim,
19
+ postgres_engine=engine,
20
+ influx_engine=clickhouse,
21
+ reading_dim=reading_dim,
22
+ custom_func=get_custom_funcs(),
23
+ )
24
+ ```
25
+
26
+ ## Rule JSON
27
+
28
+ ```json
29
+ {
30
+ "steps": [
31
+ {
32
+ "name": "irr_filter#GLOBAL",
33
+ "type": "filter",
34
+ "value": "$$Tilt_Irr#GLOBAL$$ > 400"
35
+ },
36
+ {
37
+ "name": "Power_Active#INV",
38
+ "value": "$$Power_Active#INV$$.round(0)",
39
+ "filter": "irr_filter#GLOBAL",
40
+ "all_ticks": true
41
+ },
42
+ {
43
+ "name": "AC_Capacity#INV",
44
+ "value": "$$AC_Capacity#INV$$",
45
+ "all_ticks": true,
46
+ "fill_missing": true
47
+ },
48
+ {
49
+ "name": "Clipping_status#INV",
50
+ "value": "detect_clipping(df['Power_Active#INV'], df['AC_Capacity#INV'])",
51
+ "eval_meter": true
52
+ },
53
+ {
54
+ "name": "Clipping_status_F#INV",
55
+ "type": "filter",
56
+ "value": "$$Clipping_status#INV$$ > 0"
57
+ }
58
+ ],
59
+ "output": ["Clipping_status_F#INV"]
60
+ }
61
+ ```
62
+
63
+ ## Function signature
64
+
65
+ ```python
66
+ detect_clipping(
67
+ power, # pd.Series — active power, pre-filtered to evaluation window
68
+ capacity, # pd.Series — AC capacity, forward-filled
69
+ window=5, # rolling window size in ticks
70
+ std_threshold=1.0, # max rolling std (kW) to qualify as a flat plateau
71
+ min_power_fraction=0.2, # min power fraction of capacity to exclude standby
72
+ )
73
+ ```
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pvclipping"
7
+ version = "0.1.0"
8
+ description = "Clipping detection helpers for the solar plant rule engine"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ dependencies = [
12
+ "numpy>=1.20",
13
+ "pandas>=1.3",
14
+ ]
15
+
16
+ [tool.hatch.build.targets.wheel]
17
+ packages = ["src/pvclipping"]
@@ -0,0 +1,16 @@
1
+ from .clipping import detect_clipping
2
+
3
+ __all__ = ["detect_clipping", "get_custom_funcs"]
4
+
5
+
6
+ def get_custom_funcs() -> dict:
7
+ """Return all pvclipping helpers ready for RuleParser's custom_func param.
8
+
9
+ Usage::
10
+
11
+ from pvclipping import get_custom_funcs
12
+ parser = RuleParser(..., custom_func=get_custom_funcs())
13
+ """
14
+ return {
15
+ "detect_clipping": detect_clipping,
16
+ }
@@ -0,0 +1,35 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+
5
+ def detect_clipping(
6
+ power: pd.Series,
7
+ capacity: pd.Series,
8
+ window: int = 5,
9
+ std_threshold: float = 1.0,
10
+ min_power_fraction: float = 0.2,
11
+ ) -> pd.Series:
12
+ """Detect inverter power clipping ticks via rolling-std plateau detection.
13
+
14
+ Intended for use inside a rule JSON step with eval_meter=true so that
15
+ the function is applied independently per inverter.
16
+
17
+ Args:
18
+ power: Active power series, pre-filtered to the evaluation window
19
+ (e.g. irradiance > 400 W/m²). NaN on excluded ticks.
20
+ capacity: AC capacity series, forward-filled so every tick has a value.
21
+ window: Rolling window size in ticks used to compute std (default 5).
22
+ std_threshold: Max rolling std (kW) to qualify as a flat plateau (default 1.0).
23
+ min_power_fraction: Min power as a fraction of capacity to exclude
24
+ standby/low-output ticks (default 0.2 → 20%).
25
+
26
+ Returns:
27
+ Series with the active power value on clipping ticks, NaN otherwise.
28
+ Index is preserved from ``power``.
29
+ """
30
+ min_threshold = capacity * min_power_fraction
31
+ rolling_std = (
32
+ power.rolling(window=window, min_periods=window).std().fillna(99)
33
+ )
34
+ is_plateau = (rolling_std < std_threshold) & (power > min_threshold)
35
+ return pd.Series(np.where(is_plateau, power, np.nan), index=power.index)