diff --git a/__pycache__/main.cpython-314.pyc b/__pycache__/main.cpython-314.pyc
new file mode 100644
index 000000000000..88544b6f9bc9
Binary files /dev/null and b/__pycache__/main.cpython-314.pyc differ
diff --git a/content/code-security/reference/supply-chain-security/dependabot-options-reference.md b/content/code-security/reference/supply-chain-security/dependabot-options-reference.md
index aba16f7acd76..3bec6e2fc10f 100644
--- a/content/code-security/reference/supply-chain-security/dependabot-options-reference.md
+++ b/content/code-security/reference/supply-chain-security/dependabot-options-reference.md
@@ -569,11 +569,11 @@ Package manager | YAML value | Supported versions |
| {% ifversion dependabot-opentofu-support %} |
| OpenTofu | `opentofu` | Not applicable |
| {% endif %} |
-| pip| `pip` | v24.2 |
-| pip-compile | `pip` | 7.4.1 |
+| pip| `pip` | 24.2 |
+| pip-compile | `pip` | 7.5.3 |
| pipenv | `pip` | <= 2024.4.1 |
| pnpm | `npm` | v7, v8
v9, v10 (version updates only) |
-| poetry | `pip` | v2 |
+| poetry | `pip` | 2.2.1 |
| {% ifversion dependabot-pre-commit-support %} |
| pre-commit | `pre-commit` | Not applicable |
| {% endif %} |
diff --git a/data/reusables/dependabot/supported-package-managers.md b/data/reusables/dependabot/supported-package-managers.md
index a5a626f523ec..dc9aa4f85ac6 100644
--- a/data/reusables/dependabot/supported-package-managers.md
+++ b/data/reusables/dependabot/supported-package-managers.md
@@ -41,11 +41,11 @@ npm | `npm` | v7, v8, v9, v10, v11 | {% octicon "check"
| {% ifversion dependabot-opentofu-support %} |
[OpenTofu](#opentofu) | `opentofu` | Not applicable | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | Not applicable |
| {% endif %} |
-[pip](#pip-and-pip-compile) | `pip` | v21.1.2 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
-pipenv | `pip` | <= 2021-05-29 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
-[pip-compile](#pip-and-pip-compile) | `pip` | 6.1.0 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
+[pip](#pip-and-pip-compile) | `pip` | 24.2 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
+pipenv | `pip` | <= 2024.4.1 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
+[pip-compile](#pip-and-pip-compile) | `pip` | 7.5.3 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
| {% ifversion dependabot-updates-pnpmv9-support %}pnpm{% else %}[pnpm](#pnpm){% endif %} | `npm` | v7, v8, v9, v10 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} {% ifversion dependabot-updates-pnpmv9-support %}{% else %}(v7 and v8 only){% endif %}| {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
-poetry | `pip` | v1 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
+poetry | `pip` | 2.2.1 | {% octicon "check" aria-label="Supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
| {% ifversion dependabot-pre-commit-support %} |
[pre-commit](#pre-commit) | `pre-commit` | Not applicable | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "x" aria-label="Not supported" %} | {% octicon "check" aria-label="Supported" %} | {% octicon "x" aria-label="Not supported" %} |
| {% endif %} |
diff --git a/gnupg b/gnupg
new file mode 160000
index 000000000000..76b47528526e
--- /dev/null
+++ b/gnupg
@@ -0,0 +1 @@
+Subproject commit 76b47528526ecea4218b1d9aeaacf477fd51ebb6
diff --git a/main.py b/main.py
new file mode 100644
index 000000000000..1f86c9f175e9
--- /dev/null
+++ b/main.py
@@ -0,0 +1,138 @@
+import html
+
+import folium
+import pandas as pd
+import streamlit as st
+from streamlit_folium import st_folium
+
+st.set_page_config(page_title="Cell Dump Analyzer", layout="wide")
+
+st.title("π± Cell Dump & CDR Analysis Tool")
+st.markdown(
+ "Unggah data *cell dump* (format CSV/Excel) untuk melakukan analisis lokasi, "
+ "target, dan pola komunikasi."
+)
+
+uploaded_file = st.file_uploader(
+ "Unggah File Cell Dump (CSV atau Excel)", type=["csv", "xlsx"]
+)
+
+if uploaded_file is None:
+ st.info("Silakan unggah file *cell dump* terlebih dahulu melalui panel di atas.")
+else:
+ try:
+ if uploaded_file.name.lower().endswith(".csv"):
+ df = pd.read_csv(uploaded_file)
+ else:
+ df = pd.read_excel(uploaded_file)
+ except (pd.errors.ParserError, ValueError, OSError) as error:
+ st.error(f"File tidak dapat dibaca: {error}")
+ st.stop()
+
+ if df.empty or len(df.columns) == 0:
+ st.warning("File tidak berisi data atau kolom yang dapat dianalisis.")
+ st.stop()
+
+ st.success(f"Berhasil memuat {len(df)} baris data.")
+
+ with st.expander("π Lihat Preview Data Mentah"):
+ st.dataframe(df.head())
+
+ st.sidebar.header("Filter Analisis")
+ columns = df.columns.tolist()
+
+ msisdn_default = columns.index("MSISDN") if "MSISDN" in columns else 0
+ imei_default = columns.index("IMEI") if "IMEI" in columns else min(1, len(columns) - 1)
+ msisdn_col = st.sidebar.selectbox(
+ "Pilih Kolom Nomor (MSISDN)", columns, index=msisdn_default
+ )
+ imei_col = st.sidebar.selectbox("Pilih Kolom IMEI", columns, index=imei_default)
+ target_number = st.sidebar.text_input("Cari Nomor Target (MSISDN / IMEI):", "")
+
+ filtered_df = df.copy()
+ if target_number:
+ matching_number = (
+ filtered_df[msisdn_col].astype("string").str.contains(
+ target_number, case=False, na=False, regex=False
+ )
+ | filtered_df[imei_col].astype("string").str.contains(
+ target_number, case=False, na=False, regex=False
+ )
+ )
+ filtered_df = filtered_df[matching_number]
+
+ col1, col2, col3 = st.columns(3)
+ col1.metric("Total Record", len(filtered_df))
+ col2.metric("Target Unik (MSISDN)", filtered_df[msisdn_col].nunique())
+ col3.metric(
+ "Menara BTS Unik",
+ filtered_df["Tower_ID"].nunique() if "Tower_ID" in filtered_df else 0,
+ )
+
+ tab1, tab2, tab3 = st.tabs(
+ ["πΊοΈ Peta Lokasi (Mapping)", "π Analisis Frekuensi", "π Data Terfilter"]
+ )
+
+ with tab1:
+ st.subheader("Visualisasi Sebaran Lokasi BTS")
+ if {"Lat", "Long"}.issubset(filtered_df.columns):
+ locations = filtered_df.copy()
+ locations["Lat"] = pd.to_numeric(locations["Lat"], errors="coerce")
+ locations["Long"] = pd.to_numeric(locations["Long"], errors="coerce")
+ locations = locations.dropna(subset=["Lat", "Long"])
+ locations = locations[
+ locations["Lat"].between(-90, 90)
+ & locations["Long"].between(-180, 180)
+ ]
+
+ if locations.empty:
+ st.warning("Tidak ada koordinat valid untuk ditampilkan pada peta.")
+ else:
+ map_lat = locations["Lat"].mean()
+ map_lon = locations["Long"].mean()
+ map_view = folium.Map(location=[map_lat, map_lon], zoom_start=13)
+
+ for _, row in locations.iterrows():
+ msisdn = html.escape(str(row.get(msisdn_col, "-")))
+ timestamp = html.escape(str(row.get("Timestamp", "-")))
+ tower_id = html.escape(str(row.get("Tower_ID", "-")))
+ popup_text = (
+ f"MSISDN: {msisdn}
Waktu: {timestamp}
BTS: {tower_id}"
+ )
+ folium.CircleMarker(
+ location=[row["Lat"], row["Long"]],
+ radius=5,
+ color="red",
+ fill=True,
+ fill_color="red",
+ popup=folium.Popup(popup_text, max_width=300),
+ ).add_to(map_view)
+
+ st_folium(map_view, width=1000, height=500)
+ else:
+ st.warning(
+ "Kolom 'Lat' dan 'Long' tidak ditemukan dalam dataset untuk membuat peta."
+ )
+
+ with tab2:
+ st.subheader("Statistik Komunikasi & Aktivitas")
+ top_numbers = filtered_df[msisdn_col].value_counts().head(10)
+ st.write("**Nomor Paling Sering Muncul (Top 10):**")
+ st.bar_chart(top_numbers)
+
+ if "Tower_ID" in filtered_df.columns:
+ top_towers = filtered_df["Tower_ID"].value_counts().head(10)
+ st.write("**BTS Paling Sering Terhubung (Top 10):**")
+ st.bar_chart(top_towers)
+
+ with tab3:
+ st.subheader("Tabel Data Terfilter")
+ st.dataframe(filtered_df)
+
+ csv = filtered_df.to_csv(index=False).encode("utf-8")
+ st.download_button(
+ label="Unduh Data Terfilter (CSV)",
+ data=csv,
+ file_name="hasil_analisa_cell_dump.csv",
+ mime="text/csv",
+ )