kobis-crawler-movie-sync.plan.md 23 KB

Plan: KOBIS Crawler ↔ Movie 사이트 동기화 오류 수정

Summary

movie 사이트(Laravel)와 crawler(Go/gin)가 KOBIS OpenAPI를 공유하는 구조에서, Laravel이 crawler에 전달하는 KOBIS 인증키 3개가 전부 무효화되어 박스오피스 신규 데이터 수집이 중단된 상태다(일별: 2026-03-12, 주간: 2026-06-07 이후 중단 — 로컬 DB 기준). crawler는 KOBIS 오류 응답(faultInfo)을 감지하지 못해 조용히 빈 목록을 반환하고, Laravel은 crawler 장애 시 500 에러가 난다. 본 계획은 키 동기화, 오류 감지/격리, 뷰 방어 처리, 수집 재가동을 다룬다.

User Story

As a movie 사이트 방문자, I want /movie/rank 에서 최신 일별·주간 박스오피스를 정상 조회하고 crawler 장애 시에도 안내를 받기를, So that 데이터 공백이나 500 에러 없이 서비스를 이용할 수 있다.

Problem → Solution

  • 현재: Laravel 상수의 KOBIS 키 3개 무효(라이브 검증: errorCode 320010) → crawler가 그대로 KOBIS에 전달 → faultInfo 응답 → crawler는 200 + 빈 목록 반환 → 사이트는 원인 표시 없이 빈 페이지. crawler 다운 시 Http::get ConnectionException 미처리로 500.
  • 해결: 유효 키로 동기화(양쪽), crawler에 faultInfo 감지 추가, Laravel에 타임아웃+예외 격리, 뷰 null 방어, cron 수집 재가동.

Metadata

  • Complexity: Medium (2개 리포지토리, ~12 files, 신규 개념 없음)
  • Source PRD: N/A (free-form)
  • PRD Phase: N/A
  • Estimated Files: movie 7 + crawler 4 + docs 1

검증된 사실 (2026-07-22 라이브 테스트)

