Skip to content

Commit 1284aea

Browse files
committed
Find materials for multi-word searches and show team projects in the library
A search that matches nothing as a whole phrase now tries each word on its own, drops a trailing particle, and reads words like 예제 or 영상 as the kind of material. The team's repositories that topics do not cover (CausalML study, Lang2SQL, the policy analysis agent, Marketing Science) show up when a search names them, and the search box suggests them too. Topics get the spellings people type that were missing (합성통제법, 콜라이더, 델타 메소드, 노벨티 효과, ...). The search event records whether the result needed the word-by-word fallback (match=phrase|words). The L2 badge text is darkened to pass contrast.
1 parent d18a732 commit 1284aea

6 files changed

Lines changed: 224 additions & 33 deletions

File tree

‎CONTRIBUTING.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ PR을 올리기 전에 `python3 scripts/check_data.py`를 실행해 주세요.
2020
| `data/builders.json` | Builder의 LinkedIn, GitHub 아이디(프로필 사진에 쓰임), 한 줄 소개, Builder가 되기 전 러너로 참여한 프로젝트(`runnerOf`)를 넣을 때. `slug`는 Builder 페이지 주소(`/builders/<slug>.html`)가 되므로 한 번 정하면 바꾸지 않습니다. |
2121
| `data/growth.json` | 기수별 누적 러너 수. 프로젝트 시트를 xlsx로 내려받아 `python scripts/runners_from_sheet.py 파일.xlsx > data/growth.json`으로 만듭니다. 이름은 파일에 남지 않습니다. |
2222
| `data/events.json` | Data Forum 같은 행사를 추가할 때. 다가오는 행사에는 `tagline`, `time`, `venue`, `address`, `signupStart`·`signupEnd`(신청 기간), `capacity`(정원), `code`(추천 코드 안내), `timetable`, `poster`·`timetableImage`(`assets/img/`의 이미지)를 적을 수 있습니다. 신청 기간에 따라 첫 화면 띠와 카드의 문구가 ‘신청 예정 · 받는 중 · 마감’으로 바뀝니다. 행사 전에는 `date`·`time`으로 캘린더 추가 링크(Google, .ics)를, `address`(없으면 `venue`)로 네이버 지도·카카오맵 링크를 카드에 만듭니다. `time`은 `13:30–17:30`처럼 한국 시간으로 적습니다. 행사 다음 날부터 6주 동안은 큰 카드가 ‘지난 행사’로 남고, `participants`(참가 인원)와 `recap`(`{"label": "후기 보기", "url": "https://..."}`)을 넣으면 카드에 함께 보입니다. |
23-
| `data/topics.json` | 자료실의 주제와 자료를 고칠 때. 주제마다 `shelf`(`causal` 인과추론, `exp` 온라인 실험), `name`, `about`, 검색어 `keys`, 자료 `items`를 적습니다. 자료는 `kind`(`video`·`read`·`code`·`slides`·`case`), `repo`(`repos.json`의 레포), `title`, `url`이고, 영상에 딸린 노트북은 `notebook`, 발표 자료 PDF는 `slides`로 붙입니다. 연재 글은 `articles.json`에 `topics`(주제 id 목록)를 적으면 공개일부터 그 주제에 함께 나옵니다. |
23+
| `data/topics.json` | 자료실의 주제와 자료를 고칠 때. 주제마다 `shelf`(`causal` 인과추론, `exp` 온라인 실험), `name`, `about`, 검색어 `keys`, 자료 `items`를 적습니다. 자료는 `kind`(`video`·`read`·`code`·`slides`·`case`), `repo`(`repos.json`의 레포), `title`, `url`이고, 영상에 딸린 노트북은 `notebook`, 발표 자료 PDF는 `slides`로 붙입니다. 연재 글은 `articles.json`에 `topics`(주제 id 목록)를 적으면 공개일부터 그 주제에 함께 나옵니다. 검색은 전체 문구를 먼저 찾고, 없으면 낱말마다 찾습니다(끝의 조사는 떼고, `예제`·`코드`·`영상`·`발표` 같은 말은 자료 종류로 읽습니다). GA의 `search` 이벤트에서 `found=no`인 검색어가 다음에 채울 자리이고, `match=words`는 낱말별로 찾아서야 결과가 나온 검색입니다. |
2424
| `data/experiments.json` | 허브에서 Iterglow(가짜연구소 실험 플랫폼)로 A/B 테스트를 돌릴 때. 기본은 꺼져 있습니다(`"enabled": false`). 켜려면 관리자가 만든 sdk_v1 실험의 `experimentId`와 연결된 flag의 `flagKey`를 실험마다 적고 `enabled`를 `true`로 바꿉니다. 공개 SDK 키(`pk_…`)는 저장소에 적지 않고(`apiKey`는 비워 둡니다) 저장소 Actions secret `ITERGLOW_SDK_KEY`에 둡니다. 배포가 실험이 켜져 있을 때만 그 키를 게시본에 넣고, secret이 없으면 실험은 꺼진 채로 배포됩니다. 공개 키는 실험이 켜지면 방문자 브라우저로 전달되는 값이라 관리 권한은 없지만, 서버 키나 계정 세션은 어디에도 넣지 않습니다. `goals`는 허브가 이미 보내는 이벤트 이름을 Iterglow 지표 이름에 잇습니다. 켜져 있는 동안 SDK가 방문자 브라우저에 익명 ID(`experibase_uid`)를 만들고, 그 ID와 페이지 경로, 기기 종류를 Iterglow로 보냅니다. 첫 실험은 `pseudo-greeting`(Pseudo가 먼저 인사하기)이고, 코드는 `assets/js/experiment.js`에 있습니다. |
2525
| `data/pseudo.json` | Pseudo가 용어를 설명할 때 쓰는 한두 문장 풀이. 키는 `glossary.json`의 용어 id(`term-…`)입니다. 풀이가 없는 용어는 원어와 웹북 장만 알려 줍니다. 해요체로, 예시를 하나 넣어 짧게 씁니다. 다른 용어를 쓸 때는 용어집의 번역어 그대로 씁니다(예: 처치군이 아니라 실험군). 배포 때마다 웹북 용어집을 새로 읽고, 웹북에서 사라진 용어나 다른 표기를 쓴 풀이는 `check_data.py`가 경고로 알려 줍니다. |
2626
| `assets/js/helper.js` | Pseudo(자료실 마스코트)의 답과 추천 질문, 마스코트가 먼저 거는 말(`LINES`: 페이지마다 첫 인사와 특정 섹션·스크롤 위치에서 한 번씩, 방문당 한 번, 닫으면 그 방문 동안 조용히)을 고칠 때. Pseudo는 `topics.json`의 이름·`keys`와 `glossary.json`의 용어로 답을 찾으므로, 잘 못 찾는 질문이 있으면 먼저 해당 주제의 `keys`를 늘려 보세요. 방문자가 적은 문장은 어디에도 보내지 않고, `helper_ask` 이벤트에는 질문이 들어온 경로(`source`)와 답의 종류(`answer`)만 남습니다. |
2727
| `assets/js/header.js` | 허브가 아닌 페이지(자료실, 게임, 실험실, 글, Builder, 404)의 상단. 메뉴는 허브와 같은 여섯 곳이고, 다음 Data Forum 배지를 `events.json`에서 그립니다. 메뉴를 바꿀 때는 `index.html`, 각 페이지, `scripts/build_builders.py`의 `site-nav`를 함께 고칩니다. |
28-
| `assets/js/suggest.js` | 검색창 아래 관련 검색어. `data-suggest`가 붙은 입력창에서 `topics.json`의 주제(이름·영문·`keys`)와 `glossary.json`의 용어(번역어·다른 표기·원어)를 찾고, 한글 초성(ㅇㅈㅊㅂ)과 영문 약어(DID)도 받습니다. 주제가 숨은 키워드로만 걸리면 어떤 키워드인지 함께 보여 줍니다. 잘 안 걸리는 말이 있으면 해당 주제의 `keys`를 늘리세요. |
28+
| `assets/js/suggest.js` | 검색창 아래 관련 검색어. `data-suggest`가 붙은 입력창에서 `topics.json`의 주제(이름·영문·`keys`)와 `glossary.json`의 용어(번역어·다른 표기·원어)를 찾고, 한글 초성(ㅇㅈㅊㅂ)과 영문 약어(DID)도 받습니다. 주제가 숨은 키워드로만 걸리면 어떤 키워드인지 함께 보여 줍니다. 팀 프로젝트(`repos.json`)도 함께 제안하고, 전체 문구로 없으면 가장 긴 낱말로 다시 찾습니다. 잘 안 걸리는 말이 있으면 해당 주제의 `keys`를 늘리세요. |
2929
| `data/glossary.json` | 직접 고치지 않습니다. 배포할 때 `scripts/build_glossary.py`가 웹북 용어집 페이지를 읽어 새로 만듭니다. 용어를 고치려면 Causal-Inference-with-Python의 `TRANSLATION_GUIDE.md` 용어표를 고치세요. 웹북이 다시 배포된 다음 허브 배포 때 반영됩니다. |
30-
| `data/repos.json` | 레포를 추가하거나 설명·단계·트랙·상태를 바꿀 때 |
30+
| `data/repos.json` | 레포를 추가하거나 설명·단계·트랙·상태를 바꿀 때. 자료실 검색은 레포 이름·`title`·`tags`·`builders`와 검색어 `keys`로 팀 프로젝트를 찾아 주제 위에 보여 줍니다(예: `CausalML`, `LLM`). 주제에 넣기 어려운 프로젝트가 검색에 안 걸리면 `keys`를 늘리세요. |
3131
| `data/map.json` | 트랙 지도에서 레포의 위치를 정할 때. 책과 강의(`paid`)의 표지와 소개도 여기서 고칩니다. |
3232
| `data/levels.json` | 레벨의 설명과 볼 순서를 바꿀 때. `learn`(배우는 것), `outcome`(끝나면), `steps`(순서대로 볼 레포와 한 줄 `note`), `more`(웹북 장이나 허브 페이지 같은 더 해 보기 링크)를 적습니다. `steps`에 없는 같은 레벨의 레포는 ‘골라 보기’로 나옵니다. |
3333
| `data/leveltest.json` | 레벨 진단 문제를 고칠 때. 문제마다 `band`(L0·L1·L2), 보기 `opts`, 정답 순서 `answer`(0부터), 해설 `why`, 다시 볼 장 `chapter`를 적습니다. `bands`의 `pass`보다 적게 맞힌 첫 구간이 시작 Level이 되고, 모두 넘으면 관심 분야에 따라 Level 3이나 4가 됩니다. |

