Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다

시뮬레이터를 만드는 것은 반나절이면 됩니다. 어려운 것은 그 숫자를 회의에서 근거로 쓸 수 있게 만드는 일입니다. 이 프로젝트에서 판정 신뢰가 깨진 사고는 전부 시뮬레이터 바깥에서 났습니다 — 상태 플래그 하나를 근거로 삼았을 때였습니다.

모듈
Module 3
난이도
심화
학습 시간
120분
선행 학습
Module 1
PM/PO AI 업무 자동화 강의1 / 26
§1

학습 목표 (Learning Objectives)

이 모듈을 마치면 수강자는 다음을 할 수 있습니다.

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다2 / 26
§2

실무 배경 — 어떤 문제에서 출발했나

💡 이 모듈의 핵심 교훈: 시뮬레이션의 값어치는 정밀도가 아니라 반증 가능성에

있습니다. "어떤 결과가 나오면 이 숫자를 못 믿는가"를 먼저 적으세요.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다3 / 26
§2

실무 배경 — 어떤 문제에서 출발했나 (계속)

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다4 / 26
§3

3.1 프롬프팅 프레임워크 적용 — R-C-T-C-F

요소실제 입력값
Role데이터 기반 의사결정을 하는 PO. 숫자를 만드는 사람이 아니라 숫자를 의심하는 사람
Context분기당 기능 가설 20여 건, 과거 롤백률 34%. 강의 중 학습자가 자기 조직 숫자를 넣고 직접 돌린다
Task가설별 착수 여부 판정과 절감 공수 추정 파이프라인 구현
Constraints결정론(시드 고정) · 오탈락 비용을 결과에 항상 노출 · 판정 불가를 미완성과 구분 · 외부 스크립트 실행 기본 차단
Format지표 표 + 실패율 분포 히스토그램 + notes(한계와 주의사항)

Constraints 의 두 번째 줄이 결과를 갈랐습니다.

"오탈락 비용을 결과에 항상 노출한다"

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다5 / 26
§3

3.1 프롬프팅 프레임워크 적용 — R-C-T-C-F (계속)

이 문장이 없으면 시뮬레이터는 "검출률을 올리면 실패율이 내려간다"는 좋은 뉴스만 보여 줍니다. 좋은 뉴스만 보여 주는 도구는 의사결정을 돕는 게 아니라 이미 내린 결정을 정당화합니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다6 / 26
§3

3.2 아키텍처 — 몬테카를로 + 3값 판정

[입력: 기능 수 · 기존 실패율 · 검출률 · 오탈락률 · 기능당 공수 · 시행 횟수]
        │
        ▼
[시드 PRNG (mulberry32)]  ← Math.random() 금지. 화면과 교안의 숫자가 달라지면 안 됨
        │
        ▼
[몬테카를로 N회]  기능마다: 실패 가설인가? → 걸러졌는가?
        │
        ├─► 착수분의 실패율          (개선된 실패율)
        ├─► 걸러낸 실패 가설 × 공수  (절감)
        └─► 오탈락 × 공수            (손실)   ← 이걸 빼면 순이익이 부풀려짐
        │
        ▼
[결과]  지표 표 + 분포 10버킷 + notes(한계)
        │
        └─ 실패 시: 예외를 던지지 않고 MOCK 폴백 + fallbackReason 노출
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다7 / 26
§3

3.2 아키텍처 — 몬테카를로 + 3값 판정 (계속)

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다8 / 26
§4

구현 과정 (Step by Step)

단계작업소요산출물
1가설을 정량 지표·임계값으로 환산20분module-3-hypotheses.csv
2시드 PRNG 로 결정론 확보20분createRng()
3오탈락 비용을 지표·notes 양쪽에 노출30분runHypothesisPipeline()
4판정을 3값으로 설계30분resolvePracticeBindings()
5실패 폴백과 실행 경로 가드20분runSimulation() · resolveScript()
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다9 / 26
§4

4.1 결정론은 취향이 아니라 요구사항이다