항목 결과
Laravel KOBIS_API_1 d470c8a1... ❌ 무효 (320010 유효하지않은 키값입니다)
Laravel KOBIS_API_2 52c8406a... ❌ 무효
Laravel KOBIS_API_3 f5eef342... ❌ 무효 (crawler cron.go GetKey() 기본 fallback으로 하드코딩되어 있음)
crawler movie.json apiKey_1 b453d855... 유효 (박스오피스 데이터 정상 반환)
crawler movie.json apiKey_2 cf7ea46f... 유효
crawler movie.json apiKey_3 82ca741a... ❌ 무효 — KOBIS 공식 문서의 샘플 키가 그대로 들어있음
KOBIS REST 응답 구조 문서(E:\server\project\crawler\docs\KOBIS-영화API.md)와 일치 — API 자체는 변경 없음
KOBIS 상세페이지 스크래핑 셀렉터 (item_tab basic, desc_info, fl thumb, #post, #stl) ✅ 여전히 유효
TMDB 키 (bfeb1c9a...) ✅ 유효 (메인/트레일러 페이지 정상)
crawler 서버 dev 192.168.0.10:1050 / prod 192.168.0.100:1050 ⚠️ 현 개발 PC에서 응답 없음 (구동 여부 확인 필요)
DB 데이터 신선도 tb_movie_daily 최종 2026-03-12, tb_movie_weekly 2026-06-07, tb_movie/tb_movie_info 2026-03-12
tb_movie_detail sale_acc/audi_acc 80,617행 중 ~15,000행만 채워짐 — crawler의 통계 스크래퍼(c2)가 주석 처리된 상태
DB 스키마 ↔ crawler positional SELECT * 스캔 ✅ 컬럼 순서 정확히 일치 (tb_movie 16, tb_movie_daily 23, tb_movie_weekly 24, tb_movie_info 20, tb_movie_detail 11 컬럼)

영화사(Company) 페이지 확인 결과

  • KOBIS 문서에는 영화사 목록/상세(searchCompanyList/searchCompanyInfo), 영화인, 공통코드 API가 정리되어 있으나 crawler는 이 중 어느 것도 구현하지 않음 (구현: 일별/주간 박스오피스, 영화 목록, 영화 상세 4종 + 상세페이지 스크래핑).
  • movie 사이트에도 영화사 전용 라우트/페이지 없음. 회사 정보는 영화 상세 뷰의 "제작/수입/배급사" 섹션에서 info->Companys JSON으로만 노출됨 (show.blade.php:270-286).
  • → 신규 기능이므로 본 계획의 범위에서 제외(NOT Building 참조).

UX Design

Before

/movie/rank?date=2026-07-21
┌────────────────────────────────┐
│ 인기 순위                       │
│ (빈 화면 — 무효 키로 인해       │
│  목록 없음, 원인 표시 없음)     │
│ crawler 다운 시: 500 에러 페이지 │
└────────────────────────────────┘

After

/movie/rank?date=2026-07-21
┌────────────────────────────────┐
│ 인기 순위                       │
│ [1위 포스터][2위 포스터]...     │  ← 유효 키로 on-demand 수집 재개
│ crawler 장애 시:                │
│ "일시적으로 순위 정보를 불러올  │
│  수 없습니다" 안내 (200 응답)   │
└────────────────────────────────┘

Interaction Changes

Touchpoint Before After Notes
/movie/rank (미수집 날짜) 빈 목록 데이터 표시 키 교체로 해결
/movie/rank (crawler 다운) 500 안내 문구 timeout+catch
/movie/search/{id} 누적 매출/관객 0원/0명 표시 값 없으면 행 숨김 sale_acc 대부분 NULL
/movie/rank/{id} 등 상세 동일 동일 변경 없음

Mandatory Reading

Priority File Lines Why
P0 E:\server\project\movie\config\constants.php 297-309 무효 키 3개 + crawler 엔드포인트 상수
P0 E:\server\project\crawler\config\movie.json all 유효 키 1,2 / 무효 샘플 키 3
P0 E:\server\project\crawler\controller\cron.go 492-500 GetKey(): 무효 키 하드코딩 fallback
P0 E:\server\project\crawler\model\kobis.go 14-36 KOBIS 호출 + faultInfo 미검사 지점
P0 E:\server\project\crawler\controller\movie.go 73-77, 171-176, 251-254 c.JSON(400)return 누락 3곳
P0 E:\server\project\movie\app\Http\Controllers\Movie\RankController.php 90-119 Http::get 미보호 + $data->total 처리
P1 E:\server\project\movie\resources\views\desktop\movie\rank\index.blade.php 30 $data->rows null 미방어 (mobile 동일 파일 존재)
P1 E:\server\project\movie\resources\views\desktop\movie\search\show.blade.php 107-126 SaleAcc/AudiAcc 0 표시 문제 (mobile 동일)
P1 E:\server\project\movie\app\Models\DTO\Movie\BoxOfficeParams.php 10-21 key 주입 지점 (KOBIS_API_1)
P2 E:\server\project\crawler\model\movieDaily.go 21-58 요청/응답 구조체 패턴
P2 E:\server\project\movie\app\Http\Controllers\Movie\SearchController.php 43, 117-169 ListParams 죽은 주입 + show()의 try/catch 패턴

External Documentation

Topic Source Key Takeaway
KOBIS OpenAPI 명세 E:\server\project\crawler\docs\KOBIS-영화API.md 9종 API. 응답 구조 현행과 일치. 키는 발급 필요
KOBIS 키 발급 https://www.kobis.or.kr/kobisopenapi (키 발급/관리 메뉴) 무효 키 3개 대체분 발급 필요 시 사용자 계정 필요
오류 응답 형식 라이브 검증 {"faultInfo":{"message":"...","errorCode":"320010"}} — HTTP 200으로 반환됨

Patterns to Mirror

GO_ERROR_HANDLING (crawler)

// SOURCE: E:\server\project\crawler\model\kobis.go:25-33
data, err := this.Rest.CallRestGetAPI(url)
if this.Rest.Check(err) {   // Check()가 error.txt 로그 기록 후 bool 반환
    return result, err
}

err = json.Unmarshal(data, &result)
if this.Rest.Check(err) {
    return result, err
}

GO_LOGGING (crawler DB 로그)

// SOURCE: E:\server\project\crawler\model\movieDaily.go:96-103
err := conn.QueryRow(query).Scan(&total)
if err != nil {
    db.SetErrorLog(err, query)
    return total
}
db.SetGeneralLog(config.GL_ACTION_SELECT, query, "select total movie daily")

PHP_CONTROLLER_ERROR_HANDLING (movie)

// SOURCE: E:\server\project\movie\app\Http\Controllers\Movie\RankController.php:129-186
try {
    ...
    $response = Http::get($url, $params->toArray());
    if(!$response->ok()) {
        throw new Exception('요청한 정보가 없습니다.');
    }
    ...
}catch(Exception $e) {
    abort($e->getCode(), $e->getMessage()); // 주의: getCode()=0 → invalid status. ?: 500 으로 보정할 것
}

PHP_DTO_PARAMS (movie)

// SOURCE: E:\server\project\movie\app\Models\DTO\Movie\BoxOfficeParams.php:10-21
public function __construct(
    public string $targetDt = "",
    ...
) {
    $this->key = KOBIS_API_1;   // ← 키 주입 지점. 상수 교체만으로 반영됨
    $this->weekGb = "0";
    $this->itemPerPage = "10";
}

BLADE_JSON_FIELD (movie 뷰 — crawler Go 구조체는 JSON 태그가 없어 PascalCase로 직렬화됨)

{{-- SOURCE: E:\server\project\movie\resources\views\desktop\movie\rank\show.blade.php:50-58 --}}
@if($info->Directors)
    @foreach(json_decode($info->Directors) as $director)
        {{ $director->peopleNm }}
    @endforeach
@endif

GO_GIN_BINDING (crawler 요청 파라미터)

// SOURCE: E:\server\project\crawler\model\movieDaily.go:21-28
type SearchDailyBoxOfficeParams struct {
    Key      string `form:"key" url:"key" binding:"required"`
    TargetDt string `form:"targetDt" url:"targetDt" binding:"required"`
    ...
}

Files to Change

File Action Justification
movie/config/constants.php UPDATE KOBIS_API_1/2/3 → 유효 키로 교체
crawler/config/movie.json UPDATE apiKey_3 샘플 키 → 유효 키로 교체
crawler/controller/cron.go UPDATE GetKey() fallback을 config 참조로, key=3 지원
crawler/model/kobis.go UPDATE faultInfo 감지 → error 반환 (4개 API 함수)
crawler/controller/movie.go UPDATE c.JSON(400) 후 return 누락 3곳 수정
movie/app/Http/Controllers/Movie/RankController.php UPDATE Http timeout+catch, abort 코드 보정
movie/app/Http/Controllers/Movie/SearchController.php UPDATE show() Http timeout, 죽은 ListParams 주입 제거
movie/resources/views/desktop/movie/rank/index.blade.php UPDATE $data null 방어 + 장애 안내
movie/resources/views/mobile/movie/rank/index.blade.php UPDATE 동일
movie/resources/views/desktop/movie/search/show.blade.php UPDATE SaleAcc/AudiAcc null 시 행 숨김
movie/resources/views/mobile/movie/search/show.blade.php UPDATE 동일
crawler/docs/KOBIS-영화API.md UPDATE 구현 현황 매트릭스 + 키 운영 정책 추가

NOT Building

  • 영화사(company) 목록/상세 페이지 및 crawler의 searchCompanyList/searchCompanyInfo 프록시 — 신규 기능, 별도 PRD로
  • 영화인(people)/공통코드(code) API 연동
  • KCISA 박스오피스 통계(getKFCC0502, 주석 처리된 /cron/stats) 부활
  • tbmovie* 스키마 변경 (현재 정합함)
  • weekGb 정책 변경 (현행 0=주간 유지 — 주말 진입 시 주간 순위를 보여주는 현행 동작은 의도로 간주)
  • crawler c2(누적 매출/관객 스크래퍼) 재활성화 — 후속 과제로 기록만 (Task 6 GOTCHA 참조)

Step-by-Step Tasks

Task 1: KOBIS 키 동기화 (양쪽 리포)

  • ACTION: 유효 키로 양쪽 통일. 검증된 유효 키: crawler movie.json의 apiKey_1, apiKey_2. 3번째 키는 KOBIS에서 신규 발급(사용자 계정 필요) 전까지 key_1 재사용.
  • IMPLEMENT: movie/config/constants.php:300-302 KOBIS_API_1/2/3 값 교체. crawler/config/movie.json apiKey_3 교체.
  • MIRROR: 기존 상수 선언 형식 유지 (const KOBIS_API_1 = '...';)
  • IMPORTS: 없음
  • GOTCHA: crawler는 Laravel이 넘긴 key 파라미터를 그대로 KOBIS에 전달하므로 Laravel 상수 교체가 실질적 수정점. crawler의 movie.json은 cron 수집 전용. 배포 서버의 movie.json은 별도 반영 필요(git 관리 여부 확인).
  • VALIDATE: curl "http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=<새 KOBIS_API_1>&targetDt=<어제>"boxOfficeResult 반환 확인.

Task 2: crawler GetKey() fallback 수정

  • ACTION: crawler/controller/cron.go:492-500의 하드코딩 무효 키 제거.
  • IMPLEMENT:

    func GetKey(c *gin.Context) string {
    switch c.Query("key") {
    case "1":
        return config.Movie.Kobis.ApiKey_1
    case "2":
        return config.Movie.Kobis.ApiKey_2
    default:
        return config.Movie.Kobis.ApiKey_3
    }
    }
    
  • MIRROR: 기존 config.Movie.Kobis 접근 패턴

  • IMPORTS: 기존 crawler/config 이미 임포트됨

  • GOTCHA: apiKey_3가 Task 1에서 유효 키로 교체된 후에만 의미 있음.

  • VALIDATE: go build ./... 통과.

Task 3: crawler faultInfo 감지

  • ACTION: crawler/model/kobis.go의 4개 API 함수(MovieDailyBoxOfficeListAPI, MovieWeeklyBoxOfficeListAPI, MovieListAPI, MovieInfoAPI)에서 KOBIS 오류 응답을 error로 승격.
  • IMPLEMENT: 파일 상단에 공용 타입/헬퍼 추가 후 각 함수의 json.Unmarshal 직전에 호출:

    type KobisFault struct {
    FaultInfo *struct {
        Message   string `json:"message"`
        ErrorCode string `json:"errorCode"`
    } `json:"faultInfo"`
    }
    
    func (this *Kobis) checkFault(data []byte) error {
    var fault KobisFault
    if json.Unmarshal(data, &fault) == nil && fault.FaultInfo != nil {
        err := fmt.Errorf("KOBIS fault %s: %s", fault.FaultInfo.ErrorCode, fault.FaultInfo.Message)
        this.Rest.Check(err) // error.txt 기록
        return err
    }
    return nil
    }
    
  • MIRROR: GO_ERROR_HANDLING 패턴 (if this.Rest.Check(err) { return result, err })

  • IMPORTS: fmt 추가

  • GOTCHA: KOBIS는 오류도 HTTP 200으로 반환하므로 상태코드 검사로는 잡을 수 없음. 반드시 body 검사.

  • VALIDATE: 무효 키로 crawler 엔드포인트 호출 시 200+빈 목록 대신 400+오류 메시지 반환.

Task 4: crawler 컨트롤러 return 누락 수정

  • ACTION: crawler/controller/movie.go에서 KOBIS API 오류 시 c.JSON(http.StatusBadRequest, ...) 후 실행이 계속되는 3곳 수정.
  • IMPLEMENT: 75-77행(SearchDailyBoxOfficeList), 174-176행(SearchWeeklyBoxOfficeList), 252-254행(SearchMovieList)에 return 추가.
  • MIRROR: 같은 파일 내 다른 오류 분기들(c.JSON(...); return)
  • GOTCHA: return 누락 시 gin이 이중 응답 기록을 시도하고, 오류인데도 빈 데이터로 후속 처리 진행됨.
  • VALIDATE: go vet ./... + 무효 키 호출 시 단일 400 응답.

Task 5: Laravel crawler 장애 격리

  • ACTION: RankController(index/show), SearchController(show)의 Http::get에 타임아웃과 예외 처리 추가, abort 코드 보정, 뷰 null 방어.
  • IMPLEMENT:
    • Http::timeout(5)->get($url, $params->toArray()) 로 교체, index()는 try { ... } catch (ConnectionException) { $data = null; }
    • show()들의 catch 블록: abort($e->getCode() ?: 500, $e->getMessage());
    • desktop/movie/rank/index.blade.php:30 및 mobile 동일 파일: @if($data && $data->rows > 0) + @else 분기에 "일시적으로 순위 정보를 불러올 수 없습니다." 안내 (기존 빈 목록 안내 마크업이 있으면 재사용)
  • MIRROR: PHP_CONTROLLER_ERROR_HANDLING 패턴 (SearchController::show의 try/catch 구조)
  • IMPORTS: use Illuminate\Http\Client\ConnectionException;
  • GOTCHA: crawler 응답은 rows=0이어도 total(테이블 전체 COUNT)이 0보다 클 수 있음 — 목록 유무 판단은 rows/list 기준으로.
  • VALIDATE: crawler 미기동 상태에서 /movie/rank 접속 → 500이 아닌 안내 문구 페이지.

Task 6: 누적 매출/관객 표시 방어 (search/show)

  • ACTION: tb_movie_detail의 sale_acc/audi_acc가 NULL인 80% 케이스에서 "0원/0명" 대신 행 숨김.
  • IMPLEMENT: desktop/movie/search/show.blade.php 및 mobile 동일 파일의 누적 매출액/관객수 <tr>@if($detail->SaleAcc) / @if($detail->AudiAcc)로 감싸기. number_format(null) 경고 제거를 위해 ?? 0을 인자 안쪽으로(number_format($detail->SaleAcc ?? 0)).
  • MIRROR: 같은 뷰의 @if($detail->Poster) 조건부 섹션 패턴
  • GOTCHA: 근본 원인은 crawler의 c2 통계 스크래퍼(searchMovieDtl.do sType=stat) 주석 처리. 재활성화는 셀렉터 재검증이 필요한 별도 작업 — cron.go:324-335, movie.go:524-537 주석 참조. 후속 과제로 남김.
  • VALIDATE: sale_acc NULL인 영화 상세 페이지에서 해당 행 미표시.

Task 7: SearchController 죽은 코드 정리

  • ACTION: SearchController::index(Request $request, ListParams $params)$params가 84행에서 SearchData::fromRequest로 즉시 덮어써짐 — 시그니처에서 ListParams 제거. 미사용 상수 MOVIE_LIST(constants.php:306)에 // unused: 검색 페이지는 DB 직조회 주석 또는 제거.
  • MIRROR: RankController 시그니처 스타일
  • GOTCHA: use App\Models\DTO\Movie\ListParams; 임포트도 함께 정리. MOVIE_LIST 상수는 crawler 라우트에는 존재하므로 문서화 후 유지해도 무방.
  • VALIDATE: php -l 통과, /movie/search 정상 렌더.

Task 8: cron 수집 재가동 + 운영 문서화

  • ACTION: crawler 서버(dev 192.168.0.10:1050, prod 192.168.0.100:1050) 기동 확인 후, 스케줄 트리거를 문서화. 두 서버 모두 현재 이 개발 PC에서 /ping 무응답이었음.
  • IMPLEMENT: crawler/docs/KOBIS-영화API.md(또는 신규 docs/OPERATIONS.md)에 다음을 기록:
    • 일별 수집: GET /cron/list?key=1GET /cron/info?key=2GET /cron/detail (마지막 페이지는 log/kobis/page.txt에 저장됨)
    • 박스오피스는 사이트 조회 시 on-demand 수집이므로 별도 백필 불필요 (키만 유효하면 날짜별 첫 조회 때 채워짐)
    • 키 소유/발급 정책: 유효 키 목록과 관리 주체
    • 구현 현황 매트릭스: 9종 중 4종 구현(daily/weekly/movieList/movieInfo), company/people/code 미구현
  • VALIDATE: crawler 기동 후 curl http://<host>:1050/pingpong, curl http://<host>:1050/db → ms 응답.

Testing Strategy

Unit / Integration Tests

Test Input Expected Output Edge Case?
crawler faultInfo 감지 무효 키로 /movie/searchDailyBoxOfficeList HTTP 400 + KOBIS fault 320010...
crawler 정상 수집 유효 키 + 어제 날짜 200 + rows=10, DB upsert
Laravel rank (crawler down) crawler 중지 후 /movie/rank 200 + 안내 문구
Laravel rank (정상) 유효 키 + 오늘 순위 목록 렌더
Laravel search/show (sale_acc NULL) 해당 movieCd 누적 행 미표시
rank/show 상세 dailyID base64 stats/info/detail 렌더

Edge Cases Checklist

  • KOBIS가 200 + faultInfo 반환 (무효 키/쿼터 초과)
  • crawler 무응답(타임아웃)
  • 조회 날짜에 상영작 없음 (미래 날짜)
  • Detail이 null인 daily row ($row->Detail->MainImg — rank/index:36, null 가드 확인)
  • 주말 날짜 조회 (weekly 경로, weekGb=0)
  • base64 변조된 rank/show id

Validation Commands

Static Analysis

# crawler (Go)
cd E:\server\project\crawler; go build ./...; go vet ./...

EXPECT: 빌드/벳 무오류

# movie (PHP) — 수정 파일 문법 검사
cd E:\server\project\movie
php -l app/Http/Controllers/Movie/RankController.php
php -l app/Http/Controllers/Movie/SearchController.php
php -l config/constants.php
php artisan config:clear

EXPECT: No syntax errors

Live API Validation

# 1. 새 키 유효성
curl "http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=<KOBIS_API_1>&targetDt=<어제 yyyymmdd>"
# EXPECT: {"boxOfficeResult":{...}} (faultInfo 아님)

# 2. crawler 경유 (crawler 기동 후)
curl "http://localhost:1050/movie/searchDailyBoxOfficeList?key=<KOBIS_API_1>&targetDt=<어제>"
# EXPECT: {"total":N,"rows":10,"list":[...]}

# 3. 무효 키 → 오류 승격 확인
curl "http://localhost:1050/movie/searchDailyBoxOfficeList?key=invalid&targetDt=<어제>"
# EXPECT: HTTP 400 + KOBIS fault 320010

Database Validation

-- 수집 재개 확인 (rank 페이지 1회 조회 후)
SELECT MAX(show_range) FROM movie.tb_movie_daily;   -- 어제 날짜 포함
SELECT MAX(created_at) FROM movie.tb_movie_daily;   -- 오늘 타임스탬프

Browser Validation

  • /movie/rank — 오늘/어제 데이터 표시
  • /movie/rank?date=<주말 날짜> — weekly 경로 정상
  • crawler 중지 후 /movie/rank — 500 아닌 안내 문구
  • /movie/search → 상세 — 누적 매출/관객 NULL 행 숨김
  • /movie/rank/{id} 상세 — 감독/장르/배우/제작사 JSON 필드 렌더

Acceptance Criteria

  • 양쪽 리포의 KOBIS 키가 동일한 유효 키 세트로 통일
  • crawler가 KOBIS faultInfo를 400으로 승격 + error.txt 기록
  • crawler 오류 분기 3곳 return 수정
  • Laravel rank 페이지가 crawler 장애 시 200 + 안내
  • tb_movie_daily에 최신 날짜 데이터 적재 확인
  • go build/go vet/php -l 무오류

Completion Checklist

  • 코드가 발견된 패턴(Rest.Check, SetErrorLog, try/catch+abort)을 따름
  • 하드코딩 키가 코드(cron.go)에 남아있지 않음
  • mobile 뷰도 desktop과 동일하게 수정됨
  • crawler docs에 구현 현황/키 정책 반영
  • 범위 외 작업(영화사 페이지 등) 미포함

Risks

Risk Likelihood Impact Mitigation
유효 키 2개도 소유자 불명/추후 만료 높음 KOBIS 계정으로 신규 발급, 발급 주체 문서화
crawler 서버 접근 불가(현재 dev/prod 무응답) 높음 서버 기동 상태 우선 확인, 로컬 기동으로 검증 대체
배포 서버의 movie.json이 git 밖 관리 배포 절차에 config 반영 단계 명시
KOBIS 일 호출 쿼터 초과 cron 수집 시 key 파라미터 분산(기존 GetKey 설계 유지)
daily/weekly 과거 공백(3~7월) 미백필 확실 on-demand 구조라 사용자 조회 시 자동 채움 — 별도 조치 불필요

Notes

  • KOBIS API 자체는 변경되지 않음 — 문서와 응답 구조 일치(라이브 검증). "API가 많이 변경됐을 것"이라는 가설의 실체는 키 무효화였음.
  • crawler Go 구조체(MovieDailyTable 등)는 JSON 태그가 없어 PascalCase로 직렬화되며, Laravel 컨트롤러/뷰가 이에 의존($row->MovieCd, $detail->MainImg). crawler 구조체에 json 태그를 추가하면 사이트가 즉시 깨지므로 금지.
  • crawler SELECT * positional scan ↔ DB 컬럼 순서 의존 — 스키마 변경 시 양쪽 동시 수정 필요 (현재는 정합).
  • 영화사/영화인/공통코드 API는 미구현 — 필요 시 별도 PRD(/prp-prd)로 진행.
  • .env/.env.production의 미커밋 변경(크리덴셜 → 환경변수 치환)은 본 건과 무관한 진행 중 작업으로 보임 — 건드리지 않음.