Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
22 changes: 16 additions & 6 deletions .github/workflows/testcase-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,34 @@ jobs:
total="$(printf '%s\n' "$clean_report" | awk -F': ' '/총 테스트 케이스/ { gsub(/[^0-9]/, "", $2); print $2; exit }')"
passed="$(printf '%s\n' "$clean_report" | awk -F': ' '/^성공:/ { gsub(/[^0-9]/, "", $2); print $2; exit }')"
failed="$(printf '%s\n' "$clean_report" | awk -F': ' '/^실패:/ { gsub(/[^0-9]/, "", $2); print $2; exit }')"
corpus_passed="$(printf '%s\n' "$clean_report" | awk '/^corpus:/ { split($2, counts, "/"); gsub(/[^0-9]/, "", counts[1]); print counts[1]; exit }')"
corpus_total="$(printf '%s\n' "$clean_report" | awk '/^corpus:/ { split($2, counts, "/"); gsub(/[^0-9]/, "", counts[2]); print counts[2]; exit }')"

total="${total:-0}"
passed="${passed:-0}"
failed="${failed:-0}"
corpus_passed="${corpus_passed:-0}"
corpus_total="${corpus_total:-0}"
success_rate="0.00"
if [ "$total" -gt 0 ]; then
success_rate="$(awk -v passed="$passed" -v total="$total" 'BEGIN { printf "%.2f", passed / total * 100 }')"
fi
corpus_failed=$((corpus_total - corpus_passed))
corpus_success_rate="0.00"
if [ "$corpus_total" -gt 0 ]; then
corpus_success_rate="$(awk -v passed="$corpus_passed" -v total="$corpus_total" 'BEGIN { printf "%.2f", passed / total * 100 }')"
else
echo '::error::NIKL corpus summary was not found in the testcase output.'
status=1
fi

{
echo '### Braillify testcase report'
echo
echo '| Metric | Count |'
echo '| --- | ---: |'
echo "| Total | $total |"
echo "| Passed | $passed |"
echo "| Failed | $failed |"
echo "| Success rate | $success_rate% |"
echo '| Suite | Passed | Total | Failed | Success rate |'
echo '| --- | ---: | ---: | ---: | ---: |'
echo "| Standard testcases | $passed | $total | $failed | $success_rate% |"
echo "| NIKL corpus | $corpus_passed | $corpus_total | $corpus_failed | $corpus_success_rate% |"
echo
echo 'Command: `cargo test test_by_testcase -- --nocapture`'
} > testcase-report.md
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ target/
node_modules
.df
test_status.json
/apps/landing/public/test-status/
.venv
__pycache__
.pytest_cache
Expand Down
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 12 additions & 11 deletions apps/landing/src/app/test-case/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import {
} from '@/components/side-bar'
import { FailedOnlyInput } from '@/components/test-case/FailedOnlyInput'
import { TestCaseFilter } from '@/components/test-case/filter/TestCaseFilter'
import { TestCaseList } from '@/components/test-case/list/TestCaseList'
import { TestCaseTable } from '@/components/test-case/table/TestCaseTable'
import { TestCaseDisplayBoundary } from '@/components/test-case/TestCaseDisplayBoundary'
import { TestCaseFilterContainer } from '@/components/test-case/TestCaseFilterContainer'
import { TestCaseFilterValue } from '@/components/test-case/TestCaseFilterValue'
Expand All @@ -24,6 +22,7 @@ import {
type TestCaseFilter as TestCaseFilterType,
TestCaseProvider,
} from '@/components/test-case/TestCaseProvider'
import { TestCaseResults } from '@/components/test-case/TestCaseResults'
import { TestCaseRuleContainer } from '@/components/test-case/TestCaseRuleContainer'
import { TestCaseStat } from '@/components/test-case/TestCaseStat'
import { TestCaseStatFiltered } from '@/components/test-case/TestCaseStatFiltered'
Expand All @@ -35,7 +34,7 @@ import {
TEST_CASE_FILTERS,
TEST_CASE_FILTERS_MAP,
} from '@/constants'
import type { TestStatusMap } from '@/types'
import type { TestStatusMap, TestStatusPageManifest } from '@/types'

