Metadata-Version: 2.4
Name: dalang
Version: 0.6.0
Summary: Dalang (Data Analytics LANGuage): bahasa analisis data yang mudah dibaca, dengan konteks eksplisit. Versi 0.6.0: MODEL, modul .dal, analitik, EXPORT ke DAX/SQL, serta dal fmt dan dal test.
Author: Tim Dalang
License-Expression: Apache-2.0
Keywords: data analytics,language,dax,duckdb,bahasa indonesia
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: Natural Language :: Indonesian
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Compilers
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: duckdb>=1.1
Provides-Extra: grafik
Requires-Dist: matplotlib>=3.7; extra == "grafik"
Provides-Extra: excel
Requires-Dist: openpyxl>=3.1; extra == "excel"
Provides-Extra: semua
Requires-Dist: matplotlib>=3.7; extra == "semua"
Requires-Dist: openpyxl>=3.1; extra == "semua"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: matplotlib>=3.7; extra == "dev"
Requires-Dist: openpyxl>=3.1; extra == "dev"
Dynamic: license-file

# Dalang (Data Analytics LANGuage)

Dalang adalah bahasa analisis data yang mudah dibaca, dirancang untuk pengguna DAX/Power BI dan analis data, dari pemula awam sampai mahir. Konteks perhitungan selalu tertulis eksplisit di kode, dan pesan error berbahasa Indonesia.

```basic
LET sales = LOAD CSV "penjualan.csv"

MODEL Store
    TABLES sales
    CALENDAR sales.tanggal
    MEASURE Omzet = SUM(sales.nilai)
    MEASURE Growth = (Omzet - Omzet PREVIOUS YEAR) / Omzet PREVIOUS YEAR
END MODEL

SHOW Omzet, Growth BY kota WHERE YEAR = 2025
EXPLAIN Omzet AT kota = "Jakarta", YEAR = 2025
```

## Status versi 0.6.0

Versi 0.6.0 menambahkan `dal fmt` dan `dal test`. Versi 0.5.0 menambahkan modul `.dal` dan `EXPORT … TO SQL`. Versi 0.4.0 menambahkan analitik: FORECAST, REGRESS, STATS, dan CORRELATE, semuanya tanpa paket tambahan. Versi 0.3.0 menambahkan JOIN, RANK, file Excel dan Parquet, serta EXPORT ke DAX. Versi 0.2.0 menambahkan **MODEL**: measure yang dihitung ulang di dalam konteks, seperti DAX, tetapi semua konteksnya tertulis di kode. Kesepuluh Program Target (1–9 dan kasus error E1–E6) kini lolos sebagai golden test.

- **`dal run`**: menjalankan file `.dal` (diterjemahkan ke Python, lalu dijalankan di atas DuckDB).
  - `--set nama=nilai` mengganti nilai `PARAMETER` (boleh berulang).
  - `--show-python` menampilkan kode Python hasil terjemahan.
- **`dal check`**: memeriksa file `.dal` tanpa menjalankannya.
- **`dal parse`**: menampilkan cara Dalang membaca program, dengan setiap ekspresi berkurung lengkap.

### MODEL (baru di 0.2.0)

```basic
LET sales = LOAD CSV "penjualan.csv"
LET products = LOAD CSV "produk.csv"

MODEL Store
    TABLES sales, products
    RELATE sales.kode TO products.kode
    CALENDAR sales.tanggal
    MEASURE Omzet = SUM(sales.nilai)
    MEASURE Growth = (Omzet - Omzet PREVIOUS YEAR) / Omzet PREVIOUS YEAR
END MODEL

SHOW Omzet, Omzet PREVIOUS YEAR, Growth BY kota WHERE YEAR = 2025 WITH totals: TRUE
```

```text
kota     Omzet  Omzet PREVIOUS YEAR  Growth
-------  -----  -------------------  ------
Bandung    100                   50  100,0%
Jakarta    290                  170   70,6%
Total      390                  220   77,3%
```

