DavMelchi commited on
Commit
0898fcc
·
1 Parent(s): 9d69630

Adding gsm paging analysis

Browse files
Changelog.md CHANGED
@@ -1,6 +1,11 @@
1
 
2
  # CHANGELOGS
3
 
 
 
 
 
 
4
  ## [0.2.8] - 2025-04-22
5
 
6
  - upgrade streamlit version to 1.44
 
1
 
2
  # CHANGELOGS
3
 
4
+ ## [0.2.9] - 2025-06-16
5
+
6
+ - Add paging analysis App
7
+ - Add capacity analysis App
8
+
9
  ## [0.2.8] - 2025-04-22
10
 
11
  - upgrade streamlit version to 1.44
README.md CHANGED
@@ -50,6 +50,8 @@ You can access the hosted version of the app at [https://davmelchi-db-query.hf.s
50
  - [x] Add the ability to select columns
51
  - [x] Add authentication
52
  - [x] Initial frequency distribution chart GSM
 
 
53
  - [ ] Improve Dashboard
54
  - [ ] Error handling
55
  - [ ] Add KPI analysis App
 
50
  - [x] Add the ability to select columns
51
  - [x] Add authentication
52
  - [x] Initial frequency distribution chart GSM
53
+ - [x] Add paging analysis App
54
+ - [x] Add capacity analysis App
55
  - [ ] Improve Dashboard
56
  - [ ] Error handling
57
  - [ ] Add KPI analysis App
app.py CHANGED
@@ -108,7 +108,7 @@ if check_password():
108
  layout="wide",
109
  initial_sidebar_state="expanded",
110
  menu_items={
111
- "About": "**📡 NPO DB Query v0.2.8**",
112
  },
113
  )
114
 
@@ -133,7 +133,7 @@ if check_password():
133
  "apps/import_physical_db.py", title="🌏Physical Database Verification"
134
  ),
135
  ],
136
- "KPI Analysis": [
137
  st.Page(
138
  "apps/kpi_analysis/gsm_capacity.py",
139
  title=" 📊 GSM Capacity Analysis",
@@ -155,6 +155,12 @@ if check_password():
155
  title=" 📊 LTE Capacity Analysis",
156
  ),
157
  ],
 
 
 
 
 
 
158
  "Documentations": [
159
  st.Page(
160
  "documentations/database_doc.py", title="📚Databases Documentation"
 
108
  layout="wide",
109
  initial_sidebar_state="expanded",
110
  menu_items={
111
+ "About": "**📡 NPO DB Query v0.2.9**",
112
  },
113
  )
114
 
 
133
  "apps/import_physical_db.py", title="🌏Physical Database Verification"
134
  ),
135
  ],
136
+ "Capacity Analysis": [
137
  st.Page(
138
  "apps/kpi_analysis/gsm_capacity.py",
139
  title=" 📊 GSM Capacity Analysis",
 
155
  title=" 📊 LTE Capacity Analysis",
156
  ),
157
  ],