export const metadata: Metadata = {
title: '테스트 케이스 - 한국·영어 점자 표준 검증',
Expand Down Expand Up @@ -84,13 +83,16 @@ export const metadata: Metadata = {
}

export default async function TestCasePage() {
const [testStatus, ruleMap] = await Promise.all([
const [testStatus, ruleMap, testStatusPageManifest] = await Promise.all([
readFile('../../test_status.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<TestStatusMap>,
readFile('../../rule_map.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<Record<string, { title: string; description: string }>>,
readFile('public/test-status/manifest.json', 'utf-8').then((data) =>
JSON.parse(data),
) as Promise<TestStatusPageManifest>,
])

// Dynamically create filter map based on rule_map keys
Expand All @@ -106,7 +108,6 @@ export default async function TestCasePage() {
},
]),
) as FilterTotalMap

let totalTest = 0
let totalFail = 0
let totalWorldTest = 0
Expand Down Expand Up @@ -173,12 +174,12 @@ export default async function TestCasePage() {
{value.description}
</Text>
</VStack>
<TestCaseDisplayBoundary option="type" value="table">
<TestCaseTable results={testStatus[key][6]} />
</TestCaseDisplayBoundary>
<TestCaseDisplayBoundary option="type" value="list">
<TestCaseList results={testStatus[key][6]} />
</TestCaseDisplayBoundary>
<TestCaseResults
pageInfo={testStatusPageManifest[key]}
results={testStatus[key][6]}
statusKey={key}
total={testStatus[key][0]}
/>
</TestCaseRuleContainer>
{currentClause !== nextClause && (
<Box bg="$text" h="1px" mx={['16px', null, null, '60px']} />
Expand Down
183 changes: 183 additions & 0 deletions apps/landing/src/components/test-case/TestCaseResults.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
'use client'

import { Button, Flex, Text, VStack } from '@devup-ui/react'
import { useEffect, useState } from 'react'

import type { TestStatus, TestStatusPageInfo } from '@/types'

import { TestCaseList } from './list/TestCaseList'
import { TestCaseTable } from './table/TestCaseTable'
import { useTestCase } from './TestCaseProvider'

interface TestCaseResultsProps {
pageInfo?: TestStatusPageInfo
results: TestStatus[6]
statusKey: string
total: number
}

/**
* Displays inline test results or lazily loads every page of a large result set.
*/
export function TestCaseResults({
pageInfo,
results,
statusKey,
total,
}: TestCaseResultsProps) {
const { options } = useTestCase()
const [page, setPage] = useState(1)
const [pagedResults, setPagedResults] = useState<TestStatus[6]>([])
const [isLoading, setIsLoading] = useState(Boolean(pageInfo))
const [error, setError] = useState('')

useEffect(() => {
if (!pageInfo) return

const abortController = new AbortController()
const encodedStatusKey = statusKey
.split('/')
.map((segment) => encodeURIComponent(segment))
.join('/')

setIsLoading(true)

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.12, ubuntu-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.13, ubuntu-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.11, ubuntu-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.14, ubuntu-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.13, macos-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.11, macos-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.11, windows-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.14, windows-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.12, macos-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.13, windows-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.12, windows-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders

Check warning on line 43 in apps/landing/src/components/test-case/TestCaseResults.tsx

View workflow job for this annotation

GitHub Actions / Test (3.14, macos-latest)

react(set-state-in-effect)

apps/landing/src/components/test-case/TestCaseResults.tsx:43:5: Calling setState synchronously within an effect can trigger cascading renders
setError('')
fetch(`/test-status/${encodedStatusKey}/page-${page}.json`, {
signal: abortController.signal,
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
return response.json() as Promise<TestStatus[6]>
})
.then((nextResults) => {
setPagedResults(nextResults)
setIsLoading(false)
})
.catch((fetchError: unknown) => {
if (
fetchError instanceof DOMException &&
fetchError.name === 'AbortError'
) {
return
}
setError('테스트 케이스를 불러오지 못했습니다.')
setIsLoading(false)
})

return () => abortController.abort()
}, [page, pageInfo, statusKey])

function handleFirstPage() {
setPage(1)
}

function handlePreviousPage() {
setPage((currentPage) => Math.max(1, currentPage - 1))
}

function handleNextPage() {
if (!pageInfo) return
setPage((currentPage) => Math.min(pageInfo.pageCount, currentPage + 1))
}

function handleLastPage() {
if (!pageInfo) return
setPage(pageInfo.pageCount)
}

const visibleResults = pageInfo ? pagedResults : results
const startIndex = pageInfo ? (page - 1) * pageInfo.pageSize : 0

return (
<VStack gap="20px">
{pageInfo ? (
<Flex
alignItems="center"
flexWrap="wrap"
gap="8px"
justifyContent="space-between"
>
<Text color="$caption" typography="body">
{startIndex + 1}–{Math.min(startIndex + pageInfo.pageSize, total)} /{' '}
{total.toLocaleString()}건
</Text>
<Flex alignItems="center" gap="8px">
<Button
_disabled={{ cursor: 'not-allowed', opacity: 0.4 }}
border="solid 1px $primary"
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === 1}
onClick={handleFirstPage}
px="12px"
py="6px"
>
처음
</Button>
<Button
_disabled={{ cursor: 'not-allowed', opacity: 0.4 }}
border="solid 1px $primary"
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === 1}
onClick={handlePreviousPage}
px="12px"
py="6px"
>
이전
</Button>
<Text color="$text" typography="body">
{page.toLocaleString()} / {pageInfo.pageCount.toLocaleString()}
</Text>
<Button
_disabled={{ cursor: 'not-allowed', opacity: 0.4 }}
border="solid 1px $primary"
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === pageInfo.pageCount}
onClick={handleNextPage}
px="12px"
py="6px"
>
다음
</Button>
<Button
_disabled={{ cursor: 'not-allowed', opacity: 0.4 }}
border="solid 1px $primary"
borderRadius="8px"
color="$primary"
cursor="pointer"
disabled={page === pageInfo.pageCount}
onClick={handleLastPage}
px="12px"
py="6px"
>
마지막
</Button>
</Flex>
</Flex>
) : null}
{isLoading ? (
<Text color="$caption" typography="body">
테스트 케이스를 불러오는 중입니다.
</Text>
) : null}
{error ? (
<Text color="$error" typography="body">
{error}
</Text>
) : null}
{!isLoading && !error && options.type === 'table' ? (
<TestCaseTable results={visibleResults} startIndex={startIndex} />
) : null}
{!isLoading && !error && options.type === 'list' ? (
<TestCaseList results={visibleResults} />
) : null}
</VStack>
)
}
12 changes: 9 additions & 3 deletions apps/landing/src/components/test-case/table/TestCaseTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ function CompetitorCell({
)
}

export function TestCaseTable({ results }: { results: TestStatus[6] }) {
export function TestCaseTable({
results,
startIndex = 0,
}: {
results: TestStatus[6]
startIndex?: number
}) {
return (
<Table>
<Thead
Expand Down Expand Up @@ -83,7 +89,7 @@ export function TestCaseTable({ results }: { results: TestStatus[6] }) {
})}
data-responsive="desktop"
>
<Td>{index + 1}</Td>
<Td>{startIndex + index + 1}</Td>
<Td>
<LatexText>{text}</LatexText>
{note ? ` (${note})` : null}
Expand Down Expand Up @@ -137,7 +143,7 @@ export function TestCaseTable({ results }: { results: TestStatus[6] }) {
justifyContent="space-between"
px="10px"
>
<Text>{index + 1}</Text>
<Text>{startIndex + index + 1}</Text>
<Image
alt={isSuccess ? 'success' : 'error'}
boxSize="24px"
Expand Down
7 changes: 7 additions & 0 deletions apps/landing/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ export type TestStatus = [
]

export type TestStatusMap = Record<string, TestStatus>

export interface TestStatusPageInfo {
pageSize: number
pageCount: number
}

export type TestStatusPageManifest = Record<string, TestStatusPageInfo>
14 changes: 14 additions & 0 deletions bench/JEOMJASESANG_CORPUS_BENCH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 점자세상 NIKL 병렬 말뭉치 정확도

- 기준: NIKL Korean–Korean Braille Parallel Corpus 2025 v1.0
- 방식: 점자 공백을 정규화한 뒤 문장 단위 완전 일치 비교

| 항목 | 값 |
|---|---:|
| 전체 문장 | 83528 |
| API 응답 수 | 83528 |
| 측정 대상 | 83528 |
| 일치 | 73369 |
| 불일치 | 10159 |
| 미수집 | 0 |
| **문장 단위 완전 일치율** | **87.84%** |
Loading
Loading