바이셀스탠다드
경력근거: 사용자가 매일 쓰는 서비스 위에서 자연스럽게 동작하는 경량 브라우저 클라이언트 설계·구현
※ 실제 산출물이 아닌 AI 예측 시안입니다.
공고가 주요업무 첫 줄에 '사용자가 매일 쓰는 서비스 위에서 자연스럽게 동작하는 경량 브라우저 클라이언트 설계·구현' 을 적었습니다. 그 화면에서 상품 정보를 읽어내는 일이 제품이 하는 첫 동작이고, 못 읽으면 그 페이지에서는 확장이 아무것도 못 띄웁니다. 이 포트폴리오는 그 첫 동작을 항목 규격으로 만들어 어디서 무엇을 못 읽었는지 지목합니다. '대상 플랫폼의 UI 변화에도 유연하게 버티는 방어적 구조 설계' 도 원천을 여럿 두고 신뢰 순서를 정하는 이 설계와 맞습니다. 이 포트폴리오가 보여주는 것은 추출 규격을 데이터로 두고 읽어낸 값과 맞춰 쓸 수 있는지 가르는 기술이며, 특정 서비스의 화면 구조나 광고·리워드 사업 지식은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
'사용자가 매일 쓰는 서비스 위에서 자연스럽게 동작하는 경량 브라우저 클라이언트 설계·구현'. 그 화면에서 무엇을 읽어내느냐가 제품의 첫 단계입니다.
'대상 플랫폼의 UI 변화에도 유연하게 버티는 방어적 구조 설계'. 원천을 여럿 두고 신뢰 순서를 정하는 것이 그 방어입니다.
'AI 코딩 에이전트가 생성한 코드의 구조적 결함·성능 문제·엣지 케이스'. 못 읽은 항목이 그 엣지가 드러나는 자리입니다.
확장이 쇼핑 페이지에서 읽어낸 상품 정보를 항목마다 그대로 쓸 수 있는지 가려 쇼핑몰별로 묶어 화면에 보여줍니다. 항목 규격에 필수 여부와 값 종류, 믿을 원천 목록, 길이 상한을 두고 읽어낸 값과 맞춰, 다 맞으면 그대로 쓰고 그렇지 않으면 값 없음·형식 안 맞음·못 믿을 원천 중 어느 쪽인지 사유로 남깁니다. 같은 항목을 두 원천에서 다르게 읽으면 충돌로 표시하고, 규격에 없는 항목은 미선언으로 남겨 받을지 먼저 정하게 합니다. 읽어낸 값은 실 서비스 대신 지원자가 만든 예제 쇼핑 화면 넷에서 뽑은 것을 가정하며 사용자 정보를 담지 않습니다.
확장 서비스의 운영 담당자입니다. 어느 쇼핑몰부터 고쳐야 하는지 매주 정해야 하는데, 지금은 개발자에게 물어보거나 사용자 문의가 쌓인 곳을 짐작으로 고르던 자리에 있습니다.
항목마다 믿을 원천과 값 종류를 규격으로 두고 읽어낸 값과 맞추면, 그대로 쓸 항목과 못 쓸 항목이 페이지를 여는 자리에서 갈리고 못 쓰는 이유가 근거로 남는다는 가정입니다. 규격에 없는 항목은 미선언으로 빠지므로 아직 받기로 정하지 않은 정보가 그대로 드러납니다.
읽어낸 값과 항목 규격을 넣으면 항목마다 사용 가능·불가를 판정하고, 불가면 값 없음·형식 안 맞음·못 믿을 원천·원천 충돌·미선언 중 어느 쪽인지 붙여 쇼핑몰별로 묶어 보여주는 화면이 도는 완결품입니다. 항목을 펼치면 읽은 자리와 값이 나옵니다.
예제 쇼핑 화면 넷을 만들어 무인 브라우저로 상품 300개를 열고 구조화 데이터·메타 태그·화면에서 각각 읽어낸 뒤, 손으로 붙인 정답 판정과 대조해 일치율을 냅니다.
필수 항목을 못 읽었을 때 다음 순위 원천으로 다시 읽고, 그래도 못 읽은 상품을 사이트별로 모아 규격을 고칠 목록으로 내는 단계입니다.
운영 담당자가 읽어낸 값과 규격표를 넣으면 항목마다 사용 가능 여부와 사유가 쇼핑몰별로 묶여 화면에 뜨는 과정을 시연합니다.
예제 화면 넷에서 읽어낸 상품 300개와 손으로 붙인 정답 판정을 대조해 일치율을 냅니다. 단, 예제 화면과 정답을 모두 작성자가 만든 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
값이 비었거나 사이트 표기가 흔들리거나 규격에 없는 항목이 섞인 입력 30종을 넣어도 화면이 안 깨지고 사유별로 빠지는 비율을 보여줍니다.
믿을 원천 순서를 바꿨을 때 못 쓰던 항목이 몇 개 살아나는지 회귀로 확인합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 예제 화면의 자가채점이며, 실제 추출 성공률은 바이셀스탠다드가 붙는 실 서비스 페이지가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 작성자가 만든 예제 화면의 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 붙였으니 잘 맞는 게 당연하므로, 값 결측과 원천 충돌, 판촉 문구가 붙은 긴 이름을 일부러 섞어 쉬운 항목만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 추출 규격을 데이터로 두고 읽어낸 값과 맞춰 쓸 수 있는지 가르는 기술이며, 특정 서비스의 화면 구조나 광고·리워드 사업 지식은 주장하지 않습니다. 판정은 종류와 목록 대조가 하고 LLM 은 담당자가 읽을 사유 문장만 맡습니다.
읽어낸 값은 상품 정보만 담고 사용자 정보를 담지 않습니다. 그 경계를 규격표에 항목으로 못 박아 나중에도 넘지 않게 둡니다.
실제 도입 때는 사이트마다 구조화 데이터의 충실도가 달라, 원천 순서를 사이트별로 정하는 일을 운영 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
확장이 상품 페이지에서 읽어낸 값을 항목 단위로 편 것입니다. 읽기 번호와 사이트, 상품 키, 항목 이름, 읽어낸 값, 읽은 자리, 읽은 시각을 한 행에 둡니다. 사이트 이름은 적는 자리에 따라 'shop-b'와 'Shop-B'처럼 흔들립니다. 값이 안 읽힌 행, 같은 항목을 두 자리에서 다르게 읽은 행, 판촉 문구까지 붙어 길어진 이름, 규격에 없는 항목을 일부러 섞어 두어 사유가 갈리게 둡니다.
| capture_no | site_raw | product_key | field_raw | value_raw | source_raw | captured_at |
|---|---|---|---|---|---|---|
| CP-001 | shop-a | A-10231 | title | 무선 이어폰 3세대 | jsonld | 2026-08-24T10:00:00 |
| CP-002 | shop-a | A-10231 | price | 129000 | jsonld | 2026-08-24T10:00:00 |
| CP-003 | shop-a | A-10231 | category | 디지털/음향 | dom | 2026-08-24T10:00:00 |
| CP-004 | shop-a | A-10231 | price | 12,900원 | dom | 2026-08-24T10:00:01 |
| CP-005 | Shop-B | B-88 | title | 캠핑 랜턴 LED | meta | 2026-08-24T11:00:00 |
아래 JSON Schema 를 그대로 만족하는 읽기 행을 900개 더 만들어 주세요. 사이트를 넷 두고 상품마다 항목을 서넛 붙이며, 읽은 자리를 구조화 데이터·메타 태그·화면으로 섞습니다. 사이트 이름은 몇 행에서 첫 글자를 대문자로 두고, 2할은 값을 비우며, 몇 상품은 같은 항목을 두 자리에서 다르게 읽은 행을 함께 넣으세요. 이름 몇 개는 판촉 문구를 붙여 60자가 넘게 두고, 규격에 없는 항목도 몇 행 넣습니다. 사용자 정보는 넣지 말고 판정 결과도 넣지 마세요.
{"type": "object", "properties": {"capture_no": {"type": "string"}, "site_raw": {"type": "string"}, "product_key": {"type": "string"}, "field_raw": {"type": "string"}, "value_raw": {"type": ["string", "null"]}, "source_raw": {"type": "string"}, "captured_at": {"type": "string"}}, "required": ["capture_no", "site_raw", "product_key", "field_raw", "value_raw", "source_raw", "captured_at"]}{"type": "object", "properties": {"capture_no": {"type": "string"}, "site_raw": {"type": "string"}, "product_key": {"type": "string"}, "field_raw": {"type": "string"}, "value_raw": {"type": ["string", "null"]}, "source_raw": {"type": "string"}, "captured_at": {"type": "string"}}, "required": ["capture_no", "site_raw", "product_key", "field_raw", "value_raw", "source_raw", "captured_at"]}항목별 추출 규격표입니다. 규격 번호와 항목 이름, 필수 여부, 값 종류, 믿을 원천 목록, 길이 상한, 담당을 한 행에 둡니다. 가격처럼 틀리면 곧바로 손해가 나는 항목은 믿을 원천을 구조화 데이터 하나로 좁혀, 같은 값도 어디서 읽었느냐로 결과가 갈립니다. 읽기에 나오는 항목 중 일부는 일부러 넣지 않아 미선언이 도출되게 둡니다.
| spec_no | field_raw | required_flag | value_type | trusted_sources | max_len | owner_name |
|---|---|---|---|---|---|---|
| FS-01 | title | true | text | ["jsonld","meta","dom"] | 40 | 클라이언트 |
| FS-02 | price | true | number | ["jsonld"] | — | 클라이언트 |
| FS-03 | category | true | text | ["jsonld","dom"] | 30 | 클라이언트 |
| FS-04 | soldout | false | bool | ["jsonld","dom"] | — | 클라이언트 |
| FS-05 | brand | false | text | ["jsonld","meta"] | 20 | 클라이언트 |
아래 JSON Schema 를 그대로 만족하는 규격 행을 20개 더 만들어 주세요. 상품 이름·가격·분류는 필수로 두고 나머지는 선택으로 둡니다. 가격과 재고처럼 틀리면 손해가 나는 항목은 믿을 원천을 구조화 데이터 하나로 좁히고, 글자 항목에는 길이 상한을 20자에서 300자 사이로 둡니다. 읽기에 나오는 항목 중 일부는 일부러 넣지 마세요 — 미선언이 있어야 규격표의 구멍이 드러납니다. 판정 결과는 넣지 마세요.
{"type": "object", "properties": {"spec_no": {"type": "string"}, "field_raw": {"type": "string"}, "required_flag": {"type": "boolean"}, "value_type": {"type": "string", "enum": ["text", "number", "bool"]}, "trusted_sources": {"type": "array", "items": {"type": "string"}}, "max_len": {"type": ["integer", "null"]}, "owner_name": {"type": "string"}}, "required": ["spec_no", "field_raw", "required_flag", "value_type", "trusted_sources", "owner_name"]}{"type": "object", "properties": {"spec_no": {"type": "string"}, "field_raw": {"type": "string"}, "required_flag": {"type": "boolean"}, "value_type": {"type": "string", "enum": ["text", "number", "bool"]}, "trusted_sources": {"type": "array", "items": {"type": "string"}}, "max_len": {"type": ["integer", "null"]}, "owner_name": {"type": "string"}}, "required": ["spec_no", "field_raw", "required_flag", "value_type", "trusted_sources", "owner_name"]}| TypeScript | 읽어낸 값과 항목 규격을 맞춰 종류와 원천을 견주는 판정기를 맡습니다. |
| Playwright | 예제 쇼핑 화면을 열어 세 원천에서 값을 읽어냅니다. |
| Next.js | 운영 담당자가 여는 현황 화면을 맡습니다. |
| React | 쇼핑몰별로 묶은 항목과 읽은 자리를 보여주는 화면을 맡습니다. |
✗ fail → 미선언이 절반을 넘으면 규격표를 먼저 채웁니다.
✓ pass → 2주차 실제 추출로 넘어갑니다.
✗ fail → 어긋난 항목을 모아 읽는 자리를 다시 봅니다.
✓ pass → 3주차 원천 메우기로 넘어갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
extract_code사유 라벨 · capture_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
usable그대로 사용value_missing값 없음type_invalid형식 안 맞음source_untrusted못 믿을 자리field_conflict원천 충돌field_undeclared항목 미선언읽어낸 항목 하나와 추출 규격이 만나는 자리마다 그대로 쓸 수 있는지 정합니다. 못 쓰면 값 없음·형식 안 맞음·못 믿을 원천·원천 충돌·미선언 중 어느 쪽인지 싣습니다. 리포트는 이 사유 코드로 묶습니다.
{"type": "object", "properties": {"verdict_no": {"type": "string"}, "capture_no": {"type": "string"}, "site_raw": {"type": "string"}, "field_raw": {"type": "string"}, "spec_no": {"type": ["string", "null"]}, "extract_code": {"type": "string", "enum": ["usable", "value_missing", "type_invalid", "source_untrusted", "field_conflict", "field_undeclared"]}, "value_type": {"type": ["string", "null"]}, "owner_name": {"type": ["string", "null"]}, "evidence": {"type": "array", "items": {"type": "object", "properties": {"field": {"type": "string"}, "agg": {"type": ["string", "null"]}, "value": {}}, "required": ["field", "value"]}}, "summary": {"type": "string"}, "hedge": {"type": ["string", "null"]}}, "required": ["verdict_no", "capture_no", "site_raw", "field_raw", "extract_code", "evidence", "summary"]}[
{
"verdict_no": "EV-001",
"capture_no": "CP-001",
"site_raw": "shop-a",
"field_raw": "title",
"spec_no": "FS-01",
"extract_code": "usable",
"value_type": "text",
"owner_name": "클라이언트",
"evidence": [
{
"field": "source_raw",
"agg": null,
"value": "jsonld"
},
{
"field": "trusted_sources",
"agg": null,
"value": [
"jsonld",
"meta",
"dom"
]
}
],
"summary": "구조화 데이터에서 읽어 길이도 상한 안이라 그대로 씁니다.",
"hedge": null
},
{
"verdict_no": "EV-002",
"capture_no": "CP-005",
"site_raw": "Shop-B",
"field_raw": "title",
"spec_no": "FS-01",
"extract_code": "usable",
"value_type": "text",
"owner_name": "클라이언트",
"evidence": [
{
"field": "site_raw",
"agg": null,
"value": "Shop-B"
},
{
"field": "value_raw",
"agg": null,
"value": "캠핑 랜턴 LED"
}
],
"summary": "사이트 이름을 대문자로 적어 왔으나 접으면 같은 곳이라 같은 규격으로 봅니다.",
"hedge": null
},
{
"verdict_no": "EV-003",
"capture_no": "CP-004",
"site_raw": "shop-a",
"field_raw": "price",
"spec_no": "FS-02",
"extract_code": "source_untrusted",
"value_type": "number",
"owner_name": "클라이언트",
"evidence": [
{
"field": "source_raw",
"agg": null,
"value": "dom"
},
{
"field": "trusted_sources",
"agg": null,
"value": [
"jsonld"
]
}
],
"summary": "화면에서 읽은 값이라 할인 전 가격이 잡힐 수 있어 가격은 구조화 데이터만 씁니다.",
"hedge": null
},
{
"verdict_no": "EV-004",
"capture_no": "CP-006",
"site_raw": "shop-b",
"field_raw": "price",
"spec_no": "FS-02",
"extract_code": "value_missing",
"value_type": "number",
"owner_name": "클라이언트",
"evidence": [
{
"field": "value_raw",
"agg": null,
"value": null
},
{
"field": "required_flag",
"agg": null,
"value": true
}
],
"summary": "가격을 못 읽어 이 상품에는 혜택을 붙일 수 없습니다.",
"hedge": null
},
{
"verdict_no": "EV-005",
"capture_no": "CP-007",
"site_raw": "shop-b",
"field_raw": "soldout",
"spec_no": "FS-04",
"extract_code": "type_invalid",
"value_type": "bool",
"owner_name": "클라이언트",
"evidence": [
{
"field": "value_raw",
"agg": null,
"value": "품절"
},
{
"field": "value_type",
"agg": null,
"value": "bool"
}
],
"summary": "글자로 온 품절 표시를 참·거짓으로 바꾸는 규칙이 없어 그대로 쓰지 못합니다.",
"hedge": null
},
{
"verdict_no": "EV-006",
"capture_no": "CP-008",
"site_raw": "shop-c",
"field_raw": "title",
"spec_no": "FS-01",
"extract_code": "type_invalid",
"value_type": "text",
"owner_name": "클라이언트",
"evidence": [
{
"field": "max_len",
"agg": null,
"value": 40
},
{
"field": "source_raw",
"agg": null,
"value": "dom"
}
],
"summary": "판촉 문구까지 붙어 상한 40자를 넘겨 상품 이름으로 쓰기 어렵습니다.",
"hedge": "앞부분만 잘라 쓸지 담당자가 정합니다."
},
{
"verdict_no": "EV-007",
"capture_no": "CP-010",
"site_raw": "shop-c",
"field_raw": "review_count",
"spec_no": null,
"extract_code": "field_undeclared",
"value_type": null,
"owner_name": null,
"evidence": [
{
"field": "field_raw",
"agg": null,
"value": "review_count"
},
{
"field": "spec_no",
"agg": "count",
"value": 12
}
],
"summary": "규격 열두 행에 없는 항목이라 받을지 먼저 정해야 합니다.",
"hedge": null
},
{
"verdict_no": "EV-008",
"capture_no": "CP-012",
"site_raw": "shop-d",
"field_raw": "category",
"spec_no": "FS-03",
"extract_code": "value_missing",
"value_type": "text",
"owner_name": "클라이언트",
"evidence": [
{
"field": "value_raw",
"agg": null,
"value": null
},
{
"field": "required_flag",
"agg": null,
"value": true
}
],
"summary": "분류를 못 읽어 카테고리로 걸리는 혜택을 고르지 못합니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. extract_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. verdict_no·capture_no·site_raw·field_raw·extract_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. capture_no 가 읽기 목록에 실재하는 번호인지 대조합니다. 4. spec_no 가 있으면 규격표에 실재하는 행 번호인지 대조합니다. 5. spec_no 가 있으면 value_type·owner_name 이 그 규격 행의 값과 같은지 대조합니다. 6. field_raw 를 대소문자를 접어 규격표와 맞추고, 맞는 행이 없으면 extract_code 가 field_undeclared 인지 확인합니다. 7. value_raw 가 비어 있으면 extract_code 가 value_missing 인지 확인합니다. 8. source_raw 가 trusted_sources 에 없으면 extract_code 가 source_untrusted 인지 확인합니다. 9. value_type 이 number 인데 value_raw 가 숫자로 안 바뀌면 extract_code 가 type_invalid 인지 다시 확인합니다. 10. value_type 이 bool 인데 value_raw 가 참·거짓으로 안 바뀌면 extract_code 가 type_invalid 인지 확인합니다. 11. max_len 이 있고 value_raw 길이가 그 값을 넘으면 extract_code 가 type_invalid 인지 다시 셉니다. 12. 같은 product_key·field_raw 를 두 source_raw 에서 다르게 읽었으면 뒤 행의 extract_code 가 field_conflict 인지 확인합니다. 13. extract_code 가 usable 이면 위 검사를 모두 통과하는지 다시 확인합니다. 14. evidence 의 field 이름이 읽기 목록과 규격표에 실재하는 컬럼인지 대조합니다. 15. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 확인합니다. 16. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 예제 화면 넷에서 읽어낸 상품 300개에 손으로 붙인 정답 판정과 대조해 일치율과 사유 코드별 분포를 내고, 필수 항목을 못 읽어 혜택을 못 붙인 상품 수를 함께 냅니다. [수동 검증 · 사람이 봅니다] 1. 규격에 없는 항목을 받을지 클라이언트 담당이 정합니다. 2. 긴 상품 이름을 잘라 쓸지 어디까지 자를지 사람이 정합니다.