This commit is contained in:
2026-04-09 13:17:52 +09:00
parent 8bb099e4f6
commit 95e44b18d0
6 changed files with 11832 additions and 9160 deletions
+69 -7
View File
@@ -16,34 +16,96 @@ import java.io.File
import java.util.zip.ZipInputStream
import javax.xml.parsers.DocumentBuilderFactory
import kotlinx.serialization.encodeToString // 추가 필요
@Serializable
data class StockItem(
val code: String,
val name: String
){}
object StockUniverseLoader {
private val json = Json { ignoreUnknownKeys = true }
fun loadUniverse(filePath: String = "stocks_universe.json"): List<Pair<String, String>> {
object StockUniverseLoader {
// prettyPrint = true 를 주면 JSON 파일이 한 줄로 안 뭉치고 예쁘게 저장됩니다.
private val json = Json { ignoreUnknownKeys = true; prettyPrint = true }
private const val DEFAULT_FILE_PATH = "stocks_universe.json"
fun loadUniverse(filePath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
return try {
val file = File(filePath)
if (!file.exists()) {
println("⚠️ 파일을 찾을 수 없습니다: ${file.absolutePath}")
return emptyList()
}
// 1. JSON 파일 읽기 및 역직렬화
val rawJson = file.readText()
val stockItems = json.decodeFromString<List<StockItem>>(rawJson)
// 2. Pair(코드, 이름) 튜플 리스트로 변환
stockItems.map { it.code to it.name }
} catch (e: Exception) {
println("❌ 유니버스 로드 실패: ${e.message}")
emptyList()
}
}
// 💡 [신규] JSON 파일로 덮어쓰기 저장
fun saveUniverse(items: List<Pair<String, String>>, filePath: String = DEFAULT_FILE_PATH) {
try {
val stockItems = items.map { StockItem(it.first, it.second) }
val jsonString = json.encodeToString(stockItems)
File(filePath).writeText(jsonString)
println("💾 [System] 유니버스 영구 저장 완료: 총 ${items.size}종목")
} catch (e: Exception) {
println("❌ 유니버스 저장 실패: ${e.message}")
}
}
// 💡 [신규] CSV 파일을 받아 파싱, 중복 제거, 저장까지 원스톱으로 처리
fun parseAndMergeCsv(file: File, targetJsonPath: String = DEFAULT_FILE_PATH): List<Pair<String, String>> {
val newItems = mutableListOf<Pair<String, String>>()
try {
val lines = file.readLines()
if (lines.isEmpty()) return loadUniverse(targetJsonPath)
// 헤더 자동 추적
val headers = lines[0].split(",").map { it.replace("\"", "").trim() }
val codeIndex = headers.indexOfFirst { it.contains("종목코드") || it.contains("코드") }
val nameIndex = headers.indexOfFirst { it.contains("종목명") || it.contains("이름") }
val finalCodeIdx = if (codeIndex != -1) codeIndex else 0
val finalNameIdx = if (nameIndex != -1) nameIndex else 1
for (i in 1 until lines.size) {
val line = lines[i]
if (line.isBlank()) continue
val parts = line.split(",").map { it.replace("\"", "").trim() }
if (parts.size > maxOf(finalCodeIdx, finalNameIdx)) {
// 엑셀이 날려먹은 앞자리 '0' 복원 (6자리 맞춤)
val rawCode = parts[finalCodeIdx].replace(Regex("[^0-9]"), "")
val code = rawCode.padStart(6, '0')
val name = parts[finalNameIdx]
if (code.length == 6) {
newItems.add(code to name)
}
}
}
// 1. 기존 데이터 불러오기
val existing = loadUniverse(targetJsonPath)
// 2. 종목코드(it.first) 기준으로 완벽하게 중복 제거 병합
val mergedList = (existing + newItems).distinctBy { it.first }
// 3. 파일에 덮어쓰기 (영구 저장)
saveUniverse(mergedList, targetJsonPath)
println("✅ CSV 병합 성공! (신규 ${mergedList.size - existing.size}건 추가됨)")
return mergedList
} catch (e: Exception) {
println("❌ CSV 파싱 및 병합 실패: ${e.message}")
return loadUniverse(targetJsonPath) // 실패 시 기존 데이터라도 반환
}
}
}