sugitora commited on
Commit
d73254b
·
verified ·
1 Parent(s): f2eeea4

Upload 2 files

Browse files
Files changed (1) hide show
  1. app.R +87 -16
app.R CHANGED
@@ -1,6 +1,7 @@
1
  # app.R - 警備マッチング可視化アプリ(日付×contract_idベースマッチング対応版)
2
  # 要件: 充足率表示、日付期間選択、要営業強化エリア識別
3
  # 修正: ac_date_xxx.csv対応、日付とcontract_idの組み合わせでマッチング
 
4
 
5
  library(shiny)
6
  library(dplyr)
@@ -169,7 +170,8 @@ ui <- fluidPage(
169
  ),
170
  hr(),
171
  h4("🗺️ 表示設定"),
172
- checkboxInput("show_guards", "割当済み隊員を表示", TRUE),
 
173
  radioButtons(
174
  "match_mode",
175
  "追加マッチング方法(未割当隊員の参考)",
@@ -197,8 +199,12 @@ ui <- fluidPage(
197
  tags$span("要営業強化(80%未満)")
198
  ),
199
  tags$div(class = "legend-item",
200
- tags$div(class = "legend-color", style = "background-color: #3498db; opacity: 0.7;"),
201
- tags$span("割当済み隊員")
 
 
 
 
202
  )
203
  ),
204
  hr(),
@@ -235,6 +241,21 @@ ui <- fluidPage(
235
  )
236
  )
237
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  leafletOutput("map", height = 480),
239
  br(),
240
  h4("現場別 需給状況(選択期間・日付×契約ID別集計)"),
@@ -271,13 +292,27 @@ server <- function(input, output, session) {
271
  filter(!is.na(対応日)) %>%
272
  filter(対応日 >= d_start, 対応日 <= d_end)
273
 
274
- # 期間内で担当予定がある隊員(日付×contract_id単位)
275
  assigned_avail <- avail %>%
276
  filter(!is.na(日付), 日付 >= d_start, 日付 <= d_end) %>%
277
  filter(available_flag == 1L, !is.na(担当予定contract_id))
278
 
279
- # 隊員マスターと結合して座標取得
280
- assigned_guards <- assigned_avail %>%
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  inner_join(guard, by = "ユーザー番号")
282
 
283
  list(
@@ -285,7 +320,8 @@ server <- function(input, output, session) {
285
  date_end = d_end,
286
  active_sites = active_sites,
287
  assigned_avail = assigned_avail,
288
- assigned_guards = assigned_guards
 
289
  )
290
  })
291
 
@@ -377,6 +413,17 @@ server <- function(input, output, session) {
377
  paste0(round(avg_rate, 0), "%")
378
  })
379
 
 
 
 
 
 
 
 
 
 
 
 
380
  # 地図(初回のみ)
