마드라스체크(플로우)
경력근거: 고객 데이터·권한 구조에 맞춘 Agent 품질 튜닝
※ 실제 산출물이 아닌 AI 예측 시안입니다.
마드라스체크의 repattern AI 는 협업툴 flow 위에서 동작하고, FDE 의 일 중 40%가 기존 flow 고객사 환경에 그것을 적용하며 고객 데이터·권한 구조에 맞춰 Agent 품질을 다듬는 일입니다. 이 제품의 어려운 자리는 답변 문장이 아니라 「이 사람이 볼 수 있는 기록은 어디까지인가」이고, 그 판정은 flow 처럼 프로젝트·카드 단위로 열람 범위가 갈리는 협업툴 데이터가 있어야 만들어집니다. 다만 이 포트폴리오는 권한을 먼저 좁히는 검색과 근거 지목 구조를 보여주는 것이고, 협업툴 운영이나 조직 업무 설계의 도메인 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
업무의 40%가 기존 flow 고객사 환경에 repattern AI 를 적용하고 고객 데이터·권한 구조에 맞춰 Agent 품질을 다듬는 일이라, 권한을 먼저 좁히고 답하는 구조가 직무의 한가운데입니다.
repattern AI 를 NestJS 백엔드와 Next.js 프론트엔드로 만든다고 못 박혀 있고 Node.js·TypeScript 경력을 자격요건으로 두어, 두 축을 이어 붙인 결과물이 서류에서 바로 읽힙니다.
flow Data API 와 MCP 서버 개발이 도메인 오너십으로 배정되고 검색 색인·랭킹 튜닝이 우대사항이라, 검색으로 근거를 좁히고 도구로 노출하는 층이 그대로 겹칩니다.
협업툴에서 내려받은 업무 카드와 댓글을 받아, 진행 상황 질문에 근거가 된 카드를 지목해 답하는 관리자 페이지입니다. 묻는 사람이 볼 권한이 있는 기록만 근거 후보로 남기고, 근거가 모자란 질문은 답을 지어내지 않고 무엇이 모자란지 적습니다.
주간회의 전날마다 담당자들에게 「그 건 어떻게 됐어요」를 스무 번씩 묻던 프로젝트 담당자입니다. 답은 이미 카드와 댓글에 있는데 프로젝트 이름을 사람마다 다르게 적어 검색이 안 걸리고, 자기가 못 보는 비공개 카드가 섞여 있어 검색 결과를 그대로 믿기도 어렵습니다.
무엇을 답하느냐보다 어느 기록을 근거로 쓸 수 있느냐가 먼저입니다. 묻는 사람의 열람 권한과 프로젝트 표기를 규칙으로 먼저 좁히면 남은 기록만으로 답과 근거가 결정되고, 모델은 그 근거가 왜 답이 되는지를 한 문장으로 요약하는 자리만 맡습니다.
카드와 댓글을 올려 진행 상황을 묻고, 근거가 된 카드와 댓글을 지목한 답을 받는 화면입니다. 권한 밖 카드는 근거에서 빠지고 그 사실이 답에 남습니다.
정답 근거를 붙인 질문 24개로 근거 카드 지목 일치를 자동으로 재는 평가 러너와, 답이 갈린 질문만 모아 보는 화면입니다.
검색과 답변을 MCP 도구 서버로 노출해 다른 에이전트가 같은 권한 규칙으로 부르게 하고, OpenSearch 색인으로 카드가 늘어도 근거를 찾아내는 형상입니다.
프로젝트 담당자가 업무 기록에 질문을 던지면 근거가 된 카드와 댓글을 지목한 답이 나옵니다. 카드 15건과 댓글 12건에서 질문 24개를 돌려 답과 근거를 한 화면에서 읽습니다.
프로젝트 이름 표기가 흔들린 카드와 마감일이 비어 있는 카드를 일부러 섞은 더미에서도 응답 형식이 그대로 유지됩니다. 표기 정규화가 성공해 같은 프로젝트로 붙은 카드는 한 답에 함께 인용됩니다.
묻는 사람이 볼 권한이 없는 카드는 근거에서 빠지고, 그 카드 때문에 답을 못 냈다는 사실이 답변에 남습니다. 카드 목록에 없는 카드번호를 가리키는 댓글은 미매핑으로 표시돼 근거에서 제외됩니다.
정답 근거를 붙인 질문 24개로 근거 카드 지목 일치를 냅니다. 단, 정답도 데이터도 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 답변 정확도는 고객사의 실 워크스페이스와 실제 권한 설정, 그리고 실제 담당자가 있어야 측정됩니다. 이 포트폴리오는 그 측정을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실측 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 마감일이 비어 있는 카드, 프로젝트 표기가 흔들린 카드, 볼 권한이 없는 카드, 카드 목록에 없는 댓글을 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
이 더미의 열람 권한은 카드마다 공개와 비공개 두 값뿐입니다. 실제 flow 는 조직·프로젝트·게스트가 겹쳐 훨씬 복잡하므로, 권한 판정을 규칙 파일로 빼 두고 고객사 모델로 갈아끼우는 것은 도입 단계 과제로 분리합니다.
근거가 붙었다는 사실이 곧 답이 최신이라는 뜻은 아닙니다. 두 달 전 댓글로도 그럴듯한 답이 나오므로 근거마다 작성일을 함께 보여 주고, 최근 기록이 없으면 답변에 그렇게 적습니다.
이 포트폴리오는 권한을 먼저 좁히는 검색과 근거 지목 구조를 보여줍니다. 협업툴 운영이나 조직 업무 설계의 도메인 깊이는 주장하지 않습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
협업툴에서 내려받은 업무 카드 목록입니다. 근거의 왼쪽 축이고, 프로젝트 이름을 사람마다 다르게 적은 카드와 마감일이 아직 비어 있는 카드, 비공개로 잠긴 카드가 섞여 있습니다.
| card_no | project_raw | project_id | title | status | assignee_raw | due_date | updated_at | visibility |
|---|---|---|---|---|---|---|---|---|
| FLW-3101 | 플로우 2.0 개편 | PRJ-01 | 댓글 멘션 알림 지연 수정 | 완료 | 김도현 | 2026-03-04 | 2026-03-04 | team |
| FLW-3102 | 플로우 2.0 개편 | PRJ-01 | 카드 이동 시 권한 재계산 | 진행 | 이수민 | 2026-03-13 | 2026-03-11 | team |
| FLW-3103 | 플로우2.0 개편 | PRJ-01 | 검색 색인 재작성 | 보류 | 김도현 | — | 2026-03-10 | team |
| FLW-3104 | 고객사 온보딩 | PRJ-02 | A사 워크스페이스 이관 리허설 | 진행 | 박세영 | 2026-03-18 | 2026-03-12 | team |
| FLW-3105 | 고객사 온보딩 | PRJ-02 | A사 권한 그룹 매핑표 확정 | 완료 | 박세영 | 2026-03-09 | 2026-03-09 | team |
아래 JSON Schema 에 정확히 맞는 협업툴 업무 카드 20건을 JSON 배열로 생성해 주세요. {"type":"object","properties":{"card_no":{"type":"string"},"project_raw":{"type":"string"},"project_id":{"type":["string","null"]},"title":{"type":"string"},"status":{"type":"string","enum":["진행","완료","보류"]},"assignee_raw":{"type":"string"},"due_date":{"type":["string","null"]},"updated_at":{"type":"string"},"visibility":{"type":"string","enum":["team","private"]}},"required":["card_no","project_raw","title","status","assignee_raw","updated_at","visibility"]} 조건: card_no 는 FLW-31NN 형식으로 서로 다르게 쓰고, 프로젝트는 네 개 안에서 고릅니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: 같은 프로젝트를 '플로우 2.0 개편' 과 '플로우2.0 개편' 처럼 다르게 적은 카드 2건, due_date 가 null 인 카드 2건, project_id 가 null 인 미등록 프로젝트 카드 1건, visibility 가 private 인 카드 1건. 형식을 깨뜨리는 시도(status 칸에 '진행중' 입력, 날짜를 '3/13' 으로 적기, 머리글 행이 중간에 다시 나오는 CSV)는 데이터가 아니라 별도 3건의 파일로 만들어 읽기 계층을 시험합니다. JSON 배열만 출력해 주세요.{"type":"object","properties":{"card_no":{"type":"string"},"project_raw":{"type":"string"},"project_id":{"type":["string","null"]},"title":{"type":"string"},"status":{"type":"string","enum":["진행","완료","보류"]},"assignee_raw":{"type":"string"},"due_date":{"type":["string","null"]},"updated_at":{"type":"string"},"visibility":{"type":"string","enum":["team","private"]}},"required":["card_no","project_raw","title","status","assignee_raw","updated_at","visibility"]}업무 카드에 달린 댓글입니다. 진행이 왜 밀렸는지 같은 답은 대개 여기 있고, 카드 목록에 없는 카드번호를 가리키는 댓글이 섞여 있어 미매핑 처리를 시험합니다.
| comment_no | card_no_raw | author_raw | body | created_at |
|---|---|---|---|---|
| CMT-9001 | FLW-3102 | 이수민 | 권한 재계산 로직은 붙였고 캐시 무효화만 남았습니다. | 2026-03-11 |
| CMT-9002 | FLW-3103 | 김도현 | 색인 재작성은 2.0 개편 배포 뒤로 미룹니다. | 2026-03-10 |
| CMT-9003 | FLW-3104 | 박세영 | 리허설 일정이 A사 사정으로 한 주 밀렸습니다. | 2026-03-12 |
| CMT-9004 | FLW-3104 | 정하늘 | 이관 대상 워크스페이스는 세 개로 확정했습니다. | 2026-03-12 |
| CMT-9005 | FLW-3107 | 이수민 | 배치 스케줄은 새벽 두 시로 옮기기로 했습니다. | 2026-03-13 |
아래 JSON Schema 에 정확히 맞는 업무 카드 댓글 16건을 JSON 배열로 생성해 주세요. {"type":"object","properties":{"comment_no":{"type":"string"},"card_no_raw":{"type":"string"},"author_raw":{"type":"string"},"body":{"type":"string"},"created_at":{"type":"string"}},"required":["comment_no","card_no_raw","author_raw","body","created_at"]} 조건: card_no_raw 는 업무 카드의 card_no 를 옮겨 적은 값이고, body 는 진행 상황이나 막힌 이유를 한두 문장으로 적은 실무 말투입니다. 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: 카드 목록에 없는 card_no_raw 를 가리키는 댓글 1건, 비공개 카드에 달린 댓글 1건, 답이 안 되는 잡담 댓글 2건. 형식을 깨뜨리는 시도(본문에 줄바꿈과 인용부호 섞기, created_at 을 '어제' 로 적기)는 데이터가 아니라 별도 2건의 파일로 만들어 읽기 계층을 시험합니다. JSON 배열만 출력해 주세요.{"type":"object","properties":{"comment_no":{"type":"string"},"card_no_raw":{"type":"string"},"author_raw":{"type":"string"},"body":{"type":"string"},"created_at":{"type":"string"}},"required":["comment_no","card_no_raw","author_raw","body","created_at"]}| TypeScript | 질문 해석·권한 필터·답변 조립을 한 타입 정의로 백엔드와 화면까지 잇습니다 |
| Node.js | 카드와 댓글을 읽어 근거 후보를 좁히는 실행 환경입니다 |
| NestJS | 질문 접수·근거 검색·답변 생성을 모듈로 나눈 백엔드입니다 |
| Next.js | 질문 입력과 답변 상세, 근거 펼침 패널을 서버 렌더링으로 그립니다 |
| React | 답변 상태별 목록과 근거 카드 패널을 컴포넌트로 나눕니다 |
| REST API | 질문 요청과 답변 조회를 버전 경로로 나눠 응답 필드를 늘려도 옛 화면이 그대로 돌게 둡니다 |
| OAuth | 묻는 사람이 누구인지를 토큰으로 받아 열람 가능한 프로젝트 범위를 정합니다 |
| LLM API | 좁혀진 근거만 넣어 담당자가 읽을 한 문장을 만듭니다 |
| LangGraph | 질문 해석과 근거 검색, 답변 조립을 상태가 있는 단계로 묶어 중간에 근거가 비면 답변 단계로 넘어가지 않게 막습니다 |
| MCP | 검색과 답변을 도구로 노출해 다른 에이전트가 같은 권한 규칙으로 부르게 합니다 |
| OpenSearch | 카드 제목과 댓글 본문을 색인해 프로젝트 표기가 흔들려도 근거 후보를 찾습니다 |
| PostgreSQL | 카드·댓글 원본과 질문별 답변 이력을 쌓습니다 |
| Redis | 같은 질문이 반복되면 근거 검색 결과를 캐시에서 되돌립니다 |
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 근거를 제대로 골랐는지 재는 평가 러너를 붙입니다.
✓ pass → 여기서 멈춰도 완결입니다. 더 키우려면 같은 기능을 MCP 도구로 노출하고 검색 색인을 붙입니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
answer_state사유 라벨 · question_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
answered근거를 찾은 답변partial_evidence근거가 일부만 있는 답변no_evidence기록에 없는 질문permission_blocked열람 권한 밖project_unmapped프로젝트 미매핑card_unmatched카드 목록 밖 참조질문 하나에 대한 답과 근거를 담습니다. 근거로 쓴 카드번호를 그대로 실어, 어느 기록에서 나온 답인지 담당자가 바로 열어 봅니다.
{"type":"object","properties":{"question_id":{"type":"string"},"question":{"type":"string"},"asker":{"type":"string"},"answer_state":{"type":"string","enum":["answered","partial_evidence","no_evidence","permission_blocked","project_unmapped","card_unmatched"]},"summary":{"type":"string"},"cited_cards":{"type":"array","items":{"type":"string"}},"evidence":{"type":"array","items":{"type":"object","properties":{"dataset":{"type":"string"},"field":{"type":"string"},"quote":{"type":"string"},"value":{"type":["string","null"]}},"required":["dataset","field"]}}},"required":["question_id","question","asker","answer_state","summary","evidence"]}[
{
"question_id": "Q-01",
"question": "A사 이관 리허설이 왜 밀렸나요?",
"asker": "김도현",
"answer_state": "answered",
"summary": "A사 사정으로 리허설 일정이 한 주 밀렸고, 이관 대상 워크스페이스는 세 개로 확정된 상태입니다.",
"cited_cards": [
"FLW-3104"
],
"evidence": [
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "리허설 일정이 A사 사정으로 한 주 밀렸습니다.",
"value": "리허설 일정이 A사 사정으로 한 주 밀렸습니다."
},
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "이관 대상 워크스페이스는 세 개로 확정했습니다.",
"value": "이관 대상 워크스페이스는 세 개로 확정했습니다."
},
{
"dataset": "flow_work_cards",
"field": "card_no",
"quote": "A사 워크스페이스 이관 리허설",
"value": "FLW-3104"
}
]
},
{
"question_id": "Q-02",
"question": "플로우 2.0 개편에서 아직 안 끝난 건 뭔가요?",
"asker": "박세영",
"answer_state": "answered",
"summary": "권한 재계산과 카드 상세 로딩 개선이 진행 중이고, 검색 색인 재작성은 배포 뒤로 미뤄 보류 상태입니다.",
"cited_cards": [
"FLW-3102",
"FLW-3113",
"FLW-3103"
],
"evidence": [
{
"dataset": "flow_work_cards",
"field": "project_raw",
"quote": "플로우 2.0 개편",
"value": "플로우 2.0 개편"
},
{
"dataset": "flow_work_cards",
"field": "project_raw",
"quote": "플로우2.0 개편",
"value": "플로우2.0 개편"
},
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "색인 재작성은 2.0 개편 배포 뒤로 미룹니다.",
"value": "색인 재작성은 2.0 개편 배포 뒤로 미룹니다."
}
]
},
{
"question_id": "Q-03",
"question": "검색 색인 재작성은 언제까지인가요?",
"asker": "박세영",
"answer_state": "no_evidence",
"summary": "마감일이 비어 있어 언제까지인지는 기록에 없습니다. 담당자인 김도현 님에게 물어야 합니다.",
"cited_cards": [
"FLW-3103"
],
"evidence": [
{
"dataset": "flow_work_cards",
"field": "due_date",
"value": null
},
{
"dataset": "flow_work_cards",
"field": "card_no",
"quote": "검색 색인 재작성",
"value": "FLW-3103"
}
]
},
{
"question_id": "Q-04",
"question": "A사 계약 단가 정리는 어디까지 됐나요?",
"asker": "김도현",
"answer_state": "permission_blocked",
"summary": "이 질문의 근거가 될 카드가 비공개라 김도현 님 권한으로는 열리지 않습니다. 계약 담당자에게 물어야 합니다.",
"cited_cards": [],
"evidence": [
{
"dataset": "flow_work_cards",
"field": "visibility",
"quote": "private",
"value": "private"
},
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "단가는 계약 담당자만 볼 수 있게 비공개로 둡니다.",
"value": "단가는 계약 담당자만 볼 수 있게 비공개로 둡니다."
}
]
},
{
"question_id": "Q-05",
"question": "AX 파일럿 진행 상황 알려 주세요.",
"asker": "이수민",
"answer_state": "project_unmapped",
"summary": "AX 파일럿은 프로젝트 코드가 아직 붙지 않아 프로젝트 단위로 묶지 못했습니다. 카드 한 건만 임시로 열려 있습니다.",
"cited_cards": [
"FLW-3112"
],
"evidence": [
{
"dataset": "flow_work_cards",
"field": "project_raw",
"quote": "AX 파일럿",
"value": "AX 파일럿"
},
{
"dataset": "flow_work_cards",
"field": "project_id",
"value": null
},
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "사내 문의 분류 시험은 프로젝트 등록 전이라 임시로 열었습니다.",
"value": "사내 문의 분류 시험은 프로젝트 등록 전이라 임시로 열었습니다."
}
]
},
{
"question_id": "Q-06",
"question": "삭제된 스프린트에서 옮겨 온 건은 어떻게 됐나요?",
"asker": "정하늘",
"answer_state": "card_unmatched",
"summary": "그 댓글이 가리키는 카드번호가 카드 목록에 없어 근거로 쓰지 못했습니다.",
"cited_cards": [],
"evidence": [
{
"dataset": "flow_card_comments",
"field": "card_no_raw",
"quote": "FLW-3199",
"value": "FLW-3199"
},
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "이 카드는 삭제된 스프린트에서 옮겨 온 것입니다.",
"value": "이 카드는 삭제된 스프린트에서 옮겨 온 것입니다."
}
]
},
{
"question_id": "Q-07",
"question": "결제 모듈 이관에서 막힌 건은 언제 풀리나요?",
"asker": "박세영",
"answer_state": "partial_evidence",
"summary": "막힌 이유는 PG 담당자 회신을 기다리는 것이고, 언제 풀리는지는 마감일이 비어 있어 기록에 없습니다.",
"cited_cards": [
"FLW-3109"
],
"evidence": [
{
"dataset": "flow_card_comments",
"field": "body",
"quote": "PG 담당자 회신이 아직 없어 마감일을 못 잡았습니다.",
"value": "PG 담당자 회신이 아직 없어 마감일을 못 잡았습니다."
},
{
"dataset": "flow_work_cards",
"field": "due_date",
"value": null
},
{
"dataset": "flow_work_cards",
"field": "status",
"quote": "보류",
"value": "보류"
}
]
}
][자동 검증 · 러너가 매 실행마다 겁니다] 1. answer_state 값이 여섯 값 목록에 있는지 확인합니다. 2. question_id·asker·answer_state·summary·evidence 필수 필드가 모두 존재하는지 확인합니다. 3. evidence 의 field 가 datasets 컬럼에 실재하는지 확인합니다. 4. evidence 의 quote 가 datasets 샘플의 원문 그대로인지 확인합니다. 5. cited_cards 의 카드번호가 업무 카드 목록에 있는지 확인합니다. answer_state 가 permission_blocked 이면 cited_cards 는 빈 배열입니다. 6. cited_cards 길이가 0 이상 다섯 이하 범위인지 확인합니다. 7. summary 가 존댓말 한 문장인지 확인합니다. [수동 검증 · 사람이 한 번 봅니다] 답이 질문에 실제로 답하는지, 근거로 지목한 카드가 그 답의 출처가 맞는지를 표본 열 건에서 눈으로 봅니다. [평가 러너 · 2주차] 정답 근거를 붙인 질문 24개로 근거 카드 지목이 정답과 얼마나 겹치는지 재고, 권한 밖 카드를 근거로 쓴 건이 하나도 없는지 따로 셉니다.