- `RELATE`: filter mengalir dari tabel "satu" ke tabel "banyak" (`BY products.kategori` menyaring `sales`). Kunci sisi "satu" wajib unik. Baris tanpa pasangan masuk kelompok MISSING, disertai peringatan.
- `CALENDAR`: dimensi waktu `YEAR`, `QUARTER`, `MONTH`, `WEEK` (ISO), `DAY` tanpa tabel tanggal terpisah. Kalender hanya mencakup rentang data.
- Pengubah: `PREVIOUS YEAR`, `LAST 3 MONTH`, `YEAR TO DATE`, `IGNORE FILTER kota`, `AS SHARE OF kota`, `(Omzet WHERE kota = "Jakarta")` (irisan, tidak menimpa), `Omzet AT kota = "Jakarta", YEAR = 2025`.
- `MEAN(Omzet) FOR EACH ROW OF products`: iterasi dengan measure di dalamnya.
- `SHOW TOP 2 products BY Omzet`, `EXPLAIN Omzet AT …` (menunjukkan rumus, filter, dan jumlah baris), `TEST … EXPECT … END TEST`.
- `WITH totals: TRUE` dihitung ulang (bukan dijumlah); baris yang semua nilainya MISSING disembunyikan kecuali `WITH show_empty: TRUE`.
- Measure juga bisa dipakai di `IF`, `LET`, dan fungsi biasa: `IF (Growth WHERE kota = k AND YEAR = tahun) IS MISSING THEN …`.
- Tampilan persen otomatis: pembagian dua besaran yang sama (misalnya Omzet dibagi Omzet tahun lalu) dan `AS SHARE OF` ditampilkan sebagai `70,6%`. Nilai aslinya tetap 0,706.

### Baru di 0.6.0

```bash
dal fmt laporan.dal          # rapikan satu file (ditulis ulang)
dal fmt .                    # rapikan semua file .dal di folder ini dan subfolder
dal fmt --check .            # hanya periksa; kode keluar 1 bila ada yang perlu dirapikan (untuk CI)
dal test                     # jalankan semua blok TEST di folder ini
dal test program09.dal --set tahun=2024
```

- **`dal fmt`** menulis kata kunci, kata kontekstual (`YEAR`, `CSV`, `NUMBER`), dan fungsi bawaan (`SUM`) dengan huruf besar. Indentasinya 4 spasi per blok dan +4 untuk baris lanjutan, spasi di akhir baris dibuang, dan baris kosong berlebih dirapatkan. Isi teks, komentar, dan nama setelah titik (`st.median`) tidak diubah. Hasilnya selalu sama bila dijalankan ulang. Aturan penamaan hanya diberi peringatan: measure diawali huruf kapital, variabel huruf kecil.
- **`dal test`** menjalankan setiap file yang berisi `TEST`. `SHOW`, `PLOT`, `EXPLAIN`, dan `EXPORT` tidak dicetak, dan grafik PNG tidak dibuat. Yang tampil hanya `[LULUS]`/`[GAGAL]` per file, `EXPECT` yang gagal (nilai didapat dan diharapkan), kesalahan, dan ringkasan. Kode keluarnya 1 bila ada yang gagal, jadi bisa langsung dipakai di CI.
- `EXPECT` tanpa measure (`EXPECT SUM(sales.nilai) = 610`) kini juga bisa dipakai di program tanpa MODEL.

### Baru di 0.5.0

**Modul `.dal`.** Satu file berisi data, MODEL, dan fungsi bersama; program lain memakainya lewat alias (keputusan T4):

```basic
' toko.dal (modul: hanya definisi)
LET sales = LOAD CSV "penjualan.csv"
MODEL Store
    TABLES sales
    CALENDAR sales.tanggal
    MEASURE Omzet = SUM(sales.nilai)
END MODEL
FUNCTION Pajak(n AS NUMBER)
    RETURN n * 0.11
END FUNCTION

' laporan.dal
USE "toko.dal" AS toko
SHOW toko.Omzet BY kota WHERE YEAR = 2025
SHOW toko.Pajak((toko.Omzet AT YEAR = 2025))
LET bandung = toko.sales WHERE kota = "Bandung"
```

- Measure, tabel, fungsi, dan nilai modul selalu ditulis dengan alias. Menulis `Omzet` saja menghasilkan pesan yang menyarankan `toko.Omzet`.
- Modul hanya boleh berisi definisi (`USE`, `LET`, `MODEL`, `FUNCTION`), sehingga `USE` tidak pernah menampilkan atau menyimpan apa pun.
- `dal check` memeriksa modul juga, termasuk nama yang salah (`toko.Omset` → saran `toko.Omzet`), jumlah argumen fungsi, dan `USE` yang melingkar.
- Path file di modul dibaca relatif terhadap folder modul. Modul yang di-USE dua kali hanya dimuat sekali. Error saat berjalan menunjuk file dan baris modulnya.

**`EXPORT … TO SQL`.** Menulis SQL standar (DuckDB, PostgreSQL):

```basic
EXPORT (sales WHERE nilai >= 80 SELECT tanggal, nilai) TO SQL
EXPORT Omzet, Transaksi BY products.kategori, YEAR TO SQL WITH file: "laporan.sql"
```

