Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files
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("
|
|
|
|
| 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;
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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$
|
| 471 |
-
gds_unique <-
|
| 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|