@@ -411,6 +411,43 @@ function isQualityQuestion(question) {
return /质量|SPC|巡检|条码|Andon|安灯|测量|工位|过程能力|趋势|直方|正态|排列|X-R|XR|X-S|XS/i . test ( String ( question || '' ) )
return /质量|SPC|巡检|条码|Andon|安灯|测量|工位|过程能力|趋势|直方|正态|排列|X-R|XR|X-S|XS/i . test ( String ( question || '' ) )
}
}
function isUsageHelpQuestion ( question ) {
const text = String ( question || '' ) . replace ( /\s+/g , '' )
return [ '\u4f7f\u7528\u8bf4\u660e' , '\u5e2e\u52a9' , '\u64cd\u4f5c\u8bf4\u660e' , '\u600e\u4e48\u7528' , '\u5982\u4f55\u4f7f\u7528' , '\u4f60\u80fd\u505a\u4ec0\u4e48' , '\u6709\u54ea\u4e9b\u529f\u80fd' ] . some ( word => text . indexOf ( word ) > - 1 )
}
function htmlForAssistantUsage ( ) {
let html = '<h4>AI\u52a9\u624b\u4f7f\u7528\u8bf4\u660e</h4>' + table ( [
{ key : 'feature' , label : '\u529f\u80fd' } ,
{ key : 'how' , label : '\u5982\u4f55\u64cd\u4f5c' } ,
{ key : 'example' , label : '\u793a\u4f8b' }
] , [
{ feature : '\u67e5\u8be2\u8d28\u91cf\u6570\u636e' , how : '\u6309\u5de5\u4f4d\u53f7\u3001\u6d4b\u91cf\u4f4d\u7f6e\u3001\u6d4b\u91cf\u9879\u76ee\u7b49\u6761\u4ef6\u67e5\u8be2\uff1b\u4e0d\u5199\u6761\u4ef6\u65f6\u8fd4\u56de\u6700\u65b050\u6761\u3002' , example : '\u67e5\u8be2\u5de5\u4f4d\u53f73165\u7684\u8d28\u91cf\u6570\u636e' } ,
{ feature : '\u67e5\u8be2\u524d\u7f6e\u6761\u4ef6' , how : '\u5148\u5217\u5de5\u4f4d\uff0c\u518d\u6309\u5de5\u4f4d\u67e5\u6d4b\u91cf\u4f4d\u7f6e\uff0c\u6700\u540e\u67e5\u6d4b\u91cf\u9879\u76ee\u3002' , example : '\u5217\u51fa\u5de5\u4f4d\u53f7\uff1b\u5217\u51fa\u5de5\u4f4d\u53f7 OP3165 \u7684\u6d4b\u91cf\u4f4d\u7f6e' } ,
{ feature : 'SPC\u56fe\u8868' , how : '\u67e5\u8d28\u91cf\u6570\u636e\u65f6\u660e\u786e\u8bf4\u8981\u7ed8\u5236\u54ea\u79cd\u56fe\u3002' , example : '\u67e5\u8be2\u8d28\u91cf\u6570\u636e\u5e76\u7ed8\u5236SPC\u57fa\u672c\u8d8b\u52bf\u56fe' } ,
{ feature : 'SPC\u667a\u80fd\u9884\u8b66/\u62a5\u8b66' , how : '\u660e\u786e\u8bf4\u201cSPC\u9884\u6d4b\u9884\u8b66\u201d\u6216\u201cSPC\u9884\u6d4b\u62a5\u8b66\u201d\uff0c\u7cfb\u7edf\u4f1a\u8ba1\u7b97\u8d8b\u52bf\u3001Cp\u3001Cpk\u5e76\u7ed9\u51fa\u5efa\u8bae\u3002' , example : '\u67e5\u8be2\u5de5\u4f4d\u53f7OP3165\u7684\u8d28\u91cf\u6570\u636e\u5e76\u505aSPC\u9884\u6d4b\u9884\u8b66' } ,
{ feature : 'SPC\u53c2\u6570\u8ba1\u7b97' , how : '\u6839\u636e\u5f53\u524d\u6216\u672c\u6b21\u8d28\u91cf\u6570\u636e\u8ba1\u7b97\u5747\u503c\u3001\u6807\u51c6\u5dee\u3001Cp\u3001Cpk\u3002' , example : '\u8ba1\u7b97\u4e00\u4e0bSPC\u4e3b\u8981\u53c2\u6570\uff0c\u8981\u8ba1\u7b97\u7ed3\u679c' } ,
{ feature : '\u9875\u9762\u5bfc\u822a' , how : '\u8bf4\u660e\u8981\u6253\u5f00\u7684\u9875\u9762\u6216\u4e3b\u9875\u3002' , example : '\u6253\u5f00\u4e3b\u9875' }
] )
html += '<h4>\u53ef\u7ed8\u5236\u7684SPC\u56fe\u8868</h4>' + table ( [
{ key : 'name' , label : '\u56fe\u8868' } ,
{ key : 'trigger' , label : '\u89e6\u53d1\u8bf4\u6cd5' }
] , [
{ name : 'SPC\u57fa\u672c\u8d8b\u52bf\u56fe' , trigger : '\u7ed8\u5236SPC\u57fa\u672c\u8d8b\u52bf\u56fe' } ,
{ name : '\u6837\u672c\u8d8b\u52bf\u56fe' , trigger : '\u7ed8\u5236\u6837\u672c\u8d8b\u52bf\u56fe' } ,
{ name : '\u76f4\u65b9\u56fe' , trigger : '\u7ed8\u5236\u76f4\u65b9\u56fe' } ,
{ name : '\u5de5\u5e8f\u80fd\u529b\u5206\u6790\u56fe' , trigger : '\u7ed8\u5236\u5de5\u5e8f\u80fd\u529b\u5206\u6790\u56fe' } ,
{ name : '\u6392\u5217\u56fe' , trigger : '\u7ed8\u5236\u6392\u5217\u56fe' } ,
{ name : '\u5747\u503c\u6781\u5dee\u56fe' , trigger : '\u7ed8\u5236\u5747\u503c\u6781\u5dee\u56fe' } ,
{ name : '\u5747\u503c\u6807\u51c6\u5dee\u56fe' , trigger : '\u7ed8\u5236\u5747\u503c\u6807\u51c6\u5dee\u56fe' }
] )
html += '<h4>\u6ce8\u610f\u4e8b\u9879</h4>' + table ( [ { key : 'item' , label : '\u9879\u76ee' } , { key : 'text' , label : '\u8bf4\u660e' } ] , [
{ item : '\u6570\u636e\u6765\u6e90' , text : '\u6240\u6709\u8d28\u91cf\u6570\u636e\u90fd\u590d\u7528\u9879\u76ee\u5df2\u6709 HMI/CreateData/MESCommonBase.ashx \u67e5\u8be2\u5165\u53e3\uff0c\u4e0d\u76f4\u8fde\u6570\u636e\u5e93\u3002' } ,
{ item : '\u63a8\u8350\u6761\u4ef6' , text : '\u505aSPC\u56fe\u8868\u6216\u9884\u8b66\u65f6\uff0c\u5efa\u8bae\u6307\u5b9a\u5355\u4e00\u5de5\u4f4d\u53f7+\u5355\u4e00\u6d4b\u91cf\u4f4d\u7f6e+\u5355\u4e00\u6d4b\u91cf\u9879\u76ee\u3002' }
] )
return html
}
function listQualityModules ( limit ) {
function listQualityModules ( limit ) {
return ( knowledgeBase . qualityModules || [ ] ) . slice ( 0 , limit || 50 ) . map ( row => ( {
return ( knowledgeBase . qualityModules || [ ] ) . slice ( 0 , limit || 50 ) . map ( row => ( {
title : row . Title ,
title : row . Title ,
@@ -821,7 +858,7 @@ async function querySpcChart(args) {
return { html , charts , data : { params , raw , rows , values , url } }
return { html , charts , data : { params , raw , rows , values , url } }
}
}
function postJson ( url , payload , headers ) {
function postJson ( url , payload , headers , timeoutMs ) {
return new Promise ( ( resolve , reject ) => {
return new Promise ( ( resolve , reject ) => {
const target = new URL ( url )
const target = new URL ( url )
const client = target . protocol === 'https:' ? https : http
const client = target . protocol === 'https:' ? https : http
@@ -846,12 +883,25 @@ function postJson(url, payload, headers) {
} )
} )
} )
} )
req . on ( 'error' , reject )
req . on ( 'error' , reject )
if ( timeoutMs ) {
req . setTimeout ( timeoutMs , ( ) => {
req . destroy ( new Error ( 'Request timeout after ' + timeoutMs + 'ms' ) )
} )
}
req . write ( bodyBuffer )
req . write ( bodyBuffer )
req . end ( )
req . end ( )
} )
} )
}
}
function shouldRouteQualityDataQuery ( question ) {
const text = String ( question || '' )
const hasQualityData = [ '\u8d28\u91cf\u6570\u636e' , '\u8d28\u91cf\u660e\u7ec6' , '\u660e\u7ec6\u6570\u636e' ] . some ( word => text . indexOf ( word ) > - 1 )
const hasAction = [ '\u67e5\u8be2' , '\u67e5\u770b' , '\u663e\u793a' , '\u5217\u51fa' , '\u83b7\u53d6' , '\u8fd4\u56de' ] . some ( word => text . indexOf ( word ) > - 1 )
return hasQualityData && hasAction
}
function isQualityDataQuestion ( question ) {
function isQualityDataQuestion ( question ) {
if ( shouldRouteQualityDataQuery ( question ) ) return true
const text = String ( question || '' )
const text = String ( question || '' )
if ( isFeatureQuestion ( text ) ) return false
if ( isFeatureQuestion ( text ) ) return false
if ( /质量数据/ . test ( text ) && /(查|查询|查找|获取|显示|展示|列出|返回|最近|最新|明细|数据)/ . test ( text ) ) return true
if ( /质量数据/ . test ( text ) && /(查|查询|查找|获取|显示|展示|列出|返回|最近|最新|明细|数据)/ . test ( text ) ) return true
@@ -1002,6 +1052,9 @@ function buildStructuredQualityDataParams(args, profile) {
const range = hasExplicitDateRange ( args )
const range = hasExplicitDateRange ( args )
? Object . assign ( parseDateRange ( args . query || '' ) , { startTime : args . startTime , endTime : args . endTime } )
? Object . assign ( parseDateRange ( args . query || '' ) , { startTime : args . startTime , endTime : args . endTime } )
: { startTime : '1900-01-01' , endTime : '2099-12-31' }
: { startTime : '1900-01-01' , endTime : '2099-12-31' }
const parsedStationNumber = pickArg ( args , [ 'stationNumber' , 'opName' ] ) ||
parseQuotedOrNamed ( args . query || '' , [ '宸ヤ綅鍙?' , '宸ヤ綅' ] ) ||
stationNumberFromQuestion ( args . query || '' )
const values = {
const values = {
startTime : withDateTime ( range . startTime , false ) ,
startTime : withDateTime ( range . startTime , false ) ,
endTime : withDateTime ( range . endTime , true ) ,
endTime : withDateTime ( range . endTime , true ) ,
@@ -1018,6 +1071,9 @@ function buildStructuredQualityDataParams(args, profile) {
pageCurrent : Math . max ( Number ( args . pageCurrent || 1 ) , 1 ) ,
pageCurrent : Math . max ( Number ( args . pageCurrent || 1 ) , 1 ) ,
pageSize : Math . min ( Math . max ( Number ( args . pageSize || args . limit || 50 ) , 1 ) , 50 )
pageSize : Math . min ( Math . max ( Number ( args . pageSize || args . limit || 50 ) , 1 ) , 50 )
}
}
values . stationNumber = normalizeStationNumber ( parsedStationNumber )
values . testPosition = pickArg ( args , [ 'testPosition' , 'measurePosition' , 'measureName' ] ) || testPositionFromQuestion ( args . query || '' ) || values . testPosition
values . testItem = pickArg ( args , [ 'testItem' , 'measureItem' , 'measureContent' ] ) || testItemFromQuestion ( args . query || '' ) || values . testItem
return ( profile . parameters || [ ] ) . map ( param => {
return ( profile . parameters || [ ] ) . map ( param => {
if ( param . output ) return { name : param . name , value : param . defaultValue , type : param . type , output : param . output }
if ( param . output ) return { name : param . name , value : param . defaultValue , type : param . type , output : param . output }
if ( param . checkFor ) {
if ( param . checkFor ) {
@@ -1316,6 +1372,737 @@ function qualityDataCharts(rows, question) {
} ) . filter ( Boolean )
} ) . filter ( Boolean )
}
}
function isQualitySpcWarningQuestion ( question ) {
const text = String ( question || '' ) . toLowerCase ( )
return [
'\u9884\u6d4b\u9884\u8b66' ,
'\u9884\u6d4b\u62a5\u8b66' ,
'spc\u9884\u8b66' ,
'spc\u62a5\u8b66' ,
'\u505aspc\u9884\u8b66' ,
'\u505aspc\u62a5\u8b66' ,
'\u505a\u9884\u8b66' ,
'\u505a\u62a5\u8b66' ,
'\u9884\u8b66\u7ed3\u679c' ,
'\u62a5\u8b66\u7ed3\u679c' ,
'\u9884\u8b66\u5efa\u8bae' ,
'\u62a5\u8b66\u5efa\u8bae' ,
'\u7efc\u8ff0\u4e00\u4e0b\u9884\u6d4b\u9884\u8b66' ,
'spc\u9884\u6d4b\u62a5\u8b66' ,
'\u667a\u80fd\u62a5\u8b66' ,
'\u8d28\u91cf\u62a5\u8b66' ,
'\u8d8b\u52bf\u62a5\u8b66' ,
'cpk\u62a5\u8b66' ,
'cpk\u8d8b\u52bf\u62a5\u8b66' ,
'\u5de5\u5e8f\u80fd\u529b\u62a5\u8b66' ,
'\u8fc7\u7a0b\u80fd\u529b\u62a5\u8b66' ,
'spc预测预警' ,
'智能预警' ,
'质量预警' ,
'趋势预警' ,
'异常预测' ,
'失控风险' ,
'cpk预警' ,
'cpk趋势预警' ,
'工序能力预警' ,
'过程能力预警' ,
'预测这个工位是否有风险'
] . some ( word => text . indexOf ( word ) > - 1 )
}
function shouldRouteQualityWarning ( question ) {
const text = String ( question || '' ) . replace ( /\s+/g , '' ) . toLowerCase ( )
return [ 'spc\u9884\u8b66' , 'spc\u62a5\u8b66' , '\u9884\u6d4b\u9884\u8b66' , '\u9884\u6d4b\u62a5\u8b66' , '\u667a\u80fd\u9884\u8b66' , '\u667a\u80fd\u62a5\u8b66' , '\u8d28\u91cf\u9884\u8b66' , '\u8d28\u91cf\u62a5\u8b66' , 'cpk\u9884\u8b66' , 'cpk\u62a5\u8b66' , '\u505a\u9884\u8b66' , '\u505a\u62a5\u8b66' ] . some ( word => text . indexOf ( word ) > - 1 )
}
function warningNumber ( value ) {
if ( value == null || value === '' ) return NaN
const match = String ( value ) . replace ( /,/g , '' ) . match ( /-?\d+(?:\.\d+)?/ )
return match ? Number ( match [ 0 ] ) : NaN
}
function compatibleValue ( row , aliases ) {
const keys = Object . keys ( row || { } )
for ( let i = 0 ; i < aliases . length ; i ++ ) {
if ( row && row [ aliases [ i ] ] != null && row [ aliases [ i ] ] !== '' ) return row [ aliases [ i ] ]
}
for ( let i = 0 ; i < aliases . length ; i ++ ) {
const needle = String ( aliases [ i ] ) . toLowerCase ( )
const key = keys . find ( item => String ( item ) . toLowerCase ( ) === needle || String ( item ) . toLowerCase ( ) . indexOf ( needle ) > - 1 )
if ( key && row [ key ] != null && row [ key ] !== '' ) return row [ key ]
}
return ''
}
function qualityWarningRecords ( rows ) {
return normalizeRows ( rows , 1000 ) . map ( ( row , index ) => {
const time = compatibleValue ( row , [ '生产日期' , '操作时间' , '完成时间' , '到达时间' , '离开时间' , '时间' ] )
return {
row ,
index ,
time ,
timeValue : parseDateValue ( time ) ,
station : compatibleValue ( row , [ '工位号' , '指南工位号' , '工位编号' , '工序名称' , '工位名称' ] ) ,
stationName : compatibleValue ( row , [ '工序名称' , '工位名称' , '工位号' , '指南工位号' ] ) ,
testPosition : compatibleValue ( row , [ '测量位置' , '位置' ] ) ,
testItem : compatibleValue ( row , [ '测量项目' , '测量内容' , '项目' ] ) ,
value : warningNumber ( compatibleValue ( row , [ '测量值' , '实际值' , 'value' , 'Value' , '数值' ] ) ) ,
upper : warningNumber ( compatibleValue ( row , [ '上限值' , 'USL' , '上限' ] ) ) ,
lower : warningNumber ( compatibleValue ( row , [ '下限值' , 'LSL' , '下限' ] ) ) ,
target : warningNumber ( compatibleValue ( row , [ '理论值' , '目标值' , '中心值' ] ) ) ,
passFlag : compatibleValue ( row , [ '合格标志' , '是否合格' , '合格' ] )
}
} ) . filter ( item => ! Number . isNaN ( item . value ) ) . sort ( ( a , b ) => ( a . timeValue || a . index ) - ( b . timeValue || b . index ) )
}
function warningAverage ( values ) {
return values . length ? values . reduce ( ( acc , item ) => acc + item , 0 ) / values . length : NaN
}
function warningStdDev ( values ) {
if ( values . length < 2 ) return NaN
const avg = warningAverage ( values )
return Math . sqrt ( values . reduce ( ( acc , item ) => acc + Math . pow ( item - avg , 2 ) , 0 ) / ( values . length - 1 ) )
}
function warningRound ( value , digits ) {
return Number . isNaN ( value ) || value == null ? '' : Number ( value ) . toFixed ( digits == null ? 4 : digits )
}
function warningCapability ( values , upper , lower ) {
const mean = warningAverage ( values )
const sigma = warningStdDev ( values )
if ( Number . isNaN ( mean ) || Number . isNaN ( sigma ) || sigma <= 0 || Number . isNaN ( upper ) || Number . isNaN ( lower ) ) {
return { mean , sigma , cp : NaN , cpu : NaN , cpl : NaN , cpk : NaN }
}
const cp = ( upper - lower ) / ( 6 * sigma )
const cpu = ( upper - mean ) / ( 3 * sigma )
const cpl = ( mean - lower ) / ( 3 * sigma )
return { mean , sigma , cp , cpu , cpl , cpk : Math . min ( cpu , cpl ) }
}
function warningCapabilityLevel ( cpk ) {
if ( Number . isNaN ( cpk ) ) return { level : '不可计算' , score : 0 , text : '缺少上下限或样本波动为 0' }
if ( cpk < 0.67 ) return { level : '严重' , score : 70 , text : 'Cpk < 0.67,工序能力严重不足' }
if ( cpk < 1 ) return { level : '预警' , score : 45 , text : 'Cpk < 1.00,工序能力不足' }
if ( cpk < 1.33 ) return { level : '关注' , score : 25 , text : 'Cpk < 1.33,工序能力边缘' }
if ( cpk < 1.67 ) return { level : '正常' , score : 0 , text : 'Cpk 满足常规质量要求' }
return { level : '优秀' , score : 0 , text : 'Cpk >= 1.67,工序能力充足' }
}
function warningRiskRank ( level ) {
return { '优秀' : 0 , '正常' : 0 , '关注' : 1 , '预警' : 2 , '严重' : 3 } [ level ] || 0
}
function warningRiskLevel ( score , strongest ) {
const scoreLevel = score >= 70 ? '严重' : score >= 40 ? '预警' : score >= 20 ? '关注' : '正常'
return warningRiskRank ( strongest ) > warningRiskRank ( scoreLevel ) ? strongest : scoreLevel
}
function warningRule ( rules , name , hit , level , score , description ) {
rules . push ( { name , hit : hit ? '是' : '否' , level : hit ? level : '正常' , score : hit ? score : 0 , description : hit ? description : '未触发' } )
}
function warningWindows ( records , windowSize , windowStep ) {
const values = records . map ( item => item . value )
const upperRecord = records . find ( item => ! Number . isNaN ( item . upper ) )
const lowerRecord = records . find ( item => ! Number . isNaN ( item . lower ) )
const upper = upperRecord ? upperRecord . upper : NaN
const lower = lowerRecord ? lowerRecord . lower : NaN
const size = Math . max ( Number ( windowSize || 25 ) , 2 )
const step = Math . max ( Number ( windowStep || 10 ) , 1 )
const windows = [ ]
if ( values . length && values . length < size ) {
const capability = warningCapability ( values , upper , lower )
const level = warningCapabilityLevel ( capability . cpk )
return [ { name : 'W1' , range : '第1-' + values . length + '条' , mean : capability . mean , sigma : capability . sigma , cp : capability . cp , cpk : capability . cpk , level : level . level , description : level . text } ]
}
for ( let start = 0 ; start + size <= values . length ; start += step ) {
const group = values . slice ( start , start + size )
const capability = warningCapability ( group , upper , lower )
const level = warningCapabilityLevel ( capability . cpk )
windows . push ( { name : 'W' + ( windows . length + 1 ) , range : '第' + ( start + 1 ) + '-' + ( start + group . length ) + '条' , mean : capability . mean , sigma : capability . sigma , cp : capability . cp , cpk : capability . cpk , level : level . level , description : level . text } )
}
return windows
}
function warningConfidence ( records , args ) {
const stations = unique ( records . map ( item => item . station ) . filter ( Boolean ) )
const positions = unique ( records . map ( item => item . testPosition ) . filter ( Boolean ) )
const items = unique ( records . map ( item => item . testItem ) . filter ( Boolean ) )
const hasLimits = records . some ( item => ! Number . isNaN ( item . upper ) ) && records . some ( item => ! Number . isNaN ( item . lower ) )
const reasons = [ ]
let confidence = '高'
if ( records . length < 10 ) {
confidence = '极低'
reasons . push ( '有效样本少于 10 条,仅能做基础超限判断' )
} else if ( records . length < 50 ) {
confidence = '低'
reasons . push ( '有效样本少于 50 条' )
} else if ( records . length < 100 ) {
confidence = '中'
reasons . push ( '有效样本少于 100 条' )
}
if ( stations . length > 1 || positions . length > 1 || items . length > 1 ) {
confidence = confidence === '极低' ? confidence : '低'
reasons . push ( '结果包含多个工位、测量位置或测量项目' )
}
if ( ! hasLimits ) {
confidence = confidence === '极低' ? confidence : '低'
reasons . push ( '缺少上限值或下限值,无法完整计算 Cp/Cpk' )
}
if ( ! ( args && ( args . stationNumber || args . testPosition || args . testItem ) ) ) {
confidence = confidence === '极低' ? confidence : '低'
reasons . push ( '用户未提供完整工位、测量位置、测量项目条件' )
}
return { confidence , reasons : reasons . length ? reasons : [ '数据粒度和规格限满足分析要求' ] }
}
function warningNg ( value ) {
const text = String ( value == null ? '' : value ) . trim ( )
return /不合格|NG|false/i . test ( text ) || text === '0'
}
function analyzeQualitySpcWarning ( records , args ) {
const values = records . map ( item => item . value )
const upperRecord = records . find ( item => ! Number . isNaN ( item . upper ) )
const lowerRecord = records . find ( item => ! Number . isNaN ( item . lower ) )
const targetRecord = records . find ( item => ! Number . isNaN ( item . target ) )
const upper = upperRecord ? upperRecord . upper : NaN
const lower = lowerRecord ? lowerRecord . lower : NaN
const target = targetRecord ? targetRecord . target : NaN
const width = ! Number . isNaN ( upper ) && ! Number . isNaN ( lower ) ? upper - lower : NaN
const rules = [ ]
const outOfSpec = records . filter ( item => ( ! Number . isNaN ( upper ) && item . value > upper ) || ( ! Number . isNaN ( lower ) && item . value < lower ) )
const ngRows = records . filter ( item => warningNg ( item . passFlag ) )
warningRule ( rules , '规格超限' , outOfSpec . length > 0 , '严重' , 70 , '存在 ' + outOfSpec . length + ' 条测量值超出规格限' )
warningRule ( rules , '不合格标志' , ngRows . length > 0 , '严重' , 60 , '存在 ' + ngRows . length + ' 条不合格标志' )
const near10 = ! Number . isNaN ( width ) && width > 0 ? records . filter ( item => ( ! Number . isNaN ( upper ) && item . value <= upper && ( upper - item . value ) / width <= 0.1 ) || ( ! Number . isNaN ( lower ) && item . value >= lower && ( item . value - lower ) / width <= 0.1 ) ) : [ ]
const near5 = ! Number . isNaN ( width ) && width > 0 ? records . filter ( item => ( ! Number . isNaN ( upper ) && item . value <= upper && ( upper - item . value ) / width <= 0.05 ) || ( ! Number . isNaN ( lower ) && item . value >= lower && ( item . value - lower ) / width <= 0.05 ) ) : [ ]
warningRule ( rules , '接近规格限10%' , near10 . length > 0 , '关注' , 20 , '存在 ' + near10 . length + ' 条数据接近规格边界' )
warningRule ( rules , '接近规格限5%' , near5 . length > 0 , '预警' , 35 , '存在 ' + near5 . length + ' 条数据高危接近规格边界' )
const recent5 = values . slice ( - 5 )
const rising = recent5 . length >= 5 && recent5 . every ( ( item , index ) => index === 0 || item > recent5 [ index - 1 ] )
const falling = recent5 . length >= 5 && recent5 . every ( ( item , index ) => index === 0 || item < recent5 [ index - 1 ] )
warningRule ( rules , '连续漂移' , rising || falling , '预警' , 30 , rising ? '最近 5 点连续上升' : '最近 5 点连续下降' )
const recent10 = values . slice ( - 10 )
const previous10 = values . slice ( - 20 , - 10 )
const meanShift = recent10 . length >= 10 && previous10 . length >= 10 && ! Number . isNaN ( width ) && width > 0 && Math . abs ( warningAverage ( recent10 ) - warningAverage ( previous10 ) ) > width * 0.1
warningRule ( rules , '均值偏移' , meanShift , '预警' , 25 , '最近窗口均值较历史窗口偏移超过规格宽度 10%' )
const volatility = recent10 . length >= 10 && previous10 . length >= 10 && warningStdDev ( previous10 ) > 0 && warningStdDev ( recent10 ) > warningStdDev ( previous10 ) * 1.5
warningRule ( rules , '波动增大' , volatility , '预警' , 25 , '最近窗口标准差大于历史窗口 1.5 倍' )
const capability = warningCapability ( values , upper , lower )
const capabilityRisk = warningCapabilityLevel ( capability . cpk )
warningRule ( rules , 'Cpk能力不足' , capabilityRisk . score > 0 , capabilityRisk . level , capabilityRisk . score , capabilityRisk . text )
const windows = warningWindows ( records , args . windowSize , args . windowStep )
const lastWindows = windows . slice ( - 3 )
const cpkFalling = lastWindows . length >= 3 && lastWindows . every ( ( item , index ) => index === 0 || ( ! Number . isNaN ( item . cpk ) && ! Number . isNaN ( lastWindows [ index - 1 ] . cpk ) && item . cpk < lastWindows [ index - 1 ] . cpk ) )
warningRule ( rules , 'Cpk连续下降' , cpkFalling , '预警' , 35 , '最近 3 个滚动窗口 Cpk 连续下降' )
const lastWindow = windows [ windows . length - 1 ]
const prevWindow = windows [ windows . length - 2 ]
const cpkFastDrop = lastWindow && prevWindow && ! Number . isNaN ( lastWindow . cpk ) && ! Number . isNaN ( prevWindow . cpk ) && prevWindow . cpk > 0 && ( prevWindow . cpk - lastWindow . cpk ) / prevWindow . cpk > 0.2
warningRule ( rules , 'Cpk快速下降' , cpkFastDrop , '预警' , 35 , '最近窗口 Cpk 较上一窗口下降超过 20%' )
const centerShift = capability . cp >= 1.33 && capability . cpk < 1
warningRule ( rules , '中心偏移' , centerShift , '预警' , 35 , 'Cp 正常但 Cpk 偏低,过程中心可能偏移' )
const score = Math . min ( 100 , rules . reduce ( ( acc , item ) => acc + Number ( item . score || 0 ) , 0 ) )
const strongest = rules . filter ( item => item . hit === '是' ) . reduce ( ( level , item ) => warningRiskRank ( item . level ) > warningRiskRank ( level ) ? item . level : level , '正常' )
const confidence = warningConfidence ( records , args )
const level = warningRiskLevel ( score , strongest )
const stations = unique ( records . map ( item => item . stationName || item . station ) . filter ( Boolean ) )
const positions = unique ( records . map ( item => item . testPosition ) . filter ( Boolean ) )
const items = unique ( records . map ( item => item . testItem ) . filter ( Boolean ) )
return {
level ,
score ,
sampleCount : records . length ,
timeRange : records . length ? ( ( records [ 0 ] . time || '无时间' ) + ' - ' + ( records [ records . length - 1 ] . time || '无时间' ) ) : '' ,
objectName : ( stations . length === 1 ? stations [ 0 ] : '多工位' ) + ' / ' + ( positions . length === 1 ? positions [ 0 ] : '多测量位置' ) + ' / ' + ( items . length === 1 ? items [ 0 ] : '多测量项目' ) ,
upper ,
lower ,
target ,
mean : capability . mean ,
sigma : capability . sigma ,
cp : capability . cp ,
cpk : capability . cpk ,
cpkTrend : cpkFalling ? '下降' : ( cpkFastDrop ? '快速下降' : '平稳' ) ,
rules ,
windows ,
confidence : confidence . confidence ,
confidenceReasons : confidence . reasons ,
summary : level === '严重' ? '已发现严重质量风险,建议立即确认并追溯相关工件。' : level === '预警' ? '存在明显趋势或工序能力风险,建议尽快人工确认。' : level === '关注' ? '存在轻微趋势或能力边缘风险,建议持续观察。' : '当前样本未发现明显 SPC 预警风险。'
}
}
function warningBadge ( level ) {
const className = { '优秀' : 'ai-risk-normal' , '正常' : 'ai-risk-normal' , '关注' : 'ai-risk-watch' , '预警' : 'ai-risk-warning' , '严重' : 'ai-risk-critical' } [ level ] || 'ai-risk-normal'
return '<span class="ai-risk-badge ' + className + '">' + escapeHtml ( level ) + '</span>'
}
function htmlForQualitySpcWarning ( analysis , queryResult , records ) {
const conclusionRows = [
{ name : '风险等级' , value : warningBadge ( analysis . level ) } ,
{ name : '风险分数' , value : analysis . score } ,
{ name : '分析对象' , value : analysis . objectName } ,
{ name : '样本数量' , value : analysis . sampleCount } ,
{ name : '时间范围' , value : analysis . timeRange } ,
{ name : '当前 Cp' , value : warningRound ( analysis . cp , 4 ) || '不可计算' } ,
{ name : '当前 Cpk' , value : warningRound ( analysis . cpk , 4 ) || '不可计算' } ,
{ name : 'Cpk 趋势' , value : analysis . cpkTrend } ,
{ name : '置信度' , value : analysis . confidence } ,
{ name : '结论摘要' , value : analysis . summary }
]
let html = '<h4>SPC智能预测预警结论</h4><table class="ai-result-table"><tbody>'
conclusionRows . forEach ( row => { html += '<tr><th>' + escapeHtml ( row . name ) + '</th><td>' + row . value + '</td></tr>' } )
html += '</tbody></table>'
html += '<h4>预警规则命中</h4>' + table ( [ { key : 'name' , label : '规则' } , { key : 'hit' , label : '是否命中' } , { key : 'level' , label : '风险等级' } , { key : 'score' , label : '分数' } , { key : 'description' , label : '说明' } ] , analysis . rules )
html += '<h4>Cp/Cpk工序能力趋势</h4>' + table ( [ { key : 'name' , label : '窗口' } , { key : 'range' , label : '样本范围' } , { key : 'meanText' , label : '平均值' } , { key : 'sigmaText' , label : '标准差' } , { key : 'cpText' , label : 'Cp' } , { key : 'cpkText' , label : 'Cpk' } , { key : 'level' , label : '判断' } ] , analysis . windows . map ( item => Object . assign ( { } , item , { meanText : warningRound ( item . mean , 4 ) || '不可计算' , sigmaText : warningRound ( item . sigma , 4 ) || '不可计算' , cpText : warningRound ( item . cp , 4 ) || '不可计算' , cpkText : warningRound ( item . cpk , 4 ) || '不可计算' } ) ) )
html += '<h4>置信度说明</h4>' + table ( [ { key : 'reason' , label : '说明' } ] , analysis . confidenceReasons . map ( reason => ( { reason } ) ) )
html += '<h4>改进建议</h4>' + table ( [ { key : 'risk' , label : '风险' } , { key : 'suggestion' , label : '建议' } ] , [
{ risk : '测量值接近上限' , suggestion : '检查设备补偿、夹具状态、刀具磨损或拧紧参数。' } ,
{ risk : '测量值接近下限' , suggestion : '检查装配不到位、零件批次或工艺参数偏低。' } ,
{ risk : 'Cpk下降' , suggestion : '优先确认过程中心是否偏移,再检查波动来源。' } ,
{ risk : '波动变大' , suggestion : '检查设备稳定性、环境、人员操作和来料一致性。' } ,
{ risk : '已超限' , suggestion : '建议立即停线确认、隔离相关工件并追溯前后批次。' }
] )
html += htmlForQualityDetailRows ( ( queryResult . data && queryResult . data . rows ) || records . map ( item => item . row ) )
return html
}
function isWarningSummaryQuestion ( question ) {
const text = String ( question || '' ) . toLowerCase ( )
return /综述|总结|概括|分析|建议|改进/ . test ( text ) && /预测|预警|报警|spc|cpk/ . test ( text )
}
function isSpcParameterQuestion ( question ) {
const text = String ( question || '' ) . toLowerCase ( )
const subjects = [
'spc' ,
'cp' ,
'cpk' ,
'cpcpk' ,
'\u8fc7\u7a0b\u80fd\u529b' ,
'\u5de5\u5e8f\u80fd\u529b' ,
'\u4e3b\u8981\u53c2\u6570' ,
'\u80fd\u529b\u6307\u6570' ,
'\u6807\u51c6\u5dee' ,
'\u5747\u503c'
]
const actions = [
'\u8ba1\u7b97' ,
'\u7b97\u4e00\u4e0b' ,
'\u53c2\u6570' ,
'\u7ed3\u679c' ,
'cp' ,
'cpk' ,
'cpcpk'
]
return subjects . some ( word => text . indexOf ( word ) > - 1 ) && actions . some ( word => text . indexOf ( word ) > - 1 )
}
function spcParameterRowsFromAnalysis ( analysis ) {
if ( ! analysis || typeof analysis !== 'object' ) return [ ]
return [
{ name : '\u6837\u672c\u6570\u91cf' , symbol : 'n' , value : analysis . sampleCount == null ? '\u672a\u77e5' : analysis . sampleCount , note : '\u53c2\u4e0e\u8ba1\u7b97\u7684\u6709\u6548\u6d4b\u91cf\u503c\u6570\u91cf' } ,
{ name : '\u89c4\u683c\u4e0a\u9650' , symbol : 'USL' , value : warningRound ( Number ( analysis . upper ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u8d28\u91cf\u7279\u6027\u7684\u4e0a\u9650\u503c' } ,
{ name : '\u89c4\u683c\u4e0b\u9650' , symbol : 'LSL' , value : warningRound ( Number ( analysis . lower ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u8d28\u91cf\u7279\u6027\u7684\u4e0b\u9650\u503c' } ,
{ name : '\u6837\u672c\u5747\u503c' , symbol : 'Mean' , value : warningRound ( Number ( analysis . mean ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u6d4b\u91cf\u503c\u5e73\u5747\u6c34\u5e73' } ,
{ name : '\u6837\u672c\u6807\u51c6\u5dee' , symbol : 'Sigma' , value : warningRound ( Number ( analysis . sigma ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u6309\u6837\u672c\u6807\u51c6\u5dee\u4f30\u8ba1\u8fc7\u7a0b\u6ce2\u52a8' } ,
{ name : '\u8fc7\u7a0b\u80fd\u529b\u6307\u6570' , symbol : 'Cp' , value : warningRound ( Number ( analysis . cp ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u53ea\u8bc4\u4ef7\u516c\u5dee\u5bbd\u5ea6\u4e0e\u8fc7\u7a0b\u6ce2\u52a8' } ,
{ name : '\u5b9e\u9645\u8fc7\u7a0b\u80fd\u529b\u6307\u6570' , symbol : 'Cpk' , value : warningRound ( Number ( analysis . cpk ) , 4 ) || '\u4e0d\u53ef\u8ba1\u7b97' , note : '\u540c\u65f6\u8003\u8651\u8fc7\u7a0b\u4e2d\u5fc3\u504f\u79fb' } ,
{ name : 'Cpk\u8d8b\u52bf' , symbol : 'Trend' , value : analysis . cpkTrend || '\u672a\u77e5' , note : '\u7531\u6eda\u52a8\u7a97\u53e3 Cp/Cpk \u7ed3\u679c\u5224\u65ad' }
]
}
function htmlForSpcParameters ( analysis , sourceText ) {
if ( ! analysis || typeof analysis !== 'object' ) {
return '<h4>SPC\u4e3b\u8981\u53c2\u6570\u8ba1\u7b97\u7ed3\u679c</h4>' + table ( [ { key : 'name' , label : '\u9879\u76ee' } , { key : 'value' , label : '\u5185\u5bb9' } ] , [
{ name : '\u72b6\u6001' , value : '\u5f53\u524d\u6ca1\u6709\u53ef\u7528\u4e8e\u8ba1\u7b97\u7684\u8d28\u91cf\u6570\u636e\u6216\u9884\u6d4b\u9884\u8b66\u4e0a\u4e0b\u6587\u3002' } ,
{ name : '\u5efa\u8bae' , value : '\u8bf7\u63d0\u4f9b\u5de5\u4f4d\u53f7\u3001\u6d4b\u91cf\u4f4d\u7f6e\u3001\u6d4b\u91cf\u9879\u76ee\uff0c\u6216\u5148\u6267\u884c\u4e00\u6b21\u8d28\u91cf\u6570\u636e\u67e5\u8be2/\u9884\u6d4b\u9884\u8b66\u3002' }
] )
}
const levelText = warningCapabilityLevel ( Number ( analysis . cpk ) ) . text
let html = '<h4>SPC\u4e3b\u8981\u53c2\u6570\u8ba1\u7b97\u7ed3\u679c</h4>' + table ( [
{ key : 'name' , label : '\u53c2\u6570' } ,
{ key : 'symbol' , label : '\u7b26\u53f7' } ,
{ key : 'value' , label : '\u8ba1\u7b97\u503c' } ,
{ key : 'note' , label : '\u8bf4\u660e' }
] , spcParameterRowsFromAnalysis ( analysis ) )
html += '<h4>\u8ba1\u7b97\u516c\u5f0f</h4>' + table ( [ { key : 'name' , label : '\u516c\u5f0f' } , { key : 'value' , label : '\u5185\u5bb9' } ] , [
{ name : 'Cp' , value : '(USL - LSL) / (6 * Sigma)' } ,
{ name : 'Cpk' , value : 'min((USL - Mean) / (3 * Sigma), (Mean - LSL) / (3 * Sigma))' }
] )
html += '<h4>\u80fd\u529b\u5224\u65ad</h4>' + table ( [ { key : 'name' , label : '\u9879\u76ee' } , { key : 'value' , label : '\u7ed3\u8bba' } ] , [
{ name : '\u6570\u636e\u6765\u6e90' , value : sourceText || '\u8d28\u91cf\u6570\u636e\u67e5\u8be2\u7ed3\u679c' } ,
{ name : '\u5206\u6790\u5bf9\u8c61' , value : analysis . objectName || '\u672a\u77e5' } ,
{ name : '\u65f6\u95f4\u8303\u56f4' , value : analysis . timeRange || '\u672a\u77e5' } ,
{ name : '\u5224\u65ad' , value : levelText }
] )
return html
}
function isQualityDimensionQuestion ( question ) {
const text = String ( question || '' ) . toLowerCase ( )
return /列出|查询|显示|有哪些|有什么/ . test ( text ) &&
/工位号|工位|测量位置|检测位置|测量项目|检测项目|检测内容|测量内容/ . test ( text )
}
function dimensionValue ( row , aliases ) {
return compatibleValue ( row , aliases )
}
function dimensionRows ( rows , kind ) {
const normalized = normalizeRows ( rows , 500 )
const mapped = normalized . map ( row => {
if ( kind === 'station' ) {
return {
stationNo : dimensionValue ( row , [ '工位号' , '指南工位号' , '工位编号' ] ) ,
stationName : dimensionValue ( row , [ '工位名称' , '工序名称' , '名称' ] )
}
}
if ( kind === 'position' ) {
return {
stationNo : dimensionValue ( row , [ '工位号' , '指南工位号' , '工位编号' ] ) ,
testPosition : dimensionValue ( row , [ '测量位置' , '检测位置' , '位置' ] )
}
}
return {
stationNo : dimensionValue ( row , [ '工位号' , '指南工位号' , '工位编号' ] ) ,
testPosition : dimensionValue ( row , [ '测量位置' , '检测位置' , '位置' ] ) ,
testItem : dimensionValue ( row , [ '测量项目' , '检测项目' , '测量内容' , '检测内容' , '项目' ] )
}
} ) . filter ( row => Object . keys ( row ) . some ( key => row [ key ] ) )
const seen = { }
return mapped . filter ( row => {
const key = Object . keys ( row ) . map ( item => row [ item ] ) . join ( '|' )
if ( seen [ key ] ) return false
seen [ key ] = true
return true
} )
}
function mesPayload ( type , name , params , modularId ) {
return {
type : String ( type || '11' ) ,
name ,
param : JSON . stringify ( params || [ ] ) ,
UserID : 'AI' ,
ModularID : modularId || '/QualityAssurance/AssemblyQualitydataQuery'
}
}
async function callMesQuery ( name , params ) {
const payload = mesPayload ( '11' , name , params || [ ] )
const response = await postJson ( MES _BACKEND _URL , JSON . stringify ( payload ) )
const parsed = unwrapMesResponse ( response )
return { payload , response , rows : parsed . rows , output : parsed . output }
}
function stationNumberFromQuestion ( question ) {
return parseQuotedOrNamed ( question || '' , [ '工位号' , '工位' , '工位名称' ] ) || ''
}
function testPositionFromQuestion ( question ) {
return parseQuotedOrNamed ( question || '' , [ '测量位置' , '检测位置' , '位置' ] ) || ''
}
async function queryQualityDimensions ( args ) {
const query = String ( ( args && args . query ) || '' )
const stationNo = ( args && ( args . stationNumber || args . stationNo ) ) || stationNumberFromQuestion ( query )
const testPosition = ( args && ( args . testPosition || args . measurePosition ) ) || testPositionFromQuestion ( query )
const wantsItem = /测量项目|检测项目|检测内容|测量内容|项目/ . test ( query )
const wantsPosition = /测量位置|检测位置|位置/ . test ( query )
const kind = wantsItem ? 'item' : ( wantsPosition && stationNo ? 'position' : 'station' )
let result
if ( kind === 'item' ) {
result = await callMesQuery ( '质量数据查询_测量项目 ' , [
[ '工位号' , stationNo ] ,
[ '测量位置' , testPosition ]
] )
} else if ( kind === 'position' ) {
result = await callMesQuery ( '质量数据查询_测量位置 ' , [
[ '工位号' , stationNo ]
] )
} else {
result = await callMesQuery ( 'MES_计划BOM_工位与名称_查询' , [ ] )
}
const rows = dimensionRows ( result . rows , kind )
let html = '<h4>质量数据查询前置条件</h4>' + table ( [
{ key : 'name' , label : '项目' } ,
{ key : 'value' , label : '内容' }
] , [
{ name : '查询类型' , value : kind === 'station' ? '工位号列表' : kind === 'position' ? '测量位置列表' : '测量项目/检测内容列表' } ,
{ name : '工位号' , value : stationNo || '未指定' } ,
{ name : '测量位置' , value : testPosition || '未指定' } ,
{ name : '查询入口' , value : result . payload . name }
] )
if ( kind === 'station' ) {
html += '<h4>工位号列表</h4>' + table ( [ { key : 'stationNo' , label : '工位号' } , { key : 'stationName' , label : '工位名称' } ] , rows )
} else if ( kind === 'position' ) {
html += '<h4>测量位置列表</h4>' + table ( [ { key : 'stationNo' , label : '工位号' } , { key : 'testPosition' , label : '测量位置' } ] , rows )
} else {
html += '<h4>测量项目/检测内容列表</h4>' + table ( [ { key : 'stationNo' , label : '工位号' } , { key : 'testPosition' , label : '测量位置' } , { key : 'testItem' , label : '测量项目/检测内容' } ] , rows )
}
html += '<h4>下一步建议</h4>' + table ( [ { key : 'step' , label : '步骤' } , { key : 'text' , label : '说明' } ] , [
{ step : '1' , text : '先选择一个工位号。' } ,
{ step : '2' , text : '根据工位号查询测量位置。' } ,
{ step : '3' , text : '根据工位号和测量位置查询测量项目, 再用于质量数据查询、SPC图表或预测预警。' }
] )
return { html , data : { kind , stationNo , testPosition , rows , payload : result . payload , output : result . output } }
}
const QUALITY _DIMENSION _SAFE = {
stationQuery : '\u8d28\u91cf\u6570\u636e\u67e5\u8be2_\u5de5\u4f4d\u53f7' ,
bomStationQuery : 'MES_\u8ba1\u5212BOM_\u5de5\u4f4d\u4e0e\u540d\u79f0_\u67e5\u8be2' ,
positionQuery : '\u8d28\u91cf\u6570\u636e\u67e5\u8be2_\u6d4b\u91cf\u4f4d\u7f6e' ,
itemQuery : '\u8d28\u91cf\u6570\u636e\u67e5\u8be2_\u6d4b\u91cf\u9879\u76ee' ,
stationParam : '\u5de5\u4f4d\u53f7' ,
positionParam : '\u6d4b\u91cf\u4f4d\u7f6e' ,
stationAliases : [ '\u5de5\u4f4d\u53f7' , '\u6307\u5357\u5de5\u4f4d\u53f7' , '\u5de5\u4f4d\u7f16\u53f7' , 'opName' , 'stationNumber' ] ,
stationNameAliases : [ '\u5de5\u4f4d\u540d\u79f0' , '\u5de5\u5e8f\u540d\u79f0' , '\u540d\u79f0' , 'stationName' ] ,
positionAliases : [ '\u6d4b\u91cf\u4f4d\u7f6e' , '\u68c0\u6d4b\u4f4d\u7f6e' , '\u4f4d\u7f6e' , 'measureName' ] ,
itemAliases : [ '\u6d4b\u91cf\u9879\u76ee' , '\u68c0\u6d4b\u9879\u76ee' , '\u6d4b\u91cf\u5185\u5bb9' , '\u68c0\u6d4b\u5185\u5bb9' , '\u9879\u76ee' , 'measureContent' ]
}
function isQualityDimensionQuestion ( question ) {
const text = String ( question || '' ) . toLowerCase ( )
const hasAction = [ '\u5217\u51fa' , '\u67e5\u8be2' , '\u663e\u793a' , '\u6709\u54ea\u4e9b' , '\u6709\u4ec0\u4e48' , '\u83b7\u53d6' ] . some ( word => text . indexOf ( word ) > - 1 )
const hasDimension = [ '\u5de5\u4f4d\u53f7' , '\u5de5\u4f4d' , '\u6d4b\u91cf\u4f4d\u7f6e' , '\u68c0\u6d4b\u4f4d\u7f6e' , '\u6d4b\u91cf\u9879\u76ee' , '\u68c0\u6d4b\u9879\u76ee' , '\u68c0\u6d4b\u5185\u5bb9' , '\u6d4b\u91cf\u5185\u5bb9' ] . some ( word => text . indexOf ( word ) > - 1 )
return hasAction && hasDimension
}
function dimensionRows ( rows , kind ) {
const normalized = normalizeRows ( rows , 500 )
const mapped = normalized . map ( row => {
if ( kind === 'station' ) {
return {
stationNo : compatibleValue ( row , QUALITY _DIMENSION _SAFE . stationAliases ) ,
stationName : compatibleValue ( row , QUALITY _DIMENSION _SAFE . stationNameAliases )
}
}
if ( kind === 'position' ) {
return {
stationNo : compatibleValue ( row , QUALITY _DIMENSION _SAFE . stationAliases ) ,
testPosition : compatibleValue ( row , QUALITY _DIMENSION _SAFE . positionAliases )
}
}
return {
stationNo : compatibleValue ( row , QUALITY _DIMENSION _SAFE . stationAliases ) ,
testPosition : compatibleValue ( row , QUALITY _DIMENSION _SAFE . positionAliases ) ,
testItem : compatibleValue ( row , QUALITY _DIMENSION _SAFE . itemAliases )
}
} ) . filter ( row => Object . keys ( row ) . some ( key => row [ key ] ) )
const seen = { }
return mapped . filter ( row => {
const key = Object . keys ( row ) . map ( item => row [ item ] ) . join ( '|' )
if ( seen [ key ] ) return false
seen [ key ] = true
return true
} )
}
async function callMesQuery ( name , params , options ) {
const payload = mesPayload ( '11' , name , params || [ ] )
const response = await postJson ( MES _BACKEND _URL , JSON . stringify ( payload ) , null , options && options . timeoutMs )
const parsed = unwrapMesResponse ( response )
return { payload , response , rows : parsed . rows , output : parsed . output }
}
function stationNumberFromQuestion ( question ) {
const text = String ( question || '' )
const exact = text . match ( /(?:\u5de5\u4f4d\u53f7|\u5de5\u4f4d)\s*[:\uff1a=]?\s*([A-Za-z0-9_-]+)/i )
if ( exact ) return exact [ 1 ]
const op = text . match ( /\b[A-Z]{1,4}\d{2,}[A-Z0-9_-]*\b/i )
if ( op ) return op [ 0 ]
return parseQuotedOrNamed ( question || '' , [ '\u5de5\u4f4d\u53f7' , '\u5de5\u4f4d' , '\u5de5\u4f4d\u540d\u79f0' ] ) || ''
}
function testPositionFromQuestion ( question ) {
const text = String ( question || '' )
const match = text . match ( /(?:\u6d4b\u91cf\u4f4d\u7f6e|\u68c0\u6d4b\u4f4d\u7f6e|\u4f4d\u7f6e)\s*[:\uff1a=]?\s*([\s\S]+?)(?:\s*(?:\u7684)?(?:\u6d4b\u91cf\u9879\u76ee|\u68c0\u6d4b\u9879\u76ee|\u6d4b\u91cf\u5185\u5bb9|\u68c0\u6d4b\u5185\u5bb9|\u9879\u76ee)|[,\uff0c;\uff1b]|\s+\u5e76|\s+\u505a|$)/ )
if ( match ) return match [ 1 ] . trim ( )
return parseQuotedOrNamed ( question || '' , [ '\u6d4b\u91cf\u4f4d\u7f6e' , '\u68c0\u6d4b\u4f4d\u7f6e' , '\u4f4d\u7f6e' ] ) || ''
}
function testItemFromQuestion ( question ) {
const text = String ( question || '' )
const match = text . match ( /(?:\u6d4b\u91cf\u9879\u76ee|\u68c0\u6d4b\u9879\u76ee|\u6d4b\u91cf\u5185\u5bb9|\u68c0\u6d4b\u5185\u5bb9|\u9879\u76ee)\s*[:\uff1a=]?\s*([\s\S]+?)(?:\u7684?SPC|\u7684?\u9884\u8b66|\u7684?\u62a5\u8b66|[,\uff0c;\uff1b]|\s+\u5e76|\s+\u505a|\s+\u7ed8\u5236|\s+spc|$)/i )
if ( match ) return match [ 1 ] . replace ( /\u7684$/ , '' ) . trim ( )
return parseQuotedOrNamed ( question || '' , [ '\u6d4b\u91cf\u9879\u76ee' , '\u68c0\u6d4b\u9879\u76ee' , '\u6d4b\u91cf\u5185\u5bb9' , '\u68c0\u6d4b\u5185\u5bb9' , '\u9879\u76ee' ] ) || ''
}
function shouldRouteQualityDimension ( question ) {
const text = String ( question || '' )
const asksQualityData = [ '\u8d28\u91cf\u6570\u636e' , '\u660e\u7ec6\u6570\u636e' , '\u6700\u65b0\u6570\u636e' , '\u67e5\u6570\u636e' ] . some ( word => text . indexOf ( word ) > - 1 )
if ( asksQualityData ) return false
const hasAction = [ '\u5217\u51fa' , '\u67e5\u8be2' , '\u663e\u793a' , '\u6709\u54ea\u4e9b' , '\u6709\u4ec0\u4e48' , '\u83b7\u53d6' ] . some ( word => text . indexOf ( word ) > - 1 )
const asksPositionOrItem = [ '\u6d4b\u91cf\u4f4d\u7f6e' , '\u68c0\u6d4b\u4f4d\u7f6e' , '\u6d4b\u91cf\u9879\u76ee' , '\u68c0\u6d4b\u9879\u76ee' , '\u68c0\u6d4b\u5185\u5bb9' , '\u6d4b\u91cf\u5185\u5bb9' ] . some ( word => text . indexOf ( word ) > - 1 )
const compactText = text . replace ( /\s+/g , '' )
const asksStationList = [ '\u5217\u51fa\u5de5\u4f4d\u53f7' , '\u663e\u793a\u5de5\u4f4d\u53f7' , '\u83b7\u53d6\u5de5\u4f4d\u53f7' , '\u5de5\u4f4d\u53f7\u5217\u8868' , '\u5de5\u4f4d\u5217\u8868' ] . some ( word => compactText . indexOf ( word ) > - 1 ) &&
[ '\u5217\u51fa' , '\u663e\u793a' , '\u6709\u54ea\u4e9b' , '\u6709\u4ec0\u4e48' , '\u83b7\u53d6' ] . some ( word => text . indexOf ( word ) > - 1 )
return hasAction && ( asksPositionOrItem || asksStationList )
}
function normalizeStationNumber ( value ) {
const text = String ( value || '' ) . trim ( )
if ( ! text ) return ''
if ( /^op/i . test ( text ) ) return text . toUpperCase ( )
if ( /^\d{3,5}$/ . test ( text ) ) {
const candidate = 'OP' + text
if ( quickQualityStationRows ( ) . some ( row => row . stationNo === candidate ) ) return candidate
}
return text
}
function quickQualityStationRows ( ) {
return [
'OP3025' , 'OP3030' , 'OP3067' , 'OP3076' , 'OP3077' , 'OP3090' , 'OP3130' , 'OP3150' , 'OP3165' , 'OP3170' ,
'OP3175' , 'OP3192' , 'OP3195' , 'OP3220' , 'OP3225' , 'OP3230' , 'OP3240' , 'OP3267' , 'OP3270' , 'OP3272' ,
'OP3290' , 'OP3291' , 'OP3328' , 'OP3387' , 'OP3388' , 'OP3395' , 'OP3400' , 'OP3410' , 'OP3420' , 'OP3421' ,
'OP4000' , 'OP5002' , 'OP5003' , 'OP5004' , 'OP5005' , 'OP5006' , 'OP5010' , 'OP5011'
] . map ( stationNo => ( { stationNo , stationName : '' } ) )
}
async function queryQualityDimensions ( args ) {
const query = String ( ( args && args . query ) || '' )
const stationNo = ( args && ( args . stationNumber || args . stationNo ) ) || stationNumberFromQuestion ( query )
const testPosition = ( args && ( args . testPosition || args . measurePosition ) ) || testPositionFromQuestion ( query )
const wantsItem = [ '\u6d4b\u91cf\u9879\u76ee' , '\u68c0\u6d4b\u9879\u76ee' , '\u68c0\u6d4b\u5185\u5bb9' , '\u6d4b\u91cf\u5185\u5bb9' ] . some ( word => query . indexOf ( word ) > - 1 )
const wantsPosition = [ '\u6d4b\u91cf\u4f4d\u7f6e' , '\u68c0\u6d4b\u4f4d\u7f6e' , '\u4f4d\u7f6e' ] . some ( word => query . indexOf ( word ) > - 1 )
const kind = wantsItem ? 'item' : ( wantsPosition && stationNo ? 'position' : 'station' )
if ( kind === 'station' && ! ( args && args . live === true ) ) {
const rows = quickQualityStationRows ( )
let html = '<h4>\u8d28\u91cf\u6570\u636e\u67e5\u8be2\u524d\u7f6e\u6761\u4ef6</h4>' + table ( [
{ key : 'name' , label : '\u9879\u76ee' } ,
{ key : 'value' , label : '\u5185\u5bb9' }
] , [
{ name : '\u67e5\u8be2\u7c7b\u578b' , value : '\u5de5\u4f4d\u53f7\u5217\u8868' } ,
{ name : '\u67e5\u8be2\u5165\u53e3' , value : QUALITY _DIMENSION _SAFE . stationQuery } ,
{ name : '\u8bf4\u660e' , value : '\u5217\u8868\u6765\u81ea\u9879\u76ee\u5df2\u6709\u8d28\u91cf\u6570\u636e\u5de5\u4f4d\u67e5\u8be2\u5165\u53e3\u9a8c\u8bc1\u7ed3\u679c\uff0c\u7528\u4e8e\u540e\u7eed\u67e5\u6d4b\u91cf\u4f4d\u7f6e\u548c\u6d4b\u91cf\u9879\u76ee\u3002' }
] )
html += '<h4>\u5de5\u4f4d\u53f7\u5217\u8868</h4>' + table ( [ { key : 'stationNo' , label : '\u5de5\u4f4d\u53f7' } , { key : 'stationName' , label : '\u5de5\u4f4d\u540d\u79f0' } ] , rows )
html += '<h4>\u4e0b\u4e00\u6b65\u5efa\u8bae</h4>' + table ( [ { key : 'step' , label : '\u6b65\u9aa4' } , { key : 'text' , label : '\u8bf4\u660e' } ] , [
{ step : '1' , text : '\u8f93\u5165\u201c\u5217\u51fa\u5de5\u4f4d\u53f7 OP3077 \u7684\u6d4b\u91cf\u4f4d\u7f6e\u201d\u67e5\u8be2\u8be5\u5de5\u4f4d\u7684\u68c0\u6d4b\u4f4d\u7f6e\u3002' } ,
{ step : '2' , text : '\u518d\u8f93\u5165\u201c\u5217\u51fa\u5de5\u4f4d\u53f7 OP3077 \u6d4b\u91cf\u4f4d\u7f6e XXX \u7684\u6d4b\u91cf\u9879\u76ee\u201d\u67e5\u68c0\u6d4b\u5185\u5bb9\u3002' }
] )
return { html , data : { kind , stationNo : '' , testPosition : '' , rows , rowCount : rows . length , payload : { name : QUALITY _DIMENSION _SAFE . stationQuery , param : '[]' } , output : [ ] , cached : true } }
}
let result
let note = ''
try {
if ( kind === 'item' ) {
result = await callMesQuery ( QUALITY _DIMENSION _SAFE . itemQuery , [
[ QUALITY _DIMENSION _SAFE . stationParam , stationNo ] ,
[ QUALITY _DIMENSION _SAFE . positionParam , testPosition ]
] , { timeoutMs : 5000 } )
} else if ( kind === 'position' ) {
result = await callMesQuery ( QUALITY _DIMENSION _SAFE . positionQuery , [
[ QUALITY _DIMENSION _SAFE . stationParam , stationNo ]
] , { timeoutMs : 5000 } )
} else {
result = await callMesQuery ( QUALITY _DIMENSION _SAFE . stationQuery , [ ] , { timeoutMs : 5000 } )
if ( ! Array . isArray ( result . rows ) || ! result . rows . length ) {
note = '\u4e13\u7528\u8d28\u91cf\u5de5\u4f4d\u53f7\u5165\u53e3\u672a\u8fd4\u56de\u6570\u636e\uff0c\u5df2\u81ea\u52a8\u56de\u9000\u5230 BOM \u5de5\u4f4d\u67e5\u8be2\u5165\u53e3\u3002'
result = await callMesQuery ( QUALITY _DIMENSION _SAFE . bomStationQuery , [ ] , { timeoutMs : 5000 } )
}
}
} catch ( error ) {
return {
html : '<h4>\u8d28\u91cf\u6570\u636e\u67e5\u8be2\u524d\u7f6e\u6761\u4ef6</h4>' + table ( [
{ key : 'name' , label : '\u9879\u76ee' } ,
{ key : 'value' , label : '\u5185\u5bb9' }
] , [
{ name : '\u72b6\u6001' , value : '\u67e5\u8be2\u5165\u53e3\u5df2\u5b9a\u4f4d\uff0c\u4f46 MESCommonBase.ashx \u8c03\u7528\u5931\u8d25\u3002' } ,
{ name : '\u67e5\u8be2\u7c7b\u578b' , value : kind === 'station' ? '\u5de5\u4f4d\u53f7\u5217\u8868' : kind === 'position' ? '\u6d4b\u91cf\u4f4d\u7f6e\u5217\u8868' : '\u6d4b\u91cf\u9879\u76ee/\u68c0\u6d4b\u5185\u5bb9\u5217\u8868' } ,
{ name : '\u5de5\u4f4d\u53f7' , value : stationNo || '\u672a\u6307\u5b9a' } ,
{ name : '\u6d4b\u91cf\u4f4d\u7f6e' , value : testPosition || '\u672a\u6307\u5b9a' } ,
{ name : '\u9519\u8bef' , value : error . message }
] ) ,
data : { kind , stationNo , testPosition , rows : [ ] , error : error . message }
}
}
const rows = dimensionRows ( result . rows , kind )
let html = '<h4>\u8d28\u91cf\u6570\u636e\u67e5\u8be2\u524d\u7f6e\u6761\u4ef6</h4>' + table ( [
{ key : 'name' , label : '\u9879\u76ee' } ,
{ key : 'value' , label : '\u5185\u5bb9' }
] , [
{ name : '\u67e5\u8be2\u7c7b\u578b' , value : kind === 'station' ? '\u5de5\u4f4d\u53f7\u5217\u8868' : kind === 'position' ? '\u6d4b\u91cf\u4f4d\u7f6e\u5217\u8868' : '\u6d4b\u91cf\u9879\u76ee/\u68c0\u6d4b\u5185\u5bb9\u5217\u8868' } ,
{ name : '\u5de5\u4f4d\u53f7' , value : stationNo || '\u672a\u6307\u5b9a' } ,
{ name : '\u6d4b\u91cf\u4f4d\u7f6e' , value : testPosition || '\u672a\u6307\u5b9a' } ,
{ name : '\u67e5\u8be2\u5165\u53e3' , value : result . payload . name } ,
{ name : '\u8bf4\u660e' , value : note || '\u590d\u7528\u9879\u76ee\u5df2\u6709 CreateData -> MESCommonBase.ashx \u67e5\u8be2\u5165\u53e3\uff0c\u4e0d\u76f4\u8fde\u6570\u636e\u5e93\u3002' }
] )
if ( kind === 'station' ) {
html += '<h4>\u5de5\u4f4d\u53f7\u5217\u8868</h4>' + table ( [ { key : 'stationNo' , label : '\u5de5\u4f4d\u53f7' } , { key : 'stationName' , label : '\u5de5\u4f4d\u540d\u79f0' } ] , rows )
} else if ( kind === 'position' ) {
html += '<h4>\u6d4b\u91cf\u4f4d\u7f6e\u5217\u8868</h4>' + table ( [ { key : 'stationNo' , label : '\u5de5\u4f4d\u53f7' } , { key : 'testPosition' , label : '\u6d4b\u91cf\u4f4d\u7f6e' } ] , rows )
} else {
html += '<h4>\u6d4b\u91cf\u9879\u76ee/\u68c0\u6d4b\u5185\u5bb9\u5217\u8868</h4>' + table ( [ { key : 'stationNo' , label : '\u5de5\u4f4d\u53f7' } , { key : 'testPosition' , label : '\u6d4b\u91cf\u4f4d\u7f6e' } , { key : 'testItem' , label : '\u6d4b\u91cf\u9879\u76ee/\u68c0\u6d4b\u5185\u5bb9' } ] , rows )
}
html += '<h4>\u4e0b\u4e00\u6b65\u5efa\u8bae</h4>' + table ( [ { key : 'step' , label : '\u6b65\u9aa4' } , { key : 'text' , label : '\u8bf4\u660e' } ] , [
{ step : '1' , text : '\u5148\u4f7f\u7528\u201c\u5217\u51fa\u5de5\u4f4d\u53f7\u201d\u83b7\u53d6\u53ef\u7528\u5de5\u4f4d\u3002' } ,
{ step : '2' , text : '\u518d\u8f93\u5165\u201c\u5217\u51fa\u5de5\u4f4d\u53f7 XXX \u7684\u6d4b\u91cf\u4f4d\u7f6e\u201d\u83b7\u53d6\u8be5\u5de5\u4f4d\u53ef\u68c0\u6d4b\u7684\u4f4d\u7f6e\u3002' } ,
{ step : '3' , text : '\u6700\u540e\u8f93\u5165\u201c\u5217\u51fa\u5de5\u4f4d\u53f7 XXX \u6d4b\u91cf\u4f4d\u7f6e YYY \u7684\u6d4b\u91cf\u9879\u76ee\u201d\uff0c\u518d\u7528\u8fd9\u4e9b\u6761\u4ef6\u67e5\u8be2\u8d28\u91cf\u6570\u636e\u6216\u7ed8\u5236 SPC \u56fe\u8868\u3002' }
] )
return { html , data : { kind , stationNo , testPosition , rows , rowCount : rows . length , payload : result . payload , output : result . output } }
}
function htmlForWarningSummary ( analysis ) {
if ( ! analysis || typeof analysis !== 'object' ) {
return '<h4>预测预警结果综述</h4>' + table ( [ { key : 'name' , label : '项目' } , { key : 'value' , label : '内容' } ] , [
{ name : '状态' , value : '当前对话中没有可用的 SPC 预测预警结果。' } ,
{ name : '建议' , value : '请先输入“查询质量数据 工位号 ... 测量位置 ... 测量项目 ... 做SPC预测预警”, 生成预警结果后再要求综述。' }
] )
}
const summaryRows = [
{ name : '风险等级' , value : analysis . level || '未知' } ,
{ name : '风险分数' , value : analysis . score == null ? '未知' : analysis . score } ,
{ name : '分析对象' , value : analysis . objectName || '未知' } ,
{ name : '样本数量' , value : analysis . sampleCount == null ? '未知' : analysis . sampleCount } ,
{ name : '时间范围' , value : analysis . timeRange || '未知' } ,
{ name : '当前 Cp' , value : warningRound ( Number ( analysis . cp ) , 4 ) || '不可计算' } ,
{ name : '当前 Cpk' , value : warningRound ( Number ( analysis . cpk ) , 4 ) || '不可计算' } ,
{ name : 'Cpk趋势' , value : analysis . cpkTrend || '未知' } ,
{ name : '置信度' , value : analysis . confidence || '未知' } ,
{ name : '综述' , value : analysis . summary || '已根据当前质量数据完成 SPC 预测预警分析。' }
]
const hitRules = Array . isArray ( analysis . rules ) ? analysis . rules . filter ( rule => rule && rule . hit === '是' ) : [ ]
const suggestionRows = [
{ risk : '优先处理' , suggestion : ( analysis . level === '严重' || analysis . level === '预警' ) ? '优先复核命中的预警规则和最近异常工件,必要时进行隔离、复测和工艺确认。' : '当前未见高等级风险,建议保持监控并关注后续趋势变化。' } ,
{ risk : '过程能力' , suggestion : Number ( analysis . cpk ) < 1.33 ? 'Cpk 低于 1.33 时应检查过程中心偏移、设备稳定性、夹具状态和关键工艺参数。' : '过程能力暂满足常规要求,可继续按现有频率监控。' } ,
{ risk : '数据粒度' , suggestion : '建议按单一工位、单一测量位置、单一测量项目重新分析,减少混合数据导致的误判。' } ,
{ risk : '响应闭环' , suggestion : '将命中规则、对应工件编号和时间范围记录到质量处置流程,形成复核、调整、验证的闭环。' }
]
let html = '<h4>预测预警结果综述</h4>' + table ( [ { key : 'name' , label : '项目' } , { key : 'value' , label : '内容' } ] , summaryRows )
html += '<h4>已触发规则</h4>' + table ( [ { key : 'name' , label : '规则' } , { key : 'level' , label : '等级' } , { key : 'score' , label : '分数' } , { key : 'description' , label : '说明' } ] , hitRules . length ? hitRules : [ { name : '无高风险规则' , level : '正常' , score : 0 , description : '当前结果未命中高风险规则。' } ] )
html += '<h4>改进建议</h4>' + table ( [ { key : 'risk' , label : '方向' } , { key : 'suggestion' , label : '建议' } ] , suggestionRows )
return html
}
function qualityWarningCharts ( records , analysis ) {
if ( ! records . length ) return [ ]
const labels = records . map ( ( item , index ) => item . time || String ( index + 1 ) )
const values = records . map ( item => item . value )
const markLine = [ ]
if ( ! Number . isNaN ( analysis . upper ) ) markLine . push ( { name : 'USL' , yAxis : analysis . upper } )
if ( ! Number . isNaN ( analysis . lower ) ) markLine . push ( { name : 'LSL' , yAxis : analysis . lower } )
if ( ! Number . isNaN ( analysis . target ) ) markLine . push ( { name : '理论值' , yAxis : analysis . target } )
if ( ! Number . isNaN ( analysis . mean ) ) markLine . push ( { name : '平均值' , yAxis : Number ( analysis . mean . toFixed ( 4 ) ) } )
const trend = polishChart ( { id : 'chart_spc_warning_trend_' + Date . now ( ) + '_' + Math . floor ( Math . random ( ) * 10000 ) , title : 'SPC智能预测预警-测量值趋势' , option : { title : { text : 'SPC智能预测预警-测量值趋势' , left : 'center' } , tooltip : { trigger : 'axis' } , grid : { left : 58 , right : 28 , top : 74 , bottom : 58 } , xAxis : { type : 'category' , data : labels , axisLabel : { rotate : 35 } } , yAxis : { type : 'value' , scale : true } , series : [ { name : '测量值' , type : 'line' , smooth : true , data : values . map ( ( value , index ) => { const item = records [ index ] ; const out = ( ! Number . isNaN ( item . upper ) && value > item . upper ) || ( ! Number . isNaN ( item . lower ) && value < item . lower ) ; return { value , itemStyle : { color : out ? '#dc2626' : '#2563eb' } } } ) , markLine : markLine . length ? { symbol : 'none' , data : markLine } : undefined } ] } } , [ '#2563eb' , '#dc2626' , '#f97316' ] )
const capability = polishChart ( { id : 'chart_spc_warning_capability_' + Date . now ( ) + '_' + Math . floor ( Math . random ( ) * 10000 ) , title : 'SPC智能预测预警-Cp/Cpk趋势' , option : { title : { text : 'SPC智能预测预警-Cp/Cpk趋势' , left : 'center' } , tooltip : { trigger : 'axis' } , legend : { top : 36 } , grid : { left : 58 , right : 28 , top : 78 , bottom : 48 } , xAxis : { type : 'category' , data : analysis . windows . map ( item => item . name ) } , yAxis : { type : 'value' , scale : true } , series : [ { name : 'Cp' , type : 'line' , smooth : true , data : analysis . windows . map ( item => Number . isNaN ( item . cp ) ? null : Number ( item . cp . toFixed ( 4 ) ) ) } , { name : 'Cpk' , type : 'line' , smooth : true , data : analysis . windows . map ( item => Number . isNaN ( item . cpk ) ? null : { value : Number ( item . cpk . toFixed ( 4 ) ) , itemStyle : { color : item . cpk < 1 ? '#dc2626' : '#16a34a' } } ) , markLine : { symbol : 'none' , data : [ { name : '优秀线1.67' , yAxis : 1.67 } , { name : '合格线1.33' , yAxis : 1.33 } , { name : '预警线1.00' , yAxis : 1 } , { name : '严重线0.67' , yAxis : 0.67 } ] } } ] } } , [ '#2563eb' , '#16a34a' , '#f97316' , '#dc2626' ] )
return [ trend , capability ]
}
async function queryQualityWarningData ( args ) {
const displaySize = Math . min ( Math . max ( Number ( args . displaySize || 50 ) , 1 ) , 50 )
const analysisSize = Math . min ( Math . max ( Number ( args . analysisSize || 150 ) , displaySize ) , 150 )
const queryResult = await queryQualityData ( Object . assign ( { } , args , { limit : analysisSize , pageSize : analysisSize , timeoutMs : 15000 } ) )
const rawRows = queryResult . data && Array . isArray ( queryResult . data . rawRows ) && queryResult . data . rawRows . length ? queryResult . data . rawRows : ( ( queryResult . data && queryResult . data . rows ) || [ ] )
const records = qualityWarningRecords ( rawRows ) . slice ( - analysisSize )
const analysis = analyzeQualitySpcWarning ( records , Object . assign ( { } , args , { displaySize , analysisSize } ) )
return { html : htmlForQualitySpcWarning ( analysis , queryResult , records ) , charts : qualityWarningCharts ( records , analysis ) , data : Object . assign ( { } , queryResult . data || { } , { rawRows , analysis , capabilityWindows : analysis . windows , confidence : analysis . confidence } ) }
}
function latestQualityRows ( rows , profile , limit ) {
function latestQualityRows ( rows , profile , limit ) {
const source = Array . isArray ( rows ) ? rows . slice ( ) : [ ]
const source = Array . isArray ( rows ) ? rows . slice ( ) : [ ]
const maxRows = Math . min ( Math . max ( Number ( limit || QUALITY _DATA _DEFAULT _LIMIT ) , 1 ) , QUALITY _DATA _DEFAULT _LIMIT )
const maxRows = Math . min ( Math . max ( Number ( limit || QUALITY _DATA _DEFAULT _LIMIT ) , 1 ) , QUALITY _DATA _DEFAULT _LIMIT )
@@ -1353,7 +2140,7 @@ async function queryQualityData(args) {
} ]
} ]
let response
let response
try {
try {
response = await postJson ( MES _BACKEND _URL , JSON . stringify ( payload ) )
response = await postJson ( MES _BACKEND _URL , JSON . stringify ( payload ) , null , args && args . timeoutMs )
} catch ( error ) {
} catch ( error ) {
return {
return {
html : '<h4>质量数据查询入口已定位</h4>' + table ( [
html : '<h4>质量数据查询入口已定位</h4>' + table ( [
@@ -1500,7 +2287,9 @@ const tools = [
{ name : 'list_quality_modules' , description : '列出质量管理相关页面。' , inputSchema : { type : 'object' , properties : { limit : { type : 'number' } } } } ,
{ name : 'list_quality_modules' , description : '列出质量管理相关页面。' , inputSchema : { type : 'object' , properties : { limit : { type : 'number' } } } } ,
{ name : 'search_quality_functions' , description : '搜索质量管理相关页面和查询入口。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , limit : { type : 'number' } } , required : [ 'query' ] } } ,
{ name : 'search_quality_functions' , description : '搜索质量管理相关页面和查询入口。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , limit : { type : 'number' } } , required : [ 'query' ] } } ,
{ name : 'describe_quality_data_query' , description : '按查询质量数据相关问题返回页面已有查询入口、参数映射和结果字段,不调用数据库。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , limit : { type : 'number' } } , required : [ 'query' ] } } ,
{ name : 'describe_quality_data_query' , description : '按查询质量数据相关问题返回页面已有查询入口、参数映射和结果字段,不调用数据库。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , limit : { type : 'number' } } , required : [ 'query' ] } } ,
{ name : 'query_quality_dimensions' , description : 'List quality query dimensions: stations, measurement positions, and measurement items using existing MESCommonBase query entries.' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , stationNumber : { type : 'string' } , stationNo : { type : 'string' } , testPosition : { type : 'string' } , measurePosition : { type : 'string' } } } } ,
{ name : 'query_quality_data' , description : '按项目 CreateData 查询入口调用质量管理数据,返回 HTML 表格结果。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , nameOrSql : { type : 'string' } , params : { type : 'object' } , startTime : { type : 'string' } , endTime : { type : 'string' } , stationNumber : { type : 'string' } , pageCurrent : { type : 'number' } , pageSize : { type : 'number' } , limit : { type : 'number' } } } } ,
{ name : 'query_quality_data' , description : '按项目 CreateData 查询入口调用质量管理数据,返回 HTML 表格结果。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , nameOrSql : { type : 'string' } , params : { type : 'object' } , startTime : { type : 'string' } , endTime : { type : 'string' } , stationNumber : { type : 'string' } , pageCurrent : { type : 'number' } , pageSize : { type : 'number' } , limit : { type : 'number' } } } } ,
{ name : 'predict_quality_spc_warning' , description : 'Run SPC warning prediction from existing quality data query results; analyze measurement trends and Cp/Cpk capability trends without direct database access.' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , stationNumber : { type : 'string' } , testPosition : { type : 'string' } , testItem : { type : 'string' } , engineId : { type : 'string' } , startTime : { type : 'string' } , endTime : { type : 'string' } , analysisSize : { type : 'number' } , displaySize : { type : 'number' } , windowSize : { type : 'number' } , windowStep : { type : 'number' } } } } ,
{ name : 'list_spc_charts' , description : '列出 SPC 图表类型与对应接口。' , inputSchema : { type : 'object' , properties : { } } } ,
{ name : 'list_spc_charts' , description : '列出 SPC 图表类型与对应接口。' , inputSchema : { type : 'object' , properties : { } } } ,
{ name : 'spc_query_chart' , description : '根据条件调用 SPC 接口并返回图表、摘要和明细。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , chartType : { type : 'string' } , opName : { type : 'string' } , measureName : { type : 'string' } , measureContent : { type : 'string' } , startTime : { type : 'string' } , endTime : { type : 'string' } } } } ,
{ name : 'spc_query_chart' , description : '根据条件调用 SPC 接口并返回图表、摘要和明细。' , inputSchema : { type : 'object' , properties : { query : { type : 'string' } , chartType : { type : 'string' } , opName : { type : 'string' } , measureName : { type : 'string' } , measureContent : { type : 'string' } , startTime : { type : 'string' } , endTime : { type : 'string' } } } } ,
{ name : 'reload_project_index' , description : '重新加载知识库。' , inputSchema : { type : 'object' , properties : { } } } ,
{ name : 'reload_project_index' , description : '重新加载知识库。' , inputSchema : { type : 'object' , properties : { } } } ,
@@ -1549,10 +2338,18 @@ async function toolResult(name, args) {
: '<h4>质量数据查询功能</h4>' + table ( [ { key : 'message' , label : '提示' } ] , [ { message : '未找到质量数据查询画像' } ] )
: '<h4>质量数据查询功能</h4>' + table ( [ { key : 'message' , label : '提示' } ] , [ { message : '未找到质量数据查询画像' } ] )
return { text : JSON . stringify ( profile || { } , null , 2 ) , html , data : { profile , rows } }
return { text : JSON . stringify ( profile || { } , null , 2 ) , html , data : { profile , rows } }
}
}
if ( name === 'query_quality_dimensions' ) {
const result = await queryQualityDimensions ( args || { } )
return { text : JSON . stringify ( result . data , null , 2 ) , html : result . html , data : result . data }
}
if ( name === 'query_quality_data' ) {
if ( name === 'query_quality_data' ) {
const result = await queryQualityData ( args || { } )
const result = await queryQualityData ( args || { } )
return { text : JSON . stringify ( result . data , null , 2 ) , html : result . html , data : result . data }
return { text : JSON . stringify ( result . data , null , 2 ) , html : result . html , data : result . data }
}
}
if ( name === 'predict_quality_spc_warning' ) {
const result = await queryQualityWarningData ( args || { } )
return { text : JSON . stringify ( result . data , null , 2 ) , html : result . html , data : result . data }
}
if ( name === 'list_spc_charts' ) {
if ( name === 'list_spc_charts' ) {
const rows = knowledgeBase . spcCharts || [ ]
const rows = knowledgeBase . spcCharts || [ ]
return { text : JSON . stringify ( rows , null , 2 ) , html : table ( [ { key : 'title' , label : '图表' } , { key : 'chartType' , label : '类型' } , { key : 'apiFile' , label : 'API 文件' } , { key : 'endpoint' , label : '后端接口' } ] , rows ) , data : rows }
return { text : JSON . stringify ( rows , null , 2 ) , html : table ( [ { key : 'title' , label : '图表' } , { key : 'chartType' , label : '类型' } , { key : 'apiFile' , label : 'API 文件' } , { key : 'endpoint' , label : '后端接口' } ] , rows ) , data : rows }
@@ -1637,8 +2434,73 @@ const server = http.createServer(async (req, res) => {
const body = await parseBody ( req )
const body = await parseBody ( req )
const question = String ( body . question || '' ) . trim ( )
const question = String ( body . question || '' ) . trim ( )
if ( ! question ) return send ( res , 400 , { error : 'question is required' } )
if ( ! question ) return send ( res , 400 , { error : 'question is required' } )
const clientContext = body . context && typeof body . context === 'object' ? body . context : { }
if ( isUsageHelpQuestion ( question ) ) {
return send ( res , 200 , { html : htmlForAssistantUsage ( ) , data : { type : 'usage-help' } , charts : [ ] } )
}
if ( shouldRouteQualityDimension ( question ) ) {
const dimensionResult = await queryQualityDimensions ( { query : question } )
return send ( res , 200 , dimensionResult )
}
const navigation = navigationResponse ( question )
const navigation = navigationResponse ( question )
if ( navigation ) return send ( res , 200 , navigation )
if ( navigation ) return send ( res , 200 , navigation )
if ( isWarningSummaryQuestion ( question ) && clientContext . lastSpcWarning && clientContext . lastSpcWarning . analysis ) {
return send ( res , 200 , {
html : htmlForWarningSummary ( clientContext . lastSpcWarning . analysis ) ,
data : { contextUsed : 'lastSpcWarning' , analysis : clientContext . lastSpcWarning . analysis }
} )
}
if ( isWarningSummaryQuestion ( question ) ) {
return send ( res , 200 , {
html : htmlForWarningSummary ( null ) ,
data : { contextUsed : 'none' }
} )
}
if ( isSpcParameterQuestion ( question ) && clientContext . lastSpcWarning && clientContext . lastSpcWarning . analysis ) {
return send ( res , 200 , {
html : htmlForSpcParameters ( clientContext . lastSpcWarning . analysis , '上一轮SPC预测预警结果' ) ,
data : { contextUsed : 'lastSpcWarning' , analysis : clientContext . lastSpcWarning . analysis }
} )
}
if ( isSpcParameterQuestion ( question ) ) {
const profile = QUALITY _DATA _QUERY _PROFILE
const warningResult = await queryQualityWarningData ( { query : question , nameOrSql : profile && profile . nameOrSql , analysisSize : 150 , displaySize : 50 , windowSize : 25 , windowStep : 10 } )
const analysis = warningResult . data && warningResult . data . analysis
return send ( res , 200 , Object . assign ( { } , warningResult , {
html : htmlForSpcParameters ( analysis , '本次质量数据查询结果' ) + htmlForQualityDetailRows ( ( warningResult . data && warningResult . data . rows ) || [ ] ) ,
charts : [ ]
} ) )
}
if ( ! isFeatureQuestion ( question ) && shouldRouteQualityDimension ( question ) ) {
const dimensionResult = await queryQualityDimensions ( { query : question } )
return send ( res , 200 , dimensionResult )
}
if ( ! isFeatureQuestion ( question ) && ( shouldRouteQualityWarning ( question ) || isQualitySpcWarningQuestion ( question ) ) ) {
const profile = QUALITY _DATA _QUERY _PROFILE
const warningResult = await queryQualityWarningData ( { query : question , nameOrSql : profile && profile . nameOrSql , analysisSize : 150 , displaySize : 50 , windowSize : 25 , windowStep : 10 } )
const requestedCharts = qualityChartTypes ( question )
if ( requestedCharts . length ) {
const chartRows = warningResult . data && Array . isArray ( warningResult . data . rawRows ) && warningResult . data . rawRows . length
? warningResult . data . rawRows
: ( ( warningResult . data && warningResult . data . rows ) || [ ] )
const normalCharts = qualityDataCharts ( chartRows , question )
const warningCharts = Array . isArray ( warningResult . charts ) ? warningResult . charts : [ ]
const chartRowsForHtml = normalCharts . concat ( warningCharts ) . map ( chart => ( {
name : chart . title || ( chart . option && chart . option . title && chart . option . title . text ) || chart . id ,
type : chart . id || '' ,
source : '同一批质量数据查询结果'
} ) )
const chartHtml = '<h4>图表输出</h4>' + table ( [
{ key : 'name' , label : '图表' } ,
{ key : 'source' , label : '数据来源' }
] , chartRowsForHtml . length ? chartRowsForHtml : [ { name : '未绘制图表' , source : '质量数据中没有可识别的数值型测量值' } ] )
return send ( res , 200 , Object . assign ( { } , warningResult , {
html : chartHtml + warningResult . html ,
charts : normalCharts . concat ( warningCharts )
} ) )
}
return send ( res , 200 , warningResult )
}
if ( ! isFeatureQuestion ( question ) && isQualityChartQuestion ( question ) ) {
if ( ! isFeatureQuestion ( question ) && isQualityChartQuestion ( question ) ) {
const profile = QUALITY _DATA _QUERY _PROFILE
const profile = QUALITY _DATA _QUERY _PROFILE
const queryResult = await queryQualityData ( { query : question , nameOrSql : profile && profile . nameOrSql , limit : 50 , pageSize : 50 } )
const queryResult = await queryQualityData ( { query : question , nameOrSql : profile && profile . nameOrSql , limit : 50 , pageSize : 50 } )
@@ -1669,6 +2531,11 @@ const server = http.createServer(async (req, res) => {
} , { useProjectContext : false } )
} , { useProjectContext : false } )
return send ( res , 200 , Object . assign ( { } , chartResult , { html } ) )
return send ( res , 200 , Object . assign ( { } , chartResult , { html } ) )
}
}
if ( ! isFeatureQuestion ( question ) && shouldRouteQualityDataQuery ( question ) ) {
const profile = QUALITY _DATA _QUERY _PROFILE
const queryResult = await queryQualityData ( { query : question , nameOrSql : profile && profile . nameOrSql , limit : 50 , pageSize : 50 } )
return send ( res , 200 , queryResult )
}
if ( ! isFeatureQuestion ( question ) && isQualityDataQuestion ( question ) ) {
if ( ! isFeatureQuestion ( question ) && isQualityDataQuestion ( question ) ) {
const profile = QUALITY _DATA _QUERY _PROFILE
const profile = QUALITY _DATA _QUERY _PROFILE
const queryResult = await queryQualityData ( { query : question , nameOrSql : profile && profile . nameOrSql , limit : 50 , pageSize : 50 } )
const queryResult = await queryQualityData ( { query : question , nameOrSql : profile && profile . nameOrSql , limit : 50 , pageSize : 50 } )