File size: 11,949 Bytes
ba9d42c
e55c1dd
10f63f4
fff8c20
ba9d42c
e55c1dd
 
10f63f4
d2c308e
10f63f4
e55c1dd
 
 
9a09158
10f63f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8d585d
10f63f4
 
 
 
 
9a09158
fff8c20
c80b5e5
 
d2c308e
10f63f4
b24fec9
 
d2c308e
 
 
10f63f4
 
 
d2c308e
 
 
 
10f63f4
 
 
 
 
9a09158
10f63f4
 
 
 
 
 
d2c308e
10f63f4
 
 
 
d2c308e
b24fec9
c404ea1
10f63f4
 
 
 
b24fec9
 
10f63f4
 
 
b24fec9
fff8c20
d2c308e
b24fec9
10f63f4
 
 
 
 
 
d2c308e
 
 
10f63f4
 
 
 
 
 
d2c308e
10f63f4
d2c308e
10f63f4
 
d2c308e
9a09158
d2c308e
 
 
 
2807f04
f591015
 
b4f2206
c03c75e
 
 
 
f591015
c03c75e
b4f2206
9a09158
c03c75e
f591015
c03c75e
f591015
 
b24fec9
a8d585d
10f63f4
b24fec9
 
a8d585d
10f63f4
a8d585d
b24fec9
 
a8d585d
 
 
 
 
 
 
 
 
 
 
 
9a09158
 
 
 
 
 
2ce8aec
 
10f63f4
2ce8aec
 
 
 
 
 
 
9a09158
 
 
 
 
 
2ce8aec
9a09158
 
4078b37
 
 
 
 
 
 
10f63f4
 
9a09158
 
4078b37
10f63f4
9a09158
 
d2c308e
10f63f4
9a09158
10f63f4
2807f04
10f63f4
 
 
 
d2c308e
 
10f63f4
 
 
 
d2c308e
10f63f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a8d585d
 
10f63f4
a8d585d
 
10f63f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fff8c20
10f63f4
 
 
9a09158
fff8c20
 
a8d585d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
import gradio as gr
import sqlite3
import os
import datetime

DB_FILE = "cases.db"

# -----------------------------
# Database setup
# -----------------------------
def init_db():
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()

    # Cases table
    c.execute('''CREATE TABLE IF NOT EXISTS summons_cases (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    name TEXT,
                    address TEXT,
                    ticket_number TEXT UNIQUE,
                    vehicle_number TEXT,
                    road_name TEXT,
                    town_city TEXT,
                    state TEXT,
                    offence_en TEXT,
                    offence_bm TEXT,
                    offence_cn TEXT,
                    base_fine REAL,
                    status TEXT,
                    created_at TEXT,
                    updated_at TEXT
                )''')

    # Appeals table
    c.execute('''CREATE TABLE IF NOT EXISTS appeals (
                    appeal_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    ticket_number TEXT,
                    appeal_text TEXT,
                    appeal_to TEXT,
                    decision TEXT,
                    final_fine REAL,
                    created_at TEXT,
                    updated_at TEXT
                )''')

    conn.commit()
    conn.close()

