Menelusuri Sistem Kontrol Akses

Modul ini menelusuri lapisan kontrol akses dan logika bisnis yang menentukan siapa yang dapat melihat apa dan bagaimana keputusan kepatuhan dihitung. Setiap langkah mengacu pada kode nyata di backend.

Langkah 1

Hierarki Rantai Pasok

Rantai pasok EUDR dimodelkan sebagai hierarki yang ketat. Setiap anggota enum memiliki level numerik yang menentukan visibilitas.

Python
class UserRole(str, Enum):
    TRADER   = "trader"      # Level 4
    REFINERY = "refinery"    # Level 3
    MILLS    = "mills"       # Level 2
    ESTATE   = "estate"      # Level 1
    ADMIN    = "admin"       # Level 99
Apa artinya
Trader (level 4) berada di puncak rantai komersial. Mereka dapat melihat semua yang di bawahnya — kilang, pabrik, dan perkebunan.
Perkebunan/Estate (level 1) berada di posisi paling bawah. Mereka hanya dapat melihat data milik sendiri.
Admin (level 99) melewati hierarki sepenuhnya dan memiliki visibilitas penuh di semua peran dan organisasi.
Metode can_view_role() cukup memeriksa: apakah level saya ≥ level target? Tidak perlu tabel izin per peran.
Hasil yang diharapkan: Sistem visibilitas berbasis peran di mana aktor level lebih tinggi secara otomatis melihat data dari semua level di bawahnya.
Langkah 2

Tingkatan Langganan

Akses fitur diatur oleh tingkatan langganan, bukan peran. Metode SubscriptionTier.get_features() mengembalikan dictionary kapabilitas untuk tingkatan pengguna. Ini berfungsi sebagai sistem feature flag.

Python
class SubscriptionTier(str, Enum):
    FREE       = "free"
    ENTERPRISE = "enterprise"

    def get_features(self):
        if self == SubscriptionTier.FREE:
            return {
                "radd_alerts": True,
                "glad_alerts": False,
                "max_plots": 5,
                "enhanced_pdf": False,
                "batch_processing": False,
                "rate_limit": 60,
            }
        else:  # ENTERPRISE
            return {
                "radd_alerts": True,
                "glad_alerts": True,
                "max_plots": 999999,
                "enhanced_pdf": True,
                "batch_processing": True,
                "rate_limit": 300,
            }
Apa artinya
Tingkat FREE: Peringatan radar RADD dasar, hingga 5 plot, laporan PDF dasar, 60 permintaan per jam. Tanpa peringatan optik GLAD, tanpa citra satelit dalam laporan, tanpa pemrosesan batch.
Tingkat ENTERPRISE: Peringatan GLAD + RADD lengkap, plot tak terbatas, PDF yang ditingkatkan dengan citra satelit, pemrosesan batch, 300 permintaan per jam, langganan peringatan.
Meningkatkan pengguna hanyalah perubahan satu field di database — tanpa deployment kode, tanpa aplikasi terpisah.
Hasil yang diharapkan: Setiap set fitur pengguna ditentukan oleh tingkatannya, dikembalikan sebagai dictionary yang dapat diperiksa oleh guard endpoint.
Langkah 3

Klasifikasi Risiko Negara

Persyaratan uji tuntas berdasarkan Pasal 29 bergantung pada klasifikasi risiko negara sumber. Sistem memetakan kode negara ISO ke level risiko dengan penyesuaian skor yang sesuai.

Python
def get_country_risk_level(country_code: str):
    code = country_code.upper()
    if code in high_risk:   # BR, ID, CD...
        return ('high', 20)
    elif code in standard_risk:
        return ('standard', 0)
    elif code in low_risk:
        return ('low', -20)
    else:
        return ('unknown', 10)
Apa artinya
Negara berisiko tinggi (+20): Brasil, Indonesia, DR Kongo, Peru, Kolombia, Bolivia, Venezuela, Malaysia, dan negara lain dengan tingkat deforestasi tinggi. Plot di sini dimulai dengan penalti risiko yang signifikan.
Risiko standar (0): Negara seperti Ekuador, Guatemala, Honduras — konteks deforestasi moderat, tanpa penyesuaian.
Risiko rendah (-20): AS, Kanada, Australia, Selandia Baru, negara UE — tata kelola hutan yang kuat memberikan penyesuaian negatif (menurunkan risiko).
Tidak diketahui (+10): Negara yang tidak ada dalam daftar mana pun mendapat penyesuaian positif yang berhati-hati karena ketiadaan data itu sendiri merupakan sinyal risiko.
Hasil yang diharapkan: Penyesuaian risiko numerik berdasarkan negara plot, dimasukkan ke dalam skor risiko komposit.
Langkah 4

