Spaces:
Sleeping
Sleeping
Update pages/laporan.py
Browse files- pages/laporan.py +103 -67
pages/laporan.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
# File: pages/laporan.py (Revisi Final
|
2 |
|
3 |
from dash import dcc, html, Input, Output, callback, no_update, State, dash_table
|
4 |
import dash_bootstrap_components as dbc
|
@@ -27,7 +27,7 @@ except (AttributeError, ImportError):
|
|
27 |
print("WARNING: Pustaka 'reportlab' atau 'kaleido' tidak terinstall. Fitur unduh PDF tidak akan berfungsi.")
|
28 |
|
29 |
# -----------------------------------------------------------------------------
|
30 |
-
# BAGIAN 1: FUNGSI-FUNGSI HELPER (
|
31 |
# -----------------------------------------------------------------------------
|
32 |
|
33 |
def get_filter_text(pusk, tahun, bulan):
|
@@ -36,7 +36,6 @@ def get_filter_text(pusk, tahun, bulan):
|
|
36 |
bulan_txt = "Semua Bulan" if not bulan else ", ".join(sorted(bulan))
|
37 |
return f"Data dari: {pusk_txt} | Tahun: {tahun_txt} | Bulan: {bulan_txt}"
|
38 |
|
39 |
-
# --- Helper untuk Analisis Tren Penyakit ---
|
40 |
def kategori_penyakit_atp(icd):
|
41 |
if pd.isna(icd) or str(icd).strip() == "": return 'Tidak Menular'
|
42 |
icd_clean = str(icd).strip().upper()
|
@@ -82,7 +81,16 @@ def create_trend_analysis_text(df, time_unit='tahun'):
|
|
82 |
analysis_points.append(f"- Kasus **{disease}** {tren}.")
|
83 |
return "Ringkasan Tren 3 Penyakit Teratas:\n\n" + "\n".join(analysis_points)
|
84 |
|
85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
def generate_gender_analysis_text(total_lk, total_pr):
|
87 |
if total_lk == 0 and total_pr == 0: return "Tidak ada data gender untuk dianalisis."
|
88 |
total_kasus = total_lk + total_pr
|
@@ -102,7 +110,7 @@ def generate_age_analysis_text(baru_data, lama_data):
|
|
102 |
return f"Kelompok umur dengan total kasus tertinggi adalah **{max_total_kelompok}** ({int(total_per_kelompok[max_total_kelompok]):,} kasus)."
|
103 |
|
104 |
# -----------------------------------------------------------------------------
|
105 |
-
# BAGIAN 2: LAYOUT HALAMAN (
|
106 |
# -----------------------------------------------------------------------------
|
107 |
layout = dbc.Container([
|
108 |
dcc.Store(id='laporan-data-store'),
|
@@ -135,9 +143,8 @@ layout = dbc.Container([
|
|
135 |
], fluid=True)
|
136 |
|
137 |
# -----------------------------------------------------------------------------
|
138 |
-
# BAGIAN 3: CALLBACKS
|
139 |
# -----------------------------------------------------------------------------
|
140 |
-
# Callback filter tidak berubah
|
141 |
@callback(
|
142 |
Output('laporan-pusk-filter', 'options'),
|
143 |
Output('laporan-tahun-filter', 'options'),
|
@@ -183,17 +190,17 @@ def laporan_update_bulan_filter(selected_tahun):
|
|
183 |
Input('laporan-bulan-filter', 'value')
|
184 |
)
|
185 |
def update_laporan_terpadu_tabs(selected_pusk, selected_tahun, selected_bulan):
|
|
|
186 |
if not selected_pusk or not selected_tahun:
|
187 |
msg = html.P("Silakan pilih minimal Puskesmas dan Tahun.", className="text-center text-primary mt-5")
|
188 |
return msg, msg, "", True, None
|
189 |
|
190 |
filter_summary_text = get_filter_text(selected_pusk, selected_tahun, selected_bulan)
|
191 |
-
|
192 |
-
if selected_bulan:
|
193 |
|
194 |
-
|
195 |
-
|
196 |
-
stmt = select(*[getattr(detail_penyakit.c, col) for col in all_cols]).where(and_(*filters))
|
197 |
|
198 |
with engine.connect() as conn:
|
199 |
df_base = pd.read_sql(stmt, conn)
|
@@ -202,18 +209,15 @@ def update_laporan_terpadu_tabs(selected_pusk, selected_tahun, selected_bulan):
|
|
202 |
msg = dbc.Alert("Tidak ada data ditemukan untuk kriteria yang dipilih.", color="warning", className="m-4")
|
203 |
return msg, msg, filter_summary_text, True, None
|
204 |
|
205 |
-
#
|
206 |
-
# BAGIAN A: Proses Data untuk TAB 1 (Analisis Tren Penyakit)
|
207 |
-
# =========================================================
|
208 |
kode_dihindari = ('V', 'W', 'X', 'Y', 'Z')
|
209 |
df_base['icd_x_str'] = df_base['icd_x'].astype(str).str.strip().str.upper()
|
210 |
df_filtered_icd = df_base[~df_base['icd_x_str'].str.startswith(kode_dihindari, na=False)].copy()
|
211 |
-
|
212 |
df_filtered_icd['kategori'] = df_filtered_icd['icd_x'].apply(kategori_penyakit_atp)
|
213 |
df_ranking_total = df_filtered_icd.groupby('jenis_penyakit')['totall'].sum().nlargest(10).sort_values().reset_index()
|
214 |
top_10_penyakit_list = df_ranking_total['jenis_penyakit'].tolist()
|
215 |
-
df_top10_base = df_filtered_icd[df_filtered_icd['jenis_penyakit'].isin(top_10_penyakit_list)]
|
216 |
-
|
217 |
fig_ranking_simple = px.bar(df_ranking_total, x='totall', y='jenis_penyakit', orientation='h', template='plotly_white', title='<b>Peringkat 10 Penyakit Teratas</b>')
|
218 |
table_ranking = create_ranking_table(df_ranking_total)
|
219 |
analysis_ranking = create_ranking_analysis_text(df_ranking_total)
|
@@ -222,70 +226,99 @@ def update_laporan_terpadu_tabs(selected_pusk, selected_tahun, selected_bulan):
|
|
222 |
fig_category_pie = px.pie(df_category_pie, values='totall', names='kategori', title='<b>Komposisi Menular vs Tidak Menular</b>', color_discrete_map={'Menular':'crimson', 'Tidak Menular':'royalblue'})
|
223 |
analysis_pie = create_pie_analysis_text(df_category_pie)
|
224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
df_monthly_line = df_top10_base.groupby(['tahun', 'bulan', 'jenis_penyakit'])['totall'].sum().reset_index()
|
226 |
df_monthly_line['periode'] = df_monthly_line['tahun'].astype(str) + '-' + df_monthly_line['bulan'].str.zfill(2)
|
227 |
-
fig_line_monthly = px.line(df_monthly_line.sort_values('periode'), x='periode', y='totall', color='jenis_penyakit', title="<b>Tren Bulanan 10
|
228 |
analysis_monthly_trend = create_trend_analysis_text(df_monthly_line, 'periode')
|
229 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
tab1_content = html.Div([
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
242 |
], className="mt-3")
|
243 |
|
244 |
-
#
|
245 |
-
# BAGIAN B: Proses Data untuk TAB 2 (Analisis Demografi)
|
246 |
-
# =========================================================
|
247 |
total_lk = df_base['laki_laki'].sum()
|
248 |
total_pr = df_base['perempuan'].sum()
|
249 |
fig_gender = go.Figure(data=[go.Pie(labels=['Laki-laki', 'Perempuan'], values=[total_lk, total_pr], hole=.4, marker_colors=['royalblue', 'crimson'])])
|
250 |
fig_gender.update_layout(title_text='<b>Distribusi Kasus Berdasarkan Jenis Kelamin</b>', template='plotly_white')
|
251 |
analysis_gender = generate_gender_analysis_text(total_lk, total_pr)
|
252 |
-
|
253 |
kelompok_map = {'Bayi & Balita (<5th)':['usia_0_7_hr_baru','usia_0_7_hr_lama','usia_8_28_hr_baru','usia_8_28_hr_lama','usia_1bl_1th_baru','usia_1bl_1th_lama','usia_1_4th_baru','usia_1_4th_lama'],'Anak (5-9th)':['usia_5_9th_baru','usia_5_9th_lama'],'Remaja (10-19th)':['usia_10_14th_baru','usia_10_14th_lama','usia_15_19th_baru','usia_15_19th_lama'],'Dewasa (20-59th)':['usia_20_44th_baru','usia_20_44th_lama','usia_45_54th_baru','usia_45_54th_lama','usia_55_59th_baru','usia_55_59th_lama'],'Lansia (60+th)':['usia_60_69th_baru','usia_60_69th_lama','usia_70pl_baru','usia_70pl_lama']}
|
254 |
age_new_data, age_old_data = {}, {}
|
255 |
for kelompok, cols in kelompok_map.items():
|
256 |
age_new_data[kelompok] = df_base[[c for c in cols if 'baru' in c]].sum().sum()
|
257 |
age_old_data[kelompok] = df_base[[c for c in cols if 'lama' in c]].sum().sum()
|
258 |
-
|
259 |
fig_age = go.Figure(data=[go.Bar(name='Kasus Baru', x=list(age_new_data.keys()), y=list(age_new_data.values())), go.Bar(name='Kasus Lama', x=list(age_old_data.keys()), y=list(age_old_data.values()))]).update_layout(barmode='group', title_text="<b>Distribusi Kasus Berdasarkan Kelompok Umur</b>", template='plotly_white')
|
260 |
analysis_age = generate_age_analysis_text(age_new_data, age_old_data)
|
|
|
261 |
|
262 |
-
|
263 |
-
|
264 |
-
dbc.Col(dcc.Graph(figure=fig_gender), md=6),
|
265 |
-
dbc.Col(dcc.Graph(figure=fig_age), md=6),
|
266 |
-
dbc.Col(dbc.Card(dbc.CardBody(dcc.Markdown(analysis_gender))), md=6),
|
267 |
-
dbc.Col(dbc.Card(dbc.CardBody(dcc.Markdown(analysis_age))), md=6),
|
268 |
-
], className="mb-4"),
|
269 |
-
], className="mt-3")
|
270 |
-
|
271 |
-
# --- SIMPAN SEMUA DATA UNTUK DIUNDUH ---
|
272 |
-
data_to_store = {
|
273 |
-
'figs_json': {
|
274 |
-
'ranking_simple': fig_ranking_simple.to_json(), 'category_pie': fig_category_pie.to_json(),
|
275 |
-
'line_trend_monthly': fig_line_monthly.to_json(), 'gender_pie': fig_gender.to_json(),
|
276 |
-
'age_bar': fig_age.to_json(),
|
277 |
-
},
|
278 |
-
'table_data': {'ranking': df_ranking_total.to_dict('records')},
|
279 |
-
'analysis_texts': {
|
280 |
-
'ranking': analysis_ranking, 'pie': analysis_pie, 'monthly_trend': analysis_monthly_trend,
|
281 |
-
'gender': analysis_gender, 'age': analysis_age,
|
282 |
-
},
|
283 |
-
'filter_text': filter_summary_text,
|
284 |
-
}
|
285 |
|
286 |
return tab1_content, tab2_content, filter_summary_text, not PDF_CAPABLE, data_to_store
|
287 |
|
288 |
-
# Callback
|
|
|
289 |
@callback(
|
290 |
Output("laporan-download-pdf", "data"),
|
291 |
Input("laporan-btn-unduh-pdf", "n_clicks"),
|
@@ -325,20 +358,23 @@ def download_laporan_as_pdf(n_clicks, stored_data):
|
|
325 |
story = [Paragraph("Laporan Analisis Terpadu", style_h1), Paragraph(stored_data.get('filter_text', ''), styles['Italic']), Spacer(1, 0.3*inch)]
|
326 |
analysis = stored_data.get('analysis_texts', {}); figs = stored_data.get('figs_json', {}); tables = stored_data.get('table_data', {})
|
327 |
|
328 |
-
# HALAMAN TREN PENYAKIT
|
329 |
story.append(Paragraph("BAGIAN 1: ANALISIS TREN PENYAKIT", style_h2))
|
330 |
-
story.append(fig_to_image(figs.get('ranking_simple'))); story.append(create_pdf_table(tables.get('ranking'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('ranking'
|
331 |
-
story.append(fig_to_image(figs.get('category_pie'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('pie'
|
332 |
-
story.append(fig_to_image(figs.get('
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
|
334 |
-
# HALAMAN ANALISIS DEMOGRAFI
|
335 |
story.append(Paragraph("BAGIAN 2: ANALISIS DEMOGRAFI", style_h2))
|
336 |
-
story.append(fig_to_image(figs.get('gender_pie'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('gender'
|
337 |
-
story.append(Spacer(1, 0.3*inch)); story.append(fig_to_image(figs.get('age_bar'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('age'
|
338 |
|
339 |
doc.build(story); return dcc.send_bytes(buffer.getvalue(), "Laporan_Terpadu_Penyakit.pdf")
|
340 |
|
341 |
-
# Callback 5: Unduh Excel
|
342 |
@callback(
|
343 |
Output("laporan-download-excel", "data"),
|
344 |
Input("laporan-btn-unduh-excel", "n_clicks"),
|
|
|
1 |
+
# File: pages/laporan.py (Revisi Final dengan Layout Satu Baris per Grafik)
|
2 |
|
3 |
from dash import dcc, html, Input, Output, callback, no_update, State, dash_table
|
4 |
import dash_bootstrap_components as dbc
|
|
|
27 |
print("WARNING: Pustaka 'reportlab' atau 'kaleido' tidak terinstall. Fitur unduh PDF tidak akan berfungsi.")
|
28 |
|
29 |
# -----------------------------------------------------------------------------
|
30 |
+
# BAGIAN 1: FUNGSI-FUNGSI HELPER (Tidak Berubah)
|
31 |
# -----------------------------------------------------------------------------
|
32 |
|
33 |
def get_filter_text(pusk, tahun, bulan):
|
|
|
36 |
bulan_txt = "Semua Bulan" if not bulan else ", ".join(sorted(bulan))
|
37 |
return f"Data dari: {pusk_txt} | Tahun: {tahun_txt} | Bulan: {bulan_txt}"
|
38 |
|
|
|
39 |
def kategori_penyakit_atp(icd):
|
40 |
if pd.isna(icd) or str(icd).strip() == "": return 'Tidak Menular'
|
41 |
icd_clean = str(icd).strip().upper()
|
|
|
81 |
analysis_points.append(f"- Kasus **{disease}** {tren}.")
|
82 |
return "Ringkasan Tren 3 Penyakit Teratas:\n\n" + "\n".join(analysis_points)
|
83 |
|
84 |
+
def create_comparison_analysis_text(df):
|
85 |
+
if df.empty: return "Tidak ada data untuk dianalisis."
|
86 |
+
pusk_contribution = df.groupby('kode_pusk')['totall'].sum().sort_values(ascending=False)
|
87 |
+
if pusk_contribution.empty: return "Tidak ada data puskesmas untuk dianalisis."
|
88 |
+
top_pusk, top_pusk_cases, total_cases = pusk_contribution.index[0], pusk_contribution.iloc[0], pusk_contribution.sum()
|
89 |
+
if total_cases == 0: return "Total kasus adalah nol."
|
90 |
+
top_pusk_percent = (top_pusk_cases / total_cases * 100).round(1)
|
91 |
+
top_disease_by_pusk = df[df['kode_pusk'] == top_pusk].groupby('jenis_penyakit')['totall'].sum().idxmax()
|
92 |
+
return f"- **{top_pusk}** menjadi puskesmas dengan kontribusi kasus tertinggi, yaitu **{int(top_pusk_cases):,}** kasus ({top_pusk_percent}% dari total).\n- Penyakit yang paling banyak disumbangkan oleh {top_pusk} adalah **{top_disease_by_pusk}**."
|
93 |
+
|
94 |
def generate_gender_analysis_text(total_lk, total_pr):
|
95 |
if total_lk == 0 and total_pr == 0: return "Tidak ada data gender untuk dianalisis."
|
96 |
total_kasus = total_lk + total_pr
|
|
|
110 |
return f"Kelompok umur dengan total kasus tertinggi adalah **{max_total_kelompok}** ({int(total_per_kelompok[max_total_kelompok]):,} kasus)."
|
111 |
|
112 |
# -----------------------------------------------------------------------------
|
113 |
+
# BAGIAN 2: LAYOUT HALAMAN (Tidak Berubah)
|
114 |
# -----------------------------------------------------------------------------
|
115 |
layout = dbc.Container([
|
116 |
dcc.Store(id='laporan-data-store'),
|
|
|
143 |
], fluid=True)
|
144 |
|
145 |
# -----------------------------------------------------------------------------
|
146 |
+
# BAGIAN 3: CALLBACKS (Tidak Berubah)
|
147 |
# -----------------------------------------------------------------------------
|
|
|
148 |
@callback(
|
149 |
Output('laporan-pusk-filter', 'options'),
|
150 |
Output('laporan-tahun-filter', 'options'),
|
|
|
190 |
Input('laporan-bulan-filter', 'value')
|
191 |
)
|
192 |
def update_laporan_terpadu_tabs(selected_pusk, selected_tahun, selected_bulan):
|
193 |
+
# Logika pengambilan dan pemrosesan data awal tidak berubah
|
194 |
if not selected_pusk or not selected_tahun:
|
195 |
msg = html.P("Silakan pilih minimal Puskesmas dan Tahun.", className="text-center text-primary mt-5")
|
196 |
return msg, msg, "", True, None
|
197 |
|
198 |
filter_summary_text = get_filter_text(selected_pusk, selected_tahun, selected_bulan)
|
199 |
+
base_filters = [detail_penyakit.c.kode_pusk.in_(selected_pusk), detail_penyakit.c.tahun.in_(selected_tahun)]
|
200 |
+
if selected_bulan: base_filters.append(detail_penyakit.c.bulan.in_(selected_bulan))
|
201 |
|
202 |
+
all_cols = ['jenis_penyakit', 'icd_x', 'tahun', 'bulan', 'kode_pusk', 'laki_laki', 'perempuan', 'usia_0_7_hr_baru', 'usia_0_7_hr_lama', 'usia_8_28_hr_baru', 'usia_8_28_hr_lama', 'usia_1bl_1th_baru', 'usia_1bl_1th_lama', 'usia_1_4th_baru', 'usia_1_4th_lama', 'usia_5_9th_baru', 'usia_5_9th_lama', 'usia_10_14th_baru', 'usia_10_14th_lama', 'usia_15_19th_baru', 'usia_15_19th_lama', 'usia_20_44th_baru', 'usia_20_44th_lama', 'usia_45_54th_baru', 'usia_45_54th_lama', 'usia_55_59th_baru', 'usia_55_59th_lama', 'usia_60_69th_baru', 'usia_60_69th_lama', 'usia_70pl_baru', 'usia_70pl_lama', 'totall']
|
203 |
+
stmt = select(*[getattr(detail_penyakit.c, col) for col in all_cols]).where(and_(*base_filters))
|
|
|
204 |
|
205 |
with engine.connect() as conn:
|
206 |
df_base = pd.read_sql(stmt, conn)
|
|
|
209 |
msg = dbc.Alert("Tidak ada data ditemukan untuk kriteria yang dipilih.", color="warning", className="m-4")
|
210 |
return msg, msg, filter_summary_text, True, None
|
211 |
|
212 |
+
# Pembuatan semua 9 grafik + 1 tabel (tidak berubah)
|
|
|
|
|
213 |
kode_dihindari = ('V', 'W', 'X', 'Y', 'Z')
|
214 |
df_base['icd_x_str'] = df_base['icd_x'].astype(str).str.strip().str.upper()
|
215 |
df_filtered_icd = df_base[~df_base['icd_x_str'].str.startswith(kode_dihindari, na=False)].copy()
|
|
|
216 |
df_filtered_icd['kategori'] = df_filtered_icd['icd_x'].apply(kategori_penyakit_atp)
|
217 |
df_ranking_total = df_filtered_icd.groupby('jenis_penyakit')['totall'].sum().nlargest(10).sort_values().reset_index()
|
218 |
top_10_penyakit_list = df_ranking_total['jenis_penyakit'].tolist()
|
219 |
+
df_top10_base = df_filtered_icd[df_filtered_icd['jenis_penyakit'].isin(top_10_penyakit_list)].copy()
|
220 |
+
|
221 |
fig_ranking_simple = px.bar(df_ranking_total, x='totall', y='jenis_penyakit', orientation='h', template='plotly_white', title='<b>Peringkat 10 Penyakit Teratas</b>')
|
222 |
table_ranking = create_ranking_table(df_ranking_total)
|
223 |
analysis_ranking = create_ranking_analysis_text(df_ranking_total)
|
|
|
226 |
fig_category_pie = px.pie(df_category_pie, values='totall', names='kategori', title='<b>Komposisi Menular vs Tidak Menular</b>', color_discrete_map={'Menular':'crimson', 'Tidak Menular':'royalblue'})
|
227 |
analysis_pie = create_pie_analysis_text(df_category_pie)
|
228 |
|
229 |
+
df_yearly_data = df_top10_base.groupby(['tahun', 'jenis_penyakit'])['totall'].sum().reset_index()
|
230 |
+
df_yearly_data['tahun'] = df_yearly_data['tahun'].astype(str)
|
231 |
+
fig_bar_trend_yearly = px.bar(df_yearly_data, x='totall', y='jenis_penyakit', color='tahun', orientation='h', barmode='group', title="<b>Perbandingan Kasus Tahunan (Top 10)</b>", template='plotly_white')
|
232 |
+
fig_bar_trend_yearly.update_layout(yaxis={'categoryorder':'total ascending'})
|
233 |
+
|
234 |
+
fig_line_trend_yearly = px.line(df_yearly_data, x='tahun', y='totall', color='jenis_penyakit', markers=True, title="<b>Tren Tahunan (Top 10)</b>")
|
235 |
+
analysis_yearly_trend = create_trend_analysis_text(df_yearly_data, 'tahun')
|
236 |
+
|
237 |
df_monthly_line = df_top10_base.groupby(['tahun', 'bulan', 'jenis_penyakit'])['totall'].sum().reset_index()
|
238 |
df_monthly_line['periode'] = df_monthly_line['tahun'].astype(str) + '-' + df_monthly_line['bulan'].str.zfill(2)
|
239 |
+
fig_line_monthly = px.line(df_monthly_line.sort_values('periode'), x='periode', y='totall', color='jenis_penyakit', title="<b>Tren Bulanan (Top 10)</b>")
|
240 |
analysis_monthly_trend = create_trend_analysis_text(df_monthly_line, 'periode')
|
241 |
|
242 |
+
df_monthly_cat_trend = df_filtered_icd.groupby(['tahun', 'bulan', 'kategori'])['totall'].sum().reset_index()
|
243 |
+
df_monthly_cat_trend['periode'] = df_monthly_cat_trend['tahun'].astype(str) + '-' + df_monthly_cat_trend['bulan'].str.zfill(2)
|
244 |
+
fig_monthly_compare_trend = px.area(df_monthly_cat_trend.sort_values('periode'), x='periode', y='totall', color='kategori', title="<b>Tren Bulanan Kasus per Kategori</b>", color_discrete_map={'Menular':'crimson', 'Tidak Menular':'royalblue'})
|
245 |
+
|
246 |
+
df_pusk_compare = df_top10_base.groupby(['kode_pusk', 'jenis_penyakit'])['totall'].sum().reset_index()
|
247 |
+
fig_pusk_stacked = px.bar(df_pusk_compare, x='totall', y='jenis_penyakit', color='kode_pusk', orientation='h', title='<b>Kontribusi Kasus per Puskesmas</b>')
|
248 |
+
fig_pusk_stacked.update_layout(yaxis={'categoryorder':'total ascending'})
|
249 |
+
analysis_pusk_comparison = create_comparison_analysis_text(df_pusk_compare)
|
250 |
+
|
251 |
+
df_menular = df_filtered_icd[df_filtered_icd['kategori'] == 'Menular']; top_10_menular = df_menular.groupby('jenis_penyakit')['totall'].sum().nlargest(10).index
|
252 |
+
df_trend_menular = df_menular[df_menular['jenis_penyakit'].isin(top_10_menular)].groupby(['tahun', 'jenis_penyakit'])['totall'].sum().reset_index(); df_trend_menular['tahun'] = df_trend_menular['tahun'].astype(str)
|
253 |
+
fig_trend_menular = px.bar(df_trend_menular, x='totall', y='jenis_penyakit', color='tahun', orientation='h', barmode='group', title="<b>Perbandingan Tahunan (10 Penyakit Menular Teratas)</b>", template='plotly_white'); fig_trend_menular.update_layout(yaxis={'categoryorder':'total ascending'})
|
254 |
+
|
255 |
+
df_tidak_menular = df_filtered_icd[df_filtered_icd['kategori'] == 'Tidak Menular']; top_10_tidak_menular = df_tidak_menular.groupby('jenis_penyakit')['totall'].sum().nlargest(10).index
|
256 |
+
df_trend_tidak_menular = df_tidak_menular[df_tidak_menular['jenis_penyakit'].isin(top_10_tidak_menular)].groupby(['tahun', 'jenis_penyakit'])['totall'].sum().reset_index(); df_trend_tidak_menular['tahun'] = df_trend_tidak_menular['tahun'].astype(str)
|
257 |
+
fig_trend_tidak_menular = px.bar(df_trend_tidak_menular, x='totall', y='jenis_penyakit', color='tahun', orientation='h', barmode='group', title="<b>Perbandingan Tahunan (10 Penyakit Tidak Menular Teratas)</b>", template='plotly_white'); fig_trend_tidak_menular.update_layout(yaxis={'categoryorder':'total ascending'})
|
258 |
+
|
259 |
+
# =================================================================
|
260 |
+
# <<< INI ADALAH BAGIAN YANG DIREVISI SESUAI PERMINTAAN ANDA >>>
|
261 |
+
# =================================================================
|
262 |
+
# --- Menyusun Layout Baru untuk Tab 1 (Satu Visualisasi per Baris) ---
|
263 |
tab1_content = html.Div([
|
264 |
+
# 1. Grafik Peringkat dan Tabel
|
265 |
+
dbc.Row(dbc.Col(dcc.Graph(figure=fig_ranking_simple), md=12), className="mb-2"),
|
266 |
+
dbc.Row(dbc.Col([html.H5("Tabel Peringkat 10 Besar"), table_ranking, dbc.Card(dbc.CardBody(dcc.Markdown(analysis_ranking)), className="mt-3")], md=12)),
|
267 |
+
html.Hr(className="my-4"),
|
268 |
+
|
269 |
+
# 2. Grafik Pie Komposisi
|
270 |
+
dbc.Row(dbc.Col([dcc.Graph(figure=fig_category_pie), dbc.Card(dbc.CardBody(dcc.Markdown(analysis_pie)), className="mt-2")], md=12)),
|
271 |
+
html.Hr(className="my-4"),
|
272 |
+
|
273 |
+
# 3. Grafik Perbandingan Tahunan (Batang)
|
274 |
+
dbc.Row(dbc.Col(dcc.Graph(figure=fig_bar_trend_yearly), md=12)),
|
275 |
+
html.Hr(className="my-4"),
|
276 |
+
|
277 |
+
# 4. Grafik Tren Tahunan (Garis)
|
278 |
+
dbc.Row(dbc.Col([dcc.Graph(figure=fig_line_trend_yearly), dbc.Card(dbc.CardBody(dcc.Markdown(analysis_yearly_trend)), className="mt-2")], md=12)),
|
279 |
+
html.Hr(className="my-4"),
|
280 |
+
|
281 |
+
# 5. Grafik Tren Bulanan (Garis)
|
282 |
+
dbc.Row(dbc.Col([dcc.Graph(figure=fig_line_monthly), dbc.Card(dbc.CardBody(dcc.Markdown(analysis_monthly_trend)), className="mt-2")], md=12)),
|
283 |
+
html.Hr(className="my-4"),
|
284 |
+
|
285 |
+
# 6. Grafik Tren Bulanan per Kategori (Area)
|
286 |
+
dbc.Row(dbc.Col(dcc.Graph(figure=fig_monthly_compare_trend), md=12)),
|
287 |
+
html.Hr(className="my-4"),
|
288 |
+
|
289 |
+
# 7. Grafik Kontribusi Puskesmas
|
290 |
+
dbc.Row(dbc.Col([dcc.Graph(figure=fig_pusk_stacked), dbc.Card(dbc.CardBody(dcc.Markdown(analysis_pusk_comparison)), className="mt-2")], md=12)),
|
291 |
+
html.Hr(className="my-4"),
|
292 |
+
|
293 |
+
# 8 & 9. Grafik Perbandingan Tahunan per Kategori
|
294 |
+
html.H4("Analisis Detail Berdasarkan Kategori Penyakit", className="text-center my-4"),
|
295 |
+
dbc.Row(dbc.Col(dcc.Graph(figure=fig_trend_menular), md=12), className="mb-4"),
|
296 |
+
dbc.Row(dbc.Col(dcc.Graph(figure=fig_trend_tidak_menular), md=12)),
|
297 |
+
|
298 |
], className="mt-3")
|
299 |
|
300 |
+
# BAGIAN B: Proses Data untuk TAB 2 (Analisis Demografi) - Tidak Berubah
|
|
|
|
|
301 |
total_lk = df_base['laki_laki'].sum()
|
302 |
total_pr = df_base['perempuan'].sum()
|
303 |
fig_gender = go.Figure(data=[go.Pie(labels=['Laki-laki', 'Perempuan'], values=[total_lk, total_pr], hole=.4, marker_colors=['royalblue', 'crimson'])])
|
304 |
fig_gender.update_layout(title_text='<b>Distribusi Kasus Berdasarkan Jenis Kelamin</b>', template='plotly_white')
|
305 |
analysis_gender = generate_gender_analysis_text(total_lk, total_pr)
|
|
|
306 |
kelompok_map = {'Bayi & Balita (<5th)':['usia_0_7_hr_baru','usia_0_7_hr_lama','usia_8_28_hr_baru','usia_8_28_hr_lama','usia_1bl_1th_baru','usia_1bl_1th_lama','usia_1_4th_baru','usia_1_4th_lama'],'Anak (5-9th)':['usia_5_9th_baru','usia_5_9th_lama'],'Remaja (10-19th)':['usia_10_14th_baru','usia_10_14th_lama','usia_15_19th_baru','usia_15_19th_lama'],'Dewasa (20-59th)':['usia_20_44th_baru','usia_20_44th_lama','usia_45_54th_baru','usia_45_54th_lama','usia_55_59th_baru','usia_55_59th_lama'],'Lansia (60+th)':['usia_60_69th_baru','usia_60_69th_lama','usia_70pl_baru','usia_70pl_lama']}
|
307 |
age_new_data, age_old_data = {}, {}
|
308 |
for kelompok, cols in kelompok_map.items():
|
309 |
age_new_data[kelompok] = df_base[[c for c in cols if 'baru' in c]].sum().sum()
|
310 |
age_old_data[kelompok] = df_base[[c for c in cols if 'lama' in c]].sum().sum()
|
|
|
311 |
fig_age = go.Figure(data=[go.Bar(name='Kasus Baru', x=list(age_new_data.keys()), y=list(age_new_data.values())), go.Bar(name='Kasus Lama', x=list(age_old_data.keys()), y=list(age_old_data.values()))]).update_layout(barmode='group', title_text="<b>Distribusi Kasus Berdasarkan Kelompok Umur</b>", template='plotly_white')
|
312 |
analysis_age = generate_age_analysis_text(age_new_data, age_old_data)
|
313 |
+
tab2_content = html.Div([dbc.Row([dbc.Col(dcc.Graph(figure=fig_gender), md=6),dbc.Col(dcc.Graph(figure=fig_age), md=6),dbc.Col(dbc.Card(dbc.CardBody(dcc.Markdown(analysis_gender))), md=6),dbc.Col(dbc.Card(dbc.CardBody(dcc.Markdown(analysis_age))), md=6)], className="mb-4")], className="mt-3")
|
314 |
|
315 |
+
# Penyimpanan data ke dcc.Store tidak berubah
|
316 |
+
data_to_store = {'figs_json': {'ranking_simple': fig_ranking_simple.to_json(), 'category_pie': fig_category_pie.to_json(),'bar_trend_yearly': fig_bar_trend_yearly.to_json(),'line_trend_yearly': fig_line_trend_yearly.to_json(),'line_trend_monthly': fig_line_monthly.to_json(),'monthly_compare_trend': fig_monthly_compare_trend.to_json(),'pusk_stacked': fig_pusk_stacked.to_json(),'trend_menular': fig_trend_menular.to_json(),'trend_tidak_menular': fig_trend_tidak_menular.to_json(),'gender_pie': fig_gender.to_json(),'age_bar': fig_age.to_json(),},'table_data': {'ranking': df_ranking_total.to_dict('records')},'analysis_texts': {'ranking': analysis_ranking, 'pie': analysis_pie,'yearly_trend': analysis_yearly_trend,'monthly_trend': analysis_monthly_trend,'pusk_comparison': analysis_pusk_comparison,'gender': analysis_gender, 'age': analysis_age,},'filter_text': filter_summary_text,}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
317 |
|
318 |
return tab1_content, tab2_content, filter_summary_text, not PDF_CAPABLE, data_to_store
|
319 |
|
320 |
+
# Callback PDF dan Excel tidak perlu diubah, karena mereka mengambil dari dcc.Store
|
321 |
+
# ... (sisa kode sama seperti sebelumnya) ...
|
322 |
@callback(
|
323 |
Output("laporan-download-pdf", "data"),
|
324 |
Input("laporan-btn-unduh-pdf", "n_clicks"),
|
|
|
358 |
story = [Paragraph("Laporan Analisis Terpadu", style_h1), Paragraph(stored_data.get('filter_text', ''), styles['Italic']), Spacer(1, 0.3*inch)]
|
359 |
analysis = stored_data.get('analysis_texts', {}); figs = stored_data.get('figs_json', {}); tables = stored_data.get('table_data', {})
|
360 |
|
|
|
361 |
story.append(Paragraph("BAGIAN 1: ANALISIS TREN PENYAKIT", style_h2))
|
362 |
+
story.append(fig_to_image(figs.get('ranking_simple'))); story.append(create_pdf_table(tables.get('ranking'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('ranking'))); story.append(PageBreak())
|
363 |
+
story.append(fig_to_image(figs.get('category_pie'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('pie'))); story.append(Spacer(1, 0.2*inch))
|
364 |
+
story.append(fig_to_image(figs.get('bar_trend_yearly'))); story.append(PageBreak())
|
365 |
+
story.append(fig_to_image(figs.get('line_trend_yearly'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('yearly_trend'))); story.append(Spacer(1, 0.2*inch))
|
366 |
+
story.append(fig_to_image(figs.get('line_trend_monthly'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('monthly_trend'))); story.append(PageBreak())
|
367 |
+
story.append(fig_to_image(figs.get('monthly_compare_trend'))); story.append(Spacer(1, 0.2*inch))
|
368 |
+
story.append(fig_to_image(figs.get('pusk_stacked'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('pusk_comparison'))); story.append(PageBreak())
|
369 |
+
story.append(fig_to_image(figs.get('trend_menular'))); story.append(Spacer(1, 0.2*inch))
|
370 |
+
story.append(fig_to_image(figs.get('trend_tidak_menular'))); story.append(PageBreak())
|
371 |
|
|
|
372 |
story.append(Paragraph("BAGIAN 2: ANALISIS DEMOGRAFI", style_h2))
|
373 |
+
story.append(fig_to_image(figs.get('gender_pie'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('gender')))
|
374 |
+
story.append(Spacer(1, 0.3*inch)); story.append(fig_to_image(figs.get('age_bar'))); story.append(Spacer(1, 0.1*inch)); story.append(text_to_paragraph(analysis.get('age')))
|
375 |
|
376 |
doc.build(story); return dcc.send_bytes(buffer.getvalue(), "Laporan_Terpadu_Penyakit.pdf")
|
377 |
|
|
|
378 |
@callback(
|
379 |
Output("laporan-download-excel", "data"),
|
380 |
Input("laporan-btn-unduh-excel", "n_clicks"),
|