DavMelchi commited on
Commit
824d7bb
·
1 Parent(s): d13ea55

Align 2G CIQ output with final schema

Browse files
queries/ciq_2g_final_schema.json ADDED
@@ -0,0 +1,743 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "BTS": {
3
+ "columns": [
4
+ "site",
5
+ "bscid",
6
+ "cellId",
7
+ "bcfId",
8
+ "btsId",
9
+ "Check",
10
+ "bsIdentityCodeNCC",
11
+ "bsIdentityCodeBCC",
12
+ "locationAreaIdLAC",
13
+ "locationAreaIdMCC",
14
+ "locationAreaIdMNC",
15
+ "usedMobileAllocation",
16
+ "malId",
17
+ "name",
18
+ "template_name",
19
+ "sectorId",
20
+ "masterBCF",
21
+ "allowIMSIAttachDetach",
22
+ "amhLowerLoadThreshold",
23
+ "amhMaxLoadOfTgtCell",
24
+ "amhTrhoGuardTime",
25
+ "amhUpperLoadThreshold",
26
+ "btsIsHopping",
27
+ "btsLoadInSeg",
28
+ "btsLoadThreshold",
29
+ "btsMeasAver",
30
+ "callReestablishmentAllowed",
31
+ "cellBarQualify",
32
+ "cellBarred",
33
+ "cellLoadForChannelSearch",
34
+ "cellNumberInBtsHw",
35
+ "cellReselectHysteresis",
36
+ "cellReselectOffset",
37
+ "cellReselectParamInd",
38
+ "cnThreshold",
39
+ "diversityUsed",
40
+ "dlNoiseLevel",
41
+ "drInUse",
42
+ "drMethod",
43
+ "dtxMode",
44
+ "earlySendingIndication",
45
+ "emergencyCallRestricted",
46
+ "fddQMin",
47
+ "fddQOffset",
48
+ "frequencyBandInUse",
49
+ "hoppingMode",
50
+ "hoppingSequenceNumber1",
51
+ "hoppingSequenceNumber2",
52
+ "idleStateBcchAllocListId",
53
+ "idrCellType",
54
+ "idrUsed",
55
+ "interferenceAveragingProcessAverPeriod",
56
+ "interferenceAveragingProcessBoundary0",
57
+ "interferenceAveragingProcessBoundary1",
58
+ "interferenceAveragingProcessBoundary2",
59
+ "interferenceAveragingProcessBoundary3",
60
+ "interferenceAveragingProcessBoundary4",
61
+ "interferenceAveragingProcessBoundary5",
62
+ "lowPriorityThr",
63
+ "maioOffset",
64
+ "maxNumberOfRepetition",
65
+ "maxNumberRetransmission",
66
+ "maxQueueLength",
67
+ "maxTimeLimitDirectedRetry",
68
+ "measListUsedDuringMeas",
69
+ "measurementBCCHAllocation",
70
+ "minMsTxPower",
71
+ "minTimeLimitDirectedRetry",
72
+ "msMaxDistInCallSetup",
73
+ "msPriorityUsedInQueueing",
74
+ "msTxPwrMaxCCH",
75
+ "msTxPwrMaxCCH1x00",
76
+ "msTxPwrMaxGSM",
77
+ "msTxPwrMaxGSM1x00",
78
+ "multiBandCell",
79
+ "multiBandCellReporting",
80
+ "nbrOfSlotsSpreadTrans",
81
+ "newEstabCausesSupport",
82
+ "noOfBlocksForAccessGrant",
83
+ "noOfMFramesBetweenPaging",
84
+ "penaltyTime",
85
+ "powerOffset",
86
+ "prioritySearchThr",
87
+ "qSearchI",
88
+ "qSearchP",
89
+ "queuePriorityNonUrgentHo",
90
+ "queuePriorityUsed",
91
+ "queueingPriorityCall",
92
+ "queueingPriorityHandover",
93
+ "radioLinkTimeout",
94
+ "radioLinkTimeoutUlIncreaseStep",
95
+ "reselectionAlgorithmHysteresis",
96
+ "rxLevAccessMin",
97
+ "segmentId",
98
+ "smsCbUsed",
99
+ "stircEnabled",
100
+ "temporaryOffset",
101
+ "timeHysteresis",
102
+ "timeLimitCall",
103
+ "timeLimitHandover",
104
+ "timerPeriodicUpdateMs",
105
+ "trxPriorityInTchAlloc",
106
+ "ulNoiseLevel",
107
+ "utranQualRxLevelMargin",
108
+ "utranThresholdReselection",
109
+ "wcdmaPriority",
110
+ "gprsCapacityThroughputFactor",
111
+ "fddRscpMin",
112
+ "fddQMinOffset"
113
+ ],
114
+ "defaults": {
115
+ "locationAreaIdMCC": 610,
116
+ "locationAreaIdMNC": 2,
117
+ "masterBCF": 1,
118
+ "allowIMSIAttachDetach": 1,
119
+ "amhLowerLoadThreshold": -1,
120
+ "amhMaxLoadOfTgtCell": 85,
121
+ "amhTrhoGuardTime": 30,
122
+ "amhUpperLoadThreshold": 30,
123
+ "btsIsHopping": 1,
124
+ "btsLoadInSeg": 70,
125
+ "btsLoadThreshold": 85,
126
+ "btsMeasAver": 2,
127
+ "callReestablishmentAllowed": 1,
128
+ "cellBarQualify": 0,
129
+ "cellBarred": 0,
130
+ "cellLoadForChannelSearch": 0,
131
+ "cellNumberInBtsHw": 255,
132
+ "cellReselectHysteresis": 7,
133
+ "cellReselectOffset": 0,
134
+ "cellReselectParamInd": 1,
135
+ "cnThreshold": 0,
136
+ "diversityUsed": 1,
137
+ "dlNoiseLevel": 3,
138
+ "drInUse": 1,
139
+ "drMethod": 0,
140
+ "dtxMode": 1,
141
+ "earlySendingIndication": 1,
142
+ "emergencyCallRestricted": 0,
143
+ "fddQMin": 7,
144
+ "fddQOffset": 8,
145
+ "hoppingMode": 2,
146
+ "hoppingSequenceNumber2": 0,
147
+ "idleStateBcchAllocListId": 0,
148
+ "idrCellType": 0,
149
+ "idrUsed": 0,
150
+ "interferenceAveragingProcessAverPeriod": 10,
151
+ "interferenceAveragingProcessBoundary0": 0,
152
+ "interferenceAveragingProcessBoundary1": 5,
153
+ "interferenceAveragingProcessBoundary2": 10,
154
+ "interferenceAveragingProcessBoundary3": 15,
155
+ "interferenceAveragingProcessBoundary4": 20,
156
+ "interferenceAveragingProcessBoundary5": 63,
157
+ "lowPriorityThr": 15,
158
+ "maioOffset": 0,
159
+ "maxNumberOfRepetition": 35,
160
+ "maxNumberRetransmission": 2,
161
+ "maxQueueLength": 50,
162
+ "maxTimeLimitDirectedRetry": 5,
163
+ "measListUsedDuringMeas": 1,
164
+ "measurementBCCHAllocation": 1,
165
+ "minMsTxPower": 13,
166
+ "minTimeLimitDirectedRetry": 0,
167
+ "msMaxDistInCallSetup": 64,
168
+ "msPriorityUsedInQueueing": 0,
169
+ "msTxPwrMaxCCH": 2,
170
+ "msTxPwrMaxCCH1x00": 0,
171
+ "msTxPwrMaxGSM": 2,
172
+ "msTxPwrMaxGSM1x00": 0,
173
+ "multiBandCell": 1,
174
+ "multiBandCellReporting": 3,
175
+ "nbrOfSlotsSpreadTrans": 10,
176
+ "newEstabCausesSupport": 0,
177
+ "noOfBlocksForAccessGrant": 2,
178
+ "noOfMFramesBetweenPaging": 3,
179
+ "penaltyTime": 0,
180
+ "powerOffset": 0,
181
+ "prioritySearchThr": 15,
182
+ "qSearchI": 7,
183
+ "qSearchP": 7,
184
+ "queuePriorityNonUrgentHo": 9,
185
+ "queuePriorityUsed": 1,
186
+ "queueingPriorityCall": 10,
187
+ "queueingPriorityHandover": 9,
188
+ "radioLinkTimeout": 7,
189
+ "radioLinkTimeoutUlIncreaseStep": 2,
190
+ "reselectionAlgorithmHysteresis": 0,
191
+ "rxLevAccessMin": 8,
192
+ "smsCbUsed": 1,
193
+ "stircEnabled": 1,
194
+ "temporaryOffset": 0,
195
+ "timeHysteresis": 0,
196
+ "timeLimitCall": 6,
197
+ "timeLimitHandover": 3,
198
+ "timerPeriodicUpdateMs": 60,
199
+ "trxPriorityInTchAlloc": 0,
200
+ "ulNoiseLevel": 3,
201
+ "utranQualRxLevelMargin": 2,
202
+ "utranThresholdReselection": 3,
203
+ "wcdmaPriority": 4,
204
+ "gprsCapacityThroughputFactor": 80,
205
+ "fddRscpMin": 4,
206
+ "fddQMinOffset": 0
207
+ },
208
+ "formulas": {
209
+ "segmentId": {
210
+ "kind": "same_row_ref",
211
+ "source_column": "btsId"
212
+ }
213
+ }
214
+ },
215
+ "BTS_GPRS": {
216
+ "columns": [
217
+ "Site",
218
+ "bscid",
219
+ "cellId",
220
+ "bcfId",
221
+ "btsId",
222
+ "template_name",
223
+ "dedicatedGPRScapacity",
224
+ "egprsEnabled",
225
+ "egprsLinkAdaptEnabled",
226
+ "gprsEnabled",
227
+ "gprsMsTxPwrMaxCCH1x00",
228
+ "gprsMsTxpwrMaxCCH",
229
+ "gprsRxlevAccessMin",
230
+ "csAckDl",
231
+ "csAckUl",
232
+ "csUnackDl",
233
+ "csUnackUl",
234
+ "preferBCCHfreqGPRS2",
235
+ "raReselectHysteresis",
236
+ "egprsInitMcsAckMode",
237
+ "egprsInitMcsUnAckMode",
238
+ "maxGPRSCapacity",
239
+ "adaptiveLaAlgorithm",
240
+ "cs3Cs4Enabled",
241
+ "defaultGPRScapacity",
242
+ "directGPRSAccessBts",
243
+ "egprsMaxBlerAckMode",
244
+ "egprsMaxBlerUnAckMode",
245
+ "egprsMeanBepOffset8psk",
246
+ "egprsMeanBepOffsetGmsk",
247
+ "gprsDlPcEnabled",
248
+ "gprsNonBCCHRxlevLower",
249
+ "gprsNonBCCHRxlevUpper",
250
+ "hcsPriorityClass",
251
+ "hcsThreshold",
252
+ "pcuCsHopping",
253
+ "pcuCsNonHopping",
254
+ "pcuDlBlerCpHopping",
255
+ "pcuDlBlerCpNonHop",
256
+ "pcuDlLaRiskLevel",
257
+ "pcuUlBlerCpHopping",
258
+ "pcuUlBlerCpNonHop",
259
+ "pcuUlLaRiskLevel",
260
+ "throughputFactor_cs1cs4dlcs",
261
+ "throughputFactor_cs1cs4ulcs",
262
+ "throughputFactor_mcs1mcs4ulcs",
263
+ "throughputFactor_mcs1mcs9dlcs",
264
+ "throughputFactor_mcs1mcs9ulcs",
265
+ "inactEndTimeHour",
266
+ "inactEndTimeMinute",
267
+ "inactStartTimeHour",
268
+ "inactStartTimeMinute",
269
+ "inactWeekDays",
270
+ "nsei",
271
+ "psei",
272
+ "rac"
273
+ ],
274
+ "defaults": {
275
+ "template_name": "All",
276
+ "dedicatedGPRScapacity": 15,
277
+ "egprsEnabled": 1,
278
+ "egprsLinkAdaptEnabled": 2,
279
+ "gprsEnabled": 1,
280
+ "gprsMsTxPwrMaxCCH1x00": 2,
281
+ "gprsMsTxpwrMaxCCH": 2,
282
+ "gprsRxlevAccessMin": 10,
283
+ "csAckDl": 5,
284
+ "csAckUl": 5,
285
+ "csUnackDl": 5,
286
+ "csUnackUl": 5,
287
+ "preferBCCHfreqGPRS2": 1,
288
+ "raReselectHysteresis": 2,
289
+ "egprsInitMcsAckMode": 2,
290
+ "egprsInitMcsUnAckMode": 2,
291
+ "maxGPRSCapacity": 100,
292
+ "adaptiveLaAlgorithm": 0,
293
+ "cs3Cs4Enabled": 1,
294
+ "defaultGPRScapacity": 50,
295
+ "directGPRSAccessBts": 0,
296
+ "egprsMaxBlerAckMode": 90,
297
+ "egprsMaxBlerUnAckMode": 10,
298
+ "egprsMeanBepOffset8psk": 31,
299
+ "egprsMeanBepOffsetGmsk": 31,
300
+ "gprsDlPcEnabled": 1,
301
+ "gprsNonBCCHRxlevLower": 10,
302
+ "gprsNonBCCHRxlevUpper": 15,
303
+ "hcsPriorityClass": 0,
304
+ "hcsThreshold": 255,
305
+ "pcuCsHopping": 0,
306
+ "pcuCsNonHopping": 0,
307
+ "pcuDlBlerCpHopping": 20,
308
+ "pcuDlBlerCpNonHop": 90,
309
+ "pcuDlLaRiskLevel": 20,
310
+ "pcuUlBlerCpHopping": 24,
311
+ "pcuUlBlerCpNonHop": 90,
312
+ "pcuUlLaRiskLevel": 10,
313
+ "throughputFactor_cs1cs4dlcs": 12,
314
+ "throughputFactor_cs1cs4ulcs": 12,
315
+ "throughputFactor_mcs1mcs4ulcs": 16,
316
+ "throughputFactor_mcs1mcs9dlcs": 30,
317
+ "throughputFactor_mcs1mcs9ulcs": 30,
318
+ "inactEndTimeHour": 18,
319
+ "inactEndTimeMinute": 0,
320
+ "inactStartTimeHour": 8,
321
+ "inactStartTimeMinute": 0,
322
+ "inactWeekDays": 0,
323
+ "nsei": "",
324
+ "psei": 0,
325
+ "rac": 1
326
+ },
327
+ "formulas": {}
328
+ },
329
+ "BTS_AMR": {
330
+ "columns": [
331
+ "Site",
332
+ "bscId",
333
+ "cellId",
334
+ "bcfId",
335
+ "btsId",
336
+ "template_name",
337
+ "amrConfFrCodecModeSet",
338
+ "amrConfFrDlThreshold1",
339
+ "amrConfFrDlThreshold2",
340
+ "amrConfFrDlThreshold3",
341
+ "amrConfFrHysteresis1",
342
+ "amrConfFrHysteresis2",
343
+ "amrConfFrHysteresis3",
344
+ "amrConfFrInitCodecMode",
345
+ "amrConfFrStartMode",
346
+ "amrConfFrUlThreshold1",
347
+ "amrConfFrUlThreshold2",
348
+ "amrConfFrUlThreshold3",
349
+ "amrConfHrCodecModeSet",
350
+ "amrConfHrDlThreshold1",
351
+ "amrConfHrDlThreshold2",
352
+ "amrConfHrDlThreshold3",
353
+ "amrConfHrHysteresis1",
354
+ "amrConfHrHysteresis2",
355
+ "amrConfHrHysteresis3",
356
+ "amrConfHrInitCodecMode",
357
+ "amrConfHrStartMode",
358
+ "amrConfHrUlThreshold1",
359
+ "amrConfHrUlThreshold2",
360
+ "amrConfHrUlThreshold3",
361
+ "amrHoFrInHoThrDlRxQual",
362
+ "amrHoFrThrDlRxQual",
363
+ "amrHoFrThrUlRxQual",
364
+ "amrHoHrInHoThrDlRxQual",
365
+ "amrHoHrThrDlRxQual",
366
+ "amrHoHrThrUlRxQual",
367
+ "amrPocFrPcLThrDlRxQual",
368
+ "amrPocFrPcLThrUlRxQual",
369
+ "amrPocFrPcUThrDlRxQual",
370
+ "amrPocFrPcUThrUlRxQual",
371
+ "amrPocHrPcLThrDlRxQual",
372
+ "amrPocHrPcLThrUlRxQual",
373
+ "amrPocHrPcUThrDlRxQual",
374
+ "amrPocHrPcUThrUlRxQual",
375
+ "amrSegLoadDepTchRateLower",
376
+ "amrSegLoadDepTchRateUpper",
377
+ "radioLinkTimeoutAmrHr",
378
+ "radioLinkTimeoutAmr",
379
+ "btsSpLoadDepTchRateLower",
380
+ "btsSpLoadDepTchRateUpper",
381
+ "radioLinkTimeoutAmrHrUlIncreaseStep",
382
+ "radioLinkTimeoutAmrUlIncreaseStep",
383
+ "tchRateIntraCellHo"
384
+ ],
385
+ "defaults": {
386
+ "template_name": "All",
387
+ "amrConfFrCodecModeSet": 149,
388
+ "amrConfFrDlThreshold1": 14,
389
+ "amrConfFrDlThreshold2": 26,
390
+ "amrConfFrDlThreshold3": 52,
391
+ "amrConfFrHysteresis1": 8,
392
+ "amrConfFrHysteresis2": 8,
393
+ "amrConfFrHysteresis3": 8,
394
+ "amrConfFrInitCodecMode": 1,
395
+ "amrConfFrStartMode": 0,
396
+ "amrConfFrUlThreshold1": 18,
397
+ "amrConfFrUlThreshold2": 28,
398
+ "amrConfFrUlThreshold3": 46,
399
+ "amrConfHrCodecModeSet": 21,
400
+ "amrConfHrDlThreshold1": 40,
401
+ "amrConfHrDlThreshold2": 52,
402
+ "amrConfHrDlThreshold3": 52,
403
+ "amrConfHrHysteresis1": 8,
404
+ "amrConfHrHysteresis2": 8,
405
+ "amrConfHrHysteresis3": 8,
406
+ "amrConfHrInitCodecMode": 1,
407
+ "amrConfHrStartMode": 0,
408
+ "amrConfHrUlThreshold1": 40,
409
+ "amrConfHrUlThreshold2": 52,
410
+ "amrConfHrUlThreshold3": 52,
411
+ "amrHoFrInHoThrDlRxQual": 0,
412
+ "amrHoFrThrDlRxQual": 5,
413
+ "amrHoFrThrUlRxQual": 5,
414
+ "amrHoHrInHoThrDlRxQual": 5,
415
+ "amrHoHrThrDlRxQual": 5,
416
+ "amrHoHrThrUlRxQual": 5,
417
+ "amrPocFrPcLThrDlRxQual": 3,
418
+ "amrPocFrPcLThrUlRxQual": 3,
419
+ "amrPocFrPcUThrDlRxQual": 1,
420
+ "amrPocFrPcUThrUlRxQual": 1,
421
+ "amrPocHrPcLThrDlRxQual": 3,
422
+ "amrPocHrPcLThrUlRxQual": 3,
423
+ "amrPocHrPcUThrDlRxQual": 1,
424
+ "amrPocHrPcUThrUlRxQual": 1,
425
+ "amrSegLoadDepTchRateLower": 100,
426
+ "amrSegLoadDepTchRateUpper": 0,
427
+ "radioLinkTimeoutAmrHr": 7,
428
+ "radioLinkTimeoutAmr": 7,
429
+ "btsSpLoadDepTchRateLower": 100,
430
+ "btsSpLoadDepTchRateUpper": 0,
431
+ "radioLinkTimeoutAmrHrUlIncreaseStep": 2,
432
+ "radioLinkTimeoutAmrUlIncreaseStep": 2,
433
+ "tchRateIntraCellHo": 0
434
+ },
435
+ "formulas": {}
436
+ },
437
+ "HOC": {
438
+ "columns": [
439
+ "Site",
440
+ "bscid",
441
+ "cellId",
442
+ "bcfId",
443
+ "btsId",
444
+ "hocId",
445
+ "template_name",
446
+ "averagingWindowSizeAdjCell",
447
+ "ddeThresholdsLevRxLevel",
448
+ "enableIntraHoDl",
449
+ "enableIntraHoUl",
450
+ "enableMsDistance",
451
+ "enablePowerBudgetHo",
452
+ "enableSddchHandover",
453
+ "hoAvaragingLevDLWeighting",
454
+ "hoAvaragingLevDlWindowSize",
455
+ "hoAveragingLevUlWeighting",
456
+ "hoAveragingLevUlWindowSize",
457
+ "hoAveragingQualDlWeighting",
458
+ "hoAveragingQualDlWindowSize",
459
+ "hoAveragingQualUl",
460
+ "hoAveragingQualUlWindowSize",
461
+ "hoPeriodPbgt",
462
+ "hoTLDlRxLevel",
463
+ "hoTLUlRxLevel",
464
+ "hoTQDlRxQual",
465
+ "hoTQUlRxQual",
466
+ "hoThresholdsInterferenceDlRxLevel",
467
+ "hoThresholdsInterferenceULRxLevel",
468
+ "minIntBetweenUnsuccHoAttempt",
469
+ "msDistanceAveragingParamHreqave",
470
+ "msDistanceHoThresholdParamMsRangeMax",
471
+ "qSearchC",
472
+ "allAdjacentCellsAveraged",
473
+ "allUtranAdjAver",
474
+ "amhTrafficControlIUO",
475
+ "amhTrafficControlMCN",
476
+ "amhTrhoPbgtMargin",
477
+ "ddeThresholdsLevNx",
478
+ "ddeThresholdsLevPx",
479
+ "ddeWindow",
480
+ "enaFastAveCallSetup",
481
+ "enaFastAveHo",
482
+ "enaFastAvePc",
483
+ "enaHierCellHo",
484
+ "erfdEnabled",
485
+ "erfdOver",
486
+ "failMoveThreshold",
487
+ "gsmPlmnPriorisation",
488
+ "hoPeriodUmbrella",
489
+ "hoTLDlPx",
490
+ "hoTLUlNx",
491
+ "hoTLUlPx",
492
+ "hoTQDlNx",
493
+ "hoTQDlPx",
494
+ "hoTQUlNx",
495
+ "hoTQUlPx",
496
+ "hoThrInterferenceDlNx",
497
+ "hoThrInterferenceDlPx",
498
+ "hoThresholdsInterferenceULNx",
499
+ "hoThresholdsInterferenceULPx",
500
+ "hoThresholdsLevDLNx",
501
+ "hoThresholdsRapidLevUl",
502
+ "hoThresholdsRapidLevUlN",
503
+ "interSystemDa",
504
+ "intraHoLoRxLevLimAmrHr",
505
+ "intraHoLoRxQualLimAmr",
506
+ "intraHoUpRxLevLimAmrHr",
507
+ "lowerSpeedLimit",
508
+ "minIntBetweenHoReq",
509
+ "minIntUnsuccIsHo",
510
+ "modifiedAveWinNCell",
511
+ "modifiedNoz",
512
+ "msDHoThrParamN8",
513
+ "msDistanceHoThresholdParamP8",
514
+ "msSpeedAveraging",
515
+ "msSpeedDetectionState",
516
+ "msSpeedThresholdNx",
517
+ "msSpeedThresholdPx",
518
+ "multiratRep",
519
+ "noOfZeroResUtran",
520
+ "numberOfZeroResults",
521
+ "upperSpeedLimit",
522
+ "utranAveragingNumber",
523
+ "utranHoThScTpdc",
524
+ "wcdmaRanCellPenalty",
525
+ "enableUmbrellaHo"
526
+ ],
527
+ "defaults": {
528
+ "hocId": 1,
529
+ "averagingWindowSizeAdjCell": 10,
530
+ "ddeThresholdsLevRxLevel": 10,
531
+ "enableIntraHoDl": 0,
532
+ "enableIntraHoUl": 0,
533
+ "enableMsDistance": 0,
534
+ "enablePowerBudgetHo": 1,
535
+ "enableSddchHandover": "",
536
+ "hoAvaragingLevDLWeighting": 2,
537
+ "hoAvaragingLevDlWindowSize": 5,
538
+ "hoAveragingLevUlWeighting": 2,
539
+ "hoAveragingLevUlWindowSize": 5,
540
+ "hoAveragingQualDlWeighting": 3,
541
+ "hoAveragingQualDlWindowSize": 5,
542
+ "hoAveragingQualUl": "",
543
+ "hoAveragingQualUlWindowSize": 5,
544
+ "hoPeriodPbgt": 6,
545
+ "hoTLDlRxLevel": 15,
546
+ "hoTLUlRxLevel": 15,
547
+ "hoTQDlRxQual": 4,
548
+ "hoTQUlRxQual": 4,
549
+ "hoThresholdsInterferenceDlRxLevel": 12,
550
+ "hoThresholdsInterferenceULRxLevel": 3,
551
+ "minIntBetweenUnsuccHoAttempt": 20,
552
+ "msDistanceAveragingParamHreqave": 4,
553
+ "msDistanceHoThresholdParamMsRangeMax": 63,
554
+ "qSearchC": 15,
555
+ "allAdjacentCellsAveraged": 0,
556
+ "allUtranAdjAver": 0,
557
+ "amhTrafficControlIUO": 0,
558
+ "amhTrafficControlMCN": 0,
559
+ "amhTrhoPbgtMargin": -2,
560
+ "ddeThresholdsLevNx": 3,
561
+ "ddeThresholdsLevPx": 2,
562
+ "ddeWindow": 2,
563
+ "enaFastAveCallSetup": 0,
564
+ "enaFastAveHo": 1,
565
+ "enaFastAvePc": 1,
566
+ "enaHierCellHo": 0,
567
+ "erfdEnabled": 0,
568
+ "erfdOver": 10,
569
+ "failMoveThreshold": 50,
570
+ "gsmPlmnPriorisation": 0,
571
+ "hoPeriodUmbrella": 0,
572
+ "hoTLDlPx": 1,
573
+ "hoTLUlNx": 1,
574
+ "hoTLUlPx": 1,
575
+ "hoTQDlNx": 6,
576
+ "hoTQDlPx": 4,
577
+ "hoTQUlNx": 6,
578
+ "hoTQUlPx": 4,
579
+ "hoThrInterferenceDlNx": 1,
580
+ "hoThrInterferenceDlPx": 1,
581
+ "hoThresholdsInterferenceULNx": 1,
582
+ "hoThresholdsInterferenceULPx": 1,
583
+ "hoThresholdsLevDLNx": 1,
584
+ "hoThresholdsRapidLevUl": 0,
585
+ "hoThresholdsRapidLevUlN": 0,
586
+ "interSystemDa": 0,
587
+ "intraHoLoRxLevLimAmrHr": 63,
588
+ "intraHoLoRxQualLimAmr": 0,
589
+ "intraHoUpRxLevLimAmrHr": 0,
590
+ "lowerSpeedLimit": 0,
591
+ "minIntBetweenHoReq": 5,
592
+ "minIntUnsuccIsHo": 3,
593
+ "modifiedAveWinNCell": 2,
594
+ "modifiedNoz": 1,
595
+ "msDHoThrParamN8": 1,
596
+ "msDistanceHoThresholdParamP8": 1,
597
+ "msSpeedAveraging": 4,
598
+ "msSpeedDetectionState": 0,
599
+ "msSpeedThresholdNx": 6,
600
+ "msSpeedThresholdPx": 3,
601
+ "multiratRep": 2,
602
+ "noOfZeroResUtran": 5,
603
+ "numberOfZeroResults": 2,
604
+ "upperSpeedLimit": 0,
605
+ "utranAveragingNumber": 6,
606
+ "utranHoThScTpdc": 80,
607
+ "wcdmaRanCellPenalty": 127,
608
+ "enableUmbrellaHo": 1
609
+ },
610
+ "formulas": {}
611
+ },
612
+ "POC": {
613
+ "columns": [
614
+ "Site",
615
+ "bscid",
616
+ "cellId",
617
+ "bcfId",
618
+ "btsId",
619
+ "hocId",
620
+ "template_name",
621
+ "alpha",
622
+ "bepPeriod",
623
+ "bsTxPwrMax",
624
+ "bsTxPwrMax1x00",
625
+ "bsTxPwrMin",
626
+ "gamma",
627
+ "pcALDlWeighting",
628
+ "pcALDlWindowSize",
629
+ "pcALUlWeighting",
630
+ "pcALUlWindowSize",
631
+ "pcAQLDlWeighting",
632
+ "pcAQLDlWindowSize",
633
+ "pcAQLUlWeighting",
634
+ "pcAQLUlWindowSize",
635
+ "pcControlEnabled",
636
+ "pcControlInterval",
637
+ "pcIncrStepSize",
638
+ "pcLowerThresholdsLevDLRxLevel",
639
+ "pcLowerThresholsLevULRxLevel",
640
+ "pcRedStepSize",
641
+ "pcUpperThresholdsLevDLRxLevel",
642
+ "pcUpperThresholdsLevULRxLevel",
643
+ "tAvgT",
644
+ "tAvgW",
645
+ "pcLTQualDlRxQual",
646
+ "pcLTQualUlRxQual",
647
+ "pcUTQualDlRxQual",
648
+ "pcUTQualUlRxQual",
649
+ "dlPcPowerReduction",
650
+ "dlPcWindowSize",
651
+ "enableAla",
652
+ "maxPwrCompensation",
653
+ "minIntBetweenAla",
654
+ "pcLTLevDlNx",
655
+ "pcLTLevDlPx",
656
+ "pcLTLevUlNx",
657
+ "pcLTLevUlPx",
658
+ "pcLTQual144Nx",
659
+ "pcLTQual144Px",
660
+ "pcLTQual144RxQual",
661
+ "pcLTQualDlNx",
662
+ "pcLTQualDlPx",
663
+ "pcLTQualUlNx",
664
+ "pcLTQualUlPx",
665
+ "pcUTLevDlNx",
666
+ "pcUTLevDlPx",
667
+ "pcUTLevUlNx",
668
+ "pcUTLevUlPx",
669
+ "pcUTQualDlNx",
670
+ "pcUTQualDlPx",
671
+ "pcUTQualUlNx",
672
+ "pcUTQualUlPx",
673
+ "powerDecrQualFactor",
674
+ "powerLimitAla",
675
+ "pwrDecrLimitBand0",
676
+ "pwrDecrLimitBand1",
677
+ "pwrDecrLimitBand2",
678
+ "transmitPowerReduction"
679
+ ],
680
+ "defaults": {
681
+ "hocId": 1,
682
+ "alpha": 10,
683
+ "bepPeriod": 6,
684
+ "bsTxPwrMax": 0,
685
+ "bsTxPwrMax1x00": 0,
686
+ "bsTxPwrMin": 15,
687
+ "gamma": 15,
688
+ "pcALDlWeighting": 1,
689
+ "pcALDlWindowSize": 1,
690
+ "pcALUlWeighting": 1,
691
+ "pcALUlWindowSize": 4,
692
+ "pcAQLDlWeighting": 1,
693
+ "pcAQLDlWindowSize": 1,
694
+ "pcAQLUlWeighting": 1,
695
+ "pcAQLUlWindowSize": 4,
696
+ "pcControlEnabled": 1,
697
+ "pcControlInterval": 1,
698
+ "pcIncrStepSize": 1,
699
+ "pcLowerThresholdsLevDLRxLevel": 25,
700
+ "pcLowerThresholsLevULRxLevel": 20,
701
+ "pcRedStepSize": 1,
702
+ "pcUpperThresholdsLevDLRxLevel": 35,
703
+ "pcUpperThresholdsLevULRxLevel": 30,
704
+ "tAvgT": 10,
705
+ "tAvgW": 20,
706
+ "pcLTQualDlRxQual": 3,
707
+ "pcLTQualUlRxQual": 3,
708
+ "pcUTQualDlRxQual": 1,
709
+ "pcUTQualUlRxQual": 0,
710
+ "dlPcPowerReduction": 4,
711
+ "dlPcWindowSize": 8,
712
+ "enableAla": 0,
713
+ "maxPwrCompensation": 10,
714
+ "minIntBetweenAla": 10,
715
+ "pcLTLevDlNx": 1,
716
+ "pcLTLevDlPx": 1,
717
+ "pcLTLevUlNx": 1,
718
+ "pcLTLevUlPx": 1,
719
+ "pcLTQual144Nx": 4,
720
+ "pcLTQual144Px": 3,
721
+ "pcLTQual144RxQual": 2,
722
+ "pcLTQualDlNx": 1,
723
+ "pcLTQualDlPx": 1,
724
+ "pcLTQualUlNx": 4,
725
+ "pcLTQualUlPx": 3,
726
+ "pcUTLevDlNx": 1,
727
+ "pcUTLevDlPx": 1,
728
+ "pcUTLevUlNx": 1,
729
+ "pcUTLevUlPx": 1,
730
+ "pcUTQualDlNx": 1,
731
+ "pcUTQualDlPx": 1,
732
+ "pcUTQualUlNx": 1,
733
+ "pcUTQualUlPx": 1,
734
+ "powerDecrQualFactor": 1,
735
+ "powerLimitAla": 3,
736
+ "pwrDecrLimitBand0": 0,
737
+ "pwrDecrLimitBand1": 19,
738
+ "pwrDecrLimitBand2": 19,
739
+ "transmitPowerReduction": 4
740
+ },
741
+ "formulas": {}
742
+ }
743
+ }
queries/ciq_2g_schema_loader.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from pathlib import Path
3
+
4
+
5
+ _SCHEMA_PATH = Path(__file__).with_name("ciq_2g_final_schema.json")
6
+
7
+
8
+ def _load_schema() -> dict[str, dict[str, object]]:
9
+ return json.loads(_SCHEMA_PATH.read_text(encoding="utf-8"))
10
+
11
+
12
+ _SCHEMA = _load_schema()
13
+
14
+ BTS_FINAL_COLUMNS = _SCHEMA["BTS"]["columns"]
15
+ BTS_FINAL_DEFAULTS = _SCHEMA["BTS"]["defaults"]
16
+ BTS_FINAL_FORMULAS = _SCHEMA["BTS"]["formulas"]
17
+
18
+ BTS_GPRS_FINAL_COLUMNS = _SCHEMA["BTS_GPRS"]["columns"]
19
+ BTS_GPRS_FINAL_DEFAULTS = _SCHEMA["BTS_GPRS"]["defaults"]
20
+ BTS_GPRS_FINAL_FORMULAS = _SCHEMA["BTS_GPRS"]["formulas"]
21
+
22
+ BTS_AMR_FINAL_COLUMNS = _SCHEMA["BTS_AMR"]["columns"]
23
+ BTS_AMR_FINAL_DEFAULTS = _SCHEMA["BTS_AMR"]["defaults"]
24
+ BTS_AMR_FINAL_FORMULAS = _SCHEMA["BTS_AMR"]["formulas"]
25
+
26
+ HOC_FINAL_COLUMNS = _SCHEMA["HOC"]["columns"]
27
+ HOC_FINAL_DEFAULTS = _SCHEMA["HOC"]["defaults"]
28
+ HOC_FINAL_FORMULAS = _SCHEMA["HOC"]["formulas"]
29
+
30
+ POC_FINAL_COLUMNS = _SCHEMA["POC"]["columns"]
31
+ POC_FINAL_DEFAULTS = _SCHEMA["POC"]["defaults"]
32
+ POC_FINAL_FORMULAS = _SCHEMA["POC"]["formulas"]
queries/process_ciq_2g.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import io
2
  import re