Measure menjadi satu `SELECT … GROUP BY` lengkap dengan JOIN dari `RELATE`. `WHERE` pada measure menjadi `CASE WHEN` di dalam agregasi, sehingga tetap irisan. Measure dengan `PREVIOUS`, `LAST`, `TO DATE`, `IGNORE FILTER`, `AS SHARE OF`, `AT`, atau `FOR EACH ROW OF` ditulis sebagai komentar beserta alasannya.

### Baru di 0.4.0

```basic
SHOW STATS(sales.nilai)                                  ' jumlah data, MISSING, rata-rata, kuartil, …
SHOW CORRELATE(promo.iklan, promo.omzet) BY kota
LET hasil = REGRESS promo.omzet ON promo.iklan, promo.kota
SHOW hasil                                               ' koefisien, galat baku, t, p-nilai, R²
SHOW hasil.prediksi(10, 1)
SHOW FORECAST Omzet BY MONTH NEXT 3 MONTH WHERE kota = "Jakarta"
```

- `STATS(kolom)` atau `STATS(tabel)`: statistik deskriptif. Kuartil sama dengan Excel `PERCENTILE.INC`.
- `CORRELATE(a, b)`: korelasi Pearson, bisa per kelompok dengan `BY`.
- `REGRESS target ON x1, x2`: regresi linear dengan p-nilai. Kolom teks otomatis menjadi variabel dummy (pembandingnya disebutkan), dan baris berisi MISSING dilewati dengan peringatan. Hasilnya punya `.r2`, `.r2_disesuaikan`, `.n`, `.koefisien`, `.p_nilai`, dan `.prediksi(…)`.
- `FORECAST measure BY satuan NEXT n satuan`: pemulusan eksponensial yang memilih sendiri metode yang cocok dengan panjang data: Holt-Winters (tren + musiman), Holt (tren), atau sederhana. Hasilnya tabel berisi data asli, ramalan, dan interval (`WITH level: 0.8` untuk 80%). SHOW menampilkan baris terakhir dan metode yang dipakai.
- Semua dihitung dalam Python murni dan sudah dicocokkan dengan numpy dan tabel distribusi t. `USING R` menyusul bersama `USE R`.

### Baru di 0.3.0

- `LOAD EXCEL "laporan.xlsx" WITH sheet: "Juni"`, `LOAD PARQUET "data.parquet"`, serta `SAVE … AS EXCEL` dan `SAVE … AS PARQUET`. Kolom file Excel dan Parquet ikut diperiksa oleh `dal check`. Excel butuh `pip install "dalang[excel]"`.
- `JOIN sales, products ON sales.kode = products.kode`: semua baris kiri tetap ada (LEFT JOIN). Kunci yang sama tidak diulang, nama kolom yang bentrok diberi akhiran `_<tabel kanan>`, dan ada peringatan bila baris tidak punya pasangan atau berlipat.
- `RANK sales BY nilai` (setiap baris diberi `Peringkat`) dan, dengan MODEL, `RANK kota BY Omzet` (setiap nilai kolom). Nilai sama mendapat peringkat yang sama (1, 2, 2, 4).
- `EXPORT Store TO DAX` (atau `EXPORT Omzet, Growth TO DAX WITH file: "store.dax"`): menulis tabel kalender, relasi, dan measure DAX yang siap ditempel di Power BI Desktop. Contoh:

```text
Growth = DIVIDE([Omzet] - CALCULATE([Omzet], DATEADD(Kalender[Date], -1, YEAR)), CALCULATE([Omzet], DATEADD(Kalender[Date], -1, YEAR)))
Omzet_Jkt = CALCULATE([Omzet], KEEPFILTERS(sales[kota] = "Jakarta"), KEEPFILTERS(Kalender[Tahun] = 2025))
Per_Produk = AVERAGEX(products, [Omzet])
```

  `WHERE` pada measure diterjemahkan dengan `KEEPFILTERS` agar tetap irisan seperti di Dalang. Fungsi Python di dalam measure tidak punya padanan DAX dan ditulis sebagai komentar.

### Yang sudah bisa dijalankan sejak 0.1.0

