CarboAny / scripts /export-methodology-vectors.ts
Esketch's picture
deploy: [P4] Strangler Pattern API Adapter Release (Orphan Clean Build v2)
daaf9d7
Raw
History Blame Contribute Delete
7.51 kB
#!/usr/bin/env bun
/**
* export-methodology-vectors.ts
*
* WS-5 μ‚°μΆœλ¬Ό: methodology_vectors ν…Œμ΄λΈ”μ„ CSV둜 λ€ν”„ν•˜μ—¬
* μ™ΈλΆ€ 검증인·감사관이 방법둠 데이터λ₯Ό κ²€ν† ν•  수 μžˆλ„λ‘ ν•œλ‹€.
*
* μ‚¬μš©λ²•:
* bun scripts/export-methodology-vectors.ts [--output <path>] [--format <csv|json>]
*
* ν•„μš” ν™˜κ²½ λ³€μˆ˜ (ν”„λ‘œμ νŠΈ 루트 .env.local λ˜λŠ” ν™˜κ²½ λ³€μˆ˜):
* SUPABASE_URL β€” Supabase ν”„λ‘œμ νŠΈ URL
* SUPABASE_ANON_KEY β€” Supabase anon key (읽기 μ „μš©)
* λ˜λŠ”:
* SUPABASE_SERVICE_KEY β€” Service role key (embedding 포함 전체 내보내기 μ‹œ)
*
* 주의:
* - ν”„λ‘œλ•μ…˜ Supabase(emkxmdksuartspqvozkh)에 λŒ€ν•΄ db reset / κ°•μ œ push κΈˆμ§€.
* - 이 μŠ€ν¬λ¦½νŠΈλŠ” SELECT만 μˆ˜ν–‰ν•˜λ©° 데이터λ₯Ό λ³€κ²½ν•˜μ§€ μ•ŠλŠ”λ‹€.
* - embedding 벑터(1536차원)λŠ” 기본적으둜 CSVμ—μ„œ μ œμ™Έλœλ‹€(파일 크기 문제).
* --include-embedding ν”Œλž˜κ·Έλ‘œ ν™œμ„±ν™” κ°€λŠ₯.
*/
import { createClient } from '@supabase/supabase-js'
import { writeFile } from 'node:fs/promises'
import { resolve } from 'node:path'
// ── νƒ€μž… μ •μ˜ ──────────────────────────────────────────────────
interface MethodologyVector {
id: string
methodology_code: string
registry: string
name_ko: string
name_en: string
applicability: string
emission_factors: Record<string, unknown>
required_params: string[]
embedding?: number[] | null
created_at: string
updated_at: string
}
interface ExportRow {
id: string
methodology_code: string
registry: string
name_ko: string
name_en: string
applicability: string
emission_factors_json: string
required_params: string
embedding_dim?: number // 차원 수만 기둝 (벑터 전체 λŒ€μ‹ )
created_at: string
updated_at: string
}
// ── 인수 νŒŒμ‹± ──────────────────────────────────────────────────
const args = process.argv.slice(2)
const outputPath = (() => {
const idx = args.indexOf('--output')
return idx !== -1 ? args[idx + 1] : `methodology-vectors-${new Date().toISOString().slice(0, 10)}.csv`
})()
const format = (() => {
const idx = args.indexOf('--format')
return idx !== -1 ? args[idx + 1] : 'csv'
})() as 'csv' | 'json'
const includeEmbedding = args.includes('--include-embedding')
// ── Supabase ν΄λΌμ΄μ–ΈνŠΈ ────────────────────────────────────────
const supabaseUrl = process.env.SUPABASE_URL
const supabaseKey = process.env.SUPABASE_SERVICE_KEY ?? process.env.SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseKey) {
console.error(
'ERROR: SUPABASE_URL 및 SUPABASE_ANON_KEY (λ˜λŠ” SUPABASE_SERVICE_KEY)λ₯Ό ν™˜κ²½ λ³€μˆ˜λ‘œ μ„€μ •ν•˜μ„Έμš”.'
)
console.error(' export SUPABASE_URL=https://your-project.supabase.co')
console.error(' export SUPABASE_ANON_KEY=your-anon-key')
process.exit(1)
}
const supabase = createClient(supabaseUrl, supabaseKey)
// ── 데이터 쑰회 ────────────────────────────────────────────────
async function fetchMethodologyVectors(): Promise<MethodologyVector[]> {
const select = includeEmbedding
? '*'
: 'id, methodology_code, registry, name_ko, name_en, applicability, emission_factors, required_params, created_at, updated_at'
const { data, error } = await supabase
.from('methodology_vectors')
.select(select)
.order('registry', { ascending: true })
.order('methodology_code', { ascending: true })
if (error) {
throw new Error(`Supabase 쑰회 μ‹€νŒ¨: ${error.message}`)
}
return (data ?? []) as unknown as MethodologyVector[]
}
// ── CSV λ³€ν™˜ ───────────────────────────────────────────────────
function toCsvRow(row: ExportRow): string {
const fields = [
row.id,
row.methodology_code,
row.registry,
row.name_ko,
row.name_en,
row.applicability.replace(/"/g, '""'), // CSV μ΄μŠ€μΌ€μ΄ν”„
row.emission_factors_json.replace(/"/g, '""'),
row.required_params,
includeEmbedding ? String(row.embedding_dim ?? '') : '',
row.created_at,
row.updated_at,
]
return fields.map((f) => `"${f}"`).join(',')
}
function buildCsvHeader(): string {
const cols = [
'id',
'methodology_code',
'registry',
'name_ko',
'name_en',
'applicability',
'emission_factors_json',
'required_params',
...(includeEmbedding ? ['embedding_dimensions'] : []),
'created_at',
'updated_at',
]
return cols.join(',')
}
// ── 메인 ──────────────────────────────────────────────────────
async function main() {
console.log('methodology_vectors ν…Œμ΄λΈ” 내보내기 μ‹œμž‘...')
console.log(` Supabase URL: ${supabaseUrl}`)
console.log(` 좜λ ₯ ν˜•μ‹: ${format}`)
console.log(` 좜λ ₯ 경둜: ${resolve(outputPath)}`)
console.log(` μž„λ² λ”© 포함: ${includeEmbedding}`)
console.log()
let rows: MethodologyVector[]
try {
rows = await fetchMethodologyVectors()
} catch (err) {
console.error('데이터 쑰회 μ‹€νŒ¨:', err)
process.exit(1)
}
console.log(` 쑰회된 방법둠 수: ${rows.length}건`)
// λ³€ν™˜
const exportRows: ExportRow[] = rows.map((r) => ({
id: r.id,
methodology_code: r.methodology_code,
registry: r.registry,
name_ko: r.name_ko,
name_en: r.name_en,
applicability: r.applicability,
emission_factors_json: JSON.stringify(r.emission_factors),
required_params: (r.required_params ?? []).join(';'),
embedding_dim: Array.isArray(r.embedding) ? r.embedding.length : undefined,
created_at: r.created_at,
updated_at: r.updated_at,
}))
let output: string
if (format === 'json') {
output = JSON.stringify(
rows.map((r) => ({
...r,
embedding: includeEmbedding ? r.embedding : `<${Array.isArray(r.embedding) ? r.embedding.length : 0}-dim vector omitted>`,
})),
null,
2
)
} else {
// CSV
const lines = [buildCsvHeader(), ...exportRows.map(toCsvRow)]
output = lines.join('\n')
}
const absPath = resolve(outputPath)
try {
await writeFile(absPath, output, 'utf-8')
} catch (err) {
console.error('파일 μ“°κΈ° μ‹€νŒ¨:', err)
process.exit(1)
}
console.log(`\n내보내기 μ™„λ£Œ: ${absPath}`)
console.log()
console.log('방법둠 μ½”λ“œ λͺ©λ‘:')
for (const r of exportRows) {
console.log(` [${r.registry.padEnd(14)}] ${r.methodology_code.padEnd(20)} β€” ${r.name_ko}`)
}
console.log()
console.log('μ™ΈλΆ€ 검증인 μ‚¬μš© μ§€μΉ¨:')
console.log(' 1. emission_factors_json: 방법둠별 λ°°μΆœκ³„μˆ˜ κΈ°λ³Έκ°’ 확인')
console.log(' 2. required_params: ν•΄λ‹Ή 방법둠에 ν•„μš”ν•œ λͺ¨λ‹ˆν„°λ§ λ³€μˆ˜ λͺ©λ‘ (μ„Έλ―Έμ½œλ‘  ꡬ뢄)')
console.log(' 3. μž„λ² λ”©(--include-embedding): ν”„λ‘œλ•μ…˜ μž¬μ‹œλ“œ ν›„ OpenAI μ‹€μ œ λ²‘ν„°λ‘œ ꡐ체 ν•„μš”')
console.log(' 4. 방법둠 상세: docs/methodology/ 디렉터리 μ°Έμ‘°')
}
main().catch((err) => {
console.error('예기치 μ•Šμ€ 였λ₯˜:', err)
process.exit(1)
})