‎assets/css/hub.css‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2401,6 +2401,21 @@ dialog.sheet .toast:not(.show) { transition: opacity .25s ease, visibility 0s li
24012401
.lib-extra { display: inline-flex; gap: 12px; margin-left: 12px; }
24022402
.lib-extra a { color: var(--accent); font-weight: 650; text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
24032403
.lib-all { margin-top: 8px; font: inherit; font-size: 14px; font-weight: 700; color: var(--accent); background: none; border: 0; padding: 6px 0; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; }
2404+
/* a search that fell back to single words says so; the team's projects a search names sit above the topics */
2405+
.lib-note { font-size: 14px; color: var(--muted); margin: 0 0 4px; }
2406+
.lib-pjs { margin-bottom: 8px; }
2407+
.lib-pjs-h { font-size: 15px; font-weight: 800; display: flex; align-items: baseline; gap: 10px; margin: 0 0 10px; }
2408+
.lib-pjs-h span { font-size: 13px; font-weight: 500; color: var(--muted); }
2409+
.lib-pjs ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
2410+
.lib-pj { border-radius: 14px; scroll-margin-top: 100px; }
2411+
.lib-pj:focus { outline: none; }
2412+
.lib-pj.hit { animation: lib-hit 1.6s ease-out; }
2413+
.lib-pj a { display: grid; gap: 4px; height: 100%; padding: 14px 16px; border: 1.5px solid var(--rule); border-radius: 14px; background: var(--surface); color: var(--ink); text-decoration: none; }
2414+
.lib-pj a:hover { border-color: var(--ink); }
2415+
.lib-pj a:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
2416+
.lib-pj-t { font-size: 16px; font-weight: 800; letter-spacing: -.01em; }
2417+
.lib-pj-s { font-size: 13.5px; line-height: 1.5; color: var(--ink); }
2418+
.lib-pj .lib-src { margin-top: 2px; }
24042419
.lib-empty { padding: 28px 24px; border: 1.5px dashed var(--rule); border-radius: 16px; display: grid; gap: 12px; color: var(--muted); }
24052420
.lib-empty b { color: var(--ink); }
24062421
.lib-try { display: flex; flex-wrap: wrap; gap: 6px; }
@@ -2673,6 +2688,7 @@ body.has-helper .site-footer { padding-bottom: calc(96px + env(safe-area-inset-b
26732688
.sg-o[aria-selected="true"] { background: var(--surface-2); box-shadow: inset 3px 0 0 var(--accent); }
26742689
.sg-k { grid-row: 1 / 3; align-self: center; font-size: 11.5px; font-weight: 800; padding: 3px 7px; border-radius: 6px; background: color-mix(in srgb, var(--l-main) 12%, var(--surface)); color: var(--l-main); }
26752690
.sg-o.t-term .sg-k { background: color-mix(in srgb, var(--l-exp) 12%, var(--surface)); color: var(--l-exp); }
2691+
.sg-o.t-project .sg-k { background: var(--surface-2); color: var(--ink); }
26762692
.sg-l { font-size: 15px; font-weight: 600; line-height: 1.35; }
26772693
.sg-l b { font-weight: 800; color: var(--accent); }
26782694
.sg-s { font-size: 12.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@@ -2693,7 +2709,7 @@ body.has-helper .site-footer { padding-bottom: calc(96px + env(safe-area-inset-b
26932709
.lib-lv { display: inline-block; margin-right: 6px; padding: 1px 6px; border-radius: 6px; font-size: 11.5px; font-weight: 800; line-height: 1.5; vertical-align: 1px; background: var(--surface-2); color: var(--ink); }
26942710
.lib-lv.lv-L0 { background: color-mix(in srgb, var(--l-main) 14%, var(--surface)); color: var(--l-main); }
26952711
.lib-lv.lv-L1 { background: color-mix(in srgb, var(--l-ai) 16%, var(--surface)); color: var(--ink); }
2696-
.lib-lv.lv-L2 { background: color-mix(in srgb, var(--l-exp) 14%, var(--surface)); color: var(--l-exp); }
2712+
.lib-lv.lv-L2 { background: color-mix(in srgb, var(--l-exp) 12%, var(--surface)); color: color-mix(in srgb, var(--l-exp) 80%, var(--ink)); }
26972713
.lib-lv.lv-L3, .lib-lv.lv-L4 { background: color-mix(in srgb, var(--l-ml) 14%, var(--surface)); color: var(--l-ml); }
26982714
.lib-levels { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; flex: 1 1 100%; }
26992715
.lib-lv-k { font-size: 13px; font-weight: 700; color: var(--muted); margin-right: 4px; }

0 commit comments

Comments
 (0)