aephidayatuloh commited on
Commit
e972379
·
verified ·
1 Parent(s): da081a5

Upload 4 files

Browse files
Files changed (4) hide show
  1. 00_db_helper.R +25 -0
  2. Dockerfile +59 -0
  3. plumber.R +139 -0
  4. run_backend.R +1 -0
00_db_helper.R ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 00_db_helper.R
2
+ library(DBI)
3
+ library(RPostgres)
4
+
5
+ # Cek apakah file .env ada di folder (biasanya saat di laptop lokal)
6
+ # Jika tidak ada (seperti di Hugging Face), lewati saja agar tidak error
7
+ if (file.exists(".env")) {
8
+ library(dotenv)
9
+ load_dot_env(".env")
10
+ }
11
+
12
+
13
+ # Fungsi sentral untuk koneksi ke Supabase
14
+ connect_supabase <- function() {
15
+ dbConnect(
16
+ RPostgres::Postgres(),
17
+ host = Sys.getenv("DB_HOST1"),
18
+ dbname = Sys.getenv("DB_NAME1"),
19
+ user = Sys.getenv("DB_USER1"),
20
+ password = Sys.getenv("DB_PSWD1"),
21
+ port = as.integer(Sys.getenv("DB_PORT1")),
22
+ bigint = "integer",
23
+ options = "-c search_path=mlops"
24
+ )
25
+ }
Dockerfile ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================================
2
+ # Dockerfile untuk Deployment
3
+ # ============================================================================
4
+
5
+ FROM rocker/r-ver:4.5.0
6
+
7
+ # Install system dependencies
8
+ # Pasang dependensi sistem Linux yang dibutuhkan
9
+ RUN apt-get update && apt-get install -y \
10
+ libcurl4-openssl-dev \
11
+ libssl-dev \
12
+ libxml2-dev \
13
+ libfontconfig1-dev \
14
+ libharfbuzz-dev \
15
+ libfribidi-dev \
16
+ libfreetype6-dev \
17
+ libpng-dev \
18
+ libtiff5-dev \
19
+ libjpeg-dev \
20
+ cmake \
21
+ libpq-dev \
22
+ && rm -rf /var/lib/apt/lists/*
23
+
24
+ # Set working directory
25
+ WORKDIR /app
26
+
27
+
28
+ # Install R packages
29
+ # RUN R -e "install.packages(c( \
30
+ # 'tidymodels', \
31
+ # 'vetiver', \
32
+ # 'pins', \
33
+ # 'plumber', \
34
+ # 'ranger', \
35
+ # 'httr', \
36
+ # 'jsonlite', \
37
+ # 'dplyr', \
38
+ # 'ggplot2' \
39
+ # ), repos='https://cloud.r-project.org/')"
40
+
41
+ # 5. Salin semua file dari proyek lokal Anda ke dalam kontainer
42
+ COPY . .
43
+
44
+ # COPY renv.lock ./
45
+ # RUN R -e "install.packages('renv'); renv::restore()"
46
+
47
+ # Pasang paket R versi biner (Cepat dan Anti-Gagal Kompilasi)
48
+ # Kita menggunakan repositori 'noble' karena rocker:4.5.0 berbasis Ubuntu 24.04
49
+ RUN R -e "options(repos = c(CRAN = 'https://packagemanager.posit.co/cran/__linux__/noble/latest')); install.packages(c('plumber', 'vetiver', 'pins', 'DBI', 'RPostgres', 'jsonlite', 'dotenv', 'purrr', 'dplyr', 'ranger', 'tidymodels'))"
50
+
51
+ # 6. Atur hak akses agar kontainer bisa menulis file (misal saat mengunduh model)
52
+ RUN chmod -R 777 /app
53
+
54
+ # 7. Berikan instruksi port dinamis (Hugging Face default adalah 7860)
55
+ ENV PORT=7860
56
+ EXPOSE 7860
57
+
58
+ # 8. Jalankan eksekusi API Server menggunakan skrip yang sudah kita buat
59
+ CMD ["R", "-e", "source('run_backend.R')"]
plumber.R ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ library(plumber)
2
+ library(dplyr)
3
+ library(DBI)
4
+ library(RPostgres)
5
+ library(httr2)
6
+ library(jsonlite)
7
+
8
+ source("00_db_helper.R")
9
+ # source("backend/00_db_helper.R")
10
+
11
+ # Baca statistik training sekali saja saat API start
12
+ train_stats <- NULL
13
+ if (file.exists("models/train_statistics.rds")) {
14
+ train_stats <- readRDS("models/train_statistics.rds")
15
+ # print(train_stats)
16
+ } else {
17
+ con <- connect_supabase()
18
+ train_stats <- dbGetQuery(con, "SELECT * FROM mlops.train_statistics ORDER BY create_date DESC LIMIT 1")
19
+ DBI::dbDisconnect(con)
20
+ }
21
+
22
+ #* @filter cors
23
+ function(req, res) {
24
+ res$setHeader("Access-Control-Allow-Origin", "*")
25
+ res$setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
26
+ res$setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
27
+
28
+ if (req$REQUEST_METHOD == "OPTIONS") {
29
+ res$status <- 200
30
+ return(list())
31
+ }
32
+ plumber::forward()
33
+ }
34
+
35
+ #* Mengambil ringkasan performa model
36
+ #* @param window:int Ukuran window data
37
+ #* @get /performance
38
+ function(window = 100) {
39
+ con <- connect_supabase()
40
+ db_res <- dbGetQuery(con,
41
+ "SELECT predicted_value FROM mlops.predictions ORDER BY timestamp DESC LIMIT $1",
42
+ list(as.integer(window))
43
+ )
44
+
45
+ total_db_res <- dbGetQuery(con, "SELECT count(predicted_value) as total FROM mlops.predictions")
46
+ dbDisconnect(con)
47
+
48
+ if(nrow(db_res) == 0) {
49
+ return(list(total = 0, mean_pred = NA, sd_pred = NA))
50
+ }
51
+
52
+ list(
53
+ total = total_db_res$total,
54
+ window = window,
55
+ mean_pred = mean(db_res$predicted_value, na.rm = TRUE),
56
+ sd_pred = sd(db_res$predicted_value, na.rm = TRUE),
57
+ min_pred = min(db_res$predicted_value, na.rm = TRUE),
58
+ max_pred = max(db_res$predicted_value, na.rm = TRUE)
59
+ )
60
+ }
61
+
62
+ #* Mengambil data logs dan drift yang sudah diproses
63
+ #* @param window:int Ukuran window data
64
+ #* @param drift_feature:str Fitur yang ingin dihitung Z-Score nya
65
+ #* @get /logs-drift
66
+ function(window = 100) {
67
+ con <- connect_supabase()
68
+ query_sql <- "
69
+ WITH exploded_data AS (
70
+ SELECT
71
+ request_id,
72
+ timestamp,
73
+ variant, version, row_id,
74
+ predicted_value,
75
+ (input_features->>'displ')::numeric AS displ,
76
+ (input_features->>'year')::numeric AS year,
77
+ (input_features->>'cyl')::numeric AS cyl,
78
+ (input_features->>'class')::text as class,
79
+ status,
80
+ COALESCE(error_message::text, '') AS error_message
81
+ FROM mlops.predictions
82
+ WHERE status = 'SUCCESS'
83
+ ORDER BY timestamp DESC
84
+ LIMIT %d
85
+ )
86
+ SELECT
87
+ *,
88
+ ABS(displ - %f) / %f AS z_displ,
89
+ ABS(year - %f) / %f AS z_year,
90
+ ABS(cyl - %f) / %f AS z_cyl
91
+ FROM exploded_data;
92
+ "
93
+ # print(train_stats)
94
+ query_filled <- sprintf(
95
+ query_sql,
96
+ as.integer(window),
97
+ train_stats$displ_mean, train_stats$displ_sd,
98
+ train_stats$year_mean, train_stats$year_sd,
99
+ train_stats$cyl_mean, train_stats$cyl_sd
100
+ )
101
+ # print(query_filled)
102
+
103
+ final_data <- dbGetQuery(con, query_filled)
104
+ # print("final_data:")
105
+ # print(head(final_data))
106
+ # print(str(final_data))
107
+
108
+ # Jika query kosong, langsung kembalikan data frame kosong
109
+ if(nrow(final_data) == 0) {
110
+ dbDisconnect(con)
111
+ return(list(data = data.frame()))
112
+ }
113
+
114
+ actuals_db <- tryCatch({
115
+ dbGetQuery(con, "SELECT request_id, row_id, actual_value FROM mlops.actuals;")
116
+ }, error = function(e) {
117
+ data.frame(request_id = character(), row_id = character(), actual_value = numeric())
118
+ })
119
+ dbDisconnect(con)
120
+
121
+
122
+ # Gabung Actual
123
+ # print(nrow(actuals_db) > 0)
124
+ # print("row_id" %in% names(final_data))
125
+ if (nrow(actuals_db) > 0 && "row_id" %in% names(final_data)) {
126
+ actuals_db$request_id <- as.character(actuals_db$request_id)
127
+ actuals_db$row_id <- as.character(actuals_db$row_id)
128
+ final_data <- final_data %>%
129
+ left_join(actuals_db, by = c("request_id", "row_id"))
130
+ # print(final_data)
131
+ } else {
132
+ final_data$actual_value <- NA_real_
133
+ }
134
+ # print("final_data:")
135
+ # print(nrow(final_data))
136
+ # print(str(final_data))
137
+
138
+ return(final_data)
139
+ }
run_backend.R ADDED
@@ -0,0 +1 @@
 
 
1
+ plumber::plumb("backend/plumber.R")$run(port=7860)