Formula Skor Risiko

Skor risiko adalah nilai komposit dari 0 hingga 100. Dihitung secara aditif dari faktor-faktor independen, kemudian dibatasi ke rentang yang valid. Berikut cara setiap komponen berkontribusi:

Python (disederhanakan)
risk_score = 0

# 1. Penyesuaian risiko negara
risk_score += country_adjustment  # -20 hingga +20

# 2. Faktor kehilangan hutan
if forest_loss > 5:
    risk_score += 10

# 3. Penalti peringatan pasca-batas waktu
if any_alert_after_cutoff:
    risk_score += 25

# 4. Faktor luas peringatan
area_ratio = alert_area / plot_area
risk_score += min(area_ratio * 5, 25)

# 5. Dasar ketidakpastian data
risk_score += 5

# Batasi ke rentang valid
risk_score = max(0, min(100, risk_score))
Apa artinya
Mulai dari 0. Skor dimulai dari netral dan mengakumulasi risiko dari sinyal-sinyal independen.
Penyesuaian negara (-20 hingga +20): Lokasi plot menentukan dasar. Plot di Brasil dimulai dari 20; plot di Jerman dimulai dari -20.
Kehilangan hutan (+10): Jika data Hansen menunjukkan lebih dari 5% kehilangan kanopi dalam plot, tambahkan 10 poin.
Peringatan pasca-batas waktu (+25): Peringatan GLAD atau RADD yang terdeteksi setelah 31 Desember 2020 menambahkan penalti berat. Ini adalah faktor individual terbesar.
Faktor luas peringatan (hingga +25): Proporsional terhadap seberapa besar plot yang terpengaruh. Pembukaan kecil di plot besar mendapat skor lebih rendah dibanding pembukaan besar di plot kecil.
Ketidakpastian data (+5): Selalu ditambahkan sebagai margin dasar untuk kesalahan pengukuran satelit dan ketidakpastian model.
Dibatasi ke 0–100: Negara berisiko rendah tanpa peringatan dapat mencapai nilai negatif — dibatasi ke 0. Akumulasi kasus terburuk dapat melebihi 100 — dibatasi ke 100.
Hasil yang diharapkan: Skor risiko numerik antara 0 dan 100 yang merangkum semua sinyal risiko menjadi satu nilai yang dapat dibandingkan.
Langkah 5

Keputusan Kepatuhan

Pemeriksaan ambang batas kepatuhan akhir menggunakan tiga pemicu independen. Jika salah satu di antaranya aktif, plot ditandai NON_COMPLIANT:

Python (disederhanakan)
# Tiga pemicu independen
trigger_1 = forest_loss > 2.0   # %
trigger_2 = risk_score  > 70
trigger_3 = has_post_cutoff_alert

if trigger_1 or trigger_2 or trigger_3:
    status = ComplianceStatus.NON_COMPLIANT
elif borderline_conditions:
    status = ComplianceStatus.NEEDS_REVIEW
else:
    status = ComplianceStatus.COMPLIANT
Apa artinya
Pemicu 1 — Kehilangan hutan > 2%: Lebih dari 2% kanopi plot telah hilang. Ini adalah pengukuran fisik langsung dari data satelit Hansen.
Pemicu 2 — Skor risiko > 70: Skor risiko komposit (dari Langkah 4) melebihi ambang batas. Ini menangkap risiko negara, tingkat keparahan peringatan, dan kualitas data dalam satu pemeriksaan.
Pemicu 3 — Peringatan pasca-batas waktu: Peringatan deforestasi apa pun (GLAD atau RADD) yang bertanggal setelah 31 Desember 2020. Ini adalah garis hukum keras dari EUDR.
Sebuah plot harus lolos ketiga pemeriksaan untuk dinyatakan COMPLIANT (Patuh). Gagal satu saja sudah cukup untuk NON_COMPLIANT (Tidak Patuh).
Hasil yang diharapkan: Keputusan kepatuhan yang definitif — COMPLIANT, NON_COMPLIANT, atau NEEDS_REVIEW — disimpan di database dan disertakan dalam laporan.