강의 중에 같은 입력으로 두 번 돌렸는데 숫자가 다르면, 학습자는 그 도구를 다시 열지 않습니다. 그래서 Math.random() 을 쓰지 않고 시드 PRNG 를 씁니다.

/** mulberry32 — 32비트 시드 하나로 재현 가능한 균등분포를 냅니다. */
export function createRng(seed: number): () => number {
  let state = seed >>> 0;
  return () => {
    state = (state + 0x6d2b79f5) >>> 0;
    let t = state;
    t = Math.imul(t ^ (t >>> 15), t | 1);
    t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다10 / 26
§4

4.1 결정론은 취향이 아니라 요구사항이다 (계속)

결정론이 만든 부수 효과가 하나 더 있습니다. 시뮬레이터 설정의 목(mock) 출력을 손으로 적지 않고 파이프라인을 실제로 돌려서 만듭니다(prisma/seed.ts). 목 데이터가 실제 계산과 어긋나는 사고가 구조적으로 불가능해집니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다11 / 26
§4

4.2 오탈락 — 좋은 뉴스만 보여 주지 않기

한 판의 규칙은 두 줄입니다. 실패 가설은 검출률로, 성공 가설은 오탈락률로 걸러집니다.

const willFail = rng() < input.baselineFailureRate;
const rejected = rng() < (willFail ? input.screeningRecall : input.falseRejectRate);

검출률만 올리면 실패율은 계속 내려갑니다. 그래서 오탈락으로 날린 공수를 결과에 항상 함께 적습니다.

오탈락으로 날린 공수는 약 N man-day 입니다.
검출률만 올리고 오탈락을 안 보면 순이익이 과대평가됩니다.

이 문장이 사라지지 않도록 테스트로 고정했습니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다12 / 26
§4

4.2 오탈락 — 좋은 뉴스만 보여 주지 않기 (계속)

test('오탈락 비용을 주의사항에 반드시 적는다', () => {
  const output = runHypothesisPipeline(base, HYPOTHESIS_SCHEMA.seed);
  assert.ok(
    output.notes.some((note) => note.includes('오탈락')),
    '오탈락 비용을 숨기면 순이익이 과대평가됩니다',
  );
});

⚠️ 일반화: 지표의 좋은 쪽과 나쁜 쪽은 같은 화면에 있어야 합니다.

나쁜 쪽을 상세 페이지로 밀면 아무도 안 봅니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다13 / 26
§4

4.3 판정은 2값이 아니라 3값이다

Module 2 실습 바인딩(Tool A~D 완성 여부)을 판정하면서 배운 것인데, 시뮬레이션 결과 해석과 정확히 같은 구조입니다.

판정의미잘못 뭉개면
READY기준을 만족함—
PENDING기준을 만족하지 못함 (근거 있음)—
UNKNOWN조회 자체가 실패해 판정할 수 없음이미 완성됐던 도구가 되돌아간 것처럼 보임
if (jira.offline) {
  return { ...binding, status: 'UNKNOWN', doneCount: 0, totalCount: 0, jiraUrl, confluenceUrl,
    note: 'Atlassian 자격증명이 없어 완성 여부를 판정하지 못했습니다.' };
}
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다14 / 26
§4

4.3 판정은 2값이 아니라 3값이다 (계속)

시뮬레이션도 같습니다. 통과한 가설은 "성공한다"가 아니라 "지금 기각할 근거가 없다" 입니다. 세 번째 값을 없애고 2값으로 접으면, 모르는 것이 아는 것으로 둔갑합니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다15 / 26
§4

4.4 실패해도 던지지 않는다

강의 중에 시뮬레이터가 500 을 뱉는 것보다, "지금은 저장된 예시 결과를 보여주는 중"이라고 화면에 쓰는 편이 낫습니다. 단 왜 폴백했는지를 반드시 함께 적습니다.

return {
  /* … */
  source: 'MOCK',
  output: config.mockOutputData,
  fallbackReason:
    `'${config.scriptPath}' 는 내장 파이프라인이 아닙니다. ` +
    '외부 스크립트를 실제로 돌리려면 SIMULATOR_ALLOW_SCRIPT_EXEC=true 로 두고 scripts/ 아래에 두세요.',
};

이유 없는 폴백은 Module 1 §4.3 의 빈 템플릿과 같은 사고입니다 — 화면은 채워져 있고 내용은 가짜인데, 그 사실이 어디에도 안 적혀 있습니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다16 / 26
§4

4.5 시뮬레이터가 원격 코드 실행 통로가 되지 않게

관리자 화면에서 scriptPath 를 아무 값으로나 저장할 수 있다면, API 하나가 임의 파일 실행 경로가 됩니다. 내장 레지스트리만 실행하는 것이 기본값이고, 환경변수를 켠 경우에도 프로젝트 안 경로·허용 확장자만 통과합니다.

export function resolveScript(scriptPath: string): string | null {
  const root = path.join(process.cwd(), 'scripts');
  const resolved = path.resolve(root, scriptPath.replace(/^scripts[\\/]/, ''));
  if (!resolved.startsWith(`${root}${path.sep}`)) return null;
  if (!/\.(py|js|mjs)$/.test(resolved)) return null;
  return resolved;
}

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다17 / 26
§5

5.1 파이프라인 자체

지표BeforeAfter측정 근거
같은 입력 재현성보장 없음항상 동일tests/simulator.test.ts 결정론 3건
오탈락 비용 노출없음지표 + notes 양쪽테스트로 고정
실행 실패 시 화면500MOCK 폴백 + 이유 표기runSimulation()
임의 스크립트 실행가능화이트리스트 + 경로 가드실행 경로 가드 테스트 3건
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다18 / 26
§5

5.2 판정 신뢰 — 2026-08-10 실측에서 실제로 있었던 일

여기까지의 연동은 코드와 테스트로만 존재했습니다. 배포본에 자격증명을 넣고 실제 Atlassian 에 붙여 본 기록입니다(04 §9).

① offline: false 를 근거로 삼을 뻔했습니다.

응답에 offline: false 가 찍히면 연결된 것처럼 보입니다. 아닙니다 — 토큰이 틀려도 이 값은 false 로 나옵니다. 배너만 보고 "연동 완료"라고 판정하면 그 뒤의 모든 판정이 빈 결과 위에서 돌아갑니다.

판정 근거를 플래그에서 관측치로 바꿨습니다.

근거로 삼은 것신뢰 가능이유
offline: false❌자격증명 유효성과 무관하게 false
recentIssues 10건 · recentIssuesError: null✅실제로 읽어 온 데이터
LECTWEB-12 · LECTWEB-13 신규 생성✅실제로 쓴 결과
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다19 / 26
§5

5.2 판정 신뢰 — 2026-08-10 실측에서 실제로 있었던 일 (계속)

② Tool A~D 판정이 처음으로 진짜 값을 냈습니다.

자격증명이 없던 동안에는 네 도구가 전부 UNKNOWN 이었습니다.

도구판정완료
Tool APENDING0/1
Tool BREADY1/1
Tool CREADY1/1
Tool DREADY1/1

Tool A 만 PENDING 인 것은 버그가 아니라 진행 상황을 정확히 읽은 것입니다. 2값 판정이었다면 UNKNOWN 시절의 네 건이 전부 "미완성"으로 보였을 것이고, 자격증명을 넣은 순간 세 건이 갑자기 완성된 것처럼 보였을 겁니다.

확인 순서 — 다음에도 이 순서로 합니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다20 / 26
§5

5.2 판정 신뢰 — 2026-08-10 실측에서 실제로 있었던 일 (계속)

  1. INTEGRATION_DRY_RUN=true 인 채로 자격증명만 넣고 읽기부터
  2. recentIssues 로 실제 읽기 검증 — offline: false 만으로는 부족
  3. false 로 바꿔 쓰기 1건만
  4. 확인 후 즉시 true 로 복구

⚠️ 이 세션의 네트워크 정책이 your-site.atlassian.net 과 *.vercel.app 을 차단하므로

위 실측은 배포본에서 수행했고, 이 문서에서 재확인하지 않았습니다. 숫자는 2026-08-10

관측치입니다. 결정론·오탈락·폴백·경로 가드는 로컬 테스트가 계속 고정합니다.

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다21 / 26
§5

배운 것 / 다시 한다면 바꿀 것

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다22 / 26
§6

산출물 링크 (Deliverables)

구분URL
교안 PDF/슬라이드/downloads/module-3-simulation.pdf
슬라이드 (HTML)/downloads/module-3-simulation.html
실습용 데이터 세트/downloads/module-3-hypotheses.csv
시뮬레이터 실행/simulator
GitHub 저장소https://github.com/ursulax/action
파이프라인 구현lecture-web/src/lib/simulator-pipeline.ts
결정론·가드 테스트lecture-web/tests/simulator.test.ts
연동 실측 기록docs/4-product-system/04-product3-course-web.md
관련 PRhttps://github.com/ursulax/action/pull/45

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다23 / 26
§7

실습 과제 (Action Item)

  1. 자기 팀의 최근 1년 롤백률을 구하세요. 없으면 체감값을 쓰되 "체감값"이라고 적으세요 (Module 1 §4.4 — 출처 없는 숫자는 출처 없다고 적힌 채로 두는 것이 낫습니다).
  1. /simulator 에 넣고 검출률을 0.5 / 0.7 / 0.9 로 바꿔 절감 공수를 비교하세요. 그다음 오탈락률을 0.05 → 0.20 으로 올려 순이익이 뒤집히는 지점을 찾습니다.
  1. "이 숫자를 못 믿을 조건" 3개를 먼저 적고, 각각을 판정하는 방법을 옆에 쓰세요. ``` 못 믿을 조건 판정 방법
  2. 시드가 안 고정됐다 두 번 돌려 같은 값인지 본다
  3. 오탈락률이 0 이다 입력값을 직접 확인한다
  4. 표본이 과거 1분기뿐 기간을 늘려 재실행한다 ```
  1. 본인 시스템의 판정 로직에서 2값으로 접혀 있는 곳을 하나 찾아 3값으로 고치세요. "조회 실패"가 "실패"로 뭉개져 있는 자리가 대체로 있습니다.
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다24 / 26
§7

실습 과제 (Action Item) (계속)

제출물: 시뮬레이션 결과 + 못 믿을 조건 3개와 판정 방법 + 3값 판정 diff 자가 점검 기준:

---

MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다25 / 26
§8

Q&A / 자주 막히는 지점

증상원인해결
돌릴 때마다 숫자가 달라진다Math.random() 또는 시드 미고정시드 PRNG 로 교체. 두 번 돌려 같은지 테스트로 고정
절감 공수가 비현실적으로 크다오탈락률을 0 으로 뒀다실제 값을 넣고 순이익을 다시 본다
통과한 가설이 실패했다시뮬레이션은 채택을 보증하지 않는다통과 = "기각할 근거가 없다"로 읽는다
연동이 됐다는데 데이터가 안 보인다offline: false 를 근거로 삼았다실제 조회 결과 건수로 판정한다
자격증명을 넣으니 판정이 확 바뀐다조회 실패를 미완성으로 뭉갰다UNKNOWN 을 별도 값으로 둔다
시뮬레이터가 500 을 뱉는다실패 시 예외를 던진다목으로 폴백하되 이유를 화면에 적는다
목 데이터가 실제 계산과 다르다목을 손으로 적었다결정론 파이프라인을 실제로 돌려 생성한다
관리자 화면에서 아무 스크립트나 지정된다실행 경로 검증이 없다화이트리스트 + 프로젝트 내부 경로·확장자 가드
MOD3 · Module 3 — 가설 시뮬레이션: 숫자보다 먼저 '못 믿을 조건'을 정한다26 / 26