381
  output$map <- renderLeaflet({
382
  center_lat <- mean(c(BBOX$lat_min, BBOX$lat_max))
@@ -386,7 +433,7 @@ server <- function(input, output, session) {
386
  addTiles() %>%
387
  setView(lng = center_lng, lat = center_lat, zoom = 9) %>%
388
  addLayersControl(
389
- overlayGroups = c("現場(充足率)", "割当済み隊員"),
390
  options = layersControlOptions(collapsed = FALSE)
391
  )
392
  })
@@ -395,12 +442,14 @@ server <- function(input, output, session) {
395
  observe({
396
  dd <- daily()
397
  sites_data <- summary_tbl()
398
- gds <- dd$assigned_guards
 
399
 
400
  proxy <- leafletProxy("map")
401
  proxy %>%
402
  clearGroup("現場(充足率)") %>%
403
- clearGroup("割当済み隊員")
 
404
 
405
  # 現場マーカー(充足率付き)- 地理的にユニークな場所ごとに集約
406
  if (!("メッセージ" %in% colnames(sites_data)) && nrow(sites_data) > 0) {
@@ -466,9 +515,9 @@ server <- function(input, output, session) {
466
  }
467
  }
468
 
469
- # 割当済み隊員マーカー(ユニークな隊員のみ表示
470
- if (isTRUE(input$show_guards) && nrow(gds) > 0) {
471
- gds_unique <- gds %>%
472
  filter(!is.na(home_lat), !is.na(home_lng)) %>%
473
  distinct(ユーザー番号, .keep_all = TRUE)
474
 
@@ -480,10 +529,32 @@ server <- function(input, output, session) {
480
  radius = 6,
481
  color = "#3498db",
482
  fillColor = "#3498db",
483
- fillOpacity = 0.6,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  weight = 1,
485
- label = ~paste0("ユーザー番号: ", ユーザー番号, " | ", 苗字, " ", 名前, " | ", guard_city),
486
- group = "割当済み隊員"
487
  )
488
  }
489
  }
 
1
  # app.R - 警備マッチング可視化アプリ(日付×contract_idベースマッチング対応版)
2
  # 要件: 充足率表示、日付期間選択、要営業強化エリア識別
3
  # 修正: ac_date_xxx.csv対応、日付とcontract_idの組み合わせでマッチング
4
+ # 追加: アベイラブル隊員/ノンアベイラブル隊員の表示切替
5
 
6
  library(shiny)
7
  library(dplyr)
 
170
  ),
171
  hr(),
172
  h4("🗺️ 表示設定"),
173
+ checkboxInput("show_available", "アベイラブル隊員を表示(対応可否=1)", TRUE),
174
+ checkboxInput("show_non_available", "ノンアベイラブル隊員を表示(対応可否=0)", FALSE),
175
  radioButtons(
176
  "match_mode",
177
  "追加マッチング方法(未割当隊員の参考)",
 
199
  tags$span("要営業強化(80%未満)")
200
  ),
201
  tags$div(class = "legend-item",
202
+ tags$div(class = "legend-color", style = "background-color: #3498db;"),
203
+ tags$span("アベイラブル隊員(対応可否=1)")
204
+ ),
205
+ tags$div(class = "legend-item",
206
+ tags$div(class = "legend-color", style = "background-color: #95a5a6;"),
207
+ tags$span("ノンアベイラブル隊員(対応可否=0)")
208
  )
209
  ),
210
  hr(),
 
241
  )
242
  )
243
  ),
244
+ # 隊員統計
245
+ fluidRow(
246
+ column(6,
247
+ tags$div(class = "summary-header",
248
+ h5("アベイラブル隊員数(期間内)"),
249
+ textOutput("stat_available_guards", inline = TRUE)
250
+ )
251
+ ),
252
+ column(6,
253
+ tags$div(class = "summary-header",
254
+ h5("ノンアベイラブル隊員数(期間内)"),
255
+ textOutput("stat_non_available_guards", inline = TRUE)
256
+ )
257
+ )
258
+ ),
259
  leafletOutput("map", height = 480),
260
  br(),
261
  h4("現場別 需給状況(選択期間・日付×契約ID別集計)"),
 
292
  filter(!is.na(対応日)) %>%
293
  filter(対応日 >= d_start, 対応日 <= d_end)
294
 
295
+ # 期間内で担当予定がある隊員(日付×contract_id単位)- 充足率計算用
296
  assigned_avail <- avail %>%
297
  filter(!is.na(日付), 日付 >= d_start, 日付 <= d_end) %>%
298
  filter(available_flag == 1L, !is.na(担当予定contract_id))
299
 
300
+ # アベイラブル隊員(対応可否=1)
301
+ available_guards_avail <- avail %>%
302
+ filter(!is.na(日付), 日付 >= d_start, 日付 <= d_end) %>%
303
+ filter(available_flag == 1L) %>%
304
+ distinct(ユーザー番号)
305
+
306
+ available_guards <- available_guards_avail %>%
307
+ inner_join(guard, by = "ユーザー番号")
308
+
309
+ # ノンアベイラブル隊員(対応可否=0)
310
+ non_available_guards_avail <- avail %>%
311
+ filter(!is.na(日付), 日付 >= d_start, 日付 <= d_end) %>%
312
+ filter(available_flag == 0L) %>%
313
+ distinct(ユーザー番号)
314
+
315
+ non_available_guards <- non_available_guards_avail %>%
316
  inner_join(guard, by = "ユーザー番号")
