From ec5f83dd4cac7775dcadf57626a5c8ea6acbec5b Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 8 Mar 2026 12:08:10 +0700 Subject: [PATCH] 260308:1208 20260308:1200 fix n8n workflow add model --- .windsurf/workflows/review.md | 22 +++++++++++++++++++ .../03-04-legacy-data-migration.md | 2 +- specs/03-Data-and-Storage/n8n.workflow.json | 6 ++++- 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 .windsurf/workflows/review.md diff --git a/.windsurf/workflows/review.md b/.windsurf/workflows/review.md new file mode 100644 index 0000000..6595c4a --- /dev/null +++ b/.windsurf/workflows/review.md @@ -0,0 +1,22 @@ +--- +auto_execution_mode: 0 +description: Review code changes for bugs, security issues, and improvements +--- +You are a senior software engineer performing a thorough code review to identify potential bugs. + +Your task is to find all potential bugs and code improvements in the code changes. Focus on: +1. Logic errors and incorrect behavior +2. Edge cases that aren't handled +3. Null/undefined reference issues +4. Race conditions or concurrency issues +5. Security vulnerabilities +6. Improper resource management or resource leaks +7. API contract violations +8. Incorrect caching behavior, including cache staleness issues, cache key-related bugs, incorrect cache invalidation, and ineffective caching +9. Violations of existing code patterns or conventions + +Make sure to: +1. If exploring the codebase, call multiple tools in parallel for increased efficiency. Do not spend too much time exploring. +2. If you find any pre-existing bugs in the code, you should also report those since it's important for us to maintain general code quality for the user. +3. Do NOT report issues that are speculative or low-confidence. All your conclusions should be based on a complete understanding of the codebase. +4. Remember that if you were given a specific git commit, it may not be checked out and local code states may be different. \ No newline at end of file diff --git a/specs/03-Data-and-Storage/03-04-legacy-data-migration.md b/specs/03-Data-and-Storage/03-04-legacy-data-migration.md index cedfee9..8de732a 100644 --- a/specs/03-Data-and-Storage/03-04-legacy-data-migration.md +++ b/specs/03-Data-and-Storage/03-04-legacy-data-migration.md @@ -49,7 +49,7 @@ ```bash # แนะนำ: llama3.2:3b (เร็ว, VRAM ~3GB, เหมาะ Classification) หรือ ollama run llama3.2:3b ollama pull llama3.2:3b - +ollama pull qwen2.5:7b-instruct-q4_K_M # Fallback: mistral:7b-instruct-q4_K_M (แม่นกว่า, VRAM ~5GB) # ollama pull mistral:7b-instruct-q4_K_M ``` diff --git a/specs/03-Data-and-Storage/n8n.workflow.json b/specs/03-Data-and-Storage/n8n.workflow.json index 6b8a8ea..c33558a 100644 --- a/specs/03-Data-and-Storage/n8n.workflow.json +++ b/specs/03-Data-and-Storage/n8n.workflow.json @@ -16,6 +16,7 @@ { "parameters": { "jsCode": "// ============================================\n// CONFIGURATION - แก้ไขค่าที่นี่\n// ============================================\nconst CONFIG = {\n // Ollama Settings\n OLLAMA_HOST: 'http://192.168.20.100:11434',\n OLLAMA_MODEL_PRIMARY: 'llama3.2:3b',\n OLLAMA_MODEL_FALLBACK: 'mistral:7b-instruct-q4_K_M',\n \n // Backend Settings\n BACKEND_URL: 'https://backend.np-dms.work',\n MIGRATION_TOKEN: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1pZ3JhdGlvbl9ib3QiLCJzdWIiOjUsInNjb3BlIjoiR2xvYmFsIiwiaWF0IjoxNzcyNzc0MzI5LCJleHAiOjQ5Mjg1MzQzMjl9.TtA8zoHy7G9J5jPgYQPv7yw-9X--B_hl-Nv-c9V4PaA',\n \n // Batch Settings\n BATCH_SIZE: 2,\n BATCH_ID: 'migration_20260226',\n DELAY_MS: 2000,\n \n // Thresholds\n CONFIDENCE_HIGH: 0.85,\n CONFIDENCE_LOW: 0.60,\n MAX_RETRY: 3,\n FALLBACK_THRESHOLD: 5,\n \n // Source Definitions - แก้ไขโฟลเดอร์และไฟล์ทำงานที่นี่\n EXCEL_FILE: '/home/node/.n8n-files/staging_ai/C22024.xlsx',\n SOURCE_PDF_DIR: '/home/node/.n8n-files/staging_ai/Incoming/08C.2/2567',\n LOG_PATH: '/home/node/.n8n-files/migration_logs',\n \n // Database\n DB_HOST: '192.168.10.8',\n DB_PORT: 3306,\n DB_NAME: 'lcbp3',\n DB_USER: 'migration_bot',\n DB_PASSWORD: 'Center2025',\n PROJECT_ID: 1\n};\n\nreturn [{ json: { config_loaded: true, timestamp: new Date().toISOString(), config: CONFIG } }];" + // Ollama Settings\n OLLAMA_HOST: 'http://192.168.20.100:11434',\n OLLAMA_MODEL_PRIMARY: 'qwen2.5:7b',\n OLLAMA_MODEL_FALLBACK: 'mistral:7b-instruct-q4_K_M',\n \n // Backend Settings\n BACKEND_URL: 'https://backend.np-dms.work',\n MIGRATION_TOKEN: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im1pZ3JhdGlvbl9ib3QiLCJzdWIiOjUsInNjb3BlIjoiR2xvYmFsIiwiaWF0IjoxNzcyNzc0MzI5LCJleHAiOjQ5Mjg1MzQzMjl9.TtA8zoHy7G9J5jPgYQPv7yw-9X--B_hl-Nv-c9V4PaA',\n \n // Batch Settings\n BATCH_SIZE: 2,\n BATCH_ID: 'migration_20260226',\n DELAY_MS: 2000,\n \n // Thresholds\n CONFIDENCE_HIGH: 0.85,\n CONFIDENCE_LOW: 0.60,\n MAX_RETRY: 3,\n FALLBACK_THRESHOLD: 5,\n \n // Source Definitions - แก้ไขโฟลเดอร์และไฟล์ทำงานที่นี่\n EXCEL_FILE: '/home/node/.n8n-files/staging_ai/C22024.xlsx',\n SOURCE_PDF_DIR: '/home/node/.n8n-files/staging_ai/Incoming/08C.2/2567',\n LOG_PATH: '/home/node/.n8n-files/migration_logs',\n \n // Database\n DB_HOST: '192.168.10.8',\n DB_PORT: 3306,\n DB_NAME: 'lcbp3',\n DB_USER: 'migration_bot',\n DB_PASSWORD: 'Center2025',\n PROJECT_ID: 1\n};\n\nreturn [{ json: { config_loaded: true, timestamp: new Date().toISOString(), config: CONFIG } }];" }, "id": "bc8c9b9d-284d-4ce5-b7ff-d5b4bb36e748", "name": "Set Configuration", @@ -220,6 +221,7 @@ { "parameters": { "jsCode": "const config = $('Set Configuration').first().json.config;\nconst fallbackState = $input.first().json[0] || { is_fallback_active: false, recent_error_count: 0 };\n\nconst isFallback = fallbackState.is_fallback_active || false;\nconst model = isFallback ? config.OLLAMA_MODEL_FALLBACK : config.OLLAMA_MODEL_PRIMARY;\n\n// Safely pull categories from the first Check node\nlet systemCategories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];\ntry { systemCategories = $('File Mount Check').first().json.system_categories || systemCategories; } catch (e) {}\n\nconst items = $('Extract PDF Text').all();\n\nreturn items.map(item => {\n const docNum = String(item.json.document_number || '');\n const title = String(item.json.title || '');\n const legacyNum = String(item.json.legacy_number || '');\n\n const isRFA = docNum.includes('-RFA-') || title.toLowerCase().includes('rfa');\n\n const systemPrompt = `You are an expert Document Controller for a construction project (LCBP3) in Thailand.\nThe documents are primarily in THAI and ENGLISH.\nYour task is to classify documents and extract metadata from noisy OCR text.\nIf the OCR text is unreadable or gibberish, rely on the provided EXCEL METADATA.\nRespond ONLY with valid JSON.`;\n\n const pdfText = String(item.json.data || '').substring(0, 3500).replace(/[^a-zA-Z0-9ก-๙\\s\\.\\/\\-:\\[\\]\\(\\)]/g, ' ');\n\n const userPrompt = `Analyze this document:\n[EXCEL METADATA]\nDocument Number: ${docNum || 'Not provided'}\nTitle: ${title || 'Not provided'}\nLegacy Number: ${legacyNum || 'Not provided'}\n\n[OCR TEXT EXTRACTION]\n${pdfText}\n\nRules:\n1. Category must be one of: ${JSON.stringify(systemCategories)}\n2. If Document Number contains \"-RFA-\", suggest_category MUST be \"RFA\".\n3. For RFA, extract \"ref_no\" and \"response_to\".\n4. For Letters, identify \"from_org\" and \"to_org\".\n5. IMPORTANT: You MUST write a new 1-3 sentence summary in Thai evaluating the [OCR TEXT EXTRACTION] and place it in the \"body\" field. If the OCR is gibberish, write \"ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\" in the body.\n6. DO NOT invent non-existent English or Thai words for suggested_title. If you cannot find a clear title from the text, just use the exact EXCEL METADATA Title (${title}).\n\nRespond ONLY with this EXACT JSON structure:\n{\n \"is_valid\": true,\n \"confidence\": 0.95,\n \"suggested_category\": \"${isRFA ? 'RFA' : 'Correspondence'}\",\n \"detected_issues\": [],\n \"suggested_title\": \"${title}\",\n \"suggested_tags\": [\"Construction\", \"${isRFA ? 'Request' : 'Letter'}\"],\n \"metadata\": {\n \"ref_no\": null,\n \"response_to\": null,\n \"from_org\": null,\n \"to_org\": null,\n \"body\": \"สรุปสั้นๆ เป็นภาษาไทย 1-3 ประโยค หรือ ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\"\n }\n}`;\n\n return {\n json: {\n ...item.json,\n active_model: model,\n is_fallback: isFallback,\n system_categories: systemCategories,\n ollama_payload: {\n model: model,\n prompt: `${systemPrompt}\\n\\n${userPrompt}`,\n stream: false,\n format: 'json'\n }\n }\n };\n});" +const model = isFallback ? config.OLLAMA_MODEL_FALLBACK : config.OLLAMA_MODEL_PRIMARY;\n\n// Safely pull categories and tags from the first Check node\nlet systemCategories = ['Correspondence','RFA','Drawing','Transmittal','Report','Other'];\nlet existingTags = [];\ntry {\n const checkData = $('File Mount Check').first().json;\n systemCategories = checkData.system_categories || systemCategories;\n existingTags = checkData.existing_tags || [];\n} catch (e) {}\n\nconst items = $('Extract PDF Text').all();\n\nreturn items.map(item => {\n const docNum = String(item.json.document_number || '');\n const title = String(item.json.title || '');\n const legacyNum = String(item.json.legacy_number || '');\n\n const isRFA = docNum.includes('-RFA-') || title.toLowerCase().includes('rfa');\n\n const systemPrompt = `You are an expert Document Controller for a construction project (LCBP3) in Thailand.\nThe documents are primarily in THAI and ENGLISH.\nYour task is to classify documents and extract metadata from noisy OCR text.\nIf the OCR text is unreadable or gibberish, rely on the provided EXCEL METADATA.\nRespond ONLY with valid JSON.`;\n\n const pdfText = String(item.json.data || '').substring(0, 3500).replace(/[^a-zA-Z0-9ก-๙\\s\\.\\/\\-:\\[\\]\\(\\)]/g, ' ');\n\n const userPrompt = `Analyze this document:\n[EXCEL METADATA]\nDocument Number: ${docNum || 'Not provided'}\nTitle: ${title || 'Not provided'}\nLegacy Number: ${legacyNum || 'Not provided'}\n\n[OCR TEXT EXTRACTION]\n${pdfText}\n\nRules:\n1. Category must be one of: ${JSON.stringify(systemCategories)}\n2. If Document Number contains \"-RFA-\", suggest_category MUST be \"RFA\".\n3. For RFA, extract \"ref_no\" and \"response_to\".\n4. For Letters, identify \"from_org\" and \"to_org\".\n5. Extract \"document_date\" from text (e.g., \"Date:\", \"วันที่\"). Convert Thai Year (BE 2567) to AD (2024) by subtracting 543. Format as YYYY-MM-DD. If not found, return null.\n6. IMPORTANT: You MUST write a new 1-3 sentence summary in Thai evaluating the [OCR TEXT EXTRACTION] and place it in the \"body\" field. If the OCR is gibberish, write \"ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\" in the body.\n7. DO NOT invent non-existent English or Thai words for suggested_title. If you cannot find a clear title from the text, just use the exact EXCEL METADATA Title (${title}).\n8. Suggest 3-5 relevant tags based on content. Check against EXISTING TAGS: ${JSON.stringify(existingTags)}. Use existing tags if possible, otherwise suggest new concise tags (English/Thai).\n\nRespond ONLY with this EXACT JSON structure:\n{\n \"is_valid\": true,\n \"confidence\": 0.95,\n \"suggested_category\": \"${isRFA ? 'RFA' : 'Correspondence'}\",\n \"detected_issues\": [],\n \"suggested_title\": \"${title}\",\n \"suggested_tags\": [\"Construction\", \"${isRFA ? 'Request' : 'Letter'}\"],\n \"metadata\": {\n \"ref_no\": null,\n \"response_to\": null,\n \"from_org\": null,\n \"to_org\": null,\n \"document_date\": null,\n \"body\": \"สรุปสั้นๆ เป็นภาษาไทย 1-3 ประโยค หรือ ไม่สามารถวิเคราะห์รายละเอียดจาก OCR ได้\"\n }\n}`;\n\n return {\n json: {\n ...item.json,\n active_model: model,\n is_fallback: isFallback,\n system_categories: systemCategories,\n ollama_payload: {\n model: model,\n prompt: `${systemPrompt}\\n\\n${userPrompt}`,\n stream: false,\n format: 'json'\n }\n }\n };\n});" }, "id": "9f82950f-7533-4cbd-8e1e-8e441c1cb2a5", "name": "Build AI Prompt", @@ -255,6 +257,8 @@ { "parameters": { "jsCode": "const ollamaItems = $input.all();\nconst originalItems = $('Build AI Prompt').all();\nconst results = [];\n\nfor (let i = 0; i < ollamaItems.length; i++) {\n const ollamaItem = ollamaItems[i];\n const originalItem = originalItems[i];\n\n if (!originalItem) continue; // safety check\n\n // Reconstruct original JSON\n const baseJson = originalItem.json;\n\n try {\n let raw = ollamaItem.json.response || '';\n\n // Clean markdown and whitespace\n raw = raw.replace(/\\`\\`\\`json/gi, '').replace(/\\`\\`\\`/g, '').trim();\n if (!raw) throw new Error('Empty response from AI');\n\n const result = JSON.parse(raw);\n\n // Metadata mapping & normalization\n const meta = result.metadata || {};\n const metadata = {\n ref_no: String(meta.ref_no || '').trim() || null,\n response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;" + response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n document_date: String(meta.document_date || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;" + response_to: String(meta.response_to || '').trim() || null,\n from_org: String(meta.from_org || '').trim() || null,\n to_org: String(meta.to_org || '').trim() || null,\n document_date: String(meta.document_date || '').trim() || null,\n body: String(result.body || meta.body || '').trim() || null\n };\n\n // Tag Validation\n let tags = Array.isArray(result.suggested_tags) ? result.suggested_tags : [];\n tags = [...new Set(tags.map(t => String(t).trim()).filter(t => t.length > 0))];\n\n // Enum Validation for Category\n const systemCategories = baseJson.system_categories || [];\n let finalCategory = result.suggested_category;\n if (!systemCategories.includes(finalCategory)) {\n finalCategory = String(baseJson.document_number || '').includes('-RFA-') ? 'RFA' : 'Correspondence';\n }\n\n const d_issued = baseJson.issued_date || metadata.document_date || null;\n const d_received = baseJson.received_date || d_issued;\n results.push({\n json: {\n ...baseJson,\n ai_result: { ...result, suggested_category: finalCategory, suggested_tags: tags, body: result.body || meta.body || null },\n metadata: metadata,\n issued_date: d_issued,\n received_date: d_received,\n parse_error: null\n }\n });\n } catch (err) {\n results.push({\n json: {\n ...baseJson,\n ai_result: null,\n parse_error: err.message,\n raw_ai_response: ollamaItem.json.response,\n error_type: 'AI_PARSE_ERROR'\n }\n });\n }\n}\n\nreturn results;" }, "id": "281dc950-a3b6-4412-a0b4-76663b8c37ea", "name": "Parse & Validate AI Response", @@ -953,4 +957,4 @@ }, "id": "u7CLP05AyFb8Um0P", "tags": [] -} \ No newline at end of file +}