Tugas Kontrol Akses

Cara memeriksa apakah pengguna dapat mengakses fitur

  1. Panggil user.has_feature("glad_alerts") pada objek pengguna yang telah diautentikasi. Ini mendelegasikan ke tingkatan langganan pengguna.
  2. Secara internal, has_feature() pertama-tama memeriksa apakah peran pengguna adalah ADMIN. Jika ya, selalu mengembalikan True — admin melewati semua gerbang fitur.
  3. Untuk pengguna non-admin, metode ini memanggil SubscriptionTier.get_features() dan mencari kunci fitur yang diminta dalam dictionary yang dikembalikan.
  4. Gunakan ini di guard endpoint: if not user.has_feature("batch_processing"): raise HTTPException(403).
  5. Kunci fitur harus sama persis — kunci yang tersedia adalah: radd_alerts, glad_alerts, max_plots, enhanced_pdf, batch_processing, rate_limit, alert_subscriptions.

Cara menambahkan tingkatan langganan baru

  1. Buka enum SubscriptionTier di models dan tambahkan anggota baru, misalnya PROFESSIONAL = "professional".
  2. Di metode get_features(), tambahkan cabang elif self == SubscriptionTier.PROFESSIONAL dengan dictionary fitur untuk tingkatan baru.
  3. Definisikan semua kunci fitur dengan nilainya — tingkatan baru dapat menggabungkan kapabilitas antara FREE dan ENTERPRISE.
  4. Perbarui cabang default/fallback di get_features() jika diperlukan untuk memastikan tingkatan yang tidak dikenal gagal dengan aman.
  5. Tidak diperlukan migrasi database — kolom subscription_tier menyimpan nilai string. Cukup perbarui record pengguna ke nama tingkatan baru.

Cara mengubah bobot skor risiko

  1. Semua bobot skor risiko didefinisikan sebagai konstanta konfigurasi. Nilai kunci adalah: COMPLIANCE_FOREST_LOSS_THRESHOLD (default: 2%), COMPLIANCE_RISK_SCORE_THRESHOLD (default: 70), RISK_COUNTRY_HIGH (default: +20), RISK_ALERT_POST_CUTOFF (default: +25).
  2. Ubah konstanta yang relevan untuk menyesuaikan sensitivitas. Misalnya, menurunkan COMPLIANCE_RISK_SCORE_THRESHOLD dari 70 ke 60 membuat sistem lebih agresif dalam menandai ketidakpatuhan.
  3. Tidak diperlukan perubahan kode selain nilai konfigurasi — formula perhitungan risiko membaca dari konstanta ini saat runtime.
  4. Setelah mengubah ambang batas, jalankan ulang analisis yang perlu mencerminkan pengaturan baru. Hasil yang sudah tersimpan tidak akan diperbarui secara otomatis.

Cara menambahkan negara baru ke daftar risiko

  1. Buka forest_analyzer_with_alerts.py dan temukan dictionary EU_COUNTRY_RISK_CLASSIFICATION.
  2. Temukan daftar level risiko yang sesuai: high_risk, standard_risk, atau low_risk.
  3. Tambahkan kode ISO 3166-1 alpha-2 negara tersebut (dua huruf kapital, misalnya "TZ" untuk Tanzania) ke daftar yang dipilih.
  4. Perubahan langsung berlaku pada analisis berikutnya — tidak diperlukan migrasi database atau restart selain memuat ulang modul.
  5. Verifikasi dengan menjalankan analisis untuk plot di negara tersebut dan periksa bahwa penyesuaian risiko negara sesuai dengan nilai yang diharapkan dalam hasil.

Alasan Desain

Mengapa Ambang Batas yang Dapat Dikonfigurasi?

Pasal 29 EUDR tidak menentukan metodologi risiko yang pasti. Pasal ini menetapkan kerangka benchmarking tetapi menyerahkan implementasi spesifik kepada operator. Ini disengaja — operator yang berbeda memiliki selera risiko yang secara fundamental berbeda.

Satu sistem, banyak deployment

