# M4 Web Review Server & API Endpoints In-Depth Analysis

## 1. Executive Summary
An exhaustive audit of `web_review/server.py`, `web_review/templates/index.html`, `05_Production_Pipeline/` (`run_shot.py`, `production_orchestrator.py`), and the Feature F11 test suites across all 4 tiers (`tests/test_tier1_features.py`, `tests/test_tier2_boundaries.py`, `tests/test_tier3_interactions.py`, `tests/test_tier4_workloads.py`) was performed.
The investigation identified several critical omissions, compatibility gaps, and enhancement opportunities:
1. **Missing Module-Level Port/Host Constants**: `DEFAULT_PORT = 1515` and `DEFAULT_HOST = "0.0.0.0"` were not exposed at the module level.
2. **Missing Endpoint `/api/concat`**: Completely absent from `server.py` despite being specified for scene master concatenation.
3. **Schema & Runner Disconnect in `/api/generate`**: `GenerateRequest` lacked `shot_id`, `input_frame`, and `force`, and bypassed `production_orchestrator.py`.
4. **Windows Encoding Crash (`cp1252`)**: `print()` in `log_msg()` threw `UnicodeEncodeError` on Windows consoles with Vietnamese characters.
5. **Master Classification Bug**: Files matching `grand_master` (e.g. `ep01_grand_master_v1.mp4`) were dropped from `build_library_data()` because only `grand_feature_master` was checked.
6. **Filtering Parameters for `/api/shots` and `/api/scenes`**: Neither endpoint supported filtering by `episode` or `scene`.
7. **UI Concat Trigger Missing**: `index.html` lacked UI controls to trigger scene concatenation.

All proposed fixes were implemented and verified in `.agents/teamwork/explorer_m4_1/proposed_server_production.py` and tested via `.agents/teamwork/explorer_m4_1/test_proposed_server.py` with 100% test pass rate.

---

## 2. Detailed Findings by Component

### 2.1 Server Configuration & Routing Constants
- **Current Observation (`web_review/server.py:567-568`)**:
  ```python
  if __name__ == "__main__":
      uvicorn.run(app, host="0.0.0.0", port=1515, log_level="info")
  ```
  `DEFAULT_PORT` and `DEFAULT_HOST` are not defined as module-level constants.
- **Impact**: Any test, runner, or external script attempting `from server import DEFAULT_PORT, DEFAULT_HOST` fails with `ImportError`.
- **Resolution**:
  Expose at module level:
  ```python
  DEFAULT_HOST = "0.0.0.0"
  DEFAULT_PORT = 1515
  ```
  and reference them in `uvicorn.run(app, host=DEFAULT_HOST, port=DEFAULT_PORT, log_level="info")`.

---

### 2.2 Missing `/api/concat` Endpoint
- **Current Observation**: Searching for `concat` in `web_review/server.py` yields zero results.
- **Requirement**: POST endpoint `/api/concat` triggering scene concatenation.
- **Design & Contract**:
  - Request Model:
    ```python
    class ConcatRequest(BaseModel):
        scene_id: str
        output_path: Optional[str] = None
        crossfade_dur: Optional[float] = 1.0
    ```
  - Validation: Missing or empty `scene_id` returns HTTP 422.
  - Concurrency Guard: If `gen_state["status"]` is `"generating"` or `"concatenating"`, returns HTTP 409 Conflict.
  - Background Task: Launches `run_scene_concat` using `production_orchestrator.concat_scene_shots` via FastAPI `BackgroundTasks`.
  - State Tracking: Updates `gen_state["status"] = "concatenating"` -> `"completed"` / `"error"`, sets `last_video`, logs progress, and invalidates `_library_cache`.
  - Response: `{"status": "accepted", "message": "Đã bắt đầu tác vụ ghép nối cảnh ... trong nền"}`.

---

### 2.3 Generation Endpoint Schema & Production Runner Synchronization (`/api/generate`)
- **Current Observation (`web_review/server.py:68-71, 558-565`)**:
  ```python
  class GenerateRequest(BaseModel):
      prompt: str
      scene_id: Optional[str] = None
      title: Optional[str] = None
  ```
  Does not accept `shot_id`, `input_frame`, or `force`.
  Directly triggers `run_agent_browser_generation` without Start Frame resolution or Tail Frame extraction.
- **Contract Compatibility Requirements**:
  - `test_tier1_features.py`: `/api/status` contract.
  - `test_tier2_boundaries.py`:
    - `test_f11_b01_concurrent_generate_conflict_409`: expects 409 when generating.
    - `test_f11_b03_empty_prompt_generation_validation`: `GenerateRequest(prompt="valid prompt")` must succeed without validation error; omitting both `prompt` and `shot_id` must fail validation (HTTP 422).
  - `test_tier3_interactions.py`:
    - `test_f11_f12_web_studio_generate_triggers_production_runner`: `GenerateRequest(prompt="Test prompt", scene_id="ep01_scene01")`.
