diff --git a/.gitignore b/.gitignore
index 98bb42d..0cf0e7a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,2 @@
node_modules
*.xls*
-
-
-module-c/*
-!module-c/.gitkeep
-
-module-d/*
-!module-d/.gitkeep
\ No newline at end of file
diff --git a/module-c/module-c-en.md b/module-c/module-c-en.md
new file mode 100644
index 0000000..5216f87
--- /dev/null
+++ b/module-c/module-c-en.md
@@ -0,0 +1,81 @@
+# ๐ฎ Test Project: Module C - CyberTyper 2026
+
+## 1. Project Overview
+The objective of this task is to develop a high-performance arcade game based on English typing. Competitors must analyze the provided `words_data.json` file to design a **Database (MySQL)** and complete a dynamic front-end engine that tracks real-time coordinates to destroy words by integrating it with an API.
+
+## 2. Technical Definitions
+* **VFX (Visual Effects)**: Visual special effects within the game. This refers to effects such as letters breaking into fragments upon bullet collision.
+* **WPM (Words Per Minute)**: An index of typing speed. (Calculation: `Total words typed / Play time in minutes`)
+* **HP (Health Point)**: The player's vitality (starting at 100%). It decreases when words are missed, and the game ends when it reaches 0%.
+
+## 3. Step 1: Backend API and Database Design (Server-side)
+All API endpoints must use the `/module_c/api/` path, and data persistence must be guaranteed.
+
+### **A. Database Configuration (Mandatory)**
+* **Word Table**: You must create a table that stores the word text, difficulty level, and unique points based on the contents of the provided `words_data.json`.
+* **Ranking Table**: You must design a table to store player names, final scores, and max combos. This data must be maintained even after a server restart.
+
+### **B. API Logic Requirements**
+* **Word Supply API (`GET /module_c/api/words?level=n`)**:
+ 1. Randomly extract words corresponding to the requested difficulty (`level`).
+ 2. **Duplicate Prevention**: Words already appearing on the current game screen must not be duplicated in the API call.
+* **Ranking API**: Records must be stored in the DB via `POST` requests, and the **Global TOP 5** records must be returned in descending order of score upon `GET` requests.
+
+## 4. Step 2: Game Engine and Scoring Logic (Front-end Development)
+
+### **A. Difficulty Settings (Spawn Interval & Fall Speed)**
+Words are generated at **random horizontal positions** at the top of the screen (y=0) and move downwards.
+
+| Difficulty (Level) | Spawn Interval | Fall Speed | Multiplier ($Multiplier$) |
+| :--- | :--- | :--- | :--- |
+| **Level 1** | Every 2.0s | Slow (50px/sec) | 1.0 |
+| **Level 2** | Every 1.5s | Normal (80px/sec) | 1.5 |
+| **Level 3** | Every 1.0s | Fast (120px/sec) | 2.0 |
+
+### **B. Detailed Scoring Formula ($FinalScore$)**
+$$FinalScore = BasePoints + (CurrentCombo \times LevelMultiplier)$$
+* **$BasePoints$**: The unique points assigned to the matched word in the DB.
+* **$CurrentCombo$**: The number of consecutive successes. (Increments by 1 on success; resets to 0 immediately if a word hits the bottom).
+* **Display**: The calculated score must be displayed as a real-time cumulative value in the **Score** area of the game screen.
+
+### **C. HP System and Recovery**
+* **Deduction**: HP decreases by **10%** whenever a word hits the bottom line.
+* **Recovery**: Every time a **20 Combo** is achieved, **5%** of the currently lost HP is restored as a bonus.
+
+## 5. Step 3: UI/UX and Scene Specifications (Design Implementation)
+
+### **A. 3-Scene SPA (Single Page Application) Structure**
+* This task must be developed as an **SPA structure** where screens transition without a full page refresh using JavaScript.
+* **Scene 1 (Start)**: Player name entry and difficulty selection.
+* **Scene 2 (Play)**: The actual game progression screen.
+* **Scene 3 (Result)**: Final score summary and Global TOP 5 ranking display.
+
+### **B. Start Scene Requirements**
+* **Input Elements**: Game title (CyberTyper 2026), **Player Name input field (Required)**, **Difficulty Selection (Level 1, 2, 3)** via radio buttons or a select box.
+* **Start Button**: Becomes active only when all information is entered; clicking it enters the game screen.
+
+### **C. Game Screen Mandatory Components (Marking Items)**
+* **Score**: Real-time cumulative scoreboard.
+* **Combo**: Current consecutive success count.
+* **WPM**: Real-time calculated words per minute.
+* **HP Gauge**: A visually changing health bar.
+* **Input Field**: Text area for the player to type English words.
+* **Falling Words**: Words falling from random top positions.
+
+### **D. Projectile and Fever Effects**
+* **Bullet and Collision**: Upon pressing Enter, a bullet is fired from the bottom toward the **real-time coordinates** of the target word. When a collision occurs, a letter-fragment VFX is generated and the word is destroyed.
+* **Fever Mode**: Achieving a 20 Combo reduces the fall speed by 50% for 5 seconds and activates a neon background effect.
+
+### **E. Ranking Scene (Result Scene)**
+* Displays the **Global TOP 5** leaderboard retrieved from the database.
+* **[New Game]** Button: Clicking this resets all settings and returns to the Name Input screen.
+
+---
+
+## 6. Marking Scheme Summary (Total 25.00)
+
+| Category | Detailed Criteria | Marks |
+| :--- | :--- | :--- |
+| **Design (7.0)** | Dynamic projectile/particle effects, visibility of essential UI (HP, Score), and VFX completion. | 7.0 |
+| **Front-end (11.0)** | Difficulty-specific logic (Interval/Speed), duplicate-free word management, and bullet coordinate calculation. | 11.0 |
+| **Back-end (7.0)** | **DB table design and data persistence**, Global TOP 5 Ranking API design. | 7.0 |
\ No newline at end of file
diff --git a/module-c/module-c-kr.md b/module-c/module-c-kr.md
new file mode 100644
index 0000000..b1a4c99
--- /dev/null
+++ b/module-c/module-c-kr.md
@@ -0,0 +1,84 @@
+# ๐ฎ Test Project: Module C - CyberTyper 2026
+
+## 1. ํ๋ก์ ํธ ๊ฐ์ (Project Overview)
+๋ณธ ๊ณผ์ ๋ ์๋ฌธ ํ์ดํ์ ๊ธฐ๋ฐ์ผ๋ก ํ ๊ณ ์ฑ๋ฅ ์์ผ์ด๋ ๊ฒ์ ๊ฐ๋ฐ์
๋๋ค. ์ ์๋ ์ ๊ณต๋ `words_data.json` ํ์ผ์ ๋ถ์ํ์ฌ **๋ฐ์ดํฐ๋ฒ ์ด์ค(MySQL)**๋ฅผ ์ค๊ณํ๊ณ , ํํ์ด ์ค์๊ฐ ์ขํ๋ฅผ ์ถ์ ํ์ฌ ๋จ์ด๋ฅผ ํ๊ดดํ๋ ์ญ๋์ ์ธ ํ๋ก ํธ์๋ ์์ง์ API์ ์ฐ๋ํ์ฌ ์์ฑํด์ผ ํฉ๋๋ค.
+
+## 2. ์ฃผ์ ์ฉ์ด ์ ์ (Technical Definitions)
+* **VFX (Visual Effects)**: ๊ฒ์ ๋ด ์๊ฐ ํน์ ํจ๊ณผ์
๋๋ค. ํํ ์ถฉ๋ ์ ๊ธ์๊ฐ ํํธ์ผ๋ก ๋ถ์์ง๋ ํจ๊ณผ ๋ฑ์ ์๋ฏธํฉ๋๋ค.
+* **WPM (Words Per Minute)**: ๋ถ๋น ๋จ์ด ์
๋ ฅ ์๋ ์งํ์
๋๋ค. (๊ณ์ฐ์: `ํ์ดํํ ์ด ๋จ์ด ์ / ํ๋ ์ด ์๊ฐ(๋ถ)`)
+* **HP (Health Point)**: ํ๋ ์ด์ด์ ์๋ช
๋ ฅ(100%)์ด๋ฉฐ, ๋จ์ด๋ฅผ ๋์น ๋๋ง๋ค ์ฐจ๊ฐ๋์ด 0%๊ฐ ๋๋ฉด ๊ฒ์์ด ์ข
๋ฃ๋ฉ๋๋ค.
+
+## 3. Step 1: ๋ฐฑ์๋ API ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค๊ณ (Server-side)
+๋ชจ๋ API ์๋ํฌ์ธํธ๋ `/module_c/api/` ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ฉฐ , ๋ฐ์ดํฐ์ ์์์ฑ(Persistence)์ ๋ณด์ฅํด์ผ ํฉ๋๋ค.
+
+### **A. ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์ฑ (ํ์ ๊ตฌํ)**
+* **Word Table**: ์ ๊ณต๋ `words_data.json`์ ๋ด์ฉ์ ๊ธฐ๋ฐ์ผ๋ก ๋จ์ด ํ
์คํธ, ๋์ด๋ ๋ ๋ฒจ, ๊ณ ์ ์ ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ํ
์ด๋ธ์ ์์ฑํด์ผ ํฉ๋๋ค.
+* **Ranking Table**: ํ๋ ์ด์ด ์ด๋ฆ, ์ต์ข
์ ์, ์ต๋ ์ฝค๋ณด๋ฅผ ์ ์ฅํ๋ ํ
์ด๋ธ์ ์ค๊ณํด์ผ ํฉ๋๋ค. ์ด ๋ฐ์ดํฐ๋ ์๋ฒ ์ฌ์์ ํ์๋ ์ ์ง๋์ด์ผ ํฉ๋๋ค.
+
+### **B. API ๋ก์ง ์๊ตฌ์ฌํญ**
+* **๋จ์ด ๊ณต๊ธ API (`GET /module_c/api/words?level=n`)**:
+ 1. ์์ฒญํ ๋์ด๋(`level`)์ ํด๋นํ๋ ๋จ์ด ์ค ๋๋คํ๊ฒ ์ถ์ถํฉ๋๋ค.
+ 2. **์ค๋ณต ๋ฐฉ์ง**: ํ์ฌ ๊ฒ์ ํ๋ฉด์ ์ด๋ฏธ ๋ํ๋ ์๋ ๋จ์ด๋ ์ค๋ณตํด์ ํธ์ถ๋์ง ์๋๋ก ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค.
+* **๋ญํน API**: `POST` ์์ฒญ์ ํตํด ๊ธฐ๋ก์ DB์ ์ ์ฅํ๊ณ , `GET` ์์ฒญ ์ ์์ **Global TOP 5** ๊ธฐ๋ก์ ์ ์ ๋ด๋ฆผ์ฐจ์์ผ๋ก ๋ฐํํฉ๋๋ค.
+
+### **C. ์ ์ถ๋ฌผ (Deliverables)**
+* ์ ์๋ ์์ฑํ API ์์ค์ฝ๋์ ํจ๊ป, ์ค๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์กฐ๋ฅผ ํ์ธํ ์ ์๋ **`table.sql`** ํ์ผ์ ๋ฐ๋์ ๋ฃจํธ ํด๋์ ํฌํจํด์ผ ํฉ๋๋ค.
+
+## 4. Step 2: ๊ฒ์ ์์ง ๋ฐ ์ ์ ๋ก์ง (Front-end Development)
+
+### **A. ๋์ด๋๋ณ ์ค์ (์์ฑ ์ฃผ๊ธฐ ๋ฐ ๋ํ ์๋)**
+๋จ์ด๋ ํ๋ฉด ์๋จ(y=0)์ **๊ฐ๋ก์ถ ๋๋ค ์์น**์์ ์์ฑ๋์ด ์๋๋ก ์ด๋ํฉ๋๋ค.
+
+| ๋์ด๋ (Level) | ๋จ์ด ์์ฑ ์ฃผ๊ธฐ (Interval) | ๋ํ ์๋ (Speed) | ๋์ด๋ ๊ฐ์ค์น ($Multiplier$) |
+| :--- | :--- | :--- | :--- |
+| **Level 1** | 2.0์ด๋ง๋ค 1๊ฐ ์์ฑ | ๋๋ฆผ (50px/sec) | 1.0 |
+| **Level 2** | 1.5์ด๋ง๋ค 1๊ฐ ์์ฑ | ๋ณดํต (80px/sec) | 1.5 |
+| **Level 3** | 1.0์ด๋ง๋ค 1๊ฐ ์์ฑ | ๋น ๋ฆ (120px/sec) | 2.0 |
+
+### **B. ์์ธ ์ ์ ์ฐ์ถ ๊ณต์ ($FinalScore$)**
+$$FinalScore = BasePoints + (CurrentCombo \times LevelMultiplier)$$
+* **$BasePoints$ (๊ณ ์ ์ ์)**: ๋ง์ถ ๋จ์ด๊ฐ DB์์ ๊ฐ์ง๊ณ ์๋ ๊ณ ์ ์ ์ ๊ฐ์
๋๋ค.
+* **$CurrentCombo$**: ์ฐ์ ์ฑ๊ณต ํ์์
๋๋ค. (์ฑ๊ณต ์ +1, ๋จ์ด๊ฐ ๋ฐ๋ฅ์ ๋ฟ์ผ๋ฉด ์ฆ์ 0์ผ๋ก ์ด๊ธฐํ).
+* **ํ์**: ๊ณ์ฐ๋ ์ ์๋ ๊ฒ์ ํ๋ฉด **Score** ์์ญ์ ์ค์๊ฐ ๋์ ํ์๋ฉ๋๋ค.
+
+### **C. HP ์์คํ
๋ฐ ํ๋ณต**
+* **๊ฐ์**: ๋จ์ด๊ฐ ๋ฐ๋ฅ(Bottom Line)์ ๋ฟ์ผ๋ฉด HP๊ฐ **10%** ์ฐจ๊ฐ๋ฉ๋๋ค.
+* **ํ๋ณต**: **20 ์ฝค๋ณด** ๋ฌ์ฑ ์๋ง๋ค ํ์ฌ ์ค์ด๋ HP์ **5%**๊ฐ ๋ณด๋์ค๋ก ์ฆ์ ํ๋ณต๋ฉ๋๋ค.
+
+## 5. Step 3: UI/UX ๋ฐ ์ฅ๋ฉด๋ณ ๋ช
์ธ (Design Implementation)
+
+### **A. 3-Scene SPA(Single Page Application) ๊ตฌ์กฐ**
+* ๋ณธ ๊ณผ์ ๋ ํ์ด์ง ์ ์ฒด ์๋ก๊ณ ์นจ ์์ด ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ํ๋ฉด์ด ์ ํ๋๋ **SPA ๊ตฌ์กฐ**๋ก ๊ฐ๋ฐ๋์ด์ผ ํฉ๋๋ค.
+* **Scene 1 (Start)**: ํ๋ ์ด์ด ์ด๋ฆ ์
๋ ฅ ๋ฐ ๋์ด๋ ์ ํ.
+* **Scene 2 (Play)**: ์ค์ ๊ฒ์ ์งํ ํ๋ฉด.
+* **Scene 3 (Result)**: ์ต์ข
์ ์ ์์ฝ ๋ฐ Global TOP 5 ๋ญํน ์ถ๋ ฅ ํ๋ฉด.
+
+### **B. ์์ ํ๋ฉด (Start Scene)**
+* **์
๋ ฅ ์์**: ๊ฒ์ ์ ๋ชฉ(CyberTyper 2026), **ํ๋ ์ด์ด ์ด๋ฆ ์
๋ ฅ๋(ํ์)**, **๋์ด๋ ์ ํ(Level 1, 2, 3)** ๋ผ๋์ค ๋ฒํผ ๋๋ ์ ํ์ฐฝ.
+* **์์ ๋ฒํผ**: ๋ชจ๋ ์ ๋ณด๊ฐ ์
๋ ฅ๋๋ฉด ํ์ฑํ๋๋ฉฐ, ํด๋ฆญ ์ ๊ฒ์ ํ๋ฉด์ผ๋ก ์ง์
ํฉ๋๋ค.
+
+### **C. ๊ฒ์ ํ๋ฉด ํ์ ๊ตฌ์ฑ ์์ (์ฑ์ ํญ๋ชฉ)**
+* **Score**: ์ค์๊ฐ ๋์ ์ ์ํ.
+* **Combo**: ํ์ฌ ์ฐ์ ์ฑ๊ณต ํ์.
+* **WPM**: ์ค์๊ฐ์ผ๋ก ๊ณ์ฐ๋๋ ๋ถ๋น ํ์.
+* **HP Gauge**: ์๊ฐ์ ์ผ๋ก ๋ณํ๋ ์๋ช
๋ ฅ ๋ฐ.
+* **Input Field**: ํ๋ ์ด์ด๊ฐ ์๋ฌธ ๋จ์ด๋ฅผ ์
๋ ฅํ๋ ํ
์คํธ ์์ญ.
+* **Falling Words**: ์๋จ ๋๋ค ์์น์์ ๋ํํ๋ ๋จ์ด๋ค.
+
+### **D. ํฌ์ฌ์ฒด(Projectile) ๋ฐ ํผ๋ฒ ํจ๊ณผ**
+* **ํํ ๋ฐ ์ถฉ๋**: ์ํฐ ์
๋ ฅ ์ ํ๋จ์์ ๋จ์ด์ **์ค์๊ฐ ์ขํ**๋ก ํํ์ด ๋ฐ์ฌ๋๋ฉฐ, ์ถฉ๋ ์ ๊ธ์ ํํธ VFX๊ฐ ๋ฐ์ํ๋ฉฐ ๋จ์ด๊ฐ ์๋ฉธํฉ๋๋ค.
+* **Fever Mode ์๊ฐ ํจ๊ณผ**: 20 ์ฝค๋ณด ๋ฌ์ฑ ์ ํ๋ฉด ์ค์์ **"FEVER MODE"** ํ
์คํธ ์ ๋๋ฉ์ด์
์ด ์ ์ ๋ํ๋ฌ๋ค๊ฐ ์ฌ๋ผ์ ธ์ผ ํ๋ฉฐ, ๋ฐฐ๊ฒฝ์ ๋ค์จ VFX๋ ๊ธฐ์กด ๋ฐฐ๊ฒฝ๊ณผ ์๊ฐ์ ์ผ๋ก ํ์ฐํ ๊ตฌ๋ถ๋์ด์ผ ํฉ๋๋ค.
+
+### **E. ๋ญํน ํ๋ฉด (Result Scene)**
+* ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ถ๋ฌ์จ **Global TOP 5** ์์ํ๋ฅผ ํ์ํฉ๋๋ค. ์์ํ์๋ 1๋ฑ๋ถํฐ 5๋ฑ๊น์ง์ ์์์ ์ด๋ฆ, ์ ์, ์์๋ฌ์ฑ์ผ์๊ฐ ํ์๋์ด์ผ ํฉ๋๋ค.
+* **[New Game]** ๋ฒํผ ํด๋ฆญ ์ ๋ชจ๋ ์ค์ ์ ์ด๊ธฐํํ๊ณ ์ด๋ฆ ์
๋ ฅ ํ๋ฉด์ผ๋ก ๋๋์๊ฐ๋๋ค.
+
+---
+
+## 6. ์ฑ์ ๊ธฐ์ค ์์ฝ (Marking Scheme - 25.00)
+
+| ํญ๋ชฉ | ์์ธ ๊ธฐ์ค | ๋ฐฐ์ |
+| :--- | :--- | :--- |
+| **Design (7.0)** | ํํ/ํํธ ์ญ๋์ฑ, ํ์ UI(HP, Score) ๊ฐ์์ฑ ๋ฐ VFX ์์ฑ๋ | 7.0 |
+| **Front-end (11.0)** | ๋์ด๋๋ณ ๋ก์ง(์ฃผ๊ธฐ/์๋), ์ค๋ณต ์๋ ๋จ์ด ๊ด๋ฆฌ, ํํ ์ขํ ์ฐ์ฐ | 11.0 |
+| **Back-end (7.0)** | **DB ํ
์ด๋ธ ์ค๊ณ ๋ฐ ๋ฐ์ดํฐ ์์์ฑ**, TOP 5 ๋ญํน API ์ค๊ณ | 7.0 |
\ No newline at end of file
diff --git a/module-c/module-c-uz.md b/module-c/module-c-uz.md
new file mode 100644
index 0000000..00e9224
--- /dev/null
+++ b/module-c/module-c-uz.md
@@ -0,0 +1,81 @@
+# ๐ฎ Test Project: Module C - CyberTyper 2026
+
+## 1. Loyiha sharhi (Project Overview)
+Ushbu topshiriqning maqsadi ingliz tili bo'yicha yuqori mahsuldorlikka ega arkada o'yinini ishlab chiqishdir. Ishtirokchilar taqdim etilgan `words_data.json` faylini tahlil qilib, **Ma'lumotlar bazasi (MySQL)**ni loyihalashlari hamda so'zlarni yo'q qilish uchun real vaqt rejimida koordinatalarni kuzatuvchi dinamik front-end dvigatelini API bilan integratsiya qilgan holda yakunlashlari kerak.
+
+## 2. Texnik tushunchalar (Technical Definitions)
+* **VFX (Visual Effects)**: O'yindagi vizual maxsus effektlar. Bu o'q urilishi natijasida so'z harflarining parchalanib ketishi kabi effektlarni anglatadi.
+* **WPM (Words Per Minute)**: Daqiqasiga yozilgan so'zlar tezligi ko'rsatkichi. (Hisoblash: `Yozilgan umumiy so'zlar / O'yin vaqti (daqiqada)`)
+* **HP (Health Point)**: O'yinchining hayot darajasi (100% dan boshlanadi). So'zlar o'tkazib yuborilganda u kamayadi va 0% ga yetganda o'yin yakunlanadi.
+
+## 3. 1-qadam: Backend API va ma'lumotlar bazasi dizayni (Server-side)
+Barcha API nuqtalari (endpoints) `/module_c/api/` yo'lidan foydalanishi va ma'lumotlarning saqlanishi (persistence) kafolatlanishi kerak.
+
+### **A. Ma'lumotlar bazasi konfiguratsiyasi (Majburiy)**
+* **Word Table**: Taqdim etilgan `words_data.json` fayli asosida so'z matni, qiyinchilik darajasi va unikal ballarni saqlaydigan jadval yaratishingiz kerak.
+* **Ranking Table**: O'yinchi ismlari, yakuniy ballar va maksimal kombolarni saqlash uchun jadval loyihalashingiz kerak. Ushbu ma'lumotlar server qayta ishga tushirilgandan keyin ham saqlanib qolishi shart.
+
+### **B. API mantiqiy talablari**
+* **So'zlarni yetkazib berish API (`GET /module_c/api/words?level=n`)**:
+ 1. So'ralgan qiyinchilik darajasiga (`level`) mos keladigan so'zlarni tasodifiy tarzda chiqarish.
+ 2. **Takrorlanishning oldini olish**: Hozirgi o'yin ekranida mavjud bo'lgan so'zlar API so'rovida qayta takrorlanmasligi kerak.
+* **Ranking API**: Rekordlar `POST` so'rovlari orqali MBda saqlanishi va `GET` so'rovlari orqali ballarning kamayish tartibida **Global TOP 5** rekordlari qaytarilishi kerak.
+
+## 4. 2-qadam: O'yin dvigateli va ballar hisobi mantiqi (Front-end Development)
+
+### **A. Qiyinchilik darajasi sozlamalari (Yaratilish oralig'i va tushish tezligi)**
+So'zlar ekranning yuqori qismida (y=0) **tasodifiy gorizontal pozitsiyalarda** yaratiladi va pastga qarab harakatlanadi.
+
+| Qiyinchilik (Daraja) | Yaratilish oralig'i | Tushish tezligi | Ko'paytiruvchi ($Multiplier$) |
+| :--- | :--- | :--- | :--- |
+| **Level 1** | Har 2.0s da | Sekin (50px/sek) | 1.0 |
+| **Level 2** | Har 1.5s da | O'rta (80px/sek) | 1.5 |
+| **Level 3** | Har 1.0s da | Tez (120px/sek) | 2.0 |
+
+### **B. Ballarni hisoblashning batafsil formulasi ($FinalScore$)**
+$$FinalScore = BasePoints + (CurrentCombo \times LevelMultiplier)$$
+* **$BasePoints$**: MBdagi mos kelgan so'zga biriktirilgan unikal ball.
+* **$CurrentCombo$**: Ketma-ket muvaffaqiyatlar soni. (Muvaffaqiyatda 1 ga oshadi; so'z pastki qismga tegsa, darhol 0 ga qaytadi).
+* **Ko'rsatkich**: Hisoblangan ball o'yin ekranining **Score** qismida real vaqt rejimida yig'ilib borishi kerak.
+
+### **C. HP tizimi va tiklanish**
+* **Kamayish**: So'z pastki chiziqqa tegsa, HP **10%** ga kamayadi.
+* **Tiklanish**: Har safar **20 Combo** ga erishilganda, yo'qotilgan HPning **5%** miqdori bonus sifatida tiklanadi.
+
+## 5. 3-qadam: UI/UX va sahna spetsifikatsiyalari (Design Implementation)
+
+### **A. 3-Scene SPA (Single Page Application) tuzilmasi**
+* Ushbu topshiriq JavaScript yordamida sahifani to'liq yangilamasdan almashadigan **SPA tuzilmasida** ishlab chiqilishi kerak.
+* **Scene 1 (Start)**: O'yinchi ismini kiritish va qiyinchilik darajasini tanlash.
+* **Scene 2 (Play)**: Haqiqiy o'yin jarayoni ekrani.
+* **Scene 3 (Result)**: Yakuniy ball xulosasi va Global TOP 5 reyting ekrani.
+
+### **B. Boshlang'ich sahna talablari**
+* **Kiritish elementlari**: O'yin nomi (CyberTyper 2026), **O'yinchi ismi maydoni (Majburiy)**, radio-tugmalar yoki tanlash qutisi orqali **Qiyinchilikni tanlash (Level 1, 2, 3)**.
+* **Start tugmasi**: Barcha ma'lumotlar kiritilgandagina faollashadi; uni bosish o'yin ekraniga olib kiradi.
+
+### **C. O'yin ekranining majburiy komponentlari (Baholash elementlari)**
+* **Score**: Real vaqt rejimidagi ballar jadvali.
+* **Combo**: Ketma-ket muvaffaqiyatli urishlar soni.
+* **WPM**: Real vaqtda hisoblangan daqiqalik so'zlar soni.
+* **HP Gauge**: Vizual tarzda o'zgarib turadigan hayot bari.
+* **Input Field**: O'yinchi inglizcha so'zlarni yozishi uchun matn maydoni.
+* **Falling Words**: Yuqoridan tasodifiy joylardan tushayotgan so'zlar.
+
+### **D. Proyektil (o'q) va Fever effektlari**
+* **O'q va to'qnashuv**: Enter tugmasi bosilganda, pastdan maqsadli so'zning **real vaqt koordinatalari** tomon o'q otiladi. To'qnashuv sodir bo'lganda, harflar parchalanishi VFX hosil bo'ladi va so'z yo'q qilinadi.
+* **Fever Mode**: 20 ta komboga erishish tushish tezligini 5 soniya davomida 50% ga kamaytiradi va neon fon effektini faollashtiradi.
+
+### **E. Reyting sahnasi (Natija sahifasi)**
+* Ma'lumotlar bazasidan olingan **Global TOP 5** yetakchilar jadvalini ko'rsatadi.
+* **[New Game]** tugmasi: Buni bosish barcha sozlamalarni tiklaydi va ism kiritish ekraniga qaytaradi.
+
+---
+
+## 6. Baholash sxemasi xulosasi (Jami 25.00)
+
+| Toifa | Batafsil mezonlar | Ballar |
+| :--- | :--- | :--- |
+| **Design (7.0)** | Dinamik proyektil/zarracha effektlari, muhim UI elementlari (HP, Score) ko'rinishi va VFX sifati. | 7.0 |
+| **Front-end (11.0)** | Qiyinchilikka xos mantiq (Interval/Tezlik), takrorlanishsiz so'zlar boshqaruvi va o'q koordinatalarini hisoblash. | 11.0 |
+| **Back-end (7.0)** | **MB jadvali dizayni va ma'lumotlar barqarorligi**, Global TOP 5 reyting API dizayni. | 7.0 |
\ No newline at end of file
diff --git a/module-c/words_data.json b/module-c/words_data.json
new file mode 100644
index 0000000..7867bda
--- /dev/null
+++ b/module-c/words_data.json
@@ -0,0 +1,303 @@
+[
+ { "word": "Apple", "level": 1, "points": 10 },
+ { "word": "Blue", "level": 1, "points": 10 },
+ { "word": "Cake", "level": 1, "points": 10 },
+ { "word": "Desk", "level": 1, "points": 10 },
+ { "word": "East", "level": 1, "points": 10 },
+ { "word": "Fish", "level": 1, "points": 10 },
+ { "word": "Golf", "level": 1, "points": 10 },
+ { "word": "Home", "level": 1, "points": 10 },
+ { "word": "Icon", "level": 1, "points": 10 },
+ { "word": "Jump", "level": 1, "points": 10 },
+ { "word": "King", "level": 1, "points": 10 },
+ { "word": "Lamp", "level": 1, "points": 10 },
+ { "word": "Moon", "level": 1, "points": 10 },
+ { "word": "Note", "level": 1, "points": 10 },
+ { "word": "Open", "level": 1, "points": 10 },
+ { "word": "Park", "level": 1, "points": 10 },
+ { "word": "Quiz", "level": 1, "points": 10 },
+ { "word": "Road", "level": 1, "points": 10 },
+ { "word": "Star", "level": 1, "points": 10 },
+ { "word": "Tree", "level": 1, "points": 10 },
+ { "word": "Unit", "level": 1, "points": 10 },
+ { "word": "View", "level": 1, "points": 10 },
+ { "word": "Wind", "level": 1, "points": 10 },
+ { "word": "Xray", "level": 1, "points": 10 },
+ { "word": "Yard", "level": 1, "points": 10 },
+ { "word": "Zero", "level": 1, "points": 10 },
+ { "word": "Bird", "level": 1, "points": 10 },
+ { "word": "Cold", "level": 1, "points": 10 },
+ { "word": "Door", "level": 1, "points": 10 },
+ { "word": "Edge", "level": 1, "points": 10 },
+ { "word": "Fire", "level": 1, "points": 10 },
+ { "word": "Gate", "level": 1, "points": 10 },
+ { "word": "Hill", "level": 1, "points": 10 },
+ { "word": "Ink", "level": 1, "points": 10 },
+ { "word": "Java", "level": 1, "points": 10 },
+ { "word": "Kite", "level": 1, "points": 10 },
+ { "word": "Leaf", "level": 1, "points": 10 },
+ { "word": "Mail", "level": 1, "points": 10 },
+ { "word": "Near", "level": 1, "points": 10 },
+ { "word": "Oil", "level": 1, "points": 10 },
+ { "word": "Page", "level": 1, "points": 10 },
+ { "word": "Rain", "level": 1, "points": 10 },
+ { "word": "Sand", "level": 1, "points": 10 },
+ { "word": "Time", "level": 1, "points": 10 },
+ { "word": "User", "level": 1, "points": 10 },
+ { "word": "Vase", "level": 1, "points": 10 },
+ { "word": "Wave", "level": 1, "points": 10 },
+ { "word": "Zone", "level": 1, "points": 10 },
+ { "word": "Area", "level": 1, "points": 10 },
+ { "word": "Best", "level": 1, "points": 10 },
+ { "word": "City", "level": 1, "points": 10 },
+ { "word": "Data", "level": 1, "points": 10 },
+ { "word": "Easy", "level": 1, "points": 10 },
+ { "word": "Fast", "level": 1, "points": 10 },
+ { "word": "Girl", "level": 1, "points": 10 },
+ { "word": "Hope", "level": 1, "points": 10 },
+ { "word": "Item", "level": 1, "points": 10 },
+ { "word": "Join", "level": 1, "points": 10 },
+ { "word": "Keep", "level": 1, "points": 10 },
+ { "word": "Line", "level": 1, "points": 10 },
+ { "word": "Mind", "level": 1, "points": 10 },
+ { "word": "Name", "level": 1, "points": 10 },
+ { "word": "Only", "level": 1, "points": 10 },
+ { "word": "Plan", "level": 1, "points": 10 },
+ { "word": "Real", "level": 1, "points": 10 },
+ { "word": "Side", "level": 1, "points": 10 },
+ { "word": "Text", "level": 1, "points": 10 },
+ { "word": "Upon", "level": 1, "points": 10 },
+ { "word": "Very", "level": 1, "points": 10 },
+ { "word": "Work", "level": 1, "points": 10 },
+ { "word": "Year", "level": 1, "points": 10 },
+ { "word": "Able", "level": 1, "points": 10 },
+ { "word": "Back", "level": 1, "points": 10 },
+ { "word": "Case", "level": 1, "points": 10 },
+ { "word": "Done", "level": 1, "points": 10 },
+ { "word": "Even", "level": 1, "points": 10 },
+ { "word": "Feel", "level": 1, "points": 10 },
+ { "word": "Give", "level": 1, "points": 10 },
+ { "word": "High", "level": 1, "points": 10 },
+ { "word": "Into", "level": 1, "points": 10 },
+ { "word": "Just", "level": 1, "points": 10 },
+ { "word": "Know", "level": 1, "points": 10 },
+ { "word": "Last", "level": 1, "points": 10 },
+ { "word": "Make", "level": 1, "points": 10 },
+ { "word": "Next", "level": 1, "points": 10 },
+ { "word": "Over", "level": 1, "points": 10 },
+ { "word": "Part", "level": 1, "points": 10 },
+ { "word": "Read", "level": 1, "points": 10 },
+ { "word": "Same", "level": 1, "points": 10 },
+ { "word": "Tell", "level": 1, "points": 10 },
+ { "word": "Used", "level": 1, "points": 10 },
+ { "word": "Vary", "level": 1, "points": 10 },
+ { "word": "Want", "level": 1, "points": 10 },
+ { "word": "Your", "level": 1, "points": 10 },
+ { "word": "Acid", "level": 1, "points": 10 },
+ { "word": "Bank", "level": 1, "points": 10 },
+ { "word": "Call", "level": 1, "points": 10 },
+ { "word": "Deep", "level": 1, "points": 10 },
+ { "word": "Each", "level": 1, "points": 10 },
+ { "word": "Face", "level": 1, "points": 10 },
+ { "word": "Action", "level": 2, "points": 20 },
+ { "word": "Bridge", "level": 2, "points": 20 },
+ { "word": "Camera", "level": 2, "points": 20 },
+ { "word": "Device", "level": 2, "points": 20 },
+ { "word": "Energy", "level": 2, "points": 20 },
+ { "word": "Future", "level": 2, "points": 20 },
+ { "word": "Garden", "level": 2, "points": 20 },
+ { "word": "Hammer", "level": 2, "points": 20 },
+ { "word": "Island", "level": 2, "points": 20 },
+ { "word": "Jungle", "level": 2, "points": 20 },
+ { "word": "Knight", "level": 2, "points": 20 },
+ { "word": "Laptop", "level": 2, "points": 20 },
+ { "word": "Market", "level": 2, "points": 20 },
+ { "word": "Number", "level": 2, "points": 20 },
+ { "word": "Object", "level": 2, "points": 20 },
+ { "word": "Player", "level": 2, "points": 20 },
+ { "word": "Quartz", "level": 2, "points": 20 },
+ { "word": "Rocket", "level": 2, "points": 20 },
+ { "word": "Screen", "level": 2, "points": 20 },
+ { "word": "Target", "level": 2, "points": 20 },
+ { "word": "Update", "level": 2, "points": 20 },
+ { "word": "Visual", "level": 2, "points": 20 },
+ { "word": "Window", "level": 2, "points": 20 },
+ { "word": "Yellow", "level": 2, "points": 20 },
+ { "word": "Zodiac", "level": 2, "points": 20 },
+ { "word": "Animal", "level": 2, "points": 20 },
+ { "word": "Battle", "level": 2, "points": 20 },
+ { "word": "Castle", "level": 2, "points": 20 },
+ { "word": "Desert", "level": 2, "points": 20 },
+ { "word": "Effect", "level": 2, "points": 20 },
+ { "word": "Flower", "level": 2, "points": 20 },
+ { "word": "Guitar", "level": 2, "points": 20 },
+ { "word": "Header", "level": 2, "points": 20 },
+ { "word": "Impact", "level": 2, "points": 20 },
+ { "word": "Junior", "level": 2, "points": 20 },
+ { "word": "Kernel", "level": 2, "points": 20 },
+ { "word": "Layout", "level": 2, "points": 20 },
+ { "word": "Method", "level": 2, "points": 20 },
+ { "word": "Native", "level": 2, "points": 20 },
+ { "word": "Output", "level": 2, "points": 20 },
+ { "word": "Public", "level": 2, "points": 20 },
+ { "word": "Return", "level": 2, "points": 20 },
+ { "word": "Silver", "level": 2, "points": 20 },
+ { "word": "Travel", "level": 2, "points": 20 },
+ { "word": "Useful", "level": 2, "points": 20 },
+ { "word": "Valley", "level": 2, "points": 20 },
+ { "word": "Worker", "level": 2, "points": 20 },
+ { "word": "Author", "level": 2, "points": 20 },
+ { "word": "Beauty", "level": 2, "points": 20 },
+ { "word": "Circle", "level": 2, "points": 20 },
+ { "word": "Driver", "level": 2, "points": 20 },
+ { "word": "Expert", "level": 2, "points": 20 },
+ { "word": "Friend", "level": 2, "points": 20 },
+ { "word": "Gender", "level": 2, "points": 20 },
+ { "word": "Health", "level": 2, "points": 20 },
+ { "word": "Income", "level": 2, "points": 20 },
+ { "word": "Leader", "level": 2, "points": 20 },
+ { "word": "Module", "level": 2, "points": 20 },
+ { "word": "Nature", "level": 2, "points": 20 },
+ { "word": "Option", "level": 2, "points": 20 },
+ { "word": "Policy", "level": 2, "points": 20 },
+ { "word": "Report", "level": 2, "points": 20 },
+ { "word": "Sector", "level": 2, "points": 20 },
+ { "word": "Theory", "level": 2, "points": 20 },
+ { "word": "Unique", "level": 2, "points": 20 },
+ { "word": "Volume", "level": 2, "points": 20 },
+ { "word": "Weight", "level": 2, "points": 20 },
+ { "word": "Advice", "level": 2, "points": 20 },
+ { "word": "Bottom", "level": 2, "points": 20 },
+ { "word": "Client", "level": 2, "points": 20 },
+ { "word": "Design", "level": 2, "points": 20 },
+ { "word": "Engine", "level": 2, "points": 20 },
+ { "word": "Family", "level": 2, "points": 20 },
+ { "word": "Growth", "level": 2, "points": 20 },
+ { "word": "Height", "level": 2, "points": 20 },
+ { "word": "Inside", "level": 2, "points": 20 },
+ { "word": "Memory", "level": 2, "points": 20 },
+ { "word": "Notice", "level": 2, "points": 20 },
+ { "word": "Office", "level": 2, "points": 20 },
+ { "word": "Period", "level": 2, "points": 20 },
+ { "word": "Result", "level": 2, "points": 20 },
+ { "word": "Series", "level": 2, "points": 20 },
+ { "word": "Source", "level": 2, "points": 20 },
+ { "word": "Update", "level": 2, "points": 20 },
+ { "word": "Window", "level": 2, "points": 20 },
+ { "word": "Agency", "level": 2, "points": 20 },
+ { "word": "Button", "level": 2, "points": 20 },
+ { "word": "Course", "level": 2, "points": 20 },
+ { "word": "Degree", "level": 2, "points": 20 },
+ { "word": "Entity", "level": 2, "points": 20 },
+ { "word": "Factor", "level": 2, "points": 20 },
+ { "word": "Global", "level": 2, "points": 20 },
+ { "word": "Hidden", "level": 2, "points": 20 },
+ { "word": "Involved", "level": 2, "points": 20 },
+ { "word": "Length", "level": 2, "points": 20 },
+ { "word": "Modern", "level": 2, "points": 20 },
+ { "word": "Object", "level": 2, "points": 20 },
+ { "word": "Parent", "level": 2, "points": 20 },
+ { "word": "Region", "level": 2, "points": 20 },
+ { "word": "Status", "level": 2, "points": 20 },
+ { "word": "Algorithm", "level": 3, "points": 40 },
+ { "word": "Blockchain", "level": 3, "points": 40 },
+ { "word": "Cybersecurity", "level": 3, "points": 40 },
+ { "word": "Development", "level": 3, "points": 40 },
+ { "word": "Environment", "level": 3, "points": 40 },
+ { "word": "Framework", "level": 3, "points": 40 },
+ { "word": "Generation", "level": 3, "points": 40 },
+ { "word": "Hierarchical", "level": 3, "points": 40 },
+ { "word": "Infrastructure", "level": 3, "points": 40 },
+ { "word": "JavaScript", "level": 3, "points": 40 },
+ { "word": "Knowledge", "level": 3, "points": 40 },
+ { "word": "Localization", "level": 3, "points": 40 },
+ { "word": "Maintenance", "level": 3, "points": 40 },
+ { "word": "Networking", "level": 3, "points": 40 },
+ { "word": "Optimization", "level": 3, "points": 40 },
+ { "word": "Programming", "level": 3, "points": 40 },
+ { "word": "Quantitative", "level": 3, "points": 40 },
+ { "word": "Relationship", "level": 3, "points": 40 },
+ { "word": "Sustainable", "level": 3, "points": 40 },
+ { "word": "Technology", "level": 3, "points": 40 },
+ { "word": "Universally", "level": 3, "points": 40 },
+ { "word": "Verification", "level": 3, "points": 40 },
+ { "word": "Workstation", "level": 3, "points": 40 },
+ { "word": "Xenophobic", "level": 3, "points": 40 },
+ { "word": "Yesterday", "level": 3, "points": 40 },
+ { "word": "Zillionaire", "level": 3, "points": 40 },
+ { "word": "Application", "level": 3, "points": 40 },
+ { "word": "Benchmarks", "level": 3, "points": 40 },
+ { "word": "Coefficient", "level": 3, "points": 40 },
+ { "word": "Declaration", "level": 3, "points": 40 },
+ { "word": "Efficiency", "level": 3, "points": 40 },
+ { "word": "Functional", "level": 3, "points": 40 },
+ { "word": "Geospatial", "level": 3, "points": 40 },
+ { "word": "Hypothesis", "level": 3, "points": 40 },
+ { "word": "Implementation", "level": 3, "points": 40 },
+ { "word": "Justification", "level": 3, "points": 40 },
+ { "word": "Keyboards", "level": 3, "points": 40 },
+ { "word": "Laboratory", "level": 3, "points": 40 },
+ { "word": "Management", "level": 3, "points": 40 },
+ { "word": "Navigation", "level": 3, "points": 40 },
+ { "word": "Orientation", "level": 3, "points": 40 },
+ { "word": "Perspective", "level": 3, "points": 40 },
+ { "word": "Qualitative", "level": 3, "points": 40 },
+ { "word": "Replication", "level": 3, "points": 40 },
+ { "word": "Simulation", "level": 3, "points": 40 },
+ { "word": "Transition", "level": 3, "points": 40 },
+ { "word": "Utilization", "level": 3, "points": 40 },
+ { "word": "Vulnerability", "level": 3, "points": 40 },
+ { "word": "Windshield", "level": 3, "points": 40 },
+ { "word": "Atmosphere", "level": 3, "points": 40 },
+ { "word": "Background", "level": 3, "points": 40 },
+ { "word": "Complexity", "level": 3, "points": 40 },
+ { "word": "Description", "level": 3, "points": 40 },
+ { "word": "Evolution", "level": 3, "points": 40 },
+ { "word": "Foundation", "level": 3, "points": 40 },
+ { "word": "Government", "level": 3, "points": 40 },
+ { "word": "Historical", "level": 3, "points": 40 },
+ { "word": "Interaction", "level": 3, "points": 40 },
+ { "word": "Leadership", "level": 3, "points": 40 },
+ { "word": "Mathematics", "level": 3, "points": 40 },
+ { "word": "Negotiation", "level": 3, "points": 40 },
+ { "word": "Observation", "level": 3, "points": 40 },
+ { "word": "Performance", "level": 3, "points": 40 },
+ { "word": "Recognition", "level": 3, "points": 40 },
+ { "word": "Statistics", "level": 3, "points": 40 },
+ { "word": "Temperature", "level": 3, "points": 40 },
+ { "word": "Understand", "level": 3, "points": 40 },
+ { "word": "Variation", "level": 3, "points": 40 },
+ { "word": "Architecture", "level": 3, "points": 40 },
+ { "word": "Biological", "level": 3, "points": 40 },
+ { "word": "Collection", "level": 3, "points": 40 },
+ { "word": "Definition", "level": 3, "points": 40 },
+ { "word": "Expression", "level": 3, "points": 40 },
+ { "word": "Generation", "level": 3, "points": 40 },
+ { "word": "Hypothesis", "level": 3, "points": 40 },
+ { "word": "Instrument", "level": 3, "points": 40 },
+ { "word": "Literature", "level": 3, "points": 40 },
+ { "word": "Measurement", "level": 3, "points": 40 },
+ { "word": "Objectives", "level": 3, "points": 40 },
+ { "word": "Parameters", "level": 3, "points": 40 },
+ { "word": "Psychology", "level": 3, "points": 40 },
+ { "word": "Resolution", "level": 3, "points": 40 },
+ { "word": "Strategies", "level": 3, "points": 40 },
+ { "word": "University", "level": 3, "points": 40 },
+ { "word": "Activities", "level": 3, "points": 40 },
+ { "word": "Comparison", "level": 3, "points": 40 },
+ { "word": "Discussion", "level": 3, "points": 40 },
+ { "word": "Experience", "level": 3, "points": 40 },
+ { "word": "Hypothesis", "level": 3, "points": 40 },
+ { "word": "Investment", "level": 3, "points": 40 },
+ { "word": "Motivation", "level": 3, "points": 40 },
+ { "word": "Operations", "level": 3, "points": 40 },
+ { "word": "Population", "level": 3, "points": 40 },
+ { "word": "Reflection", "level": 3, "points": 40 },
+ { "word": "Structures", "level": 3, "points": 40 },
+ { "word": "Vocabulary", "level": 3, "points": 40 },
+ { "word": "Appearance", "level": 3, "points": 40 },
+ { "word": "Conclusion", "level": 3, "points": 40 },
+ { "word": "Efficiency", "level": 3, "points": 40 },
+ { "word": "Expression", "level": 3, "points": 40 },
+ { "word": "Individual", "level": 3, "points": 40 }
+]
\ No newline at end of file
diff --git a/module-d/analyze-image.png b/module-d/analyze-image.png
new file mode 100644
index 0000000..a291bde
Binary files /dev/null and b/module-d/analyze-image.png differ
diff --git a/module-d/generate-text.png b/module-d/generate-text.png
new file mode 100644
index 0000000..b36f436
Binary files /dev/null and b/module-d/generate-text.png differ
diff --git a/module-d/module-d-en.md b/module-d/module-d-en.md
new file mode 100644
index 0000000..e4bad22
--- /dev/null
+++ b/module-d/module-d-en.md
@@ -0,0 +1,141 @@
+# ๐ WSC2026 Test Project - Web Technologies
+## **Module D: AI-Powered Global City Explorer (Full-stack Integration)**
+
+### **1. Project Overview**
+You are a full-stack developer for the 'Global City Explorer' platform. The internal AI Engineering team has developed an **'AI Middleware Server'** using local AI models (Ollama) to analyze text information and extract keywords from images. This has been provided to you as a Starter Kit.
+
+Your task is to run the provided AI server, analyze its APIs, and build a sophisticated **Front-end (UI)** where administrators can create, read, update, and delete (CRUD) city information. Furthermore, you must develop a robust **Back-end (DB Storage)** that processes and securely stores the data only after it passes strict server-side validation.
+
+---
+
+### **2. Infrastructure Setup**
+Before beginning the task, you must ensure that your local PC meets the requirements to run the AI models and the Node.js server, and configure the environment accordingly.
+
+#### **2.1. Hardware Requirements**
+To run the two AI models smoothly in a local environment, the following specifications are recommended:
+* **Memory (RAM):** Minimum 8GB / Recommended 16GB+ (Both models need to be loaded into memory).
+* **GPU:** NVIDIA GPU with 6GB+ VRAM recommended (CPU execution is possible, but image analysis processing time may be delayed).
+* **Storage:** Minimum 10GB of free space (SSD is highly recommended over HDD).
+
+#### **2.2. Node.js Verification & Installation**
+A Node.js environment is required to run the back-end middleware.
+1. Open your terminal (or Command Prompt) and type `node -v` to check the installation. (A version of `v18.x.x` or higher is expected).
+2. **If not installed:** Visit the [Node.js official website (nodejs.org)](https://nodejs.org/), download the LTS (Long Term Support) version, and install it.
+
+#### **2.3. Ollama (AI Engine) Verification & Installation**
+1. Open your terminal and type `ollama -v` to check the version.
+2. **If not installed:** On Windows, run **PowerShell** as an Administrator and paste the following command to install:
+ `irm https://ollama.com/install.ps1 | iex`
+ *(After installation, you must restart your terminal to apply the environment variables.)*
+
+#### **2.4. AI Model Download & Characteristics**
+Open your terminal and run the following commands sequentially to download the two AI models used in this project. (Verify the installation using the `ollama list` command).
+
+* **Download Text Processing Model:** `ollama pull phi3`
+ * **[Model Characteristics]:** A Small Language Model (SLM) developed by Microsoft. It is lightweight and fast, possessing excellent text generation and logical reasoning capabilities, making it ideal for writing city introductions.
+* **Download Vision (Image) Processing Model:** `ollama pull llava`
+ * **[Model Characteristics]:** A Multimodal AI equipped with 'Vision' capabilities alongside text. It analyzes uploaded landmark images to describe objects or scenery and extracts core keywords.
+
+---
+
+### **3. AI Middleware Server Setup & Execution**
+The provided Starter Kit contains an `ai-api` folder created by the AI engineering team. Inside, you will find `server.js` and `package.json`. **(๐จ NOTICE: Do NOT modify the code in this server under any circumstances.)**
+
+1. Open a terminal and navigate to the provided `ai-api` folder directory.
+2. Enter the following command to install the required packages:
+ `npm install`
+3. Once the installation is complete, start the server:
+ `npm start`
+4. If the message `๐ AI Middleware Server running on http://localhost:3000` appears in the terminal, the server is running correctly. Do not close this terminal until the competition module ends.
+
+---
+
+### **4. API Testing via HOPPSCOTCH**
+Before starting your development, you must use Hoppscotch (or Postman) to test the two provided APIs and understand the format of the data they return.
+
+* **API 1: Text Information Generation (`POST /api/generate-text`)**
+ * Body (`application/json`): `{"city_name": "Paris", "country": "France"}`
+ 
+
+* **API 2: Integrated Image Analysis (`POST /api/analyze-image`)**
+ * Body (`multipart/form-data`): `city_name` (Text), `country` (Text), `image` (File type for image upload)
+ 
+
+---
+
+### **5. Tasks to Complete**
+
+#### **5.1. Front-end Requirements (Advanced UI/UX)**
+Build the `index.html` (or use an SPA framework) interface for administrators. The screen should be broadly divided into a **'Data Registration/Edit Area'** and a **'Registered List View Area'**.
+
+* **[Data Registration & Edit Area (Form Area)]**
+ * **Image Preview:** When an image is selected via ``, an image thumbnail must instantly appear on the screen as a preview before it is sent to the server.
+ * **AI Data Integration:** Call the AI text generation API after entering the city/country name, and call the AI vision analysis API after uploading a photo. Populate the form with the returned data.
+ * **Form Lock & Loading (Asynchronous State Management):** Because heavy AI analysis takes time (10~30 seconds), you must **disable** all input fields and buttons during the process to prevent duplicate clicks and user errors. Additionally, display a clear loading UI (spinner, progress bar, etc.) to optimize the User Experience.
+ * **Edit Data & AI Re-fetch:**
+ * Clicking an 'Edit' button on a specific city in the list should populate the form with that city's data.
+ * The edit form must include a **'Re-fetch All Info from AI'** button. Clicking this button should trigger both AI APIs again based on the current city, country, and image in the form, entirely replacing the text values (description, keywords) with the newly generated AI response. The data will update in the DB only when the user clicks 'Final Update'.
+
+* **[List View & Filtering Area (List & Interactive Filtering)]**
+ * Fetch the globally saved city information from the DB and render it visually in a List or Card Gallery format. Each item must include an **'Edit'** and **'Delete'** button.
+ * **Delete Data:** When the 'Delete' button is clicked, prevent accidental deletions by prompting the user with a browser native `confirm` dialog or a custom Modal. Upon confirmation, delete the data from the DB and immediately update the list.
+ * **Hashtag Filtering:** Clicking a specific keyword (e.g., `#EiffelTower`) displayed in the list must instantly filter the entire list to show only the city cards that contain that keyword.
+
+#### **5.2. Back-end Requirements (DB & Server-side Validation)**
+Construct your own back-end server and database to perfectly handle the Create, Read, Update, and Delete (CRUD) operations for your data.
+
+* **DB Schema:** Design and create a `city_contents` table in MySQL/MariaDB.
+* **API Implementation (CRUD):**
+ 1. **GET (Read):** Returns all data saved in the DB in JSON format.
+ 2. **POST (Create):** `INSERT` new data into the DB and physically save the uploaded image to an upload folder.
+ 3. **PUT or PATCH (Update):** Updates data for a specific ID. (If a new image is submitted, you must replace the old image file or update the new file path).
+ 4. **DELETE (Delete):** Removes data for a specific ID from the DB.
+* **Server-side Validation (Security Check):** For POST and PUT/PATCH requests, ONLY data that passes the following validation checks should be saved to the server:
+ 1. **File Size Limit:** If the uploaded image exceeds **5MB**, the server must reject the request and return an error message along with HTTP status code 400 (Bad Request).
+ 2. **Extension Check:** The server must reject the upload if it is not an allowed image format (`.jpg`, `.jpeg`, `.png`, `.webp`).
+
+#### **5.3. Additional Requirements (Pitfalls & Evaluation Points)**
+* **Data Parsing:** Within the Vision API's response, the `keywords` are not returned as a pure array, but as a **Stringified Array wrapped in JSON format**. To render these cleanly as individual tag UIs, the front-end MUST execute proper parsing (`JSON.parse`).
+* **Exception Handling:** If the back-end validation fails (e.g., File size exceeded), the front-end application must not crash. It must catch the error and clearly notify the user via an `alert` or Modal window.
+
+---
+
+### **6. Competitor Workspace Guide**
+
+* **AI Middleware Execution Location:** The provided `ai-api` folder. (Run in Terminal 1 and leave it active).
+* **Competitor Workspace:** Your web server's root folder (e.g., `http://localhost/module-d/`).
+* **Evaluation Preparation:** At the end of the module, both the AI Middleware terminal and the web server you created must be running. Experts will connect via browser and sequentially test the following cycle:
+ 1. Attempt to upload an image exceeding 5MB (Check Error Handling)
+ 2. Normal data generation (Create)
+ 3. List view and Hashtag click filtering (Read & Filter)
+ 4. **Click 'Edit', 'Re-fetch from AI' to renew contents, and Save (Update)**
+ 5. **Click 'Delete' to permanently erase data (Delete)**
+
+---
+
+### **7. Appendix: Sample Data**
+Utilize the city/country list below for testing and final demonstration. **Recommended landmark images for testing are provided in the `sample-images` folder inside the Starter Kit.** (Prepare at least one high-resolution image larger than 5MB to test your back-end defense logic).
+
+| City Name | Country Name | Landmark |
+| :--- | :--- | :--- |
+| **Paris** | France | Eiffel Tower, Louvre Museum |
+| **Seoul** | South Korea | Gyeongbokgung, N Seoul Tower |
+| **Tokyo** | Japan | Tokyo Tower, Shibuya Crossing |
+| **New York** | USA | Statue of Liberty, Times Square |
+| **Rome** | Italy | Colosseum, Trevi Fountain |
+| **Sydney** | Australia | Opera House, Harbour Bridge |
+| **Cairo** | Egypt | Pyramids of Giza, Sphinx |
+| **London** | UK | Big Ben, London Eye |
+
+---
+
+### **8. Mark Summary (Total: 25 Marks)**
+This table provides a summary of the points allocated per evaluation category. A detailed Marking Scheme will be provided separately.
+
+| Category | Description | Max Marks |
+| :--- | :--- | :---: |
+| **A. Front-end UI & UX** | UI composition, Async state management (Loading/Locking), Data binding & Parsing | 7 |
+| **B. Data Interaction** | Data list rendering and Hashtag-based filtering functionality | 5 |
+| **C. Back-end CRUD API** | DB & File System Create, Read, Update (including AI Re-fetch), and Delete operations | 8 |
+| **D. Security & Validation**| Server-side file size/extension validation, Client-side exception handling | 5 |
+| **Total Marks** | | **25** |
\ No newline at end of file
diff --git a/module-d/module-d-kr.md b/module-d/module-d-kr.md
new file mode 100644
index 0000000..a771a27
--- /dev/null
+++ b/module-d/module-d-kr.md
@@ -0,0 +1,141 @@
+# ๐ WSC2026 Test Project - Web Technologies
+## **Module D: AI-Powered Global City Explorer (Full-stack Integration)**
+
+### **1. ๊ณผ์ ๊ฐ์ (Project Overview)**
+๋น์ ์ 'Global City Explorer' ํ๋ซํผ์ ํ์คํ ๊ฐ๋ฐ์์
๋๋ค. ์ฌ๋ด AI ์์ง๋์ด๋ง ํ์ด ๋ก์ปฌ AI ๋ชจ๋ธ(Ollama)์ ํ์ฉํ์ฌ ๋์์ ํ
์คํธ ์ ๋ณด์ ์ด๋ฏธ์ง์ ํค์๋๋ฅผ ๋ถ์ํด ์ฃผ๋ **'AI ๋ง์ดํฌ๋ก์๋น์ค(AI Middleware Server)'**๋ฅผ ๊ฐ๋ฐํ์ฌ Starter Kit์ผ๋ก ์ ๊ณตํ์ต๋๋ค.
+
+๋น์ ์ ์๋ฌด๋ ์ ๊ณต๋ AI ์๋ฒ๋ฅผ ๊ตฌ๋ํ๊ณ API๋ฅผ ๋ช
ํํ ๋ถ์ํ ๋ค, ๊ด๋ฆฌ์๊ฐ ๋์ ์ ๋ณด๋ฅผ ๋ฑ๋ก, ์กฐํ, **์์ ๋ฐ ์ญ์ (CRUD)** ํ ์ ์๋ ๊ณ ๋ํ๋ **ํ๋ก ํธ์๋(UI)**์ ์๊ฒฉํ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ ๋ฐ์ดํฐ๋ง ์์ ํ๊ฒ ์ฒ๋ฆฌํ๋ **๋ฐฑ์๋(DB Storage)**๋ฅผ ์๋ฒฝํ๊ฒ ๊ตฌ์ถํ๋ ๊ฒ์
๋๋ค.
+
+---
+
+### **2. ์ธํ๋ผ ๊ตฌ์ถ ๋ฐฉ๋ฒ (Infrastructure Setup)**
+๊ณผ์ ๋ฅผ ์ํํ๊ธฐ ์ , ๋ก์ปฌ PC๊ฐ AI ๋ชจ๋ธ๊ณผ Node.js ์๋ฒ๋ฅผ ๊ตฌ๋ํ๊ธฐ ์ํ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ๋์ง ํ์ธํ๊ณ ํ๊ฒฝ์ ์ธํ
ํด์ผ ํฉ๋๋ค.
+
+#### **2.1. ํ๋์จ์ด ๊ถ์ฅ ์ฌ์ (Hardware Requirements)**
+๋ก์ปฌ ํ๊ฒฝ์์ ๋ ๊ฐ์ AI ๋ชจ๋ธ์ ์ํํ๊ฒ ๊ตฌ๋ํ๊ธฐ ์ํด ์๋ ์ฌ์์ ๊ถ์ฅํฉ๋๋ค.
+* **Memory (RAM):** ์ต์ 8GB / ๊ถ์ฅ 16GB ์ด์ (๋ ๋ชจ๋ธ์ด ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฌ๋์ด์ผ ํ๋ฏ๋ก RAM ์ฉ๋์ด ์ค์ํฉ๋๋ค.)
+* **GPU:** VRAM 6GB ์ด์์ NVIDIA GPU ๊ถ์ฅ (CPU๋ก๋ ๊ตฌ๋์ ๊ฐ๋ฅํ๋, ์ด๋ฏธ์ง ๋ถ์ ์ ์ฒ๋ฆฌ ์๊ฐ์ด ๋ค์ ์ง์ฐ๋ ์ ์์ต๋๋ค.)
+* **Storage:** ์ต์ 10GB ์ด์์ ์ฌ์ ๊ณต๊ฐ (HDD๋ณด๋ค SSD ์ฌ์ฉ์ ๊ฐ๋ ฅํ ๊ถ์ฅํฉ๋๋ค.)
+
+#### **2.2. Node.js ํ์ธ ๋ฐ ์ค์น**
+๋ฐฑ์๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์คํํ๊ธฐ ์ํด Node.js ํ๊ฒฝ์ด ํ์ํฉ๋๋ค.
+1. ํฐ๋ฏธ๋(๋๋ ๋ช
๋ น ํ๋กฌํํธ)์ ์ด๊ณ `node -v`๋ฅผ ์
๋ ฅํ์ฌ ์ค์น ์ฌ๋ถ๋ฅผ ํ์ธํฉ๋๋ค. (`v18.x.x` ์ด์์ ๋ฒ์ ์ด ํ์ถ๋๋ฉด ์ ์์
๋๋ค.)
+2. **๋ฏธ์ค์น ์:** [Node.js ๊ณต์ ํํ์ด์ง(nodejs.org)](https://nodejs.org/)์ ์ ์ํ์ฌ LTS(Long Term Support) ๋ฒ์ ์ ๋ค์ด๋ก๋ํ๊ณ ์ค์นํ์ญ์์ค.
+
+#### **2.3. Ollama (AI ๊ตฌ๋ ์์ง) ํ์ธ ๋ฐ ์ค์น**
+1. ํฐ๋ฏธ๋์ ์ด๊ณ `ollama -v`๋ฅผ ์
๋ ฅํ์ฌ ๋ฒ์ ์ ๋ณด๊ฐ ๋์ค๋์ง ํ์ธํฉ๋๋ค.
+2. **๋ฏธ์ค์น ์:** Windows์ ๊ฒฝ์ฐ **PowerShell**์ ๊ด๋ฆฌ์ ๊ถํ์ผ๋ก ์คํํ ๋ค, ์๋ ๋ช
๋ น์ด๋ฅผ ๋ถ์ฌ๋ฃ์ด ์ค์น๋ฅผ ์งํํ์ญ์์ค.
+ `irm https://ollama.com/install.ps1 | iex`
+ *(์ค์น๊ฐ ์๋ฃ๋๋ฉด ํฐ๋ฏธ๋์ ์ฌ์์ํด์ผ ํ๊ฒฝ๋ณ์๊ฐ ์ ์ฉ๋ฉ๋๋ค.)*
+
+#### **2.4. AI ๋ชจ๋ธ ๋ค์ด๋ก๋ ๋ฐ ํน์ง**
+ํฐ๋ฏธ๋์ ์ด๊ณ ์๋ ๋ช
๋ น์ด๋ฅผ ์์ฐจ์ ์ผ๋ก ์คํํ์ฌ ๋ณธ ๊ณผ์ ์ ์ฌ์ฉํ 2๊ฐ์ AI ๋ชจ๋ธ์ ๋ค์ด๋ก๋ํฉ๋๋ค. (`ollama list` ๋ช
๋ น์ด๋ก ์ค์น ์๋ฃ ํ์ธ)
+
+* **ํ
์คํธ ์ฒ๋ฆฌ ๋ชจ๋ธ ๋ค์ด๋ก๋:** `ollama pull phi3`
+ * **[๋ชจ๋ธ ํน์ง]:** Microsoft์์ ๊ฐ๋ฐํ ์ด๊ฒฝ๋ ์ธ์ด ๋ชจ๋ธ(SLM)์
๋๋ค. ๊ฐ๋ณ๊ณ ๋น ๋ฅด๋ฉด์๋ ํ๋ฅญํ ๋ฌธ์ฅ ์์ฑ ๋ฐ ๋
ผ๋ฆฌ์ ์ถ๋ก ๋ฅ๋ ฅ์ ๊ฐ์ถ๊ณ ์์ด ๋์์ ์๊ฐ๊ธ์ ์์ฑํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.
+* **๋น์ (์ด๋ฏธ์ง) ์ฒ๋ฆฌ ๋ชจ๋ธ ๋ค์ด๋ก๋:** `ollama pull llava`
+ * **[๋ชจ๋ธ ํน์ง]:** ํ
์คํธ๋ฟ๋ง ์๋๋ผ '๋(Vision)'์ ๊ฐ์ง ๋ฉํฐ๋ชจ๋ฌ(Multimodal) AI์
๋๋ค. ์ฌ์ฉ์๊ฐ ์
๋ก๋ํ ๋ช
์ ์ด๋ฏธ์ง๋ฅผ ๋ถ์ํ์ฌ ๊ทธ ์์ ๊ฐ์ฒด๋ ํ๊ฒฝ์ ์ค๋ช
ํ๊ณ ํต์ฌ ํค์๋๋ฅผ ์ถ์ถํ๋ ์ญํ ์ ํฉ๋๋ค.
+
+---
+
+### **3. AI Middleware Server ์ค์น ๋ฐ ์คํ**
+์ ๊ณต๋ Starter Kit ์์๋ AI ์์ง๋์ด๋ง ํ์ด ์์ฑํ `ai-api` ํด๋๊ฐ ์์ต๋๋ค. ์ด ํด๋ ์์๋ `server.js`์ `package.json`์ด ํฌํจ๋์ด ์์ต๋๋ค. **(๐จ ์ฃผ์: ์ด ์๋ฒ์ ์ฝ๋๋ ์ ๋ ์์ ํ์ง ๋ง์ญ์์ค.)**
+
+1. ํฐ๋ฏธ๋์ ์ด๊ณ ์ ๊ณต๋ `ai-api` ํด๋ ๊ฒฝ๋ก๋ก ์ด๋ํฉ๋๋ค.
+2. ์๋ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ฌ ํ์ ํจํค์ง๋ฅผ ์ค์นํฉ๋๋ค.
+ `npm install`
+3. ์ค์น๊ฐ ์๋ฃ๋๋ฉด ์๋ฒ๋ฅผ ์คํํฉ๋๋ค.
+ `npm start`
+4. ํฐ๋ฏธ๋์ `๐ AI Middleware Server running on http://localhost:3000` ๋ฉ์์ง๊ฐ ์ถ๋ ฅ๋๋ฉด ์๋ฒ๊ฐ ์ ์ ์๋ ์ค์ธ ๊ฒ์
๋๋ค. ์ด ํฐ๋ฏธ๋์ ๊ณผ์ ๊ฐ ๋๋ ๋๊น์ง ๋ซ์ง ๋ง์ญ์์ค.
+
+---
+
+### **4. HOPPSCOTCH๋ฅผ ํ์ฉํ API ํ
์คํธ**
+๋ณธ๊ฒฉ์ ์ธ ๊ฐ๋ฐ์ ์์, Hoppscotch(๋๋ Postman)๋ฅผ ์ด์ฉํ์ฌ ์ ๊ณต๋ ๋ ๊ฐ์ API๊ฐ ์ด๋ค ํํ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐํํ๋์ง ๋ฐ๋์ ํ
์คํธํ์ญ์์ค.
+
+* **API 1: ํ
์คํธ ์ ๋ณด ์์ฑ (`POST /api/generate-text`)**
+ * Body (`application/json`): `{"city_name": "Paris", "country": "France"}`
+ 
+
+* **API 2: ์ด๋ฏธ์ง ํตํฉ ๋ถ์ (`POST /api/analyze-image`)**
+ * Body (`multipart/form-data`): `city_name` (Text), `country` (Text), `image` (File ํ์์ผ๋ก ์ด๋ฏธ์ง ์
๋ก๋)
+ 
+
+---
+
+### **5. ์ ์๋ค์ด ๋ง๋ค์ด์ผ ํ ๊ณผ์ (Tasks to Complete)**
+
+#### **5.1. Front-end ์๊ตฌ์ฌํญ (Advanced UI/UX)**
+๊ด๋ฆฌ์๊ฐ ์ฌ์ฉํ `index.html` (๋๋ SPA ํ๋ ์์ํฌ) ํ๋ฉด์ ๊ตฌ์ถํ์ญ์์ค. ํ๋ฉด์ ํฌ๊ฒ **'์ ๋ณด ๋ฑ๋ก/์์ ์์ญ'**๊ณผ **'๋ฑ๋ก๋ ๋ชฉ๋ก ์กฐํ ์์ญ'**์ผ๋ก ๋๋์ด์ผ ํฉ๋๋ค.
+
+* **[์ ๋ณด ๋ฑ๋ก ๋ฐ ์์ ์์ญ (Form Area)]**
+ * **์ด๋ฏธ์ง ๋ฏธ๋ฆฌ๋ณด๊ธฐ (Image Preview):** ``์ ํตํด ์ด๋ฏธ์ง๋ฅผ ์ ํํ๋ฉด, ์๋ฒ์ ์ ์กํ๊ธฐ ์ ํ๋ฉด์ ์ฆ์ ์ด๋ฏธ์ง ์ธ๋ค์ผ์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ก ํ์๋์ด์ผ ํฉ๋๋ค.
+ * **AI ๋ฐ์ดํฐ ์ฐ๋:** ๋์๋ช
/๊ตญ๊ฐ๋ช
์
๋ ฅ ํ AI ํ
์คํธ ์์ฑ API๋ฅผ ํธ์ถํ๊ณ , ์ฌ์ง ๋ฑ๋ก ํ AI ๋น์ ๋ถ์ API๋ฅผ ํธ์ถํ์ฌ ๋ฐํ๋ ๋ฐ์ดํฐ๋ฅผ ํผ(Form)์ ์ฑ์ฐ์ญ์์ค.
+ * **์๋ฒฝํ ๋น๋๊ธฐ ์ํ ๊ด๋ฆฌ (Form Lock & Loading):** ๋ฌด๊ฑฐ์ด AI ๋ถ์ ์์
(10~30์ด ์์)์ด ์งํ๋๋ ๋์ ์ฌ์ฉ์์ ์ค๋ณต ํด๋ฆญ ๋ฐ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด **๋ชจ๋ ์
๋ ฅ ํผ๊ณผ ๋ฒํผ์ ๋นํ์ฑํ(Disabled)** ์ฒ๋ฆฌํด์ผ ํฉ๋๋ค. ๋ํ ์์
์งํ ์ํ๋ฅผ ๋ช
ํํ ์ ์ ์๋ ๋ก๋ฉ UI(์คํผ๋ ์ ๋๋ฉ์ด์
, ํ๋ก๊ทธ๋ ์ค ๋ฐ ๋ฑ)๋ฅผ ํ๋ฉด์ ํ์ํ์ฌ ์ฌ์ฉ์ ๊ฒฝํ์ ์ต์ ํํ์ญ์์ค.
+ * **์ ๋ณด ์์ ๋ฐ AI ๋ค์ ๋ถ๋ฌ์ค๊ธฐ (Re-fetch):**
+ * ๋ชฉ๋ก์์ ํน์ ๋์์ '์์ ' ๋ฒํผ์ ๋๋ฅด๋ฉด ํด๋น ๋์์ ๋ฐ์ดํฐ๊ฐ ํผ์ ์ฑ์์ ธ์ผ ํฉ๋๋ค.
+ * ์์ ํผ์๋ **'AI์์ ๋ชจ๋ ์ ๋ณด ๋ค์ ๋ถ๋ฌ์ค๊ธฐ'** ๋ฒํผ์ด ์์ด์ผ ํฉ๋๋ค. ์ด ๋ฒํผ์ ํด๋ฆญํ๋ฉด ํ์ฌ ํผ์ ์๋ ๋์, ๊ตญ๊ฐ, ์ด๋ฏธ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฐํ์ผ๋ก AI API ๋ ๊ฐ๋ฅผ ๋ค์ ํธ์ถํ๊ณ , ํผ์ ํ
์คํธ ๊ฐ๋ค(์ค๋ช
, ํค์๋ ๋ฑ)์ ์์ ํ ์๋ก์ด AI ์๋ต์ผ๋ก ๊ต์ฒดํด์ผ ํฉ๋๋ค. ์ดํ ์ฌ์ฉ์๊ฐ '์ต์ข
์์ ' ๋ฒํผ์ ๋๋ฅด๋ฉด ์
๋ฐ์ดํธ๋ฉ๋๋ค.
+
+* **[๋ชฉ๋ก ์กฐํ ๋ฐ ํํฐ๋ง ์์ญ (List & Interactive Filtering)]**
+ * ์ต์ข
์ ์ฅ๋ ๊ธ๋ก๋ฒ ๋์ ์ ๋ณด๋ค์ DB์์ ๋ถ๋ฌ์ ๋ฆฌ์คํธ(List) ๋๋ ์นด๋(Card) ๊ฐค๋ฌ๋ฆฌ ํํ๋ก ๋ ๋๋งํ์ญ์์ค. ๊ฐ ์์ดํ
์๋ **'์์ (Edit)'** ๋ฐ **'์ญ์ (Delete)'** ๋ฒํผ์ด ํฌํจ๋์ด์ผ ํฉ๋๋ค.
+ * **๋ฐ์ดํฐ ์ญ์ (Delete):** '์ญ์ ' ๋ฒํผ ํด๋ฆญ ์, ์ค์๋ก ์ญ์ ํ๋ ๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด ๋ธ๋ผ์ฐ์ ์ ๊ธฐ๋ณธ `confirm` ์ฐฝ์ด๋ ์ปค์คํ
๋ชจ๋ฌ(Modal) ์ฐฝ์ผ๋ก ํ์ธ์ ๊ฑฐ์น ํ DB์์ ๋ฐ์ดํฐ๋ฅผ ์ญ์ ํ๊ณ ๋ชฉ๋ก์ ์ฆ์ ๊ฐฑ์ ํ์ญ์์ค.
+ * **ํด์ํ๊ทธ ํํฐ๋ง:** ๋ชฉ๋ก์ ํ์๋ ํน์ ํค์๋(์: `#์ํ ํ`)๋ฅผ ํด๋ฆญํ๋ฉด, ์ ์ฒด ๋ชฉ๋ก์ด ์ฆ์ ํํฐ๋ง๋์ด ํด๋น ํค์๋๋ฅผ ๋ณด์ ํ ๋์ ์นด๋๋ค๋ง ํ๋ฉด์ ๋
ธ์ถ๋์ด์ผ ํฉ๋๋ค.
+
+#### **5.2. Back-end ์๊ตฌ์ฌํญ (DB ๋ฐ Server-side Validation)**
+๋ฐ์ดํฐ์ ์์ฑ(Create), ์กฐํ(Read), ์์ (Update), ์ญ์ (Delete)๋ฅผ ์๋ฒฝํ๊ฒ ์ฒ๋ฆฌํ๋ ๋ณธ์ธ๋ง์ ๋ฐฑ์๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๊ตฌ์ถํ์ญ์์ค.
+
+* **DB ์คํค๋ง:** MySQL/MariaDB์ `city_contents` ํ
์ด๋ธ์ ์ง์ ์ค๊ณํ์ฌ ์์ฑํ์ญ์์ค.
+* **API ๊ตฌํ (CRUD):**
+ 1. **GET (์กฐํ):** DB์ ์ ์ฅ๋ ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ JSON ํํ๋ก ๋ฐํํฉ๋๋ค.
+ 2. **POST (์์ฑ):** ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ DB์ `INSERT` ํ๊ณ ์ด๋ฏธ์ง๋ฅผ ์
๋ก๋ ํด๋์ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ ์ฅํฉ๋๋ค.
+ 3. **PUT ๋๋ PATCH (์์ ):** ํน์ ID์ ๋ฐ์ดํฐ๋ฅผ ์์ ํฉ๋๋ค. (์๋ก์ด ์ด๋ฏธ์ง๊ฐ ์ ์ก๋์๋ค๋ฉด ๊ธฐ์กด ์ด๋ฏธ์ง ํ์ผ์ ๊ต์ฒดํ๊ฑฐ๋ ์๋ก์ด ๊ฒฝ๋ก๋ฅผ ์
๋ฐ์ดํธํด์ผ ํฉ๋๋ค.)
+ 4. **DELETE (์ญ์ ):** ํน์ ID์ ๋ฐ์ดํฐ๋ฅผ DB์์ ์ญ์ ํฉ๋๋ค.
+* **์ ์ฅ/์์ ๋ก์ง์ ๋ณด์ ๊ฒ์ฌ (Server-side Validation):** POST ๋ฐ PUT/PATCH ์์ฒญ ์, ์๋์ ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ๋ฐ๋์ ํต๊ณผํ ๋ฐ์ดํฐ๋ง ์๋ฒ์ ์ ์ฅํด์ผ ํฉ๋๋ค.
+ 1. **ํ์ผ ์ฉ๋ ์ ํ:** ์
๋ก๋๋ ์ด๋ฏธ์ง ํ์ผ์ด **5MB**๋ฅผ ์ด๊ณผํ ๊ฒฝ์ฐ ์ฒ๋ฆฌ๋ฅผ ๊ฑฐ๋ถํ๊ณ HTTP ์ํ ์ฝ๋ 400(Bad Request)๊ณผ ํจ๊ป ์๋ฌ ๋ฉ์์ง๋ฅผ ๋ฐํํด์ผ ํฉ๋๋ค.
+ 2. **ํ์ฅ์ ๊ฒ์ฌ:** ํ์ฉ๋ ์ด๋ฏธ์ง ํฌ๋งท(`.jpg`, `.jpeg`, `.png`, `.webp`)์ด ์๋ ๊ฒฝ์ฐ ์ ์ฅ์ ๊ฑฐ๋ถํด์ผ ํฉ๋๋ค.
+
+#### **5.3. ๊ธฐํ ์๊ตฌ์ฌํญ (ํจ์ ๋ฐ ํ๊ฐ ํฌ์ธํธ)**
+* **๋ฐ์ดํฐ ํ์ฑ(Parsing):** ๋น์ API์ ์๋ต ์ค `keywords`๋ ์์ ๋ฐฐ์ด์ด ์๋ **JSON ํฌ๋งท์ผ๋ก ๊ฐ์ธ์ง ํ
์คํธ ๋ฌธ์์ด(Stringified Array)**๋ก ๋ฐํ๋ฉ๋๋ค. ์ด๋ฅผ ํ๋ฉด์ ๊น๋ํ ํ๊ทธ๋ก ๋ ๋๋งํ๊ธฐ ์ํด ํ๋ก ํธ์๋์์ ๋ฐ๋์ ์ ์ ํ ํ์ฑ(`JSON.parse`) ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํฉ๋๋ค.
+* **์์ธ ์ฒ๋ฆฌ:** ๋ฐฑ์๋ ์ ํจ์ฑ ๊ฒ์ฌ ์คํจ ์(์: ์ฉ๋ ์ด๊ณผ) ํ๋ก ํธ์๋ ํ๋ก๊ทธ๋จ์ด ๋ฉ์ถ์ง ์๊ณ , ์บ์น(Catch)ํ์ฌ ์ฌ์ฉ์์๊ฒ `alert` ๋๋ ๋ชจ๋ฌ ์ฐฝ์ผ๋ก ๋ช
ํํ ์๋ดํด์ผ ํฉ๋๋ค.
+
+---
+
+### **6. ์ ์ ์์
์๋ด (Competitor Workspace Guide)**
+
+* **AI Middleware ์คํ ์์น:** ์ ๊ณต๋ `ai-api` ํด๋. (ํฐ๋ฏธ๋ 1์์ ์คํ ํ ๋ฐฉ์น)
+* **์ ์ ์์
์์น:** ๋ณธ์ธ์ ์น ์๋ฒ ๋ฃจํธ ํด๋(์: `http://localhost/module-d/`).
+* **ํ๊ฐ ์ค๋น:** ๊ณผ์ ์ข
๋ฃ ์์ ์๋ AI Middleware ํฐ๋ฏธ๋๊ณผ ์ ์๊ฐ ์์ฑํ ์น ์๋ฒ๊ฐ ๋ชจ๋ ์คํ ์ค์ด์ด์ผ ํฉ๋๋ค. ์ฌ์ฌ์์(Expert)์ ๋ธ๋ผ์ฐ์ ๋ฅผ ํตํด ์ ์ํ์ฌ ์๋ ์ฌ์ดํด์ ์์ฐจ์ ์ผ๋ก ํ
์คํธํ ๊ฒ์
๋๋ค.
+ 1. 5MB ์ด๊ณผ ์ด๋ฏธ์ง ์
๋ก๋ ์๋ (์๋ฌ ์ฒ๋ฆฌ ํ์ธ)
+ 2. ์ ์ ๋ฐ์ดํฐ ์์ฑ (Create)
+ 3. ๋ชฉ๋ก ์กฐํ ๋ฐ ํด์ํ๊ทธ ํด๋ฆญ ํํฐ๋ง (Read & Filter)
+ 4. **'์์ ' ๋ฒํผ ํด๋ฆญ ํ 'AI ๋ค์ ๋ถ๋ฌ์ค๊ธฐ'๋ฅผ ํตํ ๋ด์ฉ ๊ฐฑ์ ๋ฐ ์ ์ฅ (Update)**
+ 5. **'์ญ์ ' ๋ฒํผ ํด๋ฆญ์ ํตํ ๋ฐ์ดํฐ ์์ ์ญ์ (Delete)**
+
+---
+
+### **7. Appendix: ํ
์คํธ์ฉ ์ ๊ณต ๋ฐ์ดํฐ (Sample Data)**
+ํ
์คํธ ๋ฐ ์ต์ข
์์ฐ์ ์ํด ์๋์ ๋์/๊ตญ๊ฐ ๋ชฉ๋ก์ ํ์ฉํ์ญ์์ค. **๊ณผ์ ํ
์คํธ์ฉ ๋ช
์ ์ด๋ฏธ์ง๋ ์ ๊ณต๋ Starter Kit ๋ด์ `sample-images` ํด๋์ ์ค๋น๋์ด ์์ต๋๋ค.** (ํนํ 5MB๊ฐ ๋๋ ๊ณ ํด์๋ ์ด๋ฏธ์ง๋ฅผ ํ๋ ์ค๋นํ์ฌ ๋ฐฑ์๋ ๋ฐฉ์ด ๋ก์ง์ ํ
์คํธํ์ญ์์ค.)
+
+| ๋์๋ช
(City) | ๊ตญ๊ฐ๋ช
(Country) | ๋ํ ๋ช
์ |
+| :--- | :--- | :--- |
+| **Paris** | France | ์ํ ํ, ๋ฃจ๋ธ๋ฅด ๋ฐ๋ฌผ๊ด |
+| **Seoul** | South Korea | ๊ฒฝ๋ณต๊ถ, N์์ธํ์ |
+| **Tokyo** | Japan | ๋์ฟ ํ์, ์๋ถ์ผ ๊ต์ฐจ๋ก |
+| **New York** | USA | ์์ ์ ์ฌ์ ์, ํ์์คํ์ด |
+| **Rome** | Italy | ์ฝ๋ก์ธ์, ํธ๋ ๋น ๋ถ์ |
+| **Sydney** | Australia | ์คํ๋ผ ํ์ฐ์ค, ํ๋ฒ ๋ธ๋ฆฟ์ง |
+| **Cairo** | Egypt | ๊ธฐ์ ํผ๋ผ๋ฏธ๋, ์คํํฌ์ค |
+| **London** | UK | ๋น
๋ฒค, ๋ฐ๋ ์์ด |
+
+---
+
+### **8. Mark Summary (์ฑ์ ์์ฝํ - Total 25 Marks)**
+์ด ํ๋ ํ๊ฐ ์นดํ
๊ณ ๋ฆฌ๋ณ ๋ฐฐ์ ์์ฝ์
๋๋ค. ์ธ๋ถ ์ฑ์ ๊ธฐ์ค(Marking Scheme)์ ๋ณ๋๋ก ์ ๊ณต๋ฉ๋๋ค.
+
+| Category | Description | Max Marks |
+| :--- | :--- | :---: |
+| **A. Front-end UI & UX** | UI ๊ตฌ์ฑ, ๋น๋๊ธฐ ์ํ ๊ด๋ฆฌ(๋ก๋ฉ/์ ๊ธ), ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ๋ฐ ํ์ฑ ์ฒ๋ฆฌ | 7 |
+| **B. Data Interaction** | ๋ฐ์ดํฐ ๋ชฉ๋ก ๋ ๋๋ง ๋ฐ ํด์ํ๊ทธ ๊ธฐ๋ฐ ํํฐ๋ง ๊ธฐ๋ฅ | 5 |
+| **C. Back-end CRUD API** | ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ฐ ํ์ผ ์์คํ
๊ธฐ๋ฐ์ ์์ฑ, ์กฐํ, ์์ (AI ๋ค์ ๋ถ๋ฌ์ค๊ธฐ ํฌํจ), ์ญ์ ๊ธฐ๋ฅ | 8 |
+| **D. Security & Validation**| ์๋ฒ ์ธก ํ์ผ ์ฉ๋ ๋ฐ ํ์ฅ์ ๊ฒ์ฌ, ํด๋ผ์ด์ธํธ ์ธก ์์ธ ์ฒ๋ฆฌ | 5 |
+| **Total Marks** | | **25** |
\ No newline at end of file
diff --git a/module-d/module-d-uz.md b/module-d/module-d-uz.md
new file mode 100644
index 0000000..6f5d275
--- /dev/null
+++ b/module-d/module-d-uz.md
@@ -0,0 +1,141 @@
+# ๐ WSC2026 Test Project - Veb Texnologiyalar
+## **D Moduli: AI yordamida ishlaydigan Global City Explorer (Full-stack Integratsiya)**
+
+### **1. Loyiha haqida umumiy ma'lumot (Project Overview)**
+Siz 'Global City Explorer' platformasining full-stack dasturchisisiz. Ichki AI muhandislik jamoasi matnli ma'lumotlarni tahlil qilish va rasmlardan kalit so'zlarni ajratib olish uchun mahalliy AI modellaridan (Ollama) foydalanadigan **'AI Middleware Server'** ni (AI Oraliq Serveri) ishlab chiqdi. Bu sizga Starter Kit (Boshlang'ich to'plam) sifatida taqdim etilgan.
+
+Sizning vazifangiz taqdim etilgan AI serverini ishga tushirish, uning API'larini tahlil qilish va administratorlar shahar ma'lumotlarini yaratishi, o'qishi, yangilashi va o'chirishi (CRUD) mumkin bo'lgan mukammal **Front-end (UI)** yaratishdir. Bundan tashqari, ma'lumotlarni faqat qat'iy server tekshiruvidan (server-side validation) o'tgandan keyingina qayta ishlaydigan va xavfsiz saqlaydigan kuchli **Back-end (DB Storage)** ni ishlab chiqishingiz kerak.
+
+---
+
+### **2. Infratuzilmani sozlash (Infrastructure Setup)**
+Vazifani boshlashdan oldin, shaxsiy kompyuteringiz AI modellari va Node.js serverini ishga tushirish talablariga javob berishiga ishonch hosil qilishingiz va atrof-muhitni shunga mos ravishda sozlashingiz kerak.
+
+#### **2.1. Uskuna talablari (Hardware Requirements)**
+Mahalliy muhitda ikkita AI modelini uzluksiz ishga tushirish uchun quyidagi xususiyatlar tavsiya etiladi:
+* **Xotira (RAM):** Kamida 8GB / Tavsiya etiladi 16GB+ (Ikkala model ham xotiraga yuklanishi kerak).
+* **GPU:** 6GB+ VRAM'ga ega NVIDIA GPU tavsiya etiladi (CPU orqali ishlash ham mumkin, lekin tasvirni tahlil qilish vaqti kechikishi mumkin).
+* **Disk hajmi (Storage):** Kamida 10GB bo'sh joy (HDD dan ko'ra SSD dan foydalanish qat'iy tavsiya etiladi).
+
+#### **2.2. Node.js'ni tekshirish va o'rnatish**
+Back-end oraliq dasturini (middleware) ishga tushirish uchun Node.js muhiti talab qilinadi.
+1. Terminalni (yoki Command Prompt) oching va o'rnatilganligini tekshirish uchun `node -v` deb yozing. (`v18.x.x` yoki undan yuqori versiya chiqishi kutiladi).
+2. **Agar o'rnatilmagan bo'lsa:** [Node.js rasmiy veb-saytiga (nodejs.org)](https://nodejs.org/) kiring, LTS (Long Term Support) versiyasini yuklab oling va o'rnating.
+
+#### **2.3. Ollama (AI Dvigateli) ni tekshirish va o'rnatish**
+1. Terminalni oching va versiyani tekshirish uchun `ollama -v` deb yozing.
+2. **Agar o'rnatilmagan bo'lsa:** Windows tizimida **PowerShell**'ni Administrator sifatida ishga tushiring va o'rnatish uchun quyidagi buyruqni kiriting:
+ `irm https://ollama.com/install.ps1 | iex`
+ *(O'rnatish tugagandan so'ng, muhit o'zgaruvchilari (environment variables) qo'llanilishi uchun terminalni qayta ishga tushirishingiz kerak.)*
+
+#### **2.4. AI modellarini yuklab olish va ularning xususiyatlari**
+Ushbu loyihada foydalaniladigan ikkita AI modelini yuklab olish uchun terminalni oching va quyidagi buyruqlarni ketma-ket bajaring. (O'rnatish muvaffaqiyatli bo'lganini `ollama list` buyrug'i orqali tekshiring).
+
+* **Matnni qayta ishlash modelini yuklab olish:** `ollama pull phi3`
+ * **[Model xususiyatlari]:** Microsoft tomonidan ishlab chiqilgan Kichik Til Modeli (SLM - Small Language Model). U yengil va tez bo'lib, mukammal matn yaratish va mantiqiy fikrlash qobiliyatiga ega. Bu uni shahar haqida qisqacha ma'lumot yozish uchun ideal qiladi.
+* **Ko'rish (Tasvir) ni qayta ishlash modelini yuklab olish:** `ollama pull llava`
+ * **[Model xususiyatlari]:** Matn bilan bir qatorda 'Ko'rish' (Vision) imkoniyatlari bilan jihozlangan Multimodal AI. U foydalanuvchi tomonidan yuklangan diqqatga sazovor joylar tasvirlarini tahlil qilib, ob'ektlar yoki manzaralarni tasvirlaydi va asosiy kalit so'zlarni (keywords) ajratib oladi.
+
+---
+
+### **3. AI Middleware Serverni o'rnatish va ishga tushirish**
+Taqdim etilgan Starter Kit ichida AI muhandislik jamoasi tomonidan yaratilgan `ai-api` papkasi mavjud. Uning ichida `server.js` va `package.json` fayllarini topasiz. **(๐จ DIQQAT: Hech qanday holatda ushbu server kodini o'zgartirmang.)**
+
+1. Terminalni oching va taqdim etilgan `ai-api` papkasi katalogiga o'ting.
+2. Kerakli paketlarni o'rnatish uchun quyidagi buyruqni kiriting:
+ `npm install`
+3. O'rnatish tugagach, serverni ishga tushiring:
+ `npm start`
+4. Agar terminalda `๐ AI Middleware Server running on http://localhost:3000` xabari paydo bo'lsa, server to'g'ri ishlamoqda. Musobaqa moduli tugamaguncha bu terminalni yopmang.
+
+---
+
+### **4. HOPPSCOTCH orqali API test qilish**
+Dasturlashni boshlashdan oldin, taqdim etilgan ikkita API qanday formatdagi ma'lumotlarni qaytarishini tushunish va test qilish uchun Hoppscotch (yoki Postman) dan foydalanishingiz SHART.
+
+* **1-API: Matn ma'lumotlarini yaratish (`POST /api/generate-text`)**
+ * Body (`application/json`): `{"city_name": "Paris", "country": "France"}`
+ 
+
+* **2-API: Tasvirni kompleks tahlil qilish (`POST /api/analyze-image`)**
+ * Body (`multipart/form-data`): `city_name` (Text), `country` (Text), `image` (Rasm yuklash uchun File formati)
+ 
+
+---
+
+### **5. Bajarilishi kerak bo'lgan vazifalar (Tasks to Complete)**
+
+#### **5.1. Front-end talablari (Kengaytirilgan UI/UX)**
+Administratorlar uchun `index.html` (yoki SPA framework) interfeysini yarating. Ekran asosan **'Ma'lumotlarni ro'yxatdan o'tkazish/Tahrirlash hududi'** va **'Ro'yxatga olinganlarni ko'rish hududi'** ga bo'linishi kerak.
+
+* **[Ma'lumotlarni ro'yxatdan o'tkazish va tahrirlash hududi (Form Area)]**
+ * **Tasvirni oldindan ko'rish (Image Preview):** `` orqali rasm tanlanganda, u serverga yuborilishidan oldin ekranda rasm eskizi (thumbnail) darhol paydo bo'lishi kerak.
+ * **AI ma'lumotlar integratsiyasi:** Shahar/davlat nomini kiritgandan so'ng AI matn yaratish API'sini chaqiring va rasm yuklangandan so'ng AI vizual tahlil API'sini chaqiring. Qaytarilgan ma'lumotlar bilan formani to'ldiring.
+ * **Formani qulflash va yuklanish (Asynchronous State Management):** Og'ir AI tahlili biroz vaqt (10~30 soniya) talab qilganligi sababli, jarayon davomida foydalanuvchi qayta-qayta bosishining va xatoliklarning oldini olish uchun barcha kiritish maydonlari (input fields) va tugmalarni **qulflashingiz (disabled)** kerak. Bundan tashqari, foydalanuvchi tajribasini (UX) optimallashtirish uchun aniq yuklanish UI elementini (spinner, progress bar va hk.) ko'rsating.
+ * **Ma'lumotlarni tahrirlash va AI'dan qayta yuklash (Re-fetch):**
+ * Ro'yxatdagi ma'lum bir shahar uchun 'Tahrirlash' (Edit) tugmachasini bosish, formani o'sha shaharning ma'lumotlari bilan to'ldirishi kerak.
+ * Tahrirlash formasi **'Barcha ma'lumotlarni AI'dan qayta yuklash'** tugmasini o'z ichiga olishi kerak. Ushbu tugmani bosish formadagi joriy shahar, davlat va rasmga asoslanib, ikkala AI API'sini ham qayta ishga tushirishi va matn qiymatlarini (tavsif, kalit so'zlar) butunlay yangi AI javobi bilan almashtirishi kerak. Foydalanuvchi 'Yakuniy saqlash' ni bosganidagina ma'lumotlar DB'da yangilanadi.
+
+* **[Ro'yxatni ko'rish va filtrlash hududi (List & Interactive Filtering)]**
+ * Butun dunyo bo'ylab saqlangan shahar ma'lumotlarini DB'dan oling va uni vizual ravishda Ro'yxat (List) yoki Kartalar Galereyasi (Card Gallery) formatida ko'rsating. Har bir elementda **'Tahrirlash' (Edit)** va **'O'chirish' (Delete)** tugmalari bo'lishi kerak.
+ * **Ma'lumotlarni o'chirish (Delete):** 'O'chirish' tugmasi bosilganda, tasodifan o'chirib yuborishning oldini olish uchun brauzerning standart `confirm` oynasi yoki maxsus Modal orqali foydalanuvchidan tasdiqlashni so'rang. Tasdiqlangandan so'ng, ma'lumotlarni DB'dan o'chiring va ro'yxatni darhol yangilang.
+ * **Heshteg orqali filtrlash:** Ro'yxatda ko'rsatilgan ma'lum bir kalit so'zni (masalan, `#EiffelTower`) bosish orqali darhol butun ro'yxat filtrlari ishga tushishi va faqat o'sha kalit so'zni o'z ichiga olgan shahar kartalarigina ko'rsatilishi kerak.
+
+#### **5.2. Back-end talablari (DB va Server tomonida tekshirish)**
+Ma'lumotlaringizni Yaratish, O'qish, Yangilash va O'chirish (CRUD - Create, Read, Update, Delete) operatsiyalarini mukammal bajarish uchun shaxsiy back-end serveringiz va ma'lumotlar bazasini yarating.
+
+* **DB Sxemasi:** MySQL/MariaDB da `city_contents` jadvalini loyihalashtiring va yarating.
+* **API ilovasi (CRUD):**
+ 1. **GET (O'qish):** DB'da saqlangan barcha ma'lumotlarni JSON formatida qaytaradi.
+ 2. **POST (Yaratish):** DB'ga yangi ma'lumotlarni `INSERT` qiladi va yuklangan rasmni jismoniy jihatdan yuklash (uploads) papkasiga saqlaydi.
+ 3. **PUT yoki PATCH (Yangilash):** Muayyan ID uchun ma'lumotlarni yangilaydi. (Agar yangi rasm yuborilsa, eski rasm faylini almashtirishingiz yoki yangi fayl yo'lini yangilashingiz kerak).
+ 4. **DELETE (O'chirish):** Muayyan ID uchun ma'lumotlarni DB'dan o'chiradi.
+* **Server tomonida tekshirish (Xavfsizlikni tekshirish):** POST va PUT/PATCH so'rovlari uchun FAQAT quyidagi tekshiruvlardan o'tgan ma'lumotlargina serverga saqlanishi kerak:
+ 1. **Fayl hajmi chegarasi:** Agar yuklangan rasm **5MB** dan oshsa, server so'rovni rad etishi va 400 HTTP status kodi (Bad Request) bilan birga xatolik xabarini qaytarishi kerak.
+ 2. **Kengaytmani tekshirish:** Agar rasm ruxsat etilgan formatda bo'lmasa (`.jpg`, `.jpeg`, `.png`, `.webp`), server yuklashni rad etishi kerak.
+
+#### **5.3. Qo'shimcha talablar (Xatolar va baholash mezonlari)**
+* **Ma'lumotlarni tahlil qilish (Data Parsing):** Vision API javobida `keywords` toza massiv (array) sifatida emas, balki **JSON formatida o'ralgan matnli massiv (Stringified Array)** sifatida qaytariladi. Ularni toza va alohida teg UI sifatida ko'rsatish uchun front-end albatta to'g'ri tahlil qilish (parsing - `JSON.parse`) ni amalga oshirishi SHART.
+* **Istisnolarni ko'rib chiqish (Exception Handling):** Agar back-end tekshiruvi muvaffaqiyatsiz bo'lsa (masalan, Fayl hajmi oshib ketdi), front-end ilovasi buzilmasligi (crash) kerak. U xatoni ushlab olishi (catch) va foydalanuvchini `alert` yoki Modal oyna orqali aniq xabardor qilishi kerak.
+
+---
+
+### **6. Ishtirokchi ish stoli bo'yicha qo'llanma (Competitor Workspace Guide)**
+
+* **AI Middleware ishga tushirish joyi:** Taqdim etilgan `ai-api` papkasi. (1-Terminalda ishga tushiring va uni ochiq qoldiring).
+* **Ishtirokchi ish stoli:** Sizning veb-serveringizning asosiy(root) papkasi (masalan, `http://localhost/module-d/`).
+* **Baholashga tayyorgarlik:** Modul oxirida AI Middleware terminali ham, siz yaratgan veb-server ham ishlab turgan bo'lishi kerak. Ekspertlar brauzer orqali ulanadi va quyidagi siklni ketma-ket sinab ko'radi:
+ 1. 5MB dan katta hajmdagi rasmni yuklashga urinish (Xatoni ushlab qolishni tekshirish)
+ 2. Oddiy ma'lumotlarni yaratish (Create)
+ 3. Ro'yxatni ko'rish va heshtegni bosish orqali filtrlash (Read & Filter)
+ 4. **'Tahrirlash' tugmasini bosish, 'AI'dan qayta yuklash' orqali tarkibni yangilash va Saqlash (Update)**
+ 5. **Ma'lumotni butunlay yo'q qilish uchun 'O'chirish' tugmasini bosish (Delete)**
+
+---
+
+### **7. Ilova: Namuna ma'lumotlar (Sample Data)**
+Test qilish va yakuniy namoyish uchun quyidagi shahar/davlat ro'yxatidan foydalaning. **Test qilish uchun tavsiya etilgan diqqatga sazovor joylarning rasmlari Starter Kit ichidagi `sample-images` papkasida taqdim etilgan.** (Back-end himoya mantiqini sinab ko'rish uchun kamida bitta 5MB dan katta yuqori aniqlikdagi rasmni tayyorlab qo'ying).
+
+| Shahar nomi | Davlat nomi | Diqqatga sazovor joy (Landmark) |
+| :--- | :--- | :--- |
+| **Paris** | France | Eyfel minorasi, Luvr muzeyi |
+| **Seoul** | South Korea | Kyonbokkun saroyi, N Seul minorasi |
+| **Tokyo** | Japan | Tokio minorasi, Shibuya chorrahasi |
+| **New York** | USA | Ozodlik haykali, Tayms maydoni |
+| **Rome** | Italy | Kolizey, Trevi favvorasi |
+| **Sydney** | Australia | Opera teatri, Harbor ko'prigi |
+| **Cairo** | Egypt | Giza piramidalari, Sfinks |
+| **London** | UK | Big Ben, London ko'zi |
+
+---
+
+### **8. Baholash xulosasi (Mark Summary - Jami 25 ball)**
+Ushbu jadval baholash toifalari bo'yicha ajratilgan ballar xulosasini beradi. Batafsil baholash sxemasi (Marking Scheme) alohida taqdim etiladi.
+
+| Toifa (Category) | Tavsif (Description) | Maksimal Ball |
+| :--- | :--- | :---: |
+| **A. Front-end UI & UX** | UI tuzilishi, Asinxron holatni boshqarish (Yuklash/Qulflash), Ma'lumotlarni ulash (Binding) va Tahlil qilish (Parsing) | 7 |
+| **B. Data Interaction** | Ma'lumotlar ro'yxatini ko'rsatish (rendering) va Heshtegga asoslangan filtrlash funksiyasi | 5 |
+| **C. Back-end CRUD API** | DB va Fayl tizimida Yaratish, O'qish, Yangilash (AI'dan qayta yuklash bilan birga) va O'chirish amallari | 8 |
+| **D. Security & Validation**| Server tomonida fayl hajmi/kengaytmani tekshirish, Mijoz tomonida istisnolarni (xatolarni) ko'rib chiqish | 5 |
+| **Jami Ballar** | | **25** |
\ No newline at end of file
diff --git a/module-d/package-lock.json b/module-d/package-lock.json
new file mode 100644
index 0000000..bde0f31
--- /dev/null
+++ b/module-d/package-lock.json
@@ -0,0 +1,1017 @@
+{
+ "name": "wsc2026-module-b-ai-middleware",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "wsc2026-module-b-ai-middleware",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.19.2",
+ "multer": "^1.4.5-lts.1"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==",
+ "license": "MIT"
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "license": "MIT"
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "license": "MIT"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/multer": {
+ "version": "1.4.5-lts.2",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz",
+ "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==",
+ "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.",
+ "license": "MIT",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.0.0",
+ "concat-stream": "^1.5.2",
+ "mkdirp": "^0.5.4",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.4",
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
+ "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
+ "license": "MIT"
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz",
+ "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ }
+ }
+}
diff --git a/module-d/package.json b/module-d/package.json
new file mode 100644
index 0000000..dbc59a3
--- /dev/null
+++ b/module-d/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "wsc2026-module-b-ai-middleware",
+ "version": "1.0.0",
+ "description": "WSC2026 Web Technologies - Test Project: AI Middleware",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js"
+ },
+ "author": "WorldSkills Expert Team",
+ "license": "ISC",
+ "dependencies": {
+ "cors": "^2.8.5",
+ "express": "^4.19.2",
+ "multer": "^1.4.5-lts.1"
+ },
+ "keywords": [],
+ "type": "commonjs"
+}
diff --git a/module-d/sample_images/Cairo_Egypt_1.jpg b/module-d/sample_images/Cairo_Egypt_1.jpg
new file mode 100644
index 0000000..cf91ba7
Binary files /dev/null and b/module-d/sample_images/Cairo_Egypt_1.jpg differ
diff --git a/module-d/sample_images/Cairo_Egypt_2.jpg b/module-d/sample_images/Cairo_Egypt_2.jpg
new file mode 100644
index 0000000..ca1d156
Binary files /dev/null and b/module-d/sample_images/Cairo_Egypt_2.jpg differ
diff --git a/module-d/sample_images/London_UK_1.jpg b/module-d/sample_images/London_UK_1.jpg
new file mode 100644
index 0000000..d8fe89b
Binary files /dev/null and b/module-d/sample_images/London_UK_1.jpg differ
diff --git a/module-d/sample_images/London_UK_2.jpg b/module-d/sample_images/London_UK_2.jpg
new file mode 100644
index 0000000..34089cb
Binary files /dev/null and b/module-d/sample_images/London_UK_2.jpg differ
diff --git a/module-d/sample_images/New York_USA_1.jpg b/module-d/sample_images/New York_USA_1.jpg
new file mode 100644
index 0000000..2fe2873
Binary files /dev/null and b/module-d/sample_images/New York_USA_1.jpg differ
diff --git a/module-d/sample_images/New York_USA_2.jpg b/module-d/sample_images/New York_USA_2.jpg
new file mode 100644
index 0000000..3786c6f
Binary files /dev/null and b/module-d/sample_images/New York_USA_2.jpg differ
diff --git a/module-d/sample_images/Paris_France_1.jpg b/module-d/sample_images/Paris_France_1.jpg
new file mode 100644
index 0000000..7b5e88a
Binary files /dev/null and b/module-d/sample_images/Paris_France_1.jpg differ
diff --git a/module-d/sample_images/Paris_france_2.jpg b/module-d/sample_images/Paris_france_2.jpg
new file mode 100644
index 0000000..93eda87
Binary files /dev/null and b/module-d/sample_images/Paris_france_2.jpg differ
diff --git a/module-d/sample_images/Rome_Italy_1.jpg b/module-d/sample_images/Rome_Italy_1.jpg
new file mode 100644
index 0000000..464a13c
Binary files /dev/null and b/module-d/sample_images/Rome_Italy_1.jpg differ
diff --git a/module-d/sample_images/Rome_Italy_2.jpg b/module-d/sample_images/Rome_Italy_2.jpg
new file mode 100644
index 0000000..18c7c48
Binary files /dev/null and b/module-d/sample_images/Rome_Italy_2.jpg differ
diff --git a/module-d/sample_images/Seoul_Korea_1.jpg b/module-d/sample_images/Seoul_Korea_1.jpg
new file mode 100644
index 0000000..d5eeb2b
Binary files /dev/null and b/module-d/sample_images/Seoul_Korea_1.jpg differ
diff --git a/module-d/sample_images/Seoul_Korea_2.jpg b/module-d/sample_images/Seoul_Korea_2.jpg
new file mode 100644
index 0000000..9894bdc
Binary files /dev/null and b/module-d/sample_images/Seoul_Korea_2.jpg differ
diff --git a/module-d/sample_images/Sydney_Australia_1.jpg b/module-d/sample_images/Sydney_Australia_1.jpg
new file mode 100644
index 0000000..0eb5ba1
Binary files /dev/null and b/module-d/sample_images/Sydney_Australia_1.jpg differ
diff --git a/module-d/sample_images/Sydney_Australia_2.jpg b/module-d/sample_images/Sydney_Australia_2.jpg
new file mode 100644
index 0000000..d400f9f
Binary files /dev/null and b/module-d/sample_images/Sydney_Australia_2.jpg differ
diff --git a/module-d/sample_images/Tokyo_Japan_1.jpg b/module-d/sample_images/Tokyo_Japan_1.jpg
new file mode 100644
index 0000000..fff6450
Binary files /dev/null and b/module-d/sample_images/Tokyo_Japan_1.jpg differ
diff --git a/module-d/sample_images/Tokyo_Japan_2.jpg b/module-d/sample_images/Tokyo_Japan_2.jpg
new file mode 100644
index 0000000..0dcfb1e
Binary files /dev/null and b/module-d/sample_images/Tokyo_Japan_2.jpg differ
diff --git a/module-d/sample_images/over_5mb/OrexModel.115728.png b/module-d/sample_images/over_5mb/OrexModel.115728.png
new file mode 100644
index 0000000..075f751
Binary files /dev/null and b/module-d/sample_images/over_5mb/OrexModel.115728.png differ
diff --git a/module-d/server.js b/module-d/server.js
new file mode 100644
index 0000000..7e06dd8
--- /dev/null
+++ b/module-d/server.js
@@ -0,0 +1,173 @@
+/**
+ * ============================================================================
+ * WSC2026 Web Technologies - Test Project: AI Middleware Server
+ * ============================================================================
+ * ๐จ [Competitor Notice]
+ * You do NOT need to modify this file. This is a black-box AI microservice.
+ * Your task is to run this server, test the APIs, and build your own
+ * Front-end and Back-end (Database) application that communicates with it.
+ * ============================================================================
+ */
+
+const express = require('express');
+const cors = require('cors');
+const multer = require('multer');
+
+const app = express();
+app.use(cors());
+app.use(express.json());
+
+// Use memory storage (Converts image buffer directly to Base64)
+const upload = multer({ storage: multer.memoryStorage() });
+const OLLAMA_API = 'http://localhost:11434/api/generate';
+
+// ----------------------------------------------------------------------------
+// API 1: Text AI (Generate City Description and Country Info)
+// ----------------------------------------------------------------------------
+app.post('/api/generate-text', async (req, res) => {
+ const { city_name, country } = req.body;
+ if (!city_name || !country) {
+ return res.status(400).json({ error: 'city_name and country are required.' });
+ }
+
+ try {
+ const response = await fetch(OLLAMA_API, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ model: 'phi3',
+ // ๐ Advanced Prompt: Request city description and country info in JSON format
+ prompt: `Provide information about ${city_name} in ${country}.
+ Return a JSON object with exactly two keys:
+ 1. "city_description": A 2-sentence tourist introduction for the city.
+ 2. "country_info": One interesting or essential fact about the country ${country}.
+ DO NOT output any explanations. JUST the JSON object.`,
+ format: 'json',
+ stream: false
+ })
+ });
+ const data = await response.json();
+ const rawText = data.response;
+
+ console.log(`[Text API] AI Original Response for ${city_name}, ${country}:`, rawText);
+
+ // --- ๐ก๏ธ Text Hallucination Defense Logic ---
+ let cityDescription = "Failed to generate city description. Please enter manually.";
+ let countryInfo = "Failed to generate country information. Please enter manually.";
+
+ try {
+ const parsed = JSON.parse(rawText);
+ cityDescription = parsed.city_description || cityDescription;
+ countryInfo = parsed.country_info || countryInfo;
+ } catch (e) {
+ // Fallback: Use regular expressions if JSON parsing fails
+ console.warn("[Text API] JSON parsing failed. Attempting regex extraction.");
+ const descMatch = rawText.match(/"city_description"\s*:\s*"([^"]+)"/i);
+ if (descMatch) cityDescription = descMatch[1];
+ const infoMatch = rawText.match(/"country_info"\s*:\s*"([^"]+)"/i);
+ if (infoMatch) countryInfo = infoMatch[1];
+ }
+
+ res.json({
+ city_description: cityDescription,
+ country_info: countryInfo
+ });
+ } catch (error) {
+ console.error("[Text API] Error:", error);
+ res.status(500).json({ error: 'Text AI processing failed. Ensure Ollama is running.' });
+ }
+});
+
+// ----------------------------------------------------------------------------
+// API 2: Vision AI (Integrated Image Analysis - Description and Keywords)
+// ----------------------------------------------------------------------------
+app.post('/api/analyze-image', upload.single('image'), async (req, res) => {
+ const file = req.file;
+ const { city_name, country } = req.body;
+
+ if (!file || !city_name || !country) {
+ return res.status(400).json({ error: 'image, city_name, and country are required.' });
+ }
+
+ try {
+ const base64Image = file.buffer.toString('base64');
+ const response = await fetch(OLLAMA_API, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ model: 'llava',
+ prompt: `Analyze this image of ${city_name}, ${country}.
+ Return a JSON object with:
+ 1. "description": A short 1-2 sentence description of the image.
+ 2. "keywords": An array of exactly 3 descriptive words.
+ JUST the JSON object. DO NOT output any markdown, tags, or extra text.`,
+ images: [base64Image],
+ format: 'json',
+ stream: false
+ })
+ });
+
+ const data = await response.json();
+ const rawAiText = data.response;
+
+ console.log(`[Vision API] AI Original Response for ${city_name}, ${country}:`, rawAiText);
+
+ // --- ๐ก๏ธ Hallucination Defense & Data Sanitization Logic ---
+ let finalDescription = "No description generated. Please enter manually.";
+ let finalKeywords = [];
+
+ try {
+ // Attempt 1: Force extract inside curly braces {} in case AI adds markdown wrappers
+ const jsonMatch = rawAiText.match(/\{[\s\S]*\}/);
+ const targetText = jsonMatch ? jsonMatch[0] : rawAiText;
+ const parsed = JSON.parse(targetText);
+
+ if (parsed.description) finalDescription = parsed.description;
+ if (parsed.keywords) {
+ if (Array.isArray(parsed.keywords)) {
+ finalKeywords = parsed.keywords;
+ } else if (typeof parsed.keywords === 'string') {
+ // Split into an array if keywords arrive as a comma-separated string
+ finalKeywords = parsed.keywords.split(',');
+ }
+ }
+ } catch (e) {
+ console.warn("[Vision API] JSON parsing failed. Attempting regex fallback.");
+
+ // Attempt fallback extraction for description
+ const descMatch = rawAiText.match(/"description"\s*:\s*"([^"]+)"/i);
+ if (descMatch) finalDescription = descMatch[1];
+
+ // Attempt fallback extraction for keywords array
+ const arrMatch = rawAiText.match(/\[(.*?)\]/s);
+ if (arrMatch) finalKeywords = arrMatch[1].replace(/["']/g, '').split(',');
+ }
+
+ // Sanitize the keywords array (trim, remove brackets/quotes, filter empty, limit to 3)
+ finalKeywords = finalKeywords
+ .map(w => w.trim().replace(/[{}"\[\]]/g, ''))
+ .filter(w => w.length > 0)
+ .slice(0, 3);
+
+ if (finalKeywords.length === 0) {
+ finalKeywords = ["analysis_failed", "manual_input_required", "error"];
+ }
+
+ // Finally, return the description (Text) and keywords (Stringified Array)
+ res.json({
+ description: finalDescription,
+ keywords: JSON.stringify(finalKeywords)
+ });
+
+ } catch (error) {
+ console.error("[Vision API] Error:", error);
+ res.status(500).json({ error: 'Vision AI processing failed. Ensure Ollama is running.' });
+ }
+});
+
+const PORT = 3000;
+app.listen(PORT, () => {
+ console.log(`๐ AI Middleware Server running on http://localhost:${PORT}`);
+ console.log(`- Text API: POST http://localhost:${PORT}/api/generate-text`);
+ console.log(`- Vision API: POST http://localhost:${PORT}/api/analyze-image`);
+});
\ No newline at end of file