317
 
318
  list(
 
320
  date_end = d_end,
321
  active_sites = active_sites,
322
  assigned_avail = assigned_avail,
323
+ available_guards = available_guards,
324
+ non_available_guards = non_available_guards
325
  )
326
  })
327
 
 
413
  paste0(round(avg_rate, 0), "%")
414
  })
415
 
416
+ # 隊員統計
417
+ output$stat_available_guards <- renderText({
418
+ dd <- daily()
419
+ paste0(nrow(dd$available_guards), " 人")
420
+ })
421
+
422
+ output$stat_non_available_guards <- renderText({
423
+ dd <- daily()
424
+ paste0(nrow(dd$non_available_guards), " 人")
425
+ })
426
+
427
  # 地図(初回のみ)
428
  output$map <- renderLeaflet({
429
  center_lat <- mean(c(BBOX$lat_min, BBOX$lat_max))
 
433
  addTiles() %>%
434
  setView(lng = center_lng, lat = center_lat, zoom = 9) %>%
435
  addLayersControl(
436
+ overlayGroups = c("現場(充足率)", "アベイラブル隊員", "ノンアベイラブル隊員"),
437
  options = layersControlOptions(collapsed = FALSE)
438
  )
439
  })
 
442
  observe({
443
  dd <- daily()
444
  sites_data <- summary_tbl()
445
+ available_gds <- dd$available_guards
446
+ non_available_gds <- dd$non_available_guards
447
 
448
  proxy <- leafletProxy("map")
449
  proxy %>%
450
  clearGroup("現場(充足率)") %>%
451
+ clearGroup("アベイラブル隊員") %>%
452
+ clearGroup("ノンアベイラブル隊員")
453
 
454
  # 現場マーカー(充足率付き)- 地理的にユニークな場所ごとに集約
455
  if (!("メッセージ" %in% colnames(sites_data)) && nrow(sites_data) > 0) {
 
515
  }
516
  }
517
 
518
+ # アベイラブル隊員マーカー(
519
+ if (isTRUE(input$show_available) && nrow(available_gds) > 0) {
520
+ gds_unique <- available_gds %>%
521
  filter(!is.na(home_lat), !is.na(home_lng)) %>%
522
  distinct(ユーザー番号, .keep_all = TRUE)
523
 
 
529
  radius = 6,
530
  color = "#3498db",
531
  fillColor = "#3498db",
532
+ fillOpacity = 0.7,
533
+ weight = 1,
534
+ label = ~paste0("【アベイラブル】ユーザー番号: ", ユーザー番号, " | ", 苗字, " ", 名前, " | ", guard_city),
535
+ group = "アベイラブル隊員"
536
+ )
537
+ }
538
+ }
539
+
540
+ # ノンアベイラブル隊員マーカー(グレー)
541
+ if (isTRUE(input$show_non_available) && nrow(non_available_gds) > 0) {
542
+ gds_unique <- non_available_gds %>%
543
+ filter(!is.na(home_lat), !is.na(home_lng)) %>%
544
+ distinct(ユーザー番号, .keep_all = TRUE)
545
+
546
+ if (nrow(gds_unique) > 0) {
547
+ proxy %>%
548
+ addCircleMarkers(
549
+ data = gds_unique,
550
+ lng = ~home_lng, lat = ~home_lat,
551
+ radius = 6,
552
+ color = "#95a5a6",
553
+ fillColor = "#95a5a6",
554
+ fillOpacity = 0.7,
555
  weight = 1,
556
+ label = ~paste0("【ノンアベイラブル】ユーザー番号: ", ユーザー番号, " | ", 苗字, " ", 名前, " | ", guard_city),
557
+ group = "ノンアベイラブル隊員"
558
  )
559
  }
560
  }