forbiden BCF
Browse files- 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,
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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():
|