원프레딕트
경력근거: 실시간 시계열 데이터 처리 및 대용량 데이터 파이프라인 구축
공고가 주요업무에 '실시간 시계열 데이터 처리 및 대용량 데이터 파이프라인 구축' 과 '산업 설비 데이터를 활용한 대시보드 및 분석 서비스 개발' 을 나란히 적었고, 근무형태에는 '고객사 상주 기반 프로젝트 수행 역할' 이라고 밝혔습니다. 상주하면 선이 끊길 때마다 이유를 그 자리에서 답해야 하고, 이 포트폴리오는 그 답의 앞단을 태그별 기준표로 옮깁니다. 'Kafka 기반 데이터 스트리밍 처리' 와 'React 기반 사용자 친화적 UI 및 데이터 시각화 구현' 이 이 편의 2주차와 1주차 산출물에 그대로 놓입니다. 이 포트폴리오가 보여주는 것은 수신 기준을 데이터로 두고 표본과 맞춰 상태를 가르는 기술이며, 진동 진단이나 설비 고장 예측 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
'실시간 시계열 데이터 처리 및 대용량 데이터 파이프라인 구축'. 그 파이프라인이 약속대로 도는지 보는 층입니다.
'산업 설비 데이터를 활용한 대시보드 및 분석 서비스 개발'. 이 편의 산출물이 그 대시보드 옆에 서는 감시 화면입니다.
'고객사 상주 기반 프로젝트 수행 역할'. 현장에서 바로 답해야 하는 질문이 이 화면에 있습니다.
설비에서 올라오는 시계열이 태그마다 약속한 주기와 범위를 지키는지 보여줍니다. 태그별 수신 기준에 기대 주기와 허용 지연, 값 범위, 받을 품질 코드를 두고 들어온 표본과 맞춰, 다 맞으면 정상으로 그리고 그렇지 않으면 주기 지연·스트림 끊김·범위 밖 값·품질 이상 중 어느 쪽인지 사유로 묶습니다. 기준이 없는 태그는 미작성으로 남겨 몇 초마다 와야 하는지 먼저 정하게 합니다. 표본은 실 고객사 설비 대신 지원자가 공개 시계열 규격으로 만들어 메시지 큐로 흘린 것을 가정합니다.
고객사에 상주하며 설비 대시보드를 만드는 풀스택 개발자입니다. 현장에서 '이 선이 왜 끊겼냐'는 질문을 받으면 설비가 멈춘 건지 데이터가 안 온 건지부터 확인하러 매번 로그와 큐를 번갈아 뒤지던 사람입니다.
태그마다 기대 주기와 허용 지연, 값 범위를 기준으로 적어 두고 들어온 표본과 맞추면, 선이 끊긴 이유가 화면에서 사유별로 갈리고 그 판단 근거가 함께 남는다는 가정입니다. 기준이 없는 태그는 미작성으로 빠지므로 아직 약속하지 않은 태그가 그대로 드러납니다.
표본과 태그별 수신 기준을 넣으면 표본마다 상태를 판정하고, 어긋나면 주기 지연·스트림 끊김·범위 밖 값·품질 이상·기준 미작성 중 어느 쪽인지 붙여 사유별로 묶어 보여주는 화면이 도는 완결품입니다. 카드를 펼치면 그 판단의 근거 값이 나옵니다.
공개 시계열 규격으로 만든 표본 600건을 메시지 큐로 흘려 화면이 실시간으로 갱신되게 하고, 손으로 붙인 정답 상태와 대조해 일치율을 냅니다.
태그별로 표본이 안 들어온 구간을 시간축에 띠로 그리고, 같은 설비의 다른 태그와 겹쳐 보아 설비가 멈춘 것인지 수집이 멈춘 것인지 가려 보는 단계입니다.
상주 개발자가 표본과 수신 기준을 넣으면 태그마다 상태와 어긋난 사유가 사유별로 묶여 화면에 뜨는 과정을 시연합니다.
공개 규격으로 만든 표본 600건과 손으로 붙인 정답 상태를 대조해 일치율을 냅니다. 단, 정답과 표본을 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
값이 비었거나 태그 표기가 흔들리거나 기준에 없는 태그가 섞인 입력 30종을 넣어도 화면이 안 깨지고 사유별로 빠지는 비율을 보여줍니다.
허용 지연과 값 범위를 바꿨을 때 옛 판정이 어떻게 뒤집히는지 회귀로 확인합니다.
진짜 성능 검증은 여기서 멈춥니다. 위 수치는 작성자가 만든 표본의 자가채점이며, 실제 수집 안정성은 원프레딕트 고객사의 실 설비와 실 수집 환경이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태까지 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아니므로, 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 붙였으니 잘 맞는 게 당연하므로, 값 결측과 범위 밖 값, 기준에 없는 태그를 일부러 섞어 쉬운 표본만 푸는 함정을 피합니다.
이 포트폴리오가 보여주는 것은 수신 기준을 데이터로 두고 표본과 맞춰 상태를 가르는 기술이며, 진동 진단이나 설비 고장 예측 전문성은 주장하지 않습니다. 판정은 시각 계산과 범위 비교가 하고 LLM 은 담당자가 읽을 사유 문장만 맡습니다.
표본이 안 온 것과 설비가 멈춘 것은 데이터만으로 갈리지 않아서, 3주차에 같은 설비의 다른 태그를 겹쳐 보게 두고 최종 판단은 현장에 맡깁니다.
실제 도입 때는 고객사마다 태그 이름과 수집 주기가 다르므로, 기준표를 새로 채우는 일을 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
설비에서 올라온 시계열 표본입니다. 표본 번호와 태그 표기, 설비 표기, 수신 시각, 값, 품질 코드, 앞 표본 수신 시각을 한 행에 둡니다. 태그 표기는 수집기 설정에 따라 'bearing_temp'와 'BEARING_TEMP'처럼 흔들립니다. 값이나 품질 코드가 빈 행, 상한을 넘는 값, 앞 표본이 없는 행, 기준표에 없는 태그를 일부러 섞어 두어 사유가 갈리게 둡니다.
| sample_no | tag_raw | asset_raw | received_at | value | quality_raw | prev_received_at |
|---|---|---|---|---|---|---|
| SM-001 | vib_rms | PUMP-01 | 2026-08-22T09:00:00 | 2.4 | GOOD | 2026-08-22T08:59:59 |
| SM-002 | vib_rms | PUMP-01 | 2026-08-22T09:00:02 | 2.5 | GOOD | 2026-08-22T09:00:00 |
| SM-003 | vib_rms | PUMP-01 | 2026-08-22T09:00:31 | 2.6 | GOOD | 2026-08-22T09:00:02 |
| SM-004 | vib_rms | PUMP-02 | 2026-08-22T09:04:10 | 41.2 | GOOD | 2026-08-22T09:04:09 |
| SM-005 | bearing_temp | PUMP-02 | 2026-08-22T09:05:00 | 68 | GOOD | 2026-08-22T09:04:55 |
아래 JSON Schema 를 그대로 만족하는 시계열 표본 행을 600개 더 만들어 주세요. 설비를 서넛 두고 태그를 대여섯 종 섞으며, 태그 표기는 몇 행에서 대문자로 둡니다. 수신 시각은 태그마다 정해진 주기로 늘어놓되 몇 군데는 20초 넘게 비우고, 2할은 값이나 품질 코드를 비우며, 몇 행은 상한을 넘는 값과 기준표에 없는 태그를 넣으세요. 판정 결과나 사유는 넣지 마세요.
{"type": "object", "properties": {"sample_no": {"type": "string"}, "tag_raw": {"type": "string"}, "asset_raw": {"type": "string"}, "received_at": {"type": "string"}, "value": {"type": ["number", "null"]}, "quality_raw": {"type": ["string", "null"]}, "prev_received_at": {"type": ["string", "null"]}}, "required": ["sample_no", "tag_raw", "asset_raw", "received_at", "value", "quality_raw", "prev_received_at"]}{"type": "object", "properties": {"sample_no": {"type": "string"}, "tag_raw": {"type": "string"}, "asset_raw": {"type": "string"}, "received_at": {"type": "string"}, "value": {"type": ["number", "null"]}, "quality_raw": {"type": ["string", "null"]}, "prev_received_at": {"type": ["string", "null"]}}, "required": ["sample_no", "tag_raw", "asset_raw", "received_at", "value", "quality_raw", "prev_received_at"]}태그별 수신 기준표입니다. 기준 번호와 태그, 기대 주기, 허용 지연, 값 범위, 받을 품질 코드 목록, 담당을 한 행에 둡니다. 태그마다 주기와 허용 지연이 달라 같은 간격이라도 어떤 태그는 정상이고 어떤 태그는 지연입니다. 표본에 나오는 태그 중 일부는 일부러 넣지 않아 미작성이 도출되게 둡니다.
| contract_no | tag_raw | expected_interval_sec | allow_delay_sec | min_value | max_value | quality_ok_values | owner_name |
|---|---|---|---|---|---|---|---|
| SC-01 | vib_rms | 2 | 5 | 0 | 20 | ["GOOD"] | 설비운영 |
| SC-02 | bearing_temp | 5 | 10 | -20 | 150 | ["GOOD","UNCERTAIN"] | 설비운영 |
| SC-03 | motor_current | 2 | 5 | 0 | 80 | ["GOOD"] | 전기보전 |
| SC-04 | flow_rate | 10 | 20 | 0 | 500 | ["GOOD"] | 공정 |
| SC-05 | discharge_press | 5 | 10 | 0 | 30 | ["GOOD"] | 공정 |
아래 JSON Schema 를 그대로 만족하는 수신 기준 행을 30개 더 만들어 주세요. 태그마다 기대 주기를 2초에서 300초 사이로 다르게 두고 허용 지연은 그 두 배쯤으로 둡니다. 값 범위는 물리량에 맞게 정하고 받을 품질 코드는 태그에 따라 한 값 또는 두 값으로 둡니다. 표본에 나오는 태그 중 일부는 일부러 넣지 마세요 — 미작성이 있어야 기준표의 구멍이 드러납니다. 판정 결과는 넣지 마세요.
{"type": "object", "properties": {"contract_no": {"type": "string"}, "tag_raw": {"type": "string"}, "expected_interval_sec": {"type": "number"}, "allow_delay_sec": {"type": "number"}, "min_value": {"type": ["number", "null"]}, "max_value": {"type": ["number", "null"]}, "quality_ok_values": {"type": "array", "items": {"type": "string"}}, "owner_name": {"type": "string"}}, "required": ["contract_no", "tag_raw", "expected_interval_sec", "allow_delay_sec", "quality_ok_values", "owner_name"]}{"type": "object", "properties": {"contract_no": {"type": "string"}, "tag_raw": {"type": "string"}, "expected_interval_sec": {"type": "number"}, "allow_delay_sec": {"type": "number"}, "min_value": {"type": ["number", "null"]}, "max_value": {"type": ["number", "null"]}, "quality_ok_values": {"type": "array", "items": {"type": "string"}}, "owner_name": {"type": "string"}}, "required": ["contract_no", "tag_raw", "expected_interval_sec", "allow_delay_sec", "quality_ok_values", "owner_name"]}| FastAPI | 표본과 수신 기준을 맞춰 상태를 판정하고 화면에 넘기는 API 를 맡습니다. |
| React | 사유별로 묶은 태그 상태와 근거 값을 보여주는 화면을 맡습니다. |
| Kafka | 2주차부터 표본을 흘려 화면이 실시간으로 갱신되게 하는 통로를 맡습니다. |
| PostgreSQL | 표본과 수신 기준, 판정 결과를 담습니다. |
✗ fail → 미작성이 절반을 넘으면 기준표를 먼저 채웁니다.
✓ pass → 2주차 메시지 큐 연결로 넘어갑니다.
✗ fail → 어긋난 태그를 모아 허용 지연과 범위를 다시 봅니다.
✓ pass → 3주차 시간축 그림으로 넘어갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
health_code사유 라벨 · sample_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
healthy정상sampling_slow주기 지연stream_broken스트림 끊김value_out_of_range범위 밖 값quality_flagged품질 이상contract_absent기준 미작성표본 하나와 수신 기준이 만나는 자리마다 그림에 그릴 수 있는 상태인지 정합니다. 어긋나면 주기 지연·스트림 끊김·범위 밖 값·품질 이상·기준 미작성 중 어느 쪽인지 싣습니다. 화면은 이 상태 코드로 묶습니다.
{"type": "object", "properties": {"health_no": {"type": "string"}, "sample_no": {"type": "string"}, "tag_raw": {"type": "string"}, "asset_raw": {"type": "string"}, "contract_no": {"type": ["string", "null"]}, "health_code": {"type": "string", "enum": ["healthy", "sampling_slow", "stream_broken", "value_out_of_range", "quality_flagged", "contract_absent"]}, "observed_interval_sec": {"type": ["number", "null"], "description": "received_at 에서 prev_received_at 을 뺀 초입니다. 앞 표본이 없으면 비웁니다."}, "expected_interval_sec": {"type": ["number", "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": ["health_no", "sample_no", "tag_raw", "asset_raw", "health_code", "evidence", "summary"]}[
{
"health_no": "SH-001",
"sample_no": "SM-002",
"tag_raw": "vib_rms",
"asset_raw": "PUMP-01",
"contract_no": "SC-01",
"health_code": "healthy",
"observed_interval_sec": 2,
"expected_interval_sec": 2,
"owner_name": "설비운영",
"evidence": [
{
"field": "value",
"agg": null,
"value": 2.5
},
{
"field": "max_value",
"agg": null,
"value": 20
}
],
"summary": "들어온 간격이 약속한 2초와 같고 값도 범위 안이라 그대로 그립니다.",
"hedge": null
},
{
"health_no": "SH-002",
"sample_no": "SM-006",
"tag_raw": "BEARING_TEMP",
"asset_raw": "PUMP-01",
"contract_no": "SC-02",
"health_code": "healthy",
"observed_interval_sec": 5,
"expected_interval_sec": 5,
"owner_name": "설비운영",
"evidence": [
{
"field": "tag_raw",
"agg": null,
"value": "BEARING_TEMP"
},
{
"field": "value",
"agg": null,
"value": 64.5
}
],
"summary": "태그를 대문자로 보내 왔으나 접으면 같은 값이라 같은 기준으로 봅니다.",
"hedge": null
},
{
"health_no": "SH-003",
"sample_no": "SM-003",
"tag_raw": "vib_rms",
"asset_raw": "PUMP-01",
"contract_no": "SC-01",
"health_code": "sampling_slow",
"observed_interval_sec": 29,
"expected_interval_sec": 2,
"owner_name": "설비운영",
"evidence": [
{
"field": "prev_received_at",
"agg": null,
"value": "2026-08-22T09:00:02"
},
{
"field": "allow_delay_sec",
"agg": null,
"value": 5
}
],
"summary": "29초 만에 들어와 허용 지연 5초를 넘겼고 그 사이 그림이 비어 있습니다.",
"hedge": null
},
{
"health_no": "SH-004",
"sample_no": "SM-004",
"tag_raw": "vib_rms",
"asset_raw": "PUMP-02",
"contract_no": "SC-01",
"health_code": "value_out_of_range",
"observed_interval_sec": 1,
"expected_interval_sec": 2,
"owner_name": "설비운영",
"evidence": [
{
"field": "value",
"agg": null,
"value": 41.2
},
{
"field": "max_value",
"agg": null,
"value": 20
}
],
"summary": "진동값 41.2 가 상한 20 을 넘어 센서 배율을 먼저 봐야 합니다.",
"hedge": null
},
{
"health_no": "SH-005",
"sample_no": "SM-007",
"tag_raw": "bearing_temp",
"asset_raw": "FAN-01",
"contract_no": "SC-02",
"health_code": "quality_flagged",
"observed_interval_sec": 5,
"expected_interval_sec": 5,
"owner_name": "설비운영",
"evidence": [
{
"field": "quality_raw",
"agg": null,
"value": "BAD"
},
{
"field": "value",
"agg": null,
"value": null
}
],
"summary": "품질 코드가 이상으로 오고 값도 비어 그림에서 뺍니다.",
"hedge": null
},
{
"health_no": "SH-006",
"sample_no": "SM-010",
"tag_raw": "flow_rate",
"asset_raw": "PUMP-02",
"contract_no": "SC-04",
"health_code": "stream_broken",
"observed_interval_sec": null,
"expected_interval_sec": 10,
"owner_name": "공정",
"evidence": [
{
"field": "prev_received_at",
"agg": null,
"value": null
},
{
"field": "expected_interval_sec",
"agg": null,
"value": 10
}
],
"summary": "앞 표본이 없어 간격을 재지 못하므로 스트림이 끊겼다 다시 붙은 자리로 봅니다.",
"hedge": "재시작 직후일 수 있어 앞뒤 표본을 함께 보고 담당자가 확인합니다."
},
{
"health_no": "SH-007",
"sample_no": "SM-011",
"tag_raw": "oil_level",
"asset_raw": "PUMP-02",
"contract_no": null,
"health_code": "contract_absent",
"observed_interval_sec": 5,
"expected_interval_sec": null,
"owner_name": null,
"evidence": [
{
"field": "tag_raw",
"agg": null,
"value": "oil_level"
},
{
"field": "contract_no",
"agg": "count",
"value": 12
}
],
"summary": "기준 열두 행에 없는 태그라 몇 초마다 와야 하는지 정해 두지 않았습니다.",
"hedge": null
},
{
"health_no": "SH-008",
"sample_no": "SM-008",
"tag_raw": "motor_current",
"asset_raw": "FAN-01",
"contract_no": "SC-03",
"health_code": "quality_flagged",
"observed_interval_sec": 2,
"expected_interval_sec": 2,
"owner_name": "전기보전",
"evidence": [
{
"field": "quality_raw",
"agg": null,
"value": "UNCERTAIN"
},
{
"field": "quality_ok_values",
"agg": null,
"value": [
"GOOD"
]
}
],
"summary": "이 태그는 좋음만 받기로 해 불확실 표본을 그림에서 뺍니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. health_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. health_no·sample_no·tag_raw·asset_raw·health_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. sample_no 가 표본에 실재하는 번호인지 대조합니다. 4. contract_no 가 있으면 기준표에 실재하는 행 번호인지 대조합니다. 5. contract_no 가 있으면 expected_interval_sec·owner_name 이 그 기준 행의 값과 같은지 대조합니다. 6. tag_raw 를 대소문자를 접어 기준표와 맞추고, 맞는 행이 없으면 health_code 가 contract_absent 인지 확인합니다. 7. observed_interval_sec 을 received_at 에서 prev_received_at 을 빼 초로 다시 계산합니다. 8. health_code 가 healthy 이면 observed_interval_sec 이 allow_delay_sec 이하이고 값이 범위 안이며 품질 코드가 받을 목록에 있는지 다시 확인합니다. 9. health_code 가 sampling_slow 이면 observed_interval_sec 이 allow_delay_sec 을 넘는지 다시 계산합니다. 10. prev_received_at 이 비어 있으면 health_code 가 stream_broken 인지 확인합니다. 11. health_code 가 value_out_of_range 이면 value 가 min_value 와 max_value 밖인지 다시 확인합니다. 12. quality_raw 가 받을 목록에 없거나 value 가 비어 있으면 health_code 가 quality_flagged 인지 확인합니다. 13. evidence 의 field 이름이 표본과 기준표에 실재하는 컬럼인지 대조합니다. 14. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 행 값과 같은지 확인합니다. 15. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 표본 600건을 메시지 큐로 흘려 화면이 갱신되게 하고, 손으로 붙인 정답 상태와 대조해 일치율과 상태 코드별 분포를 냅니다. [수동 검증 · 사람이 봅니다] 1. 기준이 없는 태그의 주기와 범위를 담당 조직이 정합니다. 2. 표본이 끊긴 구간이 설비 정지인지 수집 정지인지 현장이 확인합니다.