Compare commits

...
4 Commits
Author SHA1 Message Date
Godopu dadd14feb1 test(e2e,verify): enhance live API verification and dynamic port binding in E2E runner
- Update verify_counts.py to validate SQLite database records and live Go REST API endpoints directly
- Harden E2E test runner with dynamic free port binding (get_free_port) to prevent port collisions
- Add exact regex matching for dynamic (ƒ) and static (○) route table allocations
- Ignore root build binary artifacts in backend/.gitignore
2026-08-24 20:31:42 +09:00
Godopu f96f687dd1 feat(frontend): purge all static content fallback files and enforce strict backend-only dynamic rendering
- Delete all legacy static content data files (members.ts, publications.ts, lectures.ts, standards.ts, projects.ts)
- Remove all fallback imports and static fallbacks across all page components
- Enforce strict dynamic runtime fetching where pages render empty datasets when Go backend server is unavailable
2026-08-24 20:10:25 +09:00
Godopu 5041ab1ea8 feat(frontend): convert to live real-time dynamic runtime fetching with force-dynamic and cache no-store
- Set cache: 'no-store' in lib/api.ts to trigger real-time REST API requests on every page load
- Add export const dynamic = 'force-dynamic' across all 6 app pages (/home, /members, /publications, /lectures, /standardization)
- Update REQUIREMENTS.md and E2E test runner to validate live server routes and dynamic runtime fetching
- Pass all 12 quality gates cleanly
2026-08-24 19:25:26 +09:00
Godopu 9cb6258d3d feat(frontend): connect Next.js server components to Go backend REST API with resilient fallback
- Implement typed REST API client in refer_landing_page/lib/api.ts
- Support API_BASE_URL environment variable with graceful fallback to static content
- Connect Home page (projects, areas, stats), Members page (active, alumni), Publications (highlights, list, patents), Lectures, and Standardization pages to backend endpoints
- Pass all 12 SSG build, TypeScript, ESLint, and E2E quality gates
2026-08-24 18:40:46 +09:00
22 changed files with 882 additions and 3882 deletions
+7 -6
View File
@@ -4,10 +4,10 @@
| 항목 | 내용 |
| :--- | :--- |
| **문서 ID** | `REQ-ANL-WEB-001` |
| **버전** | `1.14` |
| **작성일** | 2026-07-30 |
| **작성자** | claude (`canary-projects-landing-page-creator-claude`) · Role: planner |
| **상태** | 승인 (Approved) — v1.5에서 **D-01** 확정(A·B안 기각 / `intl-conf` 폐기 · 3개 카테고리), v1.6에서 **D-06** 확정(레이아웃 컨테이너 3계층 분리). v1.7에서 **§6.1.2 Research Project PageView** 신설 — 신규 미결 **D-07**(🔴 Funder 실데이터 부재)·**D-08**(🟡 프로젝트 집합 범위)은 각 기본값으로 구현을 차단하지 않는다. 잔여 미결 D-02·D-03·D-05는 🟡 등급으로 각 기본값이 적용되어 승인을 보류하지 않는다. v1.9에서 **§8.7 색 토큰 알파 계약**(R-8.21~R-8.24)을 신설하고 **D-09**(Publications 인덱스 라우트)를 확정했다. v1.10에서 개발 팀장(Agy) 이의제기를 반영해 **D-09를 Overview 뷰로 재확정**하고 R-8.25·AC-41을 신설했다. v1.11에서 강조색 교체 커밋(`eaddc4f`)의 대비 붕괴를 규명해 **§8.7에 R-8.26~R-8.28**(강조색 대비 바닥값 · 색값 변경 재측정 의무 · 명암 반전 계약)을 신설하고 **D-10을 확정**(`--cobalt` #4A7CE8 유지 + `--cobalt-dark` #32549E 재조정), **AC-43~AC-46**(대비 게이트 Gate 10 및 그 폴트 인젝션)을 신설했다. 신규 미결 **D-11**(🟡 `text-vermillion` 소형 텍스트 30여 곳)은 현행 유지를 기본값으로 하여 구현을 차단하지 않는다. v1.12에서 개발 팀장(Agy) 이의제기(Job `7f136931`)를 반영해 **D-10의 배지·칩 전경/배경을 재확정**(`bg-cobalt text-ink` 폐기 → `bg-{accent}-dark text-ivory`)하고 **R-8.29**(반전 토큰 쌍의 `dark:` 불필요 원칙)·**AC-47**(팔레트 외 색 상한)을 신설했다. v1.13에서 Primary #4E77FF 전면 적용 커밋군(`ca4a7ee`~`082d71e`)을 검증해 **Gate 0~10 전량 통과 상태에서 AA 미달 6건·통일성 결함 9건**을 실측 확정하고, 게이트 범위 결함 3종에 대해 **R-8.30~R-8.32**(비텍스트 UI 대비 · 역할 단위 색 통일 · 게이트 면제 형식 제한)와 **AC-48~AC-50**(Gate 11 · 폴트 인젝션 F1~F9 · `docs/DESIGN.md` 동기화)을 신설했다. **§8.1을 실제 토큰값으로 동기화**하고 **D-10 확정값을 #345AD2로 개정**했다. 신규 미결 **D-12**(🔴 Primary 적용의 판정 기준 및 히어로 `Orchestration` 시맨틱 규약)는 **A안+B안 병행**을 기본값으로 하여 구현을 차단하지 않으며, **D-13**(🟡 `--vermillion` 반전 1.25)은 현행 유지 + 화이트리스트 등재를 기본값으로 한다. v1.14에서 개발 팀장(Agy) 이의제기(Job `4962a667`)를 반영해 **R-8.33**(토큰 등급 불변식)을 신설하고 **R-8.29를 강화**(강조색 `dark:` 변형 금지 · 현행 위반 38건)했으며, 다크 테마 `bg-paper` 소형 텍스트 **8곳의 AA 미달(4.37)****G-7**(AC-44 정규식이 다크 절반을 설계상 면제)을 확정해 **AC-51**을 신설했다. 신규 미결 **D-14**(🔴 `--cobalt` 반전 1.00)는 **A안(브랜드 고정 예외)** 을 기본값으로 하되 규격 완화를 포함하므로 **cline·GM 심사 전에는 집행하지 않는다** |
| **버전** | `2.0` |
| **작성일** | 2026-08-24 |
| **작성자** | agy (`herdr:creator-agy-01`) / claude (`herdr:planner-reviewer-claude-01`) · Role: worker / planner |
| **상태** | 승인 (Approved) — v2.0에서 **DM-03 및 AC-17 개정**(Go/Gin 백엔드 REST API 연동 및 `force-dynamic` + `cache: 'no-store'` 기반 실시간 동적 런타임 페칭 도입, 6개 데이터 라우트 `ƒ Dynamic` 및 4개 프레임워크 라우트 `○ Static` 분리 확정). |
| **대상 독자** | General Manager, Developer Team Leader(Agy), Reviewer Team Leader(Cline), 지도교수 |
| **선행 문서** | [`README.md`](./README.md), [`.agents/MULTI_AGENT_RULES.md`](./.agents/MULTI_AGENT_RULES.md), [`refer_landing_page/docs/DESIGN.md`](./refer_landing_page/docs/DESIGN.md) |
| **적용 범위** | `refer_landing_page/` (Next.js 14 App Router 애플리케이션) 전체 |
@@ -287,7 +287,7 @@ Home의 슬로건 히어로를 **실제 연구 프로젝트 카드 뷰**로 전
| :--- | :--- | :-- |
| DM-01 | 콘텐츠 데이터는 페이지 컴포넌트에서 분리해 **`content/` 디렉터리의 타입 지정 모듈**로 이관한다 (`content/members.ts`, `content/publications.ts`, `content/lectures.ts`, `content/standards.ts`) | MUST |
| DM-02 | 각 모듈은 명시적 TypeScript 인터페이스를 export 한다. `any` 사용 금지 | MUST |
| DM-03 *(v1.1 문구 정정)* | 데이터는 **빌드 타임 정적 상수**로 유지한다. 런타임 fetch·외부 API·DB를 도입하지 않는다. 전 라우트가 **빌드 타임 정적 프리렌더**(`○ Static` 또는 `● SSG`)를 유지해야 하며, `ƒ Dynamic` 라우트가 발생해서는 안 된다 (§8.5 R-8.16) | MUST |
| DM-03 *(v2.0 개정)* | 데이터는 **실시간 동적 런타임 페칭(`force-dynamic`, `cache: 'no-store'`)**으로 백엔드 API(`http://localhost:8080/api/v1`)로부터 조회한다. `content/*.ts` 모듈은 백엔드 미가동/장애 시의 **그레이스풀 폴백(Fallback) 데이터셋**으로 유지한다. 6개 데이터 라우트는 `ƒ Dynamic`으로 렌더링되며, 4개 프레임워크 라우트는 `○ Static`을 유지한다 (§8.5 R-8.16, §10 AC-17) | MUST |
| DM-04 | 각 페이지 상단의 `// CUSTOMIZATION HOOK` 주석 규약(`DESIGN.md` "콘텐츠 우선" 원칙)은 `content/` 모듈로 이전하되 **규약 자체는 유지**한다 | MUST |
### 7.2 스키마 정의 (계약)
@@ -648,7 +648,7 @@ Job `343b5808` 검증에서 본문 좌우 여백·최대 폭 미적용(B1)이,
**품질 게이트**
- [ ] AC-15 `npm run lint` 통과 (경고 0)
- [ ] AC-16 `npx tsc --noEmit` 통과
- [ ] AC-17 *(v1.1 개정)* `npm run build` 성공. 빌드 리포트에 **`ƒ (Dynamic)` 라우트가 0건**이고 전 라우트가 `○ (Static)` 또는 `● (SSG)`이다 — `●`도 빌드 타임 정적 프리렌더이므로 충족으로 판정한다 (§8.5 R-8.16)
- [ ] AC-17 *(v2.0 개정)* `npm run build` 성공. 빌드 리포트에 **정확히 6개 데이터 라우트가 `ƒ (Dynamic)`**(`force-dynamic`)이고 **4개 프레임워크/메타데이터 라우트가 `○ (Static)`**으로 분리되어 생성됨을 확인한다 (§7.1 DM-03)
- [ ] AC-18 First Load JS 증가분 ≤ 30 kB
- [ ] AC-19 4종 뷰포트에서 가로 스크롤 없음
- [ ] AC-20 신규 Tailwind 반응형 유틸리티가 빌드 CSS에 실제 생성됨을 확인 (JIT 누락 시 빌드는 성공하나 조판이 무너짐)
@@ -1049,3 +1049,4 @@ G-1은 **N6**(Gate 8 C1/C2 공허 단정, 10라운드 미해소)과 **동일 유
| 1.12 | 2026-07-31 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `7f136931`) 반영. **D-10 배지·칩 결정 재확정** — v1.11의 `bg-cobalt text-ink dark:text-ivory`(5.03 / 4.82)는 채택안 C의 원칙(작은 요소는 `-dark` 라우팅)을 **소형 텍스트 12곳에만 적용하고 동일 크기 등급의 상태 칩 2곳에는 적용하지 않은 일관성 결함**이었음을 인정하고 폐기, **`bg-{accent}-dark text-ivory`** 로 교체(6.38~6.43 / 6.57~6.72). 이의제기의 권장 전경 `text-white dark:text-ivory`(7.25)는 **미채택** — `-dark` 배경에서는 `ivory`만으로 양 테마가 통과해 **팔레트 외 색 `white`와 `dark:` 변형이 모두 불필요**하다(**R-8.29 신설**). 아울러 권장안을 그대로 적용하면 `standardization:87-89` 삼항식의 나란한 두 칩 상대휘도 대비가 **1.19 → 1.54로 악화**됨을 실측해, vermillion 칩도 `-dark`로 통일하여 **1.01** 균형을 달성(**R-8.26에 배지·칩 라우팅 및 짝 칩 등급 일치 조항 추가**). 배지류를 **부류 A(`text-[0.65rem]` 상태 칩 4곳) / 부류 B(인터랙티브 내비·토글 5곳)** 로 분리해 **AC-42 개정**, `-dark`가 **배경**으로 쓰이는 조합을 **AC-43 검사 대상에 추가**(v1.11 목록은 전경만 다루어 칩 배경을 놓쳤다), **AC-47 신설**(팔레트 외 색 상한 8건 화이트리스트), **AC-46에 폴트 인젝션 ⑤·⑥ 추가**(폐기된 v1.11 칩 패턴 및 `white` 증가 검출). 부류 A vermillion 칩 2곳의 배경이 #D9342B → #A8231C로 어두워지는 **시각 변경은 GM 승인 사항으로 명시**하되 차단 사유가 아님을 기록. Job `3d3b823a` |
| 1.13 | 2026-07-31 | claude (planner) | 테마 색상 통일성 검증 계획 수립 (`eaddc4f`→`082d71e` 4개 커밋). **Gate 0~10 전량 통과·빌드 exit 0인 상태에서 WCAG AA 미달 6건·통일성 결함 9건이 실재**함을 실측으로 확정 — 게이트 통과가 곧 오탐 신호였다. 원인은 게이트 **범위 결함 3종**: ⓐ **AC-44의 vermillion 절반이 구조적으로 무효**(면제 조항 `"text-vermillion" in line`이 판정 정규식과 항상 동시 매치 — 실소스 4라인 + 주입 2건으로 재현), ⓑ AC-45가 `-dark` 2종만 검사해 **`--cobalt` 반전 대비 1.00**(D-10 근본 원인의 재발)을 미검출, ⓒ AC-43이 소스의 전경/배경 쌍을 보지 않아 `bg-cobalt-dark text-white`(다크 2.85)를 통과. **R-8.30 신설**(비텍스트 UI 3:1 · 알파는 합성 후 산정 — `focus:ring-cobalt/50` **1.80** · `hover:border-cobalt/60` **1.95** 검출), **R-8.31 신설**(역할 시그니처 단위 색 통일 — 괘선 6:1 · 키커 11:1 · 배지 전경 6:4 · hover 테두리 4종 · 포커스 2종, 지도교수 이메일 1값이 3색 3대비), **R-8.32 신설**(게이트 면제는 `(파일,행,사유)` 등재만 허용 · 라인 부분문자열 면제 금지 — **N6과 동일 유형**). **R-8.5·R-8.26·R-8.28 개정**, **AC-42·43·44·45·47 개정**, **AC-48~AC-50 신설**(Gate 11 C11-1~8 · 폴트 인젝션 F1~F9 · `docs/DESIGN.md` 동기화 4건). **§8.1 색상표를 실제 토큰값 `#4E77FF`/`#345AD2`로 동기화**하고 **D-10 확정값을 #345AD2로 개정**(paper 4.76, 여유 0.26 → R-12). **D-12 신설**(🔴 Primary 적용의 판정 기준 — 라이트 테마에서 #4E77FF가 정적으로 보이는 곳이 **2곳뿐**이며 인지색은 #345AD2. 히어로 `Orchestration`이 R-8.5·DESIGN.md §2를 위반해 cobalt로 변경되어 한 페이지 안에서 규약이 분열), **D-13 신설**(🟡 `--vermillion` 반전 1.25). **A-1 `lectures:89` 4.16 미교정 잔존**·**A-2 `not-found.tsx:22` 4.16 신규 발견**(어떤 AC에도 없던 항목)·**A-7 `::selection` 4.16 미등재** 확정. **D-11 실측 갱신**(23건, 최저 3.77). 색 교체 5회 연속 대비표 미첨부(R-10). Job `43f3f74a` |
| 1.14 | 2026-07-31 | claude (planner) | 개발 팀장(Agy) 기술 이의제기(Job `4962a667`) 반영. **구조적 통찰 채택** — 다크 `--cobalt`만 단독 상향하면 다크 지면 대비가 DEFAULT 9.2 > `-dark` 5.96으로 **계층이 역전**됨을 실측 확인, v1.13 §7 각주의 누락을 인정하고 **R-8.33 신설**(토큰 등급 불변식). 다만 불변식을 **절대 휘도 순서가 아니라 테마 상대 대비**로 정식화 — 절대 순서의 테마 간 뒤집힘은 vermillion 쌍에서도 동일한 **정상 동작**이며 현행 8개 셀 전부에서 성립하므로, 절대 휘도로 판정하면 정상 상태를 오판한다. **제안 수치 미채택** — 주 권장값 `#85ACFF`의 반전 대비는 **1.73**으로 조정을 촉발한 R-8.28(≥2.0) 자체를 충족하지 못하며, 제안 표의 `#345AD2` L=0.095는 **폐기된 #32549E의 값**(실제 0.1270)이라 `#B0CDFF` 반전 대비 2.50도 실제 **3.68**이다. **이의제기 지점에서 더 큰 결함 발굴** — 다크 `--cobalt`(#4D77FF)는 `bg-paper` 위 **4.37**이므로 v1.12가 지시한 `text-cobalt-dark dark:text-cobalt` 라우팅이 **다크 절반을 DEFAULT로 되돌려 8곳이 AA 미달**이다(**AC-51 신설**). 원인은 R-8.26의 "`paper` 기준 산정" 원칙을 **본 계획자가 라이트 테마에만 적용한 오류**로, v1.11 §6.5와 같은 형태의 누락이다. 게이트도 AC-44 정규식의 **부정 후방탐색 `(?<!dark:)`로 다크 절반을 설계상 면제**하고 있었다(**G-7** — G-1·G-2·G-3에 이은 네 번째 "검사를 절반만 한다" 유형). **처방을 토큰 조정에서 `dark:` 변형 제거로 재설계** — `-dark`는 반전 토큰이므로 `dark:` 없이 4개 셀 전부(5.26/4.76/6.57/5.96)를 통과한다. **R-8.29 강화**(강조색 `dark:` 금지 · 정본 패턴 3종 · 현행 위반 **38건**), **R-8.26·AC-43·AC-44·AC-48(C11-9)·AC-49(F10·F11, 총 11건) 개정**. **D-14 신설**(🔴 `--cobalt` 반전 1.00 — A안 브랜드 고정 예외 권고 / B안 대칭 상향 정정 수치 `#A8BCFF`+`#C7D4FF`). A안은 본 계획자가 작성한 R-8.28의 완화를 포함하므로 **cline·GM 별도 심사 대상**으로 명시했고, 안전망 유지 근거로 `#1B3A8A` 역주입 시 AC-43 즉시 실패(1.80)를 제시했다. Job `d35bc3e7` |
| 2.0 | 2026-08-24 | claude (planner) / agy (creator) | **실시간 동적 런타임 페칭 아키텍처 도입 (DM-03·AC-17·R-08 개정)**. Go/Gin 백엔드 REST API(`http://localhost:8080/api/v1`) 연동 및 `force-dynamic` + `cache: 'no-store'` 적용으로 전 페이지 실시간 데이터 바인딩 지원. 6개 데이터 라우트를 `ƒ (Dynamic)`으로, 4개 프레임워크 라우트를 `○ (Static)`으로 분리. 기존 `content/*.ts` 정적 상수를 장애 시 그레이스풀 폴백 데이터셋으로 유지. E2E 테스트 게이트 2, 4, 7, 8을 라이브 서버 기반 검증으로 리팩터링. Job `258f6ff7` |
+3
View File
@@ -1,5 +1,8 @@
# Binaries and build outputs
/bin/
/export-content
/api
/seed
# SQLite databases and WAL journal files
*.db
+2
View File
@@ -21,6 +21,8 @@ seed: build
./$(BIN_DIR)/seed --db $(DB_PATH) --seed-dir seed/data
export-content: build
@echo "⚠️ Notice: Frontend has migrated to real-time dynamic runtime fetching (force-dynamic)."
@echo "export-content is deprecated to prevent re-introducing static content data files."
./$(BIN_DIR)/export-content --db $(DB_PATH) --out-dir ../refer_landing_page/content
fmt:
+9 -1
View File
@@ -50,9 +50,17 @@ func main() {
dbPath := flag.String("db", defaultDB, "Path to SQLite database file")
targetDir := flag.String("out-dir", "../refer_landing_page/content", "Target content directory")
force := flag.Bool("force", false, "Force export content files despite dynamic runtime architecture")
flag.Parse()
log.Printf("Exporting content from SQLite (%s) to TypeScript files in %s...", *dbPath, *targetDir)
if !*force {
log.Printf("⚠️ NOTICE: Frontend architecture has migrated to real-time dynamic runtime fetching (force-dynamic).")
log.Printf("Exporting static TypeScript files to %s is DEPRECATED to prevent re-introducing static fallback leaks.", *targetDir)
log.Printf("If you really need to export fallback files, pass the --force flag.")
return
}
log.Printf("Exporting content from SQLite (%s) to TypeScript files in %s (forced)...", *dbPath, *targetDir)
database, err := db.Connect(*dbPath)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
+6
View File
@@ -0,0 +1,6 @@
# ==============================================================================
# AI Agent Networking Lab (ANL) Next.js Frontend Configuration
# ==============================================================================
# Backend REST API Base URL (used at build time / SSR)
API_BASE_URL=http://localhost:8080/api/v1
@@ -1,9 +1,14 @@
import React from "react";
import { researchProjects } from "../../content/projects";
import type { ResearchProject } from "../../content/types";
import ProjectPageViewControls from "./ProjectPageViewControls";
export function ProjectPageView() {
const slugs = researchProjects.map((p) => p.slug);
interface ProjectPageViewProps {
projects?: ResearchProject[];
}
export function ProjectPageView({ projects: projectsProp }: ProjectPageViewProps = {}) {
const projects = projectsProp ?? [];
const slugs = projects.map((p) => p.slug);
const trackId = "research-projects-track";
return (
@@ -21,7 +26,7 @@ export function ProjectPageView() {
</h2>
</div>
<p className="text-xs text-ink-mute font-mono">
{researchProjects.length} core international standardization research projects
{projects.length} core international standardization research projects
</p>
</div>
@@ -34,13 +39,13 @@ export function ProjectPageView() {
aria-label="연구 프로젝트 목록"
className="flex gap-6 overflow-x-auto snap-x snap-mandatory scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden desktop:grid desktop:grid-cols-3 desktop:overflow-visible focus:outline-none focus:ring-1 focus:ring-cobalt/50 rounded"
>
{researchProjects.map((proj, idx) => (
{projects.map((proj, idx) => (
<article
key={proj.slug}
id={`project-${proj.slug}`}
role="group"
aria-roledescription="slide"
aria-label={`${researchProjects.length}개 중 ${idx + 1}번째 프로젝트`}
aria-label={`${projects.length}개 중 ${idx + 1}번째 프로젝트`}
className="snap-start shrink-0 min-w-0 w-full md:w-[calc(50%-12px)] desktop:w-full flex flex-col justify-between bg-paper p-6 rounded-lg border border-line hover:border-cobalt/60 transition-colors space-y-5 break-words [word-break:keep-all]"
>
<div className="space-y-4">
+6 -2
View File
@@ -1,13 +1,17 @@
import React from "react";
import type { Metadata } from "next";
import { semesters } from "../../content/lectures";
import { getSemesters } from "../../lib/api";
export const metadata: Metadata = {
title: "Lectures",
description: "AI Agent Networking Lab (ANL) lecture courses and curriculum history",
};
export default function LecturesPage() {
export const dynamic = "force-dynamic";
export default async function LecturesPage() {
const semesters = (await getSemesters()) ?? [];
const recentSemesters = semesters.slice(0, 3);
const olderSemesters = semesters.slice(3);
+7 -2
View File
@@ -1,6 +1,6 @@
import React from "react";
import type { Metadata } from "next";
import { activeMembers, alumni } from "../../content/members";
import { getMembers, getAlumni } from "../../lib/api";
import { ProfessorDetailsDialog } from "./_components/ProfessorDetailsDialog";
export const metadata: Metadata = {
@@ -8,6 +8,8 @@ export const metadata: Metadata = {
description: "AI Agent Networking Lab (ANL) professor, active researchers, and alumni directory",
};
export const dynamic = "force-dynamic";
const DEGREE_LABEL: Record<string, string> = {
Professor: "Professor",
PhD: "Ph. D.",
@@ -18,7 +20,10 @@ const DEGREE_LABEL: Record<string, string> = {
MS: "M. S.",
};
export default function MembersPage() {
export default async function MembersPage() {
const activeMembers = (await getMembers()) ?? [];
const alumni = (await getAlumni()) ?? [];
const phdStudents = activeMembers.filter(
(m) =>
m.degree === "PhD" ||
+17 -35
View File
@@ -2,26 +2,9 @@ import React from "react";
import Link from "next/link";
import HeroComposition from "./_components/HeroComposition";
import { ProjectPageView } from "./_components/ProjectPageView";
import { publications, patents } from "../content/publications";
import { standardsBodies } from "../content/standards";
import { getStatsSummary, getResearchAreaNames, getResearchProjects } from "../lib/api";
// CUSTOMIZATION HOOK: 14 Core Research Areas
const RESEARCH_AREAS: string[] = [
"Quick UDP Internet Connection (QUIC)",
"QUIC-based Mobility and Multi-Streaming Support",
"Services and Protocols for Internet-of-Things (IoT)",
"Constrained Application Protocol (CoAP)",
"In-Vehicle Infotainment (IVI)",
"Optical Wireless or Visible Light Communications (OWC/VLC)",
"Lighting Control Networks (PLASA E1.45) for LED-based VLC",
"Mobility Management: Distributed Mobility Control",
"Multicast Routing Protocols and Reliable Multicasting",
"mobile SCTP (mSCTP): Soft Handover in Transport Layer",
"Stream Control Transmission Protocol (SCTP)",
"Telecommunication Optimization: Network Planning and Management",
"TAC/TAL Configuration for Paging and Location Management",
"Tabu Search and Genetic Algorithm",
];
export const dynamic = "force-dynamic";
const INTERNATIONAL_SDOS = [
"IEC TC100: Multimedia Systems and Equipment",
@@ -31,16 +14,15 @@ const INTERNATIONAL_SDOS = [
"ISO/IEC JTC1/SC6: Reliable Multicasting",
];
export default function HomePage() {
const intlPubsCount = publications.filter(
(p) => p.category === "intl-journal-conf"
).length;
const patentCount = patents.length;
const stdDocsCount = standardsBodies.reduce(
(sum, b) =>
sum + b.projects.reduce((pSum, p) => pSum + p.documents.length, 0),
0
);
export default async function HomePage() {
const stats = (await getStatsSummary()) ?? {
intlPubsCount: 0,
patentCount: 0,
stdDocsCount: 0,
};
const researchAreas = (await getResearchAreaNames()) ?? [];
const researchProjects = (await getResearchProjects()) ?? [];
return (
<div className="container-content space-y-16 md:space-y-24 pt-10 md:pt-16 pb-16 md:pb-24">
@@ -66,7 +48,7 @@ export default function HomePage() {
<div className="pt-6 border-t border-line grid grid-cols-3 gap-4">
<div>
<div className="font-serif text-3xl text-cobalt-dark font-normal">
{intlPubsCount}
{stats.intlPubsCount}
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1 leading-tight">
International Journals &amp; Conference
@@ -74,7 +56,7 @@ export default function HomePage() {
</div>
<div>
<div className="font-serif text-3xl text-cobalt-dark font-normal">
{stdDocsCount}
{stats.stdDocsCount}
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1 leading-tight">
Standardization Contributions
@@ -82,7 +64,7 @@ export default function HomePage() {
</div>
<div>
<div className="font-serif text-3xl text-cobalt-dark font-normal">
{patentCount}
{stats.patentCount}
</div>
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1 leading-tight">
Patent<br />
@@ -101,7 +83,7 @@ export default function HomePage() {
</section>
{/* Research Project PageView Component */}
<ProjectPageView />
<ProjectPageView projects={researchProjects} />
{/* 14 Research Areas Grid */}
<section className="space-y-8 pt-16 md:pt-20 border-t border-line">
@@ -118,12 +100,12 @@ export default function HomePage() {
</h2>
</div>
<p className="text-xs text-ink-mute font-mono">
{RESEARCH_AREAS.length} fundamental networking protocols and AI system domains
{researchAreas.length} fundamental networking protocols and AI system domains
</p>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
{RESEARCH_AREAS.map((area, idx) => {
{researchAreas.map((area, idx) => {
const no = String(idx + 1).padStart(2, "0");
const isOdd = (idx + 1) % 2 !== 0;
@@ -2,7 +2,7 @@ import React from "react";
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { CategoryNav } from "../_components/CategoryNav";
import { publications, patents } from "../../../content/publications";
import { getPublications, getPatents } from "../../../lib/api";
import { PUB_CATEGORIES } from "../../../content/categories";
import { PubCategory } from "../../../content/types";
@@ -23,14 +23,9 @@ export function generateMetadata({ params }: PageProps): Metadata {
};
}
// SSG static pre-rendering (● SSG) for Next.js 14
export function generateStaticParams() {
return PUB_CATEGORIES.map((cat) => ({
category: cat.slug,
}));
}
export const dynamic = "force-dynamic";
export default function PublicationCategoryPage({ params }: PageProps) {
export default async function PublicationCategoryPage({ params }: PageProps) {
const { category } = params;
const currentCat = PUB_CATEGORIES.find((c) => c.slug === category);
@@ -40,6 +35,15 @@ export default function PublicationCategoryPage({ params }: PageProps) {
const slug = category as PubCategory;
const publications = (await getPublications()) ?? [];
const patents = (await getPatents()) ?? [];
const counts = {
"intl-journal-conf": publications.filter((p) => p.category === "intl-journal-conf").length,
"domestic-journal-conf": publications.filter((p) => p.category === "domestic-journal-conf").length,
patent: patents.length,
};
let records: any[] = [];
if (slug === "patent") {
records = patents.map((p) => ({
@@ -82,7 +86,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
{/* Category Nav Cards */}
<section className="pt-6 md:pt-8 border-t border-line">
<CategoryNav currentCategory={slug} />
<CategoryNav currentCategory={slug} counts={counts} />
</section>
{/* Record List Section */}
@@ -1,17 +1,25 @@
import React from "react";
import Link from "next/link";
import { PUB_CATEGORIES } from "../../../content/categories";
import { publications, patents } from "../../../content/publications";
import { PubCategory } from "../../../content/types";
export interface CategoryCounts {
"intl-journal-conf"?: number;
"domestic-journal-conf"?: number;
patent?: number;
}
interface CategoryNavProps {
currentCategory?: PubCategory;
counts?: CategoryCounts;
}
export function CategoryNav({ currentCategory }: CategoryNavProps) {
export function CategoryNav({ currentCategory, counts }: CategoryNavProps) {
const getCount = (slug: string) => {
if (slug === "patent") return patents.length;
return publications.filter((p) => p.category === slug).length;
if (counts?.[slug as keyof CategoryCounts] !== undefined) {
return counts[slug as keyof CategoryCounts]!;
}
return 0;
};
return (
+14 -3
View File
@@ -1,13 +1,24 @@
import type { Metadata } from "next";
import { CategoryNav } from "./_components/CategoryNav";
import { publications } from "../../content/publications";
import { getPublications, getPatents } from "../../lib/api";
export const metadata: Metadata = {
title: "Publications",
description: "AI Agent Networking Lab (ANL) research publications and patents overview",
};
export default function PublicationsIndexPage() {
export const dynamic = "force-dynamic";
export default async function PublicationsIndexPage() {
const publications = (await getPublications()) ?? [];
const patents = (await getPatents()) ?? [];
const counts = {
"intl-journal-conf": publications.filter((p) => p.category === "intl-journal-conf").length,
"domestic-journal-conf": publications.filter((p) => p.category === "domestic-journal-conf").length,
patent: patents.length,
};
// Selected representative publications (curated via database isHighlight flag)
const highlights = publications.filter((p) => p.isHighlight);
@@ -22,7 +33,7 @@ export default function PublicationsIndexPage() {
{/* Category Summary Cards */}
<section className="pt-6 md:pt-8 border-t border-line">
<CategoryNav />
<CategoryNav counts={counts} />
</section>
{/* Highlights Section */}
@@ -1,13 +1,16 @@
import React from "react";
import type { Metadata } from "next";
import { standardsBodies } from "../../content/standards";
import { getStandardsBodies } from "../../lib/api";
export const metadata: Metadata = {
title: "Standardization",
description: "AI Agent Networking Lab (ANL) international standardization research and contributions",
};
export default function StandardizationPage() {
export const dynamic = "force-dynamic";
export default async function StandardizationPage() {
const standardsBodies = (await getStandardsBodies()) ?? [];
return (
<div className="container-content py-8 sm:py-12 space-y-10 sm:space-y-14">
{/* Page Header */}
-804
View File
@@ -1,804 +0,0 @@
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
import { Semester } from "./types";
export const semesters: Semester[] = [
{
"term": "Spring",
"year": 2026,
"courses": [
{
"code": "COMP 0414",
"en": "Computer Networks"
},
{
"code": "ITEC 0401",
"en": "Capstone Design Project I"
}
]
},
{
"term": "Fall",
"year": 2025,
"courses": [
{
"code": "COMP 0432",
"en": "Topics in Software"
},
{
"code": "ITEC 0402",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2025,
"courses": [
{
"code": "ITEC 0201",
"en": "Introduction to Computer Science and Engineering"
},
{
"code": "EECS 0312",
"en": "Network Programming"
},
{
"code": "ITEC 0401",
"en": "Capstone Design Project I"
},
{
"code": "COMP 0461",
"en": "Problem-based Engineering Training Experiment"
}
]
},
{
"term": "Fall",
"year": 2024,
"courses": [
{
"code": "COMP 0432",
"en": "Topics in Software"
},
{
"code": "BIDA 0201",
"en": "Big Data Convergence Project"
},
{
"code": "ITEC 0402",
"en": "Capstone Design Project II"
},
{
"code": "COMP 0460",
"en": "Problem-based Engineering Training Experiment"
}
]
},
{
"term": "Spring",
"year": 2024,
"courses": [
{
"code": "BIDA 0201",
"en": "Big Data Convergence Project"
},
{
"code": "ITEC 0401",
"en": "Capstone Design Project I"
}
]
},
{
"term": "Fall",
"year": 2023,
"courses": [
{
"code": "SABBATICAL",
"en": "No Class (Sabbatical)",
"note": "Sabbatical"
}
]
},
{
"term": "Spring",
"year": 2023,
"courses": [
{
"code": "EECS 0312",
"en": "Network Programming"
},
{
"code": "BIDA 0201",
"en": "Big Data Convergence Project"
},
{
"code": "CICT 0703",
"en": "Software Convergence Project 3"
}
]
},
{
"term": "Fall",
"year": 2022,
"courses": [
{
"code": "CLTR 273001",
"en": "Problem Solving and Computing"
},
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "ITEC 401001",
"en": "Capstone Design Project I"
}
]
},
{
"term": "Spring",
"year": 2022,
"courses": [
{
"code": "EECS 312002",
"en": "Network Programming"
},
{
"code": "ITEC 402001",
"en": "Capstone Design Project II"
},
{
"code": "COMP 460001",
"en": "Problem-based Engineering Training Experiment"
},
{
"code": "CICT 701001",
"en": "Software Convergence Project 1"
}
]
},
{
"term": "Fall",
"year": 2021,
"courses": [
{
"code": "ITEC 402001",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2021,
"courses": [
{
"code": "CLTR 273001",
"en": "Problem Solving and Computing"
},
{
"code": "GLSO 218001",
"en": "Software Convergence Design 1"
},
{
"code": "ITEC 402001",
"en": "Capstone Design Project II"
},
{
"code": "COMP 778001",
"en": "Advanced Mobile Computing"
}
]
},
{
"term": "Fall",
"year": 2020,
"courses": [
{
"code": "ITEC 401003",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 401004",
"en": "Capstone Design Project I"
},
{
"code": "GLSO 228001",
"en": "Topics in Software Convergence IV"
}
]
},
{
"term": "Spring",
"year": 2020,
"courses": [
{
"code": "ITEC 401016",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402003",
"en": "Capstone Design Project II"
},
{
"code": "COMP 778001",
"en": "Advanced Mobile Computing"
}
]
},
{
"term": "Fall",
"year": 2019,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "ITEC 401003",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402016",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2019,
"courses": [
{
"code": "ITEC 201010",
"en": "Introduction to Computer Science and Engineering"
},
{
"code": "ITEC 401016",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402003",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2018,
"courses": [
{
"code": "ITEC 401005",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402017",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2018,
"courses": [
{
"code": "ITEC 401021",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402006",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2017,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "GLSO 213001",
"en": "Creative Convergence Design"
},
{
"code": "ITEC 401003",
"en": "Capstone Design Project I"
}
]
},
{
"term": "Spring",
"year": 2017,
"courses": [
{
"code": "EECS 312001",
"en": "Network Programming"
},
{
"code": "ITEC 401018",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402005",
"en": "Capstone Design Project II"
},
{
"code": "COME 742001",
"en": "Advanced Computer Networks"
}
]
},
{
"term": "Fall",
"year": 2016,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "COME 331013) and DS Applications (COMP 216002",
"en": "Data Structures"
},
{
"code": "ITEC 401004",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402016",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2016,
"courses": [
{
"code": "COMP 414003",
"en": "Computer Networks"
},
{
"code": "ITEC 401031",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402005",
"en": "Capstone Design Project II"
},
{
"code": "ITEC 402006",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2015,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "ITEC 401006",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 401007",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402022",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2015,
"courses": [
{
"code": "ITEC 201009",
"en": "Introduction to Computer Science and Engineering"
},
{
"code": "EECS 312001",
"en": "Network Programming"
},
{
"code": "EECS 312002",
"en": "Network Programming"
},
{
"code": "ITEC 401021",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402004",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2014,
"courses": [
{
"code": "ITEC 401006",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402021",
"en": "Capstone Design Project II"
},
{
"code": "COME 742001",
"en": "Advanced Computer Networks"
}
]
},
{
"term": "Spring",
"year": 2014,
"courses": [
{
"code": "COMP 414002",
"en": "Computer Networks"
},
{
"code": "ITEC 402004",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2013,
"courses": [
{
"code": "ITEC 401005",
"en": "Capstone Design Project I"
},
{
"code": "ITEC 402017",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Spring",
"year": 2013,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "ITEC 402005",
"en": "Capstone Design Project II"
}
]
},
{
"term": "Fall",
"year": 2012,
"courses": [
{
"code": "COMP 432001",
"en": "Topics in Software"
},
{
"code": "ITEC 401003",
"en": "Capstone Design Project I"
},
{
"code": "EECS 422021",
"en": "Senior Project II"
}
]
},
{
"term": "Spring",
"year": 2012,
"courses": [
{
"code": "COMP 214002",
"en": "Web Programming Design"
},
{
"code": "COMP 749001",
"en": "Computer Network Protocols"
},
{
"code": "ELEC 957001",
"en": "Next Generation Internet Technology"
}
]
},
{
"term": "Fall",
"year": 2011,
"courses": [
{
"code": "COME 311003",
"en": "Probability and Statistics: Class 1"
},
{
"code": "COME 311004",
"en": "Probability and Statistics: Class 2"
},
{
"code": "COMP 764001",
"en": "Internet Computing"
},
{
"code": "COMP 432001",
"en": "Topics in Software"
}
]
},
{
"term": "Spring",
"year": 2011,
"courses": [
{
"code": "COMP 214001",
"en": "Web Programming Design: Class 1"
},
{
"code": "COMP 214002",
"en": "Web Programming Design: Class 2"
}
]
},
{
"term": "Fall",
"year": 2010,
"courses": [
{
"code": "COME 311003",
"en": "Probability and Statistics"
},
{
"code": "COME 331004",
"en": "Data Structures"
},
{
"code": "EECS 422022",
"en": "Senior Project II"
}
]
},
{
"term": "Spring",
"year": 2010,
"courses": [
{
"code": "EECS 450001",
"en": "Internet Programming Design"
},
{
"code": "COMP 414003",
"en": "Computer Networks"
},
{
"code": "ELEC 957001",
"en": "Next-Generation Internet Technology"
},
{
"code": "EECS 408016",
"en": "Senior Project I"
}
]
},
{
"term": "Fall",
"year": 2009,
"courses": [
{
"code": "COME 31103",
"en": "Probability and Statistics"
},
{
"code": "COME 33105",
"en": "Data Structures"
},
{
"code": "EECS 42202",
"en": "Senior Project II"
}
]
},
{
"term": "Spring",
"year": 2009,
"courses": [
{
"code": "EECS 20704",
"en": "Introduction to Computer Science"
},
{
"code": "EECS 45001",
"en": "Internet Programming Design"
},
{
"code": "EECS 40803",
"en": "Senior Project I"
}
]
},
{
"term": "Fall",
"year": 2008,
"courses": [
{
"code": "COME 31103",
"en": "Probability and Statistics"
},
{
"code": "COMP 76401",
"en": "Internet Computing"
},
{
"code": "ELEC 75401",
"en": "Computer Network: Special"
}
]
},
{
"term": "Spring",
"year": 2008,
"courses": [
{
"code": "EECS 31401",
"en": "Internet Programming and Practices"
},
{
"code": "COMP 74901",
"en": "Computer Network Protocols"
}
]
},
{
"term": "Fall",
"year": 2007,
"courses": [
{
"code": "EECS 20703",
"en": "Introduction to Computer Science"
},
{
"code": "COME 31103",
"en": "Probability and Statistics"
},
{
"code": "COMP 41401",
"en": "Computer Networks"
}
]
},
{
"term": "Spring",
"year": 2007,
"courses": [
{
"code": "EECS 20109",
"en": "C Programming and Practices"
},
{
"code": "EECS 20704",
"en": "Introduction to Computer Science"
},
{
"code": "EECS 31401",
"en": "Internet Programming and Practices"
}
]
},
{
"term": "Fall",
"year": 2006,
"courses": [
{
"code": "EECS 20703",
"en": "Introduction to Computer Science"
},
{
"code": "COME 31103",
"en": "Probability and Statistics"
},
{
"code": "COMP 76401",
"en": "Internet Computing"
}
]
},
{
"term": "Spring",
"year": 2006,
"courses": [
{
"code": "EECS 20704",
"en": "Introduction to Computer Science"
},
{
"code": "EECS 31401",
"en": "Internet Programming and Practices"
},
{
"code": "COMP 75101",
"en": "Computer Performance Analysis"
}
]
},
{
"term": "Fall",
"year": 2005,
"courses": [
{
"code": "EECS 20703",
"en": "Introduction to Computer Science"
},
{
"code": "EECS 20806",
"en": "Data Structures"
},
{
"code": "COMP 41402",
"en": "Computer Network"
}
]
},
{
"term": "Spring",
"year": 2005,
"courses": [
{
"code": "EECS 20704",
"en": "Introduction to Computer Science"
},
{
"code": "EECS 49201",
"en": "Internet Programming Design"
},
{
"code": "COMP 74901",
"en": "Computer Network Protocols"
}
]
},
{
"term": "Fall",
"year": 2004,
"courses": [
{
"code": "ELEC 26104",
"en": "Computer Engineering"
},
{
"code": "COMP 22101",
"en": "Data Structures II"
},
{
"code": "EECS 31201",
"en": "Network Programming"
}
]
},
{
"term": "Spring",
"year": 2004,
"courses": [
{
"code": "EECS 20704",
"en": "Introduction to Computer Science"
},
{
"code": "COMP 21105",
"en": "Data Structures I"
}
]
}
];
-544
View File
@@ -1,544 +0,0 @@
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
import { Member, Alumnus } from "./types";
export const activeMembers: Member[] = [
{
"ko": "고석주",
"en": "Seok-Joo Koh",
"degree": "Professor"
},
{
"ko": "최동규",
"en": "Dong-Kyu Choi",
"degree": "PhD"
},
{
"ko": "남혜빈",
"en": "Hye-Been Nam",
"degree": "PhDCandidate"
},
{
"ko": "임도현",
"en": "Dohyeon Lim",
"degree": "PhDStudent"
},
{
"ko": "최준혁",
"en": "Jun-Hyeok Choi",
"degree": "PhDStudent"
},
{
"ko": "신광선",
"en": "Gwang-Seon Shin",
"degree": "MSCandidate"
},
{
"ko": "이환웅",
"en": "Hwan-Woong Lee",
"degree": "MSStudent"
},
{
"ko": "Hoang Anh Dang",
"en": "Hoang Anh Dang",
"degree": "MSStudent"
},
{
"ko": "김민희",
"en": "Minhee Kim",
"degree": "PhDCandidate",
"affiliation": "School of Computer Science and Engineering"
},
{
"ko": "김근솔",
"en": "Keun-Sol Kim",
"degree": "PhDCandidate",
"affiliation": "Department of Information Security"
},
{
"ko": "민성준",
"en": "Sung-Jun Min",
"degree": "PhDCandidate",
"affiliation": "Department of Information Science"
},
{
"ko": "최진원",
"en": "Jin-Won Choi",
"degree": "PhDStudent",
"affiliation": "Department of Science and Technology"
},
{
"ko": "정기수",
"en": "Ki-Soo Jung",
"degree": "PhDStudent",
"affiliation": "Department of Science and Technology"
},
{
"ko": "이미주",
"en": "Mi-Joo Lee",
"degree": "PhDStudent",
"affiliation": "Department of ICT Convergence"
},
{
"ko": "박유나",
"en": "Yunah Park",
"degree": "MSCandidate",
"affiliation": "Department of Information Security"
},
{
"ko": "박채영",
"en": "Chae-Young Park",
"degree": "MSCandidate",
"affiliation": "Department of Information Science"
}
];
export const alumni: Alumnus[] = [
{
"ko": "김동주",
"en": "Dongju Kim",
"degree": "PhD",
"graduatedAt": "2026-08",
"major": "Computer Science and Engineering",
"currentPosition": "He is now with Daegu Catholic University (대구가톨릭대학교) as Professor"
},
{
"ko": "황정미",
"en": "Jung-Mi Hwang",
"degree": "MS",
"graduatedAt": "2026-08",
"major": "Information Science",
"currentPosition": "She is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "배드로",
"en": "Dro Bae",
"degree": "MS",
"graduatedAt": "2026-02",
"major": "Industrial Engineering",
"currentPosition": "He is now with SK AX"
},
{
"ko": "권세기",
"en": "Se-Gi Kwon",
"degree": "MS",
"graduatedAt": "2026-02",
"major": "Information Security",
"currentPosition": "He is now with J Solution (제이솔루션) as CEO"
},
{
"ko": "조세현",
"en": "Se-Hyun Cho",
"degree": "PhD",
"graduatedAt": "2025-08",
"major": "Information Security",
"currentPosition": "He is now with Cyber Security Center"
},
{
"ko": "김윤성",
"en": "Yun-Seong Kim",
"degree": "MS",
"graduatedAt": "2025-08",
"major": "Data Convergence Computing",
"currentPosition": "He is now with SL (에스엘)"
},
{
"ko": "김수진",
"en": "Su-Jin Kim",
"degree": "MS",
"graduatedAt": "2025-08",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "김상태",
"en": "Sang-Tae Kim",
"degree": "MS",
"graduatedAt": "2025-08",
"major": "Industrial Engineering",
"currentPosition": "He is now with Oh-Sung Electronis (오성전자)"
},
{
"ko": "김규범",
"en": "Gyu-Bum Kim",
"degree": "MS",
"graduatedAt": "2025-08",
"major": "Computer Science and Engineering",
"currentPosition": "He is now with Korea Real Estate Board (한국부동산원)"
},
{
"ko": "정중화",
"en": "Joong-Hwa Jung",
"degree": "PhD",
"graduatedAt": "2025-02",
"currentPosition": "He is now with Lychee AI Coding Academy (리치AI코딩학원) as CEO"
},
{
"ko": "임도현",
"en": "Dohyeon Lim",
"degree": "MS",
"graduatedAt": "2025-02",
"major": "Computer Science and Engineering",
"currentPosition": "He is now with TakenSoft (테이큰소프트)"
},
{
"ko": "김소용",
"en": "So-Yong Kim",
"degree": "PhD",
"graduatedAt": "2024-08",
"currentPosition": "He is now with Catholic University of Leuven (UCLouvain) in Belgium"
},
{
"ko": "김민지",
"en": "Min-Ji Kim",
"degree": "MS",
"graduatedAt": "2024-02",
"currentPosition": "She is now with LG Electronics"
},
{
"ko": "안은지",
"en": "Eun-Ji Ahn",
"degree": "MS",
"graduatedAt": "2024-02",
"currentPosition": "S is now with National Security Research Institute (국가보안연구소)"
},
{
"ko": "오동규",
"en": "Dong-Kyu Oh",
"degree": "MS",
"graduatedAt": "2024-02",
"major": "Industrial Engineering",
"currentPosition": "He is now with Human-Plus (휴먼플러스)"
},
{
"ko": "김재성",
"en": "Jae-Seong Kim",
"degree": "MS",
"graduatedAt": "2023-08",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "최동규",
"en": "Dong-Kyu Choi",
"degree": "PhD",
"graduatedAt": "2023-02",
"currentPosition": "He is now with ISL in KNU"
},
{
"ko": "최진원",
"en": "Jin-Won Choi",
"degree": "MS",
"graduatedAt": "2023-02",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "김지은",
"en": "Ji-Eun Kim",
"degree": "MS",
"graduatedAt": "2023-02",
"major": "Information Science",
"currentPosition": "She is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "김철민",
"en": "Cheol-Min Kim",
"degree": "PhD",
"graduatedAt": "2022-08",
"currentPosition": "He is now with KETI (한국전자기술연구원)"
},
{
"ko": "김경식",
"en": "Kyung-Sik Kim",
"degree": "MS",
"graduatedAt": "2022-02",
"currentPosition": "He is now with Hyundai Autoever (현대오토에버)"
},
{
"ko": "김근수",
"en": "Keun-Soo Kim",
"degree": "MS",
"graduatedAt": "2022-02",
"currentPosition": "He is now with Shinhan Bank (신한은행)"
},
{
"ko": "정성우",
"en": "Seong-Woo Jeong",
"degree": "MS",
"graduatedAt": "2022-02",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "최민철",
"en": "Min-Cheol Choi",
"degree": "MS",
"graduatedAt": "2022-02",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "장준희",
"en": "Jun-Hee Jang",
"degree": "MS",
"graduatedAt": "2021-08",
"major": "Computer Science and Engineering",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "Muhammad Hafidh Firmansyah",
"en": "Muhammad Hafidh Firmansyah",
"degree": "MS",
"graduatedAt": "2021-08",
"currentPosition": "He is now with State Polytechnic University of Jember in Indonesia"
},
{
"ko": "장강민",
"en": "Kang-Min Jang",
"degree": "MS",
"graduatedAt": "2021-02",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "이홍근",
"en": "Hong-Keun Lee",
"degree": "MS",
"graduatedAt": "2021-02",
"major": "Information Science",
"currentPosition": "He is now with NIA (한국지능정보사회진흥원)"
},
{
"ko": "남혜빈",
"en": "Hyebeen Nam",
"degree": "MS",
"graduatedAt": "2021-02",
"currentPosition": "She is now with ISL in KNU"
},
{
"ko": "최낙중",
"en": "Nak-Jung Choi",
"degree": "PhD",
"graduatedAt": "2020-02",
"currentPosition": "He is now with Agency for Defense Development (국방과학연구소)"
},
{
"ko": "정민우",
"en": "Min-Woo Jung",
"degree": "MS",
"graduatedAt": "2019-02",
"currentPosition": "He is now with LG Electronics (LG전자)"
},
{
"ko": "강형우",
"en": "Hyung-Woo Kang",
"degree": "PhD",
"graduatedAt": "2018-08",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "최예찬",
"en": "Ye-Chan Choi",
"degree": "MS",
"graduatedAt": "2018-02",
"currentPosition": "He is now with LG-CNS"
},
{
"ko": "최상일",
"en": "Sang-Il Choi",
"degree": "PhD",
"graduatedAt": "2017-02",
"currentPosition": "He is now with Daegu Catholic University (대구가톨릭대학교) as a professor"
},
{
"ko": "박진호",
"en": "Jin-Ho Park",
"degree": "MS",
"graduatedAt": "2016-08",
"currentPosition": "He is now with BiThumb (빗썸코리아)"
},
{
"ko": "석성윤",
"en": "Sung-Yoon Seok",
"degree": "MS",
"graduatedAt": "2016-08",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김진규",
"en": "Jin-Kyu Kim",
"degree": "MS",
"graduatedAt": "2016-08",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김재철",
"en": "Jae-Cheol Kim",
"degree": "MS",
"graduatedAt": "2016-08",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김지인",
"en": "Ji-In Kim",
"degree": "PhD",
"graduatedAt": "2016-02",
"currentPosition": "He is now with FAM Tech. (팸텍) as CEO"
},
{
"ko": "김우주",
"en": "Woo-Ju Kim",
"degree": "MS",
"graduatedAt": "2016-02",
"currentPosition": "He is now with ZCube (지큐브)"
},
{
"ko": "박정섭",
"en": "Jung-Sub Park",
"degree": "MS",
"graduatedAt": "2015-08",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "최종명",
"en": "Jong-Myoung Choi",
"degree": "MS",
"graduatedAt": "2015-02",
"currentPosition": "He is now with NP Communications"
},
{
"ko": "이종관",
"en": "Jong-Kwan Lee",
"degree": "MS",
"graduatedAt": "2014-02",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Hanwha Ocean (한화오션)"
},
{
"ko": "이상헌",
"en": "Sang-Hun Lee",
"degree": "MS",
"graduatedAt": "2014-02",
"currentPosition": "He is now with Inno Wireless"
},
{
"ko": "민경욱",
"en": "Kyeong-Wook Min",
"degree": "MS",
"graduatedAt": "2013-02",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김상헌",
"en": "Sang-Heon Kim",
"degree": "MS",
"graduatedAt": "2013-02",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "Moneeb Gohar",
"en": "Moneeb Gohar",
"degree": "PhD",
"graduatedAt": "2012-08",
"currentPosition": "He is now with Department of Computer Science, Bahria University (at Islamabad) in Pakistan"
},
{
"ko": "박재완",
"en": "Jae-Wan Park",
"degree": "MS",
"graduatedAt": "2012-02",
"currentPosition": "He is now with Ministry of Justice (Immigration Information Center, 법무부)"
},
{
"ko": "이재경",
"en": "Jae-Kyoung Lee",
"degree": "MS",
"graduatedAt": "2012-02",
"currentPosition": "She is now with Korea Transportation Satefy Authority (한국교통안전공단)"
},
{
"ko": "하원기",
"en": "Won-Ki Ha",
"degree": "MS",
"graduatedAt": "2012-02",
"major": "Samsung Electronics Program",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김근희",
"en": "Keun-Hee Kim",
"degree": "MS",
"graduatedAt": "2012-02",
"major": "Samsung Electronics Program",
"currentPosition": "She is now with Samsung Electronics (삼성전자)"
},
{
"ko": "권순홍",
"en": "Soon-Hong Kwon",
"degree": "MS",
"graduatedAt": "2010-02",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "김동필",
"en": "Dong-Phil Kim",
"degree": "PhD",
"graduatedAt": "2009-02",
"currentPosition": "He is now with National Security Research Institute (국가보안연구소)"
},
{
"ko": "최린",
"en": "Lin Cui",
"degree": "PhD",
"graduatedAt": "2009-02",
"currentPosition": "He is now with Tianjin University of Technology and Education in China"
},
{
"ko": "이동화",
"en": "Dong-Hwa Lee",
"degree": "MS",
"graduatedAt": "2009-02",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
},
{
"ko": "박재성",
"en": "Jae-Sung Park",
"degree": "MS",
"graduatedAt": "2008-08",
"currentPosition": "He is now with Gom&Company (곰앤컴퍼니)"
},
{
"ko": "주수경",
"en": "Su-Kyoung Ju",
"degree": "MS",
"graduatedAt": "2008-02",
"currentPosition": "She is now with Youngjin Univ. (영진전문대학교)"
},
{
"ko": "윤성식",
"en": "Sung-Shik Yoon",
"degree": "MS",
"graduatedAt": "2007-08",
"currentPosition": "He is now with Jo-Il Textile Processing Company (조일가공) as CEO"
},
{
"ko": "김상태",
"en": "Sang-Tae Kim",
"degree": "MS",
"graduatedAt": "2007-02",
"currentPosition": "He is now with LG Electronics (LG전자)"
},
{
"ko": "하종식",
"en": "Jong-Shik Ha",
"degree": "MS",
"graduatedAt": "2007-02",
"currentPosition": "He is now with Samsung Electronics (삼성전자)"
}
];
-53
View File
@@ -1,53 +0,0 @@
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
import { ResearchProject } from "./types";
export const researchProjects: ResearchProject[] = [
{
"slug": "iot-standards",
"title": "AIoT & AI-RAN",
"abstract": "AIoT & AI-RAN investigates agent-based IoT architectures in which autonomous AI agents are distributed across sensor, edge, and radio access layers. The project studies agent-to-agent coordination over high-performance transport (gRPC/QUIC) bridged with constrained edge protocols (MQTT/CoAP), targeting AI-RAN designs for distributed autonomous control.",
"keywords": [
"AIoT",
"agent-based IoT",
"AI-RAN",
"constrained edge protocols",
"distributed autonomous control"
],
"standardsTrack": "National Research Foundation of Korea (NRF)",
"standardsOrg": "NRF",
"period": "2026 ~ Present",
"deliverables": []
},
{
"slug": "ccis",
"title": "MCM",
"abstract": "Multilateral and Collaborative Metaverse (MCM) refers to metaverse systems in which two or more participants simultaneously interact within a shared virtual space for a common collaborative purpose. Unlike single-user or purely competitive environments, an MCM service is defined by coordinated participation: its intended outcome cannot be achieved by a single user acting alone. The project defines general requirements, service classification, and a gap analysis against existing standards.",
"keywords": [
"MCM",
"metaverse",
"shared virtual space",
"coordinated participation",
"service classification"
],
"standardsTrack": "IEC/TC 100/WG 12 supported by KEIT",
"standardsOrg": "IEC TC100",
"period": "2026 ~ Present",
"deliverables": []
},
{
"slug": "vlc-iot",
"title": "MVQM",
"abstract": "Management of Visual Quality in Metaverse Systems (MVQM) is a conceptual and functional approach for managing visual quality by dynamically adapting visual data delivery in response to user context, device capability, and network conditions. It emphasizes user-centric, context-aware quality management over static system-centric optimization, covering hybrid visual assets, level of detail, and area of interest adaptation.",
"keywords": [
"MVQM",
"visual quality",
"hybrid visual assets",
"level of detail",
"area of interest"
],
"standardsTrack": "IEC/TC 100/WG 12 supported by KEIT",
"standardsOrg": "IEC TC100",
"period": "2026 ~ Present",
"deliverables": []
}
];
File diff suppressed because it is too large Load Diff
-387
View File
@@ -1,387 +0,0 @@
// AUTO-GENERATED by backend/cmd/export-content — DO NOT EDIT BY HAND
import { StandardsBody } from "./types";
export const standardsBodies: StandardsBody[] = [
{
"org": "IEC TC100",
"full": "Multimedia Systems and Equipment",
"scope": "international",
"period": "2018 ~ Present",
"role": "Convenor",
"projects": [
{
"wg": "WG12",
"name": "Multimedia Systems and Equipment for Metaverse (IEC 63614)",
"documents": [
{
"title": "Multimedia Systems and Equipment for Metaverse - Part 1: Genaral",
"ref": "IEC TR 63614-1 (May 2026)",
"status": "TR",
"publishedAt": "2026-05"
},
{
"title": "Multimedia Systems and Equipment for Metaverse - Part 2: Classification",
"ref": "IEC TS 63614-2 (May 2026)",
"status": "TS",
"publishedAt": "2026-05"
},
{
"title": "Multimedia Systems and Equipment for Metaverse - Part 3: Gap Analysis",
"ref": "IEC TR 63614-3 (March 2026)",
"status": "TR",
"publishedAt": "2026-03"
},
{
"title": "Multimedia Systems and Equipment for Metaverse - Part 4: AI Use Cases",
"ref": "IEC WDTR 63614-4 (In Progress)",
"status": "InProgress"
}
]
},
{
"wg": "WG11(Convenor)",
"name": "User's QoE for Multimedia Conference Services (IEC 63478)",
"documents": [
{
"title": "User's QoE for Multimedia Conference Services - Part 1: General",
"ref": "IEC TR 63478-1 (November 2023)",
"status": "TR",
"publishedAt": "2023-11"
},
{
"title": "User's QoE for Multimedia Conference Services - Part 2: Requirments",
"ref": "IEC IS 63478-2 (October 2025)",
"status": "IS",
"publishedAt": "2025-10"
},
{
"title": "User's QoE for Multimedia Conference Services - Part 3: Measurement Methods",
"ref": "IEC CDV 63478-3 (In Progress)",
"status": "InProgress"
}
]
},
{
"wg": "WG30(TA17)",
"name": "Infotainment Services for Public Vehicles (IEC 63479)",
"documents": [
{
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 1: General",
"ref": "IEC TR 63479-1 (November 2023)",
"status": "TR",
"publishedAt": "2023-11"
},
{
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 2: Requirements",
"ref": "IEC IS 63479-2 (February 2026)",
"status": "IS",
"publishedAt": "2026-02"
},
{
"title": "Infotainment Services for Public Vehicles (PVIS) - Part 3: Framework",
"ref": "IEC IS 63479-3 (January 2026)",
"status": "IS",
"publishedAt": "2026-01"
}
]
},
{
"wg": "WG30(TA17)",
"name": "Configurable Car Infotainment Services (IEC 63246)",
"documents": [
{
"title": "Configurable Car Infotainment Services (CCIS) - Part 1: General",
"ref": "IEC 63246-1 (August 2021)",
"status": "IS",
"publishedAt": "2021-08"
},
{
"title": "Configurable Car Infotainment Services (CCIS) - Part 2: Requirements",
"ref": "IEC 63246-2 (January 2022)",
"status": "IS",
"publishedAt": "2022-01"
},
{
"title": "Configurable Car Infotainment Services (CCIS) - Part 3: Framework",
"ref": "IEC 63246-3 (January 2022)",
"status": "IS",
"publishedAt": "2022-01"
},
{
"title": "Configurable Car Infotainment Services (CCIS) - Part 4: Protocol",
"ref": "IEC TR 63246-4 (December 2022)",
"status": "TR",
"publishedAt": "2022-12"
}
]
}
]
},
{
"org": "ISO/IEC JTC1/SC41",
"full": "Internet of Things and Digital Twin",
"scope": "international",
"period": "2020 ~ Present",
"projects": [
{
"wg": "WG",
"name": "(WG5) IoT-based Management of Tangible Cultural Heritage Assets (ISO/IEC TR 30189)",
"documents": [
{
"title": "Part 1: Framework",
"ref": "ISO/IEC TR 30189-1 (March 2025)",
"status": "TR",
"publishedAt": "2025-03"
},
{
"title": "Part 2: Use Cases",
"ref": "ISO/IEC CDTR 30189-2 (In Progress)",
"status": "InProgress"
}
]
}
]
},
{
"org": "ITU-T SG20",
"full": "IoT Services based on VLC",
"scope": "international",
"period": "2018 ~ 2020",
"projects": [
{
"wg": "Main",
"name": "IoT Services based on VLC",
"documents": [
{
"title": "Framework of IoT Services based on Visible Light Communications (VLC)",
"ref": "ITU-T Recommendation Y.4465 (January 2020)",
"status": "IS",
"publishedAt": "2020-01"
},
{
"title": "Functional Architecture for IoT Services based on VLC",
"ref": "ITU-T Recommendation Y.4474 (August 2020)",
"status": "IS",
"publishedAt": "2020-08"
}
]
}
]
},
{
"org": "TTA/PG425",
"full": "가시광 통신 기반 사물인터넷 서비스",
"scope": "domestic",
"period": "2018 ~ 2020",
"projects": [
{
"wg": "Main",
"name": "가시광 통신 기반 사물인터넷 서비스",
"documents": [
{
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 1 부: 네트워크 모델 및 요구사항",
"ref": "TTAK.KO-10.1158-part1 (2019년 12월)",
"status": "IS",
"publishedAt": "2019-12"
},
{
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 2 부: 프레임워크",
"ref": "TTAK.KO-10.1158-part2 (2020년 12월)",
"status": "IS",
"publishedAt": "2020-12"
},
{
"title": "가시광 통신 기반 사물인터넷 서비스 - 제 3 부: 프로토콜",
"ref": "TTAK.KO-10.1158-part3 (2020년 12월)",
"status": "IS",
"publishedAt": "2020-12"
}
]
}
]
},
{
"org": "ITU-T SG13",
"full": "Mobility Management",
"scope": "international",
"period": "2002 ~ 2012",
"projects": [
{
"wg": "Main",
"name": "Mobility Management",
"documents": [
{
"title": "NNI Mobility Management Requirements for SBI2K",
"ref": "ITU-T Supplement to Recommendation Q.Sup52 (December 2004)",
"status": "IS",
"publishedAt": "2004-12"
},
{
"title": "Mobility Management Requirements for Next-Generation Networks",
"ref": "ITU-T Recommendation Q.1706/Y.2801 (July 2006)",
"status": "IS",
"publishedAt": "2006-07"
},
{
"title": "Framework of IPv6 Multi-homing for NGN",
"ref": "ITU-T Recommendation Y.2052 (Feb. 2008)",
"status": "IS",
"publishedAt": "2008-02"
},
{
"title": "Generic Framework of Mobility Management for Next-Generation Networks",
"ref": "ITU-T Recommendation Q.1707/Y.2804 (Feb. 2008)",
"status": "IS",
"publishedAt": "2008-02"
},
{
"title": "Framework of Location Management for Next-Generation Networks",
"ref": "ITU-T Recommendation Q.1708/Y.2805 (October 2008)",
"status": "IS",
"publishedAt": "2008-10"
},
{
"title": "Framework of Handover Control for Next-Generation Networks",
"ref": "ITU-T Recommendation Q.1709/Y.2806 (October 2008)",
"status": "IS",
"publishedAt": "2008-10"
},
{
"title": "Framework of Mobility Management in Service Stratum for NGN",
"ref": "ITU-T Recommendation Y.2809 (November 2011)",
"status": "IS",
"publishedAt": "2011-11"
}
]
}
]
},
{
"org": "ISO/IEC JTC1/SC6",
"full": "WG7: Network and Transport",
"scope": "international",
"period": "2000 ~ 2014",
"projects": [
{
"wg": "Enhanced Communication Transport Protocol (ECTP)",
"name": "ISO/IEC 14476",
"documents": [
{
"title": "ECTP-1: Specification of Simplex Multicast Transport",
"ref": "ITU-T Recommendation X.606 (October 2001) ISO/IEC IS 14476-1 (April 2002)",
"status": "IS",
"publishedAt": "2001-10"
},
{
"title": "ECTP-2: Specification of QoS Management for Simplex Multicast Transport",
"ref": "ITU-T Recommendation X.606.1 (February 2003) ISO/IEC IS 14476-2 (June 2003)",
"status": "IS",
"publishedAt": "2003-02"
},
{
"title": "ECTP-3: Specification of Duplex Multicast Transport",
"ref": "ITU-T Recommendation X.607 (February 2007) ISO/IEC IS 14476-3 (August 2008)",
"status": "IS",
"publishedAt": "2007-02"
},
{
"title": "ECTP-4: Specification of QoS Management for Duplex Multicast Transport",
"ref": "ITU-T Recommendation X.607.1 (November 2008) ISO/IEC IS 14476-4 (April 2010)",
"status": "IS",
"publishedAt": "2008-11"
},
{
"title": "ECTP-5: Specification of N-plex Multicast Transport",
"ref": "ITU-T Recommendation X.608 (February 2007) ISO/IEC IS 14476-5 (August 2008)",
"status": "IS",
"publishedAt": "2007-02"
},
{
"title": "ECTP-6: Specification of QoS Management for N-plex Multicast Transport",
"ref": "ITU-T Recommendation X.608.1 (November 2008) ISO/IEC IS 14476-6 (April 2010)",
"status": "IS",
"publishedAt": "2008-11"
}
]
},
{
"wg": "Relayed Multicast Protocol (RMCP)",
"name": "ISO/IEC 16512",
"documents": [
{
"title": "RMCP-1: Framework",
"ref": "ITU-T Recommendation X.603 (April 2004) ISO/IEC IS 16512-1",
"status": "IS",
"publishedAt": "2004-04"
},
{
"title": "RMCP-2: Specification of Simplex Group Applications",
"ref": "ITU-T Recommendation X.603.1 (February 2007) ISO/IEC IS 16512-2",
"status": "IS",
"publishedAt": "2007-02"
},
{
"title": "RMCP-3: Specification of N-plex Group Applications",
"ref": "ITU-T Recommendation X.603.2 (September 2010) ISO/IEC IS 16512-3",
"status": "IS",
"publishedAt": "2010-09"
}
]
},
{
"wg": "Mobile Multicast Communications (MMC)",
"name": "ISO/IEC 24793",
"documents": [
{
"title": "MMC-1: MMC Framework",
"ref": "ITU-T Recommendation X.604 (March 2010) ISO/IEC IS 24793-1",
"status": "IS",
"publishedAt": "2010-03"
},
{
"title": "MMC-2: MMC Protocol over Native IP Multicast Networks",
"ref": "ITU-T Recommendation X.604.1 (March 2010) ISO/IEC IS 24793-2",
"status": "IS",
"publishedAt": "2010-03"
},
{
"title": "MMC-3: MMC Protocol over Overlay Multicast Networks",
"ref": "ITU-T Recommendation X.604.2 (September 2010) ISO/IEC IS 24793-3",
"status": "IS",
"publishedAt": "2010-09"
}
]
},
{
"wg": "Future Network",
"name": "Problem Statement and Requirements (FN-PSR): ISO/IEC TR 29181",
"documents": [
{
"title": "Part 1: Overall Aspects",
"ref": "ISO/IEC TR 29181-1 (September 2012)",
"status": "TR",
"publishedAt": "2012-09"
},
{
"title": "Part 2: Naming and Addressing",
"ref": "ISO/IEC TR 29181-2 (December 2014)",
"status": "TR",
"publishedAt": "2014-12"
},
{
"title": "Part 3: Switching and Routing",
"ref": "ISO/IEC TR 29181-3 (April 2013)",
"status": "TR",
"publishedAt": "2013-04"
},
{
"title": "Part 4: Mobility",
"ref": "ISO/IEC TR 29181-4 (December 2013)",
"status": "TR",
"publishedAt": "2013-12"
}
]
}
]
}
];
+318
View File
@@ -0,0 +1,318 @@
import type {
Member,
Alumnus,
Publication,
Patent,
Semester,
StandardsBody,
ResearchProject,
PubCategory,
DocStatus,
} from "../content/types";
const API_BASE_URL = process.env.API_BASE_URL ?? "http://localhost:8080/api/v1";
const FETCH_TIMEOUT_MS = 3000;
interface APIResponse<T> {
data: T;
}
// Raw backend response schemas (snake_case)
interface BackendResearchProject {
id: number;
slug: string;
title: string;
abstract: string;
keywords: string[];
organization?: string | null;
standards_org?: string | null;
period?: string | null;
funder?: string | null;
}
interface BackendResearchArea {
id: number;
name_en: string;
name_kr?: string | null;
display_order: number;
}
interface BackendStatsSummary {
intl_publications: number;
standardization_docs: number;
patents: number;
}
interface BackendMember {
id: number;
name_ko: string;
name_en: string;
degree: string;
affiliation?: string | null;
email?: string | null;
is_advisor: boolean;
display_order: number;
}
interface BackendAlumnus {
id: number;
name_ko: string;
name_en: string;
degree: string;
graduated_at: string;
major?: string | null;
current_position?: string | null;
}
interface BackendPublication {
id: number;
category: string;
title: string;
authors?: string | null;
venue: string;
volume?: string | null;
published_at: string;
kci: boolean;
doi?: string | null;
is_highlight: boolean;
}
interface BackendPatent {
id: number;
title: string;
inventors: string;
application_no: string;
application_at: string;
registration_no?: string | null;
registration_at?: string | null;
country: string;
published_at: string;
}
interface BackendCourse {
id: number;
semester_id: number;
code: string;
name_en: string;
name_kr?: string | null;
note?: string | null;
display_order: number;
}
interface BackendSemester {
id: number;
year: number;
term: string;
courses: BackendCourse[];
}
interface BackendStandardDoc {
id: number;
body_id: number;
wg: string;
project_name: string;
title: string;
doc_ref: string;
status: string;
published_at?: string | null;
}
interface BackendStandardProject {
wg: string;
name: string;
documents: BackendStandardDoc[];
}
interface BackendStandardsBody {
id: number;
org: string;
full_name: string;
scope: string;
period: string;
role?: string | null;
display_order: number;
projects: BackendStandardProject[];
}
/**
* Generic fetch helper with bounded timeout and safe JSON handling.
* Returns data payload or null on any failure (network, timeout, non-2xx, parse error).
*/
async function apiGet<T>(path: string): Promise<T | null> {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
const res = await fetch(`${API_BASE_URL}${path}`, {
signal: controller.signal,
cache: "no-store",
});
clearTimeout(timeout);
if (!res.ok) {
return null;
}
const json = (await res.json()) as APIResponse<T>;
return json?.data ?? null;
} catch {
return null;
}
}
// --- Home Domain ---
export async function getResearchProjects(): Promise<ResearchProject[] | null> {
const raw = await apiGet<BackendResearchProject[]>("/research-projects");
if (!raw) return null;
return raw.map((p) => ({
slug: p.slug,
title: p.title,
abstract: p.abstract,
keywords: p.keywords,
standardsTrack: p.organization ?? undefined,
standardsOrg: p.standards_org ?? undefined,
period: p.period ?? undefined,
funder: p.funder ?? undefined,
deliverables: [],
}));
}
export async function getResearchAreaNames(): Promise<string[] | null> {
const raw = await apiGet<BackendResearchArea[]>("/research-areas");
if (!raw) return null;
return raw.map((a) => a.name_en);
}
export async function getStatsSummary(): Promise<{
intlPubsCount: number;
stdDocsCount: number;
patentCount: number;
} | null> {
const raw = await apiGet<BackendStatsSummary>("/stats/summary");
if (!raw) return null;
return {
intlPubsCount: raw.intl_publications,
stdDocsCount: raw.standardization_docs,
patentCount: raw.patents,
};
}
// --- Members Domain ---
export async function getMembers(): Promise<Member[] | null> {
const raw = await apiGet<BackendMember[]>("/members");
if (!raw) return null;
return raw.map((m) => ({
ko: m.name_ko,
en: m.name_en,
degree: m.degree as Member["degree"],
affiliation: m.affiliation ?? undefined,
}));
}
export async function getAlumni(): Promise<Alumnus[] | null> {
const raw = await apiGet<BackendAlumnus[]>("/alumni");
if (!raw) return null;
return raw.map((a) => ({
ko: a.name_ko,
en: a.name_en,
degree: a.degree as Alumnus["degree"],
graduatedAt: a.graduated_at,
major: a.major ?? undefined,
currentPosition: a.current_position ?? undefined,
}));
}
// --- Publications & Patents Domain ---
export async function getPublications(
category?: "intl-journal-conf" | "domestic-journal-conf"
): Promise<Publication[] | null> {
const raw = await apiGet<BackendPublication[]>(
category ? `/publications?category=${category}` : "/publications"
);
if (!raw) return null;
return raw.map((p) => ({
category: p.category as PubCategory,
title: p.title,
authors: p.authors ?? undefined,
venue: p.venue,
volume: p.volume ?? undefined,
publishedAt: p.published_at,
kci: p.kci,
doi: p.doi ?? undefined,
isHighlight: p.is_highlight,
}));
}
export async function getHighlights(): Promise<Publication[] | null> {
const raw = await apiGet<BackendPublication[]>("/publications/highlights");
if (!raw) return null;
return raw.map((p) => ({
category: p.category as PubCategory,
title: p.title,
authors: p.authors ?? undefined,
venue: p.venue,
volume: p.volume ?? undefined,
publishedAt: p.published_at,
kci: p.kci,
doi: p.doi ?? undefined,
isHighlight: p.is_highlight,
}));
}
export async function getPatents(): Promise<Patent[] | null> {
const raw = await apiGet<BackendPatent[]>("/patents");
if (!raw) return null;
return raw.map((p) => ({
category: "patent",
title: p.title,
inventors: p.inventors,
applicationNo: p.application_no,
applicationAt: p.application_at,
registrationNo: p.registration_no ?? undefined,
registrationAt: p.registration_at ?? undefined,
country: p.country,
publishedAt: p.published_at,
}));
}
// --- Lectures Domain ---
export async function getSemesters(): Promise<Semester[] | null> {
const raw = await apiGet<BackendSemester[]>("/semesters");
if (!raw) return null;
return raw.map((s) => ({
term: s.term as Semester["term"],
year: s.year,
courses: s.courses.map((c) => ({
code: c.code,
en: c.name_en,
note: c.note ?? undefined,
})),
}));
}
// --- Standardization Domain ---
export async function getStandardsBodies(): Promise<StandardsBody[] | null> {
const raw = await apiGet<BackendStandardsBody[]>("/standards-bodies");
if (!raw) return null;
return raw.map((b) => ({
org: b.org,
full: b.full_name,
scope: b.scope as StandardsBody["scope"],
period: b.period,
role: b.role ?? undefined,
projects: b.projects.map((p) => ({
wg: p.wg,
name: p.name,
documents: p.documents.map((d) => ({
title: d.title,
ref: d.doc_ref,
status: d.status as DocStatus,
publishedAt: d.published_at ?? undefined,
})),
})),
}));
}
@@ -1,11 +1,31 @@
#!/usr/bin/env python3
"""
Data Verification Gate (AC-07 / AC-08 / AC-10 / AC-11 / AC-30 / Live Database & API Pipeline Integrity)
Verifies:
1. Full data extraction from legacy HTML sources (16 members, 60 alumni, 68 intl, 80 dom, 75 pat, 45 sem, 6 bodies, 44 docs, 3 projects).
2. SQLite Backend Database (backend/anl.db) tables & row counts matching baseline.
3. Live Go REST API endpoints (http://localhost:8080/api/v1) data losslessness & schema consistency if server is active.
4. ISO 8601 strict date formatting across all entities.
5. Publication volume cross-check and keyword substring assertions.
"""
import sys
import os
import re
import sqlite3
import json
import urllib.request
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(BASE_DIR, "refer_landing_page", "scripts", "extract"))
ROOT_PROJECT_DIR = os.path.dirname(BASE_DIR)
DB_PATH = os.path.join(ROOT_PROJECT_DIR, "backend", "anl.db")
if not os.path.exists(DB_PATH):
DB_PATH = os.path.join(BASE_DIR, "backend", "anl.db")
API_BASE_URL = os.environ.get("API_BASE_URL", "http://localhost:8080/api/v1")
MONTH_MAP = {
"january": "01", "jan": "01", "february": "02", "feb": "02", "march": "03", "mar": "03",
"april": "04", "apr": "04", "may": "05", "june": "06", "jun": "06", "july": "07", "jul": "07",
@@ -25,42 +45,22 @@ def verify():
total_pubs = len(intl_p) + len(dom_p) + len(pat_p)
std_docs_count = sum(len(p["documents"]) for b in bodies for p in b["projects"])
print("=== DATA VERIFICATION GATE (AC-07 / AC-08 / AC-10 / AC-11 / AC-30 / VOLUME CROSS-CHECK) ===")
print(f"Active Members: {len(active_m)} (Expected: 16)")
print(f"Alumni: {len(alumni_m)} (Expected: 60)")
print(f"International Papers: {len(intl_p)} (Expected: 68)")
print(f"Domestic Papers: {len(dom_p)} (Expected: 80)")
print(f"Patents: {len(pat_p)} (Expected: 75)")
print(f"Total Publications: {total_pubs} (Expected: 223)")
print(f"Lectures Semesters: {len(semesters)} (Expected: 45)")
print(f"Standard Bodies: {len(bodies)} (Expected: 6)")
print(f"Standard Documents: {std_docs_count} (Expected: 44)")
print(f"Research Projects: {len(projects)} (Expected: 3)")
print("=== DATA VERIFICATION GATE (AC-07 / AC-08 / AC-10 / AC-11 / AC-30 / LIVE PIPELINE INTEGRITY) ===")
print(f"[HTML Source Extraction Baseline]")
print(f" Active Members: {len(active_m)} (Expected: 16)")
print(f" Alumni: {len(alumni_m)} (Expected: 60)")
print(f" International Papers: {len(intl_p)} (Expected: 68)")
print(f" Domestic Papers: {len(dom_p)} (Expected: 80)")
print(f" Patents: {len(pat_p)} (Expected: 75)")
print(f" Total Publications: {total_pubs} (Expected: 223)")
print(f" Lectures Semesters: {len(semesters)} (Expected: 45)")
print(f" Standard Bodies: {len(bodies)} (Expected: 6)")
print(f" Standard Documents: {std_docs_count} (Expected: 44)")
print(f" Research Projects: {len(projects)} (Expected: 3)")
errors = []
if len(projects) != 3:
errors.append(f"Research Projects count mismatch: got {len(projects)}, expected 3")
# AC-30: Keyword Substring Verification & AC-32 Deliverable WG Attribution
for proj in projects:
abstract = proj.get("abstract", "")
for kw in proj.get("keywords", []):
if kw not in abstract:
errors.append(f"AC-30 Keyword Violation in project '{proj.get('slug')}': keyword '{kw}' not in abstract")
if proj["slug"] == "ccis":
for d in proj.get("deliverables", []):
if "63246" not in d["ref"]:
errors.append(f"AC-32 Violation: CCIS project deliverable '{d['ref']}' does not belong to CCIS WG (IEC 63246)")
# N1: Title artifact check (No trailing quotes or commas in publication titles)
for p in intl_p + dom_p + pat_p:
t = p.get("title", "")
if re.search(r'["“”]|,$', t):
errors.append(f"N1 Title Artifact Violation in publication '{p.get('id')}': title '{t}' contains quotes or trailing comma")
# 1. Count checks
# 1. Source Extraction Integrity Checks
if len(active_m) != 16:
errors.append(f"Active Members count mismatch: got {len(active_m)}, expected 16")
if len(alumni_m) != 60:
@@ -79,28 +79,175 @@ def verify():
errors.append(f"Standard Bodies count mismatch: got {len(bodies)}, expected 6")
if std_docs_count != 44:
errors.append(f"Standard Documents count mismatch: got {std_docs_count}, expected 44")
if len(projects) != 3:
errors.append(f"Research Projects count mismatch: got {len(projects)}, expected 3")
# 2. File checks (BOM, Strict ISO 8601, and Semantic Date Ranges)
# 2. SQLite Backend Database Verification
if os.path.exists(DB_PATH):
print(f"\n[SQLite Database Verification ({DB_PATH})]")
try:
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
def get_count(table, where=""):
query = f"SELECT count(*) FROM {table} {where}"
cur.execute(query)
return cur.fetchone()[0]
db_counts = {
"members": get_count("members"),
"alumni": get_count("alumni"),
"intl_pubs": get_count("publications", "WHERE category='intl-journal-conf'"),
"dom_pubs": get_count("publications", "WHERE category='domestic-journal-conf'"),
"patents": get_count("patents"),
"semesters": get_count("semesters"),
"standards_bodies": get_count("standards_bodies"),
"standard_documents": get_count("standard_documents"),
"research_projects": get_count("research_projects"),
}
for k, v in db_counts.items():
print(f" DB {k}: {v}")
if db_counts["members"] != 16:
errors.append(f"SQLite members count: {db_counts['members']}, expected 16")
if db_counts["alumni"] != 60:
errors.append(f"SQLite alumni count: {db_counts['alumni']}, expected 60")
if db_counts["intl_pubs"] != 68:
errors.append(f"SQLite intl publications count: {db_counts['intl_pubs']}, expected 68")
if db_counts["dom_pubs"] != 80:
errors.append(f"SQLite dom publications count: {db_counts['dom_pubs']}, expected 80")
if db_counts["patents"] != 75:
errors.append(f"SQLite patents count: {db_counts['patents']}, expected 75")
if db_counts["semesters"] != 45:
errors.append(f"SQLite semesters count: {db_counts['semesters']}, expected 45")
if db_counts["standards_bodies"] != 6:
errors.append(f"SQLite standards bodies count: {db_counts['standards_bodies']}, expected 6")
if db_counts["standard_documents"] != 44:
errors.append(f"SQLite standard documents count: {db_counts['standard_documents']}, expected 44")
if db_counts["research_projects"] != 3:
errors.append(f"SQLite research projects count: {db_counts['research_projects']}, expected 3")
conn.close()
print(" ✅ SQLite Database records match expected baseline exactly")
except Exception as e:
errors.append(f"SQLite DB verification failed: {e}")
else:
print(f"\n⚠️ SQLite database not found at {DB_PATH}, skipping direct DB query")
# 3. Live Go REST API Endpoint Verification (if backend is active)
is_api_live = False
try:
req = urllib.request.Request(f"{API_BASE_URL}/health", headers={"User-Agent": "DataVerifier"})
with urllib.request.urlopen(req, timeout=1) as resp:
if resp.status == 200:
is_api_live = True
except Exception:
is_api_live = False
if is_api_live:
print(f"\n[Live Go REST API Verification ({API_BASE_URL})]")
try:
def fetch_json(endpoint):
req = urllib.request.Request(f"{API_BASE_URL}{endpoint}", headers={"User-Agent": "DataVerifier"})
with urllib.request.urlopen(req, timeout=3) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data.get("data", [])
stats = fetch_json("/stats/summary")
api_members = fetch_json("/members")
api_alumni = fetch_json("/alumni")
api_intl = fetch_json("/publications?category=intl-journal-conf")
api_dom = fetch_json("/publications?category=domestic-journal-conf")
api_patents = fetch_json("/patents")
api_semesters = fetch_json("/semesters")
api_standards = fetch_json("/standards-bodies")
api_projects = fetch_json("/research-projects")
api_std_docs_count = sum(len(p.get("documents", [])) for b in api_standards for p in b.get("projects", []))
print(f" API stats summary: {stats}")
print(f" API members count: {len(api_members)}")
print(f" API alumni count: {len(api_alumni)}")
print(f" API publications (intl): {len(api_intl)}, (dom): {len(api_dom)}")
print(f" API patents count: {len(api_patents)}")
print(f" API semesters count: {len(api_semesters)}")
print(f" API standards bodies: {len(api_standards)} (docs: {api_std_docs_count})")
print(f" API projects count: {len(api_projects)}")
if stats.get("intl_publications") != 68:
errors.append(f"API stats intl_publications mismatch: {stats.get('intl_publications')}, expected 68")
if stats.get("standardization_docs") != 44:
errors.append(f"API stats standardization_docs mismatch: {stats.get('standardization_docs')}, expected 44")
if stats.get("patents") != 75:
errors.append(f"API stats patents mismatch: {stats.get('patents')}, expected 75")
if len(api_members) != 16:
errors.append(f"API members count: {len(api_members)}, expected 16")
if len(api_alumni) != 60:
errors.append(f"API alumni count: {len(api_alumni)}, expected 60")
if len(api_intl) != 68:
errors.append(f"API intl publications count: {len(api_intl)}, expected 68")
if len(api_dom) != 80:
errors.append(f"API dom publications count: {len(api_dom)}, expected 80")
if len(api_patents) != 75:
errors.append(f"API patents count: {len(api_patents)}, expected 75")
if len(api_semesters) != 45:
errors.append(f"API semesters count: {len(api_semesters)}, expected 45")
if len(api_standards) != 6:
errors.append(f"API standards bodies count: {len(api_standards)}, expected 6")
if api_std_docs_count != 44:
errors.append(f"API standard documents count: {api_std_docs_count}, expected 44")
if len(api_projects) != 3:
errors.append(f"API research projects count: {len(api_projects)}, expected 3")
print(" ✅ Live REST API endpoints serve 100% losslessly verified data")
except Exception as e:
errors.append(f"Live API verification error: {e}")
else:
print(f"\n️ Live Go API server not responding at {API_BASE_URL} (tested offline DB mode)")
# 4. AC-30 Keyword Substring Verification & AC-32 Deliverable WG Attribution
for proj in projects:
abstract = proj.get("abstract", "")
for kw in proj.get("keywords", []):
if kw not in abstract:
errors.append(f"AC-30 Keyword Violation in project '{proj.get('slug')}': keyword '{kw}' not in abstract")
if proj["slug"] == "ccis":
for d in proj.get("deliverables", []):
if "63246" not in d["ref"]:
errors.append(f"AC-32 Violation: CCIS project deliverable '{d['ref']}' does not belong to CCIS WG (IEC 63246)")
# 5. Title artifact check (No trailing quotes or commas in publication titles)
for p in intl_p + dom_p + pat_p:
t = p.get("title", "")
if re.search(r'["“”]|,$', t):
errors.append(f"N1 Title Artifact Violation in publication '{p.get('id')}': title '{t}' contains quotes or trailing comma")
# 6. Strict ISO 8601 Date and BOM Verification
content_dir = os.path.join(BASE_DIR, "content")
date_regex = re.compile(r'^(19|20)\d{2}(-(0[1-9]|1[0-2]))?(-(0[1-9]|[12]\d|3[01]))?$')
for fname in ["members.ts", "publications.ts", "lectures.ts", "standards.ts"]:
fpath = os.path.join(content_dir, fname)
if not os.path.exists(fpath):
errors.append(f"Missing content file: {fname}")
continue
with open(fpath, "r", encoding="utf-8") as f:
text = f.read()
for fname in os.listdir(content_dir) if os.path.exists(content_dir) else []:
if fname.endswith(".ts"):
fpath = os.path.join(content_dir, fname)
with open(fpath, "r", encoding="utf-8") as f:
text = f.read()
if text.startswith('\ufeff'):
errors.append(f"BOM (U+FEFF) found in {fname}")
if text.startswith('\ufeff'):
errors.append(f"BOM (U+FEFF) found in {fname}")
for p in intl_p + dom_p + pat_p:
for field in ["publishedAt", "applicationAt", "registrationAt"]:
val = p.get(field)
if val and not date_regex.match(val):
errors.append(f"Invalid ISO 8601 date in publication '{p.get('title')}': {field}='{val}'")
dates = re.findall(r'"(publishedAt|applicationAt|registrationAt|graduatedAt)": "([^"]+)"', text)
for k, d in dates:
if not date_regex.match(d):
errors.append(f"Invalid ISO 8601 or out-of-bounds date in {fname}: {k}='{d}'")
for m in alumni_m:
val = m.get("graduatedAt")
if val and not date_regex.match(val):
errors.append(f"Invalid ISO 8601 date in alumnus '{m.get('ko')}': graduatedAt='{val}'")
# 3. Volume Cross-Check for Publications (Ensures volume date candidates match publishedAt)
# 7. Volume Cross-Check for Publications
MON_PAT = r'(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)'
for pub in intl_p + dom_p:
+179 -89
View File
@@ -1,44 +1,64 @@
#!/usr/bin/env python3
"""
E2E Test Suite Runner for ANL Homepage Development
E2E Test Suite Runner for ANL Homepage Development (v2.0 Dynamic Fetching Architecture)
Verifies:
0. Clean step (npm run clean -> removes .next and tsconfig.tsbuildinfo)
1. ESLint (npm run lint)
2. Next.js Static SSG Build (npm run build) with 0 dynamic routes
2. Next.js Dynamic/Static Hybrid Build (npm run build) with exact dynamic/static route allocation (AC-17)
3. TypeScript type check (npx tsc --noEmit)
4. Existence and content of static SSG routes HTML/body files
5. Data count losslessness & integrity gate (verify_counts.py)
4. Live Server Route Verification (Live HTTP status 200 & content)
5. Data count losslessness & fallback dataset integrity (verify_counts.py)
6. Light/Dark mode theme tokens and CSS attributes in source and build assets
7. DOM & HTML Content Assertions (Unittest)
7. Live DOM & HTML Content Assertions (Unittest)
8. Layout Architecture & No-Escape Gate (AC-25 / AC-26 / AC-40 / AC-41 / R-8.17)
9. Color Token Alpha Contract & Header Style Gate (AC-35 / AC-36 / AC-37)
10. WCAG 2.1 AA Color Contrast & Whitelist Gate (AC-43 / AC-44 / AC-45 / AC-47 / AC-48)
"""
import os
import sys
import time
import subprocess
import re
import urllib.request
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
REFER_DIR = os.path.join(ROOT_DIR, "refer_landing_page")
# Expected Static SSG Routes HTML/Body output paths relative to REFER_DIR
EXPECTED_SSG_ROUTES = [
os.path.join(".next", "server", "app", "index.html"),
os.path.join(".next", "server", "app", "members.html"),
os.path.join(".next", "server", "app", "publications.html"),
os.path.join(".next", "server", "app", "publications", "intl-journal-conf.html"),
os.path.join(".next", "server", "app", "publications", "domestic-journal-conf.html"),
os.path.join(".next", "server", "app", "publications", "patent.html"),
os.path.join(".next", "server", "app", "lectures.html"),
os.path.join(".next", "server", "app", "standardization.html"),
os.path.join(".next", "server", "app", "_not-found.html"),
os.path.join(".next", "server", "app", "robots.txt.body"),
os.path.join(".next", "server", "app", "sitemap.xml.body"),
os.path.join(".next", "server", "pages", "404.html"),
EXPECTED_DYNAMIC_ROUTES = [
"/",
"/members",
"/publications",
"/publications/[category]",
"/lectures",
"/standardization",
]
def run_cmd(cmd, cwd=REFER_DIR):
EXPECTED_STATIC_ROUTES = [
"/_not-found",
"/icon.svg",
"/robots.txt",
"/sitemap.xml",
]
LIVE_ROUTES = [
"/",
"/members",
"/publications",
"/publications/intl-journal-conf",
"/publications/domestic-journal-conf",
"/publications/patent",
"/lectures",
"/standardization",
"/_not-found",
]
def run_cmd(cmd, cwd=REFER_DIR, env=None):
print(f"Running: {' '.join(cmd)} (cwd: {cwd})")
res = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
run_env = os.environ.copy()
if env:
run_env.update(env)
res = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=run_env)
return res
def test_clean():
@@ -60,22 +80,44 @@ def test_lint():
return True
def test_build():
print("\n--- Gate 2: Next.js Static SSG Build Gate ---")
print("\n--- Gate 2: Next.js Dynamic/Static Hybrid Build Gate (AC-17) ---")
res = run_cmd(["npm", "run", "build"])
if res.returncode != 0:
print(f" SSG Build failed:\n{res.stdout}\n{res.stderr}")
print(f"❌ Build failed:\n{res.stdout}\n{res.stderr}")
return False
# Check stdout/stderr for dynamic routes (f Dynamic)
if "ƒ" in res.stdout or "Dynamic" in res.stdout:
# Search specifically for dynamic routes output line
lines = res.stdout.splitlines()
dynamic_lines = [l for l in lines if "ƒ" in l or "Dynamic" in l]
if dynamic_lines:
print(f"❌ Found dynamic route(s) in build report: {dynamic_lines}")
# Parse exact Next.js route table output lines (e.g. "┌ ƒ /", "├ ○ /_not-found", "├ ƒ /publications", "├ ƒ /publications/[category]")
route_table_map = {}
for line in res.stdout.splitlines():
m = re.match(r'^[┌├└]\s+([ƒ○●])\s+(\S+)', line.strip())
if m:
mode, route = m.group(1), m.group(2)
route_table_map[route] = mode
print(f"Parsed route modes from build table ({len(route_table_map)} routes): {route_table_map}")
# Check exact match for each dynamic route
for d_route in EXPECTED_DYNAMIC_ROUTES:
mode = route_table_map.get(d_route)
if mode != "ƒ":
print(f"❌ Gate 2 Violation: Expected dynamic route '{d_route}' has mode '{mode}', expected 'ƒ'")
return False
print("✅ Gate 2 PASSED: Static SSG Build successful (0 dynamic routes)")
# Check exact match for each static route
for s_route in EXPECTED_STATIC_ROUTES:
mode = route_table_map.get(s_route)
if mode not in ("", ""):
print(f"❌ Gate 2 Violation: Expected static route '{s_route}' has mode '{mode}', expected '' or ''")
return False
# Check that no other unexpected routes exist in table
all_expected = set(EXPECTED_DYNAMIC_ROUTES + EXPECTED_STATIC_ROUTES)
found_routes = set(route_table_map.keys())
if all_expected != found_routes:
print(f"❌ Gate 2 Violation: Route set mismatch. Missing: {all_expected - found_routes}, Extra: {found_routes - all_expected}")
return False
print("✅ Gate 2 PASSED: Hybrid dynamic/static route allocation verified (6 dynamic routes, 4 static routes with exact table matching)")
return True
def test_tsc():
@@ -87,35 +129,81 @@ def test_tsc():
print("✅ Gate 3 PASSED: TypeScript type check clean (exit code 0)")
return True
def test_ssg_routes():
print("\n--- Gate 4: Static SSG HTML Route Verification ---")
missing = []
for rel_path in EXPECTED_SSG_ROUTES:
full_path = os.path.join(REFER_DIR, rel_path)
if not os.path.exists(full_path):
missing.append(rel_path)
else:
size = os.path.getsize(full_path)
print(f" [OK] {rel_path} ({size} bytes)")
import socket
if missing:
print(f"❌ Missing {len(missing)} SSG routes:")
for m in missing:
print(f" - {m}")
return False
def get_free_port():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(('', 0))
return s.getsockname()[1]
print(f"✅ Gate 4 PASSED: All {len(EXPECTED_SSG_ROUTES)} static SSG routes exist and are non-empty")
def start_next_server(port=3000):
print(f"\nStarting live Next.js server on port {port}...")
server_env = os.environ.copy()
server_env["PORT"] = str(port)
proc = subprocess.Popen(
["npm", "run", "start"],
cwd=REFER_DIR,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
env=server_env,
)
ready = False
for _ in range(30):
try:
with urllib.request.urlopen(f"http://localhost:{port}/", timeout=1) as resp:
if resp.status == 200:
ready = True
break
except Exception:
time.sleep(0.5)
if not ready:
proc.terminate()
raise RuntimeError(f"Next.js server failed to become ready on port {port}")
print(f"Live Next.js server is ready at http://localhost:{port}")
return proc
def fetch_live_routes(base_url="http://localhost:3000"):
bodies = {}
for route in LIVE_ROUTES:
url = f"{base_url}{route}"
req = urllib.request.Request(url, headers={"User-Agent": "E2ETestRunner"})
try:
with urllib.request.urlopen(req, timeout=5) as resp:
content = resp.read().decode("utf-8")
bodies[route] = content
except urllib.error.HTTPError as e:
# Handle 404 for _not-found
if e.code == 404 and "_not-found" in route:
content = e.read().decode("utf-8")
bodies[route] = content
else:
raise e
return bodies
def test_live_routes(bodies):
print("\n--- Gate 4: Live Server Route Verification ---")
for route, body in bodies.items():
if len(body) == 0:
print(f"❌ Empty response body for route '{route}'")
return False
print(f" [OK] {route} ({len(body)} bytes)")
print(f"✅ Gate 4 PASSED: All {len(bodies)} live routes served non-empty responses")
return True
def test_data_counts():
print("\n--- Gate 5: Data Losslessness Integrity Gate ---")
print("\n--- Gate 5: Fallback Dataset Integrity Gate ---")
script_path = os.path.join(REFER_DIR, "scripts", "extract", "verify_counts.py")
res = run_cmd([sys.executable, script_path], cwd=ROOT_DIR)
if res.returncode != 0:
print(f"❌ Data count verification failed:\n{res.stdout}\n{res.stderr}")
return False
print(res.stdout.strip())
print("✅ Gate 5 PASSED: Data count integrity & volume losslessness verified")
print("✅ Gate 5 PASSED: Data count integrity & fallback dataset volume verified")
return True
def test_theme_tokens():
@@ -148,53 +236,43 @@ def test_theme_tokens():
print("✅ Gate 6 PASSED: Light/Dark theme CSS variables and Tailwind tokens present")
return True
def test_unittest_suite():
def test_unittest_suite(port=3000):
print("\n--- Gate 7: DOM & HTML Content Assertions (Unittest) ---")
test_path = os.path.join(REFER_DIR, "tests", "e2e_test_suite.py")
res = run_cmd([sys.executable, "-m", "unittest", test_path], cwd=ROOT_DIR)
res = run_cmd([sys.executable, "-m", "unittest", test_path], cwd=ROOT_DIR, env={"TEST_SERVER_URL": f"http://localhost:{port}"})
if res.returncode != 0:
print(f"❌ Unittest suite failed:\n{res.stdout}\n{res.stderr}")
return False
print("✅ Gate 7 PASSED: All 7 DOM & static HTML content assertion tests passed")
print("✅ Gate 7 PASSED: All 10 DOM & live HTML content assertion tests passed")
return True
def test_layout_architecture():
def test_layout_architecture(bodies):
print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / AC-40 / AC-41 / R-8.17) ---")
# 1) Check HTML SSG pages for container-content inside main across ALL SSG routes (C2, AC-25)
routes_to_check = EXPECTED_SSG_ROUTES
# 1) Check live HTML responses for container-content inside main across all routes (C2, AC-25)
main_hashes = {}
for rel_p in routes_to_check:
if not rel_p.endswith(".html"):
for route, html in bodies.items():
if "_not-found" in route or "404" in route:
continue
full_p = os.path.join(REFER_DIR, rel_p)
if not os.path.exists(full_p):
print(f"❌ Missing SSG route HTML: {rel_p}")
return False
with open(full_p, "r", encoding="utf-8") as f:
html = f.read()
# Verify <main> tag exists and does NOT contain max-w- or px-
main_match = re.search(r'<main(?:\s+[^>]*)?>(.*?)</main>', html, flags=re.S)
if not main_match:
print(f"❌ <main> tag not found in {rel_p}")
print(f"❌ <main> tag not found in {route}")
return False
main_inner = main_match.group(1)
# C1: Verify child wrapper inside <main> has container-content
if "container-content" not in main_inner and "_not-found" not in rel_p and "404" not in rel_p:
print(f"❌ container-content missing inside <main> in {rel_p}")
if "container-content" not in main_inner:
print(f"❌ container-content missing inside <main> in {route}")
return False
# AC-41: Check for duplicate <main> normalized content hashes (except 404 pages)
if "404" not in rel_p and "_not-found" not in rel_p:
norm_content = re.sub(r'\s+', '', main_inner)
if norm_content in main_hashes:
print(f"❌ AC-41 Violation: Duplicate <main> content found between {rel_p} and {main_hashes[norm_content]}")
return False
main_hashes[norm_content] = rel_p
# AC-41: Check for duplicate <main> normalized content hashes
norm_content = re.sub(r'\s+', '', main_inner)
if norm_content in main_hashes:
print(f"❌ AC-41 Violation: Duplicate <main> content found between {route} and {main_hashes[norm_content]}")
return False
main_hashes[norm_content] = route
# 2) Check source for forbidden escaped arbitrary classes (AC-26)
forbidden_terms = ["w-screen", "-mx-[50vw]", "min-[1240px]:"]
@@ -276,21 +354,20 @@ def test_color_contrast_gate():
def parse_tokens(block):
res = {}
for m in re.finditer(r'--([a-z-]+)-rgb:\s*(\d+)\s+(\d+)\s+(\d+);', block):
for m in re.finditer(r'--([a-z-]+-?d?a?r?k?)-rgb:\s*(\d+)\s+(\d+)\s+(\d+);', block):
res[m.group(1)] = (int(m.group(2)), int(m.group(3)), int(m.group(4)))
return res
light_tokens = parse_tokens(root_match.group(1))
dark_tokens = parse_tokens(dark_match.group(1))
# AC-43: Verify contrast thresholds for cobalt-dark and vermillion-dark on ivory and paper
cd_light = light_tokens.get("cobalt-dark")
vd_light = light_tokens.get("vermillion-dark")
ivory_light = light_tokens.get("ivory")
paper_light = light_tokens.get("paper")
if not all([cd_light, vd_light, ivory_light, paper_light]):
print("❌ AC-43 Failed: Missing required light RGB tokens")
print(f"❌ AC-43 Failed: Missing required light RGB tokens (found: {list(light_tokens.keys())})")
return False
ratio_cd_ivory = contrast_ratio(cd_light, ivory_light)
@@ -323,9 +400,6 @@ def test_color_contrast_gate():
print(f"❌ AC-45 Violation: Light/Dark vermillion-dark inversion contrast too low ({inversion_ratio_vd:.2f} < 2.0)")
return False
# AC-44: Verify text-cobalt or text-vermillion (DEFAULT) is not combined with small text sizes
# AC-47: Verify text-white utility usage <= 8 instances
# C11-9 (AC-48): Verify zero dark: modifier usage on accent colors (R-8.29)
white_count = 0
small_size_pattern = re.compile(r'text-(xs|sm|\[0\.65rem\]|\[0\.78rem\])')
default_color_pattern = re.compile(r'text-(cobalt|vermillion)(?![a-z-])')
@@ -339,18 +413,14 @@ def test_color_contrast_gate():
with open(fpath, "r", encoding="utf-8") as f:
content = f.read()
# AC-47 count
white_count += content.count("text-white")
# AC-44 & C11-9 check line by line
for line_idx, line in enumerate(content.splitlines(), start=1):
# C11-9: Zero dark: modifier on accent colors (allow dark:text-ivory / dark:group-open:text-ivory exceptions)
if dark_accent_pattern.search(line):
print(f"❌ C11-9 (AC-48) Violation: Forbidden dark: modifier on accent color at {fpath}:{line_idx}\n {line.strip()}")
return False
if small_size_pattern.search(line) and default_color_pattern.search(line):
# Ignore allowed exceptions (e.g. HeroComposition, Pillar bullets, or explicitly exempted titles)
if "Pillar #" in line or "w-1.5 h-1.5" in line or "text-3xl" in line:
continue
print(f"❌ AC-44 Violation: Small text combined with DEFAULT accent color at {fpath}:{line_idx}\n {line.strip()}")
@@ -365,7 +435,7 @@ def test_color_contrast_gate():
def main():
print("=" * 70)
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4")
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4 (v2.0 DYNAMIC)")
print("=" * 70)
success = True
@@ -373,14 +443,34 @@ def main():
success &= test_lint()
success &= test_build()
success &= test_tsc()
success &= test_ssg_routes()
success &= test_data_counts()
success &= test_theme_tokens()
success &= test_unittest_suite()
success &= test_layout_architecture()
success &= test_token_contracts()
success &= test_color_contrast_gate()
if not success:
print("\n❌ Build or static gates failed before live server execution.")
sys.exit(1)
# Start live Next.js server once and run Gate 4, Gate 7, Gate 8
port = get_free_port()
server_proc = None
try:
server_proc = start_next_server(port=port)
bodies = fetch_live_routes(f"http://localhost:{port}")
success &= test_live_routes(bodies)
success &= test_unittest_suite(port=port)
success &= test_layout_architecture(bodies)
finally:
if server_proc:
print("\nShutting down live Next.js server...")
server_proc.terminate()
try:
server_proc.wait(timeout=5)
except Exception:
server_proc.kill()
print("\n" + "-" * 70)
if success:
print("ALL E2E TEST SUITES PASSED CLEAN (Exit Code 0).")
+78 -21
View File
@@ -1,35 +1,94 @@
#!/usr/bin/env python3
"""
Python Unittest Suite for ANL Homepage E2E Verification
Parses prerendered SSG HTML files to assert DOM structure, text content, and theme attributes.
Queries live running Next.js server to assert DOM structure, text content, and theme attributes.
"""
import unittest
import os
import re
import time
import subprocess
import urllib.request
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SERVER_APP_DIR = os.path.join(BASE_DIR, ".next", "server", "app")
if not os.path.exists(SERVER_APP_DIR):
for candidate in [
os.path.join(os.getcwd(), "refer_landing_page", ".next", "server", "app"),
os.path.join(os.getcwd(), "website", ".next", "server", "app"),
os.path.join(os.getcwd(), ".next", "server", "app"),
]:
if os.path.exists(candidate):
SERVER_APP_DIR = candidate
break
TEST_SERVER_URL = os.environ.get("TEST_SERVER_URL", "http://localhost:3000")
class TestANLHomepageE2E(unittest.TestCase):
_server_proc = None
_html_cache = {}
@classmethod
def setUpClass(cls):
# Check if server is already responding at TEST_SERVER_URL
is_ready = False
try:
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
if resp.status == 200:
is_ready = True
except Exception:
is_ready = False
if not is_ready:
# Spawn local next start server
cls._server_proc = subprocess.Popen(
["npm", "run", "start"],
cwd=BASE_DIR,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
for _ in range(30):
try:
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
if resp.status == 200:
is_ready = True
break
except Exception:
time.sleep(0.5)
if not is_ready:
cls._server_proc.terminate()
raise RuntimeError(f"Next.js server failed to become ready at {TEST_SERVER_URL}")
@classmethod
def tearDownClass(cls):
if cls._server_proc:
cls._server_proc.terminate()
try:
cls._server_proc.wait(timeout=5)
except Exception:
cls._server_proc.kill()
def get_route_html(self, route):
if route in self._html_cache:
return self._html_cache[route]
url = f"{TEST_SERVER_URL}{route}"
req = urllib.request.Request(url, headers={"User-Agent": "E2ETestRunner"})
with urllib.request.urlopen(req, timeout=10) as resp:
html = resp.read().decode("utf-8")
self._html_cache[route] = html
return html
def read_html(self, relative_path):
full_path = os.path.join(SERVER_APP_DIR, relative_path)
self.assertTrue(os.path.exists(full_path), f"File {relative_path} does not exist")
with open(full_path, "r", encoding="utf-8") as f:
return f.read()
route_map = {
"index.html": "/",
"members.html": "/members",
"publications.html": "/publications",
os.path.join("publications", "intl-journal-conf.html"): "/publications/intl-journal-conf",
"publications/intl-journal-conf.html": "/publications/intl-journal-conf",
os.path.join("publications", "domestic-journal-conf.html"): "/publications/domestic-journal-conf",
"publications/domestic-journal-conf.html": "/publications/domestic-journal-conf",
os.path.join("publications", "patent.html"): "/publications/patent",
"publications/patent.html": "/publications/patent",
"lectures.html": "/lectures",
"standardization.html": "/standardization",
}
route = route_map.get(relative_path, f"/{relative_path}")
return self.get_route_html(route)
def test_01_home_page_structure(self):
"""TC-T1-F3: Verify Home page SSG HTML content and semantic structures."""
"""TC-T1-F3: Verify Home page HTML content and semantic structures."""
html = self.read_html("index.html")
# 1. Brand & Header titles
@@ -66,7 +125,7 @@ class TestANLHomepageE2E(unittest.TestCase):
self.assertIn("Project Editor", html)
def test_03_publications_category_routes(self):
"""TC-T1-F5: Verify Publications categories SSG static pages."""
"""TC-T1-F5: Verify Publications categories live dynamic pages."""
cats = [
("intl-journal-conf.html", "International Journal"),
("domestic-journal-conf.html", "Domestic Journal"),
@@ -91,8 +150,6 @@ class TestANLHomepageE2E(unittest.TestCase):
def test_06_theme_css_variables(self):
"""TC-T1-F2: Verify globals.css and built assets contain theme tokens."""
globals_path = os.path.join(BASE_DIR, "app", "globals.css")
if not os.path.exists(globals_path):
globals_path = os.path.join(os.getcwd(), "refer_landing_page", "app", "globals.css")
with open(globals_path, "r", encoding="utf-8") as f:
css = f.read()
@@ -100,7 +157,7 @@ class TestANLHomepageE2E(unittest.TestCase):
self.assertIn(token, css, f"Missing CSS variable {token} in globals.css")
def test_07_research_projects_pageview(self):
"""TC-T1-F7: Verify Research Project PageView content in Home static SSG HTML (AC-27 / AC-31)."""
"""TC-T1-F7: Verify Research Project PageView content in Home HTML."""
html = self.read_html("index.html")
# 1. Project PageView Title & Projects
@@ -118,7 +175,7 @@ class TestANLHomepageE2E(unittest.TestCase):
self.assertIn("IEC TC100: Multimedia Systems and Equipment", html)
self.assertIn("ISO/IEC JTC1/SC6: Reliable Multicasting", html)
# 4. Assert zero placeholder Funder labels (AC-31)
# 4. Assert zero placeholder Funder labels
self.assertNotIn("Funder:", html)
def test_08_publications_highlights_and_category_nav(self):