158
+ "Paging Analysis": [
159
+ st.Page(
160
+ "apps/kpi_analysis/gsm_lac_load.py",
161
+ title=" 📊 GSM LAC Load Analysis",
162
+ ),
163
+ ],
164
  "Documentations": [
165
  st.Page(
166
  "documentations/database_doc.py", title="📚Databases Documentation"
apps/kpi_analysis/gsm_lac_load.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Tuple
2
+
3
+ import pandas as pd
4
+ import plotly.express as px
5
+ import streamlit as st
6
+
7
+ from queries.process_gsm import combined_gsm_database
8
+ from utils.convert_to_excel import convert_gsm_dfs, save_dataframe
9
+ from utils.kpi_analysis_utils import create_hourly_date, kpi_naming_cleaning
10
+
11
+ # Constants
12
+ GSM_COLUMNS = [
13
+ "ID_BTS",
14
+ "BSC",
15
+ "code",
16
+ "Region",
17
+ "locationAreaIdLAC",
18
+ "Longitude",
19
+ "Latitude",
20
+ ]
21
+
22
+ TRX_COLUMNS = [
23
+ "ID_BTS",
24
+ "number_trx_per_cell",
25
+ "number_tch_per_cell",
26
+ "number_sd_per_cell",
27
+ "number_bcch_per_cell",
28
+ "number_ccch_per_cell",
29
+ "number_cbc_per_cell",
30
+ "number_total_channels_per_cell",
31
+ "number_signals_per_cell",
32
+ ]
33
+
34
+ KPI_COLUMNS = [
35
+ "BSC_name",
36
+ "BCF_name",
37
+ "BTS_name",
38
+ "Paging_messages_on_air_interface",
39
+ "DELETE_PAGING_COMMAND_c003038",
40
+ "datetime",
41
+ "date",
42
+ "hour",
43
+ ]
44
+
45
+
46
+ def get_gsm_databases(dump_path: str) -> pd.DataFrame:
47
+ """
48
+ Process GSM database dump and return combined DataFrame with BTS and TRX data.
49
+
50
+ Args:
51
+ dump_path: Path to the GSM dump file
52
+
53
+ Returns:
54
+ pd.DataFrame: Combined DataFrame with BTS and TRX information
55
+ """
56
+ dfs = combined_gsm_database(dump_path)
57
+ bts_df: pd.DataFrame = dfs[0]
58
+ trx_df: pd.DataFrame = dfs[2]
59
+
60
+ # Clean GSM df
61
+ bts_df = bts_df[GSM_COLUMNS]
62
+ trx_df = trx_df[TRX_COLUMNS]
63
+ trx_df = trx_df.drop_duplicates(subset=["ID_BTS"])
64
+
65
+ gsm_df = pd.merge(bts_df, trx_df, on="ID_BTS", how="left")
66
+
67
+ # Create BSC_Lac column
68
+ gsm_df["BSC_Lac"] = (
69
+ gsm_df["BSC"].astype(str) + "_" + gsm_df["locationAreaIdLAC"].astype(str)
70
+ )
71
+
72
+ # Calculate number of TRX per LAC
73
+ gsm_df["number_trx_per_lac"] = gsm_df.groupby("BSC_Lac")[
74
+ "number_trx_per_cell"
75
+ ].transform("sum")
76
+
77
+ return gsm_df
78
+
79
+
80
+ def analyze_lac_load_kpi(hourly_report_path: str) -> pd.DataFrame:
81
+ """
82
+ Process hourly KPI report and prepare it for LAC load analysis.
83
+
84
+ Args:
85
+ hourly_report_path: Path to the hourly KPI report CSV file
86
+
87
+ Returns:
88
+ pd.DataFrame: Processed DataFrame with KPI data
89
+ """
90
+ df = pd.read_csv(hourly_report_path, delimiter=";")
91
+ df = kpi_naming_cleaning(df)
92
+ df = create_hourly_date(df)
93
+ df = df[KPI_COLUMNS]
94
+
95
+ # Clean and process BTS codes
96
+ df = df[df["BTS_name"].str.len() >= 5]
97
+ df["code"] = df["BTS_name"].str.split("_").str[0]
98
+ df["code"] = pd.to_numeric(df["code"], errors="coerce").fillna(0).astype(int)
99
+
100
+ return df
101
+
102
+
103
+ def analyze_lac_load(dump_path: str, hourly_report_path: str) -> List[pd.DataFrame]:
104
+ """
105
+ Analyze LAC load from GSM dump and hourly KPI report.
106
+
107
+ Args:
108
+ dump_path: Path to the GSM dump file
109
+ hourly_report_path: Path to the hourly KPI report CSV file
110
+
111
+ Returns:
112
+ List containing two DataFrames: [lac_load_df, max_paging_df]
113
+ """
114
+ gsm_df = get_gsm_databases(dump_path)
115
+ lac_load_df = analyze_lac_load_kpi(hourly_report_path)
116
+ lac_load_df = pd.merge(gsm_df, lac_load_df, on="code", how="left")
117
+
118
+ # Aggregate data
119
+ lac_load_df = (
120
+ lac_load_df.groupby(
121
+ [
122
+ "datetime",
123
+ "date",
124
+ "hour",
125
+ "BSC_name",
126
+ "BSC_Lac",
127
+ "number_trx_per_lac",
128
+ ]
129
+ )
130
+ .agg(
131
+ {
132
+ "Paging_messages_on_air_interface": "max",
133
+ "DELETE_PAGING_COMMAND_c003038": "max",
134
+ }
135
+ )
136
+ .reset_index()
137
+ )
138
+
139
+ # Get max paging messages
140
+ max_paging_messages = lac_load_df.sort_values(
141
+ by=["BSC_Lac", "Paging_messages_on_air_interface"], ascending=False
142
+ ).drop_duplicates(subset=["BSC_Lac"], keep="first")[
143
+ [
144
+ "BSC_name",
145
+ "BSC_Lac",
146
+ "number_trx_per_lac",
147
+ "Paging_messages_on_air_interface",
148
+ ]
149
+ ]
150
+
151
+ # Get max delete paging commands
152
+ max_delete_paging = lac_load_df.sort_values(
153
+ by=["BSC_Lac", "DELETE_PAGING_COMMAND_c003038"], ascending=False
154
+ ).drop_duplicates(subset=["BSC_Lac"], keep="first")[
155
+ ["BSC_name", "BSC_Lac", "DELETE_PAGING_COMMAND_c003038"]
156
+ ]
157
+
158
+ # Merge results
159
+ max_paging_df = pd.merge(
160
+ max_paging_messages,
161
+ max_delete_paging,
162
+ on=["BSC_name", "BSC_Lac"],
163
+ how="left",
164
+ )
165
+
166
+ # Calculate utilization (paging/640800)
167
+ max_paging_df["Utilization"] = (
168
+ (max_paging_df["Paging_messages_on_air_interface"] / 640800) * 100
169
+ ).round(2)
170
+
171
+ return [lac_load_df, max_paging_df]
172
+
173
+
174
+ def display_ui() -> None:
175
+ """Display the Streamlit user interface."""
176
+ st.title(" 📊 GSM LAC Load Analysis")
177
+ doc_col, image_col = st.columns(2)
178
+
179
+ with doc_col:
180
+ st.write(
181
+ """
182
+ The report should be run with a minimum of 7 days of data.
183
+ - Dump file required
184
+ - Hourly Report in CSV format
185
+ """
186
+ )
187
+
188
+ with image_col:
189
+ st.image("./assets/gsm_lac_load.png", width=250)
190
+
191
+
192
+ @st.fragment
193
+ def display_filtered_lac_load(lac_load_df: pd.DataFrame) -> None:
194
+ """
195
+ Display filtered LAC load data with interactive charts.
196
+
197
+ Args:
198
+ lac_load_df: DataFrame containing LAC load data
199
+ """
200
+ st.write("### Filtered LAC Load by BSC and BSC_Lac")
201
+
202
+ bsc_col, bsc_lac_col = st.columns(2)
203
+
204
+ with bsc_col:
205
+ selected_bsc = st.multiselect(
206
+ "Select BSC",
207
+ lac_load_df["BSC_name"].unique(),
208
+ key="selected_bsc",
209
+ default=[lac_load_df["BSC_name"].unique()[0]],
210
+ )
211
+
212
+ with bsc_lac_col:
213
+ selected_bsc_lac = st.multiselect(
214
+ "Select BSC_Lac",
215
+ lac_load_df[lac_load_df["BSC_name"].isin(selected_bsc)]["BSC_Lac"].unique(),
216
+ key="selected_bsc_lac",
217
+ default=lac_load_df[lac_load_df["BSC_name"].isin(selected_bsc)][
218
+ "BSC_Lac"
219
+ ].unique(),
220
+ )
221
+
222
+ filtered_lac_load_df = lac_load_df[
223
+ lac_load_df["BSC_name"].isin(selected_bsc)
224
+ & lac_load_df["BSC_Lac"].isin(selected_bsc_lac)
225
+ ]
226
+
227
+ # Display charts
228
+ chart1, chart2 = st.columns(2)
229
+ with chart1:
230
+ st.write("### Paging Messages on Air Interface")
231
+ fig1 = px.line(
232
+ filtered_lac_load_df,
233
+ x="datetime",
234
+ y="Paging_messages_on_air_interface",
235
+ color="BSC_Lac",
236
+ title="Max Paging Messages on Air Interface Per BSC_Lac",
237
+ )
238
+ fig1.update_layout(
239
+ xaxis_title="<b>Datetime</b>",
240
+ yaxis_title="<b>Paging Messages on Air Interface</b>",
241
+ )
242
+ fig1.add_hline(y=256000, line_color="red", line_dash="dash", line_width=2)
243
+ st.plotly_chart(fig1)
244
+
245
+ with chart2:
246
+ st.write("### Delete Paging Commands")
247
+ fig2 = px.line(
248
+ filtered_lac_load_df,
249
+ x="datetime",
250
+ y="DELETE_PAGING_COMMAND_c003038",
251
+ color="BSC_Lac",
252
+ title="Max Delete Paging Commands Per BSC_Lac",
253
+ )
254
+ fig2.update_layout(
255
+ xaxis_title="<b>Datetime</b>",
256
+ yaxis_title="<b>Delete Paging Commands</b>",
257
+ )
258
+ st.plotly_chart(fig2)
259
+
260
+ st.write("### Filtered LAC Load Data")
261
+ st.dataframe(filtered_lac_load_df)
262
+
263
+
264
+ def main() -> None:
265
+ """Main function to run the Streamlit app."""
266
+ display_ui()
267
+
268
+ # File uploaders
269
+ file1, file2 = st.columns(2)
270
+ with file1:
271
+ uploaded_dump = st.file_uploader("Upload Dump file in xlsb format", type="xlsb")
272
+ with file2:
273
+ uploaded_hourly_report = st.file_uploader(
274
+ "Upload Hourly Report in CSV format", type="csv"
275
+ )
276
+
277
+ if uploaded_dump is not None and uploaded_hourly_report is not None:
278
+ if st.button("Analyze Data", type="primary"):
279
+ with st.spinner("Analyzing data..."):
280
+ dfs = analyze_lac_load(
281
+ dump_path=uploaded_dump,
282
+ hourly_report_path=uploaded_hourly_report,
283
+ )
284
+
285
+ lac_load_df = dfs[0]
286
+ max_paging_df = dfs[1]
287
+
288
+ if lac_load_df is not None and "lac_load_df" not in st.session_state:
289
+ st.session_state.lac_load_df = lac_load_df
290
+ st.write("### LAC Load and Utilization with Max Paging 640800")
291
+ st.dataframe(max_paging_df)
292
+ display_filtered_lac_load(lac_load_df)
293
+
294
+
295
+ if __name__ == "__main__":
296
+ main()
assets/gsm_lac_load.png ADDED