DavMelchi commited on
Commit
68f370b
·
1 Parent(s): e8f2ad1

forbiden BCF

Browse files
Files changed (1) hide show
  1. queries/process_ciq_2g.py +109 -8
queries/process_ciq_2g.py CHANGED
@@ -265,8 +265,46 @@ def _parse_ciq_sites(ciq_df: pd.DataFrame) -> list[_PlannedSite]:
265
  return sorted(sites, key=lambda s: (s.bsc, s.site_number, s.site_name))
266
 
267
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  def _assign_bcfs(
269
- dump_bts: pd.DataFrame, planned_sites: list[_PlannedSite], ciq_df: pd.DataFrame
 
 
 
270
  ) -> list[_PlannedSite]:
271
 
272
  dump_bts = dump_bts.dropna(subset=["BSC"])
@@ -277,8 +315,11 @@ def _assign_bcfs(
277
  for s in planned_sites:
278
  sites_by_bsc.setdefault(s.bsc, []).append(s)
279
 
 
 
280
  for bsc, sites_in_bsc in sites_by_bsc.items():
281
  sub_dump = dump_bts[dump_bts["BSC"].fillna(-1).astype(int) == int(bsc)]
 
282
 
283
  used_bcfs: set[int] = set(
284
  pd.to_numeric(sub_dump["BCF"], errors="coerce")
@@ -312,7 +353,7 @@ def _assign_bcfs(
312
  assigned_needed_ids: Optional[tuple[int, ...]] = None
313
 
314
  for cand in range(10, 4401, 10):
315
- if cand in used_bcfs:
316
  continue
317
 
318
  site_needed_ids = _needed_bts_ids_from_site_rows(cand, site_rows)
@@ -354,11 +395,14 @@ def _assign_bcfs(
354
  return sorted(assigned, key=lambda s: (s.bsc, s.site_number, s.site_name))
355
 
356
 
357
- def build_bcf_sheet(dump_file, ciq_file) -> pd.DataFrame:
358
  dump_bts = _read_dump_bts_required_columns(dump_file)
359
  ciq_df = _read_ciq_df(ciq_file)
360
  planned_sites = _parse_ciq_sites(ciq_df)
361
- assigned_sites = _assign_bcfs(dump_bts, planned_sites, ciq_df)
 
 
 
362
  return _build_bcf_sheet_from_assigned_sites(assigned_sites)
363
 
364
 
@@ -381,6 +425,50 @@ def _build_bcf_sheet_from_assigned_sites(
381
  return pd.DataFrame(rows)
382
 
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  def _sector_id_from_band_sector(band: object, sector: object) -> int:
385
  if band not in {"G9", "G18"}:
386
  raise ValueError(f"Invalid band '{band}'")
@@ -530,12 +618,17 @@ def _build_trx_sheet_from_assigned_sites(
530
  return df_trx
531
 
532
 
533
- def build_bts_sheet(dump_file, ciq_file, mcc: int = 610, mnc: int = 2) -> pd.DataFrame:
 
 
534
  dump_bts = _read_dump_bts_required_columns(dump_file)
535
  ciq_df = _read_ciq_df(ciq_file)
536
 
537
  planned_sites = _parse_ciq_sites(ciq_df)
538
- assigned_sites = _assign_bcfs(dump_bts, planned_sites, ciq_df)
 
 
 
539
  return _build_bts_sheet_from_assigned_sites(
540
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
541
  )
@@ -686,14 +779,18 @@ def _build_mal_sheet_from_assigned_sites(
686
 
687
 
688
  def generate_ciq_2g_excel(
689
- dump_file, ciq_file, mcc: int = 610, mnc: int = 2
690
  ) -> tuple[dict[str, pd.DataFrame], bytes]:
691
  dump_bts = _read_dump_bts_required_columns(dump_file)
692
  ciq_df = _read_ciq_df(ciq_file)
693
  planned_sites = _parse_ciq_sites(ciq_df)
694
- assigned_sites = _assign_bcfs(dump_bts, planned_sites, ciq_df)
 
 
 
695
 
696
  df_bcf = _build_bcf_sheet_from_assigned_sites(assigned_sites)
 
697
  df_bts = _build_bts_sheet_from_assigned_sites(
698
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
699
  )
@@ -741,6 +838,7 @@ def generate_ciq_2g_excel(
741
 
742
  sheets: dict[str, pd.DataFrame] = {
743
  "BCF": df_bcf,
 
744
  "BTS": df_bts,
745
  "BTS_GPRS": df_bts_min,
746
  "BTS_AMR": df_bts_min,
@@ -751,6 +849,9 @@ def generate_ciq_2g_excel(
751
  "TRX": df_trx,
752
  }
753
 
 
 
 
754
  bytes_io = io.BytesIO()
755
  with pd.ExcelWriter(bytes_io, engine="xlsxwriter") as writer:
756
  for sheet_name, df in sheets.items():
 
265
  return sorted(sites, key=lambda s: (s.bsc, s.site_number, s.site_name))
266
 
267
 
268
+ def _read_forbidden_bcfs(
269
+ forbidden_file,
270
+ ) -> tuple[dict[int, set[int]], pd.DataFrame]:
271
+ if forbidden_file is None:
272
+ return {}, pd.DataFrame()
273
+
274
+ if hasattr(forbidden_file, "seek"):
275
+ forbidden_file.seek(0)
276
+
277
+ df = pd.read_excel(forbidden_file, engine="calamine")
278
+ df = _clean_columns(df)
279
+
280
+ if "BSCID" in df.columns and "BSC" not in df.columns:
281
+ df = df.rename(columns={"BSCID": "BSC"})
282
+ if "BCFID" in df.columns and "BCF" not in df.columns:
283
+ df = df.rename(columns={"BCFID": "BCF"})
284
+
285
+ required = ["BSC", "BCF"]
286
+ missing = [c for c in required if c not in df.columns]
287
+ if missing:
288
+ raise ValueError(f"Forbidden BCF list is missing required columns: {missing}")
289
+
290
+ df["BSC"] = pd.to_numeric(df["BSC"], errors="coerce")
291
+ df["BCF"] = pd.to_numeric(df["BCF"], errors="coerce")
292
+ df = df.dropna(subset=["BSC", "BCF"]).copy()
293
+ df["BSC"] = df["BSC"].astype(int)
294
+ df["BCF"] = df["BCF"].astype(int)
295
+
296
+ forbidden_by_bsc: dict[int, set[int]] = {}
297
+ for bsc, group in df.groupby("BSC"):
298
+ forbidden_by_bsc[int(bsc)] = set(group["BCF"].tolist())
299
+
300
+ return forbidden_by_bsc, df[["BSC", "BCF"]]
301
+
302
+
303
  def _assign_bcfs(
304
+ dump_bts: pd.DataFrame,
305
+ planned_sites: list[_PlannedSite],
306
+ ciq_df: pd.DataFrame,
307
+ forbidden_by_bsc: dict[int, set[int]] | None = None,
308
  ) -> list[_PlannedSite]:
309
 
310
  dump_bts = dump_bts.dropna(subset=["BSC"])
 
315
  for s in planned_sites:
316
  sites_by_bsc.setdefault(s.bsc, []).append(s)
317
 
318
+ forbidden_by_bsc = forbidden_by_bsc or {}
319
+
320
  for bsc, sites_in_bsc in sites_by_bsc.items():
321
  sub_dump = dump_bts[dump_bts["BSC"].fillna(-1).astype(int) == int(bsc)]
322
+ forbidden = forbidden_by_bsc.get(int(bsc), set())
323
 
324
  used_bcfs: set[int] = set(
325
  pd.to_numeric(sub_dump["BCF"], errors="coerce")
 
353
  assigned_needed_ids: Optional[tuple[int, ...]] = None
354
 
355
  for cand in range(10, 4401, 10):
356
+ if cand in used_bcfs or cand in forbidden:
357
  continue
358
 
359
  site_needed_ids = _needed_bts_ids_from_site_rows(cand, site_rows)
 
395
  return sorted(assigned, key=lambda s: (s.bsc, s.site_number, s.site_name))
396
 
397
 
398
+ def build_bcf_sheet(dump_file, ciq_file, forbidden_file=None) -> pd.DataFrame:
399
  dump_bts = _read_dump_bts_required_columns(dump_file)
400
  ciq_df = _read_ciq_df(ciq_file)
401
  planned_sites = _parse_ciq_sites(ciq_df)
402
+ forbidden_by_bsc, _ = _read_forbidden_bcfs(forbidden_file)
403
+ assigned_sites = _assign_bcfs(
404
+ dump_bts, planned_sites, ciq_df, forbidden_by_bsc=forbidden_by_bsc
405
+ )
406
  return _build_bcf_sheet_from_assigned_sites(assigned_sites)
407
 
408
 
 
425
  return pd.DataFrame(rows)
426
 
427
 
428
+ def _build_free_bcf_sheet(
429
+ dump_bts: pd.DataFrame, forbidden_by_bsc: dict[int, set[int]] | None = None
430
+ ) -> pd.DataFrame:
431
+ rows = []
432
+ forbidden_by_bsc = forbidden_by_bsc or {}
433
+ bsc_values = (
434
+ pd.to_numeric(dump_bts["BSC"], errors="coerce").dropna().astype(int).unique()
435
+ )
436
+ for bsc in sorted(bsc_values):
437
+ sub_dump = dump_bts[dump_bts["BSC"].fillna(-1).astype(int) == int(bsc)]
438
+ forbidden = forbidden_by_bsc.get(int(bsc), set())
439
+
440
+ used_bcfs: set[int] = set(
441
+ pd.to_numeric(sub_dump["BCF"], errors="coerce")
442
+ .dropna()
443
+ .astype(int)
444
+ .tolist()
445
+ )
446
+ used_bts: set[int] = set(
447
+ pd.to_numeric(sub_dump["BTS"], errors="coerce")
448
+ .dropna()
449
+ .astype(int)
450
+ .tolist()
451
+ )
452
+ used_mal: set[int] = set(
453
+ pd.to_numeric(sub_dump["usedMobileAllocation"], errors="coerce")
454
+ .dropna()
455
+ .astype(int)
456
+ .tolist()
457
+ )
458
+
459
+ for cand in range(10, 4401, 10):
460
+ if cand in used_bcfs or cand in forbidden:
461
+ continue
462
+
463
+ required_ids = tuple(cand + i for i in range(1, 7))
464
+ if any((i in used_bts) or (i in used_mal) for i in required_ids):
465
+ continue
466
+
467
+ rows.append({"BSC": int(bsc), "BCF": int(cand)})
468
+
469
+ return pd.DataFrame(rows)
470
+
471
+
472
  def _sector_id_from_band_sector(band: object, sector: object) -> int:
473
  if band not in {"G9", "G18"}:
474
  raise ValueError(f"Invalid band '{band}'")
 
618
  return df_trx
619
 
620
 
621
+ def build_bts_sheet(
622
+ dump_file, ciq_file, mcc: int = 610, mnc: int = 2, forbidden_file=None
623
+ ) -> pd.DataFrame:
624
  dump_bts = _read_dump_bts_required_columns(dump_file)
625
  ciq_df = _read_ciq_df(ciq_file)
626
 
627
  planned_sites = _parse_ciq_sites(ciq_df)
628
+ forbidden_by_bsc, _ = _read_forbidden_bcfs(forbidden_file)
629
+ assigned_sites = _assign_bcfs(
630
+ dump_bts, planned_sites, ciq_df, forbidden_by_bsc=forbidden_by_bsc
631
+ )
632
  return _build_bts_sheet_from_assigned_sites(
633
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
634
  )
 
779
 
780
 
781
  def generate_ciq_2g_excel(
782
+ dump_file, ciq_file, mcc: int = 610, mnc: int = 2, forbidden_file=None
783
  ) -> tuple[dict[str, pd.DataFrame], bytes]:
784
  dump_bts = _read_dump_bts_required_columns(dump_file)
785
  ciq_df = _read_ciq_df(ciq_file)
786
  planned_sites = _parse_ciq_sites(ciq_df)
787
+ forbidden_by_bsc, forbidden_df = _read_forbidden_bcfs(forbidden_file)
788
+ assigned_sites = _assign_bcfs(
789
+ dump_bts, planned_sites, ciq_df, forbidden_by_bsc=forbidden_by_bsc
790
+ )
791
 
792
  df_bcf = _build_bcf_sheet_from_assigned_sites(assigned_sites)
793
+ df_bcf_free = _build_free_bcf_sheet(dump_bts, forbidden_by_bsc=forbidden_by_bsc)
794
  df_bts = _build_bts_sheet_from_assigned_sites(
795
  ciq_df, assigned_sites, mcc=mcc, mnc=mnc
796
  )
 
838
 
839
  sheets: dict[str, pd.DataFrame] = {
840
  "BCF": df_bcf,
841
+ "BCF_LIBRE": df_bcf_free,
842
  "BTS": df_bts,
843
  "BTS_GPRS": df_bts_min,
844
  "BTS_AMR": df_bts_min,
 
849
  "TRX": df_trx,
850
  }
851
 
852
+ if not forbidden_df.empty:
853
+ sheets["BCF_FORBIDDEN"] = forbidden_df
854
+
855
  bytes_io = io.BytesIO()
856
  with pd.ExcelWriter(bytes_io, engine="xlsxwriter") as writer:
857
  for sheet_name, df in sheets.items():