DavMelchi commited on
Commit
b29ed17
Β·
1 Parent(s): 26cb917

wcel capacity 1st trial

Browse files
app.py CHANGED
@@ -134,13 +134,17 @@ if check_password():
134
  ),
135
  ],
136
  "KPI Analysis": [
 
 
 
 
137
  st.Page(
138
  "apps/kpi_analysis/wbts_capacty.py",
139
  title=" πŸ“Š WBTS Capacity BB and CE Analysis",
140
  ),
141
  st.Page(
142
- "apps/kpi_analysis/gsm_capacity.py",
143
- title=" πŸ“Š GSM Capacity Analysis",
144
  ),
145
  st.Page(
146
  "apps/kpi_analysis/lte_capacity.py",
 
134
  ),
135
  ],
136
  "KPI Analysis": [
137
+ st.Page(
138
+ "apps/kpi_analysis/gsm_capacity.py",
139
+ title=" πŸ“Š GSM Capacity Analysis",
140
+ ),
141
  st.Page(
142
  "apps/kpi_analysis/wbts_capacty.py",
143
  title=" πŸ“Š WBTS Capacity BB and CE Analysis",
144
  ),
145
  st.Page(
146
+ "apps/kpi_analysis/wcel_capacity.py",
147
+ title=" πŸ“Š WCEL Capacity Analysis",
148
  ),
149
  st.Page(
150
  "apps/kpi_analysis/lte_capacity.py",
apps/kpi_analysis/wcel_capacity.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import plotly.express as px
3
+ import streamlit as st
4
+
5
+ from process_kpi.process_wcel_capacity import (
6
+ WcelCapacity,
7
+ load_and_process_wcel_capacity_data,
8
+ )
9
+ from utils.convert_to_excel import convert_dfs
10
+
11
+ # Streamlit UI
12
+
13
+ st.title(" πŸ“Š WCEL Capacity Analysis")
14
+ doc_col, image_col = st.columns(2)
15
+
16
+ with doc_col:
17
+ st.write(
18
+ """This app allows you to analyze the capacity of WCELS in a network.
19
+ It provides insights into the utilization of BB and CE resources,
20
+ helping you identify potential capacity issues and plan for upgrades.
21
+
22
+ The report should be run with a minimum of 3 days of data.
23
+ - Daily Aggregated
24
+ - WCEL level
25
+ - Exported in CSV format.
26
+ """
27
+ )
28
+
29
+ with image_col:
30
+ st.image("./assets/wbts_capacity.png", width=400)
31
+
32
+ uploaded_file = st.file_uploader(
33
+ "Upload WCEL capacity report in CSV format", type="csv"
34
+ )
35
+
36
+ # num_last_days
37
+ # num_threshold_days
38
+ # availability_threshold
39
+ # iub_frameloss_threshold
40
+ # hsdpa_congestion_rate_iub_threshold
41
+ # fails_treshold
42
+
43
+
44
+ param_col1, param_col2, param_col3 = st.columns(3)
45
+ param_col4, param_col5, param_col6 = st.columns(3)
46
+
47
+
48
+ if uploaded_file is not None:
49
+ WcelCapacity.final_results = None
50
+ with param_col1:
51
+ num_last_days = st.number_input(
52
+ "Number of days for analysis",
53
+ min_value=3,
54
+ max_value=30,
55
+ value=7,
56
+ )
57
+ with param_col2:
58
+ num_threshold_days = st.number_input(
59
+ "Number of days for threshold",
60
+ min_value=1,
61
+ max_value=30,
62
+ value=2,
63
+ )
64
+ with param_col3:
65
+ availability_threshold = st.number_input(
66
+ "Availability threshold (%)", value=99, min_value=0, max_value=100
67
+ )
68
+ with param_col4:
69
+ iub_frameloss_threshold = st.number_input(
70
+ "IUB frameloss threshold (%)",
71
+ value=100,
72
+ min_value=0,
73
+ max_value=10000000,
74
+ )
75
+ with param_col5:
76
+ hsdpa_congestion_rate_iub_threshold = st.number_input(
77
+ "HSDPA Congestion Rate IUB threshold (%)",
78
+ value=10,
79
+ min_value=0,
80
+ max_value=100,
81
+ )
82
+ with param_col6:
83
+ fails_treshold = st.number_input(
84
+ "Fails threshold (%)", value=10, min_value=0, max_value=10000000
85
+ )
86
+
87
+ if st.button("Analyze Data", type="primary"):
88
+ with st.spinner("Processing data..."):
89
+ results = load_and_process_wcel_capacity_data(
90
+ uploaded_file,
91
+ num_last_days,
92
+ num_threshold_days,
93
+ availability_threshold,
94
+ iub_frameloss_threshold,
95
+ hsdpa_congestion_rate_iub_threshold,
96
+ fails_treshold,
97
+ )
98
+
99
+ if results is not None:
100
+
101
+ kpi_df = results[0]
102
+
103
+ WcelCapacity.final_results = convert_dfs([kpi_df], ["kpi_df"])
104
+ st.download_button(
105
+ on_click="ignore",
106
+ type="primary",
107
+ label="Download the Analysis Report",
108
+ data=WcelCapacity.final_results,
109
+ file_name="WCEL_Capacity_Report.xlsx",
110
+ mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
111
+ )
112
+ st.write(kpi_df)
process_kpi/process_wcel_capacity.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+
3
+ from utils.kpi_analysis_utils import (
4
+ analyze_fails_kpi,
5
+ cell_availability_analysis,
6
+ combine_comments,
7
+ create_daily_date,
8
+ create_dfs_per_kpi,
9
+ kpi_naming_cleaning,
10
+ summarize_fails_comments,
11
+ )
12
+
13
+ tx_comments_mapping = {
14
+ "iub_frameloss exceeded threshold": "iub frameloss",
15
+ "iub_frameloss exceeded threshold, hsdpa_congestion_rate_iub exceeded threshold": "iub frameloss and hsdpa iub congestion",
16
+ "hsdpa_congestion_rate_iub exceeded threshold": "hsdpa iub congestion",
17
+ }
18
+ operational_comments_mapping = {
19
+ "Down Site": "Down Cell",
20
+ "iub frameloss, instability": "Availability and TX issues",
21
+ "iub frameloss and hsdpa iub congestion, Availability OK": "TX issues",
22
+ "iub frameloss, Availability OK": "TX issues",
23
+ "critical instability": "Availability issues",
24
+ "iub frameloss, critical instability": "Availability and TX issues",
25
+ "iub frameloss and hsdpa iub congestion, instability": "Availability and TX issues",
26
+ "Availability OK": "Site OK",
27
+ "hsdpa iub congestion, instability": "Availability and TX issues",
28
+ "instability": "Availability issues",
29
+ "hsdpa iub congestion, Availability OK": "TX issues",
30
+ "iub frameloss and hsdpa iub congestion, critical instability": "Availability and TX issues",
31
+ "hsdpa iub congestion, critical instability": "Availability and TX issues",
32
+ }
33
+
34
+ KPI_COLUMNS = [
35
+ "WCEL_name",
36
+ "date",
37
+ "Cell_Availability_excluding_blocked_by_user_state_BLU",
38
+ "Total_CS_traffic_Erl",
39
+ "HSDPA_TRAFFIC_VOLUME",
40
+ "HSDPA_USER_THROUGHPUT",
41
+ "Max_simult_HSDPA_users",
42
+ "IUB_LOSS_CC_FRAME_LOSS_IND_M1022C71",
43
+ "HSDPA_congestion_rate_in_Iub",
44
+ "rrc_conn_stp_fail_ac_M1001C3",
45
+ "RRC_CONN_STP_FAIL_AC_UL_M1001C731",
46
+ "RRC_CONN_STP_FAIL_AC_DL_M1001C732",
47
+ "RRC_CONN_STP_FAIL_AC_COD_M1001C733",
48
+ "rrc_conn_stp_fail_bts_M1001C4",
49
+ ]
50
+
51
+
52
+ class WcelCapacity:
53
+ final_results: pd.DataFrame = None
54
+
55
+
56
+ def wcel_kpi_analysis(
57
+ df: pd.DataFrame,
58
+ num_last_days: int,
59
+ num_threshold_days: int,
60
+ availability_threshold: int,
61
+ iub_frameloss_threshold: int,
62
+ hsdpa_congestion_rate_iub_threshold: int,
63
+ fails_treshold: int,
64
+ ) -> pd.DataFrame:
65
+ pivoted_kpi_dfs = create_dfs_per_kpi(
66
+ df=df,
67
+ pivot_date_column="date",
68
+ pivot_name_column="WCEL_name",
69
+ kpi_columns_from=2,
70
+ )
71
+ cell_availability_df = cell_availability_analysis(
72
+ df=pivoted_kpi_dfs["Cell_Availability_excluding_blocked_by_user_state_BLU"],
73
+ days=num_last_days,
74
+ availability_threshold=availability_threshold,
75
+ )
76
+
77
+ # Trafics, throughput and max users
78
+ trafic_cs_df = pivoted_kpi_dfs["Total_CS_traffic_Erl"]
79
+ hsdpa_traffic_df = pivoted_kpi_dfs["HSDPA_TRAFFIC_VOLUME"]
80
+ hsdpa_user_throughput_df = pivoted_kpi_dfs["HSDPA_USER_THROUGHPUT"]
81
+ max_simult_hsdpa_users_df = pivoted_kpi_dfs["Max_simult_HSDPA_users"]
82
+ # Add Max of Trafics, throughput and max users
83
+ trafic_cs_df["max_traffic_cs"] = trafic_cs_df.max(axis=1)
84
+ hsdpa_traffic_df["max_traffic_dl"] = hsdpa_traffic_df.max(axis=1)
85
+ hsdpa_user_throughput_df["max_dl_throughput"] = hsdpa_user_throughput_df.max(axis=1)
86
+ max_simult_hsdpa_users_df["max_users"] = max_simult_hsdpa_users_df.max(axis=1)
87
+ # add average of Trafics, throughput and max users
88
+ trafic_cs_df["avg_traffic_cs"] = trafic_cs_df.mean(axis=1)
89
+ hsdpa_traffic_df["avg_traffic_dl"] = hsdpa_traffic_df.mean(axis=1)
90
+ hsdpa_user_throughput_df["avg_dl_throughput"] = hsdpa_user_throughput_df.mean(
91
+ axis=1
92
+ )
93
+ max_simult_hsdpa_users_df["avg_users"] = max_simult_hsdpa_users_df.mean(axis=1)
94
+
95
+ # TX Congestion
96
+ iub_frameloss_df = pivoted_kpi_dfs["IUB_LOSS_CC_FRAME_LOSS_IND_M1022C71"]
97
+ hsdpa_congestion_rate_iub_df = pivoted_kpi_dfs["HSDPA_congestion_rate_in_Iub"]
98
+
99
+ iub_frameloss_df = analyze_fails_kpi(
100
+ df=iub_frameloss_df,
101
+ number_of_kpi_days=num_last_days,
102
+ number_of_threshold_days=num_threshold_days,
103
+ kpi_threshold=iub_frameloss_threshold,
104
+ kpi_column_name="iub_frameloss",
105
+ )
106
+ hsdpa_congestion_rate_iub_df = analyze_fails_kpi(
107
+ df=hsdpa_congestion_rate_iub_df,
108
+ number_of_kpi_days=num_last_days,
109
+ number_of_threshold_days=num_threshold_days,
110
+ kpi_threshold=hsdpa_congestion_rate_iub_threshold,
111
+ kpi_column_name="hsdpa_congestion_rate_iub",
112
+ )
113
+
114
+ # Fails
115
+ rrc_conn_stp_fail_ac_df = analyze_fails_kpi(
116
+ df=pivoted_kpi_dfs["rrc_conn_stp_fail_ac_M1001C3"],
117
+ number_of_kpi_days=num_last_days,
118
+ number_of_threshold_days=num_threshold_days,
119
+ kpi_threshold=fails_treshold,
120
+ kpi_column_name="rrc_fail_ac",
121
+ )
122
+ rrc_conn_stp_fail_ac_ul_df = analyze_fails_kpi(
123
+ df=pivoted_kpi_dfs["RRC_CONN_STP_FAIL_AC_UL_M1001C731"],
124
+ number_of_kpi_days=num_last_days,
125
+ number_of_threshold_days=num_threshold_days,
126
+ kpi_threshold=fails_treshold,
127
+ kpi_column_name="rrc_fail_ac_ul",
128
+ )
129
+ rrc_conn_stp_fail_ac_dl_df = analyze_fails_kpi(
130
+ df=pivoted_kpi_dfs["RRC_CONN_STP_FAIL_AC_DL_M1001C732"],
131
+ number_of_kpi_days=num_last_days,
132
+ number_of_threshold_days=num_threshold_days,
133
+ kpi_threshold=fails_treshold,
134
+ kpi_column_name="rrc_fail_ac_dl",
135
+ )
136
+ rrc_conn_stp_fail_ac_cod_df = analyze_fails_kpi(
137
+ df=pivoted_kpi_dfs["RRC_CONN_STP_FAIL_AC_COD_M1001C733"],
138
+ number_of_kpi_days=num_last_days,
139
+ number_of_threshold_days=num_threshold_days,
140
+ kpi_threshold=fails_treshold,
141
+ kpi_column_name="rrc_fail_code",
142
+ )
143
+ rrc_conn_stp_fail_bts_df = analyze_fails_kpi(
144
+ df=pivoted_kpi_dfs["rrc_conn_stp_fail_bts_M1001C4"],
145
+ number_of_kpi_days=num_last_days,
146
+ number_of_threshold_days=num_threshold_days,
147
+ kpi_threshold=fails_treshold,
148
+ kpi_column_name="rrc_fail_bts",
149
+ )
150
+
151
+ kpi_df = pd.concat(
152
+ [
153
+ cell_availability_df,
154
+ trafic_cs_df,
155
+ hsdpa_traffic_df,
156
+ hsdpa_user_throughput_df,
157
+ max_simult_hsdpa_users_df,
158
+ iub_frameloss_df,
159
+ hsdpa_congestion_rate_iub_df,
160
+ rrc_conn_stp_fail_ac_df,
161
+ rrc_conn_stp_fail_ac_ul_df,
162
+ rrc_conn_stp_fail_ac_dl_df,
163
+ rrc_conn_stp_fail_ac_cod_df,
164
+ rrc_conn_stp_fail_bts_df,
165
+ ],
166
+ axis=1,
167
+ )
168
+ kpi_df = kpi_df.reset_index()
169
+
170
+ kpi_df = combine_comments(
171
+ kpi_df,
172
+ "iub_frameloss_comment",
173
+ "hsdpa_congestion_rate_iub_comment",
174
+ new_column="tx_congestion_comments",
175
+ )
176
+ kpi_df["tx_congestion_comments"] = kpi_df["tx_congestion_comments"].apply(
177
+ lambda x: tx_comments_mapping.get(x, x)
178
+ )
179
+
180
+ kpi_df = combine_comments(
181
+ kpi_df,
182
+ "tx_congestion_comments",
183
+ "availability_comment_daily",
184
+ new_column="operational_comments",
185
+ )
186
+ kpi_df["operational_comments"] = kpi_df["operational_comments"].apply(
187
+ lambda x: operational_comments_mapping.get(x, x)
188
+ )
189
+ kpi_df = combine_comments(
190
+ kpi_df,
191
+ "rrc_fail_ac_comment",
192
+ "rrc_fail_ac_ul_comment",
193
+ "rrc_fail_ac_dl_comment",
194
+ "rrc_fail_code_comment",
195
+ "rrc_fail_bts_comment",
196
+ new_column="fails_comments",
197
+ )
198
+ kpi_df["fails_comments"] = kpi_df["fails_comments"].apply(summarize_fails_comments)
199
+ return [kpi_df]
200
+
201
+
202
+ def load_and_process_wcel_capacity_data(
203
+ uploaded_file: pd.DataFrame,
204
+ num_last_days: int,
205
+ num_threshold_days: int,
206
+ availability_threshold: int,
207
+ iub_frameloss_threshold: int,
208
+ hsdpa_congestion_rate_iub_threshold: int,
209
+ fails_treshold: int,
210
+ ) -> pd.DataFrame:
211
+ """
212
+ Load and process data for WCEL capacity analysis.
213
+
214
+ Args:
215
+ uploaded_file: Uploaded CSV file containing WCEL capacity data
216
+ num_last_days: Number of days for analysis
217
+ num_threshold_days: Minimum days above threshold to flag for upgrade
218
+ availability_threshold: Utilization threshold percentage for flagging
219
+ iub_frameloss_threshold: Utilization threshold percentage for flagging
220
+ hsdpa_congestion_rate_iub_threshold: Utilization threshold percentage for flagging
221
+ fails_treshold: Utilization threshold percentage for flagging
222
+
223
+ Returns:
224
+ Processed DataFrame with WCEL capacity analysis results
225
+ """
226
+ # Load data
227
+ df = pd.read_csv(uploaded_file, delimiter=";")
228
+ df = kpi_naming_cleaning(df)
229
+ df = create_daily_date(df)
230
+ df = df[KPI_COLUMNS]
231
+ df = wcel_kpi_analysis(
232
+ df,
233
+ num_last_days,
234
+ num_threshold_days,
235
+ availability_threshold,
236
+ iub_frameloss_threshold,
237
+ hsdpa_congestion_rate_iub_threshold,
238
+ fails_treshold,
239
+ )
240
+ return df
utils/kpi_analysis_utils.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import numpy as np
2
  import pandas as pd
3
 
@@ -283,6 +285,22 @@ def combine_comments(df: pd.DataFrame, *columns: str, new_column: str) -> pd.Dat
283
  return result_df
284
 
285
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
286
  def kpi_naming_cleaning(df: pd.DataFrame) -> pd.DataFrame:
287
  """
288
  Clean KPI column names by replacing special characters and standardizing format.
@@ -293,7 +311,7 @@ def kpi_naming_cleaning(df: pd.DataFrame) -> pd.DataFrame:
293
  Returns:
294
  DataFrame with cleaned column names
295
  """
296
- name_df = df.copy()
297
  name_df.columns = name_df.columns.str.replace("[ /(),-.']", "_", regex=True)
298
  name_df.columns = name_df.columns.str.replace("___", "_")
299
  name_df.columns = name_df.columns.str.replace("__", "_")
@@ -312,7 +330,7 @@ def create_daily_date(df: pd.DataFrame) -> pd.DataFrame:
312
  Returns:
313
  DataFrame with new date column and unnecessary columns removed
314
  """
315
- date_df = df.copy()
316
  date_df[["mois", "jour", "annee"]] = date_df["PERIOD_START_TIME"].str.split(
317
  ".", expand=True
318
  )
@@ -322,8 +340,8 @@ def create_daily_date(df: pd.DataFrame) -> pd.DataFrame:
322
  return date_df
323
 
324
 
325
- def create_hourly_date(df: pd.DataFrame):
326
- date_df = df
327
  date_df[["date_t", "hour"]] = date_df["PERIOD_START_TIME"].str.split(
328
  " ", expand=True
329
  )
@@ -590,3 +608,31 @@ def analyze_prb_usage(
590
  None,
591
  )
592
  return result_df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+
3
  import numpy as np
4
  import pandas as pd
5
 
 
285
  return result_df
286
 
287
 
288
+ def summarize_fails_comments(comment):
289
+ if not comment or pd.isna(comment) or comment.strip() == "":
290
+ return ""
291
+
292
+ # Extract all `rrc_fail_xxx` fields
293
+ matches = re.findall(r"rrc_fail_([a-z_]+)", comment)
294
+ if not matches:
295
+ return ""
296
+
297
+ # Remove duplicates, sort alphabetically
298
+ unique_sorted = sorted(set(matches))
299
+
300
+ # Combine and add 'fails'
301
+ return ", ".join(unique_sorted) + " fails"
302
+
303
+
304
  def kpi_naming_cleaning(df: pd.DataFrame) -> pd.DataFrame:
305
  """
306
  Clean KPI column names by replacing special characters and standardizing format.
 
311
  Returns:
312
  DataFrame with cleaned column names
313
  """
314
+ name_df: pd.DataFrame = df.copy()
315
  name_df.columns = name_df.columns.str.replace("[ /(),-.']", "_", regex=True)
316
  name_df.columns = name_df.columns.str.replace("___", "_")
317
  name_df.columns = name_df.columns.str.replace("__", "_")
 
330
  Returns:
331
  DataFrame with new date column and unnecessary columns removed
332
  """
333
+ date_df: pd.DataFrame = df.copy()
334
  date_df[["mois", "jour", "annee"]] = date_df["PERIOD_START_TIME"].str.split(
335
  ".", expand=True
336
  )
 
340
  return date_df
341
 
342
 
343
+ def create_hourly_date(df: pd.DataFrame) -> pd.DataFrame:
344
+ date_df: pd.DataFrame = df
345
  date_df[["date_t", "hour"]] = date_df["PERIOD_START_TIME"].str.split(
346
  " ", expand=True
347
  )
 
608
  None,
609
  )
610
  return result_df
611
+
612
+
613
+ def analyze_fails_kpi(
614
+ df: pd.DataFrame,
615
+ number_of_kpi_days: int,
616
+ number_of_threshold_days: int,
617
+ kpi_threshold: int,
618
+ kpi_column_name: str,
619
+ ) -> pd.DataFrame:
620
+ result_df: pd.DataFrame = df.copy()
621
+ last_days_df: pd.DataFrame = result_df.iloc[:, -number_of_kpi_days:]
622
+ # last_days_df = last_days_df.fillna(0)
623
+
624
+ result_df[f"avg_{kpi_column_name}"] = last_days_df.mean(axis=1).round(2)
625
+ result_df[f"max_{kpi_column_name}"] = last_days_df.max(axis=1)
626
+ # Count the number of days above threshold
627
+ result_df[f"number_of_days_with_{kpi_column_name}_exceeded"] = last_days_df.apply(
628
+ lambda row: sum(1 for x in row if x >= kpi_threshold), axis=1
629
+ )
630
+
631
+ # Add the {kpi_column_name}_comment : if number_of_days_with_{kpi_column_name}_exceeded_daily is >= number_of_threshold_days : {kpi_column_name} exceeded threshold , else : None
632
+ result_df[f"{kpi_column_name}_comment"] = np.where(
633
+ result_df[f"number_of_days_with_{kpi_column_name}_exceeded"]
634
+ >= number_of_threshold_days,
635
+ f"{kpi_column_name} exceeded threshold",
636
+ None,
637
+ )
638
+ return result_df