def seed_mock_data():
    """Seed Ali, Ah Kow, Muthu if DB empty"""
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT COUNT(*) FROM summons_cases")
    if c.fetchone()[0] == 0:
        now = datetime.datetime.now().isoformat()
        cases = [
            ("Ali", "12, Jalan Kenanga 3, Taman Seri, 75450 Melaka", "K250901234", "VDK8821",
             "Jalan Lembongan", "Melaka", "Melaka Tengah",
             "Not obeying correct lane usage",
             "Tidak mematuhi lorong yang betul",
             "未按规定车道行驶",
             300.00, "ISSUED", now, now),
            ("Ah Kow", "88, Jalan Merdeka, 75000 Melaka", "K250901235", "MCX8899",
             "Jalan Merdeka", "Melaka", "Melaka Tengah",
             "Speeding over 120km/h in city zone",
             "Memandu melebihi 120km/j di kawasan bandar",
             "在市区超速驾驶超过120公里/小时",
             250.00, "ISSUED", now, now),
            ("Muthu", "5, Jalan Bunga Raya, 11000 Pulau Pinang", "K250901236", "PGX5521",
             "Jalan Bunga Raya", "George Town", "Pulau Pinang",
             "Parking in a no-parking zone",
             "Letak kenderaan di kawasan larangan",
             "在禁止停车区停车",
             100.00, "ISSUED", now, now)
        ]
        c.executemany('''INSERT INTO summons_cases
                         (name, address, ticket_number, vehicle_number, road_name, town_city, state,
                          offence_en, offence_bm, offence_cn, base_fine, status, created_at, updated_at)
                         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', cases)
        conn.commit()
    conn.close()

# -----------------------------
# DB Helper
# -----------------------------
def find_case(ticket_number, name, vehicle_number):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("""SELECT * FROM summons_cases 
                 WHERE ticket_number=? OR name=? OR vehicle_number=?""",
              (ticket_number, name, vehicle_number))
    row = c.fetchone()
    conn.close()
    return row

# -----------------------------
# Citizen Portal
# -----------------------------
def create_case(name, address, ticket_number, vehicle_number, road_name, town_city, state,
                offence_en, offence_bm, offence_cn, base_fine):
    now = datetime.datetime.now().isoformat()
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    try:
        c.execute('''INSERT INTO summons_cases
                     (name, address, ticket_number, vehicle_number, road_name, town_city, state,
                      offence_en, offence_bm, offence_cn, base_fine, status, created_at, updated_at)
                     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''',
                  (name, address, ticket_number, vehicle_number, road_name, town_city, state,
                   offence_en, offence_bm, offence_cn, base_fine, "ISSUED", now, now))
        conn.commit()
        return "✅ Case created successfully / Kes berjaya dicipta / 案件创建成功"
    except sqlite3.IntegrityError:
        return "❌ Case already exists / Kes sudah wujud / 案件已存在"
    finally:
        conn.close()

def view_case(ticket_number, name, vehicle_number):
    row = find_case(ticket_number, name, vehicle_number)
    if not row:
        return "❌ Case not found / Kes tidak dijumpai / 未找到案件"

    return (
        f"Case / Kes / 案件 — Ticket: {row[3]}\n"
        f"Name / Nama / 姓名: {row[1]}\n"
        f"Address / Alamat / 地址: {row[2]}\n\n"
        f"Vehicle No. / No. Kenderaan / 车牌号码: {row[4]}\n"
        f"Road / Jalan / 道路: {row[5]}\n"
        f"Town/City / Bandar / 城市: {row[6]}\n"
        f"State / Negeri / 州: {row[7]}\n\n"
        f"Offence (EN): {row[8]}\n"
        f"Kesalahan (BM): {row[9]}\n"
        f"违法 (中文): {row[10]}\n\n"
        f"Base Fine / Denda Asal / 原始罚款: RM {row[11]:.2f}\n"
        f"Status / Status / 状态: {row[12]}\n\n"
        f"Created / Dicipta / 创建: {row[13]}\n"
        f"Updated / Dikemaskini / 更新: {row[14]}"
    )

def appeal_to_pdrm(ticket_number, appeal_text):
    now = datetime.datetime.now().isoformat()
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''INSERT INTO appeals (ticket_number, appeal_text, appeal_to, decision, final_fine, created_at, updated_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?)''',
              (ticket_number, appeal_text, "PDRM", None, None, now, now))
    conn.commit()
    conn.close()
    return "📨 Appeal submitted to PDRM / Rayuan dihantar ke PDRM / 上诉已提交给皇家警察"

def appeal_to_magistrate(ticket_number, appeal_text):
    now = datetime.datetime.now().isoformat()
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute('''INSERT INTO appeals (ticket_number, appeal_text, appeal_to, decision, final_fine, created_at, updated_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?)''',
              (ticket_number, appeal_text, "MAGISTRATE", None, None, now, now))
    conn.commit()
    conn.close()
    return "📨 Appeal submitted to Magistrate / Rayuan dihantar ke Majistret / 上诉已提交给地方法官"

def view_appeal_status(ticket_number):
    conn = sqlite3.connect(DB_FILE)
    c = conn.cursor()
    c.execute("SELECT decision, final_fine FROM appeals WHERE ticket_number=? ORDER BY appeal_id DESC LIMIT 1", (ticket_number,))
    row = c.fetchone()
    decision, final_fine = None, None

    # Predefined outcomes
    if ticket_number == "K250901234":  # Ali
        decision, final_fine = "APPROVED_REDUCED", 150.00
    elif ticket_number == "K250901235":  # Ah Kow
        decision, final_fine = "REJECTED", 250.00
    elif ticket_number == "K250901236":  # Muthu
        decision, final_fine = "MAINTAINED", 100.00

    if not row or not row[0]:
        c.execute("UPDATE appeals SET decision=?, final_fine=? WHERE ticket_number=?",
                  (decision, final_fine, ticket_number))
        conn.commit()
    else:
        decision, final_fine = row

    conn.close()

    decision_map = {
        "APPROVED_REDUCED": "APPROVED_REDUCED / DILULUSKAN & DIKURANGKAN / 批准并减免",
        "REJECTED": "REJECTED / DITOLAK / 驳回",
        "MAINTAINED": "MAINTAINED / DIKEKALKAN / 维持原判"
    }
    decision_str = decision_map.get(decision, decision)

    fine_str = "Pending / Menunggu / 待定" if final_fine is None else f"RM {final_fine:.2f}"

    return (
        f"Appeal Status / Status Rayuan / 上诉状态 — Ticket: {ticket_number}\n"
        f"Decision / Keputusan / 决定: {decision_str}\n"
        f"Final Fine / Denda Akhir / 最终罚款: {fine_str}"
    )