- **Resolution**:
  ```python
  class GenerateRequest(BaseModel):
      prompt: Optional[str] = None
      shot_id: Optional[str] = None
      scene_id: Optional[str] = None
      title: Optional[str] = None
      input_frame: Optional[str] = None
      force: Optional[bool] = False

      @model_validator(mode="after")
      def check_prompt_or_shot(self):
          if not self.prompt and not self.shot_id:
              raise ValueError("Either prompt or shot_id must be provided")
          return self
  ```
  In `run_unified_generation(req: GenerateRequest)`:
  - If `shot_id` is present: invokes `production_orchestrator.render_single_shot(shot_id, input_frame=req.input_frame, prompt_override=req.prompt)`. This automatically handles Start Frame resolution (Sweet-Spot 720p / Head-Tail chaining), prompt lookup with Audio Guard, OpenCV Tail Frame extraction (`clean_frame_239.jpg`), continuity scoring, and versioned naming (`<shot_id>_10s_v<N>.mp4`).
  - If only `prompt` is present: delegates to `run_agent_browser_generation(prompt, title)` with `resolve_next_version()` naming and Tail Frame extraction.

---

### 2.4 Windows Console Encoding Fix (`cp1252` `UnicodeEncodeError`)
- **Observation**:
  Running on Windows PowerShell/CMD without UTF-8 reconfigured throws:
  `UnicodeEncodeError: 'charmap' codec can't encode character '\u1ed1' in position 31: character maps to <undefined>`
  inside `log_msg()` when printing Vietnamese characters.
- **Resolution**:
  1. Add stdout/stderr reconfigure at module init:
     ```python
     if sys.platform == "win32":
         try:
             sys.stdout.reconfigure(encoding="utf-8")
             sys.stderr.reconfigure(encoding="utf-8")
         except Exception:
             pass
     ```
  2. Implement defensive fallback printing in `log_msg()`:
     ```python
     def log_msg(msg: str):
         ts = time.strftime("%H:%M:%S")
         entry = f"[{ts}] {msg}"
         gen_state["logs"].append(entry)
         gen_state["logs"] = gen_state["logs"][-30:]
         try:
             print(entry)
         except Exception:
             try:
                 print(entry.encode("ascii", errors="replace").decode("ascii"))
             except Exception:
                 pass
     ```

---

### 2.5 Library Scan & Master Categorization Bug
- **Observation (`web_review/server.py:236`)**:
  ```python
  if "grand_feature_master" in lower:
  ```
  Files named using the Director's versioning guideline `<ep_id>_grand_master_v1.mp4` do not match `grand_feature_master`. Consequently, they are omitted from `episode_masters` and fallback into no category.
- **Resolution**:
  Update matching condition:
  ```python
  if "grand_feature_master" in lower or "grand_master" in lower:
  ```
  Extract and record `version_num` (`v_m = re.search(r"_v(\d+)\.mp4$", lower)`) into metadata for all exports and raw shots.

---

### 2.6 Enhanced Query Parameters for `/api/shots` and `/api/scenes`
- **Observation**:
  `GET /api/shots` currently dumps all 1149 prompts without filtering capabilities.
  `GET /api/scenes` only returns whatever MP4s exist in `04_Assets/videos`.
- **Resolution**:
  - `/api/shots`:
    ```python
    @app.get("/api/shots")
    async def list_shots_api(
        scene: Optional[str] = Query(None, description="Lọc theo Scene ID (vd: ep01_scene02)"),
        episode: Optional[str] = Query(None, description="Lọc theo Episode ID (vd: ep01)")
    ):
    ```
    - When called with no parameters, returns full dictionary (100% backward compatible).
    - When called with `?episode=ep01`, returns EP01's 188 shots.
    - When called with `?scene=ep01_scene05`, returns Scene 05's shots.
  - `/api/scenes`:
    ```python
    @app.get("/api/scenes")
    async def list_scenes_api(
        episode: Optional[str] = Query(None, description="Lọc theo Episode ID (vd: ep01)")
    ):
    ```
    - Filters scenes by episode prefix if specified.

---

### 2.7 Pipeline Module Path Resolution
- **Observation**:
  `web_review/server.py` does not include `05_Production_Pipeline` in `sys.path`.
- **Resolution**:
  Add `sys.path` injection at module load:
  ```python
  PIPELINE_DIR = BASE_DIR / "05_Production_Pipeline"
  for p in [str(PIPELINE_DIR), str(BASE_DIR)]:
      if p not in sys.path:
          sys.path.insert(0, p)
  ```

---

### 2.8 UI Integration (`web_review/templates/index.html`)
- **Additions**:
  1. Add "🎬 Ghép Master Cảnh" button to each scene accordion header in `renderRawScenes()`.
  2. Implement `triggerConcatScene(sceneId)` communicating with `POST /api/concat`.
  3. Update `pollStatus()` to handle status `'concatenating'` with distinct badge/styling.
  4. Track `window.currentSelectedShotId` in `usePrompt()` and include `shot_id` in `triggerCustomPrompt()`.