3
  from dataclasses import dataclass
@@ -5,6 +6,23 @@ from typing import Optional
5
 
6
  import pandas as pd
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from utils.ciq_excel import read_ciq_excel
9
  from utils.dump_excel import detect_dump_header_row
10
  from utils.utils_vars import UtilsVars
@@ -28,6 +46,8 @@ BTS_EXPORT_COLUMNS = [
28
  "name",
29
  "template_name",
30
  "sectorId",
 
 
31
  ]
32
 
33
 
@@ -87,6 +107,22 @@ TRX_STATIC_DEFAULTS = {
87
  "trxRfPower": 20000,
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  def _normalize_col(col: object) -> str:
92
  return re.sub(r"[^0-9A-Za-z]", "", str(col))
@@ -98,6 +134,79 @@ def _clean_columns(df: pd.DataFrame) -> pd.DataFrame:
98
  return df
99
 
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  def _read_dump_bts_required_columns(dump_file) -> pd.DataFrame:
102
  if hasattr(dump_file, "seek"):
103
  dump_file.seek(0)
@@ -734,6 +843,66 @@ def _build_trx_sheet_from_assigned_sites(
734
  return df_trx
735
 
736
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  def build_bts_sheet(
738
  dump_file, ciq_file, mcc: int = 610, mnc: int = 2, forbidden_file=None
739
  ) -> pd.DataFrame:
@@ -755,6 +924,9 @@ def _build_bts_sheet_from_assigned_sites(
755
  ) -> pd.DataFrame:
756
  assigned_by_site = {s.site_name: s for s in assigned_sites}
757
 
 
 
 
758
  required = [
759
  "Sites",
760
  "NOM_CELLULE",
@@ -785,6 +957,7 @@ def _build_bts_sheet_from_assigned_sites(
785
  lac = pd.to_numeric(r.get("LAC"), errors="coerce")
786
  ncc = pd.to_numeric(r.get("NCC"), errors="coerce")
787
  bcc = pd.to_numeric(r.get("BCC"), errors="coerce")
 
788
 
789
  rows.append(
790
  {
@@ -804,6 +977,8 @@ def _build_bts_sheet_from_assigned_sites(
804
  "name": f"{str(r.get('NOM_CELLULE'))}_NA",
805
  "template_name": _template_name_from_band(r.get("band")),
806
  "sectorId": int(sector_id),
 
 
807
  }
808
  )
809
 
@@ -904,6 +1079,8 @@ def generate_ciq_2g_excel(
904
  ) -> tuple[dict[str, pd.DataFrame], bytes]:
905
  dump_bts = _read_dump_bts_required_columns(dump_file)
906
  ciq_df = _read_ciq_df(ciq_file, ciq_sheet_name=ciq_sheet_name)
 
 
907
  planned_sites = _parse_ciq_sites(ciq_df)
908
  forbidden_by_bsc, forbidden_df = _read_forbidden_bcfs(forbidden_file)
909
  assigned_sites = _assign_bcfs(
@@ -912,40 +1089,23 @@ def generate_ciq_2g_excel(
912
 
913
  df_bcf = _build_bcf_sheet_from_assigned_sites(assigned_sites)
914
  df_bcf_free = _build_free_bcf_sheet(dump_bts, forbidden_by_bsc=forbidden_by_bsc)
915
- df_bts = _build_bts_sheet_from_assigned_sites(
916
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
917
  )
 
918
  df_mal = _build_mal_sheet_from_assigned_sites(ciq_df, assigned_sites)
919
  df_trx = _build_trx_sheet_from_assigned_sites(ciq_df, assigned_sites)
920
 
921
- df_bts_min = pd.DataFrame()
922
- if not df_bts.empty:
923
- df_bts_min = df_bts[["site", "bscid", "cellId", "bcfId", "btsId"]].rename(
924
- columns={"site": "Site"}
925
- )
926
-
927
- df_hoc = pd.DataFrame()
928
- df_poc = pd.DataFrame()
929
- if not df_bts.empty:
930
- base = df_bts[
931
- ["site", "bscid", "cellId", "bcfId", "btsId", "template_name"]
932
- ].rename(columns={"site": "Site"})
933
-
934
- df_hoc = base.copy()
935
- df_hoc.insert(5, "hocId", 1)
936
- df_hoc = df_hoc[
937
- ["Site", "bscid", "cellId", "bcfId", "btsId", "hocId", "template_name"]
938
- ]
939
-
940
- df_poc = base.copy()
941
- df_poc.insert(5, "pocId", 1)
942
- df_poc = df_poc[
943
- ["Site", "bscid", "cellId", "bcfId", "btsId", "pocId", "template_name"]
944
- ]
945
 
946
  df_plmn_permitted = pd.DataFrame()
947
- if not df_bts.empty:
948
- base_plmn = df_bts[["bscid", "cellId", "bcfId", "btsId"]].rename(
949
  columns={"bscid": "BSCId"}
950
  )
951
  df_plmn_permitted = base_plmn.loc[base_plmn.index.repeat(8)].reset_index(
@@ -961,8 +1121,8 @@ def generate_ciq_2g_excel(
961
  "BCF": df_bcf,
962
  "BCF_LIBRE": df_bcf_free,
963
  "BTS": df_bts,
964
- "BTS_GPRS": df_bts_min,
965
- "BTS_AMR": df_bts_min,
966
  "HOC": df_hoc,
967
  "POC": df_poc,
968
  "MAL": df_mal,
@@ -973,8 +1133,9 @@ def generate_ciq_2g_excel(
973
  if not forbidden_df.empty:
974
  sheets["BCF_FORBIDDEN"] = forbidden_df
975
 
 
976
  bytes_io = io.BytesIO()
977
- with pd.ExcelWriter(bytes_io, engine="xlsxwriter") as writer:
978
  for sheet_name, df in sheets.items():
979
  df.to_excel(writer, sheet_name=sheet_name, index=False)
980
 
 
1
+ import importlib.util
2
  import io
3
  import re
4
  from dataclasses import dataclass
 
6
 
7
  import pandas as pd
8
 
9
+ from queries.ciq_2g_schema_loader import (
10
+ BTS_AMR_FINAL_COLUMNS,
11
+ BTS_AMR_FINAL_DEFAULTS,
12
+ BTS_AMR_FINAL_FORMULAS,
13
+ BTS_FINAL_COLUMNS,
14
+ BTS_FINAL_DEFAULTS,
15
+ BTS_FINAL_FORMULAS,
16
+ BTS_GPRS_FINAL_COLUMNS,
17
+ BTS_GPRS_FINAL_DEFAULTS,
18
+ BTS_GPRS_FINAL_FORMULAS,
19
+ HOC_FINAL_COLUMNS,
20
+ HOC_FINAL_DEFAULTS,
21
+ HOC_FINAL_FORMULAS,
22
+ POC_FINAL_COLUMNS,
23
+ POC_FINAL_DEFAULTS,
24
+ POC_FINAL_FORMULAS,
25
+ )
26
  from utils.ciq_excel import read_ciq_excel
27
  from utils.dump_excel import detect_dump_header_row
28
  from utils.utils_vars import UtilsVars
 
46
  "name",
47
  "template_name",
48
  "sectorId",
49
+ "frequencyBandInUse",
50
+ "hoppingSequenceNumber1",
51
  ]
52
 
53
 
 
107
  "trxRfPower": 20000,
108
  }
109
 
110
+ FINAL_SCHEMA = {
111
+ "BTS": (BTS_FINAL_COLUMNS, BTS_FINAL_DEFAULTS, BTS_FINAL_FORMULAS),
112
+ "BTS_GPRS": (
113
+ BTS_GPRS_FINAL_COLUMNS,
114
+ BTS_GPRS_FINAL_DEFAULTS,
115
+ BTS_GPRS_FINAL_FORMULAS,
116
+ ),
117
+ "BTS_AMR": (
118
+ BTS_AMR_FINAL_COLUMNS,
119
+ BTS_AMR_FINAL_DEFAULTS,
120
+ BTS_AMR_FINAL_FORMULAS,
121
+ ),
122
+ "HOC": (HOC_FINAL_COLUMNS, HOC_FINAL_DEFAULTS, HOC_FINAL_FORMULAS),
123
+ "POC": (POC_FINAL_COLUMNS, POC_FINAL_DEFAULTS, POC_FINAL_FORMULAS),
124
+ }
125
+
126
 
127
  def _normalize_col(col: object) -> str:
128
  return re.sub(r"[^0-9A-Za-z]", "", str(col))
 
134
  return df
135
 
136
 
137
+ def _raise_missing_hsn_error() -> None:
138
+ raise ValueError(
139
+ "CIQ brut is missing required column: HSN. "
140
+ "The 2G final export uses HSN to populate BTS.hoppingSequenceNumber1."
141
+ )
142
+
143
+
144
+ def _get_excel_writer_engine() -> str:
145
+ for engine_name in ("xlsxwriter", "openpyxl"):
146
+ if importlib.util.find_spec(engine_name) is not None:
147
+ return engine_name
148
+ raise RuntimeError(
149
+ "Excel export requires 'xlsxwriter' or 'openpyxl' to be installed."
150
+ )
151
+
152
+
153
+ def _excel_column_name(index: int) -> str:
154
+ if index < 1:
155
+ raise ValueError("Excel column index must be >= 1")
156
+
157
+ result = ""
158
+ while index > 0:
159
+ index, remainder = divmod(index - 1, 26)
160
+ result = chr(65 + remainder) + result
161
+ return result
162
+
163
+
164
+ def apply_final_schema(df: pd.DataFrame, sheet_name: str) -> pd.DataFrame:
165
+ if sheet_name not in FINAL_SCHEMA:
166
+ raise ValueError(f"Unsupported 2G CIQ sheet '{sheet_name}'")
167
+
168
+ final_columns, default_values, formula_specs = FINAL_SCHEMA[sheet_name]
169
+ final_df = df.copy()
170
+ missing_columns = [column for column in final_columns if column not in final_df.columns]
171
+
172
+ if missing_columns:
173
+ if final_df.empty:
174
+ additions = pd.DataFrame(
175
+ {column: pd.Series(dtype="object") for column in missing_columns}
176
+ )
177
+ else:
178
+ additions = pd.DataFrame(
179
+ {
180
+ column: [default_values.get(column, "")] * len(final_df)
181
+ for column in missing_columns
182
+ },
183
+ index=final_df.index,
184
+ )
185
+ final_df = pd.concat([final_df, additions], axis=1)
186
+
187
+ final_df = final_df.loc[:, final_columns]
188
+
189
+ if final_df.empty or not formula_specs:
190
+ return final_df
191
+
192
+ for formula_column, spec in formula_specs.items():
193
+ kind = spec.get("kind")
194
+ if kind != "same_row_ref":
195
+ raise ValueError(
196
+ f"Unsupported formula kind '{kind}' for 2G sheet '{sheet_name}'"
197
+ )
198
+
199
+ source_column = spec["source_column"]
200
+ source_col_idx = final_columns.index(source_column) + 1
201
+ source_col_letter = _excel_column_name(source_col_idx)
202
+ final_df[formula_column] = [
203
+ f"={source_col_letter}{row_number}"
204
+ for row_number in range(2, len(final_df) + 2)
205
+ ]
206
+
207
+ return final_df
208
+
209
+
210
  def _read_dump_bts_required_columns(dump_file) -> pd.DataFrame:
211
  if hasattr(dump_file, "seek"):
212
  dump_file.seek(0)
 
843
  return df_trx
844
 
845
 
846
+ def _build_bts_gprs_sheet_from_bts(df_bts: pd.DataFrame) -> pd.DataFrame:
847
+ if df_bts.empty:
848
+ return pd.DataFrame(
849
+ columns=["Site", "bscid", "cellId", "bcfId", "btsId", "template_name"]
850
+ )
851
+
852
+ df_bts_gprs = df_bts[["site", "bscid", "cellId", "bcfId", "btsId"]].rename(
853
+ columns={"site": "Site"}
854
+ )
855
+ df_bts_gprs["template_name"] = "All"
856
+ return df_bts_gprs[
857
+ ["Site", "bscid", "cellId", "bcfId", "btsId", "template_name"]
858
+ ]
859
+
860
+
861
+ def _build_bts_amr_sheet_from_bts(df_bts: pd.DataFrame) -> pd.DataFrame:
862
+ if df_bts.empty:
863
+ return pd.DataFrame(
864
+ columns=["Site", "bscId", "cellId", "bcfId", "btsId", "template_name"]
865
+ )
866
+
867
+ df_bts_amr = df_bts[["site", "bscid", "cellId", "bcfId", "btsId"]].rename(
868
+ columns={"site": "Site", "bscid": "bscId"}
869
+ )
870
+ df_bts_amr["template_name"] = "All"
871
+ return df_bts_amr[
872
+ ["Site", "bscId", "cellId", "bcfId", "btsId", "template_name"]
873
+ ]
874
+
875
+
876
+ def _build_hoc_sheet_from_bts(df_bts: pd.DataFrame) -> pd.DataFrame:
877
+ if df_bts.empty:
878
+ return pd.DataFrame(
879
+ columns=["Site", "bscid", "cellId", "bcfId", "btsId", "hocId", "template_name"]
880
+ )
881
+
882
+ df_hoc = df_bts[
883
+ ["site", "bscid", "cellId", "bcfId", "btsId", "template_name"]
884
+ ].rename(columns={"site": "Site"})
885
+ df_hoc.insert(5, "hocId", 1)
886
+ return df_hoc[
887
+ ["Site", "bscid", "cellId", "bcfId", "btsId", "hocId", "template_name"]
888
+ ]
889
+
890
+
891
+ def _build_poc_sheet_from_bts(df_bts: pd.DataFrame) -> pd.DataFrame:
892
+ if df_bts.empty:
893
+ return pd.DataFrame(
894
+ columns=["Site", "bscid", "cellId", "bcfId", "btsId", "hocId", "template_name"]
895
+ )
896
+
897
+ df_poc = df_bts[
898
+ ["site", "bscid", "cellId", "bcfId", "btsId", "template_name"]
899
+ ].rename(columns={"site": "Site"})
900
+ df_poc.insert(5, "hocId", 1)
901
+ return df_poc[
902
+ ["Site", "bscid", "cellId", "bcfId", "btsId", "hocId", "template_name"]
903
+ ]
904
+
905
+
906
  def build_bts_sheet(
907
  dump_file, ciq_file, mcc: int = 610, mnc: int = 2, forbidden_file=None
908
  ) -> pd.DataFrame:
 
924
  ) -> pd.DataFrame:
925
  assigned_by_site = {s.site_name: s for s in assigned_sites}
926
 
927
+ if "HSN" not in ciq_df.columns:
928
+ _raise_missing_hsn_error()
929
+
930
  required = [
931
  "Sites",
932
  "NOM_CELLULE",
 
957
  lac = pd.to_numeric(r.get("LAC"), errors="coerce")
958
  ncc = pd.to_numeric(r.get("NCC"), errors="coerce")
959
  bcc = pd.to_numeric(r.get("BCC"), errors="coerce")
960
+ hsn = pd.to_numeric(r.get("HSN"), errors="coerce")
961
 
962
  rows.append(
963
  {
 
977
  "name": f"{str(r.get('NOM_CELLULE'))}_NA",
978
  "template_name": _template_name_from_band(r.get("band")),
979
  "sectorId": int(sector_id),
980
+ "frequencyBandInUse": _frequency_band_in_use_from_band(r.get("band")),
981
+ "hoppingSequenceNumber1": int(hsn) if not pd.isna(hsn) else None,
982
  }
983
  )
984
 
 
1079
  ) -> tuple[dict[str, pd.DataFrame], bytes]:
1080
  dump_bts = _read_dump_bts_required_columns(dump_file)
1081
  ciq_df = _read_ciq_df(ciq_file, ciq_sheet_name=ciq_sheet_name)
1082
+ if "HSN" not in ciq_df.columns:
1083
+ _raise_missing_hsn_error()
1084
  planned_sites = _parse_ciq_sites(ciq_df)
1085
  forbidden_by_bsc, forbidden_df = _read_forbidden_bcfs(forbidden_file)
1086
  assigned_sites = _assign_bcfs(
 
1089
 
1090
  df_bcf = _build_bcf_sheet_from_assigned_sites(assigned_sites)
1091
  df_bcf_free = _build_free_bcf_sheet(dump_bts, forbidden_by_bsc=forbidden_by_bsc)
1092
+ df_bts_base = _build_bts_sheet_from_assigned_sites(
1093
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
1094
  )
1095
+ df_bts = apply_final_schema(df_bts_base, "BTS")
1096
  df_mal = _build_mal_sheet_from_assigned_sites(ciq_df, assigned_sites)
1097
  df_trx = _build_trx_sheet_from_assigned_sites(ciq_df, assigned_sites)
1098
 
1099
+ df_bts_gprs = apply_final_schema(
1100
+ _build_bts_gprs_sheet_from_bts(df_bts_base), "BTS_GPRS"
1101
+ )
1102
+ df_bts_amr = apply_final_schema(_build_bts_amr_sheet_from_bts(df_bts_base), "BTS_AMR")
1103
+ df_hoc = apply_final_schema(_build_hoc_sheet_from_bts(df_bts_base), "HOC")
1104
+ df_poc = apply_final_schema(_build_poc_sheet_from_bts(df_bts_base), "POC")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1105
 
1106
  df_plmn_permitted = pd.DataFrame()
1107
+ if not df_bts_base.empty:
1108
+ base_plmn = df_bts_base[["bscid", "cellId", "bcfId", "btsId"]].rename(
1109
  columns={"bscid": "BSCId"}
1110
  )
1111
  df_plmn_permitted = base_plmn.loc[base_plmn.index.repeat(8)].reset_index(
 
1121
  "BCF": df_bcf,
1122
  "BCF_LIBRE": df_bcf_free,
1123
  "BTS": df_bts,
1124
+ "BTS_GPRS": df_bts_gprs,
1125
+ "BTS_AMR": df_bts_amr,
1126
  "HOC": df_hoc,
1127
  "POC": df_poc,
1128
  "MAL": df_mal,
 
1133
  if not forbidden_df.empty:
1134
  sheets["BCF_FORBIDDEN"] = forbidden_df
1135
 
1136
+ engine_name = _get_excel_writer_engine()
1137
  bytes_io = io.BytesIO()
1138
+ with pd.ExcelWriter(bytes_io, engine=engine_name) as writer:
1139
  for sheet_name, df in sheets.items():
1140
  df.to_excel(writer, sheet_name=sheet_name, index=False)
1141
 
tests/test_process_ciq_2g_final_schema.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ from numbers import Integral
3
+ from unittest.mock import patch
4
+
5
+ import pandas as pd
6
+ from openpyxl import load_workbook
7
+
8
+ from queries.ciq_2g_schema_loader import (
9
+ BTS_AMR_FINAL_COLUMNS,
10
+ BTS_AMR_FINAL_DEFAULTS,
11
+ BTS_FINAL_COLUMNS,
12
+ BTS_FINAL_DEFAULTS,
13
+ BTS_GPRS_FINAL_COLUMNS,
14
+ BTS_GPRS_FINAL_DEFAULTS,
15
+ HOC_FINAL_COLUMNS,
16
+ HOC_FINAL_DEFAULTS,
17
+ POC_FINAL_COLUMNS,
18
+ POC_FINAL_DEFAULTS,
19
+ )
20
+ from queries.process_ciq_2g import (
21
+ _PlannedSite,
22
+ _build_bts_amr_sheet_from_bts,
23
+ _build_bts_gprs_sheet_from_bts,
24
+ _build_bts_sheet_from_assigned_sites,
25
+ _build_hoc_sheet_from_bts,
26
+ _build_poc_sheet_from_bts,
27
+ _get_excel_writer_engine,
28
+ apply_final_schema,
29
+ generate_ciq_2g_excel,
30
+ )
31
+
32
+
33
+ def _sample_ciq_df() -> pd.DataFrame:
34
+ return pd.DataFrame(
35
+ [
36
+ {
37
+ "Sites": "0273_TBC_DIRE-HAMDALLAYE_2G",
38
+ "NOM_CELLULE": "0273_TBC_DIRE-HAMDALLAYE_1_900",
39
+ "Nbre_TRE_DR": 4,
40
+ "LAC": 30802,
41
+ "RAC": 1,
42
+ "CI": 2731,
43
+ "Frequence": "GSM900",
44
+ "BCCH": 5,
45
+ "TRX": "47,51,58",
46
+ "NCC": 5,
47
+ "BCC": 5,
48
+ "HSN": 56,
49
+ "MAIO": 0,
50
+ "Nom BSC": "ASBSCMSC3",
51
+ "BSC ID": 403703,
52
+ "band": "G9",
53
+ "sector": 1,
54
+ "site_number": 273,
55
+ },
56
+ {
57
+ "Sites": "0273_TBC_DIRE-HAMDALLAYE_2G",
58
+ "NOM_CELLULE": "0273_TBC_DIRE-HAMDALLAYE_1_1800",
59
+ "Nbre_TRE_DR": 3,
60
+ "LAC": 30802,
61
+ "RAC": 1,
62
+ "CI": 2734,
63
+ "Frequence": "GSM1800",
64
+ "BCCH": 879,
65
+ "TRX": "859,864",
66
+ "NCC": 3,
67
+ "BCC": 6,
68
+ "HSN": 54,
69
+ "MAIO": 0,
70
+ "Nom BSC": "ASBSCMSC3",
71
+ "BSC ID": 403703,
72
+ "band": "G18",
73
+ "sector": 1,
74
+ "site_number": 273,
75
+ },
76
+ ]
77
+ )
78
+
79
+
80
+ def _sample_assigned_sites() -> list[_PlannedSite]:
81
+ return [
82
+ _PlannedSite(
83
+ site_name="0273_TBC_DIRE-HAMDALLAYE_2G",
84
+ site_number=273,
85
+ bsc=403703,
86
+ bsc_name="ASBSCMSC3",
87
+ name="0273_TBC_DIRE-HAMDALLAYE_2G_NA",
88
+ configuration="G9-4, G18-3",
89
+ assigned_bcf=200,
90
+ needed_bts_ids=(201, 204),
91
+ )
92
+ ]
93
+
94
+
95
+ def _sample_bts_base_df() -> pd.DataFrame:
96
+ return _build_bts_sheet_from_assigned_sites(
97
+ _sample_ciq_df(), _sample_assigned_sites(), mcc=610, mnc=2
98
+ )
99
+
100
+
101
+ def test_apply_final_schema_bts_keeps_core_values_and_adds_formula_and_derived_fields():
102
+ df_bts = _sample_bts_base_df()
103
+
104
+ final_df = apply_final_schema(df_bts, "BTS")
105
+
106
+ assert list(final_df.columns) == BTS_FINAL_COLUMNS
107
+ assert len(final_df.columns) == 109
108
+ assert list(final_df["site"]) == [273, 273]
109
+ assert list(final_df["btsId"]) == [201, 204]
110
+ assert list(final_df["template_name"]) == ["GSM900", "GSM1800"]
111
+ assert list(final_df["frequencyBandInUse"]) == [0, 1]
112
+ assert list(final_df["hoppingSequenceNumber1"]) == [56, 54]
113
+ assert list(final_df["segmentId"]) == ["=E2", "=E3"]
114
+ assert final_df.loc[0, "masterBCF"] == BTS_FINAL_DEFAULTS["masterBCF"]
115
+ assert (
116
+ final_df.loc[0, "gprsCapacityThroughputFactor"]
117
+ == BTS_FINAL_DEFAULTS["gprsCapacityThroughputFactor"]
118
+ )
119
+ assert isinstance(final_df.loc[0, "fddRscpMin"], Integral)
120
+
121
+
122
+ def test_apply_final_schema_bts_gprs_adds_defaults_without_overriding_base_columns():
123
+ final_df = apply_final_schema(
124
+ _build_bts_gprs_sheet_from_bts(_sample_bts_base_df()), "BTS_GPRS"
125
+ )
126
+
127
+ assert list(final_df.columns) == BTS_GPRS_FINAL_COLUMNS
128
+ assert len(final_df.columns) == 56
129
+ assert list(final_df["Site"]) == [273, 273]
130
+ assert list(final_df["btsId"]) == [201, 204]
131
+ assert list(final_df["template_name"]) == ["All", "All"]
132
+ assert (
133
+ final_df.loc[0, "dedicatedGPRScapacity"]
134
+ == BTS_GPRS_FINAL_DEFAULTS["dedicatedGPRScapacity"]
135
+ )
136
+ assert final_df.loc[0, "nsei"] == ""
137
+ assert final_df.loc[0, "rac"] == BTS_GPRS_FINAL_DEFAULTS["rac"]
138
+
139
+
140
+ def test_apply_final_schema_bts_amr_keeps_bscid_renamed_as_bscid_final_name():
141
+ final_df = apply_final_schema(
142
+ _build_bts_amr_sheet_from_bts(_sample_bts_base_df()), "BTS_AMR"
143
+ )
144
+
145
+ assert list(final_df.columns) == BTS_AMR_FINAL_COLUMNS
146
+ assert len(final_df.columns) == 53
147
+ assert list(final_df["bscId"]) == [403703, 403703]
148
+ assert list(final_df["template_name"]) == ["All", "All"]
149
+ assert (
150
+ final_df.loc[0, "amrConfFrCodecModeSet"]
151
+ == BTS_AMR_FINAL_DEFAULTS["amrConfFrCodecModeSet"]
152
+ )
153
+ assert (
154
+ final_df.loc[0, "radioLinkTimeoutAmr"]
155
+ == BTS_AMR_FINAL_DEFAULTS["radioLinkTimeoutAmr"]
156
+ )
157
+
158
+
159
+ def test_apply_final_schema_hoc_and_poc_keep_identifiers_and_defaults():
160
+ df_bts = _sample_bts_base_df()
161
+ hoc_df = apply_final_schema(_build_hoc_sheet_from_bts(df_bts), "HOC")
162
+ poc_df = apply_final_schema(_build_poc_sheet_from_bts(df_bts), "POC")
163
+
164
+ assert list(hoc_df.columns) == HOC_FINAL_COLUMNS
165
+ assert len(hoc_df.columns) == 87
166
+ assert list(hoc_df["hocId"]) == [1, 1]
167
+ assert hoc_df.loc[0, "enableSddchHandover"] == ""
168
+ assert hoc_df.loc[0, "enableUmbrellaHo"] == HOC_FINAL_DEFAULTS["enableUmbrellaHo"]
169
+
170
+ assert list(poc_df.columns) == POC_FINAL_COLUMNS
171
+ assert len(poc_df.columns) == 65
172
+ assert "pocId" not in poc_df.columns
173
+ assert list(poc_df["hocId"]) == [1, 1]
174
+ assert poc_df.loc[0, "alpha"] == POC_FINAL_DEFAULTS["alpha"]
175
+ assert (
176
+ poc_df.loc[0, "transmitPowerReduction"]
177
+ == POC_FINAL_DEFAULTS["transmitPowerReduction"]
178
+ )
179
+
180
+
181
+ def test_get_excel_writer_engine_prefers_xlsxwriter_then_openpyxl():
182
+ with patch("queries.process_ciq_2g.importlib.util.find_spec") as mock_find_spec:
183
+ mock_find_spec.side_effect = lambda name: object() if name == "xlsxwriter" else None
184
+ assert _get_excel_writer_engine() == "xlsxwriter"
185
+
186
+ with patch("queries.process_ciq_2g.importlib.util.find_spec") as mock_find_spec:
187
+ mock_find_spec.side_effect = lambda name: object() if name == "openpyxl" else None
188
+ assert _get_excel_writer_engine() == "openpyxl"
189
+
190
+
191
+ def test_get_excel_writer_engine_raises_clear_error_when_missing():
192
+ with patch("queries.process_ciq_2g.importlib.util.find_spec", return_value=None):
193
+ try:
194
+ _get_excel_writer_engine()
195
+ except RuntimeError as exc:
196
+ assert "xlsxwriter" in str(exc)
197
+ assert "openpyxl" in str(exc)
198
+ else:
199
+ raise AssertionError("Expected RuntimeError when no Excel writer engine is installed")
200
+
201
+
202
+ def test_generate_ciq_2g_excel_raises_clear_error_when_hsn_is_missing():
203
+ ciq_df = _sample_ciq_df().drop(columns=["HSN"])
204
+
205
+ with (
206
+ patch(
207
+ "queries.process_ciq_2g._read_dump_bts_required_columns",
208
+ return_value=pd.DataFrame(columns=["BSC", "BCF", "BTS", "usedMobileAllocation"]),
209
+ ),
210
+ patch("queries.process_ciq_2g._read_ciq_df", return_value=ciq_df),
211
+ ):
212
+ try:
213
+ generate_ciq_2g_excel(io.BytesIO(b"dump"), io.BytesIO(b"ciq"))
214
+ except ValueError as exc:
215
+ assert "missing required column: HSN" in str(exc)
216
+ assert "BTS.hoppingSequenceNumber1" in str(exc)
217
+ else:
218
+ raise AssertionError("Expected ValueError when HSN is missing")
219
+
220
+
221
+ def test_generate_ciq_2g_excel_returns_finalized_target_sheets_only():
222
+ captured_sheet_names = []
223
+ captured_engine = None
224
+
225
+ class DummyWriter:
226
+ def __enter__(self):
227
+ return self
228
+
229
+ def __exit__(self, exc_type, exc, tb):
230
+ return False
231
+
232
+ def _capture_excel_writer(*args, **kwargs):
233
+ nonlocal captured_engine
234
+ captured_engine = kwargs.get("engine")
235
+ return DummyWriter()
236
+
237
+ def _capture_to_excel(self, writer, sheet_name=None, index=True, **kwargs):
238
+ captured_sheet_names.append(sheet_name)
239
+
240
+ with (
241
+ patch(
242
+ "queries.process_ciq_2g._read_dump_bts_required_columns",
243
+ return_value=pd.DataFrame(columns=["BSC", "BCF", "BTS", "usedMobileAllocation"]),
244
+ ),
245
+ patch("queries.process_ciq_2g._read_ciq_df", return_value=_sample_ciq_df()),
246
+ patch("queries.process_ciq_2g._read_forbidden_bcfs", return_value=({}, pd.DataFrame())),
247
+ patch("queries.process_ciq_2g._assign_bcfs", return_value=_sample_assigned_sites()),
248
+ patch("queries.process_ciq_2g.importlib.util.find_spec") as mock_find_spec,
249
+ patch("queries.process_ciq_2g.pd.ExcelWriter", side_effect=_capture_excel_writer),
250
+ patch.object(pd.DataFrame, "to_excel", autospec=True, side_effect=_capture_to_excel),
251
+ ):
252
+ mock_find_spec.side_effect = lambda name: object() if name == "xlsxwriter" else None
253
+ sheets, excel_bytes = generate_ciq_2g_excel(io.BytesIO(b"dump"), io.BytesIO(b"ciq"))
254
+
255
+ assert list(sheets["BTS"].columns) == BTS_FINAL_COLUMNS
256
+ assert list(sheets["BTS_GPRS"].columns) == BTS_GPRS_FINAL_COLUMNS
257
+ assert list(sheets["BTS_AMR"].columns) == BTS_AMR_FINAL_COLUMNS
258
+ assert list(sheets["HOC"].columns) == HOC_FINAL_COLUMNS
259
+ assert list(sheets["POC"].columns) == POC_FINAL_COLUMNS
260
+ assert len(sheets["MAL"].columns) == 16
261
+ assert len(sheets["TRX"].columns) == 78
262
+ assert captured_sheet_names == [
263
+ "BCF",
264
+ "BCF_LIBRE",
265
+ "BTS",
266
+ "BTS_GPRS",
267
+ "BTS_AMR",
268
+ "HOC",
269
+ "POC",
270
+ "MAL",
271
+ "BTS_PLMNPERMITTED",
272
+ "TRX",
273
+ ]
274
+ assert captured_engine == "xlsxwriter"
275
+ assert isinstance(excel_bytes, bytes)
276
+ assert list(sheets["BTS"]["segmentId"]) == ["=E2", "=E3"]
277
+
278
+
279
+ def test_generate_ciq_2g_excel_writes_real_workbook_with_segmentid_formula():
280
+ with (
281
+ patch(
282
+ "queries.process_ciq_2g._read_dump_bts_required_columns",
283
+ return_value=pd.DataFrame(columns=["BSC", "BCF", "BTS", "usedMobileAllocation"]),
284
+ ),
285
+ patch("queries.process_ciq_2g._read_ciq_df", return_value=_sample_ciq_df()),
286
+ patch("queries.process_ciq_2g._read_forbidden_bcfs", return_value=({}, pd.DataFrame())),
287
+ patch("queries.process_ciq_2g._assign_bcfs", return_value=_sample_assigned_sites()),
288
+ ):
289
+ sheets, excel_bytes = generate_ciq_2g_excel(io.BytesIO(b"dump"), io.BytesIO(b"ciq"))
290
+
291
+ assert isinstance(excel_bytes, bytes)
292
+ assert len(excel_bytes) > 0
293
+ assert list(sheets["BTS"]["segmentId"]) == ["=E2", "=E3"]
294
+
295
+ workbook = load_workbook(io.BytesIO(excel_bytes), data_only=False)
296
+ ws_bts = workbook["BTS"]
297
+ headers = [cell.value for cell in ws_bts[1]]
298
+ segment_col_idx = headers.index("segmentId") + 1
299
+ btsid_col_idx = headers.index("btsId") + 1
300
+ btsid_col_letter = ws_bts.cell(1, btsid_col_idx).column_letter
301
+
302
+ assert ws_bts.cell(2, segment_col_idx).value == f"={btsid_col_letter}2"
303
+ assert ws_bts.cell(3, segment_col_idx).value == f"={btsid_col_letter}3"