def pay_fine(ticket_number):
    return f"💳 Payment successful / Bayaran berjaya / 支付成功 — Ticket: {ticket_number}. Receipt issued / Resit dikeluarkan / 收据已发出."

# -----------------------------
# Gradio UI
# -----------------------------
def citizen_portal():
    with gr.Tab("Citizen Portal / Portal Warganegara / 公民门户"):
        with gr.Tab("Create Case / Cipta Kes / 创建案件"):
            name = gr.Textbox(label="Name / Nama / 姓名")
            address = gr.Textbox(label="Address / Alamat / 地址")
            ticket_number = gr.Textbox(label="Ticket Number / Nombor Saman / 传票号码")
            vehicle_number = gr.Textbox(label="Vehicle No. / No. Kenderaan / 车牌号码")
            road_name = gr.Textbox(label="Road / Jalan / 道路")
            town_city = gr.Textbox(label="Town/City / Bandar / 城市")
            state = gr.Textbox(label="State / Negeri / 州")
            offence_en = gr.Textbox(label="Offence (EN)")
            offence_bm = gr.Textbox(label="Kesalahan (BM)")
            offence_cn = gr.Textbox(label="违法 (中文)")
            base_fine = gr.Number(label="Base Fine / Denda Asal / 原始罚款", value=300.00)
            create_btn = gr.Button("Create / Cipta / 创建")
            create_output = gr.Textbox(label="Result / Keputusan / 结果")
            create_btn.click(create_case, [name, address, ticket_number, vehicle_number,
                                           road_name, town_city, state,
                                           offence_en, offence_bm, offence_cn, base_fine],
                             create_output)

        with gr.Tab("View Case / Lihat Kes / 查看案件"):
            vc_ticket = gr.Textbox(label="Ticket Number / Nombor Saman / 传票号码")
            vc_name = gr.Textbox(label="Name / Nama / 姓名")
            vc_vehicle = gr.Textbox(label="Vehicle No. / No. Kenderaan / 车牌号码")
            view_btn = gr.Button("View / Lihat / 查看")
            view_output = gr.Textbox(label="Case Details / Butiran Kes / 案件详情", lines=15)
            view_btn.click(view_case, [vc_ticket, vc_name, vc_vehicle], view_output)

        with gr.Tab("Submit Appeal / Hantar Rayuan / 提交上诉"):
            sa_ticket = gr.Textbox(label="Ticket Number / Nombor Saman / 传票号码")
            sa_text = gr.Textbox(label="Appeal Text / Teks Rayuan / 上诉内容")
            sa_pdrm_btn = gr.Button("Appeal to PDRM / Rayuan ke PDRM / 向皇家警察上诉")
            sa_mag_btn = gr.Button("Appeal to Magistrate / Rayuan ke Majistret / 向地方法官上诉")
            sa_output = gr.Textbox(label="Result / Keputusan / 结果")
            sa_pdrm_btn.click(appeal_to_pdrm, [sa_ticket, sa_text], sa_output)
            sa_mag_btn.click(appeal_to_magistrate, [sa_ticket, sa_text], sa_output)

        with gr.Tab("View Appeal Status / Lihat Status Rayuan / 查看上诉状态"):
            vas_ticket = gr.Textbox(label="Ticket Number / Nombor Saman / 传票号码")
            vas_btn = gr.Button("Check / Semak / 检查")
            vas_output = gr.Textbox(label="Appeal Status / Status Rayuan / 上诉状态", lines=5)
            vas_btn.click(view_appeal_status, [vas_ticket], vas_output)

        with gr.Tab("Pay Fine / Bayar Denda / 缴纳罚款"):
            pf_ticket = gr.Textbox(label="Ticket Number / Nombor Saman / 传票号码")
            pf_btn = gr.Button("Pay / Bayar / 支付")
            pf_output = gr.Textbox(label="Result / Keputusan / 结果")
            pf_btn.click(pay_fine, [pf_ticket], pf_output)

# -----------------------------
# Launch
# -----------------------------
init_db()
seed_mock_data()

with gr.Blocks() as demo:
    gr.Markdown("## 🚦 SwiftCase.ai — Smart Traffic Justice Platform")
    citizen_portal()

if __name__ == "__main__":
    demo.launch()