@things-factory/board-import 10.1.154 → 10.1.156
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/service/adapters/image-adapter.d.ts +15 -0
- package/dist-server/service/adapters/image-adapter.js +2 -2
- package/dist-server/service/adapters/image-adapter.js.map +1 -1
- package/dist-server/service/ai/types.d.ts +14 -8
- package/dist-server/service/ai/types.js +11 -20
- package/dist-server/service/ai/types.js.map +1 -1
- package/dist-server/service/extraction/contracts.d.ts +2 -0
- package/dist-server/service/extraction/contracts.js.map +1 -1
- package/dist-server/service/extraction/vlm-recognizer.d.ts +1 -1
- package/dist-server/service/extraction/vlm-recognizer.js +2 -12
- package/dist-server/service/extraction/vlm-recognizer.js.map +1 -1
- package/dist-server/service/import-session/import-session.d.ts +1 -0
- package/dist-server/service/import-session/import-session.js +4 -0
- package/dist-server/service/import-session/import-session.js.map +1 -1
- package/dist-server/service/import-session/import-worker.js +4 -1
- package/dist-server/service/import-session/import-worker.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/server/service/adapters/image-adapter.ts +8 -3
- package/server/service/ai/types.ts +12 -19
- package/server/service/extraction/contracts.ts +2 -0
- package/server/service/extraction/extraction.test.ts +8 -2
- package/server/service/extraction/vlm-recognizer.ts +5 -4
- package/server/service/import-session/import-session.ts +3 -0
- package/server/service/import-session/import-worker.ts +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vlm-recognizer.js","sourceRoot":"","sources":["../../../server/service/extraction/vlm-recognizer.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"vlm-recognizer.js","sourceRoot":"","sources":["../../../server/service/extraction/vlm-recognizer.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * VlmRecognizer — region 이 *어떤 컴포넌트인지* 식별 (Phase Board-AI §3.8 의 Recognizer).\n *\n * 좌표/레이아웃은 절대 추정하지 않는다(그건 Geometry/CV 의 몫). VLM/LLM 의 강점인\n * *의미 인식* 만: 주어진 신호(라벨·모양·크기·레이어) + 카탈로그 → componentType.\n * 카탈로그(ctx.catalog) 가 있으면 그 안의 값만 허용(그라운딩).\n *\n * provider 는 ai-client-base 의 AIClient. 생성자로 주입하거나(테스트), 실행하는 쪽이 그 사업장 에이전트의 client 를\n * ctx.aiClient 로 넘긴다.\n */\nimport type { AIClient } from '@things-factory/ai-client-base'\nimport type { Recognizer, RecognitionResult, GeometricPrimitive, ExtractionContext } from './contracts'\n\ninterface RawRecognition {\n componentType?: string | null\n confidence?: number\n attribs?: Record<string, any>\n}\n\nconst SYSTEM =\n '당신은 시설 도면의 한 영역(region)이 어떤 보드 컴포넌트 type 인지 판정하는 분류기다. ' +\n '좌표/위치/크기를 추정하지 말고, 주어진 신호(라벨/모양/크기/레이어)와 카탈로그만으로 *type* 만 판정한다. ' +\n 'STRICT JSON 만 반환(마크다운/주석 금지).'\n\nexport class VlmRecognizer implements Recognizer {\n readonly id = 'vlm-recognizer'\n\n constructor(private readonly client?: AIClient) {}\n\n async recognize(region: GeometricPrimitive, ctx?: ExtractionContext): Promise<RecognitionResult> {\n const client = this.client ?? ctx?.aiClient\n if (!client) {\n throw new Error('[board-import/extraction] VlmRecognizer needs the AI client of the domain it runs for (ctx.aiClient).')\n }\n const raw = await client.generateJSON<RawRecognition>(buildPrompt(region, ctx), {\n systemPrompt: SYSTEM,\n temperature: 0,\n maxTokens: 300\n })\n return normalize(raw, ctx?.catalog)\n }\n}\n\n/** region 신호 + 카탈로그 → 인식 프롬프트. */\nexport function buildPrompt(region: GeometricPrimitive, ctx?: ExtractionContext): string {\n const w = region.bbox.max.x - region.bbox.min.x\n const h = region.bbox.max.y - region.bbox.min.y\n const aspect = h !== 0 ? w / h : 0\n const a = region.entity?.attribs ?? {}\n const m = region.meta ?? {}\n\n const signals: string[] = [`kind=${region.kind}`, `size=${round(w)}x${round(h)}`, `aspect=${aspect.toFixed(2)}`]\n const name = a.name ?? m.name ?? m.label\n if (name) signals.push(`label=\"${name}\"`)\n if (a.shape ?? m.shape) signals.push(`shape=${a.shape ?? m.shape}`)\n if (a.layer ?? m.layer) signals.push(`layer=${a.layer ?? m.layer}`)\n if (a.text ?? m.text) signals.push(`text=\"${a.text ?? m.text}\"`)\n\n const catalogLine =\n ctx?.catalog && ctx.catalog.length\n ? `\\n카탈로그(이 중에서만 선택, 맞는 게 없으면 componentType=null):\\n${ctx.catalog.join(', ')}`\n : '\\n카탈로그 미지정 — 가장 적절한 컴포넌트 type 을 자유롭게 판정.'\n const hint = ctx?.userPrompt ? `\\n사용자 힌트: ${ctx.userPrompt}` : ''\n\n return (\n `다음 영역의 신호로 보드 컴포넌트 type 을 판정하라.\\n신호: ${signals.join(', ')}${catalogLine}${hint}\\n` +\n `반환 JSON: { \"componentType\": string|null, \"confidence\": 0..1, \"attribs\": { ... } }`\n )\n}\n\n/** 응답 정규화 + 카탈로그 그라운딩 + confidence 클램프. */\nexport function normalize(raw: RawRecognition | null | undefined, catalog?: string[]): RecognitionResult {\n let type =\n typeof raw?.componentType === 'string' && raw.componentType.trim() ? raw.componentType.trim() : undefined\n // 카탈로그 있으면 그 안의 값만 허용 — 그라운딩(환각 type 차단).\n if (type && catalog && catalog.length && !catalog.includes(type)) type = undefined\n\n let conf = typeof raw?.confidence === 'number' && Number.isFinite(raw.confidence) ? raw.confidence : 0.5\n conf = Math.max(0, Math.min(1, conf))\n if (!type) conf = Math.min(conf, 0.3) // 타입 미상이면 신뢰도 상한\n\n const attribs = raw?.attribs && typeof raw.attribs === 'object' ? raw.attribs : undefined\n return { componentType: type, confidence: conf, attribs }\n}\n\nfunction round(n: number): number {\n return Math.round(n * 100) / 100\n}\n"]}
|
|
@@ -128,6 +128,10 @@ tslib_1.__decorate([
|
|
|
128
128
|
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true }),
|
|
129
129
|
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
130
130
|
], ImportSession.prototype, "creator", void 0);
|
|
131
|
+
tslib_1.__decorate([
|
|
132
|
+
(0, typeorm_1.RelationId)((session) => session.creator),
|
|
133
|
+
tslib_1.__metadata("design:type", String)
|
|
134
|
+
], ImportSession.prototype, "creatorId", void 0);
|
|
131
135
|
tslib_1.__decorate([
|
|
132
136
|
(0, typeorm_1.CreateDateColumn)(),
|
|
133
137
|
(0, type_graphql_1.Field)({ nullable: true }),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-session.js","sourceRoot":"","sources":["../../../server/service/import-session/import-session.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;;;GAWG;AACH,qCASgB;AAChB,+CAAgE;AAChE,qDAA6C;AAE7C,iDAA8C;AAC9C,yDAAgD;AAChD,6CAA4C;AAE5C,MAAM,SAAS,GAAG,YAAM,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AAC7C,MAAM,aAAa,GAAG,SAAS,CAAC,IAAI,CAAA;AAM7B,IAAM,aAAa,GAAnB,MAAM,aAAa;IAoDxB,IACI,OAAO;QACT,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;IAiBD,IACI,MAAM;QACR,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QAC7D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;
|
|
1
|
+
{"version":3,"file":"import-session.js","sourceRoot":"","sources":["../../../server/service/import-session/import-session.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ImportSession — 도면(Attachment) → BoardModel 변환 작업 단위.\n *\n * 비동기 워크플로우:\n * 1) 클라이언트가 importBoardAsync(attachmentId) 호출 → ImportSession 즉시 반환 (status:'queued')\n * 2) 백그라운드 worker 가 attachment 본문 → importBoard() 실행 → status 갱신\n * 3) 클라이언트는 polling 또는 subscription 으로 진행 상황 추적\n * 4) status='completed' 시 result 에 BoardModel + stats + warnings 포함\n *\n * board-ai 의 ChatSession 과 연결 가능 (chatSessionId) — chat 안에서 자동 발동되면 진행 메시지가\n * 자동으로 ChatMessage(role:'system') 으로 기록되어 AI 컨텍스트에 흐름.\n */\nimport {\n Column,\n CreateDateColumn,\n Entity,\n Index,\n ManyToOne,\n PrimaryGeneratedColumn,\n RelationId,\n UpdateDateColumn\n} from 'typeorm'\nimport { Field, Float, ID, Int, ObjectType } from 'type-graphql'\nimport { GraphQLJSON } from 'graphql-scalars'\n\nimport { Domain } from '@things-factory/shell'\nimport { User } from '@things-factory/auth-base'\nimport { config } from '@things-factory/env'\n\nconst ORMCONFIG = config.get('ormconfig', {})\nconst DATABASE_TYPE = ORMCONFIG.type\n\n@Entity()\n@Index('ix_import_session_1', (session: ImportSession) => [session.domain, session.createdAt])\n@Index('ix_import_session_2', (session: ImportSession) => [session.chatSessionId])\n@ObjectType({ description: '도면 → 보드 변환 작업의 영속 단위 (비동기 진행상태 추적).' })\nexport class ImportSession {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { nullable: true })\n readonly id?: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true })\n domain?: Domain\n\n @RelationId((session: ImportSession) => session.domain)\n domainId?: string\n\n /** Attachment.id (도면 파일). attachment-base 의존이지만 직접 ManyToOne 대신 ID 만 보유. */\n @Column()\n @Field({ description: 'Attachment id (the source drawing file)' })\n attachmentId!: string\n\n /** 어느 ChatSession 에서 발동됐는지 (board-ai 와 연동). 없으면 직접 호출. */\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'ChatSession id when triggered from chat (board-ai).' })\n chatSessionId?: string\n\n /** 'queued' | 'parsing' | 'mapping' | 'assembling' | 'binding' | 'completed' | 'failed' */\n @Column({ type: 'varchar', length: 32, default: 'queued' })\n @Field({ description: 'queued | parsing | mapping | assembling | binding | completed | failed' })\n status!: string\n\n /** 0..100 */\n @Column({ type: 'float', default: 0 })\n @Field(type => Float, { description: 'Progress percentage 0..100' })\n progress!: number\n\n /** 현재 단계 설명 또는 에러 메시지 */\n @Column({ nullable: true, type: 'text' })\n @Field({ nullable: true })\n message?: string\n\n /** import 옵션 (scopes, parseOptions, normalizeOrigin 등) */\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n optionsJson?: string\n\n @Field(type => GraphQLJSON, { nullable: true, description: 'Options used (parsed JSON).' })\n get options(): any {\n try {\n return this.optionsJson ? JSON.parse(this.optionsJson) : null\n } catch {\n return null\n }\n }\n\n /** 결과: { boardModel, mapping: { stats, warnings } } */\n @Column({\n nullable: true,\n type:\n DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'\n ? 'longtext'\n : DATABASE_TYPE == 'oracle'\n ? 'clob'\n : DATABASE_TYPE == 'mssql'\n ? 'nvarchar'\n : 'text',\n length: DATABASE_TYPE == 'mssql' ? 'MAX' : undefined\n })\n resultJson?: string\n\n @Field(type => GraphQLJSON, { nullable: true, description: 'Result (parsed JSON): { boardModel, stats, warnings }.' })\n get result(): any {\n try {\n return this.resultJson ? JSON.parse(this.resultJson) : null\n } catch {\n return null\n }\n }\n\n @Column({ nullable: true })\n @Field(type => Int, { nullable: true, description: 'Total entities counted (when known).' })\n totalEntities?: number\n\n @ManyToOne(type => User)\n @Field(type => User, { nullable: true })\n creator?: User\n\n @RelationId((session: ImportSession) => session.creator)\n creatorId?: string\n\n @CreateDateColumn()\n @Field({ nullable: true })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true })\n updatedAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true })\n completedAt?: Date\n}\n"]}
|
|
@@ -17,6 +17,7 @@ const shell_1 = require("@things-factory/shell");
|
|
|
17
17
|
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
18
18
|
const attachment_base_2 = require("@things-factory/attachment-base");
|
|
19
19
|
const index_1 = require("../pipeline/index");
|
|
20
|
+
const types_1 = require("../ai/types");
|
|
20
21
|
const import_session_1 = require("./import-session");
|
|
21
22
|
async function runImportJob(sessionId) {
|
|
22
23
|
const repo = (0, shell_1.getRepository)(import_session_1.ImportSession);
|
|
@@ -43,7 +44,9 @@ async function runImportJob(sessionId) {
|
|
|
43
44
|
// 다른 adapter (DXF) 에는 무해 (몰라도 무시).
|
|
44
45
|
const mergedParseOptions = {
|
|
45
46
|
...(opts.parseOptions || {}),
|
|
46
|
-
...(opts.userPrompt ? { userPrompt: opts.userPrompt } : {})
|
|
47
|
+
...(opts.userPrompt ? { userPrompt: opts.userPrompt } : {}),
|
|
48
|
+
/* The domain's AI agent, asked only if the drawing needs it (design/ai/agent-settings.md). */
|
|
49
|
+
resolveAI: () => (0, types_1.boardImportAIFor)({ domain: { id: session.domainId }, user: session.creatorId ? { id: session.creatorId } : null })
|
|
47
50
|
};
|
|
48
51
|
// AI 가 같은 도면을 다른 관점으로 해석한 multi-variant. parse 한 번, 시안별 mapping + assemble.
|
|
49
52
|
// DXF 등 single-variant adapter 는 1 boardModel 만.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-worker.js","sourceRoot":"","sources":["../../../server/service/import-session/import-worker.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"import-worker.js","sourceRoot":"","sources":["../../../server/service/import-session/import-worker.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Background worker — ImportSession 처리.\n *\n * 흐름:\n * 1) ImportSession 로드 (status: queued)\n * 2) Attachment 본문 읽기 (attachment-base STORAGE.readFile)\n * 3) importBoard() 실행 (단계별로 status/progress 갱신)\n * 4) result 저장 + status='completed'\n * 5) 실패 시 status='failed' + message\n *\n * 단일 프로세스 내 setImmediate 기반. 추후 외부 큐(Redis/BullMQ) 로 이전 가능.\n */\nimport { getRepository } from '@things-factory/shell'\n\nimport { Attachment } from '@things-factory/attachment-base'\nimport { STORAGE } from '@things-factory/attachment-base'\n\nimport { importBoardVariants } from '../pipeline/index'\nimport { boardImportAIFor } from '../ai/types'\nimport { ImportSession } from './import-session'\n\nexport async function runImportJob(sessionId: string): Promise<void> {\n const repo = getRepository(ImportSession)\n const session = await repo.findOneBy({ id: sessionId })\n if (!session) {\n console.error(`[board-import] ImportSession ${sessionId} not found`)\n return\n }\n\n try {\n await update(repo, sessionId, { status: 'parsing', progress: 5, message: 'Reading attachment' })\n\n const attachment = await getRepository(Attachment).findOneBy({ id: session.attachmentId })\n if (!attachment) {\n throw new Error(`Attachment ${session.attachmentId} not found`)\n }\n\n // attachment-base STORAGE.readFile 으로 본문 로드 (Buffer).\n // file storage 는 sync, db/s3/azure 는 async — await 으로 양쪽 다 흡수.\n const fileBuffer = (await STORAGE.readFile(attachment.path!, null)) as Buffer\n\n await update(repo, sessionId, { status: 'parsing', progress: 20, message: 'Parsing drawing' })\n\n const opts = session.options || {}\n const filename = attachment.name || attachment.path\n\n // importBoard 는 내부에서 stage 1~4 모두 수행. 단계별 progress 는 우선 거시적 업데이트.\n await update(repo, sessionId, { status: 'mapping', progress: 50, message: 'Mapping entities' })\n\n // userPrompt 는 parseOptions 로 합류 — image-adapter 가 VLM 프롬프트에 사용.\n // 다른 adapter (DXF) 에는 무해 (몰라도 무시).\n const mergedParseOptions = {\n ...(opts.parseOptions || {}),\n ...(opts.userPrompt ? { userPrompt: opts.userPrompt } : {}),\n /* The domain's AI agent, asked only if the drawing needs it (design/ai/agent-settings.md). */\n resolveAI: () => boardImportAIFor({ domain: { id: session.domainId! }, user: session.creatorId ? { id: session.creatorId } : null })\n }\n // AI 가 같은 도면을 다른 관점으로 해석한 multi-variant. parse 한 번, 시안별 mapping + assemble.\n // DXF 등 single-variant adapter 는 1 boardModel 만.\n const result = await importBoardVariants({\n file: fileBuffer,\n filename,\n scopes: opts.scopes,\n normalizeOrigin: opts.normalizeOrigin,\n flipY: opts.flipY,\n scale: opts.scale,\n applyBinding: opts.applyBinding,\n parseOptions: mergedParseOptions\n })\n\n await update(repo, sessionId, { status: 'assembling', progress: 80, message: 'Assembling boards' })\n\n // 시안별 카테고리 분포 — 각 variant 의 entities 가 다르므로.\n const variantsWithStats = result.variants.map((v, i) => {\n const dist: Record<string, number> = {}\n for (const c of v.boardModel.components ?? []) {\n const k = c.category ?? 'unknown'\n dist[k] = (dist[k] ?? 0) + 1\n }\n return { variant: v, mapping: result.mappings[i], categoryDistribution: dist }\n })\n\n // default variantId — 첫 variant.\n const defaultVariantId = result.variants[0]?.id\n\n // resultJson 구조:\n // - variants: 풀 시안 데이터 (선택용 — boardModel + subject + importStrategy + stats + categoryDist)\n // - boardModel: default variant 의 boardModel (back-compat)\n // - defaultVariantId: 첫 variant 의 id\n // - metadata: viewType / aiClientId / userPrompt 등 시안 무관 공통\n const defaultVariant = variantsWithStats[0]\n const resultJson = JSON.stringify({\n variants: variantsWithStats.map(({ variant, mapping, categoryDistribution }) => ({\n id: variant.id,\n label: variant.label,\n description: variant.description,\n subjectName: variant.subjectName,\n subjectDescription: variant.subjectDescription,\n importStrategy: variant.importStrategy,\n boardModel: variant.boardModel,\n stats: mapping.stats,\n warnings: mapping.warnings,\n categoryDistribution\n })),\n defaultVariantId,\n boardModel: defaultVariant?.variant.boardModel,\n stats: defaultVariant?.mapping.stats,\n warnings: defaultVariant?.mapping.warnings,\n metadata: result.parse.metadata,\n categoryDistribution: defaultVariant?.categoryDistribution\n })\n\n const totalAcross = result.mappings.reduce((s, m) => s + (m.stats.total ?? 0), 0)\n await update(repo, sessionId, {\n status: 'completed',\n progress: 100,\n message: `Completed: ${result.variants.length} variants, ${totalAcross} entities total`,\n totalEntities: defaultVariant?.mapping.stats.total ?? 0,\n resultJson,\n completedAt: new Date()\n })\n } catch (err) {\n const message = (err as Error)?.message ?? String(err)\n console.error(`[board-import] ImportSession ${sessionId} failed:`, err)\n await update(repo, sessionId, {\n status: 'failed',\n message: `Failed: ${message}`,\n completedAt: new Date()\n })\n }\n}\n\nasync function update(repo: any, id: string, patch: Partial<ImportSession>): Promise<void> {\n await repo.update(id, patch as any)\n}\n"]}
|