- `LOAD CSV` (pemisah `;`, `,`, tab, atau `|` dikenali otomatis), `SAVE ... AS CSV`;
- tabel: `WHERE`, `SELECT`, `SORT BY ... DESCENDING`, `TOP n tabel BY kolom`, kolom baru `LET sales.pajak = nilai * 0.11`, `RENAME`, `REMOVE`, `FILL`, `APPEND`;
- `SHOW` ringkasan `SUM`/`MEAN`/`MEDIAN`/`MIN`/`MAX`/`COUNT`/`COUNTDISTINCT`/`STDEV`/`VARIANCE` dengan `BY` dan `WHERE`, serta opsi `rows`, `totals`, `show_empty`;
- `PLOT ... BY ...`: grafik teks di layar, dan file PNG bila matplotlib terpasang;
- `LET`, `SET`, `PARAMETER`, `IF`, `FOR`, `FOR EACH`, `WHILE`, `EXIT`, `CONTINUE`, `FUNCTION`, `RETURN`, `TRY`/`CATCH`, `FAIL`;
- `USE PYTHON "paket" AS alias`;
- aturan MISSING dan angka format Indonesia (`1.250.000`, `42,9`).

Keamanan: nilai dari program tidak pernah disambung ke teks SQL. Semua nilai dikirim sebagai parameter kueri.

Belum didukung (pesan jelas, program tidak dijalankan sebagian): `EXPORT … TO PYTHON`, `ASK`, `USE R` dan `USING R`, `FORECAST` atas measure modul. Pengubah waktu belum bisa menggeser filter periodik seperti `WHERE MONTH = 3` (pakai `BY MONTH` atau `WHERE YEAR = …`).

Yang diperiksa `dal check`:

- seluruh susunan kalimat menurut Tata Bahasa v0.1 (misalnya `IF` tanpa `THEN`, `WHERE` tanpa kurung di dalam syarat `IF`/`EXPECT`, `MEASURE` di luar `MODEL`, kata kunci dipakai sebagai nama);
- teks yang belum ditutup, karakter tak dikenal, kurung tak berpasangan, `!=` (gunakan `<>`);
- bentuk lama yang tidak berlaku (`PRINT`, `REM`, `ELSEIF`, `NEXT` sebagai penutup `FOR`, `DESC`, satuan jamak seperti `MONTHS`);
- nama dan kolom yang tidak ada, dengan saran nama yang mirip (dibaca dari header CSV);
- aturan `MODEL`: `TABLES` wajib, kunci `RELATE` unik, `CALENDAR` bertipe tanggal, nama measure tidak sama dengan nama kolom;
- `PREVIOUS`, `LAST`, dan `... TO DATE` tanpa konteks waktu, termasuk lewat measure lain;
- operasi aritmetika pada teks, termasuk `WHERE` tanpa kurung yang menelan pembagian;
- `FOR EACH ROW OF` hanya untuk ekspresi berisi measure;
- `LET`/`SET`/`PARAMETER` (parameter hanya-baca), fungsi bawaan dan jumlah argumennya, nama fungsi lama seperti `AVERAGE`;
- keseimbangan blok `IF`/`FOR`/`WHILE`/`FUNCTION`/`MODEL`/`TEST`/`TRY` dengan `END` pasangannya;
- `USE` dan `PARAMETER` wajib di bagian atas file, dan `USE` wajib memakai `AS`;
- koma di dalam `WHERE` (syarat digabung dengan `AND`/`OR`);
- `TOP` tanpa `BY`.

## Pemasangan dan pemakaian

```bash
pip install dalang
dal --version
dal check analisis.dal
dal run analisis.dal
dal run analisis.dal --set tahun=2024
dal fmt analisis.dal
dal test
```

Untuk grafik PNG: `pip install "dalang[grafik]"`. Untuk file Excel: `pip install "dalang[excel]"`. Keduanya: `pip install "dalang[semua]"`.

Contoh `dal run` (Program 2 di `examples/`):

```text
610
kota     SUM(sales.nilai)  COUNT(sales)
-------  ----------------  ------------
Bandung               150             3
Jakarta               460             5
```

Contoh keluaran `dal check`:

```text
Kesalahan di analisis.dal baris 1:
    PRINT Omzet
    ^^^^^
PRINT tidak ada di Dalang. Gunakan SHOW.

[GAGAL] analisis.dal: 1 kesalahan
```

## Pengembangan

```bash
pip install -e ".[dev]"
pytest
```

Folder `examples/` berisi Program Target 1–9 dan kasus error E1–E6 yang menjadi golden test. Program 1–4 diuji oleh `tests/test_run.py`, Program 5–9 oleh `tests/test_model.py`, fitur 0.3.0–0.6.0 oleh `tests/test_v03.py` sampai `tests/test_v06.py`. Contoh modul: `examples/toko.dal` dan `examples/contoh_modul.dal`.

## Lisensi

Dalang dirilis di bawah [Apache License 2.0](LICENSE).