Pedagang kakao kecil yang bersumber dari tiga perkebunan di Afrika Barat memiliki kebutuhan sensitivitas yang sangat berbeda dibandingkan kilang minyak sawit multinasional yang memproses ribuan plot di Asia Tenggara. Pedagang kakao mungkin menginginkan ambang batas yang agresif (batas skor risiko lebih rendah, batas kehilangan hutan lebih ketat) karena setiap plot sangat penting bagi rantai pasok mereka. Kilang mungkin membutuhkan ambang batas standar untuk menghindari membanjiri tim review mereka dengan false positive. Dengan menyimpan semua bobot dalam konfigurasi, setiap deployment dapat disetel tanpa menyentuh satu baris pun kode analisis.

Pola Hierarki

Metode can_view_role() membandingkan level hierarki alih-alih memelihara matriks izin eksplisit. Pemeriksaannya sederhana: apakah level penampil lebih besar dari atau sama dengan level target?

Ini elegan karena menambahkan peran baru — misalnya, WAREHOUSE di level 1.5 — hanya berarti menetapkan nomor level. Anda tidak perlu memperbarui setiap pemeriksaan izin di seluruh codebase. Matematika hierarki menanganinya secara otomatis.

Bayangkan seperti pangkat militer. Seorang Kolonel secara otomatis melebihi semua Letnan, Kapten, dan Mayor tanpa memerlukan aturan khusus untuk setiap pasangan. Anda hanya perlu tahu di mana setiap pangkat berada dalam rantai komando. Prinsip yang sama berlaku di sini: Trader (level 4) secara otomatis melihat data Kilang (3), Pabrik (2), dan Perkebunan (1) tanpa aturan eksplisit per peran.

Feature Flag vs Aplikasi Terpisah

🔀
Satu codebase, dua pengalaman

Alih-alih memelihara dua aplikasi terpisah (versi "gratis" dan versi "enterprise"), sistem menggunakan satu codebase dengan pemeriksaan feature flag di setiap batas. Setiap endpoint yang menyajikan konten terbatas tingkatan memanggil user.has_feature() sebelum menyertakan data premium dalam respons.

Ini berarti perbaikan bug diterapkan ke semua pengguna secara bersamaan. Patch keamanan tidak perlu di-backport antar codebase. Dan meningkatkan pengguna dari FREE ke ENTERPRISE hanyalah perubahan satu field di database — tanpa redeployment, tanpa migrasi, tanpa downtime. Panggilan API berikutnya dari pengguna langsung mencerminkan kapabilitas baru mereka.

Referensi Kontrol Akses

Hierarki Peran

PeranLevelDapat Melihat
ADMIN99Semua peran
TRADER4trader + kilang + pabrik + perkebunan
REFINERY3kilang + pabrik + perkebunan
MILLS2pabrik + perkebunan
ESTATE1perkebunan saja

Fitur Langganan

FiturFreeEnterprise
Peringatan RADDYaYa
Peringatan GLADTidakYa
Maks plot5Tak terbatas
PDF yang ditingkatkan (satelit)TidakYa
Pemrosesan batchTidakYa
Batas kecepatan API60/jam300/jam
Langganan peringatanTidakYa

Komponen Skor Risiko

FaktorPoinKondisi
Negara (tinggi)+20BR, ID, CD, PE, CO, BO, VE, MY, PG, KH, LA, MM, GH, CI, NG, CM, AR, PY
Negara (standar)0EC, GU, HN, NI, CR, PA dan lainnya
Negara (rendah)-20AS, CA, AU, NZ, negara UE
Negara (tidak diketahui)+10Tidak ada dalam daftar klasifikasi mana pun
Peringatan pasca-batas waktu+25Peringatan GLAD atau RADD setelah 31-12-2020
Faktor luas peringatanhingga +25(luas_peringatan / luas_plot) × 5, dibatasi pada 25
Kehilangan hutan+10Kehilangan > 5% kanopi plot
Ketidakpastian data+5Selalu ditambahkan sebagai margin dasar

Negara Berisiko Tinggi

KodeNegara
BRBrasil
IDIndonesia
CDDR Kongo
PEPeru
COKolombia
BOBolivia
VEVenezuela
MYMalaysia
PGPapua Nugini
KHKamboja
LALaos
MMMyanmar
GHGhana
CIPantai Gading
NGNigeria
CMKamerun
ARArgentina
PYParaguay