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)
|