1const state = { 2 aiMode: 'demo', 3 aiServiceOnline: false, 4 aiSession: null, 5 projectContext: null, 6 analysis: null, 7 answers: {}, 8 questionIndex: 0, 9 interviewComplete: false, 10 answerPending: false, 11 patent: { scanned: false, confirmed: false, outputMode: 'unavailable' }, 12 flashpoc: { projectId: null, generated: false, selected: null, confirmed: false, outputMode: 'demo' }, 13 development: { created: false, taskId: null, phase: 0, activities: [], evidenceMode: 'unverified' }, 14 regai: { validated: false, documentsGenerated: false, outputMode: 'unavailable' }, 15 commercial: { algorithm: null, kolsMatched: false, completed: false, outputMode: 'planning' }, 16 intakeTrack: 'å°ä¸ç¡®å®', 17 intakeRecommendedTrack: null, 18 currentView: 'incubator' 19}; 20 21const scenarioLibrary = [ 22 { 23 match: ['骨ç§', 'é»', 'ç©¿é', 'ç®è´¨éª¨'], 24 summary: '骨ç§å¨åå·¥å ·å¨é»åç©¿éç®è´¨éª¨çç¬é´ç¼ºå°å¯é åé¦ï¼éè¦éä½åæ¹è½¯ç»ç»è¯¯ä¼¤é£é©ã', 25 tags: ['éª¨ç§ææ¯', 'å¨åå·¥å ·', 'ç©¿éæç¥', 'å®å ¨é¢è¦'], 26 mappings: [ 27 ['âæ æ³å¤æç©¿éâ', 'è½´åé»åå忣æµ', 'éæ ·ç ⥠1kHz'], 28 ['â伤å°è½¯ç»ç»â', 'ç©¿éå主å¨å¶å¨', 'ååº â¤ 20ms'], 29 ['â峿¶åé¦â', '夿¨¡æå馿¨¡å', 'è§¦è§ + 声å '], 30 ['æ¯ä¸ææä½¿ç¨', '使¯éæ¡æè®¾è®¡', 'æ´æº ⤠1.2kg'] 31 ], 32 constraints: [['æ¯å¼', '骨ç§é»å'], ['æ è', 'å¯çèéä»¶'], ['ç¯å¢', 'æ¶²ä½é£æº '], ['å®å ¨ç级', '主å¨é²æ¤']], 33 tests: [['â¡', 'ç©¿éè¯å«å»¶è¿æµè¯'], ['â', 'ä¸å骨å¯åº¦è¯å«åç¡®ç'], ['ç¾', '误å¶å¨ä¸å¤±æå®å ¨æµè¯']], 34 questions: [ 35 { q: 'è¿ä¸ªç©¿éé£é©ä¸»è¦åçå¨åªç±»éª¨ç§ææ¯ï¼', why: 'ä¸åæ¯å¼çç®æ 骨å度ãåæ¹é«é£é©ç»ç»ä¸å¯æ¥åå¶å¨è·ç¦»å·®å¼å¾å¤§ï¼è¿ä¼ç´æ¥å³å®æ£æµæ¨¡ååå®å ¨ä½éã', options: ['èæ±ææ¯', 'å伤åºå®', 'å ³èææ¯', 'å ¶ä»æ¯å¼'], insight: { 'èæ±ææ¯': 'èæ±åºæ¯é»è¿ç¥ç»ä¸ç¡¬èï¼ç³»ç»åºä¼å è¿½æ±æä½æ¼æ£çï¼å¹¶éç¨æ´ä¿å®çå¶å¨éå¼ã', 'å伤åºå®': 'å伤骨质差å¼å¤§ï¼éè¦è¦çéª¨è´¨çæ¾å°é«å¯åº¦ç®è´¨éª¨çå®½å¨ææ£æµèå´ã', 'å ³èææ¯': 'å ³èåºæ¯æ´å¼ºè°ç©ºé´éå¶ä¸éå¤é»åä¸è´æ§ï¼éè¦ç»å深度æ§å¶ã' } }, 36 { q: 'ç°ææä½ä¸ï¼å»çé常ä¾é ä»ä¹å¤æâååç©¿éâï¼', why: 'éè¦æç¡®ç°ææç¥ééï¼æè½å¤ææ°æ¹æ¡åºå¢å¼ºå»ç夿ï¼è¿æ¯æ¥ç®¡é«é£é©å¨ä½ã', options: ['ææçªå', '声é³åå', 'æ¯ä¸å½±å', '婿æé'], insight: { 'ææçªå': 'ææå±äºé«ç»éªä¾èµä¿¡å·ï¼éåç¨è½´åååæç©ååè¿è¡å®¢è§éåã', '声é³åå': '声å¦ä¿¡å·å¯ä½ä¸ºè¾ å©ï¼ä½å®¹æåææ¯å®¤èæ¯åªå£°å设å¤ä¸ªä½å·®å¼å½±åã', 'æ¯ä¸å½±å': 'å½±å夿å¯é ä½ä¸è¿ç»ï¼æ°æ¹æ¡åºè¡¥è¶³ä¸¤ä¸ªæåæ¶ç¹ä¹é´ç宿¶é£é©çªå£ã' } }, 37 { q: '䏿¦è¯å«å°ç©¿éï¼ä½ å¸æç³»ç»ä»å ¥å°ä»ä¹ç¨åº¦ï¼', why: 'âæç¤ºâä¸âèªå¨å¶å¨â对åºå®å ¨ä¸åçé£é©ççº§ãæ§å¶æ¶æå注åéªè¯å·¥ä½éã', options: ['åªåæç¤º', 'éå¶è½¬é', 'ç«å³èªå¨å¶å¨', 'å级ä»å ¥'], insight: { 'åªåæç¤º': 'ä¿çå»çæç»æ§å¶æï¼ä½å¿ é¡»ç¡®ä¿æç¤ºæ¸ æ°ä¸ä¸å¢å 认ç¥è´æ ã', 'ç«å³èªå¨å¶å¨': '主å¨å¶å¨æ¶çæé«ï¼ä¹éè¦éªè¯è¯¯è§¦åã失æå®å ¨åå°äº 20ms çéç¯ååºã', 'å级ä»å ¥': 'å级ä»å ¥è½å ¼é¡¾æ§å¶æä¸å®å ¨æ§ï¼å¯å æç¤ºï¼åé置信度åé«éå¶è½¬éã' } }, 38 { q: 'ä½ è®¤ä¸ºä¸´åºä¸å¯æ¥åç误æ¥è¦é¢çæ¯å¤å°ï¼', why: 'å®å ¨ç³»ç»è¿äºææä¼è¢«å»çå ³éï¼è¿ä¸ªçæ¡å³å®è¯å«éå¼ä»¥åçµæåº¦ä¸ç¹å¼åº¦çåèã', options: ['æ¯å°ä¸è¶ è¿ 1 次', 'æ¯ 10 å°ä¸è¶ è¿ 1 次', 'å ä¹ä¸è½è¯¯æ¥', 'å æ¶éæ°æ®'], insight: { 'æ¯å°ä¸è¶ è¿ 1 次': '坿¥åä¸å®è¯¯æ¥ï¼ç®æ³å¯ä¼å ä¿è¯çµæåº¦ï¼éåæ©æéªè¯ã', 'å ä¹ä¸è½è¯¯æ¥': 'éè¦æ´ä¸¥æ ¼çå¤ä¿¡å·äº¤å确认ï¼å¯è½çºç²å°éååºé度ã', 'å æ¶éæ°æ®': '建议å 以éä»å ¥è®°å½æ¨¡å¼å»ºç«ä¸´åºåºçº¿ï¼åå»ç»æ¥è¦éå¼ã' } } 39 ] 40 }, 41 { 42 match: ['康å¤', 'å¼å¸', 'è®ç»', 'å± å®¶', 'æ£è '], 43 summary: 'å± å®¶åº·å¤è®ç»ç¼ºå°å¨ä½è´¨é夿ä¸å³æ¶æå¯¼ï¼éè¦ç¨ä½é¨æ§äº¤äºæåæ£è ä¾ä»æ§åè®ç»æææ§ã', 44 tags: ['å± å®¶åº·å¤', 'å¨ä½è¯å«', '峿¶åé¦', 'æ£è ä¾ä»æ§'], 45 mappings: [ 46 ['âå¨ä½ä¸æ åâ', 'å¼å¸æµé/ååèåè¯ä¼°', '误差 ⤠±5%'],
47 ['âä¸ç¥éåå¾å¯¹ä¸å¯¹â', '宿¶å¯è§åæå¯¼', 'åé¦ â¤ 300ms'], 48 ['èå¹´æ£è 使ç¨', 'ä½è®¤ç¥è´è·äº¤äº', 'åä»»å¡çé¢'], 49 ['å± å®¶é¿æä½¿ç¨', '便æºä½åè设计', 'ç»èª ⥠7天'] 50 ], 51 constraints: [['ç¨æ·', 'èå¹´æ£è '], ['åºæ', 'éä¸ä¸å± å®¶ç¯å¢'], ['è¿æ¥', 'èç / 离线å¯ç¨'], ['æ¸ æ´', 'æ¥è§¦ä»¶å¯ææ´']], 52 tests: [['â', 'è®ç»å¨ä½è¯å«åç¡®ç'], ['人', 'èå¹´ç¨æ·å¯ç¨æ§æµè¯'], ['çµ', 'ç»èªä¸ä½çµéçç¥æµè¯']], 53 questions: [ 54 { q: 'è¿å¥è®ç»ä¸»è¦æå¡åªç±»æ£è ï¼', why: 'ä¸åç¾ç çè®ç»ç®æ ãå±é©ä¿¡å·åææå¨ä½æ åå¹¶ä¸ç¸åï¼å¿ é¡»å éå®ç®æ 人群ã', options: ['æ ¢é»èº', 'è¸è ¹é¨æ¯å', 'å®å', 'å ¶ä»äººç¾¤'], insight: { 'æ ¢é»èº': 'æ ¢é»èºæ£è ééç¹å ³æ³¨å¼æ°è®ç»ãè¡æ°§é£é©åé¿æä¾ä»æ§ã', 'è¸è ¹é¨æ¯å': 'æ¯åæ£è ç¼çåæ´»å¨èéæ³¢å¨å¤§ï¼è®ç»å¼ºåº¦éè¦æ¸è¿è°æ´ã', 'å®å': 'å®ååºæ¯éåºåæ¥å¸¸è®ç»ä¸æ¥æ§åä½ï¼é¿å é误æå¯¼ã' } }, 55 { q: 'ç®åæå¸¸è§çâ䏿 åâå ·ä½è¡¨ç°æ¯ä»ä¹ï¼', why: 'åªææå¨ä½é误ææå¯æ£æµäºä»¶ï¼è®¾å¤æè½æä¾æç¨åé¦ï¼è䏿¯ç¬¼ç»è¯åã', options: ['叿°è¿å¿«', '弿°ä¸å å', 'èå¾ä¸ç¨³å®', 'å§¿å¿é误'], insight: { '叿°è¿å¿«': 'åºä»¥æµéä¸åæçå峰弿µéè¯å«è¿å¿«å¸æ°ï¼å¹¶æä¾èå¥å¼å¯¼ã', '弿°ä¸å å': 'éè¦ç»å弿°æç»æ¶é´ä¸æ«ç«¯æµéå¤æå®æåº¦ã', 'å§¿å¿é误': 'ä» é æµéä¼ æå¨ä¸è¶³ï¼å¯è½éè¦å¢å å§¿æä¼ æææåå¤´è¾ å©ã' } }, 56 { q: 'æ£è é常æ¯å¨ä»ä¹æ¯ææ¡ä»¶ä¸ä½¿ç¨ï¼', why: 'ç¬ç«ä½¿ç¨è¿æ¯æä
56ººåå©ï¼ä¼å³å®äº¤äºå¤æåº¦ãå¼å¸¸å¤çåè¿ç¨ç §æ¤åè½ã', options: ['å®å ¨ç¬ç«', 'å®¶å±å¶å°åå©', '廿¤è¿ç¨æå¯¼', '线ä¸éä¸è®ç»'], insight: { 'å®å ¨ç¬ç«': 'çé¢å¿ é¡»æç®ï¼å¹¶é ç½®é²è¯¯æä½ãè¯é³æç¤ºåæ¸ æ°çå¼å¸¸éåºè·¯å¾ã', '廿¤è¿ç¨æå¯¼': 'åºä¿çå¯è§£éçè®ç»æ°æ®æè¦ï¼æ¯æå»æ¤å¿«é夿æ¯å¦éè¦è°æ´å¤æ¹ã', '线ä¸éä¸è®ç»': '坿¥åæ´ä¸ä¸çæä½ï¼ä½éè¦é«é¢æ¶æ¯ä¸å¤æ£è æ°æ®é离ã' } }, 57 { q: '夿è®ç»ææï¼ä¸´åºä¸æçéåªä¸ªç»æï¼', why: 'å¿ é¡»å ç¡®å®ä¸»è¦ä¸´åºç»ç¹ï¼åé¦ç®æ³æä¸ä¼åªä¼åâå宿¬¡æ°âçä½ä»·å¼ææ ã', options: ['å¨ä½å®æè´¨é', 'èºåè½æ¹å', 'çç¶åè½»', 'åæè®ç»å¤©æ°'], insight: { 'å¨ä½å®æè´¨é': 'å¯å°å®æ¶å¨ä½è´¨éä½ä¸ºä¸»è¦äº§åææ ï¼å¹¶å»ºç«å¯è§£éçé误åç±»ã', 'èºåè½æ¹å': 'éè¦è®¾è®¡æ´é¿å¨æçé访ä¸èºåè½å ³èéªè¯ã', 'åæè®ç»å¤©æ°': 'åºæä½è®¤ç¥è´è·åæ£åæ¿å±çº³å ¥æ ¸å¿è®¾è®¡è¦æ±ã' } } 58 ] 59 }, 60 { 61 match: ['è ¹è é', 'æé³', 'èå£', 'ææ»', 'é¹°çª', 'ç»ç»'], 62 summary: 'è ¹è éæé³é¿æ¶é´æä½å¯¼è´èå£ç²å³ï¼å¤¹ææ¹¿æ»ç»ç»æ¶ç¨³å®æ§ä¸è¶³ï¼åæ¶éè¦æ§å¶ç»ç»æä¼¤ã', 63 tags: ['è ¹è é', 'ææå¨æ¢°', '人ä½å·¥å¦', '鲿»å¤¹æ'], 64 mappings: [ 65 ['âèå£ç¼ / ç¨ä¹ äºç´¯â', 'æææ¥è§¦å强ä¼å', '< 30 kPa'], 66 ['â夹ç»ç»ææ»â', 'é³å¤´è¡¨é¢æ©æ¦å¢å¼º', 'μ > 0.50'], 67 ['âåé¹°çªä¸æ ·æåâ', '髿²çé³å£ + èªé', '夹æå 15â30 N'], 68 ['âä¸è½æä¼¤ç»ç»â', 'è½½è·åæ£ä¸åéå¶', 'å³°å¼å强 < 200 kPa'] 69 ], 70 constraints: [['å ¥è·¯', '5 mm Trocar'], ['å·¥ä½é¿åº¦', '330â360 mm'], ['è§é', 'äºç»´è ¹è éè§é'], ['æ¸ æ´çè', 'è髿¸©æä¸æ¬¡æ§']], 71 tests: [['æ', 'èå£åºæ¥è§¦å强æµè¯'], ['â', '湿润ç»ç»ææ»ç§»æµè¯'], ['ç¾', 'ç»ç»å伤ä¸åæ»é弿µè¯']], 72 questions: [ 73 { q: 'âææ»âæå¸¸åçå¨å¤¹æåªç±»ç»ç»æ¶ï¼', why: 'ç»ç»çè弱度ã嫿°´éå表é¢ç»æä¸åï¼é³å£çº¹çä¸å 许夹æå强ä¼éç¨å®å ¨ä¸åç设计边çã', options: ['èå / è 管', 'èèªç»ç»', '纤维ç»ç»', 'å¤ç§ç»ç»'], insight: { 'èå / è 管': 'ç®æ ç»ç»è弱䏿¹¿æ¶¦ï¼ä¸è½å纯å¢å 齿å°ï¼åºä¼å æ©å¤§æ¥è§¦é¢ç§¯å¹¶æé«æ¹¿ææ©æ¦ã', 'èèªç»ç»': 'èèªç»ç»ææ»è±ï¼éè¦ææ¶²çº¹ç䏿´ç¨³å®çå ç»å¤¹æã', '纤维ç»ç»': '纤维ç»ç»å 许æ´é«å±é¨è½½è·ï¼å¯è¯ä¼°ç»é½¿æé«æ²çå¬åç»æã', 'å¤ç§ç»ç»': 'å¤ç»ç»éé æå³çéè¦å¯æ§å¤¹æåæååºçº¹çï¼é¿å åä¸é³å£åæ°è¿åº¦æä¸ã' } }, 74 { q: 'ææ»é常åçå¨ä»ä¹æä½å¨ä½ä¸ï¼', why: 'éæçµæãéæ§å离åæè½¬æä½çè½½è·æ¹åä¸åï¼è¿å³å®é³å£éè¦ä¼åè½´ååãä¾§ååè¿æ¯ææè½åã', options: ['æç»çµæ', 'éæ§å离', 'æè½¬å¥ç¦»', 'è°æ´è§é'], insight: { 'æç»çµæ': 'åºéç¹æé«è½´åææ»ç§»æ§è½ï¼åæ¶éå¶æç»å¤¹æå¯¼è´ç缺è¡é£é©ã', 'éæ§å离': 'éè¦å ¼é¡¾é³å°ç²¾ç»æ§å¶åä¾§åæå¼å度ï¼ä¸è½åªè¿½æ±æå¤§å¤¹æåã', 'æè½¬å¥ç¦»': 'ææç¨³å®æ§æ¯å ³é®ï¼åºå ³æ³¨é³å£å ç»è§åº¦ä¸æææè½¬æ§å¶ã' } }, 75 { q: 'èå£ä¸éé常å¨è¿ç»æä½å¤ä¹ ååºç°ï¼', why: 'åºç°ç²å³çæ¶é´è½å¸®å©åºåç¬æ¶åçãéå¤å¨ä½è´æ åææå§¿æé®é¢ï¼å¹¶ç¡®å®äººä½å·¥å¦æµè¯æ¶é¿ã', options: ['10 åéå ', '10â30 åé', '30â60 åé', '1 å°æ¶ä»¥ä¸'], insight: { '10 åéå ': 'çæ¶é´å³ç¼çæç¤ºå±é¨å³°å¼åå¼ºæææå°ºå¯¸ææ¾ä¸å¹é ï¼åºä¼å éææ¥è§¦é¢ã', '10â30 åé': 'å¯è½æ¯å±é¨å强ä¸éå¤å¼åå ±å导è´ï¼éè¦åæ¥æµéåååèèç²å³ã', '30â60 åé': 'å±äºç´¯ç§¯æ§è´æ ï¼å»ºè®®çº³å ¥æç»ä»»å¡ç人ä½å·¥å¦å¯¹ç §æµè¯ã' } }, 76 { q: '为äºè·å¾æ´ç¨³ç夹æï¼ä½ æ¿ææ¥ååªç§åèï¼', why: '夹æç¨³å®ãç»ç»ä¿æ¤ãææè¡ç¨åææ¬ä¹é´åå¨å²çªï¼éè¦å»çæç¡®æä¸è½çºç²ç临åºä»·å¼ã', options: ['ä¼å ä¿æ¤ç»ç»', 'ä¼å 夹æç¨³å®', 'ä¼å æä½çå', '叿å¯è°è'], insight: { 'ä¼å ä¿æ¤ç»ç»': '设计åºéç¨åéå¶åè½½è·åæ£ï¼é¿å 以å°é齿形æ¢åç¨³å®æ§ã
76', 'ä¼å 夹æç¨³å®': '坿é«å ç»ä¸èªéè½åï¼ä½å¿ é¡»ç¨ç»ç»æä¼¤æµè¯ç¡®å®ä¸éã', 'ä¼å æä½çå': 'åºä¼åä¼ å¨æ¯åæ¡æå§¿æï¼åæ¶é¿å è¡ç¨è¿é¿å½±åç²¾ç»æ§å¶ã', '叿å¯è°è': 'å¯è°å¤¹æåæå¹é å¤ç»ç»åºæ¯ï¼ä½ç»æå¤æåº¦å误ç¨é£é©éè¦åæ¥è¯ä¼°ã' } } 77 ] 78 } 79]; 80 81const fallbackScenario = { 82 summary: 'ä¸´åºæä½ä¸å卿çãæç¨æ§æå®å ¨æ§æ¹é¢çæªæ»¡è¶³éæ±ï¼éè¦è¿ä¸æ¥éåå ³é®ä½¿ç¨åºæ¯åæ§è½ææ ã', 83 tags: ['ä¸´åºæä½', 'æªæ»¡è¶³éæ±', '人æºäº¤äº', 'é£é©æ§å¶'], 84 mappings: [ 85 ['æä½ä½éªä¸çæ³', '人ä½å·¥å¦ä¼å', 'å¾ åºæ¯æµé'], 86 ['ç°ææ¹æ¡æçä½', 'å ³é®æ¥éª¤æµç¨éæ', 'ç®æ æå ⥠20%'], 87 ['å卿½å¨é£é©', '失æå®å ¨è®¾è®¡', 'å䏿 éå®å ¨'], 88 ['临åºå馿¨¡ç³', 'å¯éåè¯ä»·ä½ç³»', '建ç«åºçº¿æ°æ®'] 89 ], 90 constraints: [['使ç¨è ', '临åºä¸ä¸äººå'], ['ç¯å¢', 'å»çæä½ç¯å¢'], ['æ¥è§¦å±æ§', 'å¾ ç¡®è®¤'], ['æ³è§åç±»', 'å¾ å¤å®']], 91 tests: [['人', 'å ³é®ä»»å¡å¯ç¨æ§æµè¯'], ['â', 'æ ¸å¿æ§è½åºåæµè¯'], ['ç¾', '忥é£é©ä¸å¤±ææµè¯']], 92 questions: [ 93 { q: 'è¿ä¸ªé®é¢ä¸»è¦åçå¨ä¸´åºæµç¨çåªä¸ªç¯èï¼', why: 'å éå®é®é¢åçæ¶ç¹ï¼æè½æé¤ä¸æ ¸å¿åºæ¯æ å ³çåè½ï¼é¿å æäº§ååæå¤§èå ¨ã', options: ['æ¯ååå¤', 'æä½è¿ç¨ä¸', 'æ¯åå¤ç', 'é¢å¤é访'], insight: {} }, 94 { q: 'é®é¢åççé¢ç大è´å¦ä½ï¼', why: 'åçé¢çååæä¸¥éåº¦å ±åå³å®è¿æ¯å¿ 须解å³çå®å ¨é®é¢ï¼è¿æ¯ä½éªä¼åé®é¢ã', options: ['å 乿¯æ¬¡', 'ç»å¸¸åç', 'å¶å°åç', 'ç½è§ä½ä¸¥é'], insight: { 'å 乿¯æ¬¡': 'è¿æ¯ç¨³å®å¤ç°çæµç¨çç¹ï¼éåéè¿å¯éåçå¯¹ç §ä»»å¡éªè¯æ¹è¿ææã', 'ç½è§ä½ä¸¥é': 'åºæä½é¢é«é£é©äºä»¶å¤çï¼ä¼å åæå¤±ææ¨¡å¼åé£é©æ§å¶ã' } }, 95 { q: 'ç®å临åºä¸é常å¦ä½ç»è¿æè¡¥æè¿ä¸ªé®é¢ï¼', why: 'ç°æè¡¥ææ¹å¼æç¤ºäºçæ£çåè½éæ±ï¼ä¹è½å¸®å©å¤ææ°äº§åæ¯å¦ä¼å¢å é¢å¤æ¥éª¤ã', options: ['ä¾é 个人ç»éª', 'å¢å ä¸å婿', 'æ¢ç¨å ¶ä»å¨æ¢°', 'ç®å没æåæ³'], insight: {} }, 96 { q: '妿åªè½ä¼å æ¹åä¸ä¸ªç»æï¼ä½ ä¼éæ©ä»ä¹ï¼', why: 'æç¡®ç¬¬ä¸ä¼å 级å¯ä»¥å»ºç«äº§åä¸»è¦æ§è½ææ ï¼åå°äºç¸å²çªçæ æéæ±ã', options: ['æ£è å®å ¨', 'æä½æç', 'å»çèé度', 'æ²»çææ'], insight: {} } 97 ] 98}; 99 100const projects = [ 101 { icon: 'O', name: 'åå«å¤©åå»ç卿¢° OPC è¯ç¹é¡¹ç®', id: 'OPC-2026-001', stage: 'OPCè¯ç¹', progress: 7, owner: 'å¾ æå®ä¸»ç人', update: 'è¯ç¹é¡¹ç®å·²å»ºç«', source: 'workflow' }, 102 { icon: 'â', name: 'é¹°çªè ¹è éæé³', id: 'MD-DEMO-0710', stage: 'éæ±éªè¯', progress: 18, owner: 'æ¼ç¤ºè§è²', update: 'æµç¨æ¼ç¤ºæ°æ®', source: 'demo' }, 103 { icon: 'â', name: 'æºè½éª¨é»å®å ¨ç³»ç»', id: 'MD-DEMO-0628', stage: 'æ¦å¿µè®¾è®¡', progress: 42, owner: 'æ¼ç¤ºè§è²', update: 'æµç¨æ¼ç¤ºæ°æ®', source: 'demo' }, 104 { icon: 'â', name: 'å± å®¶å¼å¸è®ç»ä»ª', id: 'MD-DEMO-0519', stage: 'èæéªè¯', progress: 67, owner: 'æ¼ç¤ºè§è²', update: 'æµç¨æ¼ç¤ºæ°æ®', source: 'demo' }, 105 { icon: 'â', name: 'å¾®åå»åå¨åæ§æ¨¡å', id: 'MD-DEMO-0412', stage: '注ååå ¥', progress: 84, owner: 'æ¼ç¤ºè§è²', update: 'æµç¨æ¼ç¤ºæ°æ®', source: 'demo' } 106]; 107let selectedCommandProjectId = projects[0].id; 108let opcProjectState = null; 109let selectedOpcStageId = 'foundation'; 110 111const services = [ 112 { n: '01', stage: 'concept', stageName: 'æ¦å¿µéªè¯', title: 'AI 临åºéæ±æ´å¯æ¥å', desc: 'åºäºä¸´åºæè¿°è¿è¡å¨æè¿½é®ä¸å·¥ç¨æ å°ï¼çå®ä¸çæ°æ®éå¦è¡æ¥å ¥å¹¶æ ¸éªã', time: 'æé¡¹ç®ç¡®è®¤', price: 'å¾ æ ¸ä»·', status:'AIå¯ç¨' }, 113 { n: '02', stage: 'concept', stageName: 'æ¦å¿µéªè¯', title: 'ä¸å©å´æ ä¸èµéå°å¾', desc: 'çå®ä¸å©æ°æ®åºæ¥å ¥åï¼æè½æ 注æå©è¦æ±é£é©ãæ³å¾ç¶æä¸åæ°ç©ºé´ã', time: 'è½åå¾ æ¥å ¥', price: 'æä¸æ¥ä»·', status:'å¾ æ¥å ¥' }, 114 { n: '03', stage: 'concept', stageName: 'æ¦å¿µéªè¯', title: 'FlashPOC æ¦å¿µå·¥ä½å', desc: 'å½åå±ç¤ºæ¦å¿µæ¹æ³æ ·ä¾ï¼æ£å¼å·¥ç¨æ¹æ¡éå®æè®¾è®¡è¾å ¥ã计ç®ä¸å·¥ç¨å¸å®¡æ ¸ã', time: 'æ¹æ³æ¼ç¤º', price: 'æä¸æ¥ä»·', status:'æ¼ç¤º' }, 115 { n: '04', stage: 'transform', stageName: 'ææè½¬å', title: 'åè½ååè¿ä»£ç®¡ç', desc: '管çè®¾è®¡âæµè¯éç¯ãå¾çº¸ãBOMä¸å®¡æ ¸è®°å½ï¼äº¤ä»ç»æé¡»çå®ä¸ä¼ å¹¶éªæ¶ã', time: 'æ
115项ç®ç¡®è®¤', price: 'å¾ æ ¸ä»·', status:'工使µ' }, 116 { n: '05', stage: 'transform', stageName: 'ææè½¬å', title: 'RegAI åè§èµææ¡æ¶', desc: 'æ°ååªçä¸çå®éªè¯å¼ææ¥å ¥åï¼ä» è§åDHFãé£é©ç®¡çåéªè¯èµæç»æã', time: 'è½åå¾ æ¥å ¥', price: 'æä¸æ¥ä»·', status:'å¾ æ¥å ¥' }, 117 { n: '06', stage: 'market', stageName: 'åä¸å', title: 'AI åµå ¥å¼è½åè§å', desc: 'å´ç»æ£æµãè¯å«åååé¦å»ºç«æ°æ®ãç®æ³ä¸éªè¯ä»»å¡ï¼ä¸é¢è®¾é¡¹ç®éé 度ã', time: 'æé¡¹ç®ç¡®è®¤', price: 'å¾ æ ¸ä»·', status:'è§å' }, 118 { n: '07', stage: 'market', stageName: 'åä¸å', title: 'KOL ç ç©¶ä¸åä½è§å', desc: 'çå®ä¸å®¶æ°æ®æ¥å ¥å¹¶è·ææåï¼æå¼å±ç»åæ ¸éªååä½ä¼å 级åæã', time: 'è½åå¾ æ¥å ¥', price: 'æä¸æ¥ä»·', status:'å¾ æ¥å ¥' } 119]; 120 121const conceptLibraries = { 122 laparoscopic: [ 123 { type: 'æ¹æ¡ A · è¿åå¼', tone: 'evolution', symbol: 'A', title: 'ææ§å ç»é³å£', desc: 'ä¿ççæçæé³å½¢æï¼ä»¥ææ§ååºçº¹çæ©å¤§æ¥è§¦é¢ï¼é使¹¿æ¶¦ç»ç»æ»ç§»ã', tags: ['ä½å¦ä¹ ææ¬', 'ç»ç»å好'], score: 91, rationale: 'ä»¥ç°æè ¹è éæé³ä¸ºåºçº¿ï¼ä¼å è§£å³æ¹¿æ¶¦èå¼±ç»ç»çæ»ç§»é®é¢ãéè¿é³å£è¿ç«¯æé¡ºå±ãè¿ç«¯ææ¶²çº¹çååæ§éååï¼å¨ä¸æ¹åå»çä¸»è¦æä½ä¹ æ¯çåæä¸æé«ç¨³å®æ§ã', params: ['é³å£æ²ç 18â22°', 'æ¹¿ææ©æ¦ç³»æ° > 0.50', 'å³°å¼å强 < 200 kPa', '5 mm Trocar'] }, 124 { type: 'æ¹æ¡ B · 仿çå¼', tone: 'bio', symbol: 'B', title: 'é¹°çªååºå¬å', desc: 'åé´ç禽趾é¨å ç»ç»æï¼ç¨ä¸æ®µå¼æ²çå½¢æç¨³å®ææåè½½è·åæ£ã', tags: ['ææ»æä¼', '仿çå ç»'], score: 94, rationale: 'å°å»çâåé¹°çªä¸æ ·æåâçæ¯å»è½¬è¯ä¸ºä¸æ®µè¿ç»æ²çï¼èéç®åå¢å å°é½¿ãå¤ä¾§åºåæä¾å ç»ï¼ä¸å¤®å¾®çº¹çææ¶²ï¼åéå¶ç»ææ§å¶ç»ç»å³°å¼å强ã', params: ['䏿®µæ²ç 12°/20°/28°', '夹æå 15â25 N', 'èªééæ¾å < 8 N', 'æææå 35%'] }, 125 { type: 'æ¹æ¡ C · æ¿è¿å¼', tone: 'radical', symbol: 'C', title: 'å¯è°æé¡ºæ¶æ ¼é³å£', desc: 'å©ç¨å¾®åæé¡ºæ¶æ ¼éç»ç»å½¢æèªéåºï¼å ¼é¡¾å¤ç»ç»å¤¹æä¸å®æ¶åéå¶ã', tags: ['å¤ç»ç»éé ', 'ç»æåæ°'], score: 86, rationale: 'éè¿ä¸ä½åæé¡ºæ¶æ ¼äº§ç被å¨èªéåºæ¥è§¦ï¼å¯è¦çèåãèèªå纤维ç»ç»ãåæ°åº¦æé«ï¼ä½å¶é éªè¯ãæ¸ æ´åèä¹ æ§é£é©ä¹æ´é«ã', params: ['æ¶æ ¼å½¢å 0.4â1.2 mm', '两档夹æå', '循ç¯å¯¿å½ > 10,000 次', 'ä¼å 䏿¬¡æ§æ¹æ¡'] } 126 ], 127 orthopedic: [ 128 { type: 'æ¹æ¡ A · è¿åå¼', tone: 'evolution', symbol: 'A', title: 'åç©æç¥éª¨é»æ¨¡å', desc: 'å¨ç°æå¨åææä¸éæè½´ååä¸æç©æ£æµï¼ä¸æ¹åå»çä¸»è¦æä½ä¹ æ¯ã', tags: ['使¹é ææ¬', '宿¶æç¥'], score: 90, rationale: 'éè¿è½´åå䏿ç©çç¬æååè¯å«ç®è´¨éª¨ç©¿éï¼å¨ä¿çç°æé»å¤´åæææä½æ¹å¼çåæ¶æä¾å级æç¤ºã', params: ['éæ ·ç ⥠1 kHz', 'è¯å«å»¶è¿ < 30 ms', 'è¯¯æ¥ < 1/å°', 'å¯çèä¼ ææ¥å£'] }, 129 { type: 'æ¹æ¡ B · 仿çå¼', tone: 'bio', symbol: 'B', title: '忍é¸åå²å»é»è¿', desc: 'åé´åæ¨é¸çé´æå²å»æºå¶ï¼ä»¥èå²é»è¿éä½ç©¿éåçæ¯æ§åå²ã', tags: ['ä½åå²', 'ç»ç»ä¿æ¤'], score: 93, rationale: 'éè¿é«é¢çè¡ç¨è岿¿ä»£è¿ç»æ¨è¿ï¼ç©¿éåçåæ®ä½å¨è½æ´ä½ï¼æ´éååæ¹åå¨ç¥ç»æè½¯ç»ç»çé«é£é©åºæ¯ã', params: ['èå² 80â120 Hz', '忬¡è¡ç¨ < 0.4 mm', 'å¶å¨ååº < 20 ms', 'åå²éä½ > 45%'] }, 130 { type: 'æ¹æ¡ C · æ¿è¿å¼', tone: 'radical', symbol: 'C', title: '颿µå¶å¨æºè½éª¨é»', desc: 'èååã声å¦ä¸çµæºçµæµä¿¡å·ï¼å¨ç©¿éå颿µå©ä½ç®è´¨éª¨å度并主å¨åéã', tags: ['主å¨å®å ¨', '夿¨¡æç®æ³'], score: 88, rationale: '夿ºä¿¡å·äº¤å确认å¯éä½è¯¯æ¥ï¼å¹¶å¨çæ£ç©¿éåè¿å ¥ééåºé´ï¼æ§è½æ½åæé«ï¼åæ¶éè¦æ´å®æ´çä¸´åºæ°æ®å软件éªè¯ã', params: ['䏿¨¡æèå', '颿µçªå£ 50â100 ms', 'å级éé', 'å䏿 éå®å ¨'] } 131 ], 132 rehab: [ 133 { type: 'æ¹æ¡ A · è¿åå¼', tone: 'evolution', symbol: 'A', title: 'æºè½å¼å¸è®ç»å¨', desc: 'å¨ä¼ ç»è®ç»å¨ä¸å å ¥æµéãååæç¥ä¸ç¯ç¯åé¦ï¼éä½å¦ä¹ 鍿§ã', tags: ['æäºè½å°', 'ä½è®¤ç¥è´è·'], score: 92, rationale: 'æ²¿ç¨æ£è çæçå¹å¸è®ç»å½¢æï¼ä»¥æµéåååèå夿å¨ä½å®æåº¦ï¼å¹¶éè¿åä¸ç¯ç¯ç»äºå³æ¶èå¾æç¤ºã', params: ['æµé误差 ±5%', 'åé¦å»¶è¿ < 300 ms', 'å鮿ä½', 'ç»èª ⥠7 天'] }, 134 { type: 'æ¹æ¡ B · 仿çå¼', tone: 'bio', symbol: 'B', title: 'æ½®æ±èå¾å¼å¯¼å¨', desc: 'ç¨ç±»ä¼¼æ½®æ±èµ·ä¼ç触è§åå æå¸®å©æ£è å½¢æç¨³å®ãç¼æ ¢çå¼å¸èå¾ã', tags: ['èå¾å¼å¯¼', 'æ å±äº¤äº'], score: 90, rationale: 'å°æ½è±¡çå¼å¸æ²çº¿è½¬å为å¯è·éç触è§èå¾ï¼åå°èå¹´æ£è 读åå¾è¡¨ç认ç¥è´æ ï¼éåç¬ç«å±
134å®¶è®ç»ã', params: ['èå¾ 4â12 次/å', '触è§ä¸çº§å¼ºåº¦', '离线å¯ç¨', 'å¼å¸¸èªå¨æå'] }, 135 { type: 'æ¹æ¡ C · æ¿è¿å¼', tone: 'radical', symbol: 'C', title: '夿¨¡æåº·å¤æç»', desc: 'ç»åå¼å¸ä¼ æåå§¿æè¯å«ï¼å®æ¶å¤æå¨ä½ãä½ä½ä¸è®ç»è´¨éã', tags: ['å¨ä½è¯å«', 'è¿ç¨å¤æ¹'], score: 87, rationale: 'å¨å¼å¸åæ°ä¹å¤å å ¥ä¸èº«å§¿æï¼å½¢ææ´å®æ´çå¨ä½è´¨éè¯åï¼å¹¶æ¯æå»æ¤è¿ç¨æ¥çå¯è§£éçé误åç±»ã', params: ['å§¿æè¯å« > 90%', 'é误å为 5 ç±»', 'è¿ç¨å¤æ¹åæ¥', 'éç§æ¬å°å¤ç'] } 136 ] 137}; 138 139const $ = (selector, scope = document) => scope.querySelector(selector); 140const $$ = (selector, scope = document) => [...scope.querySelectorAll(selector)]; 141const BP_LEGACY_BASE = 'http://127.0.0.1:8000'; 142const BP_API_BASE = window.location.protocol === 'file:' ? BP_LEGACY_BASE : `${window.location.origin}/api/bp`; 143let bpSelectedFile = null; 144let bpLoaderTimer = null; 145let currentComparableCandidates = []; 146let currentRegistrationEstimate = null; 147let currentBpDimensions = []; 148let investmentProjects = []; 149let currentInvestmentProject = null; 150let currentInvestmentAnalysis = null; 151let currentInvestmentReportId = null; 152let investmentAssistantMessages = []; 153let investmentAssistantBusy = false; 154let investmentProjectsLoaded = false; 155let investmentWorkflowStep = 1; 156let policyMaterialFiles = []; 157let policyMatchMaterialFiles = []; 158 159const POLICY_MATERIAL_ROLES = ['ä¸å¹´åº¦ç³æ¥ä¹¦', 'è¾ å¯¼ä¼çºªè¦ï¼è¯å导åï¼', 'èªè¯æ¥å', '审计æè´¢å¡ææ', 'ä¼ä¸è¯æææ', 'å ¶ä»ææ']; 160 161function guessPolicyMaterialRole(filename) { 162 const name = String(filename || ''); 163 if (/纪è¦|è¾ å¯¼|å¹è®|ä¼è®®/.test(name)) return 'è¾ å¯¼ä¼çºªè¦ï¼è¯å导åï¼'; 164 if (/èªè¯|è¯ä»·æ¥å/.test(name)) return 'èªè¯æ¥å'; 165 if (/ç³æ¥ä¹¦|ç³è¯·ä¹¦|项ç®ä¹¦/.test(name)) return 'ä¸å¹´åº¦ç³æ¥ä¹¦'; 166 if (/审计|è´¢å¡|æ¥è¡¨|å¹´æ¥/.test(name)) return '审计æè´¢å¡ææ'; 167 return 'ä¼ä¸è¯æææ'; 168} 169 170function renderPolicyMatchMaterials() { 171 const list = $('#policyMatchMaterialsList'); 172 if (!list) return; 173 list.innerHTML = policyMatchMaterialFiles.map((entry, index) => `<div class="policy-match-material-item"><span class="material-file">${escapeHtml(entry.file.name)}</span><select data-match-material-role="${index}">${POLICY_MATERIAL_ROLES.map(role => `<option ${role === entry.role ? 'selected' : ''}>${role}</option>`).join('')}</select><button type="button" class="policy-material-remove" data-match-material-remove="${index}" title="ç§»é¤">Ã</button></div>`).join('') || '<p class="policy-match-material-empty">å°æªæ·»å è¡¥å ææï¼æ²¡æä¹å¯ä»¥ç´æ¥å¹é </p>'; 174} 175 176function addPolicyMatchMaterials(files) {
177 [...(files || [])].forEach(file => { 178 policyMatchMaterialFiles.push({ file, role: guessPolicyMaterialRole(file.name) }); 179 }); 180 renderPolicyMatchMaterials(); 181} 182let policyGuideFiles = []; 183let policyTemplateFile = null; 184let policyAnalysisResult = null; 185let policyEnterprises = []; 186let currentPolicyEnterprise = null; 187let policyEnterprisesLoaded = false; 188let policyCatalog = []; 189let currentPolicyCatalog = null; 190let policyCatalogLoaded = false; 191let policyWorkflowStep = 1; 192let currentPolicyApplicationId = null; 193let selectedPolicyCandidate = null; 194let policyCatalogMode = 'library'; 195let policyOpportunityScan = null; 196let policyCatalogQuery = ''; 197let policyCatalogLevel = 'å ¨é¨çº§å«'; 198let policyCatalogShowMismatch = false; 199let professionalProjects = []; 200let currentProfessionalProject = null; 201let currentProfessionalRun = null; 202let professionalBusy = false; 203let baseEnterpriseCompanies = []; 204let baseEnterpriseStats = null; 205let currentBaseEnterprise = null; 206let baseEnterpriseLoaded = false; 207let policyCatalogValidityFilterValue = 'å ¨é¨ç¶æ'; 208 209function escapeHtml(value) { 210 return String(value ?? '').replace(/[&<>"]/g, char => ({ '&':'&', '<':'<', '>':'>', '"':'"' })[char]); 211} 212 213async function checkBpEngine() { 214 const dot = $('#bpEngineDot'); 215 try { 216 const response = await fetch(`${BP_API_BASE}/api/health`, { signal: AbortSignal.timeout(3500) }); 217 if (!response.ok) throw new Error('offline'); 218 dot.className = 'online'; 219 $('#bpEngineStatus').textContent = 'BP 弿å¨çº¿ · æ¬æºå ç»å½'; 220 $('#connectBpEngineButton').textContent = 'æ¬æºå ç»å½'; 221 $('#connectBpEngineButton').disabled = true; 222 return true; 223 } catch { 224 dot.className = 'error'; 225 $('#bpEngineStatus').textContent = 'BP ä¸ä¸åæå¼æå°æªå¯å¨'; 226 $('#connectBpEngineButton').textContent = 'æå¡æªå¯å¨'; 227 return false; 228 } 229} 230 231function showBpLogin() { 232 $('#bpLoginPanel').classList.remove('hidden'); 233 $('#bpUsername').focus(); 234} 235 236async function loginBpEngine() { 237 const username = $('#bpUsername').value.trim(); 238 const password = $('#bpPassword').value; 239 if (!username || !password) { $('#bpLoginMessage').textContent = '请è¾å ¥å é¨è´¦å·åå¯ç ã'; return; } 240 $('#bpLoginButton').disabled = true; 241 $('#bpLoginMessage').textContent = 'æ£å¨è¿æ¥æ¬æº BP 弿â¦'; 242 try { 243 const response = await fetch(`${BP_API_BASE}/api/auth/login`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({username,password}) }); 244 const payload = await response.json(); 245 if (!response.ok) throw new Error(payload.detail || 'ç»å½å¤±è´¥'); 246 localStorage.setItem('token', payload.data.token); 247 localStorage.setItem('user', JSON.stringify(payload.data.user)); 248 $('#bpLoginPanel').classList.add('hidden'); 249 $('#bpPassword').value = ''; 250 $('#bpEngineDot').className = 'online'; 251 $('#bpEngineStatus').textContent = `BP å¼æå·²è¿æ¥ · ${payload.data.user.username}`; 252 $('#connectBpEngineButton').textContent = 'å·²è¿æ¥'; 253 showToast('BPä¸ä¸åæå¼æå·²è¿æ¥'); 254 } catch (error) { 255 if (error.message.includes('Failed to fetch')) { 256 const online = await checkBpEngine(); 257 $('#bpLoginMessage').textContent = online ? 'BP æå¡ç°å·²å¯å¨ï¼è¯·å次ç¹å»âç»å½å¹¶è¿æ¥âã' : 'æ æ³è¿æ¥æ¬æºæå¡ï¼è¯·éæ°å廿¡é¢çå¯å¨å¿«æ·æ¹å¼ã'; 258 } else { 259 $('#bpLoginMessage').textContent = error.message; 260 } 261 } finally { 262 $('#bpLoginButton').disabled = false; 263 } 264} 265 266async function registerBpEngine() { 267 const username = $('#bpUsername').value.trim(); 268 const password = $('#bpPassword').value; 269 const message = $('#bpLoginMessage'); 270 if (username.length < 3 || password.length < 6) { 271 message.textContent = '请设置è³å°3ä½ç¨æ·ååè³å°6ä½å¯ç ã'; 272 message.className = 'error'; 273 showToast('ç¨æ·åè³å°3ä½ï¼å¯ç è³å°6ä½'); 274 return; 275 } 276 $('#bpRegisterButton').disabled = true; 277 $('#bpRegisterButton').textContent = 'æ£å¨å建â¦'; 278 message.className = 'working'; 279 message.textContent = 'æ£å¨åå»ºæ¬æºå é¨è´¦å·ï¼è¯·ç¨åâ¦'; 280 try { 281 const response = await fetch(`${BP_API_BASE}/api/auth/register`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({username,password,email:''}) }); 282 const payload = await response.json(); 283 if (!response.ok) throw new Error(payload.detail || 'å建账å·å¤±è´¥'); 284 localStorage.setItem('token', payload.data.token); 285 localStorage.setItem('user', JSON.stringify(payload.data.user)); 286 $('#bpLoginPanel').classList.add('hidden'); 287 $('#bpPassword').value = ''; 288 $('#bpEngineDot').className = 'online'; 289 $('#bpEngineStatus').textContent = `BP å¼æå·²è¿æ¥ · ${payload.data.user.username}`; 290 $('#connectBpEngineButton').textContent = 'å·²è¿æ¥'; 291 showToast('å é¨è´¦å·å建æåï¼BPå¼æå·²è¿æ¥'); 292 } catch (error) { 293 const friendlyMessage = error.message.includes('Failed to fetch') 294 ? 'BP æå¡ä»å¨å¯å¨ï¼è¯·ç¨çå ç§åéè¯ã' 295 : error.message.includes('ç¨æ·åå·²åå¨') 296 ? `â${username}âå·²ç»åå¨ï¼è¯·æ¢ä¸ä¸ªæ°ç¨æ·åï¼ä¾å¦ zhang2026ï¼ï¼æè 使ç¨åå¯ç ç»å½ã` 297 : error.message; 298 message.textContent = friendlyMessage; 299 message.className = 'error'; 300 showToast(friendlyMessage); 301 } finally { 302 $('#bpRegisterButton').disabled = false;
303 $('#bpRegisterButton').textContent = '馿¬¡ä½¿ç¨ï¼å建账å·'; 304 } 305} 306 307// ============ å ¨å±ç»å½æ¦æª + å½åç¨æ· ============ 308let currentPlatformUser = null; 309let authGateMode = 'login'; 310 311function getStoredUser() { 312 try { return JSON.parse(localStorage.getItem('user') || 'null'); } catch { return null; } 313} 314 315function applyUserChrome(user) { 316 currentPlatformUser = user; 317 const isAdmin = !!user && user.role === 'admin'; 318 const adminNav = $('#adminNav'); 319 if (adminNav) adminNav.hidden = !isAdmin; 320 const profile = $('.profile-card'); 321 if (profile && user) { 322 const displayName = user.nickname || user.username || 'å é¨å·¥ä½å°'; 323 const avatar = profile.querySelector('.avatar'); 324 if (avatar) avatar.textContent = displayName.slice(0, 1); 325 const strong = profile.querySelector('strong'); 326 if (strong) strong.textContent = displayName; 327 } 328} 329 330function showAuthGate() { 331 $('#authGate')?.classList.remove('hidden'); 332 setTimeout(() => $('#authGateUsername')?.focus(), 60); 333} 334 335function hideAuthGate() { 336 $('#authGate')?.classList.add('hidden'); 337} 338 339function setAuthGateMode(mode) { 340 authGateMode = mode; 341 $('#authTabLogin')?.classList.toggle('active', mode === 'login'); 342 $('#authTabRegister')?.classList.toggle('active', mode === 'register'); 343 const submit = $('#authGateSubmit'); 344 if (submit) submit.textContent = mode === 'login' ? 'ç»å½å¹¶è¿å ¥ç³»ç»' : '注åå¹¶è¿å ¥ç³»ç»'; 345 const msg = $('#authGateMessage'); 346 if (msg) { msg.textContent = ''; msg.className = ''; } 347} 348 349async function submitAuthGate() { 350 const username = $('#authGateUsername').value.trim(); 351 const password = $('#authGatePassword').value; 352 const msg = $('#authGateMessage'); 353 const submit = $('#authGateSubmit'); 354 if (username.length < 3 || password.length < 6) { 355 msg.textContent = '请è¾å ¥è³å°3ä½ç¨æ·ååè³å°6ä½å¯ç ã'; 356 msg.className = 'error'; 357 return; 358 } 359 submit.disabled = true; 360 msg.className = 'working'; 361 msg.textContent = authGateMode === 'login' ? 'æ£å¨ç»å½â¦' : 'æ£å¨å建账å·â¦'; 362 const path = authGateMode === 'login' ? '/api/auth/login' : '/api/auth/register'; 363 try { 364 const response = await fetch(`${BP_API_BASE}${path}`, { 365 method: 'POST', 366 headers: { 'Content-Type': 'application/json' }, 367 body: JSON.stringify({ username, password, email: '' }), 368 }); 369 const payload = await response.json(); 370 if (!response.ok) throw new Error(payload.detail || (authGateMode === 'login' ? 'ç»å½å¤±è´¥' : '注å失败')); 371 localStorage.setItem('token', payload.data.token); 372 localStorage.setItem('user', JSON.stringify(payload.data.user)); 373 hideAuthGate(); 374 showToast(`${payload.data.user.username}ï¼æ¬¢è¿åæ¥`); 375 // æ°æ®æç¨æ·é离ï¼ç»å½åæ´é¡µå·æ°ä»¥ç¡®ä¿åè§å¾æå°å½åç¨æ·çæ°æ® 376 setTimeout(() => window.location.reload(), 350); 377 } catch (error) { 378 msg.textContent = error.message.includes('Failed to fetch') ? 'æ æ³è¿æ¥æ¬æºæå¡ï¼è¯·ç¡®è®¤ç³»ç»å·²å¯å¨ã' : error.message; 379 msg.className = 'error'; 380 } finally { 381 submit.disabled = false; 382 } 383} 384 385async function initPlatformAuth() { 386 $('#authTabLogin')?.addEventListener('click', () => setAuthGateMode('login')); 387 $('#authTabRegister')?.addEventListener('click', () => setAuthGateMode('register')); 388 $('#authGateSubmit')?.addEventListener('click', submitAuthGate); 389 $('#authGatePassword')?.addEventListener('keydown', event => { if (event.key === 'Enter') submitAuthGate(); }); 390 391 const token = localStorage.getItem('token'); 392 if (!token) { showAuthGate(); return; } 393 try { 394 const response = await fetch(`${BP_API_BASE}/api/auth/me`, { 395 headers: { Authorization: `Bearer ${token}` }, 396 signal: AbortSignal.timeout(5000), 397 }); 398 if (!response.ok) throw new Error('expired'); 399 const payload = await response.json(); 400 applyUserChrome(payload.data); 401 } catch { 402 localStorage.removeItem('token'); 403 localStorage.removeItem('user'); 404 showAuthGate(); 405 } 406} 407 408// ============ 管çåæ°æ®çæ¿ ============ 409const adminDetailCache = {}; 410 411function formatAdminTime(iso) { 412 if (!iso) return 'â'; 413 return String(iso).slice(0, 16).replace('T', ' '); 414} 415 416async function loadAdminDashboard() { 417 const forbidden = $('#adminForbidden'); 418 const content = $('#adminContent'); 419 if (!forbidden || !content) return; 420 const user = currentPlatformUser || getStoredUser(); 421 if (!user || user.role !== 'admin') { 422 forbidden.classList.remove('hidden'); 423 content.classList.add('hidden'); 424 return; 425 } 426 forbidden.classList.add('hidden'); 427 content.classList.remove('hidden'); 428 try { 429 const [overview, users] = await Promise.all([ 430 investmentApiRequest('/api/admin/overview'), 431 investmentApiRequest('/api/admin/users'), 432 ]); 433 renderAdminOverview(overview); 434 renderAdminUsers(users); 435 } catch (error) { 436 if (/ä» ç®¡çå|æ æ|403/.test(error.message)) { 437 forbidden.classList.remove('hidden'); 438 content.classList.add('hidden'); 439 } else { 440 showToast(`çæ¿æ°æ®å 载失败ï¼${error.message}`); 441 } 442 } 443} 444 445function renderAdminOverview(data) { 446 $('#adminStatUsers').textContent = data.total_users ?? 'â'; 447 $('#adminStatUsersSub').textContent = `è¿7天æ°å¢ ${data.recent7d_new_users ?? 0} 人`; 448 $('#adminStatProjects').textContent = data.total_projects ?? 'â'; 449 $('#adminStatReportsSub').textContent = `å岿¥å ${data.total_reports ?? 0} 份`; 450 $('#adminStatAvg').textContent = data.avg_score != null ? data.avg_score : 'â'; 451 $('#adminStatActive').textContent = `${data.recent7d_new_users ?? 0}
451人 / ${data.recent7d_new_analyses ?? 0}项`; 452 $('#adminStatActiveSub').textContent = 'æ°ç¨æ· / æ°å¢åæ'; 453 454 const list = $('#adminActivityList'); 455 const activities = data.recent_activities || []; 456 list.innerHTML = activities.length ? activities.map(item => ` 457 <div class="admin-activity-item"> 458 <span class="act-user">${escapeHtml(item.user_nickname || 'æªç¥ç¨æ·')}</span> 459 <span class="act-project">${escapeHtml(item.project_name || 'æªå½å项ç®')}</span> 460 <span class="admin-score-chip${item.score == null ? ' none' : ''}">${item.score != null ? escapeHtml(item.score) : 'æªè¯å'}</span> 461 <span class="act-time">${formatAdminTime(item.updated_at)}</span> 462 </div>`).join('') : '<p class="admin-loading">ææ åæå¨æ</p>'; 463} 464 465function renderAdminUsers(users) { 466 $('#adminUserCount').textContent = `${users.length} 个账å·`; 467 const table = $('#adminUserTable'); 468 if (!users.length) { table.innerHTML = '<p class="admin-loading">ææ ç¨æ·</p>'; return; } 469 table.innerHTML = `<table> 470 <thead><tr><th>ç¨æ·</th><th>è§è²</th><th>åææ°</th><th>æè¿æ´»è·</th><th>æ³¨åæ¶é´</th></tr></thead> 471 <tbody>${users.map(u => { 472 const name = u.nickname || u.username || 'æªå½å'; 473 const avatar = u.avatar_url 474 ? `<span class="admin-avatar"><img src="${escapeHtml(u.avatar_url)}" alt=""></span>` 475 : `<span class="admin-avatar">${escapeHtml(name.slice(0, 1))}</span>`; 476 return `<tr class="admin-user-row" data-admin-user-id="${escapeHtml(u.user_id)}"> 477 <td><span class="admin-user-cell">${avatar}<span><strong>${escapeHtml(name)}</strong><small>${escapeHtml(u.auth_type === 'wechat' ? '微信å°ç¨åº' : u.username)}</small></span></span></td> 478 <td><span class="admin-role-badge ${u.role === 'admin' ? 'admin' : 'user'}">${u.role === 'admin' ? '管çå' : 'ç¨æ·'}</span></td> 479 <td>${u.analysis_count ?? 0}</td> 480 <td>
480${u.active_recent7d ? `<span class="admin-active-dot">${formatAdminTime(u.last_active)}</span>` : formatAdminTime(u.last_active)}</td> 481 <td>${formatAdminTime(u.created_at)}</td> 482 </tr><tr class="admin-detail-row hidden" data-admin-detail-for="${escapeHtml(u.user_id)}"><td colspan="5"><div class="admin-detail-box"><p class="admin-loading">æ£å¨è¯»åæç»â¦</p></div></td></tr>`; 483 }).join('')}</tbody> 484 </table>`; 485 $$('.admin-user-row', table).forEach(row => { 486 row.addEventListener('click', () => toggleAdminUserDetail(row.dataset.adminUserId, row)); 487 }); 488} 489 490async function toggleAdminUserDetail(userId, row) { 491 const detailRow = $(`[data-admin-detail-for="${CSS.escape(userId)}"]`); 492 if (!detailRow) return; 493 const willOpen = detailRow.classList.contains('hidden'); 494 $$('.admin-detail-row').forEach(item => item.classList.add('hidden')); 495 $$('.admin-user-row').forEach(item => item.classList.remove('expanded')); 496 if (!willOpen) return; 497 detailRow.classList.remove('hidden'); 498 row.classList.add('expanded'); 499 const box = $('.admin-detail-box', detailRow); 500 try { 501 if (!adminDetailCache[userId]) { 502 adminDetailCache[userId] = await investmentApiRequest(`/api/admin/users/${encodeURIComponent(userId)}/detail`); 503 } 504 box.innerHTML = renderAdminUserDetail(adminDetailCache[userId]); 505 } catch (error) { 506 box.innerHTML = `<p class="admin-detail-empty">æç»å 载失败ï¼${escapeHtml(error.message)}</p>`; 507 } 508} 509 510function renderAdminUserDetail(detail) { 511 const projects = detail.projects || []; 512 const reports = detail.reports || []; 513 const projectBlock = projects.length ? `<table> 514 <thead><tr><th>项ç®å</th><th>è¯å</th><th>ç»è®º</th><th>å建æ¶é´</th></tr></thead> 515 <tbody>${projects.map(p => `<tr> 516 <td>${escapeHtml(p.name || 'æªå½å项ç®')}</td> 517 <td><span class="admin-score-chip${p.score == null ? ' none' : ''}">${p.score != null ? escapeHtml(p.score) : 'â'}</span></td> 518 <td>${escapeHtml(p.conclusion || p.status || 'â')}</td> 519 <td>${formatAdminTime(p.created_at)}</td> 520 </tr>`).join('')}</tbody> 521 </table>` : '<p class="admin-detail-empty">è¯¥ç¨æ·ææ åæé¡¹ç®</p>'; 522 const reportBlock = reports.length ? `<h4 style="margin-top:14px">æ¥åï¼${reports.length}ï¼</h4><table> 523 <thead><tr><th>æä»¶å</th><th>项ç®</th><th>è¯å</th><th>æ¶é´</th></tr></thead> 524 <tbody>${reports.slice(0, 20).map(r => `<tr> 525 <td>${escapeHtml(r.file_name || 'â')}</td> 526 <td>${escapeHtml(r.project_name || 'â')}</td> 527 <td>${r.overall_score != null ? escapeHtml(r.overall_score) : 'â'}</td> 528 <td>${formatAdminTime(r.created_at)}</td> 529 </tr>`).join('')}</tbody> 530 </table>` : ''; 531 return `<h4>åæé¡¹ç®ï¼${projects.length}ï¼</h4>${projectBlock}${reportBlock}`; 532} 533 534function chooseBpFile(file) { 535 if (!file) return; 536 const extension = file.name.split('.').pop().toLowerCase(); 537 if (!['pdf','docx','pptx'].includes(extension)) { showToast('BPä¸ä¸åææ¯æ PDFãDOCX å PPTX'); return; } 538 if (file.size > 120 * 1024 * 1024) { showToast('æä»¶ä¸è½è¶ è¿ 120MB'); return; } 539 bpSelectedFile = file; 540 $('#bpSelectedFile').textContent = `${file.name} · ${(file.size/1024/1024).toFixed(1)}MB`; 541 $('#bpAnalyzeFileButton').disabled = false; 542 $('#bpAnalysisProgress').textContent = 'æä»¶å·²å°±ç»ª'; 543} 544 545const projectValueDimensionWeights = { 546 'éæ±é»è¾':0.14,'转åé»è¾':0.11,'ä¸´åºæ³¨åé»è¾':0.09,'å¸åºé»è¾':0.12,'ç«äºé»è¾':0.11, 547 'åä¸åé»è¾':0.09,'å¢éé»è¾':0.13,'å£åé»è¾':0.12,'忥é»è¾':0.09 548}; 549 550function normalizeProjectValueReport(report) { 551 if (!report || typeof report !== 'object') return report || {}; 552 const method = String(report.scoring_audit?.method || report.analysis_audit?.scoring_method || ''); 553 const legacyEvidenceScore = /EvidenceScore v1|åºå®è¯å表æBPåæ|æ åæä¸å¾å/i.test(method); 554 if (!legacyEvidenceScore) return report; 555 const dimensions = Array.isArray(report.dimensions) ? report.dimensions : []; 556 let total = 0, lowTotal = 0, highTotal = 0, coverageTotal = 0, weightTotal = 0; 557 dimensions.forEach(item => { 558 const audit = item.score_audit || {}; 559 const criteria = Array.isArray(audit.criteria) ? audit.criteria : [];
560 criteria.forEach(criterion => { 561 const missing = criterion.status === '缺失' || (!criterion.evidence_quote && Number(criterion.awarded_points) === 0); 562 if (!missing) return; 563 criterion.awarded_points = null; 564 criterion.status = 'å¾ éªè¯'; 565 criterion.reason = 'BPæªæè¿°è¯¥é¡¹ï¼å½åæ æ³å¤æï¼ä¸å æ¤ç»é¡¹ç®æ£é¶åï¼è½¬å ¥å°½è°æ ¸éªä»»å¡ã'; 566 criterion.data_status = 'æªç¥ï¼ä¸çäºé¡¹ç®ä¸åå¨è¯¥é¡¹è½å'; 567 }); 568 const supported = criteria.filter(criterion => criterion.awarded_points !== null && criterion.awarded_points !== undefined).length; 569 const coverage = criteria.length ? supported / criteria.length : (item.evidence && item.evidence !== 'BP䏿ªæåç¸å ³å 容' ? 0.25 : 0); 570 const oldModelScore = Number(item.model_score_before_audit); 571 const oldDisplayedScore = Number(item.score); 572 const potentialScore = Number.isFinite(oldModelScore) 573 ? Math.max(0, Math.min(10, oldModelScore)) 574 : (coverage === 0 && (!Number.isFinite(oldDisplayedScore) || oldDisplayedScore <= 0) ? 5 : Math.max(0, Math.min(10, oldDisplayedScore || 5))); 575 const uncertainty = coverage >= 0.75 ? 0.8 : coverage >= 0.5 ? 1.3 : coverage >= 0.25 ? 2 : 2.8; 576 const low = Math.max(0, potentialScore - uncertainty); 577 const high = Math.min(10, potentialScore + uncertainty); 578 const confidence = coverage >= 0.75 ? 'é«' : coverage >= 0.45 ? 'ä¸' : 'ä½'; 579 item.score = Number(potentialScore.toFixed(1)); 580 item.potential_score = item.score; 581 item.score_range = {low:Number(low.toFixed(1)),high:Number(high.toFixed(1))}; 582 item.evidence_coverage = Math.round(coverage * 100); 583 item.score_reason = `已仿§æ¥å䏿¢å¤AIå¯¹é¡¹ç®æ½åç忥夿ï¼BPè¯æ®è¦ç${item.evidence_coverage}%ï¼åçåºé´${item.score_range.low}â${item.score_range.high}åãæªåå 容æ¹ä¸ºå¾ éªè¯ï¼ä¸åä½ä¸ºé¡¹ç®é¶åã`; 584 item.score_audit = {...audit,method:'é¡¹ç®æ½åè¯å + è¯æ®ç½®ä¿¡åº¦å轨ç å¤',confidence,supported_criteria:supported,pending_criteria:criteria.length-supported,evidence_coverage:item.evidence_coverage,criteria,boundary:'è¿æ¯æ§æ¥åææ°å£å¾çå ¼å®¹éæï¼éæ°åæå¯è·å¾æ´åç¡®ç项ç®ä»·å¼å¤æ'}; 585 const weight = Number(item.weight) || projectValueDimensionWeights[item.name] || 1 / Math.max(1, dimensions.length); 586 total += item.score * weight; lowTotal += item.score_range.low * weight; highTotal += item.score_range.high * weight; coverageTotal += coverage * weight; weightTotal += weight; 587 }); 588 if (weightTotal) { 589 report.overall_score = Number((total / weightTotal).toFixed(1)); 590 report.potential_score = report.overall_score; 591 report.score_range = {low:Number((lowTotal / weightTotal).toFixed(1)),high:Number((highTotal / weightTotal).toFixed(1))}; 592 report.evidence_coverage = Math.round(coverageTotal / weightTotal * 100); 593 report.evidence_confidence = report.evidence_coverage >= 75 ? 'é«' : report.evidence_coverage >= 45 ? 'ä¸' : 'ä½'; 594 report.recommendation = report.overall_score >= 7.5 ? '髿½åï¼è¿å ¥ä¸å®¶å审ä¸éç¹éªè¯' : report.overall_score >= 6 ? '建议继ç»è½¬åå¯è¡æ§éªè¯' : report.overall_score >= 5 ? 'è°¨æ æ¨è¿ï¼å éªè¯å ³é®ä»·å¼å设' : 'æç¼éæå ¥ï¼å éªè¯æ ¸å¿ä»·å¼å设'; 595 } 596 report.scoring_audit = {method:'ProjectValueScore v2.0 · æ§æ¥åå ¼å®¹éæ',rule:'é¡¹ç®æ½åä¸BPè¯æ®è¦ç度åå¼ï¼ç¼ºé¡¹ä¸ç´æ¥è®°é¶å'}; 597 report.analysis_audit = {...(report.analysis_audit || {}),scoring_method:'ProjectValueScore v2.0',legacy_value_reconstruction:true}; 598 return report; 599} 600 601function renderProfessionalBpReport(report, options = {}) { 602 report = normalizeProjectValueReport(report); 603 currentInvestmentAnalysis = report; 604 const dimensions = Array.isArray(report.dimensions) ? report.dimensions : []; 605 currentBpDimensions = dimensions; 606 const risks = Array.isArray(report.risks) ? report.risks : []; 607 const audit = report.analysis_audit || {}; 608 const usedAi = audit.ai_called === true || report.analysis_mode === 'ai_full_text_analysis'; 609 const modeBadge = $('#analysisModeBadge'); 610 modeBadge.textContent = usedAi ? 'â å·²å®é è°ç¨ AI' : 'æ¬æºè§ååç'; 611 modeBadge.classList.toggle('verified', usedAi); 612 $('#analysisAuditModel').textContent = usedAi 613 ? `${audit.mode_label || 'DeepSeek AI å ¨æåæ'} · ${audit.model || '模ååç§°å¾ è®°å½'}` 614 : (audit.mode_label || 'æªè°ç¨å¤§æ¨¡å'); 615 const auditParts = []; 616 if (audit.parsed_characters) auditParts.push(`å·²è¯»åæ£æ ${Number(audit.parsed_characters).toLocaleStr
616ing()} å`); 617 if (audit.request_id) auditParts.push(`è°ç¨è®°å½ ${String(audit.request_id).slice(0,12)}â¦`); 618 if (audit.usage?.total_tokens) auditParts.push(`模åç¨é ${Number(audit.usage.total_tokens).toLocaleString()} tokens`); 619 if (audit.external_claims_removed) auditParts.push(`å·²æ¦æª ${audit.external_claims_removed} æ¡æ æ¥æºæ°å`); 620 if (audit.scoring_method) auditParts.push(`è¯å ${audit.scoring_method}`); 621 if (audit.workbench_corrected_from_bp) auditParts.push('产åå·¥ä½å°å·²ææ¬æ¬¡BPåæçº å'); 622 if (audit.knowledge_isolated_from_primary_analysis) auditParts.push('å é¨èµæå·²ä¸ä¸»åæé离'); 623 if (audit.internal_references_used) auditParts.push(`主åæä½¿ç¨å é¨åè ${audit.internal_references_used} 份`); 624 else if (audit.internal_references_available) auditParts.push(`å¦é ${audit.internal_references_available} ä»½å¾ æ ¸éªç 究线索`); 625 if (audit.legacy_value_reconstruction) auditParts.unshift('æ§æ¥åå·²æé¡¹ç®ä»·å¼æ°å£å¾éæï¼å»ºè®®éæ°åæè·å¾æ£å¼ç»æ'); 626 $('#analysisAuditMeta').textContent = auditParts.join(' · ') || 'å½åæ¥åæªä¿å宿´è°ç¨è®°å½ï¼å»ºè®®éæ°åæçæå¯å®¡è®¡çæ¬'; 627 $('#analysisDataBoundary').textContent = audit.external_data_boundary || 'BPäºå®æ¥èªä¸ä¼ æä»¶ï¼è¯åå建议å±äºAI夿ï¼åªæå¸¦æ¥æºé¾æ¥çå¤é¨æ°åææ¯å·²è¿½æº¯åèã'; 628 $('#investmentDecisionMode').textContent = `${usedAi ? 'AIå ¨æåæ' : 'è§ååç'} · 项ç®ä»·å¼åå¤ Â· éæèµç»è®º`; 629 const evidenceCoverage = Number(report.evidence_coverage || 0); 630 const evidenceConfidence = report.evidence_confidence || (evidenceCoverage >= 75 ? 'é«' : evidenceCoverage >= 45 ? 'ä¸' : 'ä½'); 631 $('#investmentConfidence').textContent = `BPè¯æ®è¦ç ${evidenceCoverage}% · 置信度${evidenceConfidence}`; 632 $('#investmentScore').innerHTML = `${escapeHtml(report.overall_score ?? 'â')}<small>/10</small>`; 633 $('#investmentVerdict').textContent = report.recommendation || 'å¾ ä¸å®¶å¤æ'; 634 $('#investmentJudgment').textContent = report.summary || 'åæå®æï¼çå¾ æå§ä¼å¤æ ¸ã'; 635 renderExecutiveBrief(report.executive_brief || null); 636 const pdfBtn = $('#exportReportPdfBtn'); 637 if (pdfBtn) pdfBtn.onclick = exportReportPdf; 638 const pptxBtn = $('#exportReportPptxBtn'); 639 if (pptxBtn) pptxBtn.onclick = exportReportPptx; 640 const checklistBtn = $('#exportChecklistBtn'); 641 if (checklistBtn) checklistBtn.onclick = exportChecklistExcel; 642 const tpResult = $('#transformationPathResult'); 643 if (tpResult) { tpResult.classList.add('hidden'); tpResult.innerHTML = ''; } 644 $('#investmentResult').classList.remove('hidden'); 645 const referenceData = report.reference_data || defaultInvestmentReferenceData(); 646 renderDecisionWorkbench(report.decision_workbench || defaultDecisionWorkbench(referenceData.product_scope), report); 647 renderInvestmentEvidence(referenceData); 648 renderInternalKnowledgeReferences(report.internal_knowledge || {}); 649 renderBiomedicalRoute(report.biomedical_route || null); 650 renderInvestmentReviewChecklist(); 651 refreshInvestmentAssistant(); 652 loadInvestmentEnterpriseMatches(); 653 loadInvestorPanels(); 654 if (!options.silent) $('#investmentResult').scrollIntoView({behavior:'smooth',block:'nearest'}); 655} 656 657// ============ æèµç®æ¥ï¼AIåäºå±ï¼ï¼åæ¥è¯ææ³ï¼åºäºå ¨é¨å·²æ ¸éªæ¨¡åæ°æ®çæï¼äºå®åçº§æ æ³¨ ============ 658 659function renderExecutiveBrief(eb) { 660 const panel = $('#execBriefPanel'); 661 if (!panel) return; 662 const b = eb && eb.ran ? eb.brief : null; 663 if (!b || !b.step1_track) { panel.classList.add('hidden'); return; } 664 panel.classList.remove('hidden'); 665 $('#execBriefModel').textContent = eb.model ? `åäºæ¨¡å ${eb.model}` : ''; 666 $('#execOverview').textContent = b.overview || ''; 667 const srcBadge = source => { 668 const map = {'宿¹æ°æ®':'official','ç½ç»æ¥é':'news','BPèªè¿°':'bp','AIæ¨æ':'ai'}; 669 return `<i class="src-badge src-${map[source] || 'ai'}">${escapeHtml(source || 'AIæ¨æ')}</i>`; 670 }; 671 672 // è¦ç¤ºçº¢çï¼è¿åº¦/æ°æ®/ç«å/å¢é/å¸åºï¼ 673 const flags = b.red_flags || []; 674 const flagBox = $('#execRedFlags'); 675 flagBox.classList.toggle('hidden', !flags.length); 676 flagBox.innerHTML = flags.map(f => 677 `<div class="exec-redflag"><b>ð© ${escapeHtml(f.flag)}</b><span>${escapeHtml(f.detail)}</span></div>` 678 ).join(''); 679 680 // ç¬¬ä¸æ¥ · èµéç¿»è¯å® 681 const s1 = b.step1_track || {}; 682 $('#execStep1').innerHTML = `<header><i>ç¬¬ä¸æ¥</i><strong>èµéç¿»è¯å®</strong><small>è¿å°åºæ¯ä»ä¹ï¼ç¨è½å¬æçè¯è¯´</small></header>` 683 + (s1.essence ? `<p class="exec-essence">${escapeHtml(s1.essence)}</p>` : '') 684 + `<div class="exec-metrics">${(s1.golden_metrics || []).map(m => 685 `<div class="exec-metric"><b>${escapeHtml(m.metric)}</b><span>${escapeHtml(m.why)}</span></div>`).join('')}</div>` 686 + (s1.endgame ? `<p class="exec-endgame"><b>èµéç»å±ï¼${escapeHtml(s1.endgame)}</b><span>${escapeHtml(s1.endgame_reason || '')}</span></p>` : ''); 687 688 // ç¬¬äºæ¥ · ä»·å¼å¤å® 689 const s2 = b.step2_value || {}; 690 const a = s2.anchor || {}; 691 $('#execStep2').innerHTML = `<header><i>ç¬¬äºæ¥</i><strong>ä»·å¼å¤å®</strong><small>å¼ä¸å¼é±ãå¼å¤å°é±ï¼ç±å¯¹æè¯´äºç®</small></header>` 692 + (a.name ? `<div class="exec-anchor"><span class="exec-anchor-label">头巿»æ</span><strong>${escapeHtml(a.name)}</strong><small>${escapeHtml(a.fact || '')}</small>${srcBadge(a.source)}</div>` : '') 693 + (s2.lag_estimate ? `<p class="exec-line"><b>æ¶é´æ©ç½</b><span>${escapeHtml(s2.lag_estimate)}</span></p>` : '') 694 + (s2.valuation_range ? `<div class="exec-valuation"><small>å«è½åæ©ç½çæåä¼°å¼åºé´ï¼AIæ¨æï¼é¡»æå§ä¼å¤æ ¸
694ï¼</small><strong>${escapeHtml(s2.valuation_range)}</strong><p>${escapeHtml(s2.valuation_reason || '')}</p></div>` : '') 695 + (s2.fallback_position ? `<p class="exec-line"><b>å¤èä»·å¼</b><span>${escapeHtml(s2.fallback_position)}</span></p>` : ''); 696 697 // ç¬¬ä¸æ¥ · æ»äº¡é¢æ¼å® 698 const s3 = b.step3_death || {}; 699 const sd = s3.science_death || {}, md = s3.market_death || {}, td = s3.team_death || {}; 700 const deathCard = (title, body, primary) => 701 `<div class="death-card${primary ? ' primary' : ''}">${primary ? '<em>ä¸çº§é£é©</em>' : ''}<b>${title}</b>${body}</div>`; 702 $('#execStep3').innerHTML = `<header><i>ç¬¬ä¸æ¥</i><strong>æ»äº¡é¢æ¼å®</strong><small>è¿ä¸ªé¡¹ç®ä¼æä¹æ»ï¼ä¸ç§æ»æ³ï¼åªä¸ªæ¦çæå¤§</small></header>` 703 + `<div class="death-grid">` 704 + deathCard('æ»æ³ä¸ · ç§å¦èªæ', 705 `<span>${escapeHtml(sd.assumption || '')}</span>${sd.probability ? `<u>è¯ä¼ªæ¦ç ${escapeHtml(sd.probability)}</u>` : ''}<small>${escapeHtml(sd.reason || '')}</small>`, 706 s3.primary_risk === 'ç§å¦èªæ') 707 + deathCard('æ»æ³äº · å¸åºè°æ', 708 `<span>${escapeHtml(md.scenario || '')}</span><small>${escapeHtml(md.reason || '')}</small>`, 709 s3.primary_risk === 'å¸åºè°æ') 710 + deathCard('æ»æ³ä¸ · å¢éå è', 711 `<span>${escapeHtml(td.scenario || '')}</span><small>${escapeHtml(td.reason || '')}</small>`, 712 s3.primary_risk === 'å¢éå è') 713 + `</div>` 714 + (s3.primary_risk ? `<p class="exec-line primary-risk"><b>ä¸çº§é£é©ï¼${escapeHtml(s3.primary_risk)}</b><span>${escapeHtml(s3.primary_reason || '')}</span></p>` : '') 715 + (s3.incubator_hedge ? `<p class="exec-line"><b>åµåå¨è½å¦å¯¹å²</b><span>${escapeHtml(s3.incubator_hedge)}</span></p>` : ''); 716 717 // ç¬¬åæ¥ · è¡å¨å¯¼èªä»ª 718 const s4 = b.step4_action || {}; 719 const choiceCls = {A:'a', B:'b', C:'c', D:'d'}[s4.choice] || 'c'; 720 $('#execStep4').innerHTML = `<header><i>ç¬¬åæ¥</i><strong>è¡å¨å¯¼èªä»ª</strong><small>å«åºè¯ï¼è¯¥æä¹å</small></header>` 721 + `<div class="exec-choice"><b class="choice-${choiceCls}">${escapeHtml(s4.choice || 'â')}</b><div><strong>${escapeHtml(s4.label || '')}</strong><p>${escapeHtml(s4.reason || '')}</p></div></div>`; 722 723 $('#execWatch').innerHTML = (b.watch_points || []).length 724 ? '<strong class="exec-block-title">æªæ¥ 12â18 个æè§å¯ç¹</strong><div>' + b.watch_points.map(w => `<span>${escapeHtml(w)}</span>`).join('') + '</div>' 725 : ''; 726 $('#execBoundary').textContent = eb.boundary || ''; 727 728 // é»è¾å¯¹é½ï¼åæ¥è¯æä¸ºç³»ç»æ ¸å¿é»è¾ï¼é¡¶é¨ç»è®ºä»¥è¡å¨å¯¼èªä»ªä¸ºåï¼é¿å 䏿§è¯åå£å¾å²çª 729 if (s4.choice && s4.label) { 730 const tail = s4.label.includes('â') ? s4.label.split('â')[1].trim() : s4.label; 731 $('#investmentVerdict').textContent = `è¡å¨å»ºè®® ${s4.choice} · ${tail}`; 732 if (s4.reason) $('#investmentJudgment').textContent = s4.reason; 733 } 734} 735 736// ============ 导åºå®æ´åææ¥å PDF ============ 737 738async function exportReportPdf() { 739 if (!currentInvestmentReportId) { showToast('ææ å·²ä¿åçæ¥åï¼è¯·å 宿䏿¬¡åæ'); return; } 740 const btn = $('#exportReportPdfBtn'); 741 if (btn) btn.disabled = true; 742 showToast('æ£å¨çæå®æ´åææ¥å PDFâ¦'); 743 try { 744 const token = localStorage.getItem('token'); 745 const resp = await fetch(`${BP_API_BASE}/api/reports/${currentInvestmentReportId}/download`, { 746 headers: token ? {Authorization:`Bearer ${token}`} : {}, 747 }); 748 if (!resp.ok) throw new Error('download failed'); 749 const blob = await resp.blob(); 750 const cd = resp.headers.get('Content-Disposition') || ''; 751 const match = cd.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i); 752 const url = URL.createObjectURL(blob); 753 const link = document.createElement('a'); 754 link.href = url; 755 link.download = match ? decodeURIComponent(match[1]) : `BPåææ¥å-${currentInvestmentReportId.slice(0,8)}.pdf`; 756 link.click(); 757 URL.revokeObjectURL(url); 758 showToast('宿´åææ¥å PDF å·²å¼å§ä¸è½½'); 759 } catch (error) { 760 showToast('PDF 导åºå¤±è´¥ï¼è¯·ç¨åéè¯'); 761 } finally { 762 if (btn) btn.disabled = false;
763 } 764} 765 766// éç¨æ¥åæä»¶ä¸è½½ï¼PPTX æèµå¤å¿å½ / Excel å°½è°æ¸ åå ±ç¨åä¸å¥é»è¾ 767async function downloadReportFile({endpoint, fallbackName, btnId, busyText, okText, failText}) { 768 if (!currentInvestmentReportId) { showToast('ææ å·²ä¿åçæ¥åï¼è¯·å 宿䏿¬¡åæ'); return; } 769 const btn = $(btnId); 770 if (btn) btn.disabled = true; 771 showToast(busyText); 772 try { 773 const token = localStorage.getItem('token'); 774 const resp = await fetch(`${BP_API_BASE}/api/reports/${currentInvestmentReportId}/${endpoint}`, { 775 headers: token ? {Authorization:`Bearer ${token}`} : {}, 776 }); 777 if (!resp.ok) throw new Error('download failed'); 778 const blob = await resp.blob(); 779 const cd = resp.headers.get('Content-Disposition') || ''; 780 const match = cd.match(/filename\*?=(?:UTF-8''|")?([^";]+)/i); 781 const url = URL.createObjectURL(blob); 782 const link = document.createElement('a'); 783 link.href = url; 784 link.download = match ? decodeURIComponent(match[1]) : fallbackName; 785 link.click(); 786 URL.revokeObjectURL(url); 787 showToast(okText); 788 } catch (error) { 789 showToast(failText); 790 } finally { 791 if (btn) btn.disabled = false; 792 } 793} 794 795function exportReportPptx() { 796 downloadReportFile({ 797 endpoint:'download_pptx', 798 fallbackName:`æèµå¤å¿å½-${(currentInvestmentReportId || '').slice(0,8)}.pptx`, 799 btnId:'#exportReportPptxBtn', 800 busyText:'æ£å¨çææèµå¤å¿å½ PPTâ¦', 801 okText:'æèµå¤å¿å½ PPT å·²å¼å§ä¸è½½', 802 failText:'PPT 导åºå¤±è´¥ï¼è¯·ç¨åéè¯', 803 }); 804} 805 806function exportChecklistExcel() { 807 downloadReportFile({ 808 endpoint:'download_checklist', 809 fallbackName:`å°½è°æ¸ å-${(currentInvestmentReportId || '').slice(0,8)}.xlsx`, 810 btnId:'#exportChecklistBtn', 811 busyText:'æ£å¨çæå°½è°æ¸ å Excelâ¦', 812 okText:'å°½è°æ¸ å Excel å·²å¼å§ä¸è½½', 813 failText:'Excel 导åºå¤±è´¥ï¼è¯·ç¨åéè¯', 814 }); 815} 816 817// ============ æèµå³çä¸é®ï¼ä¸çº§çº¢ç¯æ£æ¥ + ææ¬/é£é©/å¸åº ============ 818 819let investorBriefData = null; 820let redlightResultData = null; 821let redlightRunning = false; 822 823const REDLIGHT_STATUS_META = { 824 red: { label: '红ç¯', cls: 'red' }, 825 yellow: { label: 'é»ç¯', cls: 'yellow' }, 826 green: { label: '绿ç¯', cls: 'green' }, 827 unknown: { label: 'æªæ ¸éª', cls: 'unknown' }, 828}; 829 830function redlightVerdictClass(verdict) { 831 if (verdict === '忥䏿¨è') return 'red'; 832 if (verdict === 'è°¨æ æ¨è¿') return 'yellow'; 833 if (verdict === 'éè¿åç') return 'green'; 834 return 'unknown'; 835} 836 837function renderRedlightPanel(result) { 838 const empty = $('#redlightEmpty'); 839 const loading = $('#redlightLoading'); 840 const box = $('#redlightResult'); 841 const badge = $('#redlightVerdictBadge'); 842 if (!empty || !box) return; 843 empty.classList.toggle('hidden', Boolean(result)); 844 loading.classList.add('hidden'); 845 box.classList.toggle('hidden', !result); 846 if (!result) { 847 badge?.classList.add('hidden'); 848 return; 849 } 850 $('#redlightChecks').innerHTML = (result.checks || []).map(check => { 851 const meta = REDLIGHT_STATUS_META[check.status] || REDLIGHT_STATUS_META.unknown; 852 const evidence = (check.evidence || []).map(item => 853 `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener noreferrer"><i>${escapeHtml(item.source)}</i><span>${escapeHtml(item.label)}</span></a>` 854 ).join(''); 855 const quotes = (check.bp_quotes || []).map(quote => `<li>${escapeHtml(quote)}</li>`).join(''); 856 return `<article class="redlight-check ${meta.cls}"><header><i></i><strong>${escapeHtml(check.name)}</strong><span>${meta.label}</span></header><p>${escapeHtml(check.summary || '')}</p>${evidence ? `<div class="redlight-evidence">${evidence}</div>` : ''}${quotes ? `<ul class="redlight-quotes">${quotes}</ul>` : ''}</article>`; 857 }).join(''); 858 const verdictCls = redlightVerdictClass(result.verdict); 859 const verdictEl = $('#redlightVerdict'); 860 verdictEl.textContent = result.verdict || 'â'; 861 verdictEl.className = `investor-verdict-badge ${verdictCls}`; 862 const checkedAt = result.checked_at ? new Date(result.checked_at).toLocaleStr
862ing('zh-CN', { hour12: false }) : ''; 863 $('#redlightMeta').textContent = [result.verdict_reason, result.data_note, checkedAt].filter(Boolean).join(' · '); 864 if (badge) { 865 badge.textContent = result.verdict || ''; 866 badge.className = `investor-verdict-badge ${verdictCls}`; 867 } 868} 869 870function investorCostCard(data = {}) { 871 if (data.status !== 'ok') { 872 return `<article class="investor-q-card"><header><span>ä¸é® · ææ¬</span><strong>è¦è±å¤å°é±ãå¤ä¹ æ¿è¯</strong></header><div class="investor-q-empty"><strong>${escapeHtml(data.headline || 'æ¥åæªå 嫿æ¬ä¼°ç®')}</strong><p>${escapeHtml(data.message || 'æ¥åæªå 嫿æ¬ä¼°ç®ï¼å»ºè®®éæ°åææäººå·¥è¡¥å ')}</p></div></article>`; 873 } 874 const items = (data.items || []).map(item => 875 `<li><span>${escapeHtml(item.label)}</span><b>${escapeHtml(item.low)}â${escapeHtml(item.high)}<i>åºå ${escapeHtml(item.base)}</i></b></li>` 876 ).join(''); 877 const gaps = (data.gaps || []).map(gap => `<li>${escapeHtml(gap)}</li>`).join(''); 878 return `<article class="investor-q-card"><header><span>ä¸é® · ææ¬</span><strong>è¦è±å¤å°é±ãå¤ä¹ æ¿è¯</strong></header><div class="investor-q-headline">${escapeHtml(data.headline)}</div><p class="investor-q-sub">
878${[data.profile_name, data.regulatory_route].filter(Boolean).map(escapeHtml).join(' · ')}</p><p class="investor-q-line"><b>卿估ç®</b><span>${escapeHtml(data.timeline || 'å¾ ç¡®è®¤')}</span></p>${items ? `<ul class="investor-cost-items">${items}</ul>` : ''}${gaps ? `<div class="investor-q-gaps"><strong>æ°æ®ç¼ºå£æç¤º</strong><ul>${gaps}</ul></div>` : ''}<footer>${escapeHtml(data.confidence || 'éé¢ä¸ºç«é¡¹é¶æ®µè§ååæ°ï¼éæ£å¼æ¥ä»·')}</footer></article>`; 879} 880 881function investorRiskCard(data = {}) { 882 const risks = (data.high_risks || []).map(risk => 883 `<li><strong>${escapeHtml(risk.category)}</strong><p>${escapeHtml(risk.description)}</p>${risk.mitigation ? `<small>åè§£å¨ä½ï¼${escapeHtml(risk.mitigation)}</small>` : ''}</li>` 884 ).join(''); 885 const redlight = data.redlight; 886 const redlightHtml = redlight 887 ? `<div class="investor-q-redlight ${redlightVerdictClass(redlight.verdict)}"><b>${escapeHtml(redlight.verdict || 'â')}</b><span>${(redlight.checks || []).map(check => `<i class="dot ${escapeHtml(check.status)}" title="${escapeHtml(check.name)}ï¼${escapeHtml(check.summary || '')}"></i>`).join('')}</span><small>${escapeHtml(redlight.verdict_reason || '')}</small></div>` 888 : '<div class="investor-q-redlight none"><b>å°æªè¿è¡çº¢ç¯æ£æ¥</b><small>ç¹å»ä¸æ¹ãå¼å§çº¢ç¯æ£æ¥ âãè·å PubMed / ClinicalTrials.gov å¤é¨æ°æ®æ ¸éª</small></div>'; 889 const body = risks 890 ? `<ul class="investor-risk-list">${risks}</ul>` 891 : `<div class="investor-q-empty"><p>${escapeHtml(data.message || 'æ¨¡åæªè¯å«é«é£é©é¡¹ï¼ä»é¡»äººå·¥å°½è°')}</p></div>`; 892 return `<article class="investor-q-card"><header><span>äºé® · é£é©</span><strong>ä»ä¹ä¼è®©é¡¹ç®æ»æ</strong></header>${body}${redlightHtml}</article>`; 893} 894 895function investorMarketCard(data = {}) { 896 const sizing = (data.tam || data.sam || data.som) 897 ? `<ul class="investor-market-sizing">${[['TAM æ»ä½å¸åº', data.tam], ['SAM 坿å¡å¸åº', data.sam], ['SOM å¯è·åå¸åº', data.som]].filter(([, value]) => value).map(([label, value]) => `<li><span>${label}</span><b>${escapeHtml(value)}</b></li>`).join('')}</ul>` 898 : ''; 899 const references = (data.references || []).map(item => 900 `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener noreferrer"><i>${escapeHtml(item.source)}</i><span>${escapeHtml(item.label)} ${escapeHtml(item.value)}ï¼${escapeHtml(item.period)}ï¼</span>${item.scope_note ? `<small>${escapeHtml(item.scope_note)}</small>` : ''}</a>` 901 ).join(''); 902 const judgment = data.dimension_judgment ? `<p class="investor-q-line"><b>ç»´åº¦å¤æ</b><span>${escapeHtml(data.dimension_judgment)}</span></p>` : ''; 903 const message = data.message ? `<p class="investor-q-note">${escapeHtml(data.message)}</p>` : ''; 904 const empty = (!sizing && !references && !judgment) 905 ? `<div class="investor-q-empty"><p>${escapeHtml(data.message || 'æ¥åæªå å«å¸åºæµç®ï¼å»ºè®®éæ°åææäººå·¥è¡¥å ')}</p></div>` 906 : ''; 907 return `<article class="investor-q-card"><header><span>ä¸é® · å¸åº</span><strong>èç³æå¤å¤§ãè½åå°å¤å°</strong></header>${empty}${sizing}${judgment}${references ? `<div class="investor-market-refs"><strong>å¤é¨å¸åºåè</strong>${references}</div>` : ''}${!empty ? message : ''}</article>`; 908} 909 910function renderInvestorBrief(brief) { 911 const grid = $('#investorBriefGrid'); 912 if (!grid) return; 913 if (!brief) { 914 grid.innerHTML = '<div class="investor-brief-empty">æèµå³çä¸é®ææªçæï¼è¯·å å¨å½å项ç®ä¸å®æ BP å ¨æåæã</div>'; 915 return; 916 } 917 const renderers = { cost: investorCostCard, risk: investorRiskCard, market: investorMarketCard }; 918 grid.innerHTML = (brief.questions || []).map(question => (renderers[question.key] || (() => ''))(question.data || {})).join(''); 919} 920 921async function loadInvestorPanels() { 922 if (!currentInvestmentProject?.id) { 923 renderRedlightPanel(null); 924 renderInvestorBrief(null); 925 return; 926 } 927 const projectId = currentInvestmentProject.id; 928 try { 929 redlightResultData = await investmentApiRequest(`/api/investment/projects/${projectId}/redlight-check`); 930 } catch { 931 redlightResultData = null; 932 } 933 renderRedlightPanel(redlightResultData); 934 try { 935 investorBriefData = await investmentApiRequest(`/api/investment/projects/${projectId}/investor-brief`); 936 } catch { 937 investorBriefData = null; 938 } 939 renderInvestorBrief(investorBriefData); 940 try { 941 deepAssessmentData = await investmentApiRequest(`/api/investment/projects/${projectId}/deep-assessment`); 942 } catch { 943 deepAssessmentData = null; 944 } 945 assessmentDirty = false;
946 renderDeepAssessment(deepAssessmentData); 947 try { 948 exitSimData = await investmentApiRequest(`/api/investment/projects/${projectId}/exit-simulation`); 949 } catch { 950 exitSimData = null; 951 } 952 renderExitSimulation(exitSimData); 953 await loadMilestones(); 954} 955 956async function runRedlightCheck() { 957 if (redlightRunning) return; 958 if (!currentInvestmentProject?.id) { showToast('请å éæ©æèµé¡¹ç®'); return; } 959 redlightRunning = true; 960 $('#redlightEmpty')?.classList.add('hidden'); 961 $('#redlightResult')?.classList.add('hidden'); 962 $('#redlightLoading')?.classList.remove('hidden'); 963 try { 964 redlightResultData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/redlight-check`, { method: 'POST' }); 965 renderRedlightPanel(redlightResultData); 966 // é£é©åå¼ç¨çº¢ç¯ç»è®ºï¼åæ¥å·æ°ä¸é®å¡ç 967 try { 968 investorBriefData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/investor-brief`); 969 renderInvestorBrief(investorBriefData); 970 } catch { /* ä¸é®å·æ°å¤±è´¥ä¸å½±å红ç¯ç»æå±ç¤º */ } 971 } catch (error) { 972 renderRedlightPanel(redlightResultData); 973 showToast(error.message || 'çº¢ç¯æ£æ¥å¤±è´¥ï¼è¯·ç¨åéè¯'); 974 } finally { 975 redlightRunning = false; 976 $('#redlightLoading')?.classList.add('hidden'); 977 } 978} 979 980document.addEventListener('click', event => { 981 if (event.target.closest('#redlightRunButton') || event.target.closest('#redlightRerunButton')) runRedlightCheck(); 982}); 983 984// ============ äºçº§æ¼æï¼äºç»´é·è¾¾æ·±åº¦è¯ä¼°ï¼AIé¢å¤ + 人工确认 + èµè½å¹é ï¼ ============ 985 986let deepAssessmentData = null; 987let assessmentDirty = false; 988let assessmentRunning = false; 989 990const ASSESS_CAPABILITY_MAP = { 991 science: { name: 'ç§å¦å æ ¸', capabilities: ['ä¸å®¶ä¸KOLç½ç»', 'èåç å'] }, 992 asset: { name: 'èµäº§è´¨é', capabilities: ['CMC/ä¸è¯å¹³å°', 'å·¥ç¨éªè¯'] }, 993 compliance: { name: 'é£é©ä¸åè§', capabilities: ['注åç³æ¥æå¡', 'æ³è§å¢é'] }, 994 market: { name: 'å¸åºä¸åä¸', capabilities: ['æ¸ éèµæº', 'éå¢å¸åºç½ç»'] }, 995 team: { name: 'å¢é䏿§è¡', capabilities: ['è¿è¥èµè½', '产ä¸å¯¼å¸'] }, 996}; 997 998function recomputeAssessment(data) { 999 if (!data?.dimensions) return; 1000 data.dimensions.forEach(dim => { 1001 const subs = dim.subitems || []; 1002 if (subs.length) dim.score = Math.round(subs.reduce((sum, sub) => sum + Number(sub.score || 0), 0) / subs.length * 10) / 10; 1003 }); 1004 const weak = [...data.dimensions].sort((a, b) => a.score - b.score).slice(0, 2); 1005 const matched = weak.filter(item => ASSESS_CAPABILITY_MAP[item.key]); 1006 const avgWeak = weak.length ? weak.reduce((sum, item) => sum + item.score, 0) / weak.length : 0; 1007 const bonus = Math.max(0, Math.min(15, Math.round((3 - avgWeak) * 5))); 1008 let matchIndex, summary; 1009 if (matched.length === 2) { matchIndex = Math.min(100, 85 + bonus); summary = 'çæ¿æ°ä¸ºåå«å¼ºé¡¹ï¼èµè½ä»·å¼é«'; } 1010 else if (matched.length === 1) { matchIndex = Math.min(80, 60 + bonus); summary = 'ä¸é¡¹çæ¿å¯ç±åå«è½åè¡¥é½ï¼å¦ä¸é¡¹éå¤é¨èµæº'; } 1011 else { matchIndex = Math.min(50, 30 + bonus); summary = 'çæ¿è¶ åºåå«å½åèµè½èå´ï¼éè°¨æ è¯ä¼°åµåä»·å¼'; } 1012 data.capability_match = { 1013 weak_dimensions: weak.map(item => ({ key: item.key, name: item.name, score: item.score, capabilities: (ASSESS_CAPABILITY_MAP[item.key] || {}).capabilities || [] })), 1014 matched_capabilities: matched.map(item => ({ dimension: item.name, capabilities: ASSESS_CAPABILITY_MAP[item.key].capabilities })), 1015 match_index: matchIndex, 1016 summary, 1017 }; 1018} 1019 1020function buildAssessRadarSvg(dimensions, weakKeys) { 1021 const cx = 160, cy = 118, R = 82, n = dimensions.length || 5; 1022 const angleOf = index => -Math.PI / 2 + index * 2 * Math.PI / n; 1023 const point = (index, radius) => [cx + radius * Math.cos(angleOf(index)), cy + radius * Math.sin(angleOf(index))]; 1024 let rings = ''; 1025 for (let level = 1; level <= 5; level++) { 1026 const points = dimensions.map((_, index) => point(index, R * level / 5).map(v => v.toFixed(1)).join(',')).join(' '); 1027 rings += `<polygon points="${points}" class="radar-ring${level === 5 ? ' outer' : ''}"/>`; 1028 } 1029 let axes = '', labels = '', dots = ''; 1030 dimensions.forEach((dim, index) => { 1031 const weak = weakKeys.includes(dim.key); 1032 const [x, y] = point(index, R); 1033 axes += `<line x1="${cx}" y1="${cy}" x2="${x.toFixed(1)}" y2="${y.toFixed(1)}" class="radar-axis"/>`; 1034 const [lx, ly] = point(index, R + 22); 1035 labels += `<text x="${lx.toFixed(1)}" y="${(ly + 3).toFixed(1)}" class="radar-label${weak ? ' weak' : ''}" text-anchor="middle">${escapeHtml(dim.name)} ${escapeHtml(dim.score)}</text>`; 1036 const [dx, dy] = point(index, R * Math.max(0, Math.min(5, Number(dim.score) || 0)) / 5); 1037 dots += `<circle cx="${dx.toFixed(1)}" cy="${dy.toFixed(1)}" r="4" class="radar-dot${weak ? ' weak' : ''}"/>`; 1038 }); 1039 const valuePoints = dimensions.map((dim, index) => point(index, R * Math.max(0, Math.min(5, Number(dim.score) || 0)) / 5).map(v => v.toFixed(1)).join(',')).join(' '); 1040 return `<svg viewBox="0 0 320 240" class="radar-svg" role="img" aria-label="äºç»´é·è¾¾å¾">${rings}${axes}<polygon points="${valuePoints}" class="radar-value"/>${dots}${labels}</svg><div class="radar-scale"><span>å»åº¦ 1â5</span><span><i class="radar-legend-weak"></i>çæ¿ç»´åº¦</span></div>`; 1041} 1042 1043function renderDeepAssessment(data) { 1044 const empty = $('#assessEmpty'); 1045 const loading = $('#assessLoading'); 1046 const box = $('#assessResult'); 1047 const badge = $('#assessMatchBadge'); 1048 if (!empty || !box) return; 1049 empty.classList.toggle('hidden', Boolean(data)); 1050 loading.classList.add('hidden'); 1051 box.classList.toggle('hidden', !data); 1052 if (!data) { 1053 badge?.classList.add('hidden'); 1054 return; 1055 } 1056 const dimensions = data.dimensions || []; 1057 const match = data.capability_match || {}; 1058 const weakKeys = (match.weak_dimensions || []).map(item => item.key); 1059 $('#assessRadar').innerHTML = buildAssessRadarSvg(dimensions, weakKeys); 1060 $('#assessDims').innerHTML = dimensions.map(dim => { 1061 const weak = weakKeys.includes(dim.key); 1062 const subs = (dim.subitems || []).map(sub => `<li><div class="assess-sub-head"><span>${escapeHtml(sub.name)}</span><div class="assess-adj">
1062<button type="button" data-assess-adjust="${escapeHtml(dim.key)}|${escapeHtml(sub.key)}|-1" ${Number(sub.score) <= 1 ? 'disabled' : ''} aria-label="åå">â</button><b>${escapeHtml(sub.score)}</b><button type="button" data-assess-adjust="${escapeHtml(dim.key)}|${escapeHtml(sub.key)}|1" ${Number(sub.score) >= 5 ? 'disabled' : ''} aria-label="å å">ï¼</button></div></div>${sub.reason ? `<p>${escapeHtml(sub.reason)}</p>` : ''}${sub.evidence ? `<small>è¯æ®ï¼${escapeHtml(sub.evidence)}</small>` : ''}</li>`).join(''); 1063 return `<article class="assess-dim${weak ? ' weak' : ''}"><header><strong>${escapeHtml(dim.name)}${weak ? '<i>çæ¿</i>' : ''}</strong><b>${escapeHtml(dim.score)}<small>/5</small></b></header><ul>${subs}</ul></article>`; 1064 }).join(''); 1065 const weakRows = (match.weak_dimensions || []).map(item => `<li><span>${escapeHtml(item.name)} <b>${escapeHtml(item.score)}</b> å</span><i>â</i><strong>${item.capabilities?.length ? escapeHtml(item.capabilities.join('ã')) : 'ææ å¯¹åºå¼ºé¡¹'}</strong></li>`).join(''); 1066 $('#assessMatch').innerHTML = `<div class="assess-match-index"><b>${escapeHtml(match.match_index ?? 'â')}</b><span>èµè½å¹é 度</span></div><div class="assess-match-detail"><strong>${escapeHtml(match.summary || '')}</strong><ul>${weakRows}</ul></div>`; 1067 const assessedAt = data.assessed_at ? new Date(data.assessed_at).toLocaleString('zh-CN', { hour12: false }) : ''; 1068 const adjusted = data.updated_by === 'human_adjusted' && data.adjusted_at ? ` · å·²ç±æèµäººè°æ´ç¡®è®¤ï¼${new Date(data.adjusted_at).toLocaleString('zh-CN', { hour12: false })}ï¼` : ''; 1069 $('#assessMeta').textContent = [data.product_type_note, data.data_note, assessedAt].filter(Boolean).join(' · ') + adjusted; 1070 if (badge) { 1071 badge.textContent = `èµè½å¹é ${match.match_index ?? 'â'}`; 1072 badge.className = `investor-verdict-badge ${(match.match_index ?? 0) >= 85 ? 'green' : (match.match_index ?? 0) >= 60 ? 'yellow' : 'red'}`; 1073 } 1074 $('#assessSaveButton')?.classList.toggle('hidden', !assessmentDirty); 1075} 1076 1077function adjustAssessmentScore(dimKey, subKey, delta) { 1078 if (!deepAssessmentData?.dimensions) return; 1079 const dim = deepAssessmentData.dimensions.find(item => item.key === dimKey); 1080 const sub = (dim?.subitems || []).find(item => item.key === subKey); 1081 if (!sub) return; 1082 const next = Math.max(1, Math.min(5, Number(sub.score || 3) + delta)); 1083 if (next === sub.score) return; 1084 sub.score = next; 1085 assessmentDirty = true; 1086 recomputeAssessment(deepAssessmentData); 1087 deepAssessmentData.updated_by = 'human_adjusted_draft'; 1088 renderDeepAssessment(deepAssessmentData); 1089} 1090 1091async function saveAssessmentAdjustment() { 1092 if (!deepAssessmentData || !currentInvestmentProject?.id) return; 1093 const button = $('#assessSaveButton'); 1094 button.disabled = true; 1095 button.textContent = 'ä¿åä¸â¦'; 1096 try { 1097 deepAssessmentData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/deep-assessment`, { 1098 method: 'PUT', 1099 headers: { 'Content-Type': 'application/json' }, 1100 body: JSON.stringify(deepAssessmentData), 1101 }); 1102 assessmentDirty = false; 1103 renderDeepAssessment(deepAssessmentData); 1104 showToast('äºç»´è¯ä¼°è°æ´å·²ä¿å'); 1105 } catch (error) { 1106 showToast(error.message || 'ä¿å失败ï¼è¯·ç¨åéè¯'); 1107 } finally { 1108 button.disabled = false; 1109 button.textContent = 'ä¿åè°æ´'; 1110 } 1111} 1112 1113async function runDeepAssessment() { 1114 if (assessmentRunning) return; 1115 if (!currentInvestmentProject?.id) { showToast('请å éæ©æèµé¡¹ç®'); return; } 1116 assessmentRunning = true; 1117 $('#assessEmpty')?.classList.add('hidden'); 1118 $('#assessResult')?.classList.add('hidden'); 1119 $('#assessLoading')?.classList.remove('hidden'); 1120 try { 1121 deepAssessmentData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/deep-assessment`, { method: 'POST' }); 1122 assessmentDirty = false; 1123 renderDeepAssessment(deepAssessmentData); 1124 } catch (error) { 1125 renderDeepAssessment(deepAssessmentData); 1126 showToast(error.message || 'äºç»´è¯ä¼°å¤±è´¥ï¼è¯·ç¨åéè¯'); 1127 } finally { 1128 assessmentRunning = false;
1129 $('#assessLoading')?.classList.add('hidden'); 1130 } 1131} 1132 1133document.addEventListener('click', event => { 1134 const adjuster = event.target.closest('[data-assess-adjust]'); 1135 if (adjuster) { 1136 const [dimKey, subKey, delta] = adjuster.dataset.assessAdjust.split('|'); 1137 adjustAssessmentScore(dimKey, subKey, Number(delta)); 1138 return; 1139 } 1140 if (event.target.closest('#assessRunButton') || event.target.closest('#assessRerunButton')) runDeepAssessment(); 1141 if (event.target.closest('#assessSaveButton')) saveAssessmentAdjustment(); 1142}); 1143 1144// ============ ä¸çº§æ¼æï¼æå³è¾ å©ä¸æ¨¡æï¼éç¨ç¢ + éåºè·¯å¾ + 仿¬¾æ æ¯æ¨¡æå¨ï¼ ============ 1145// 1146// 仿¬¾æ æ¯æ¨¡æå¨è®¡ç®æ¨¡åï¼ç®åæ æ¯æ¨¡åï¼ä¾è®¨è®ºåèï¼ä¸æææèµå»ºè®®ï¼ï¼ 1147// æ»æèµ T = 馿æèµé¢ + éç¨ç¢ä»æ¬¾æ»é¢ï¼ä¸å ï¼ï¼å³æå¤§äºæï¼é£é©æå£ï¼ã 1148// ä¸ä¸ªæ æ¯ï¼ 1149// æåï¼æ¦ç = éç¨ç¢è¾¾ææ¦ç pï¼éåºå款 = å½ååèä¼°å¼ Ã éåºä¼°å¼åæ° Ã è¡æå æ¯ 1150// é¨åè¾¾æï¼æ¦ç = (1âp)/2ï¼æå½ååèä¼°å¼éåºï¼å款 = å½ååèä¼°å¼ Ã è¡æå æ¯ 1151// å¤±è´¥ï¼æ¦ç = (1âp)/2ï¼è¡æ¬æ å½ï¼å款 = 0 1152// ç°éæµï¼ç¬¬0å¹´ â馿ï¼ç¬¬1å¹´ âéç¨ç¢æ¬¾ï¼å设éç¨
1152ç¢èæ ¸å¨é¦å¹´å 宿ï¼ï¼ç¬¬ N å¹´ +忬¾ã 1153// IRRï¼å¯¹ NPV(r) = Σ CF_t/(1+r)^t å¨ (â0.99, 10] åºé´äºååè§£ï¼ 1154// ææ IRR ç¨ææå款 E[忬¾] = Î£æ æ¯æ¦çÃ忬¾ ä»£å ¥åä¸ç°éæµåè§£ã 1155// ææåæ¶ç = E[忬¾] â Tã 1156// æ ¸å¯¹ç¨ä¾ï¼æ»æ 1000 ä¸ã4 å¹´å忬¾ 3000 ä¸ â IRR = 3^(1/4)â1 â 31.6%ã 1157 1158let exitSimData = null; 1159let exitSimRunning = false; 1160 1161const PAY_SLIDER_DEFS = [ 1162 { key: 'upfront', label: '馿æèµé¢', min: 0, max: 2000, step: 50, unit: 'ä¸å ' }, 1163 { key: 'milestonePay', label: 'éç¨ç¢ä»æ¬¾æ»é¢', min: 0, max: 3000, step: 50, unit: 'ä¸å ' }, 1164 { key: 'equity', label: 'åè®¡è¡æå æ¯', min: 1, max: 30, step: 1, unit: '%' }, 1165 { key: 'probability', label: 'éç¨ç¢è¾¾ææ¦ç', min: 10, max: 90, step: 5, unit: '%' }, 1166 { key: 'multiple', label: 'éåºä¼°å¼åæ°', min: 1, max: 20, step: 0.5, unit: 'Ã' }, 1167 { key: 'years', label: 'éåºå¹´é', min: 2, max: 8, step: 1, unit: 'å¹´' }, 1168]; 1169 1170function calcIrrBisect(cashflows) { 1171 // cashflows: [{year, amount}]ï¼äºååè§£ NPV(r)=0ï¼æ æ£æ¶çæ¶è¿å null 1172 const npv = rate => cashflows.reduce((sum, cf) => sum + cf.amount / Math.pow(1 + rate, cf.year), 0); 1173 let low = -0.99, high = 10; 1174 if (npv(high) > 0) return 10; // è¶ è¿ 1000% è§ä¸ºå°é¡¶ 1175 if (npv(low) < 0) return null; // å³ä½¿æ¥è¿é¶å©ç乿¶ä¸åï¼è§ä¸ºæ å¯è§£ 1176 for (let i = 0; i < 80; i++) { 1177 const mid = (low + high) / 2; 1178 if (npv(mid) > 0) low = mid; else high = mid; 1179 } 1180 return (low + high) / 2; 1181} 1182 1183function paySimDefaults(data) { 1184 const valuation = Number(data?.base_assumptions?.current_valuation_wan) || 3000; 1185 const uplifts = (data?.milestones || []).map(m => Number(m.valuation_uplift)).filter(v => v > 0).sort((a, b) => a - b); 1186 const probs = (data?.milestones || []).map(m => Number(m.success_probability)).filter(v => v > 0).sort((a, b) => a - b); 1187 const median = arr => arr.length ? arr[Math.floor(arr.length / 2)] : 0; 1188 return { 1189 upfront: Math.max(0, Math.min(2000, Math.round(valuation * 0.15 / 50) * 50)), 1190 milestonePay: Math.max(0, Math.min(3000, Math.round(valuation * 0.25 / 50) * 50)), 1191 equity: 10, 1192 probability: Math.max(10, Math.min(90, Math.round((median(probs) || 0.5) * 100 / 5) * 5)), 1193 multiple: Math.max(1, Math.min(20, median(uplifts) || 3)), 1194 years: Math.max(2, Math.min(8, Number(data?.base_assumptions?.years_to_exit) || 5)), 1195 }; 1196} 1197 1198function initPaySliders(data) { 1199 const box = $('#paySliders'); 1200 if (!box) return; 1201 const defaults = paySimDefaults(data); 1202 box.innerHTML = PAY_SLIDER_DEFS.map(def => { 1203 const value = defaults[def.key]; 1204 return `<label class="pay-slider"><span>${def.label}<b id="payVal_${def.key}">${value}${def.unit}</b></span><input type="range" id="payIn_${def.key}" min="${def.min}" max="${def.max}" step="${def.step}" value="${value}" data-pay-key="${def.key}"></label>`; 1205 }).join(''); 1206} 1207 1208function readPayParams() { 1209 const params = {}; 1210 PAY_SLIDER_DEFS.forEach(def => { 1211 params[def.key] = Number($(`#payIn_${def.key}`)?.value ?? 0); 1212 }); 1213 return params; 1214} 1215 1216function recalcPaySim() { 1217 if (!exitSimData) return; 1218 const p = readPayParams(); 1219 const valuation = Number(exitSimData.base_assumptions?.current_valuation_wan) || 3000; 1220 const equityRatio = p.equity / 100; 1221 const totalInvest = p.upfront + p.milestonePay; // é£é©æå£ = æ»æå ¥ï¼æå¤§äºæï¼ 1222 const successReturn = valuation * p.multiple * equityRatio; // æåï¼ä¼°å¼Ãåæ°éåº 1223 const partialReturn = valuation * equityRatio; // é¨åè¾¾æï¼æå½åä¼°å¼éåº 1224 const pSuccess = p.probability / 100; 1225 const pPartial = (1 - pSuccess) / 2; 1226 const pFail = 1 - pSuccess - pPartial; 1227 const expectedReturn = pSuccess * successReturn + pPartial * partialReturn + pFail * 0; 1228 const expectedNet = expectedReturn - totalInvest; 1229 const cashflows = [ 1230 { year: 0, amount: -p.upfront }, 1231 { year: 1, amount: -p.milestonePay }, 1232 { year: p.years, amount: expectedReturn }, 1233 ]; 1234 const irr = totalInvest > 0 ? calcIrrBisect(cashflows) : null; 1235 $('#payIrr').textContent = irr == null ? 'æ æ³æ¶å' : `${(irr * 100).toFixed(1)}%`; 1236 $('#payIrr').className = irr == null ? 'bad' : irr >= 0.25 ? 'good' : irr >= 0.08 ? 'mid' : 'bad';
1237 $('#payExposure').textContent = `${totalInvest.toLocaleString()} ä¸å `; 1238 $('#payExpect').textContent = `${expectedNet >= 0 ? '+' : ''}${Math.round(expectedNet).toLocaleString()} ä¸å `; 1239 $('#payExpect').className = expectedNet >= 0 ? 'good' : 'bad'; 1240 const fmtReturn = v => `${Math.round(v).toLocaleString()}ä¸`; 1241 const scenarios = [ 1242 { name: 'æåéåº', prob: pSuccess, ret: successReturn, cls: 'good', note: `ä¼°å¼ Ã${p.multiple} éåº` }, 1243 { name: 'é¨åè¾¾æ', prob: pPartial, ret: partialReturn, cls: 'mid', note: 'æå½åä¼°å¼éåº' }, 1244 { name: '失败', prob: pFail, ret: 0, cls: 'bad', note: 'è¡æ¬æ å½' }, 1245 ]; 1246 $('#payScenarios').innerHTML = scenarios.map(sc => { 1247 const net = sc.ret - totalInvest; 1248 return `<div class="pay-scenario ${sc.cls}"><header><span>${sc.name} · æ¦ç ${(sc.prob * 100).toFixed(0)}%</span><b>忬¾ ${fmtReturn(sc.ret)}<i>ï¼å${net >= 0 ? '+' : ''}${Math.round(net).toLocaleString()}ä¸ï¼</i></b></header><div class="pay-bar"><i style="width:${Math.max(2, sc.prob * 100)}%"></i></div><small>${escapeHtml(sc.note)}</small></div>`; 1249 }).join(''); 1250} 1251 1252function renderExitSimulation(data) { 1253 const empty = $('#exitSimEmpty'); 1254 const loading = $('#exitSimLoading'); 1255 const box = $('#exitSimResult'); 1256 const badge = $('#exitSimBadge'); 1257 if (!empty || !box) return; 1258 empty.classList.toggle('hidden', Boolean(data)); 1259 loading.classList.add('hidden'); 1260 box.classList.toggle('hidden', !data); 1261 if (!data) { 1262 badge?.classList.add('hidden'); 1263 return; 1264 } 1265 $('#milestoneTrack').innerHTML = (data.milestones || []).map((ms, index) => ` 1266 <article class="milestone-card"><small>第${index + 1}æ¥ Â· ${escapeHtml(ms.typical_timeline_months)}个æ</small><strong>${escapeHtml(ms.name)}</strong><p>${escapeHtml(ms.stage_note || '')}</p><div><b>Ã${escapeHtml(ms.valuation_uplift)}</b><span>è¾¾ææ¦ç ${Math.round(Number(ms.success_probability || 0) * 100)}%</span></div><i>${escapeHtml(ms.basis || 'è¡ä¸ç»éªå¼')}</i></article> 1267 ${index < data.milestones.length - 1 ? '<em class="milestone-arrow">â</em>' : ''}`).join(''); 1268 $('#exitPathGrid').innerHTML = (data.exit_paths || []).map(path => ` 1269 <article class="exitpath-card"><header><b>${escapeHtml(path.path_type)}</b><i>${escapeHtml(path.evidence_level)}</i></header><strong>${escapeHtml(path.buyer_profile)}</strong><p>${escapeHtml(path.rationale || '')}</p>${path.typical_deal_note ? `<small>${escapeHtml(path.typical_deal_note)}</small>` : ''}</article>`).join(''); 1270 initPaySliders(data); 1271 recalcPaySim(); 1272 const simulatedAt = data.simulated_at ? new Date(data.simulated_at).toLocaleString('zh-CN', { hour12: false }) : ''; 1273 $('#exitSimMeta').textContent = [data.product_type_note, `åèä¼°å¼ä¸å¼ ${Number(data.base_assumptions?.current_valuation_wan || 0).toLocaleString()} ä¸å ï¼${data.base_assumptions?.note || ''}ï¼`, data.data_note, simulatedAt].filter(Boolean).join(' · '); 1274 if (badge) { 1275 badge.textContent = `åèä¼°å¼ ${Number(data.base_assumptions?.current_valuation_wan || 0).toLocaleString()}ä¸`; 1276 badge.className = 'investor-verdict-badge yellow'; 1277 } 1278} 1279 1280async function runExitSimulation() { 1281 if (exitSimRunning) return; 1282 if (!currentInvestmentProject?.id) { showToast('请å éæ©æèµé¡¹ç®'); return; } 1283 exitSimRunning = true; 1284 $('#exitSimEmpty')?.classList.add('hidden'); 1285 $('#exitSimResult')?.classList.add('hidden'); 1286 $('#exitSimLoading')?.classList.remove('hidden'); 1287 try { 1288 exitSimData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/exit-simulation`, { method: 'POST' }); 1289 renderExitSimulation(exitSimData); 1290 } catch (error) { 1291 renderExitSimulation(exitSimData); 1292 showToast(error.message || 'æå³æ¨¡æå¤±è´¥ï¼è¯·ç¨åéè¯'); 1293 } finally { 1294 exitSimRunning = false; 1295 $('#exitSimLoading')?.classList.add('hidden'); 1296 } 1297} 1298 1299document.addEventListener('click', event => { 1300 if (event.target.closest('#exitSimRunButton') || event.target.closest('#exitSimRerunButton')) runExitSimulation(); 1301}); 1302 1303document.addEventListener('input', event => { 1304 const slider = event.target.closest('#paySliders input[type=range]'); 1305 if (!slider) return; 1306 const def = PAY_SLIDER_DEFS.find(item => item.key === slider.dataset.payKey); 1307 const label = $(`#payVal_${slider.dataset.payKey}`); 1308 if (label && def) label.textContent = `${slider.value}${def.unit}`; 1309 recalcPaySim(); 1310}); 1311
1312// ============ æåéç¨ç¢ä»æ¬¾ç®¡çï¼AI åªåºå»ºè®®ï¼æ¹å/驳忰¸è¿äººå·¥ï¼ ============ 1313 1314let milestoneData = null; 1315let milestoneBusy = false; 1316 1317const MILESTONE_STATUS_META = { 1318 pending: { label: 'æªå¯å¨', cls: 'pending' }, 1319 in_progress: { label: 'è¿è¡ä¸', cls: 'in-progress' }, 1320 submitted: { label: 'å¾ æ ¸éª', cls: 'submitted' }, 1321 verified: { label: 'å·²æ ¸éª', cls: 'verified' }, 1322 flagged: { label: 'é»ç夿 ¸', cls: 'flagged' }, 1323 paid: { label: '已仿¬¾', cls: 'paid' }, 1324 suspended: { label: 'æåè¯ä¼°', cls: 'suspended' }, 1325}; 1326 1327const MILESTONE_VERDICT_META = { 1328 achieved: { label: 'æ ¸éªéè¿', cls: 'achieved' }, 1329 partial: { label: 'é¨å符å · é»ç', cls: 'partial' }, 1330 failed: { label: '严éä¸ç¬¦', cls: 'failed' }, 1331 unknown: { label: 'æªè½æ ¸éª', cls: 'unknown' }, 1332}; 1333 1334function renderMilestoneVerifyCard(item) { 1335 const verification = item.verification || {}; 1336 if (!verification.verdict) return ''; 1337 const meta = MILESTONE_VERDICT_META[verification.verdict] || MILESTONE_VERDICT_META.unknown; 1338 const recCls = verification.recommendation === '建议æçº¦ä»æ¬¾' ? 'good' : verification.recommendation === '建议æå仿¬¾å¹¶è¯ä¼°ä¸æ¢' ? 'bad' : 'mid'; 1339 const matched = (verification.matched || []).map(text => `<li>${escapeHtml(text)}</li>`).join(''); 1340 const deviations = (verification.deviations || []).map(dev => `<li><span>${escapeHtml(dev.item)}</span><div><b>è¦æ±ï¼${escapeHtml(dev.expected || 'â')}</b><b>å®é ï¼${escapeHtml(dev.found || 'â')}</b></div></li>`).join(''); 1341 const fatal = (verification.fatal_signals || []).map(text => `<li>${escapeHtml(text)}</li>`).join(''); 1342 const checkedAt = verification.checked_at ? new Date(verification.checked_at).toLocaleString('zh-CN', { hour12: false }) : ''; 1343 const decided = item.decision === 'approved' || item.status === 'paid'; 1344 return `<div class="mstone-verify ${meta.cls}"> 1345 <header><b>${meta.label}</b><span>${escapeHtml(item.evidence_name || '')}${checkedAt ? ` · ${checkedAt}` : ''}</span></header> 1346 <div class="mstone-rec ${recCls}">${escapeHtml(verification.recommendation || 'å»ºè®®äººå·¥å¤æ ¸')}</div> 1347 ${matched ? `<div class="mstone-vblock"><strong>已满足项</strong><ul>${matched}</ul></div>` : ''} 1348 ${deviations ? `<div class="mstone-vblock deviations"><strong>å差项ï¼è¦æ± vs å®é ï¼</strong><ul>${deviations}</ul></div>` : ''} 1349 ${fatal ? `<div class="mstone-vblock fatal"><strong>è´å½ä¿¡å·</strong><ul>${fatal}</ul></div>` : ''} 1350 ${verification.reasoning ? `<p class="mstone-reasoning">${escapeHtml(verification.reasoning)}</p>` : ''} 1351 ${decided 1352 ? '<div class="mstone-decided">å·²æ¹å仿¬¾ï¼ç³»ç»å ç¶æè®°å½ï¼å®é 忬¾é¡»èµ°è´¢å¡æµç¨ï¼</div>' 1353 : `<div class="mstone-decide"><button class="analyze-button" type="button" data-mstone-approve="${escapeHtml(item.id)}">æ¹å仿¬¾</button><button class="outline-button" type="button" data-mstone-reject="${escapeHtml(item.id)}">驳å / åèµ·äººå·¥å¤æ ¸</button></div>`} 1354 </div>`; 1355} 1356 1357function renderMilestones(data) { 1358 const empty = $('#milestoneEmpty'); 1359 const content = $('#milestoneContent'); 1360 if (!empty || !content) return; 1361 const items = data?.milestones || []; 1362 empty.classList.toggle('hidden', items.length > 0); 1363 content.classList.toggle('hidden', items.length === 0); 1364 if (!items.length) return; 1365 const summary = data.summary || {}; 1366 $('#milestoneSummary').innerHTML = [ 1367 ['æ»ä»æ¬¾é¢', `${Number(summary.total_payment_wan || 0).toLocaleString()} ä¸`], 1368 ['å·²æ¹å', `${summary.approved_count || 0} ç¬`], 1369 ['å¾ æ ¸éª', `${summary.pending_verify_count || 0} ç¬`], 1370 ['é»ç', `${summary.flagged_count || 0} ç¬`], 1371 ].map(([label, value]) => `<article><small>${label}</small><b>${value}</b></article>`).join(''); 1372 $('#mstoneList').innerHTML = items.map(item => { 1373 const meta = MILESTONE_STATUS_META[item.status] || MILESTONE_STATUS_META.pending; 1374 const canUpload = item.status !== 'paid'; 1375 return `<article class="mstone-row" data-mid="${escapeHtml(item.id)}"> 1376 <header>
1377 <span class="mstone-seq">${String(item.seq || 0).padStart(2, '0')}</span> 1378 <div class="mstone-head-main"><strong>${escapeHtml(item.name)}</strong>${item.criteria ? `<p>${escapeHtml(item.criteria)}</p>` : ''}</div> 1379 <div class="mstone-meta"><small>计å ${escapeHtml(item.planned_months || 'â')} 个æ</small><label>仿¬¾ <input type="number" min="0" step="10" value="${escapeHtml(item.payment_wan ?? 0)}" data-mstone-pay="${escapeHtml(item.id)}" ${item.status === 'paid' ? 'disabled' : ''}> ä¸</label></div> 1380 <span class="mstone-status ${meta.cls}">${meta.label}</span> 1381 </header> 1382 ${canUpload ? `<div class="mstone-actions"><label class="outline-button mstone-upload">ä¸ä¼ è¾¾æè¯æ® â<input type="file" class="hidden" data-mstone-upload="${escapeHtml(item.id)}" accept=".pdf,.docx,.txt"></label><small>æ¯æ PDF / Word / TXTï¼AI æ ¸å¯¹åä» ç»åºä»æ¬¾å»ºè®®</small></div>` : ''} 1383 ${renderMilestoneVerifyCard(item)} 1384 </article>`; 1385 }).join(''); 1386} 1387 1388async function loadMilestones() { 1389 if (!currentInvestmentProject?.id) { 1390 milestoneData = null; 1391 renderMilestones(null); 1392 return; 1393 } 1394 try { 1395 milestoneData = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/milestones`); 1396 } catch { 1397 milestoneData = null; 1398 } 1399 renderMilestones(milestoneData); 1400} 1401 1402async function importMilestonesFromSimulation() { 1403 if (milestoneBusy || !currentInvestmentProject?.id) return; 1404 milestoneBusy = true; 1405 $('#milestoneLoadingText').textContent = 'æ£å¨ä»æå³æ¨¡æå¯¼å ¥éç¨ç¢â¦'; 1406 $('#milestoneLoading')?.classList.remove('hidden'); 1407 try { 1408 const result = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/milestones/import-simulation`, { method: 'POST' }); 1409 milestoneData = result; 1410 renderMilestones(milestoneData); 1411 if (result.note) showToast(result.note); 1412 } catch (error) { 1413 showToast(error.message || 'å¯¼å ¥å¤±è´¥ï¼è¯·å å¨ãâ æå³æ¨¡æãå®ææ¨æ¼'); 1414 } finally { 1415 milestoneBusy = false; 1416 $('#milestoneLoading')?.classList.add('hidden'); 1417 } 1418} 1419 1420async function addMilestoneManually() { 1421 if (!currentInvestmentProject?.id) { showToast('请å éæ©æèµé¡¹ç®'); return; } 1422 const name = window.prompt('éç¨ç¢åç§°ï¼å¦ï¼æ³¨åæ£éªï¼åæ£ï¼éè¿ï¼'); 1423 if (!name || !name.trim()) return; 1424 const criteria = window.prompt('è¾¾æ æ åï¼é项忏 å ³é®ææ ï¼å¦ï¼æ ·æ¬éâ¥200ä¾ï¼çµæåº¦â¥90%ï¼ç¹å¼åº¦â¥85%ï¼') || ''; 1425 const months = Number(window.prompt('计åå¨æï¼æï¼', '12') || 0); 1426 const payment = Number(window.prompt('å ³è仿¬¾é¢ï¼ä¸å ï¼', '100') || 0); 1427 try { 1428 const result = await investmentApiRequest(`/api/investment/projects/${currentInvestmentProject.id}/milestones`, { 1429 method: 'POST', 1430 headers: { 'Content-Type': 'application/json' }, 1431 body: JSON.stringify({ name: name.trim(), criteria, planned_months: months, payment_wan: payment }), 1432 }); 1433 milestoneData = result; 1434 renderMilestones(milestoneData); 1435 } catch (error) { 1436 showToast(error.message || 'æ·»å 失败'); 1437 } 1438} 1439 1440async function updateMilestoneField(mid, payload, toastMessage) { 1441 try { 1442 const result = await investmentApiRequest(`/api/investment/milestones/${mid}`, { 1443 method: 'PUT', 1444 headers: { 'Content-Type': 'application/json' }, 1445 body: JSON.stringify(payload), 1446 }); 1447 milestoneData = result; 1448 renderMilestones(milestoneData); 1449 if (toastMessage) showToast(toastMessage); 1450 } catch (error) { 1451 showToast(error.message || 'ä¿å失败'); 1452 } 1453} 1454 1455async function uploadMilestoneEvidence(mid, file) { 1456 if (milestoneBusy || !file) return; 1457 milestoneBusy = true; 1458 $('#milestoneLoadingText').textContent = 'AI æ£å¨æ ¸å¯¹è¯æ®ææ â¦'; 1459 $('#milestoneLoading')?.classList.remove('hidden'); 1460 try { 1461 const form = new FormData(); 1462 form.append('file', file); 1463 const result = await investmentApiRequest(`/api/investment/milestones/${mid}/verify`, { method: 'POST', body: form }); 1464 milestoneData = { milestones: null, summary: null }; 1465 await loadMilestones(); // verify è¿ååæ¡ï¼ç»ä¸æå ¨éä¿è¯æ±æ»ä¸è´ 1466 if (result?.degraded) showToast(result.note || 'è¯æ®å·²åæ¡£ï¼
1466éäººå·¥å¤æ ¸'); 1467 } catch (error) { 1468 showToast(error.message || 'è¯æ®æ ¸éªå¤±è´¥ï¼è¯·ç¨åéè¯'); 1469 } finally { 1470 milestoneBusy = false; 1471 $('#milestoneLoading')?.classList.add('hidden'); 1472 } 1473} 1474 1475document.addEventListener('click', event => { 1476 if (event.target.closest('#milestoneImportButton')) { importMilestonesFromSimulation(); return; } 1477 if (event.target.closest('#milestoneAddButton') || event.target.closest('#milestoneAddMoreButton')) { addMilestoneManually(); return; } 1478 const approve = event.target.closest('[data-mstone-approve]'); 1479 if (approve) { 1480 updateMilestoneField(approve.dataset.mstoneApprove, { status: 'paid', decision: 'approved' }, 'å·²æ è®°æ¹å仿¬¾ââä» ä¸ºç³»ç»å ç¶æè®°å½ï¼å®é 忬¾é¡»èµ°è´¢å¡æµç¨'); 1481 return; 1482 } 1483 const reject = event.target.closest('[data-mstone-reject]'); 1484 if (reject) { 1485 updateMilestoneField(reject.dataset.mstoneReject, { decision: 'under_review' }, 'å·²è½¬ä¸ºäººå·¥å¤æ ¸ï¼ä»æ¬¾æµç¨æå'); 1486 } 1487}); 1488 1489document.addEventListener('change', event => { 1490 const upload = event.target.closest('[data-mstone-upload]'); 1491 if (upload) { 1492 const file = upload.files && upload.files[0]; 1493 if (file) uploadMilestoneEvidence(upload.dataset.mstoneUpload, file); 1494 upload.value = ''; 1495 return; 1496 } 1497 const pay = event.target.closest('[data-mstone-pay]'); 1498 if (pay) { 1499 updateMilestoneField(pay.dataset.mstonePay, { payment_wan: Number(pay.value || 0) }); 1500 } 1501}); 1502 1503function investmentAssistantStorageKey() { 1504 // v2 starts a clean UTF-8 conversation store; earlier replies may have been saved after ANSI mis-decoding. 1505 return currentInvestmentProject?.id ? `huawai_bp_assistant_utf8_v2_${currentInvestmentProject.id}` : ''; 1506} 1507 1508function loadInvestmentAssistantConversation() { 1509 investmentAssistantMessages = []; 1510 const key = investmentAssistantStorageKey(); 1511 if (!key) return; 1512 try { 1513 const saved = JSON.parse(localStorage.getItem(key) || '[]'); 1514 if (Array.isArray(saved)) investmentAssistantMessages = saved.slice(-30); 1515 } catch { investmentAssistantMessages = []; } 1516} 1517 1518function saveInvestmentAssistantConversation() { 1519 const key = investmentAssistantStorageKey(); 1520 if (!key) return; 1521 try { localStorage.setItem(key, JSON.stringify(investmentAssistantMessages.slice(-30))); } catch { /* device storage may be unavailable */ } 1522} 1523 1524function compactInvestmentAssistantContext() { 1525 const report = currentInvestmentAnalysis || {}; 1526 const dimensions = (Array.isArray(report.dimensions) ? report.dimensions : []).map(item => ({ 1527 name:item.name,score:item.score,potential_score:item.potential_score,score_range:item.score_range,evidence_coverage:item.evidence_coverage,score_reason:item.score_reason || '',potential_judgment:item.potential_judgment || '',confidence:item.confidence || item.score_audit?.confidence || '',validation_tasks:(item.validation_tasks || []).slice(0,4),strengths:(item.strengths || []).slice(0,4),weaknesses:(item.weaknesses || []).slice(0,4), 1528 evidence:item.evidence || '',criteria:(item.score_audit?.criteria || []).slice(0,4).map(criterion => ({criterion:criterion.criterion,status:criterion.status,reason:criterion.reason,evidence_quote:criterion.evidence_quote,evidence_locator:criterion.evidence_locator,data_status:criterion.data_status})), 1529 benchmark_evidence:item.benchmark_evidence ? {score_explanation:item.benchmark_evidence.score_explanation,score_label:item.benchmark_evidence.score_label,good_standard:(item.benchmark_evidence.good_standard || []).slice(0,4),current_gap:(item.benchmark_evidence.current_gap || []).slice(0,4),external_references:(item.benchmark_evidence.external_references || []).slice(0,5).map(reference => ({label:reference.label,value:reference.value,period:reference.period,source:reference.source,url:reference.url,scope_note:reference.scope_note}))} : {} 1530 })); 1531 const workbench = report.decision_workbench || {}; 1532 const references = report.reference_data || {}; 1533 return { 1534 project:{id:currentInvestmentProject?.id || '',name:currentInvestmentProject?.name || report.project_name || 'æªå½å项ç®',source_type:currentInvestmentProject?.source_type || '',track:currentInvestmentProject?.track || '',stage:currentInvestmentProject?.stage || '',owner:currentInvestmentProject?.owner || '',notes:currentInvestmentProject?.notes || '',bp_file:currentInvestmentProject?.latest_file_name || '',report_id:currentInvestmentReportId || ''}, 1535 analysis:{overall_score:report.overall_score,potential_score:report.potential_score,score_range:report.score_range,evidence_coverage:report.evidence_coverage,evidence_confidence:report.evidence_confidence,recommendation:report.recommendation,summary:report.summary,dimensions,risks:(report.risks || []).slice(0,12),product_identity:workbench.product_identity || {},decision_gates:(workbench.gates || []).slice(0,8),deepening_questions:(workbench.questions || []).slice(0,10),registration_cost_estimate:workbench.registration_cost_estimate || {},reference_data:{as_of:references.as_of,product_scope:references.product_scope,market:(references.market || []).slice(0,8),registration_costs:(references.registration_costs || []).slice(0,8),official_databases:(references.official_databases || []).slice(0,8)},analysis_audit:report.analysis_audit || {}}, 1536 current_scenario:{market_target_units:Number($('#marketTargetUnits')?.value || 0
1536),market_annual_usage:Number($('#marketAnnualUsage')?.value || 0),market_unit_price:Number($('#marketUnitPrice')?.value || 0),market_reach_rate:Number($('#marketReachRate')?.value || 0),market_share_rate:Number($('#marketShareRate')?.value || 0),registration_budget_total:$('#registrationBudgetTotal')?.textContent || ''} 1537 }; 1538} 1539 1540function investmentAssistantAnswerHtml(value) { 1541 const safe = escapeHtml(value || 'æ¬è½®æ²¡æå½¢æå¯å±ç¤ºçåçã'); 1542 return `<p>${safe.replace(/\n{2,}/g,'</p><p>').replace(/\n/g,'<br>')}</p>`; 1543} 1544 1545function renderInvestmentAssistantMessages() { 1546 const container = $('#investmentAssistantMessages'); 1547 if (!container) return; 1548 if (!investmentAssistantMessages.length) { 1549 const score = currentInvestmentAnalysis?.overall_score; 1550 const dimensions = currentInvestmentAnalysis?.dimensions?.length || 0; 1551 container.innerHTML = `<div class="investment-assistant-welcome"><i>M</i><div><strong>æå·²ç»è¯»è¿è¿ä¸ªé¡¹ç®çä»·å¼ç å¤ç»æ</strong><p>å½åè½½å ¥ä¹ç»´é¡¹ç®æ½å${score == null ? '' : ` ${escapeHtml(score)} / 10`}ãåç»´å¤æåºé´ãBPè¯æ®è¦ç度ãé£é©æ¸ ååæ³¨åææ¬è§åãæ¨å¯ä»¥ç´æ¥è¿½é®ï¼ä¸å¿ éæ°æè¿°é¡¹ç®ã</p><span>建议å é®ï¼âè¿ä¸ªé¡¹ç®çæ£çåä¸ä»·å¼å¨åªéï¼âæâ妿è¿å ¥å°½è°ï¼æªæ¥30天å éªè¯ä»ä¹ï¼â</span></div></div>`; 1552 return; 1553 } 1554 container.innerHTML = investmentAssistantMessages.map(message => { 1555 if (message.role === 'user') return `<article class="investment-assistant-message user"><header><span>æ</span><small>${escapeHtml(message.time || '')}</small></header><div>${investmentAssistantAnswerHtml(message.content)}</div></article>`; 1556 const result = message.content || {}; 1557 const points = (result.key_points || []).map(point => `<li><strong>${escapeHtml(point.title || 'å ³é®å¤æ')}</strong><p>${escapeHtml(point.detail || '')}</p><span>${escapeHtml(point.basis || 'AI夿')}</span></li>`).join(''); 1558 const evidence = (result.evidence_refs || []).map(item => `<article><span>${escapeHtml(item.status || 'å¾ æ ¸éª')}</span><strong>${escapeHtml(item.label || '便®')}</strong><p>${escapeHtml(item.quote || '')}</p><small>${escapeHtml(item.locator || 'ä½ç½®å¾ 确认')}</small></article>`).join(''); 1559 const tasks = (result.verification_tasks || []).map(item => `<li><i>${escapeHtml(item.priority || 'ä»»å¡')}</i><span><strong>${escapeHtml(item.task || '')}</strong><small>${escapeHtml(item.source || 'æ¥æºå¾ æç¡®')}</small></span></li>`).join(''); 1560 const followups = (result.followup_questions || []).map(question => `<button type="button" data-bp-assistant-question="${escapeHtml(question)}">${escapeHtml(question)}</button>`).join(''); 1561 const errorClass = message.error ? ' error' : ''; 1562 return `<article class="investment-assistant-message assistant${errorClass}"><header><span>M</span><small>${escapeHtml(message.model || (message.error ? 'è¿æ¥æç¤º' : 'DeepSeek'))} · ${escapeHtml(message.time || '')}</small></header><div class="investment-assistant-answer">${investmentAssistantAnswerHtml(result.answer || String(result))}${points ? `<ul class="investment-assistant-points">${points}</ul>` : ''}${evidence ? `<div class="investment-assistant-evidence"><strong>æ¬è½®å¼ç¨ä¾æ®</strong><div>${evidence}</div></div>` : ''}${tasks ? `<div class="investment-assistant-tasks"><strong>å»ºè®®æ ¸éªä»»å¡</strong><ul>${tasks}</ul></div>` : ''}${result.boundary ? `<p class="investment-assistant-boundary">è¾¹çï¼${escapeHtml(result.boundary)}</p>` : ''}${followups ? `<div class="investment-assistant-followups"><span>ç»§ç»è¿½é®</span>${followups}</div>` : ''}</div></article>`; 1563 }).join(''); 1564 requestAnimationFrame(() => { container.scrollTop = container.scrollHeight; }); 1565} 1566 1567function refreshInvestmentAssistant() {
1568 const panel = $('#investmentAssistant'); 1569 if (!panel) return; 1570 const ready = Boolean(currentInvestmentProject && currentInvestmentAnalysis); 1571 panel.classList.toggle('hidden', !ready); 1572 if (!ready) return; 1573 $('#investmentAssistantProject').textContent = currentInvestmentProject.name || 'å½å项ç®'; 1574 const audit = currentInvestmentAnalysis.analysis_audit || {}; 1575 $('#investmentAssistantContext').textContent = `ä¹ç»´ ${currentInvestmentAnalysis.overall_score ?? 'â'} / 10 · ${currentInvestmentProject.latest_file_name || 'å½ååææ¥å'}`; 1576 $('#investmentAssistantAudit').textContent = audit.model ? `å½åæ¥åï¼${audit.model} · å©çåçä¼åç¬è®°å½æ¬æ¬¡æ¨¡å` : 'å½åæ¥åå·²è½½å ¥ · å©çåç伿 æ³¨æ°æ®è¾¹ç'; 1577 renderInvestmentAssistantMessages(); 1578} 1579 1580async function sendInvestmentAssistantQuestion(preset = '') { 1581 if (investmentAssistantBusy) return; 1582 if (!currentInvestmentProject || !currentInvestmentAnalysis) { showToast('请å 鿩项ç®å¹¶å®æBPåæ'); return; } 1583 const input = $('#investmentAssistantInput'); 1584 const question = String(preset || input.value || '').trim(); 1585 if (!question) { input.focus(); return; } 1586 const history = investmentAssistantMessages.slice(-10).map(message => ({role:message.role,content:message.role === 'user' ? message.content : message.content?.answer || ''})); 1587 investmentAssistantMessages.push({role:'user',content:question,time:new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})}); 1588 input.value = ''; 1589 investmentAssistantBusy = true; 1590 $('#investmentAssistantSend').disabled = true; 1591 $('#investmentAssistantThinking').classList.remove('hidden'); 1592 saveInvestmentAssistantConversation(); 1593 renderInvestmentAssistantMessages(); 1594 try { 1595 const assistantUrl = window.location.protocol === 'file:' ? 'http://127.0.0.1:8787/api/investment/assistant' : '/api/investment/assistant'; 1596 const response = await fetch(assistantUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({question,history,context:compactInvestmentAssistantContext()})}); 1597 const payload = await response.json(); 1598 if (!response.ok) throw new Error(payload.error || payload.detail || '项ç®åæå©çææ¶æ æ³åç'); 1599 investmentAssistantMessages.push({role:'assistant',content:payload,time:new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'}),model:payload._audit?.model || 'DeepSeek'}); 1600 $('#investmentAssistantAudit').textContent = payload._audit?.request_id ? `æ¬è½®å·²å®é è°ç¨ ${payload._audit.model || 'DeepSeek'} · è®°å½ ${String(payload._audit.request_id).slice(0,12)}â¦` : 'æ¬è½®åç已宿 · å ³é®äºå®ä»éäººå·¥æ ¸éª'; 1601 } catch (error) { 1602 investmentAssistantMessages.push({role:'assistant',error:true,content:{answer:error.message,boundary:'å½åé®é¢æ²¡æçæAIç»è®ºï¼è¯·æ£æ¥ç»ä¸å¹³å°åDeepSeeké ç½®åéè¯ã'},time:new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})}); 1603 } finally { 1604 investmentAssistantBusy = false; 1605 $('#investmentAssistantSend').disabled = false; 1606 $('#investmentAssistantThinking').classList.add('hidden'); 1607 saveInvestmentAssistantConversation(); 1608 renderInvestmentAssistantMessages(); 1609 input.focus(); 1610 } 1611} 1612 1613function clearInvestmentAssistantConversation() { 1614 if (!currentInvestmentProject) return; 1615 investmentAssistantMessages = []; 1616 const key = investmentAssistantStorageKey(); 1617 if (key) localStorage.removeItem(key); 1618 renderInvestmentAssistantMessages(); 1619 showToast('å½å项ç®è®¨è®ºå·²æ¸ 空'); 1620} 1621 1622function fallbackScoreCriteria(item) { 1623 const hasEvidence = item.evidence && item.evidence !== 'BP䏿ªæåç¸å ³å 容'; 1624 return [{ 1625 criterion:'BPæææ¯å¦æä¾ç¸å ³ä¾æ®',max_points:10,awarded_points:hasEvidence ? Number(item.score)||5 : null, 1626 status:hasEvidence ? 'å¾ ç»æåæ ¸éª' : 'å¾ éªè¯', 1627 reason:hasEvidence ? 'å½åæ¯å岿¥åææè¦åçï¼å»ºè®®éæ°ä¸ä¼ æä»¶çæé项弿ã' : 'BPæªæè¿°è¯¥é¡¹ï¼å½åæ æ³å¤æï¼æªç¥ä¸çäºé¡¹ç®æ²¡æï¼ä¸å æ¤ç´æ¥è®°é¶åã', 1628 evidence_quote:hasEvidence ? item.evidence : '',evidence_locator:'å岿¥åæªè®°å½ä½ç½®', 1629 data_status:hasEvidence ? 'æææéè¿°ï¼å°æªå®æéé¡¹è¯æ®å®¡æ ¸' : 'æªç¥ï¼è½¬å ¥å°½è°æ ¸éª' 1630 }]; 1631} 1632 1633function renderDimensionEvidence(item, index, shouldScroll = false) { 1634 if (!item) return; 1635 const audit = item.score_audit || {}; 1636 const criteria = Array.isArray(audit.criteria) && audit.criteria.length ? audit.criteria : fallbackScoreCriteria(item); 1637 const modelScore = item.model_score_before_audit; 1638 const strengths = Array.isArray(item.strengths) ? item.strengths : []; 1639 const weaknesses = Array.isArray(item.weaknesses) ? item.weaknesses : []; 1640 const criteriaHtml = criteria.map(criterion => { 1641 const statusClass = criterion.status === 'å¯è¿½æº¯çº¿ç´¢' ? 'strong' : criterion.status === 'éåæ«é²' ? 'quantified' : ['缺失','å¾ éªè¯'].includes(criterion.status) ? 'missing' : 'statement'; 1642 const quote = criterion.evidence_quote 1643 ? `<blockquote><b>${escapeHtml(criterion.evidence_locator || 'åæä½ç½®å¾ 确认')}</b><span>â${escapeHtml(criterion.evidence_quote)}â</span></blockquote>` 1644 : '<blockquote class="no-evidence"><span>BPæªæè¿°è¯¥é¡¹ï¼æªç¥ä¸çäºé¡¹ç®æ²¡æ</span></blockquote>'; 1645 const awarded = criterion.awarded_points === null || criterion.awarded_points === undefined ? 'å¾ éªè¯' : escapeHtml(criterion.awarded_points); 1646 return `<article class="evidence-criterion ${statusClass}"><header><span><strong>${escapeHtml(criterion.criterion)}</strong><small>${escapeHtml(criterion.data_status || '')}</small></span><b>${awarded} / ${escapeHtml(criterion.max_points ?? 'â')}å</b></header>${quote}<p>${escapeHtml(criterion.reason || '')}</p><i>${escapeHtml(criterion.status || 'å¾ æ ¸éª')}</i></article>`; 1647 }).join(''); 1648 const positiveHtml = strengths.length ? strengths.map(text => `<li>${escapeHtml(text)}</li>`).join('') : '<li>å½åå°æªå½¢ææå åè¯æ®æ¯æçæ£åä»·å¼å¤æ</li>'; 1649 const negativeHtml = weaknesses.length ? weaknesses.map(text => `<li>${escapeHtml(text)}</li>`).join('') : '<li>ææ æç¡®è´é¢äºå®ï¼ä¿¡æ¯ç¼ºå°çé¨ååä¸ºå¾ éªè¯</li>'; 1650 const benchmark = item.benchmark_evidence || {}; 1651 const externalReferences = Array.isArray(benchmark.external_references) ? benchmark.external_references : []; 1652 const goodStandard = Array.isArray(benchmark.good_standard) ? benchmark.good_standard : []; 1653 const currentGap = Array.isArray(benchmark.current_gap) ? benchmark.current_gap : []; 1654 const referenceHtml = externalReferences.length ? externalReferences.map(reference => { 1655 const value = reference.value ? `<b>${escapeHtml(reference.value)}</b>` : '<b>宿¹æ ¸éªå ¥å£</b>'; 1656 const period = reference.period ? `<em>${escapeHtml(reference.period)}</em>` : ''; 1657 const note = reference.scope_note || reference.use || reference.basis || 'ç¨äºè¡ä¸åºåæ ¸éªï¼ä¸ç´æ¥æ¿ä»£æ¬é¡¹ç®äºå®'; 1658 const source = reference.source || reference.source_status || 'å ¬å¼æ¥æº'; 1659 return `<article class="dimension-reference-card"><header><span>${escapeHtml(reference.source_status || 'å¯è¿½æº¯æ¥æº')}</span>
1659${period}</header><strong>${escapeHtml(reference.label || 'è¡ä¸åè')}</strong>${value}<p>${escapeHtml(note)}</p>${reference.url ? `<a href="${escapeHtml(reference.url)}" target="_blank" rel="noopener">æ¥çæ¥æº · ${escapeHtml(source)} â</a>` : `<small>${escapeHtml(source)}</small>`}</article>`; 1660 }).join('') : '<div class="dimension-reference-empty"><strong>å°æ 䏿¬é¡¹ç®ç´æ¥å¹é çå¤é¨æ°å</strong><p>ç³»ç»ä¸ä¼ç¨å¤§è¡ä¸æ°æ®åçæ¡ï¼éè¦æäº§ååç§°ãéåºçåç®æ 客æ·ç»§ç»æ£ç´¢ã</p></div>'; 1661 const goodStandardHtml = goodStandard.length ? goodStandard.map(text => `<li>${escapeHtml(text)}</li>`).join('') : '<li>éè¦ç±å¯¹åºä¸ä¸è´è´£äººç¡®å®æ¬èµéçè¾¾æ å£å¾ã</li>'; 1662 const currentGapHtml = currentGap.length ? currentGap.map(text => `<li>${escapeHtml(text)}</li>`).join('') : '<li>å½åæªè¯å«åºæç¡®å·®è·ï¼ä»éå¯¹ç §åå§ææåå¤é¨æ¥æºå¤æ ¸ã</li>'; 1663 const panel = $('#dimensionEvidencePanel'); 1664 if (!panel) return; 1665 const range = item.score_range || {low:item.score,high:item.score}; 1666 const coverage = Number(item.evidence_coverage ?? audit.evidence_coverage ?? 0); 1667 panel.innerHTML = `<div class="dimension-evidence-head"><div><span>å½å维度 · é¡¹ç®æ½å</span><h3>${escapeHtml(item.name)} · ${escapeHtml(item.score)}å</h3><p>${escapeHtml(item.score_reason || 'è¿æ¯å¯¹é¡¹ç®ä»·å¼ç忥夿ï¼BPæªæè¿°çå 容å°ä½ä¸ºå¾ éªè¯é¡¹ï¼ä¸ç´æ¥è®°é¶åã')}</p></div><div><strong>è¯æ®ç½®ä¿¡åº¦ ${escapeHtml(audit.confidence || 'å¾ æ ¸éª')}</strong><small>åç夿åºé´ ${escapeHtml(range.low)}â${escapeHtml(range.high)}å · BPè¯æ®è¦ç${coverage}%</small><b>${escapeHtml(audit.method || 'é¡¹ç®æ½åè¯å + è¯æ®ç½®ä¿¡åº¦å轨ç å¤')}</b><button class="dimension-back-button" type="button">â è¿åä¹ç»´ç å¤</button></div></div><section class="dimension-score-explanation"><span>为ä»ä¹æ¯è¿ä¸ªå</span><strong>${escapeHtml(benchmark.score_explanation || item.potential_judgment || 'å½å夿æ¥èªBPäºå®ã项ç®ä»·å¼æ¨çä¸è¯æ®ç½®ä¿¡åº¦ç»¼åç»æã')}</strong><p>${escapeHtml(benchmark.score_label || 'åæ°ç¨äºå³å®æ¯å¦å¼å¾ç»§ç»éªè¯ï¼ä¸ä»£è¡¨æç»æèµç»è®ºã')}</p></section><div class="evidence-criteria-grid">${criteriaHtml}</div><div class="dimension-reason-columns"><article><strong>æ¯æé¡¹ç®ä»·å¼çä¿¡å·</strong><ul>${positiveHtml}</ul></article><article><strong>é£é©ä¸å¾ éªè¯å设</strong><ul>${negativeHtml}</ul></article></div><section class="dimension-network-evidence"><header><div><span>ç½ç»ä¸è¡ä¸åè</span><strong>æ¯æ¡æ°æ®é½æ¾ç¤ºæ¥æºãæ¥æä¸éç¨å£å¾</strong></div><b>${externalReferences.length} æ¡å¯è¿½æº¯ä¾æ®</b></header><div>${referenceHtml}</div></section><div class="dimension-benchmark-columns"><article><span>好项ç®åºè¯¥æ¯ä»ä¹æ ·</span><ul>${goodStandardHtml}</ul></article><article><span>æ¬é¡¹ç®è·ç¦»è¾¾æ è¿å·®ä»ä¹</span><ul>${currentGapHtml}</ul></article></div><p class="dimension-source-boundary">${escapeHtml(benchmark.source_boundary || 'å¤é¨èµæåªæä¾è¡ä¸åºååæ ¸éªå£å¾ï¼ä¸æ¿ä»£æ¬é¡¹ç®BPäºå®ã')}</p><p class="dimension-boundary">${escapeHtml(audit.boundary || 'é¡¹ç®æ½ååç¨äºå¤ææ¯å¦å¼å¾ç»§ç»éªè¯ï¼BPè¯æ®è¦ç度å³å®ç»è®ºç½®ä¿¡åº¦ï¼ææç¼ºé¡¹ä¸çå项ç®ç¼ºé·ã')}</p>`; 1668 $$('.dimension-score-card').forEach((card,cardIndex) => card.classList.toggle('active', cardIndex === index)); 1669 if (shouldScroll) { 1670 panel.classList.remove('focus-pulse'); 1671 requestAnimationFrame(() => { 1672 panel.scrollIntoView({behavior:'smooth',block:'start'}); 1673 panel.focus({preventScroll:true}); 1674 panel.classList.add('focus-pulse'); 1675 setTimeout(() => panel.classList.remove('focus-pulse'), 1200); 1676 }); 1677 } 1678} 1679 1680function startBpAnalysisAnimation() { 1681 const loader = $('#bpAnalysisLoader'); 1682 const steps = [ 1683 ['æ£å¨è¯»å BP å ¨æ', 'è§£æ PDFãWord æ PPT çæ£æä¸é¡µé¢ç»æ'], 1684 ['æ£å¨æå项ç®è¯æ®', 'è¯å«äº§åã临åºéæ±ã注åè·¯å¾ãå¢éä¸èèµä¿¡æ¯'], 1685 ['æ£å¨è°ç¨ DeepSeek', 'å°æä»¶å ¨æéå ¥ä¹ç»´ä¸ä¸å¤æï¼å¹¶ä¿çå®é è°ç¨è®°å½'], 1686 ['æ£å¨æ ¸å¯¹æ°æ®è¾¹ç', 'åºå BP åæãAI 夿ä¸å¸¦æ¥æºçå¤é¨åè'], 1687 ['æ£å¨çæè¯æµæ¥å', 'æ´çè¯åãå ³é®é£é©åä¸ä¸è½®é«ä»·å¼é®é¢'] 1688 ]; 1689 let index = 0; 1690 let progress = 8; 1691 clearInterval(bpLoaderTimer); 1692 loader.classList.remove('hidden', 'failed', 'done'); 1693 $('#bpLoaderProgress').style.width = `${progress}%`; 1694 const update = () => { 1695 $('#bpLoaderTitle').textContent = steps[index][0]; 1696 $('#bpLoaderStep').textContent = steps[index][1]; 1697 $$('#bpLoaderStages li').forEach((item, itemIndex) => { 1698 item.classList.toggle('active', itemIndex === index); 1699 item.classList.toggle('complete', itemIndex < index); 1700 }); 1701 }; 1702 update(); 1703 bpLoaderTimer = setInterval(() => { 1704 progress = Math.min(92, progress + 5 + Math.random() * 7); 1705 $('#bpLoaderProgress').style.width = `${progress}%`; 1706 if (index < steps.length - 1 && progress >= 18 + index * 17) index += 1; 1707 update(); 1708 }, 1800); 1709} 1710 1711function finishBpAnalysisAnimation(success, message) { 1712 clearInterval(bpLoaderTimer); 1713 bpLoaderTimer = null; 1714 const loader = $('#bpAnalysisLoader'); 1715 loader.classList.toggle('done', success); 1716 loader.classList.toggle('failed', !success); 1717 $('#bpLoaderProgress').style.width = '100%'; 1718 $('#bpLoaderTitle').textContent = success ? 'åæå®æ' : 'åææªå®æ'; 1719 $('#bpLoaderStep').textContent = message || (success ? 'å·²çææ¥ååè°ç¨è®°å½' : 'è¯·æ£æ¥æå¡åéè¯'); 1720 $$('#bpLoaderStages li').forEach(item => item.classList.toggle('complete', success)); 1721 setTimeout(() => loader.classList.add('hidden'), success ? 900 : 2600); 1722} 1723 1724function defaultDecisionWorkbench(productScope = 'å»ç卿¢°ï¼ç»åç±»å«å¾ 确认ï¼') { 1725 const isIvd = /IVD|è¯å|ä½å¤è¯æ/i.test(productScope); 1726 const isSoftware = /软件|AI/i.test(productScope); 1727 return { 1728 product_identity: { 1729 candidate_name: isIvd ? 'å¼å¸éç å使£æµè¯åï¼åééç¨åç§°ï¼' : isSoftware ? 'å»ç软件ï¼éç¨åç§°å¾ ç¡®è®¤ï¼' : '产åéç¨åç§°å¾ ç¡®è®¤', 1730 product_scope: productScope, 1731 intended_use: 'éä»BPåæç¡®è®¤ç®æ äººç¾¤ãæ ·æ¬/é¨ä½ã使ç¨åºæ¯å临åºç®ç', 1732 regulatory_hypothesis: isIvd ? 'ä½å¤è¯æè¯åï¼ç¬¬äº/ä¸ç±»å¾ åç±»çå®' : 'å»ç卿¢°ç±»å«ä¸åç±»ç¼ç å¾ æ³è§è´è´£äººç¡®è®¤', 1733 confidence: 'åæ¥æ¨æ' 1734 }, 1735 gates: [ 1736 {name:'产åå®ä¹',status:'å¾ ç¡®è®¤',note:'éå®éç¨åç§°ä¸é¢æç¨é'}, 1737 {name:'临åºä»·å¼',status:'å¾ è¯æ®',note:'æ ¸éªæªæ»¡è¶³éæ±ä¸è·çç»ç¹'}, 1738 {name:'注åè·¯å¾',status:'å¾ æ³è§',note:'确认åç±»ãåå å临åºè·¯å¾'}, 1739 {name:'å类产å',status:'å¾ æ£ç´¢',note:'å½¢æå®æ¹æ³¨åè¯æ¸ å'}, 1740 {name:'å䏿¨¡å',status:'å¾ æµç®',note:'ç¨ç宿°éä¸ä»·æ ¼æµç®'} 1741 ], 1742 comparable_queries: isIvd ? ['å¼å¸éç åä½ + å¤èæ£ + è¯å','å¼å¸éç åä½ + æ ¸é ¸æ£æµè¯å','å¼å¸éåèç æ¯ + æµæç æ¯ + æ£æµè¯å'] : ['产åéç¨åç§°','颿ç¨éå ³é®è¯','ææ¯åç + 使ç¨åºæ¯'], 1743 comparables: [], 1744 questions: [ 1745 'æç³æ¥ç产åéç¨åç§°ãç»æç»æå颿ç¨éå嫿¯ä»ä¹ï¼', 1746 'ç®æ ç§å®¤ãç®æ æ£è /æ ·æ¬éåç°è¡æ¿ä»£æ¹æ¡çç宿°æ®æ¥èªåªéï¼', 1747 'æ¯å¦å·²æåç±»çå®ãååç§æ³¨åè¯æç管æ²éè®°å½ï¼', 1748 'æ§è½ææ ä¸ä¸´åºè·çåå«ç¨ä»ä¹å¯¹ç §æ¹æ³åç»ç¹éªè¯ï¼', 1749 '注ååå ãæ£æµé¡¹ç®ãä¸´åºæ¹æ¡åè´¨éä½ç³»æ¯å¦å·²ææ£å¼æ¥ä»·ï¼' 1750 ], 1751 official_fee: {value_wan:0,basis:'å京å¢å 第äºç±»æ³¨åè¡æ¿æ¶è´¹ä¸º0å '}, 1752 registration_cost_estimate: defaultRegistrationCostEstimate(productScope) 1753 }; 1754} 1755 1756function defaultRegistrationCostEstimate(productScope = '') { 1757 const isIvd = /IVD|è¯å|ä½å¤è¯æ/i.test(productScope); 1758 const isSoftware = /软件|AI/i.test(productScope); 1759 const profileName = isIvd ? 'ä½å¤è¯æè¯åï¼æå¢å IIç±»è§åï¼' : isSoftware ? 'å»ç软件 / AI卿¢°ï¼æå¢å IIç±»è§åï¼' : '常è§å»ç卿¢°ï¼æå¢å IIç±»è§åï¼'; 1760 const values = isIvd 1761 ? [[40,80,150],[20,45,80],[25,60,120],[40,120,300],[20,50,100],[15,40,80],[0,0,0]] 1762 : isSoftware 1763 ? [[25,60,120],[10,25,50],[20,45,90],[10,40,120],[15,35,70],[10,25,50],[0,0,0]] 1764 : [[50,120,250],[30,60,120],[40,100,220],[30,120,400],[30,70,150],[20,50,100],[0,0,0]]; 1765 const definitions = [ 1766 ['design','产åå®åä¸è®¾è®¡éªè¯'],['testing','æ³¨åæ£éª / èªæ£å»ºè®¾'],['nonclinical','çç©å¦ãå®è§ã软件ç ç©¶'], 1767 ['clinical','临åºè¯ä»· / 临åºè¯éª'],['qms','è´¨éä½ç³»ä¸æ ¸æ¥åå¤'],['cro','CROä¸èµæé¡¹ç®ç®¡ç'],['official','宿¹è¡æ¿æ¶è´¹'] 1768 ]; 1769 const items = definitions.map(([key,label],index) => ({key,label,low:values[index][0],base:values[index][1],high:values[index][2],basis:key === 'official' ? 'å京å¢å 第äºç±»æ³¨åè¡æ¿æ¶è´¹ä¸º0å ' : 'åµåå¨ç«é¡¹é¶æ®µè§ååæ°ï¼éç¨æ£å¼æ¥ä»·è¦ç',source_type:key === 'official' ? 'official' : 'planning_parameter'})); 1770 return { 1771 applicable:true,profile_code:'demo_ii',profile_name:profileName,regulatory_route:'æ¼ç¤ºæ°æ®ï¼æå䏿³¨ååå ãå¢å 馿¬¡æ³¨åæµç®',confidence:'ä¸ä½ï¼åç±»å临åºè·¯å¾ç¡®è®¤åä¼ææ¾æ¶çª',scenario:'base',items, 1772 totals:{low:items.reduce((sum,item)=>sum+item.low,0),base:items.reduce((sum,item)=>sum+item.base,0),high:items.reduce((sum,item)=>sum+item.high,0)}, 1773 assumptions:['å¢å 馿¬¡æ³¨åãå䏿³¨ååå ãæ³¨å人ä½äºå京','é宿¹å项为è§ååæ°ï¼ä¸æ¯æ£æµæºæãå»é¢æCROæ¥ä»·','ä¸å«èªå»ºåæ¿ãè§æ¨¡å产线ãèèµææ¬åéå®è´¹ç¨'], 1774 official_sources:[ 1775 {label:'åäº¬æ³¨åæ¶è´¹é为é¶',url:'https://www.beijing.gov.cn/zhengce/zhengcefagui/201909/t20190909_171208.html'}, 1776 {label:'å½å®¶è¯çå±å¨æ¢°æ³¨åæ¶è´¹',url:'https://zwfw.nmpa.gov.cn/web/taskview/11100000MB0341032Y100017214300101'} 1777 ] 1778 }; 1779} 1780 1781function renderDecisionWorkbench(workbench, report = {}) { 1782 const identity = workbench.product_identity || {}; 1783 const identityItems = [ 1784 ['åééç¨åç§°', identity.candidate_name || report.project_name || 'å¾ ç¡®è®¤'], 1785 ['产åç±»å«', identity.product_scope || 'å¾ ç¡®è®¤'], 1786 ['颿ç¨é', identity.intended_use || 'å¾ ä»BPæå'], 1787 ['注åè·¯å¾å设', identity.regulatory_hypothesis || 'å¾ æ³è§è´è´£äººç¡®è®¤'] 1788 ]; 1789 if (identity.classification_reason) identityItems.push(['为ä»ä¹è¿æ ·åç±»', identity.classification_reason]); 1790 if (identity.identity_source || identity.consistency_status) identityItems.push(['产åèº«ä»½æ ¸éª', `${identity.consistency_status || 'å¾ ç¡®è®¤'} · ${identity.identity_source || 'æ¥æºå¾ 确认'}`]); 1791 if (identity.evidence_quote) identityItems.push(['BP产ååæ', `â${identity.evidence_quote}â`]); 1792 const termCounts = identity.classification_term_counts || {}; 1793 const readEvidence = Object.entries(termCounts).map(([key,value]) => `${key}Ã${value}`).join('ã'); 1794 $('#projectIdentityGrid').innerHTML = identityItems.map(([label,value],index) => { 1795 const note = label === '为ä»ä¹è¿æ ·åç±»' 1796 ? `å·²è¯»åæ£æ ${escapeHtml(identity.parsed_characters || 'â')} å${readEvidence ? `ï¼å ³é®è¯è®¡æ°ï¼${escapeHtml(readEvidence)}` : ''}` 1797 : label === '产åèº«ä»½æ ¸éª' ? '䏿¸¸æ£ç´¢ãææ¬åæ³¨åä»»å¡å¿ 须使ç¨è¿ä¸äº§å身份' 1798 : label === 'BP产ååæ' ? 'åªå¼ç¨æ¬æ¬¡ä¸ä¼ BPï¼ä¸å¼ç¨åå²é¡¹ç®' 1799 : index === 3 ? 'æ³è§æ¨æï¼ä¸æ¯æ£å¼åç±»ç»è®º' 1800 : index === 2 ? 'å¿ é¡»ä¸BPåæå临åºåºæ¯ä¸è´' 1801 : escapeHtml(identity.confidence || 'ææåæ¥è¯å«'); 1802 return `<article class="${index > 1 ? 'wide' : ''} ${label === '为ä»ä¹è¿æ ·åç±»' ? 'classification-reason' : ''}"><span>${escapeHtml(label)}</span><strong>${escapeHtml(value)}</strong><small>${note}</small></article>`; 1803 }).join(''); 1804 const gates = Array.isArray(workbench.gates) ? workbench.gates : []; 1805 $('#decisionGateStrip').innerHTML = gates.map((gate,index) => `<article><i>${index + 1}</i>
1805<span><strong>${escapeHtml(gate.name)}</strong><small>${escapeHtml(gate.note || '')}</small></span><b>${escapeHtml(gate.status || 'å¾ ç¡®è®¤')}</b></article>`).join(''); 1806 const queries = Array.isArray(workbench.comparable_queries) ? workbench.comparable_queries : []; 1807 $('#comparableQueryList').innerHTML = queries.map(query => `<a href="https://www.nmpa.gov.cn/zwfwqjd/index.html?type=pc" target="_blank" rel="noopener"><span>${escapeHtml(query)}</span><b>å°è¯ç屿 ¸éª â</b></a>`).join('') || '<p>éè¦å 确认产åéç¨åç§°å颿ç¨éï¼æè½çææææ£ç´¢å¼ã</p>'; 1808 const comparables = Array.isArray(workbench.comparables) ? workbench.comparables : []; 1809 $('#comparableRows').innerHTML = comparables.length ? comparables.map(item => `<div class="comparable-row"><span><strong>${escapeHtml(item.name || 'â')}</strong><small>${escapeHtml(item.certificate || 'è¯å·å¾ æ ¸éª')}</small></span><span>${escapeHtml(item.holder || 'â')}</span><span>${escapeHtml(item.classification || 'â')}</span><span><b>${escapeHtml(item.status || 'å¾ æ ¸éª')}</b></span></div>`).join('') : '<div class="comparable-empty"><strong>å°æ å·²æ ¸éªå类产å</strong><span>è¿ä¸æ¯â没æç«åâï¼èæ¯å°æªå®æå®æ¹æ°æ®åºæ£ç´¢ã请æä¸æ¹æ£ç´¢å¼æ ¸éªååè¿å ¥æå§ä¼ã</span></div>'; 1810 $('#comparableTaskStatus').textContent = comparables.length ? `å·²æ ¸éª ${comparables.length} 项` : '0 é¡¹å·²æ ¸éª'; 1811 $('#deepeningQuestions').innerHTML = (workbench.questions || []).map((question,index) => `<li><i>${index + 1}</i><span>${escapeHtml(question)}</span><b>å¾ åç</b></li>`).join(''); 1812 const estimate = workbench.registration_cost_estimate || defaultRegistrationCostEstimate(identity.product_scope || ''); 1813 renderRegistrationCostEstimate(estimate); 1814 if (!estimate.items?.length) { 1815 const officialFee = workbench.official_fee || {value_wan:0,basis:'å¾ åç±»åéæ©'}; 1816 $('#officialFeeInput').value = Number(officialFee.value_wan) || 0; 1817 $('#officialFeeBasis').textContent = officialFee.basis || 'å¾ åç±»åéæ©'; 1818 } 1819 $('#investmentDeepening').classList.remove('hidden'); 1820 $('#investmentVerify')?.classList.remove('hidden'); 1821 const searchProfile = report.search_profile || {}; 1822 const verifyProfileEl = $('#verifyProfile'); 1823 if (verifyProfileEl) { 1824 if (searchProfile.ran) { 1825 const profileParts = []; 1826 if (searchProfile.product_summary) profileParts.push(`ç³»ç»çè§£ï¼${searchProfile.product_summary}`); 1827 const facets = [searchProfile.core_technology, searchProfile.product_form, searchProfile.application].filter(Boolean).join(' · '); 1828 if (facets) profileParts.push(`æ£ç´¢ç»åï¼${facets}`); 1829 if ((searchProfile.cn_queries || []).length) profileParts.push(`䏿æ£ç´¢ï¼${searchProfile.cn_queries.join(' ï½ ')}`); 1830 if ((searchProfile.en_terms || []).length) profileParts.push(`è±ææ£ç´¢ï¼${searchProfile.en_terms.join(' ï½ ')}`); 1831 verifyProfileEl.textContent = profileParts.join('ã'); 1832 verifyProfileEl.classList.remove('hidden'); 1833 } else { 1834 verifyProfileEl.classList.add('hidden'); 1835 } 1836 } 1837 calculateMarketScenario(); 1838 calculateRegistrationBudget(); 1839 renderCompetitiveComparison(report.competitive_comparison || null); 1840 renderMarketCapitalEvidence(report.market_capital_evidence || null); 1841 renderEntityVerification(report.entity_verification || null); 1842 renderIpVerification(report.ip_verification || null); 1843 renderPipelineScan(report.pipeline_scan || null); 1844 loadComparableCandidates(workbench, identity); 1845} 1846 1847// 主ä½ç宿§æ ¸éªï¼å·¥åç»å + è¡æ + é£é©æ«æ + BP声称ä¸ç»è®°åºå ¥ï¼æ°æ®éæ¥åä¿åï¼ 1848function renderEntityVerification(ev) { 1849 const panel = $('#entityVerifyPanel'); 1850 if (!panel) return; 1851 if (!ev || !ev.detected_company || !ev.detected_company.name) { panel.classList.add('hidden'); return; } 1852 panel.classList.remove('hidden'); 1853 $('#entityDetectNote').textContent = `æ ¸éªå¯¹è±¡ï¼${ev.detected_company.name}ï¼${ev.detected_company.note || 'BPèªå¨è¯å«'}ï¼`; 1854 if (ev.registered !== true) { 1855 $('#entityRiskLevel').textContent = 'æªæ¥å°ç»è®°'; 1856 $('#entityProfile').innerHTML = `<p class="me-note">${escapeHtml(ev.note || 'æªæ¶å½è¯¥ä¼ä¸')}</p>`; 1857 $('#entityShareholders').innerHTML = ''; 1858 $('#entityRisks').innerHTML = ''; 1859 $('#entityMismatchBlock').classList.add('hidden');
1860 $('#entityBoundary').textContent = ev.boundary || ''; 1861 return; 1862 } 1863 const profile = ev.profile || {}; 1864 const risk = ev.risk_scan || {}; 1865 $('#entityRiskLevel').textContent = `é£é©${risk.level || 'å¾ æ ¸éª'}`; 1866 const gridRows = [ 1867 ['ç»è¥ç¶æ', profile.status], ['æç«æ¥æ', profile.established], ['注åèµæ¬', profile.reg_capital], 1868 ['æ³å®ä»£è¡¨äºº', profile.legal_person], ['ç»ä¸ä¿¡ç¨ä»£ç ', profile.credit_code], 1869 ['åä¿äººæ°', profile.social_staff ? profile.social_staff + ' 人' : ''], 1870 ['æå¨å°åº', profile.region], ['ä¼ä¸ç±»å', profile.company_type], 1871 ].filter(pair => pair[1]); 1872 $('#entityProfile').innerHTML = '<div class="ev-grid">' + gridRows.map(([k,v]) => `<div><span>${escapeHtml(k)}</span><strong>${escapeHtml(v)}</strong></div>`).join('') + '</div>' 1873 + (profile.business_scope ? `<p class="me-note">ç»è¥èå´ï¼${escapeHtml(profile.business_scope)}</p>` : ''); 1874 const shareholders = profile.shareholders || []; 1875 $('#entityShareholders').innerHTML = shareholders.length 1876 ? shareholders.map(sh => `<div class="me-company"><span>${escapeHtml(sh.name)}</span><b class="has">${escapeHtml(sh.percent)}</b></div>`).join('') 1877 : '<p class="me-note">è¡ä¸ä¿¡æ¯æªè·åå°</p>'; 1878 const items = risk.items || {}; 1879 $('#entityRisks').innerHTML = (risk.basis ? `<p class="me-summary">${escapeHtml(risk.basis)}</p>` : '') + ['dishonesty','executed','lawsuits','penalties'].map(key => { 1880 const item = items[key]; 1881 if (!item) return ''; 1882 const count = item.count; 1883 const cls = count === null || count === undefined ? 'warn' : count > 0 ? (item.severity === 'red' ? 'bad' : 'warn') : 'ok'; 1884 const label = (count === null || count === undefined) ? 'æ¥è¯¢æªæ' : count > 0 ? `${count} æ¡` : 'æ è®°å½'; 1885 return `<div class="me-check"><b class="${cls}">${label}</b><span><strong>${escapeHtml(item.label)}</strong>${item.latest && count ? `<small>${escapeHtml(item.latest)}</small>` : ''}</span></div>`; 1886 }).join(''); 1887 const mismatch = ev.mismatch || {}; 1888 if (mismatch.ran && (mismatch.mismatches || []).length) { 1889 $('#entityMismatchBlock').classList.remove('hidden'); 1890 const clsMap = {'ä¸è´':'ok','åºå ¥':'bad','å¾ æ ¸å®':'warn'}; 1891 $('#entityMismatch').innerHTML = (mismatch.summary ? `<p class="me-summary">${escapeHtml(mismatch.summary)}</p>` : '') 1892 + mismatch.mismatches.map(m =>
1892 `<div class="me-check"><b class="${clsMap[m.verdict] || 'warn'}">${escapeHtml(m.verdict)}</b><span><strong>${escapeHtml(m.aspect)}</strong><small>BP声称ï¼${escapeHtml(m.bp_claim)} ï½ ç»è®°äºå®ï¼${escapeHtml(m.registry_fact)}</small></span></div>`).join(''); 1893 } else { 1894 $('#entityMismatchBlock').classList.add('hidden'); 1895 } 1896 $('#entityBoundary').textContent = ev.boundary || ''; 1897} 1898 1899// ç¥è¯äº§ææ ¸éªï¼ä¸»ä½ä¸å©å®¶åº + åææ¯ç¬¬ä¸æ¹ä¸å© + BP声称åºå ¥æ¯å¯¹ï¼æ°æ®éæ¥åä¿åï¼ 1900function renderIpVerification(ip) { 1901 const panel = $('#ipVerifyPanel'); 1902 if (!panel) return; 1903 if (!ip || (!ip.company && !ip.candidate_name)) { panel.classList.add('hidden'); return; } 1904 panel.classList.remove('hidden'); 1905 $('#ipVerifyNote').textContent = ip.company 1906 ? `æ ¸éªå¯¹è±¡ï¼${ip.company} · åææ¯æ¹åï¼${ip.candidate_name || 'å¾ ç¡®è®¤'}ï¼å ¬å¼ä¸å©æ°æ®ï¼` 1907 : '䏻使ªè¯å«ï¼ä» ååææ¯ä¸å©æ«æ'; 1908 const cp = ip.company_patents || {}; 1909 const statusParts = []; 1910 if (cp.count_shown) statusParts.push(`${cp.count_shown} é¡¹Â·ææ ${cp.granted_count || 0}`); 1911 $('#ipVerifyStatus').textContent = statusParts.join('') || (ip.company_patents_error ? 'æªæ¥å°' : 'å¾ æ ¸éª'); 1912 const typeText = Object.entries(cp.by_type || {}).map(([k, v]) => `${k} ${v}`).join(' · '); 1913 const statusText = Object.entries(cp.by_status || {}).map(([k, v]) => `${k} ${v}`).join(' · '); 1914 $('#ipCompanyPatents').innerHTML = (cp.count_shown 1915 ? `<p class="me-summary">${escapeHtml(typeText)}${statusText ? ' ï½ ç¶æï¼' + escapeHtml(statusText) : ''}</p>` 1916 + (cp.patents || []).map(p => `<div class="me-company"><span>${escapeHtml(p.title)}</span><b class="${/ææ|ç»´æ/.test(p.status) ? 'has' : ''}">${escapeHtml(p.status)}</b><small>${escapeHtml([p.type, p.apply_date, p.pub_number].filter(Boolean).join(' · '))}</small></div>`).join('') 1917 + `<p class="me-note">${escapeHtml(cp.count_note || '')}</p>` 1918 : `<p class="me-note">${escapeHtml(ip.company_patents_error || '主ä½å䏿ªæ¥å°ä¸å©ï¼è¥BPå£°ç§°æ¥æä¸å©åééç¹æ ¸å®')}</p>`); 1919 const competing = ip.competing_patents || {}; 1920 $('#ipCompetingPatents').innerHTML = (competing.items || []).length 1921 ? competing.items.map(p => `<div class="me-company"><span>${escapeHtml(p.title)}</span><b class="has">${escapeHtml(p.applicant || 'ç³è¯·äººæªæ 注')}</b><small>${escapeHtml([p.status, p.apply_date, p.pub_number].filter(Boolean).join(' · '))}</small></div>`).join('') 1922 + `<p class="me-note">æ£ç´¢è¯ï¼${escapeHtml(competing.keyword || 'â')} ï½ ç¬¬ä¸æ¹å¨å ä¸å©å¯è½éå¶æ¬é¡¹ç®çææåæ¯ä¸èªç±å®æ½ç©ºé´</p>` 1923 : `<p class="me-note">${escapeHtml(competing.note || 'æªæ«æ')}</p>`; 1924 const check = ip.ai_check || {}; 1925 if (check.ran && (check.mismatches || []).length) { 1926 $('#ipAiCheckBlock').classList.remove('hidden'); 1927 const clsMap = {'ä¸è´':'ok','åºå ¥':'bad','å¾ æ ¸å®':'warn'}; 1928 $('#ipAiCheck').innerHTML = (check.summary ? `<p class="me-summary">${escapeHtml(check.summary)}</p>` : '') 1929 + check.mismatches.map(m =>
1929 `<div class="me-check"><b class="${clsMap[m.verdict] || 'warn'}">${escapeHtml(m.verdict)}</b><span><strong>${escapeHtml(m.aspect)}</strong><small>BP声称ï¼${escapeHtml(m.bp_claim)} ï½ å ¬å¼æ°æ®ï¼${escapeHtml(m.registry_fact)}</small></span></div>`).join(''); 1930 } else { 1931 $('#ipAiCheckBlock').classList.add('hidden'); 1932 } 1933 $('#ipBoundary').textContent = ip.boundary || ''; 1934} 1935 1936// å¨ç 管线ä¸ä¸´åºç»è®°å¨èï¼ClinicalTrials.gov åç±»å¨ç ç ç©¶ï¼æ°æ®éæ¥åä¿åï¼ 1937function renderPipelineScan(ps) { 1938 const panel = $('#pipelineScanPanel'); 1939 if (!panel) return; 1940 if (!ps || ps.ran !== true) { 1941 if (ps && ps.note && panel) { 1942 panel.classList.remove('hidden'); 1943 $('#pipelineThreatLevel').textContent = 'æªæ«æ'; 1944 $('#pipelineScanNote').textContent = ps.candidate_name ? `对象ï¼${ps.candidate_name}` : '产åéç¨åæªç¡®è®¤'; 1945 $('#pipelineStudies').innerHTML = `<p class="me-note">${escapeHtml(ps.note)}</p>`; 1946 $('#pipelineQueryTag').textContent = ''; 1947 $('#pipelineThreatBasis').textContent = ''; 1948 $('#pipelineBoundary').textContent = ps.boundary || ''; 1949 return; 1950 } 1951 panel.classList.add('hidden'); 1952 return; 1953 } 1954 panel.classList.remove('hidden'); 1955 $('#pipelineThreatLevel').textContent = `å¨è${ps.threat_level || 'å¾ è¯ä¼°'}`; 1956 $('#pipelineScanNote').textContent = `è¿è¡ä¸ ${ps.active_count || 0} 项 · æ¶ä¸å½æºæ ${ps.china_count || 0} 项ï¼ClinicalTrials.govï¼`; 1957 $('#pipelineQueryTag').textContent = `æ£ç´¢è¯ï¼${ps.used_term || ''}`; 1958 $('#pipelineStudies').innerHTML = (ps.studies || []).length 1959 ? ps.studies.map(s => `<div class="me-check"><b class="${s.active ? 'warn' : 'ok'}">${escapeHtml(s.status)}</b><span><strong>${escapeHtml(s.title)}${s.has_china ? ' <em class="news-tag">æ¶ä¸å½æºæ</em>' : ''}</strong><small>${escapeHtml([s.sponsor, s.phase !== 'ä¸éç¨/æªæ 注' ? s.phase : '', [s.start_date, s.completion_date].filter(Boolean).join(' ~ '), (s.countries || []).join('/')].filter(Boolean).join(' · '))}</small>${s.url ? `<small><a href="${escapeHtml(s.url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(s.nct_id)} â</a></small>` : ''}</span></div>`).join('') 1960 : '<p class="me-note">æªæ£ç´¢å°åç±»å¨ç ç»è®°ï¼ä¸ä»£è¡¨å½å æ å¨ç 项ç®ï¼ChiCTRä¸å½å 夿¡é人工补æ¥ï¼</p>'; 1961 $('#pipelineThreatBasis').textContent = ps.threat_basis || ''; 1962 $('#pipelineBoundary').textContent = ps.boundary || ''; 1963} 1964 1965// å¸åºä¸èµæ¬ä½è¯ï¼ç æ¥å®æ® + ç«åèèµå² + è´¢å¡èªæ´½æ§ï¼æ°æ®éæ¥åä¿åï¼ 1966function renderMarketCapitalEvidence(evidence) { 1967 const panel = $('#marketEvidencePanel'); 1968 if (!panel) return; 1969 if (!evidence) { panel.classList.add('hidden'); return; } 1970 panel.classList.remove('hidden'); 1971 const size = evidence.market_size || {}; 1972 const fin = evidence.financing || {}; 1973 const coh = evidence.financial_coherence || {}; 1974 const parts = []; 1975 if ((size.fragments || []).length) parts.push(`ç æ¥å®æ® ${size.fragments.length} æ¡`); 1976 if (fin.checked) parts.push(`èèµå² ${fin.with_financing}/${fin.checked} å®¶æè®°å½`); 1977 if (coh.ran) parts.push('è´¢å¡èªæ´½å·²æ£æ¥'); 1978 $('#marketEvidenceStatus').textContent = parts.join(' · ') || 'æªçæ'; 1979 $('#marketSizeEvidence').innerHTML = (size.fragments || []).length 1980 ? size.fragments.map(f => `<div class="me-fragment"><strong>${escapeHtml(f.title || 'ç æ¥ç段')}</strong><small>${escapeHtml([f.institution, f.publish_date].filter(Boolean).join(' · ') || 'æºæä¸æ¥æè¯¦è§åæ¥å')}</small><p>${escapeHtml(f.excerpt || '')}</p></div>`).join('') 1981 : `<p class="me-note">${escapeHtml(size.note || 'æªæ£ç´¢å°ç æ¥æ°æ®')}</p>`; 1982 const companies = fin.companies || []; 1983 $('#financingEvidence').innerHTML = (fin.summary ? `<p class="me-summary">${escapeHtml(fin.summary)}</p>` : '') + (companies.length 1984 ? companies.map(co => { 1985 const latest = co.latest; 1986 const latestText = latest 1987 ? `æè¿ï¼${escapeHtml(latest.date || 'æ¶é´æªæ«é²')} · ${escapeHtml(latest.round || 'è½®æ¬¡æªæ«é²')} · ${escapeHtml(latest.money || 'é颿ªæ«é²')}${latest.investors ? ' · ' + escapeHtml(latest.investors) : ''}` 1988 : '该ä¼ä¸æ å ¬å¼èèµè®°å½ï¼ä¸ä»£è¡¨æªèèµï¼'; 1989 return `<div class="me-company"><span>${escapeHtml(co.holder)}</span><b class="${co.event_count ? 'has' : 'none'}">${co.event_count ? `${co.event_count} ç¬å ¬å¼èèµ` : 'æ å ¬å¼è®°å½'}</b><small>${latestText}</small></div>`; 1990 }).join('') 1991 : `<p class="me-note">${escapeHtml(fin.note || 'æªæ¥è¯¢')}</p>`); 1992 if (coh.ran) { 1993 const verdictCls = {'ä¸è´':'ok', 'åç':'warn', 'çç¾':'bad'}; 1994 $('#financialCoherence').innerHTML = (coh.summary ? `<p class="me-summary">${escapeHtml(coh.summary)}</p>` : '') 1995 + (coh.checks || []).map(ch => `<div class="me-check"><b class="${verdictCls[ch.verdict] || 'warn'}">${escapeHtml(ch.verdict)}</b><span><strong>${escapeHtml(ch.aspect)}</strong><small>${escapeHtml(ch.claim || '')}${ch.cross ? ' ï½å¯¹ç §ï¼' + escapeHtml(ch.cross) : ''}${ch.note ? ' ï½' + escapeHtml(ch.note) : ''}</small></span></div>`).join('') 1996 + (coh.funding_note ? `<p class="me-note">èèµå¹é æ§ï¼${escapeHtml(coh.funding_note)}</p>` : ''); 1997 } else { 1998 $('#financialCoherence').innerHTML = `<p class="me-note">${escapeHtml(coh.note || 'æªæ£æ¥')}</p>`; 1999 } 2000 $('#marketEvidenceBoundary').textContent = evidence.boundary || ''; 2001} 2002 2003// å类产å䏿æ¯å è¿æ§å¯¹æ¯ï¼å®æ¹UDIç«åæ¸ å + AIäº¤åæ¯å¯¹ï¼æ°æ®éæ¥åä¿åï¼ 2004function renderCompetitiveComparison(competitive) { 2005 const panel = $('#competitiveCompare'); 2006 if (!panel) return; 2007 const items = competitive && Array.isArray(competitive.items) ? competitive.items : []; 2008 const ai = competitive && competitive.ai_comparison ? competitive.ai_comparison : null; 2009 const news = competitive && competitive.news_references ? competitive.news_references : {}; 2010 const snippets = Array.isArray(news.snippets) ? news.snippets : []; 2011 if (!competitive || (!items.length && !ai && !snippets.length)) { panel.classList.add('hidden'); return; } 2012 panel.classList.remove('hidden'); 2013 const density = competitive.market_density || ''; 2014 const positioning = ai ? (ai.technical_positioning || 'å¾ éªè¯') : 'å¾ äººå·¥å¯¹æ¯'; 2015 $('#competitivePositioning').textContent = `ææ¯å®ä½ï¼${positioning}${density ? ` · ç«äºå¯åº¦${density}` : ''}`; 2016 const basisParts = []; 2017 if (ai && ai.positioning_basis) basisParts.push(ai.positioning_basis);
2018 if (competitive.density_basis) basisParts.push(competitive.density_basis); 2019 $('#competitiveBasis').textContent = basisParts.join('ï¼') || 'åºäºå®æ¹æ³¨ååç±»æ¸ åä¸BPèªè¿°äº¤åæ¯å¯¹'; 2020 $('#competitiveSummary').textContent = ai 2021 ? (ai.differentiation_summary || 'AIæªç»åºå·®å¼åæ»ç»') 2022 : ((competitive.audit && competitive.audit.note) || 'æ¬æ¬¡æªçæAI对æ¯ï¼å¯å 人工æ¥ç䏿¹ç«åæ¸ å'); 2023 const renderRows = (rows, emptyText) => (rows && rows.length) 2024 ? rows.map(row => `<li><span>${escapeHtml(row.point)}</span><small>${escapeHtml(row.compare_to || 'åç±»æ´ä½')} · ${escapeHtml(row.source || 'å¾ æ ¸éª')}</small></li>`).join('') 2025 : `<li class="empty"><span>${emptyText}</span></li>`; 2026 $('#competitiveAdvantages').innerHTML = renderRows(ai && ai.advantages, 'AIæªè¯å«åºå·²è®ºè¯çç¸å¯¹ä¼å¿'); 2027 $('#competitiveGaps').innerHTML = renderRows(ai && ai.gaps, 'AIæªè¯å«åºææ¾å·®è·'); 2028 const tasks = ai && Array.isArray(ai.verification_tasks) ? ai.verification_tasks : []; 2029 $('#competitiveTasks').innerHTML = tasks.length 2030 ? tasks.map(task => `<li>${escapeHtml(task)}</li>`).join('') 2031 : '<li>æ ¸éªç«å注åè¯éä»¶ä¸çæ§è½ææ ååä¸ç»è®º</li>'; 2032 const newsBlock = $('#competitiveNewsBlock'); 2033 if (newsBlock) { 2034 if (snippets.length) { 2035 newsBlock.classList.remove('hidden'); 2036 $('#competitiveNews').innerHTML = snippets.map(s => `<div class="me-fragment"><strong>${escapeHtml(s.title || 'æ¥é')}</strong><small>${escapeHtml([s.source, s.date].filter(Boolean).join(' · ') || 'æ¥æºå¾ æ ¸éª')}</small><p>${escapeHtml(s.excerpt || '')}</p></div>`).join(''); 2037 $('#competitiveNewsNote').textContent = news.boundary || ''; 2038 } else if (news.note) { 2039 newsBlock.classList.remove('hidden'); 2040 $('#competitiveNews').innerHTML = `<p class="me-note">${escapeHtml(news.note)}</p>`; 2041 $('#competitiveNewsNote').textContent = ''; 2042 } else { 2043 newsBlock.classList.add('hidden'); 2044 } 2045 } 2046 const source = competitive.data_source || {}; 2047 const audit = competitive.audit || {}; 2048 $('#competitiveBoundary').textContent = `${competitive.boundary || ''}${source.bundle ? ` æ°æ®æºï¼${source.bundle}` : ''}${audit.ai_called && audit.model ? ` · AIå¯¹æ¯æ¨¡åï¼${audit.model}` : ''}`; 2049} 2050 2051async function loadComparableCandidates(workbench, identity) { 2052 const queries = Array.isArray(workbench.comparable_queries) ? workbench.comparable_queries : []; 2053 if (!queries.length) return; 2054 $('#comparableTaskStatus').textContent = 'æ£å¨æ£ç´¢ UDIâ¦'; 2055 $('#comparableSourceNote').textContent = 'æ£å¨æ¥è¯¢æ¬æºå½å®¶è¯çå± UDI å ¬å¼æ°æ®ç´¢å¼â¦'; 2056 try { 2057 const response = await fetch(`${BP_API_BASE}/api/udi/search`, { 2058 method:'POST', 2059 headers:{'Content-Type':'application/json'}, 2060 body:JSON.stringify({queries,candidate_name:identity.candidate_name || '',limit:10}) 2061 }); 2062 const payload = await response.json(); 2063 if (!response.ok) throw new Error(payload.detail || 'UDIç´¢å¼æ¥è¯¢å¤±è´¥'); 2064 const data = payload.data || {}; 2065 const items = Array.isArray(data.items) ? data.items : []; 2066 currentComparableCandidates = items; 2067 const companyCount = new Set(items.map(item =>
2067 item.holder).filter(Boolean)).size; 2068 if (items.length) { 2069 renderComparableCandidateRows(items, companyCount); 2070 } else { 2071 $('#comparableRows').innerHTML = '<div class="comparable-empty"><strong>å½åæåº¦æ´æ°ç´¢å¼æªå½ä¸é«ç¸å ³äº§å</strong><span>è¿ä¸ä»£è¡¨æ²¡æç«åãåºç»§ç»æ¥è¯¢å ¨éæ³¨åæ°æ®ï¼æè°æ´éç¨åç§°ã颿ç¨é忣æµé¶æ ã</span></div>'; 2072 $('#comparableTaskStatus').textContent = 'æåº¦ç´¢å¼ 0 项'; 2073 } 2074 const meta = data.metadata || {}; 2075 $('#comparableSourceNote').innerHTML = `<b>æ°æ®æºï¼</b>${escapeHtml(data.source || 'å½å®¶è¯çå±UDIå ¬å¼æ°æ®')} · ${escapeHtml(meta.source_bundle || 'æ¬æºç´¢å¼')} · ${escapeHtml(meta.record_count || 'â')} æ¡æ¬æºè®°å½ã${escapeHtml(data.coverage_notice || 'åéç»æä»ééæ¡æ ¸éªæ³¨åè¯ã')}`; 2076 } catch (error) { 2077 $('#comparableTaskStatus').textContent = 'UDIç´¢å¼å¾ è¿æ¥'; 2078 $('#comparableSourceNote').textContent = `${error.message}ãæ¨ä»å¯ä½¿ç¨ä¸æ¹æ£ç´¢å¼è¿å ¥å½å®¶è¯çå±ç½ç«äººå·¥æ ¸éªã`; 2079 } 2080} 2081 2082function getVerifiedComparableMap() { 2083 try { return JSON.parse(localStorage.getItem('verifiedComparableRegistry') || '{}'); } 2084 catch (_error) { return {}; } 2085} 2086 2087function renderComparableCandidateRows(items, companyCount = new Set(items.map(item =>
2087 item.holder).filter(Boolean)).size) { 2088 const verified = getVerifiedComparableMap(); 2089 $('#comparableRows').innerHTML = items.map(item => { 2090 const certificate = item.certificate || ''; 2091 const isVerified = Boolean(certificate && verified[certificate]); 2092 return `<div class="comparable-row"><span><strong>${escapeHtml(item.name || 'â')}</strong><small>${escapeHtml(certificate || 'è¯å·å¾ æ ¸éª')} · ${escapeHtml(item.publish_date || item.version_time || 'æ¥æå¾ æ ¸éª')}</small></span><span>${escapeHtml(item.holder || 'â')}</span><span>${escapeHtml(item.classification || item.device_category || 'å¾ æ ¸éª')}</span><span><button class="verify-comparable ${isVerified ? 'verified' : ''}" data-certificate="${escapeHtml(certificate)}">${isVerified ? 'â å·²äººå·¥æ ¸éª' : 'æ è®°å·²æ ¸éª'}</button><small>${escapeHtml((item.matched_terms || []).slice(0,3).join(' / '))}</small></span></div>`; 2093 }).join(''); 2094 const verifiedCount = items.filter(item => item.certificate && verified[item.certificate]).length; 2095 $('#comparableTaskStatus').textContent = `å·²æ ¸éª ${verifiedCount} / å½ä¸ ${items.length} · ${companyCount} å®¶ä¼ä¸`; 2096} 2097 2098function toggleComparableVerification(certificate) { 2099 if (!certificate) return; 2100 const registry = getVerifiedComparableMap(); 2101 if (registry[certificate]) delete registry[certificate]; 2102 else registry[certificate] = {verified_at:new Date().toISOString(),source:'äººå·¥æ ¸éª'}; 2103 localStorage.setItem('verifiedComparableRegistry', JSON.stringify(registry)); 2104 renderComparableCandidateRows(currentComparableCandidates); 2105 showToast(registry[certificate] ? `å·²å° ${certificate} æ è®°ä¸ºäººå·¥æ ¸éª` : `已忶 ${certificate} çæ ¸éªæ è®°`); 2106} 2107 2108function formatWan(value) { 2109 if (!Number.isFinite(value) || value <= 0) return 'å¾ è¾å ¥'; 2110 if (value >= 10000) return `${(value / 10000).toFixed(2)} 亿å `; 2111 return `${value.toFixed(value >= 100 ? 0 : 2)} ä¸å `; 2112} 2113 2114function formatBudgetWan(value) { 2115 if (!Number.isFinite(value)) return 'â'; 2116 if (value === 0) return '0 ä¸å '; 2117 return formatWan(value); 2118} 2119 2120function calculateMarketScenario() { 2121 const target = Number($('#marketTargetUnits')?.value) || 0; 2122 const usage = Number($('#marketAnnualUsage')?.value) || 0; 2123 const price = Number($('#marketUnitPrice')?.value) || 0; 2124 const reach = Math.min(100, Math.max(0, Number($('#marketReachRate')?.value) || 0)) / 100; 2125 const share = Math.min(100, Math.max(0, Number($('#marketShareRate')?.value) || 0)) / 100; 2126 const tam = target * usage * price / 10000; 2127 const sam = tam * reach; 2128 const som = sam * share; 2129 $('#marketTam').textContent = formatWan(tam); 2130 $('#marketSam').textContent = formatWan(sam); 2131 $('#marketSom').textContent = formatWan(som); 2132} 2133 2134const marketEstimateFieldMap = [ 2135 ['target_units','marketTargetUnits','ç®æ æºæ / æ£è æ°'], 2136 ['annual_usage','marketAnnualUsage','æ¯ä¸ªç®æ å¹´ç¨é'], 2137 ['unit_price','marketUnitPrice','产ååä»·ï¼å ï¼'], 2138 ['reach_rate','marketReachRate','坿塿¯ä¾ï¼%ï¼'], 2139 ['share_rate','marketShareRate','ä¸å¹´ç®æ 份é¢ï¼%ï¼'] 2140]; 2141 2142function applyMarketEstimate(result) { 2143 marketEstimateFieldMap.forEach(([key,inputId]) => { 2144 const item = result[key] || {}; 2145 if (item.value === null || item.value === undefined || item.value === '') return; 2146 const value = Number(item.value); 2147 if (!Number.isFinite(value)) return; 2148 const input = $(`#${inputId}`); 2149 if (input) input.value = value; 2150 }); 2151 calculateMarketScenario(); 2152 const paramsHtml = marketEstimateFieldMap.map(([key,,label]) => { 2153 const item = result[key] || {}; 2154 const valueText = (item.value === null || item.value === undefined || item.value === '') ? 'æä¸ç»åº' : Number(item.value).toLocaleString('zh-CN'); 2155 return `<li><strong>${escapeHtml(label)}ï¼${escapeHtml(valueText)}</strong><span>${escapeHtml(item.basis || 'ææ ä¾æ®è¯´æ')}ï¼æ¥æºï¼${escapeHtml(item.source || 'AIæ¨ç®')}ï¼</span></li>`; 2156 }).join(''); 2157 const refsHtml = (Array.isArray(result.references) ? result.references : []).map(ref => { 2158 const link = ref.url ? ` <a href="${escapeHtml(ref.url)}" target="_blank" rel="noopener noreferrer">æ¥çæ¥æº</a>` : ''; 2159 return `<li>${escapeHtml(ref.label || 'åèæ°æ®')}ï¼${escapeHtml(ref.value ?? '')}${ref.period ? `ï¼${escapeHtml(ref.period)}ï¼` : ''} â ${escapeHtml(ref.source || 'æ¥æºå¾ æ ¸éª')}${link}</li>`; 2160 }).join(''); 2161 const notesHtml = (Array.isArray(result.notes) ? result.notes : []).map(note => `<li>${escapeHtml(note)}</li>`).join(''); 2162 const container = $('#marketEstimateNotes'); 2163 if (!container) return; 2164 container.innerHTML = ` 2165 <p class="estimate-banner">â 以ä¸ä¸º AI ä¼°ç®çæ æ¯å设ï¼ç½®ä¿¡åº¦ï¼${escapeHtml(result.confidence || 'ä¸')}ï¼ï¼å·²åå¡«çæ°å¼å¯ç´æ¥ä¿®æ¹ï¼è¯·éé¡¹ç¡®è®¤ä¾æ®åå使ç¨ã</p> 2166 <ul class="estimate-params">${paramsHtml}</ul> 2167 ${refsHtml ? `<div class="estimate-refs"><strong>åèæ¥æº</strong><ul>${refsHtml}</ul></div>` : ''} 2168 ${notesHtml ? `<div class="estimate-refs"><strong>ä½¿ç¨æé</strong><ul>${notesHtml}</ul></div>` : ''} 2169 ${result.boundary ? `<p class="estimate-boundary">è¯æ®è¾¹çï¼${escapeHtml(result.boundary)}</p>` : ''}`; 2170 container.classList.remove('hidden'); 2171} 2172 2173async function estimateMarketWithAI() { 2174 const button = $('#marketAiEstimateBtn'); 2175 if (!currentInvestmentProject || !currentInvestmentAnalysis) { showToast('请å 鿩项ç®å¹¶å®æBPåæï¼AIæè½åºäºåæç»æä¼°ç®å¸åºåæ°'); return; } 2176 if (!button || button.dataset.busy === 'true') return; 2177 button.dataset.busy = 'true'; 2178 button.disabled = true; 2179 const originalText = button.textContent; 2180 button.textContent = 'ä¼°ç®ä¸â¦'; 2181 try { 2182 const estimateUrl = window.location.protocol === 'file:' ? 'http://127.0.0.1:8787/api/investment/market-estimate' : '/api/investment/market-estimate'; 2183 const response = await fetch(estimateUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({context:compactInvestmentAssistantContext()})}); 2184 const payload = await response.json(); 2185 if (!response.ok) throw new Error(payload.error || payload.detail || 'AIå¸åºä¼°ç®ææ¶ä¸å¯ç¨'); 2186 applyMarketEstimate(payload); 2187 showToast('AIä¼°ç®å·²åå¡«ï¼è¯·éé¡¹ç¡®è®¤ä¾æ®åå使ç¨'); 2188 } catch (error) { 2189 showToast(error.message || 'AIå¸åºä¼°ç®å¤±è´¥ï¼è¯·ç¨åéè¯'); 2190 } finally {
2191 button.dataset.busy = 'false'; 2192 button.disabled = false; 2193 button.textContent = originalText; 2194 } 2195} 2196 2197function renderTransformationPath(result) { 2198 const container = $('#transformationPathResult'); 2199 if (!container) return; 2200 const audit = result._audit || {}; 2201 const stages = (Array.isArray(result.stages) ? result.stages : []).map((stage, index) => { 2202 const actions = (stage.key_actions || []).map(action => `<li>${escapeHtml(action)}</li>`).join(''); 2203 const resources = (stage.resources || []).map(resource => `<span class="tp-resource-tag">${escapeHtml(resource)}</span>`).join(''); 2204 const deliverables = (stage.deliverables || []).map(item => `<li>${escapeHtml(item)}</li>`).join(''); 2205 return `<article class="tp-stage"><div class="tp-stage-node"><i>${index + 1}</i></div><div class="tp-stage-card"><header><div><strong>${escapeHtml(stage.name || `é¶æ®µ ${index + 1}`)}</strong><p>${escapeHtml(stage.goal || '')}</p></div><span class="tp-duration">${escapeHtml(stage.duration || 'å¨æå¾ ç¡®è®¤')}</span></header><div class="tp-stage-body"><section><b>å ³é®è¡å¨</b><ul>${actions || '<li>å¾ ç¡®è®¤</li>'}</ul></section><section><b>è°ç¨éå¢èµæº</b><div class="tp-resources">${resources || '<span class="tp-resource-tag">å¾ ç¡®è®¤</span>'}</div></section><section><b>产åºç©</b><ul>${deliverables || '<li>å¾ ç¡®è®¤</li>'}</ul></section></div><div class="tp-gate"><strong>å³çé¨</strong><span>${escapeHtml(stage.decision_gate || 'å¾ ç¡®è®¤')}</span><small>${escapeHtml(stage.cost_hint || 'è´¹ç¨å¾ 确认')}</small></div></div></article>`; 2206 }).join(''); 2207 const immediate = (Array.isArray(result.immediate_actions) ? result.immediate_actions : []).map((item, index) => `<li><i>${index + 1}</i><div><strong>${escapeHtml(item.action || '')}</strong><span>责任è§è²ï¼${escapeHtml(item.owner_role || 'å¾ ç¡®è®¤')}</span></div><em>产åºï¼${escapeHtml(item.output || 'å¾ ç¡®è®¤')}</em></li>`).join(''); 2208 const risks = (Array.isArray(result.critical_risks) ? result.critical_risks : []).map(item => `<li><span class="risk-mid">é£é©</span><div><strong>${escapeHtml(item.risk || '')}</strong><p>ç¼è§£ï¼${escapeHtml(item.mitigation || 'å¾ ç¡®è®¤')}</p></div></li>`).join(''); 2209 const trackName = result.recommended_track?.track || 'å°ä¸ç¡®å®'; 2210 const trackReason = result.recommended_track?.reason || ''; 2211 let productType = currentInvestmentAnalysis?.product_identity?.product_scope || ''; 2212 if (!productType) { 2213 if (/ä½å¤è¯æ|IVD|è¯å/i.test(trackReason)) productType = 'IVDï¼ä½å¤è¯æï¼'; 2214 else if (/ææº/.test(trackReason)) productType = 'ææºå»ç卿¢°'; 2215 else if (/æ æº/.test(trackReason)) productType = 'æ æºå»ç卿¢°'; 2216 else if (/软件/.test(trackReason)) productType = 'å»ç卿¢°è½¯ä
2216»¶'; 2217 } 2218 let trackNote = ''; 2219 if (/IVD/i.test(trackName)) { 2220 trackNote = '注ï¼IVDï¼ä½å¤è¯æï¼å¨æå½æå»ç卿¢°ç®¡çï¼ä¸å¨æ¢°è®¾å¤åå±"å»ç卿¢°"çç®¡å¤§ç±»ï¼æ èµéåå¹¶å½åã'; 2221 if (productType && !/ä½å¤è¯æ|IVD|è¯å/i.test(productType)) { 2222 trackNote += `æ¬é¡¹ç®å¤å®ä¸º"${productType}"ï¼ä¸å±äºIVD产åã`; 2223 } 2224 } 2225 container.innerHTML = ` 2226 <div class="tp-banner"><p>${escapeHtml(result.path_summary || '转åè·¯å¾æ¹æ¡å·²çæã')}</p><span>AIè§åæ¹æ¡ï¼éä¸å®¶è¯å®¡åæ§è¡</span></div> 2227 <div class="tp-track"><small>æ¨èèµé</small><strong>${escapeHtml(trackName)}</strong>${productType ? `<span class="tp-track-type">产åç±»åï¼${escapeHtml(productType)}</span>` : ''}<p>${escapeHtml(trackReason)}</p>${trackNote ? `<small class="tp-track-note">${escapeHtml(trackNote)}</small>` : ''}</div> 2228 <div class="tp-timeline">${stages}</div> 2229 ${immediate ? `<div class="tp-immediate"><header><strong>30 天è¡å¨æ¸ å</strong><span>å å¨èµ·æ¥ï¼ä¸çå ¨é¨æ¡ä»¶æç</span></header><ul>${immediate}</ul></div>` : ''} 2230 ${risks ? `<div class="tp-risks"><strong>å ³é®é£é©ä¸ç¼è§£</strong><ul>${risks}</ul></div>` : ''} 2231 <footer class="tp-footer"><p>è¯æ®è¾¹çï¼${escapeHtml(result.boundary || 'æ¬æ¹æ¡ä¸ºAIè§åèæ¡ï¼æ§è¡åé人工确认ã')}</p><small>模åï¼${escapeHtml(audit.model || 'DeepSeek')} · è¯·æ± ${escapeHtml(audit.request_id || 'â')}${audit.usage?.total_tokens ? ` · tokens ${escapeHtml(String(audit.usage.total_tokens))}` : ''}</small></footer>`; 2232 container.classList.remove('hidden'); 2233 container.scrollIntoView({ behavior:'smooth', block:'nearest' }); 2234} 2235 2236async function generateTransformationPath() { 2237 const button = $('#transformationPathBtn'); 2238 if (!currentInvestmentProject || !currentInvestmentAnalysis) { showToast('请å 宿BPåæï¼AIæè½åºäºåæç»æè§å转åè·¯å¾'); return; } 2239 if (!button || button.dataset.busy === 'true') return; 2240 button.dataset.busy = 'true'; 2241 button.disabled = true; 2242 const originalText = button.textContent; 2243 button.textContent = 'è§åä¸â¦'; 2244 try { 2245 const pathUrl = window.location.protocol === 'file:' ? 'http://127.0.0.1:8787/api/investment/transformation-path' : '/api/investment/transformation-path'; 2246 const response = await fetch(pathUrl,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({context:compactInvestmentAssistantContext()})}); 2247 const payload = await response.json(); 2248 if (!response.ok) throw new Error(payload.error || payload.detail || 'AI转åè·¯å¾è§åææ¶ä¸å¯ç¨'); 2249 renderTransformationPath(payload); 2250 showToast('转åè·¯å¾æ¹æ¡å·²çæï¼è¯·ä¸å®¶è¯å®¡åæ§è¡'); 2251 } catch (error) { 2252 showToast(error.message || 'AI转åè·¯å¾è§å失败ï¼è¯·ç¨åéè¯'); 2253 } finally { 2254 button.dataset.busy = 'false'; 2255 button.disabled = false; 2256 button.textContent = originalText; 2257 } 2258} 2259 2260function calculateRegistrationBudget() { 2261 const inputs = $$('[data-budget-item]'); 2262 const values = inputs.map(input => Number(input.value) || 0); 2263 const total = values.reduce((sum,value) => sum + value, 0); 2264 const filled = values.filter(value => value > 0).length; 2265 $('#registrationBudgetTotal').textContent = formatBudgetWan(total); 2266 const activeScenario = $('.budget-scenario-switch button.active')?.dataset.budgetScenario || 'base'; 2267 const scenarioName = {low:'使¡£',base:'åºå',high:'髿¡£'}[activeScenario]; 2268 $('#registrationBudgetCoverage').textContent = `${scenarioName}è§åå¼ Â· ${filled} / ${values.length} 项å¯ç¼è¾ · 䏿¯æ£å¼æ¥ä»·`;
2269 const totals = currentRegistrationEstimate?.totals || {}; 2270 $('#registrationBudgetRange').innerHTML = `<b>使¡£ ${formatBudgetWan(Number(totals.low) || 0)}</b><b>åºå ${formatBudgetWan(Number(totals.base) || 0)}</b><b>髿¡£ ${formatBudgetWan(Number(totals.high) || 0)}</b>`; 2271} 2272 2273function renderRegistrationCostEstimate(estimate) { 2274 currentRegistrationEstimate = estimate || null; 2275 if (!estimate?.applicable) { 2276 $('#registrationEstimateProfile').textContent = estimate?.profile_name || 'å½å项ç®ä¸éç¨å¨æ¢°é¢ç®æ¨¡å'; 2277 $('#registrationEstimateRoute').textContent = estimate?.confidence || 'è¯·è½¬æ³¨åæ³è§æ£ç´¢æ¨¡å建ç«ä¸é¨æ¨¡å'; 2278 $$('[data-budget-item]').forEach(input => { input.value = 0; input.disabled = true; }); 2279 $('#registrationEstimateAssumptions').innerHTML = (estimate?.assumptions || []).map(item => `<li>${escapeHtml(item)}</li>`).join(''); 2280 $('#registrationEstimateSources').innerHTML = ''; 2281 calculateRegistrationBudget(); 2282 return; 2283 } 2284 $$('[data-budget-item]').forEach(input => { input.disabled = false; }); 2285 $('#registrationEstimateProfile').textContent = estimate.profile_name || 'æ³¨åææ¬è§å模å'; 2286 $('#registrationEstimateRoute').textContent = `${estimate.regulatory_route || ''} · å¯ä¿¡åº¦ï¼${estimate.confidence || 'å¾ ç¡®è®¤'}`; 2287 const itemMap = new Map((estimate.items || []).map(item => [item.key,item])); 2288 $$('[data-budget-item]').forEach(input => { 2289 const item = itemMap.get(input.dataset.budgetKey); 2290 if (!item) return; 2291 input.dataset.low = item.low ?? 0; 2292 input.dataset.base = item.base ?? 0; 2293 input.dataset.high = item.high ?? 0; 2294 const basis = $(`[data-budget-basis="${input.dataset.budgetKey}"]`); 2295 if (basis) basis.textContent = `${item.basis || 'è§ååæ°'}ï¼åºé´ ${formatBudgetWan(Number(item.low)||0)}â${formatBudgetWan(Number(item.high)||0)}`; 2296 }); 2297 $('#registrationEstimateAssumptions').innerHTML = (estimate.assumptions || []).map(item => `<li>${escapeHtml(item)}</li>`).join(''); 2298 $('#registrationEstimateSources').innerHTML = (estimate.official_sources || []).map(item => `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">${escapeHtml(item.label)} â</a>`).join(''); 2299 applyBudgetScenario(estimate.scenario || 'base'); 2300} 2301 2302function applyBudgetScenario(scenario = 'base') { 2303 $$('.budget-scenario-switch button').forEach(button => button.classList.toggle('active', button.dataset.budgetScenario === scenario)); 2304 $$('[data-budget-item]').forEach(input => { 2305 const next = Number(input.dataset[scenario]); 2306 if (Number.isFinite(next)) input.value = next; 2307 }); 2308 calculateRegistrationBudget(); 2309} 2310 2311function defaultInvestmentReferenceData() { 2312 return { 2313 as_of:'2026-07-15', product_scope:'å»ç卿¢°ï¼ç»åç±»å«å¾ 确认ï¼', 2314 market:[], 2315 registration_costs:[ 2316 {label:'å京å¢å 第äºç±»å¨æ¢°æ³¨åè¡æ¿æ¶è´¹',value:'0å ',basis:'2019å¹´5æ1æ¥èµ·é为é¶',source:'åäº¬å¸æ¿åºï¼äº¬åæ¹ã2019ã569å·ï¼',url:'https://www.beijing.gov.cn/zhengce/zhengcefagui/201909/t20190909_171208.html',scope_note:'ä¸å«æ£æµã临åºãä½ç³»åCROè´¹ç¨'}, 2317 {label:'å½äº§ç¬¬ä¸ç±»å¨æ¢°é¦æ¬¡æ³¨åè¡æ¿æ¶è´¹',value:'15.36ä¸å ',basis:'å½å®¶è¯çå±åäºæå',source:'å½å®¶è¯åçç£ç®¡çå±',url:'https://zwfw.nmpa.gov.cn/web/taskview/11100000MB0341032Y100017214300101',scope_note:'ææ³¨ååå 计ï¼ä¸çäºå®æ´é¡¹ç®é¢ç®'}, 2318 {label:'é«é£é©ç¬¬ä¸ç±»å¨æ¢°ä¸´åºè¯éªç³è¯·è´¹',value:'4.32ä¸å ',basis:'å½å®¶è¯çå±åäºæå',source:'å½å®¶è¯åçç£ç®¡çå±',url:'https://zwfw.nmpa.gov.cn/web/taskview/11100000MB0341032Y100017214200001',scope_note:'ä» éç¨äºé审æ¹çé«é£é©ä¸ç±»å¨æ¢°'} 2319 ], 2320 budget_components:['产åå®åãæ ·æºä¸è®¾è®¡éªè¯','æ³¨åæ£éªæèªæ£è½å建设','çç©å¦è¯ä»·ãå®è§ã软件ä¸ç½ç»å®å ¨ç ç©¶','临åºè¯ä»·æä¸´åºè¯éª','è´¨é管çä½ç³»å»ºç«ä¸æ ¸æ¥åå¤','注åå¨è¯¢ãèµæç¼å¶ãè¡¥æ£ä¸é¡¹ç®ç®¡ç'], 2321 official_databases:[ 2322 {label:'å½å®¶è¯çå±å¢å 卿¢°æ³¨åæ¥è¯¢',url:'https://www.nmpa.gov.cn/zwfwqj
2322d/index.html?type=pc',use:'æ ¸éªå类注åè¯ã注å人ã管çç±»å«'}, 2323 {label:'å½å®¶è¯çå±UDIæ°æ®åº',url:'https://udi.nmpa.gov.cn/',use:'æäº§ååç§°æä¼ä¸æ£ç´¢å¨å®å类产å'} 2324 ] 2325 }; 2326} 2327 2328function renderInvestmentEvidence(data) { 2329 // 09ãè¯æ®ä¸åèãæ¿åå·²å¹¶å ¥ 03 ç宿 ¸éª ä¸ 08 æ·±å䏿³¨åï¼é个å®å«ï¼å ç´ ä¸å卿¶è·³è¿ 2330 const costGrid = $('#registrationCostGrid'); 2331 if (costGrid) costGrid.innerHTML = (data.registration_costs || []).map(item => `<article><span>宿¹è¡æ¿æ¶è´¹</span><strong>${escapeHtml(item.value)}</strong><h3>${escapeHtml(item.label)}</h3><p>${escapeHtml(item.basis)}ã${escapeHtml(item.scope_note)}</p><a href="${escapeHtml(item.url)}" target="_blank" rel="noopener">æ ¸éªæ¶è´¹ä¾æ® · ${escapeHtml(item.source)} â</a></article>`).join(''); 2332 const budgetList = $('#registrationBudgetComponents'); 2333 if (budgetList) budgetList.innerHTML = (data.budget_components || []).map(item => `<li>${escapeHtml(item)} <b>é询价</b></li>`).join(''); 2334 const dbLinks = $('#officialDatabaseLinks'); 2335 if (dbLinks) dbLinks.innerHTML = (data.official_databases || []).map(item => `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener"><strong>${escapeHtml(item.label)} â</strong><small>${escapeHtml(item.use)}</small></a>`).join(''); 2336} 2337 2338function renderInternalKnowledgeReferences(data) {
2339 const panel = $('#investmentKnowledgeReferences'); 2340 if (!panel) return; 2341 const references = Array.isArray(data.references) ? data.references : []; 2342 if (!data.ready) { 2343 panel.classList.add('hidden'); 2344 return; 2345 } 2346 $('#knowledgeQueryTerms').innerHTML = (data.query_terms || []).slice(0,10).map(term => `<b>${escapeHtml(term)}</b>`).join('') || '<span>æªæåå°ç¨³å®çç»åèµéè¯ï¼å»ºè®®å è¡¥å 产ååç§°å颿ç¨éã</span>'; 2347 $('#knowledgeReferenceCount').textContent = `å½ä¸ ${references.length} 份å é¨èµæ`; 2348 $('#knowledgeReferenceList').innerHTML = references.length ? references.map((item,index) => { 2349 const isCase = item.source_type === 'åå²é¡¹ç®BP'; 2350 return `<article class="internal-reference-card ${isCase ? 'case' : 'report'}"><header><i>${isCase ? 'æ¡ä¾' : 'æ¥å'}</i><span><strong>R${index + 1} · ${escapeHtml(item.source_name || 'å é¨èµæ')}</strong><small>${escapeHtml(item.locator || 'ä½ç½®å¾ 确认')} · èµææ¥æ ${escapeHtml(item.source_date || 'æªè¯å«')}</small></span></header><p>${escapeHtml(item.excerpt || 'æ å¯å±ç¤ºç段')}</p><footer><b>${escapeHtml(item.source_type || item.category || 'å é¨èµæ')}</b><span>${escapeHtml(item.evidence_boundary || 'ä» ä½å é¨ç ç©¶åè')}</span></footer></article>`; 2351 }).join('') : '<div class="internal-reference-empty"><strong>ç¥è¯åºå·²è¿æ¥ï¼ä½æ²¡æå½ä¸ç´æ¥ç¸å ³èµæ</strong><span>ç³»ç»ä¸ä¼ç¨ä¸ç¸å ³æ¥ååçæ¡ï¼è¯·è¡¥å 产åéç¨åç§°ã颿ç¨éåéåºçåéæ°åæã</span></div>'; 2352 $('#knowledgeReferenceBoundary').textContent = data.boundary || 'è¡ä¸æ¥åæ¯å é¨ç ç©¶åèï¼åå²BPæ¯é¡¹ç®æ¹èªè¿°ï¼å䏿¿ä»£å®æ¹æ°æ®åä¸å®¶æ ¸éªã'; 2353 panel.classList.remove('hidden'); 2354} 2355 2356function renderBiomedicalRoute(data) {
2357 const panel = $('#investmentBiomedicalRoute'); 2358 if (!panel || !data || !data.route_candidate) { panel?.classList.add('hidden'); return; } 2359 const audit = data.audit || {}; 2360 const references = Array.isArray(data.knowledge_references) ? data.knowledge_references : []; 2361 $('#bpRouteAudit').textContent = audit.ai_called ? `AIç å¤ Â· ${references.length}份æ³è§ä¾æ®` : `æ¬æºåç · ${references.length}份æ³è§ä¾æ®`; 2362 $('#bpRouteCandidate').textContent = data.route_candidate; 2363 $('#bpRouteConfidence').textContent = `è·¯å¾ç½®ä¿¡åº¦ ${data.route_confidence || 'ä½'}`; 2364 $('#bpRouteRisk').textContent = data.risk_level || 'å¾ å¤å®'; 2365 $('#bpRouteGate').textContent = data.decision_gate || '宿屿§çå®'; 2366 $('#bpRouteScope').textContent = data.scope_judgment || 'å½åä¿¡æ¯ä¸è¶³ï¼éæ³è§è´è´£äººè¿ä¸æ¥çå®ã'; 2367 $('#bpRouteGap').textContent = `ä¼å è¡¥å ï¼${data.critical_gap || 'ä½ç¨æºå¶ãåè¯ç©ç»æå临åºä½¿ç¨æ¹å¼'}`; 2368 $('#bpRouteActions').innerHTML = (data.actions || []).slice(0,5).map(item => `<div><i>${escapeHtml(item.step || '')}</i><span><strong>${escapeHtml(item.title || '')}</strong><small>${escapeHtml(item.description || '')} · ${escapeHtml(item.owner || 'æ³è§è´è´£äºº')}</small></span></div>`).join('') || '<p>çå¾ å½¢æè·¯å¾ä»»å¡ã</p>'; 2369 $('#bpRouteSources').innerHTML = references.length ? references.map((item,index) => `<article><b>B${index + 1}</b><span><strong>${escapeHtml(item.source_name || 'æ³è§èµæ')}</strong><small>${escapeHtml(item.locator || 'ä½ç½®å¾ 确认')} · ${escapeHtml(item.source_date || 'æ¥æå¾ æ ¸éª')}</small><p>${escapeHtml(item.excerpt || '')}</p></span></article>`).join('') : '<p>æ¬è½®æªå½ä¸æ³è§åæï¼è·¯å¾ç»è®ºåªè½ä½ä¸ºä½ç½®ä¿¡åº¦çº¿ç´¢ã</p>'; 2370 $('#bpRouteBoundary').textContent = data.boundary || 'è·¯å¾ç å¤ä¸æ¿ä»£ä¸»ç®¡é¨é¨ç宿æ³è§è´è´£äººç¾ç½²ã'; 2371 panel.classList.remove('hidden'); 2372} 2373 2374// éç¨å¼æ¥ä»»å¡è½®è¯¢ï¼æäº¤åç«å³è¿åï¼ç轮询åç»æã 2375// é¿å é¿èæ¶è¯·æ±ï¼>60ç§ï¼è¢«ç¨æ·ä¾§ç½ç»è®¾å¤ææï¼Failed to fetchï¼ã 2376async function pollAsyncJob(jobId, {intervalMs = 4000, timeoutMs = 25*60*1000, onProgress} = {}) { 2377 const token = localStorage.getItem('token'); 2378 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 2379 const startedAt = Date.now(); 2380 const deadline = startedAt + timeoutMs; 2381 while (Date.now() < deadline) { 2382 await new Promise(resolve => setTimeout(resolve, intervalMs)); 2383 let job = null; 2384 try { 2385 const resp = await fetch(`${BP_API_BASE}/api/analysis-jobs/${jobId}`, { headers }); 2386 if (!resp.ok) continue; 2387 job = (await resp.json()).data || {}; 2388 } catch (fetchError) { continue; } 2389 if (job.status === 'error') throw new Error(job.detail || 'ä»»å¡å¤±è´¥'); 2390 if (job.status === 'done') return job.result; 2391 if (onProgress) onProgress(Math.round((Date.now() - startedAt) / 1000)); 2392 } 2393 throw new Error('å¤çæ¶é´è¾é¿ï¼è¯·ç¨åéè¯'); 2394} 2395 2396async function analyzeBpFile() { 2397 if (!bpSelectedFile) return; 2398 if (!currentInvestmentProject) { showToast('请å å¨ç¬¬ä¸æ¥å建æéæ©æèµé¡¹ç®æ¡£æ¡'); renderInvestmentWorkflowStep(1); return; } 2399 if (!await checkBpEngine()) { showToast('请å åå»âå¯å¨ç»ä¸å¹³å°.batâå¯å¨æ¬æºåææå¡'); return; } 2400 const token = localStorage.getItem('token'); 2401 const form = new FormData(); 2402 form.append('file', bpSelectedFile); 2403 form.append('project_id', currentInvestmentProject.id); 2404 $('#bpAnalyzeFileButton').disabled = true; 2405 $('#bpAnalyzeFileButton').classList.add('loading'); 2406 $('#bpAnalyzeFileButton').textContent = 'æ£å¨è§£æä¸ä¹ç»´åæâ¦'; 2407 $('#bpAnalysisProgress').textContent = '读åå ¨æå¹¶æ ¸å¯¹è¯æ®ï¼å®æ´åæé常éè¦ 5â15 åéï¼å«å®æ¹æ°æ®æ ¸éªä¸ä¼°å¼æµç®ï¼ï¼è¯·èå¿çå¾ '; 2408 startBpAnalysisAnimation(); 2409 try { 2410 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 2411 // 弿¥ä»»å¡æ¨¡å¼ï¼æäº¤åç«å³è¿å job_idï¼åç轮询åç»æã 2412 // é¿å 1-3 åéé¿è¿æ¥è¢«ç¨æ·ä¾§ç½ç»è®¾å¤ææï¼Failed to fetchï¼ã 2413 const createResp = await fetch(`${BP_API_BASE}/api/upload-and-analyze-async`, { method:'POST', headers, body:form }); 2414 const createPayload = await createResp.json().catch(() => ({})); 2415 if (!createResp.ok) throw new Error(createPayload.detail || 'BPåæä»»å¡å建失败'); 2416 const jobId = createPayload.data.job_id; 2417 const startedAt = Date.now(); 2418 const deadline = startedAt + 25*60*1000; 2419 let payload = null; 2420 while (Date.now() < deadline) { 2421 await new Promise(resolve => setTimeout(resolve, 4000)); 2422 const jobResp = await fetch(`${BP_API_BASE}/api/analysis-jobs/${jobId}`, { headers }); 2423 if (!jobResp.ok) continue; 2424 const job = (await jobResp.json()).data || {}; 2425 if (job.status === 'error') throw new Error(job.detail || 'BPåæå¤±è´¥'); 2426 if (job.status === 'done') { payload = job.result; break; }
2427 const elapsed = Math.round((Date.now() - startedAt) / 1000); 2428 $('#bpAnalysisProgress').textContent = `AI æ£å¨é页åæå ¨æå¹¶å®æå®æ¹æ ¸éªï¼å·²ç¨æ¶ ${elapsed} ç§ï¼é常 5â15 åéï¼â¦`; 2429 } 2430 if (!payload) throw new Error('åææ¶é´è¾é¿ï¼è¯·ç¨åå¨å岿¥å䏿¥çç»æ'); 2431 renderProfessionalBpReport(payload.data.report); 2432 currentInvestmentReportId = payload.data.report_id; 2433 await loadInvestmentProjects(true, currentInvestmentProject.id); 2434 renderInvestmentWorkflowStep(3, true); 2435 $('#bpAnalysisProgress').textContent = `åæå®æ · æ¥å ${payload.data.report_id.slice(0,8)}`; 2436 finishBpAnalysisAnimation(true, 'å·²çæä¹ç»´æ¥åï¼å¹¶ä¿åå®é AI è°ç¨è®°å½'); 2437 showToast('BPä¹ç»´åæå®æï¼å·²ä¿åå°å岿¥å'); 2438 } catch (error) { 2439 $('#bpAnalysisProgress').textContent = error.message; 2440 finishBpAnalysisAnimation(false, error.message); 2441 showToast(error.message); 2442 } finally { 2443 $('#bpAnalyzeFileButton').disabled = false; 2444 $('#bpAnalyzeFileButton').classList.remove('loading'); 2445 $('#bpAnalyzeFileButton').textContent = 'éæ°ä¸ä¼ å¹¶åæ â'; 2446 } 2447} 2448 2449function showToast(message) { 2450 const toast = $('#toast'); 2451 toast.textContent = message; 2452 toast.classList.add('show'); 2453 clearTimeout(showToast.timer); 2454 showToast.timer = setTimeout(() => toast.classList.remove('show'), 2400); 2455} 2456 2457const CAPABILITY_LEVELS = { 2458 ai: { label: 'çå® AI åæ', icon: 'AI', tone: 'ai' }, 2459 rule: { label: 'è§ååç', icon: 'è§', tone: 'rule' }, 2460 workflow: { label: '工使µå·¥å ·', icon: 'æµ', tone: 'workflow' }, 2461 demo: { label: 'æ¼ç¤ºæ ·ä¾', icon: '示', tone: 'demo' }, 2462 unavailable: { label: 'çå®å¼æå¾ æ¥å ¥', icon: 'å¾ ', tone: 'unavailable' } 2463}; 2464 2465const CAPABILITY_REGISTRY = { 2466 command: { level: 'workflow', title: '驾驶è±å·²åºåçå®é¡¹ç®ä¸æ¼ç¤ºæ¡£æ¡', detail: 'åªæå¸¦âçå®é¡¹ç®âæ è¯çæ¡£æ¡åä¸ç»è®¡ï¼æ¼ç¤ºé¡¹ç®ä» ç¨äºå±ç¤ºé¡µé¢ç»æã' }, 2467 opc: { level: 'workflow', title: 'æ¬æºä»»å¡ä¸å³çé¨å·¥ä½æµ', detail: 'ä»»å¡ç¶æä¼çå®ä¿åï¼ä½ä¸ä¸ç»è®ºä»éç±å¯¹åº AIãä¸å®¶åå¤é¨æ°æ®æ¨¡åæä¾ã' }, 2468 investment: { level: 'ai', title: '读åå½å BP åè°ç¨ DeepSeek', detail: '项ç®åæä¸åå²èµæåå±ä½¿ç¨ï¼å¸åºã注ååä¸å©å¤é¨æ°æ®ä»ææ¥æºç¶ææ¾ç¤ºã' }, 2469 policyhub: { level: 'ai', title: 'åºäºéå®ä¼ä¸æ¡£æ¡ä¸æ¿çåæåæ', detail: 'AIè´è´£æè§£åèµ·èï¼æ¿çæææ§ã硬æ¡ä»¶ä¸æäº¤ææä»é¡»éæ¡äººå·¥ç¡®è®¤ã' }, 2470 regulatory: { level: 'rule', title: 'å½åä» çææ³¨åè·¯å¾å设', detail: 'å°æªèªå¨æ£ç´¢ NMPAãCDEãCMDE 宿¹åæï¼ä¸å¾å°æ¬é¡µè¾åºä½ä¸ºæ£å¼åç±»æç³æ¥ä¾æ®ã' }, 2471 patent: { level: 'unavailable', title: 'çå®ä¸å©æ£ç´¢å°æªæ¥å ¥', detail: 'éæ¥å ¥å¯è¿½æº¯ä¸å©æ°æ®åºå¹¶è¿åå ¬å¼å·ãæå©è¦æ±ãæ³å¾ç¶æåæ£ç´¢æ¥æåï¼æè½å½¢æé¡¹ç®ç»è®ºã' }, 2472 flashpoc: { level: 'demo', title: 'å½å为æ¦å¿µæ¹æ³æ ·ä¾', detail: 'å ç½®æ¹æ¡åªå±ç¤ºè®¾è®¡æè·¯ï¼ä¸ä»£è¡¨å·²é对å½å项ç®å®æå·¥ç¨è®¾è®¡ï¼æ¼ç¤ºæ¹æ¡ä¸è½è¿å ¥æ£å¼æ·±åä»»å¡ã' }, 2473 development: { level: 'workflow', title: 'å·¥ç¨é¶æ®µç®¡çæ¡æ¶', detail: 'ç³»ç»ä¸ä¼å模æä»¿ç宿ãDFMéè¿ææ ·æºå®æï¼åªæä¸ä¼ å¹¶å®¡æ ¸çå®äº¤ä»ç©åæè½ç»è®°å®æã' }, 2474 regai: { level: 'unavailable', title: 'æ°ååªç䏿³¨åéªè¯å¼æå°æªæ¥å ¥', detail: 'æªè¿æ¥å妿¨¡åãææåæ°ãç宿£è æ°æ®ååè§éªè¯ç¯å¢ï¼ä¸è½çæâéªè¯éè¿âç»è®ºã' }, 2475 commercial: { level: 'rule', title: 'åä¸åè§åæ¡æ¶', detail: 'å½å没æçå® KOL æ°æ®åºæå»é¢æ¯å¼éæ°æ®ï¼é¡µé¢åªç¨äºç»ç»è°ç ä»»å¡ï¼ä¸æä¾ä¸å®¶æåæèæå¹é çã' }, 2476 intake: { level: 'rule', title: '转åè·¯å¾è§ååç', detail: 'åªä¾æ®è¾å ¥å ³é®è¯è¯å«åéèµéåä¸ä¸æ¥ï¼ä¸çæçä¼¼ç²¾ç¡®ä½æ æ³è§£éç项ç®åæ°ã' }, 2477 resources: { level: 'rule', title: 'éå¢èµæºè§åæ¨è', detail: 'åªå¨å·²ç»è®°èµæºä¸ææç¡®å ³é®è¯æ¨èï¼å¹¶å±ç¤ºè§¦å便®ï¼ä¸ä»£è¡¨èµæºæ¹å·²ç¡®è®¤æ¿æ¥ã' }, 2478 workspace: () => state.aiMode === 'professional' 2479 ? { level: 'ai', title: 'DeepSeek 卿䏴åºè®¿è°', detail: 'æ¯è½®è°ç¨ä¿ç模åãè¯æ®ãä¸ç¡®å®æ§åä¸å®¶å¤æ ¸è¦æ±ï¼ä¸æ¿ä»£ä¸´åºãå·¥ç¨ææ³è§ç¾åã' } 2480 : { level: 'demo', title: 'å½å为è§åæ¼ç¤ºæ¨¡å¼', detail: 'åªä¼å¹é ä¸ç»å ç½®åºæ¯ä¸éç¨é®å·ï¼ä¸åºç¨äºçå®é¡¹ç®å¤æï¼é ç½®å¯é¥åç³»ç»ä¼ä¼å å¯ç¨ä¸ä¸ AIã' } 2481}; 2482 2483function getCapability(view) { 2484 const item = CAPABILITY_REGISTRY[view]; 2485 return typeof item === 'function' ? item() : item; 2486} 2487 2488function renderCapabilityBanner(view = state.currentView) { 2489 const capability = getCapability(view); 2490 const target = $(`#${view}View`); 2491 if (!capability || !target) return; 2492 const level = CAPABILITY_LEVELS[capability.level]; 2493 let banner = $('[data-capability-disclosure]', target); 2494 if (!banner) { 2495 banner = document.createElement('section'); 2496 banner.dataset.capabilityDisclosure = 'true'; 2497 target.prepend(banner); 2498 } 2499 banner.className = `capability-disclosure capability-${level.tone}`; 2500 banner.innerHTML = `<span class="capability-mark">${level.icon}</span><div><b>${level.label}</b><strong>${escapeHtml(capability.title)}</strong><p>${escapeHtml(capability.detail)}</p></div>`; 2501} 2502 2503function createProjectContext(input = {}) { 2504 const previousId = input.newProject ? null : state.projectContext?.id; 2505 state.projectContext = { 2506 id: previousId || `MD-${new Date().toISOString().slice(2,10).replaceAll('-','')}-${String(Math.floor(Math.random() * 900) + 100)}`, 2507 title: input.title || 'å¾ å½å临åºè½¬å项ç®', 2508 description: input.description || '', 2509 summary: input.summary || '', 2510 tags: input.tags || [],
2511 sourceMode: input.sourceMode || 'unverified', 2512 audit: input.audit || {}, 2513 updatedAt: new Date().toISOString() 2514 }; 2515 return state.projectContext; 2516} 2517 2518function updateContextProject(patch = {}) { 2519 if (!state.projectContext?.id) return; 2520 let project = projects.find(item => item.id === state.projectContext.id); 2521 if (!project) { 2522 project = { 2523 icon: 'M', 2524 name: state.projectContext.title || state.projectContext.summary?.slice(0, 24) || '临åºè½¬å项ç®', 2525 id: state.projectContext.id, 2526 stage: 'éæ±æ¾æ¸ ', 2527 progress: 8, 2528 owner: 'å¾ æå®ä¸»ç人', 2529 update: 'æ¥èªå½å临åºè®¿è°', 2530 source: ['ai','rule'].includes(state.projectContext.sourceMode) ? 'active_project' : 'demo' 2531 }; 2532 projects.unshift(project); 2533 } 2534 Object.assign(project, patch); 2535 renderProjects(); 2536} 2537 2538function showCapabilityStop(container, title, detail, requirements = []) { 2539 if (!container) return; 2540 container.classList.remove('hidden'); 2541 container.innerHTML = `<div class="capability-stop-card"><span>å¾ æ¥å ¥</span><h2>${escapeHtml(title)}</h2><p>${escapeHtml(detail)}</p>${requirements.length ? `<ul>${requirements.map(item => `<li>${escapeHtml(item)}</li>`).join('')}</ul>` : ''}<b>æ¬æ¨¡åä¸ä¼çææ¨¡æç»è®ºï¼ä¹ä¸ä¼æ´æ°æ£å¼é¡¹ç®è¿åº¦ã</b></div>`; 2542} 2543
2544function appendRuleBasis(container, triggers, boundary) { 2545 if (!container) return; 2546 container.querySelector('.rule-basis')?.remove(); 2547 const basis = document.createElement('div'); 2548 basis.className = 'rule-basis'; 2549 basis.innerHTML = `<span>è§å便®</span><p>${triggers.length ? triggers.map(item => `<b>${escapeHtml(item)}</b>`).join('') : '<b>æªå½ä¸ä¸ç¨è§å</b>'}</p><small>${escapeHtml(boundary)}</small>`; 2550 container.append(basis); 2551} 2552 2553function initializeTruthfulStaticCopy() { 2554 if ($('#evaluateIntakeButton')) $('#evaluateIntakeButton').textContent = 'å¼å§è½¬åè·¯å¾è§ååç â'; 2555 const intakeLabel = $('.intake-result-head span'); 2556 if (intakeLabel) intakeLabel.textContent = 'è§ååç · 䏿¯é¡¹ç®ä»·å¼è¯å'; 2557 if ($('#intakeScore')) $('#intakeScore').innerHTML = 'è§å<small>åç</small>'; 2558 2559 const resourceBadge = $('.resource-matcher .step-badge'); 2560 if (resourceBadge) resourceBadge.textContent = 'è§'; 2561 const resourceDesc = $('.resource-matcher p'); 2562 if (resourceDesc) resourceDesc.textContent = 'ææç¡®å ³é®è¯ä»å·²ç»è®°çéå¢ä¸åä½èµæºä¸æåºåéæ¹å'; 2563 if ($('#matchResourcesButton')) $('#matchResourcesButton').textContent = 'æè§åæ¨èèµæº â'; 2564 2565 const commandMetrics = $$('.command-portfolio-metrics article'); 2566 if (commandMetrics[1]) { $('strong', commandMetrics[1]).textContent = 'â'; $('span', commandMetrics[1]).textContent = 'æçå®é¡¹ç®ä»»å¡å¨ææ±æ»'; } 2567 if (commandMetrics[2]) { $('strong', commandMetrics[2]).textContent = 'â'; $('span', commandMetrics[2]).textContent = 'å°æªå»ºç«æ¬æçå®åºçº¿'; } 2568 const engineCards = $$('.command-engine-grid .command-engine'); 2569 engineCards.forEach(card => { 2570 const metric = $('div strong', card); 2571 if (!metric) return; 2572 if (card.dataset.viewLink === 'investment') metric.textContent = 'è¿å ¥é¡¹ç®å计ç®'; 2573 if (card.dataset.viewLink === 'policyhub') metric.textContent = 'æä¼ä¸ä¸æ¿ç计ç®'; 2574 if (card.dataset.viewLink === 'regulatory') metric.textContent = 'å°æªæ£ç´¢å®æ¹åæ'; 2575 }); 2576 const evidenceLedger = $('.command-bottom-grid .evidence-ledger'); 2577 if (evidenceLedger) { 2578 const evidenceCount = $('.development-panel-head > span', evidenceLedger); 2579 if (evidenceCount) evidenceCount.textContent = 'éæé项ç®åå'; 2580 $$('.development-panel-head p, .evidence-ledger article small', evidenceLedger).forEach(item => item.textContent = item.textContent.replace(/\d+\s*(份|æ¡|项)/g, 'å¾ æ ¸éª')); 2581 } 2582 2583 const privacy = $('.privacy'); 2584 if (privacy) privacy.textContent = '请å¿è¾å ¥å¯è¯å«æ£è 身份çä¿¡æ¯'; 2585 const exampleTitle = $('.example-title span'); 2586 if (exampleTitle) exampleTitle.textContent = '没æå¤´ç»ªï¼è¯è¯è¿äºæ¼ç¤ºåºæ¯';
2587 const stats = $$('.stats-strip > div'); 2588 const statLabels = ['çå®é¡¹ç®æ°','éæ±ç¡®è®¤ç','æ ·æºå¨æ','ææ¡£æç']; 2589 stats.forEach((item,index) => { 2590 $('strong', item).textContent = 'å¾ å»ºç«'; 2591 $('small', item).textContent = `${statLabels[index]}åºçº¿`; 2592 $('em', item).textContent = 'ä¸å±ç¤ºæ¼ç¤ºæ°å'; 2593 }); 2594 2595 const patentSearchTitle = $('.patent-search h2'); 2596 if (patentSearchTitle) patentSearchTitle.textContent = 'ä¸å©æ£ç´¢çç¥åå¤'; 2597 const patentSearchDesc = $('.patent-search p'); 2598 if (patentSearchDesc) patentSearchDesc.textContent = 'æ´çå½å项ç®å ³é®è¯ï¼ç宿°æ®åºåæ³å¾ç¶ææ£ç´¢å¾ æ¥å ¥'; 2599 if ($('#scanPatentsButton')) $('#scanPatentsButton').textContent = 'æ¥çç宿£ç´¢æ¥å ¥è¦æ±'; 2600 const patentEmptyTitle = $('#patentEmpty h2'); 2601 if (patentEmptyTitle) patentEmptyTitle.textContent = 'çå®ä¸å©æ£ç´¢å¾ æ¥å ¥'; 2602 const patentEmptyDesc = $('#patentEmpty p'); 2603 if (patentEmptyDesc) patentEmptyDesc.textContent = 'ä¸ä¼ä½¿ç¨åºå®æ¨¡æ¿çæé¡¹ç®ä¸å©é£é©æåæ°ç©ºç½ã'; 2604 2605 if ($('#generateConceptsButton')) $('#generateConceptsButton').textContent = 'æ¥çæ¦å¿µæ¹æ³ç¤ºä¾'; 2606 const conceptHeading = $('.design-control-head h2'); 2607 if (conceptHeading) conceptHeading.textContent = 'æ¦å¿µæ¹æ³å·¥ä½å°'; 2608 const conceptDesc = $('.design-control-head p'); 2609 if (conceptDesc) conceptDesc.textContent = 'å½åä» å±ç¤ºå ç½®æ¹æ³æ ·ä¾ï¼ä¸ä»£è¡¨å·²å®æé¡¹ç®å·¥ç¨è®¾è®¡'; 2610 2611 if ($('#advanceMilestoneButton')) { $('#advanceMilestoneButton').disabled = true; $('#advanceMilestoneButton').textContent = 'é¡»æçå®äº¤ä»ç©åç»è®°'; } 2612 if ($('#runTwinButton')) $('#runTwinButton').textContent = 'æ¥ççå®ä»¿çæ¥å ¥è¦æ±'; 2613 const twinConfig = $$('.twin-config > div strong'); 2614 if (twinConfig[0]) twinConfig[0].textContent = 'å°æªè¿æ¥ç宿£è åºæç»ç»åæ°'; 2615 if (twinConfig[1]) twinConfig[1].textContent = 'å¾ å·¥ç¨å¸å®ä¹æ¨¡åãè½½è·ä¸è¾¹çæ¡ä»¶'; 2616 if (twinConfig[2]) twinConfig[2].textContent = 'å¾ ç»å®å½å项ç®å¯éªè¯ç»ç¹'; 2617 if ($('#twinPatientCount')) $('#twinPatientCount').textContent = 'â'; 2618 2619 $$('#algorithmGrid button span').forEach(item => item.textContent = 'åéæ¹å · å°æªè¯ä¼°'); 2620 const kolTitle = $('.kol-panel .development-panel-head strong'); 2621 if (kolTitle) kolTitle.textContent = '⦠KOL åéæ å䏿 ¸éªä»»å¡'; 2622 const kolDesc = $('.kol-panel .development-panel-head p'); 2623 if (kolDesc) kolDesc.textContent = 'æ¥å ¥ææ¥æºä¸è·ææçä¸å®¶æ°æ®åæè½è¿è¡å¹é '; 2624 if ($('#matchKolsButton')) $('#matchKolsButton').textContent = 'æ¥çä¸å®¶æ°æ®æ¥å ¥è¦æ±'; 2625} 2626 2627function aiGatewayBase() { 2628 return window.location.protocol === 'file:' ? 'http://localhost:8787' : window.location.origin; 2629} 2630 2631function updateAiModeUI() { 2632 const professional = state.aiMode === 'professional'; 2633 $('#aiModeButton').classList.toggle('professional', professional); 2634 $('#aiModeButton span').textContent = professional ? 'ä¸ä¸ AI 模å¼' : 'æ¼ç¤ºè§å模å¼'; 2635} 2636 2637async function checkAiService(options = {}) { 2638 const status = $('#aiServiceStatus'); 2639 $('#checkAiServiceButton').disabled = true; 2640 $('#checkAiServiceButton').textContent = 'æ£æµä¸â¦'; 2641 try { 2642 const response = await fetch(`${aiGatewayBase()}/api/health`, { signal: AbortSignal.timeout(5000) }); 2643 const data = await response.json(); 2644 if (!response.ok || !data.ok) throw new Error(data.error || 'æå¡ä¸å¯ç¨'); 2645 state.aiServiceOnline = Boolean(data.ai_configured); 2646 if ($('#homeAiServiceBadge')) $('#homeAiServiceBadge').innerHTML = `<i></i> ${state.aiServiceOnline ? `DeepSeek å·²è¿æ¥ · ${escapeHtml(data.model || '')}` : 'DeepSeek å°æªé ç½®'}`; 2647 if ($('#platformCapabilitySummary')) $('#platformCapabilitySummary').textContent = state.aiServiceOnline ? 'çå®AIãè§åä¸å·¥ä½æµå·²åå±' : 'è§åä¸å·¥ä½æµå¯ç¨ · AIå¾ é ç½®'; 2648 if (state.aiServiceOnline && options.autoSelect) { 2649 let explicitChoice = false; 2650 let savedMode = null; 2651 try { 2652 explicitChoice = localStorage.getItem('medici-ai-mode-explicit') === '1'; 2653 savedMode = localStorage.getItem('medici-ai-mode'); 2654 } catch (_) {} 2655 state.aiMode = explicitChoice && ['demo','professional'].includes(savedMode) ? savedMode : 'professional'; 2656 updateAiModeUI(); 2657 renderCapabilityBanner('workspace'); 2658 } 2659 status.classList.toggle('online', state.aiServiceOnline); 2660 $('strong', status).textContent = state.aiServiceOnline ? 'ç»ä¸ DeepSeek AI å·²è¿æ¥' : 'æå¡å·²å¯å¨ï¼ä½å°æªé ç½® DeepSeek å¯é¥'; 2661 $('small', status).textContent = state.aiServiceOnline ? `模åï¼${data.model} · å·²æ¥å ¥ï¼${(data.ai_domains || []).join('ã')}` : '请å廿¡é¢çâé ç½®DeepSeekå¯é¥âå®æè¿æ¥'; 2662 $('#enableProfessionalAiButton').disabled = !state.aiServiceOnline; 2663 } catch (error) { 2664 state.aiServiceOnline = false;
2665 if ($('#homeAiServiceBadge')) $('#homeAiServiceBadge').innerHTML = '<i></i> DeepSeek æå¡æªè¿æ¥'; 2666 if ($('#platformCapabilitySummary')) $('#platformCapabilitySummary').textContent = 'æ¬æºæå¡æªè¿æ¥'; 2667 status.classList.remove('online'); 2668 $('strong', status).textContent = 'æªæ£æµå°ä¸ä¸ AI æå¡'; 2669 $('small', status).textContent = '请åå»âå¯å¨åå«å¤©åææè½¬åå¹³å°âåéæ°æ£æµ'; 2670 $('#enableProfessionalAiButton').disabled = true; 2671 } finally { 2672 $('#checkAiServiceButton').disabled = false; 2673 $('#checkAiServiceButton').textContent = 'éæ°æ£æµ'; 2674 } 2675} 2676 2677function setAiMode(mode, options = { explicit: true }) { 2678 state.aiMode = mode; 2679 try { 2680 localStorage.setItem('medici-ai-mode', mode); 2681 if (options.explicit) localStorage.setItem('medici-ai-mode-explicit', '1'); 2682 } catch (_) {} 2683 updateAiModeUI(); 2684 renderCapabilityBanner('workspace'); 2685} 2686 2687async function initializeAiMode() { 2688 let savedMode = null; 2689 let explicitChoice = false; 2690 try { 2691 savedMode = localStorage.getItem('medici-ai-mode'); 2692 explicitChoice = localStorage.getItem('medici-ai-mode-explicit') === '1'; 2693 } catch (_) {} 2694 if (explicitChoice && ['demo','professional'].includes(savedMode)) state.aiMode = savedMode; 2695 updateAiModeUI(); 2696 await checkAiService({ autoSelect: true }); 2697} 2698 2699async function callProfessionalAI(payload) { 2700 const response = await fetch(`${aiGatewayBase()}/api/clinical/interview`, { 2701 method: 'POST', 2702 headers: { 'Content-Type': 'application/json' }, 2703 body: JSON.stringify(payload) 2704 }); 2705 const data = await response.json(); 2706 if (!response.ok) throw new Error(data.error || 'ä¸ä¸ AI æå¡è°ç¨å¤±è´¥'); 2707 return data; 2708} 2709 2710function normalizeProfessionalData(data) { 2711 return { 2712 summary: data.summary || 'æ£å¨å½¢æä¸ä¸é®é¢å®ä¹ã', 2713 tags: data.tags || [], 2714 mappings: (data.mappings || []).map(item => [item.clinical, item.engineering, item.target]), 2715 constraints: (data.constraints || []).map(item => [item.label, item.value]), 2716 tests: (data.tests || []).map(item => ['â', `${item.name}ï¼${item.acceptance}`]), 2717 questions: data.question?.q ? [{ q:data.question.q, why:data.question.why, options:data.question.options || [], insight:{} }] : [], 2718 citations: data.citations || [], 2719 uncertainties: data.uncertainties || [], 2720 expertReview: data.expert_review || [], 2721 audit: data._audit || {}, 2722 professional: true, 2723 sourceMode: 'ai' 2724 }; 2725} 2726 2727async function runProfessionalStart(text) { 2728 $('#resultEmpty').classList.add('hidden'); 2729 $('#resultContent').classList.add('hidden'); 2730 $('#resultLoading').classList.remove('hidden'); 2731 $('#loadingText').textContent = 'æ£å¨è°ç¨ DeepSeek çè§£ä¸´åºæè¿°å¹¶è¯å«å ³é®åéâ¦'; 2732 $('#loadingBar').style.width = '45%'; 2733 $('#analyzeButton').disabled = true; 2734 try { 2735 const response = await callProfessionalAI({ description:text, transcript:[], mode:'start' }); 2736 const data = normalizeProfessionalData(response); 2737 createProjectContext({ 2738 description: text, 2739 summary: data.summary, 2740 tags: data.tags, 2741 sourceMode: 'ai', 2742 audit: data.audit 2743 }); 2744 updateContextProject({ stage: 'AIéæ±è®¿è°', progress: 12, update: '已宿é¦è½® DeepSeek åæ', source: 'ai_project' }); 2745 renderAnalysis(data); 2746 state.aiSession = { description:text, transcript:[] }; 2747 $('#confidenceValue').textContent = `${response.confidence || 45}%`; 2748 const modelName = data.audit.model || 'DeepSeek'; 2749 $('#aiEvidenceText').textContent = data.citations.length 2750 ? `æ¬è½®å·²è°ç¨ ${modelName}ï¼è¿å ${data.citations.length} æ¡ä¾æ®ï¼ä»æ ${data.uncertainties.length} 项ä¸ç¡®å®æ§ï¼ç»è®ºéä¸å®¶å®¡æ ¸ã` 2751 : `æ¬è½®å·²è°ç¨ ${modelName}ï¼å½å没æè¿å坿 ¸éªå¤é¨ä¾æ®ï¼å æ¤å¿ é¡»ä¿çä¸ç¡®å®æ§å¹¶ç±ä¸å®¶å¤æ ¸ã`; 2752 $('#resultLoading').classList.add('hidden'); 2753 $('#resultContent').classList.remove('hidden'); 2754 } catch (error) { 2755 $('#resultLoading').classList.add('hidden'); 2756 $('#resultEmpty').classList.remove('hidden'); 2757 showToast(`ä¸ä¸ AI è°ç¨å¤±è´¥ï¼${error.message}`); 2758 $('#aiConfigModal').classList.remove('hidden'); 2759 } finally { 2760 $('#analyzeButton').disabled = false;
2761 } 2762} 2763 2764function selectScenario(text) { 2765 const normalized = text.toLowerCase(); 2766 let best = null; 2767 let score = 0; 2768 scenarioLibrary.forEach(item => { 2769 const current = item.match.filter(word => normalized.includes(word.toLowerCase())).length; 2770 if (current > score) { score = current; best = item; } 2771 }); 2772 return score ? best : fallbackScenario; 2773} 2774 2775function renderAnalysis(data) { 2776 state.analysis = data; 2777 state.answers = {}; 2778 state.questionIndex = 0; 2779 state.interviewComplete = false; 2780 state.answerPending = false; 2781 state.patent = { scanned: false, confirmed: false, outputMode: 'unavailable' }; 2782 state.flashpoc = { projectId: state.projectContext?.id || null, generated: false, selected: null, confirmed: false, outputMode: 'demo' }; 2783 state.regai = { validated: false, documentsGenerated: false, outputMode: 'unavailable' }; 2784 state.commercial = { algorithm: null, kolsMatched: false, completed: false, outputMode: 'planning' }; 2785 $('#resultSummary').textContent = data.summary; 2786 $('#resultTags').innerHTML = data.tags.map(tag => `<span>${escapeHtml(tag)}</span>`).join(''); 2787 $('#mappingCount').textContent = data.mappings.length; 2788 $('#constraintCount').textContent = data.constraints.length; 2789 $('#testCount').textContent = data.tests.length; 2790 $('.confidence').innerHTML = data.professional 2791 ? 'AIå¤æç½®ä¿¡åº¦ <b id="confidenceValue">58%</b>' 2792 : 'è§åè¦ç度 <b id="confidenceValue">ä» ä¾æ¼ç¤º</b>'; 2793 $('#mappingPane').innerHTML = data.mappings.map(row => `<div class="mapping-row"><span>${escapeHtml(row[0])}</span><i>â</i><div><strong>${escapeHtml(row[1])}</strong><em>${escapeHtml(row[2])}</em></div></div>`).join(''); 2794 $('#constraintsPane').innerHTML = `<div class="constraint-grid">${data.constraints.map(row => `<div class="constraint-item"><small>${escapeHtml(row[0])}</small><strong>${escapeHtml(row[1])}</strong></div>`).join('')}</div>`; 2795 $('#testsPane').innerHTML = `<div class="test-grid">${data.tests.map(row => `<div class="test-item"><span>${escapeHtml(row[0])}</span><strong>${escapeHtml(row[1])}</strong></div>`).join('')}</div>`; 2796 $('#conversationHistory').innerHTML = ''; 2797 $('#interviewCard').classList.remove('completed'); 2798 $('#aiEvidenceBar').classList.toggle('hidden', !data.professional); 2799 renderCapabilityBanner('workspace'); 2800 $('#currentQuestion').classList.remove('hidden'); 2801 $('#interviewComplete').classList.add('hidden'); 2802 $('#structuredResults').classList.add('hidden'); 2803 $('#generateReportButton').disabled = true; 2804 $('#generateReportButton span').textContent = 'å®æè®¿è°åçæè§æ ¼ä¹¦'; 2805 $('#generateReportButton small').textContent = 'å½å夿尿ªæ¶æ'; 2806 renderCurrentQuestion(); 2807} 2808 2809function renderCurrentQuestion() { 2810 if (!state.analysis) return; 2811 const total = state.analysis.questions.length; 2812 if (state.questionIndex >= total) { 2813 completeInterview(); 2814 return; 2815 } 2816 const question = state.analysis.questions[state.questionIndex]; 2817 const answered = Object.keys(state.answers).length; 2818 $('#questionProgress').textContent = state.analysis.professional ? `第 ${answered + 1} é® Â· å¨æè®¿è°` : `第 ${state.questionIndex + 1} / ${total} é®`; 2819 $('#interviewProgressBar').style.width = state.analysis.professional ? `${Math.min(88, 12 + answered * 16)}%` : `${Math.max(8, state.questionIndex / total * 100)}%`; 2820 $('#questionReason').textContent = question.why; 2821 $('#questionTitle').textContent = question.q; 2822 $('#questionOptions').innerHTML = question.options.map(option => `<button data-interview-answer="${escapeHtml(option)}">${escapeHtml(option)}</button>`).join(''); 2823 $('#freeAnswerInput').value = ''; 2824 $('#freeAnswerInput').placeholder = state.questionIndex === 0 ? 'ä¹å¯ä»¥è¡¥å æ´å ·ä½çä¸´åºæ åµâ¦' : 'ä¸è¿°é项ä¸åç¡®ï¼è¯·ç´æ¥æè¿°â¦'; 2825} 2826 2827function appendConversation(question, answer, insight) { 2828 const history = $('#conversationHistory'); 2829 const exchange = document.createElement('div'); 2830 exchange.className = 'conversation-exchange'; 2831 const doctor = document.createElement('div'); 2832 doctor.className = 'doctor-answer'; 2833 const doctorLabel = document.createElement('span'); 2834 doctorLabel.textContent = 'å»çåç'; 2835 const doctorText = document.createElement('p'); 2836 doctorText.textContent = answer; 2837 doctor.append(doctorLabel, doctorText); 2838 const judgment = document.createElement('div'); 2839 judgment.className = 'medici-judgment'; 2840 const judgmentLabel = document.createElement('span'); 2841 judgmentLabel.textContent = 'ç¾ç¬¬å¥å¤æ'; 2842 const judgmentText = document.createElement('p'); 2843 judgmentText.textContent = insight; 2844 judgment.append(judgmentLabel, judgmentText); 2845 exchange.append(doctor, judgment); 2846 history.append(exchange); 2847 history.scrollTop = history.scrollHeight; 2848} 2849 2850function applyProfessionalFinal(response) { 2851 const data = normalizeProfessionalData(response);
2852 state.analysis = data; 2853 createProjectContext({ 2854 description: state.aiSession?.description || state.projectContext?.description || '', 2855 summary: data.summary, 2856 tags: data.tags, 2857 sourceMode: 'ai', 2858 audit: data.audit 2859 }); 2860 $('#resultSummary').textContent = data.summary; 2861 $('#resultTags').innerHTML = data.tags.map(tag => `<span>${escapeHtml(tag)}</span>`).join(''); 2862 $('#mappingCount').textContent = data.mappings.length; 2863 $('#constraintCount').textContent = data.constraints.length; 2864 $('#testCount').textContent = data.tests.length; 2865 $('#mappingPane').innerHTML = data.mappings.map(row => `<div class="mapping-row"><span>${escapeHtml(row[0])}</span><i>â</i><div><strong>${escapeHtml(row[1])}</strong><em>${escapeHtml(row[2])}</em></div></div>`).join(''); 2866 $('#constraintsPane').innerHTML = `<div class="constraint-grid">${data.constraints.map(row => `<div class="constraint-item"><small>${escapeHtml(row[0])}</small><strong>${escapeHtml(row[1])}</strong></div>`).join('')}</div>`; 2867 $('#testsPane').innerHTML = `<div class="test-grid">${data.tests.map(row => `<div class="test-item"><span>${escapeHtml(row[0])}</span><strong>${escapeHtml(row[1])}</strong></div>`).join('')}</div>`; 2868 $('#confidenceValue').textContent = `${response.confidence || 82}%`; 2869 $('#aiEvidenceText').textContent = `${data.audit.model || 'DeepSeek'} 已形æ ${data.mappings.length} é¡¹å·¥ç¨æ å°ã${data.citations.length} æ¡ç¥è¯ä¾æ®å ${data.expertReview.length} 项ä¸å®¶å¤æ ¸è¦æ±ã`; 2870 completeInterview(); 2871} 2872 2873async function submitProfessionalAnswer(answer) { 2874 const cleanAnswer = answer.trim(); 2875 if (!cleanAnswer || !state.aiSession || state.answerPending) return; 2876 const question = state.analysis.questions[0]; 2877 state.answerPending = true; 2878 $$('#questionOptions button').forEach(button => button.disabled = true); 2879 $('#sendAnswerButton').disabled = true; 2880 try { 2881 const transcript = [...state.aiSession.transcript, { question:question.q, answer:cleanAnswer }]; 2882 const response = await callProfessionalAI({ description:state.aiSession.description, transcript, mode:'continue' }); 2883 const insight = response.judgment || '已记å½è¯¥ä¸´åºè¾¹çï¼åç»å°æ®æ¤æ¶çªå·¥ç¨åéã'; 2884 const answerIndex = transcript.length - 1; 2885 state.answers[answerIndex] = { question:question.q, answer:cleanAnswer, insight }; 2886 state.aiSession.transcript = transcript; 2887 appendConversation(question.q, cleanAnswer, insight); 2888 $('#confidenceValue').textContent = `${response.confidence || 60}%`; 2889 if (response.status === 'complete' || !response.question?.q) { 2890 applyProfessionalFinal(response); 2891 } else { 2892 state.analysis.summary = response.summary || state.analysis.summary; 2893 state.analysis.tags = response.tags || state.analysis.tags; 2894 state.analysis.questions = [{ q:response.question.q, why:response.question.why, options:response.question.options || [], insight:{} }]; 2895 state.questionIndex = 0; 2896 $('#resultSummary').textContent = state.analysis.summary; 2897 $('#resultTags').innerHTML = state.analysis.tags.map(tag => `<span>${tag}</span>`).join(''); 2898 renderCurrentQuestion(); 2899 } 2900 } catch (error) { 2901 showToast(`ä¸ä¸è®¿è°å¤±è´¥ï¼${error.message}`); 2902 } finally { 2903 state.answerPending = false; 2904 $$('#questionOptions button').forEach(button => button.disabled = false); 2905 $('#sendAnswerButton').disabled = false; 2906 } 2907} 2908 2909function submitInterviewAnswer(answer) { 2910 if (state.aiMode === 'professional' && state.analysis?.professional) { 2911 submitProfessionalAnswer(answer); 2912 return; 2913 } 2914 const cleanAnswer = answer.trim(); 2915 if (!cleanAnswer || !state.analysis || state.interviewComplete || state.answerPending) return; 2916 state.answerPending = true; 2917 const question = state.analysis.questions[state.questionIndex]; 2918 const insight = question.insight[cleanAnswer] || `å·²å°â${cleanAnswer}âè®°å½ä¸ºå½å临åºè¾¹çãè¿ä¸ªä¿¡æ¯ä¼ç¨äºæ¶çªè®¾è®¡åéï¼å¹¶å¨å·¥ç¨éªè¯é¶æ®µè®¾ç½®å¯¹åºçå¯¹ç §æ¡ä»¶ã`; 2919 state.answers[state.questionIndex] = { question: question.q, answer: cleanAnswer, insight }; 2920 appendConversation(question.q, cleanAnswer, insight); 2921 state.questionIndex += 1; 2922 $('#confidenceValue').textContent = `${state.questionIndex}/${state.analysis.questions.length}项è§å`; 2923 $('#currentQuestion').classList.add('question-changing'); 2924 setTimeout(() => { 2925 $('#currentQuestion').classList.remove('question-changing'); 2926 state.answerPending = false;
2927 renderCurrentQuestion(); 2928 }, 260); 2929} 2930 2931function completeInterview() { 2932 state.interviewComplete = true; 2933 const total = state.analysis.professional ? Object.keys(state.answers).length : state.analysis.questions.length; 2934 const confirmedAnswers = Object.values(state.answers).map(item => item.answer); 2935 $('#questionProgress').textContent = `${total} / ${total} 已宿`; 2936 $('#interviewProgressBar').style.width = '100%'; 2937 $('#interviewCard').classList.add('completed'); 2938 $('#currentQuestion').classList.add('hidden'); 2939 $('#interviewComplete').classList.remove('hidden'); 2940 $('#structuredResults').classList.remove('hidden'); 2941 $('#resultSummary').textContent = `${state.analysis.summary} ç»è®¿è°ç¡®è®¤çå ³é®è¾¹çï¼${confirmedAnswers.join('ã')}ã`; 2942 const confirmedConstraints = [ 2943 ...state.analysis.constraints, 2944 ...Object.values(state.answers).map((item, index) => [`访è°ç¡®è®¤ ${index + 1}`, item.answer]) 2945 ]; 2946 $('#constraintCount').textContent = confirmedConstraints.length; 2947 $('#constraintsPane').innerHTML = `<div class="constraint-grid">${confirmedConstraints.map(row => `<div class="constraint-item"><small>${row[0]}</small><strong>${row[1]}</strong></div>`).join('')}</div>`; 2948 $('#generateReportButton').disabled = false; 2949 const isProfessional = state.analysis.professional; 2950 $('.interview-complete strong').textContent = isProfessional ? '临åºè¯è¨è®¿è°å·²å®æ' : 'è§åæ¼ç¤ºé®å·å·²å®æ'; 2951 $('.interview-complete p').textContent = isProfessional 2952 ? 'ç³»ç»å·²æ ¹æ®å¤è½®åçæ¶æè®¾è®¡è¾¹çï¼ä»éå»å·¥ä¸å®¶ç¡®è®¤å ³é®åæ°ã' 2953 : 'å½åç»ææ¥èªå ç½®åºæ¯æ¨¡æ¿ï¼åªç¨äºä½éªæµç¨ï¼ä¸è½ä½ä¸ºé¡¹ç®è§æ ¼è¾å ¥ã'; 2954 $('#generateReportButton span').textContent = isProfessional ? 'çæãåå§è®¾è®¡éæ±è§æ ¼ä¹¦ã' : 'æ¥çè§åæ¼ç¤ºè§æ ¼ä¹¦'; 2955 $('#generateReportButton small').textContent = isProfessional ? 'å·²çº³å ¥æ¬è½®è®¿è°ç»è®º' : 'ä» ä¾çé¢ä¸æ¹æ³æ¼ç¤º'; 2956 showToast(isProfessional ? '访è°å®æï¼å·¥ç¨æ å°å·²æ ¹æ®åçæ¶æ' : 'è§åæ¼ç¤ºå®æï¼æ¬ç»æä¸ä¼è¿å ¥æ£å¼é¡¹ç®æ¡£æ¡'); 2957} 2958 2959function runAnalysis() { 2960 const input = $('#clinicalInput'); 2961 const text = input.value.trim(); 2962 if (text.length < 8) { 2963 input.focus(); 2964 showToast('请å ç¨ä¸å¥è¯æè¿°å ·ä½ç临åºçç¹'); 2965 return; 2966 } 2967 if (state.aiMode === 'professional') { 2968 runProfessionalStart(text); 2969 return; 2970 } 2971 const scenario = selectScenario(text); 2972 createProjectContext({ 2973 description: text, 2974 summary: scenario.summary, 2975 tags: scenario.tags, 2976 sourceMode: 'rule_demo', 2977 audit: { method: 'å ç½®å ³é®è¯åºæ¯å¹é ', externalEvidence: false } 2978 }); 2979 updateContextProject({ stage: 'è§åæ¼ç¤º', progress: 0, update: 'æ¼ç¤ºç»æä¸è®¡å ¥æ£å¼è¿åº¦', source: 'demo' }); 2980 $('#resultEmpty').classList.add('hidden'); 2981 $('#resultContent').classList.add('hidden'); 2982 $('#resultLoading').classList.remove('hidden'); 2983 $('#analyzeButton').disabled = true; 2984 const steps = [ 2985 [18, 'è¿è¡å ç½®å ³é®è¯è§åâ¦'], 2986 [43, 'å¹é æ¼ç¤ºåºæ¯æ¨¡æ¿â¦'], 2987 [72, 'ç»ç»ç¤ºä¾å·¥ç¨æ å°â¦'], 2988 [94, 'çæè§åæ¼ç¤ºé®é¢â¦'] 2989 ]; 2990 let current = 0; 2991 const timer = setInterval(() => { 2992 const [progress, message] = steps[current]; 2993 $('#loadingBar').style.width = `${progress}%`; 2994 $('#loadingText').textContent = message; 2995 $$('.result-loading li').forEach((li, index) => li.classList.toggle('active', index <= current)); 2996 current += 1; 2997 if (current === steps.length) { 2998 clearInterval(timer); 2999 setTimeout(() => { 3000 renderAnalysis({ ...scenario, sourceMode: 'rule_demo', professional: false }); 3001 $('#resultLoading').classList.add('hidden'); 3002 $('#resultContent').classList.remove('hidden'); 3003 $('#analyzeButton').disabled = false; 3004 }, 350); 3005 } 3006 }, 360); 3007} 3008 3009function showReport() { 3010 if (!state.analysis) return; 3011 if (!state.interviewComplete) { 3012 showToast('请å å®æéæ¥æ¾æ¸ 访è°ï¼åçæç²¾åè§æ ¼ä¹¦'); 3013 return; 3014 } 3015 $('#reportId').textContent = `ææ¡£ç¼å·ï¼MD-${new Date().toISOString().slice(0,10).replaceAll('-', '')}-${String(Math.floor(Math.random() * 900) + 100)}`; 3016 $('#reportSummary').textContent = state.analysis.summary; 3017 $('#reportMappings').innerHTML = state.analysis.mappings.map(row => `<div class="mapping-row"><span>${row[0]}</span><i>â</i><div><strong>${row[1]}</strong><em>${row[2]}</em></div></div>`).join(''); 3018 const reportConstraints = $('#reportConstraints'); 3019 reportConstraints.innerHTML = ''; 3020 const confirmedItems = [ 3021 ...state.analysis.constraints.map(row => `${row[0]}ï¼${row[1]}`),
3022 ...Object.values(state.answers).map((item, index) => `访è°ç¡®è®¤ ${index + 1}ï¼${item.answer}`) 3023 ]; 3024 confirmedItems.forEach(item => { 3025 const chip = document.createElement('span'); 3026 chip.textContent = item; 3027 reportConstraints.append(chip); 3028 }); 3029 $('#reportModal').classList.remove('hidden'); 3030} 3031 3032function hydratePatent() { 3033 const summary = state.projectContext?.summary || state.analysis?.summary || 'å°æªç»å®å½å项ç®ã请å å¨ä¸´åºè¯è¨ç¿»è¯æºä¸å»ºç«é¡¹ç®é®é¢å®ä¹ã'; 3034 const tags = state.projectContext?.tags || state.analysis?.tags || []; 3035 $('#patentRequirementSummary').textContent = summary; 3036 $('#patentQueryCount').textContent = tags.length ? `${tags.length} 个项ç®è¯` : 'å°æªçæ'; 3037 $('#patentQueryChips').innerHTML = tags.length 3038 ? tags.map(tag => `<span>${escapeHtml(tag)}</span>`).join('') 3039 : '<span>çå¾ é¡¹ç®æ ç¾</span>'; 3040 renderCapabilityBanner('patent'); 3041} 3042 3043function scanPatentLandscape() { 3044 $('#patentLoading').classList.add('hidden'); 3045 $('#patentResults').classList.add('hidden'); 3046 $('#confirmPatentPathButton').classList.add('hidden'); 3047 showCapabilityStop( 3048 $('#patentEmpty'), 3049 'çå®ä¸å©æ£ç´¢å°æªæ¥å ¥', 3050 '为é¿å æåºå®é³å£æ¨¡æ¿è¯¯å½ä½å½å项ç®ä¸å©ç»è®ºï¼æ¬æ¬¡ä¸ä¼çæé£é©ç级æâä¸å©ç©ºç½âã', 3051 ['æ¥å ¥å½å®¶ç¥è¯äº§æå±æåä¸ä¸å©æ°æ®åº', 'è¿åå ¬å¼å·ãç³è¯·äººãæå©è¦æ±çæ®µä¸æ³å¾ç¶æ', 'ä¿åæ£ç´¢å¼ãæ°æ®åºèå´åæ£ç´¢æ¥æ', 'ç±ä¸å©ä»£çå¸å¤æ ¸èªç±å®æ½ä¸å¯ä¸å©æ§'] 3052 ); 3053 $('#scanPatentsButton').textContent = 'çå®ä¸å©æ¥å£å¾ æ¥å ¥'; 3054 showToast('å·²åæ¢æ¨¡æä¸å©ç»è®ºï¼è¯·å æ¥å ¥å¯è¿½æº¯ä¸å©æ°æ®æº'); 3055} 3056 3057function confirmPatentPath() { 3058 showToast('没æå¯æ ¸éªçä¸å©æ£ç´¢ç»æï¼ä¸è½ç¡®è®¤ä¸å©è·¯å¾'); 3059} 3060 3061function getActiveConcepts() { 3062 const contextText = [ 3063 ...(state.projectContext?.tags || state.analysis?.tags || []), 3064 state.projectContext?.summary || state.analysis?.summary || '', 3065 state.projectContext?.description || '' 3066 ].join(' '); 3067 if (/(骨ç§|骨é»|é»å|ç®è´¨éª¨|å¨åå·¥å ·)/.test(contextText)) return conceptLibraries.orthopedic; 3068 if (/(康å¤|å¼å¸è®ç»|å± å®¶è®ç»|æ£è ä¾ä»æ§)/.test(contextText)) return conceptLibraries.rehab; 3069 if (/(è ¹è é|æé³|é³å£|夹æå¨æ¢°)/.test(contextText)) return conceptLibraries.laparoscopic; 3070 return []; 3071} 3072 3073function getFlashContext() { 3074 const tags = state.projectContext?.tags || state.analysis?.tags || []; 3075 const text = `${tags.join(' ')} ${state.projectContext?.summary || state.analysis?.summary || ''}`; 3076 if (/(骨ç§|骨é»|å¨åå·¥å ·)/.test(text)) { 3077 return { goal: 'è¯å«ç®è´¨éª¨ç©¿éå¹¶éä½è½¯ç»ç»æä¼¤', boundary: 'ååº â¤ 20 ms / å䏿 éå®å ¨' }; 3078 } 3079 if (/(康å¤|å¼å¸è®ç»|ä¾ä»æ§)/.test(text)) { 3080 return { goal: 'æåå± å®¶è®ç»è´¨é䏿£è ä¾ä»æ§', boundary: 'ä½è®¤ç¥è´è· / 离线å¯ç¨' }; 3081 } 3082 if (/(è ¹è é|æé³|é³å£|夹æå¨æ¢°)/.test(text)) { 3083 return { goal: 'æå夹æç¨³å®æ§å¹¶ä¿æ¤ç»ç»', boundary: 'å ¥è·¯å°ºå¯¸ä¸ç»ç»å®å ¨é宿µç¡®è®¤' }; 3084 } 3085 return { goal: 'çå¾ çå®é¡¹ç®è®¾è®¡è¾å ¥', boundary: 'å°æªå½¢æå¯æ ¸éªçå·¥ç¨è¾¹ç' }; 3086} 3087 3088function hydrateFlashPOC() { 3089 if (!state.flashpoc.projectId) { 3090 state.flashpoc.projectId = state.projectContext?.id || `FP-DEMO-${new Date().toISOString().slice(2, 10).replaceAll('-', '')}`; 3091 } 3092 const confirmedAnswers = Object.values(state.answers).map(item => item.answer); 3093 const summary = state.analysis 3094 ? `${state.analysis.summary}${confirmedAnswers.length ? ` 已确认ï¼${confirmedAnswers.join('ã')}ã` : ''}` 3095 : 'å°æªç»å®é¡¹ç®ã请å 宿䏴åºè¯è¨è®¿è°ï¼åè¿å ¥æ¦å¿µæ¹æ³å·¥ä½å°ã'; 3096 const context = getFlashContext(); 3097 $('#flashRequirementSummary').textContent = summary; 3098 $('#flashProjectId').textContent = state.flashpoc.projectId; 3099 $('#flashConstraintCount').textContent = `${(state.analysis?.constraints.length || 4) + confirmedAnswers.length} 项`; 3100 $('#flashConfidence').textContent = state.analysis?.professional ? $('#confidenceValue').textContent : 'æ¼ç¤ºè¾å ¥'; 3101 $('#flashDesignGoal').textContent = context.goal; 3102 $('#flashHardBoundary').textContent = context.boundary; 3103 renderCapabilityBanner('flashpoc'); 3104} 3105 3106function setDesignStep(activeStep) { 3107 $$('.design-step').forEach(step => { 3108 const number = Number(step.dataset.designStep); 3109 step.classList.toggle('done', number < activeStep); 3110 step.classList.toggle('active', number === activeStep); 3111 const icon = $('i', step); 3112 icon.textContent = number < activeStep ? 'â' : String(number); 3113 }); 3114} 3115 3116function renderConcepts() { 3117 const concepts = getActiveConcepts(); 3118 if (!concepts.length) { 3119 $('#conceptLoading').classList.add('hidden'); 3120 $('#conceptResults').classList.add('hidden'); 3121 showCapabilityStop($('#conceptEmpty'), '没æéç¨äºå½å项ç®çæ¼ç¤ºæ¨¡æ¿', 'ç³»ç»å·²åæ¶âæªç¥é¡¹ç®é»è®¤å¥ç¨è ¹è éæé³æ¹æ¡âçé»è¾ãç宿¦å¿µè®¾è®¡å¼ææ¥å ¥åï¼æ¬é¡¹ç®ä¸ä¼çææ¹æ¡ã', ['ç±å·¥ç¨å¸ç¡®è®¤äº§å身份å颿ç¨é', 'å½¢æå¯è¿½æº¯è®¾è®¡è¾å ¥', 'æ¥å ¥ç»æçæä¸å·¥ç¨è®¡ç®è½å']); 3122 $('#generateConceptsButton').disabled = false;
3123 $('#generateConceptsButton').textContent = 'ç宿¦å¿µå¼æå¾ æ¥å ¥'; 3124 return; 3125 } 3126 $('#conceptGrid').innerHTML = concepts.map((concept, index) => ` 3127 <button class="concept-card" data-concept-index="${index}" data-tone="${concept.tone}"> 3128 <div class="concept-visual"><span>${concept.symbol}</span></div> 3129 <span class="concept-type">æ¹æ³æ¼ç¤º · ${concept.type}</span> 3130 <h3>${concept.title}</h3> 3131 <p>${concept.desc}</p> 3132 <div class="concept-tradeoff">${concept.tags.map(tag => `<span>${tag}</span>`).join('')}</div> 3133 <div class="concept-score"><span>项ç®è¯å</span><b>ä¸è¯å</b></div> 3134 </button>`).join(''); 3135 $('#conceptLoading').classList.add('hidden'); 3136 $('#conceptResults').classList.remove('hidden'); 3137 $('#generateConceptsButton').disabled = false; 3138 $('#generateConceptsButton').textContent = 'â» éæ°æ¥çæ¹æ³ç¤ºä¾'; 3139 state.flashpoc.generated = true; 3140 state.flashpoc.outputMode = 'demo'; 3141 $('#designTaskStatus').textContent = 'æ¹æ³æ¼ç¤º'; 3142 $('#confirmConceptButton').disabled = true; 3143 $('#confirmConceptButton').textContent = 'æ¼ç¤ºæ¹æ¡ä¸è½å建æ£å¼ä»»å¡'; 3144 setDesignStep(3); 3145} 3146 3147function generateConcepts() { 3148 state.flashpoc.selected = null; 3149 state.flashpoc.confirmed = false; 3150 $('#conceptEmpty').classList.add('hidden'); 3151 $('#conceptResults').classList.add('hidden'); 3152 $('#conceptDetail').classList.add('hidden'); 3153 $('#confirmConceptButton').disabled = true; 3154 $('#confirmConceptButton').textContent = 'æ¼ç¤ºæ¹æ¡ä¸è½å建æ£å¼ä»»å¡'; 3155 $('#conceptLoading').classList.remove('hidden'); 3156 $('#generateConceptsButton').disabled = true; 3157 $('#generateConceptsButton').textContent = 'æ£å¨å è½½æ¹æ³ç¤ºä¾â¦'; 3158 $('#designTaskStatus').textContent = 'æ¼ç¤ºå è½½ä¸'; 3159 setDesignStep(2); 3160 const progressSteps = [ 3161 [24, '读åå½åé¡¹ç®æ ç¾â¦'], 3162 [49, 'å¹é å ç½®æ¹æ³ç¤ºä¾â¦'], 3163 [76, 'æ è®°æªéªè¯å·¥ç¨åæ°â¦'], 3164 [96, 'åå¤é项ç®ç»è®ºç示ä¾å¡çâ¦'] 3165 ]; 3166 let current = 0; 3167 const timer = setInterval(() => { 3168 const [progress, label] = progressSteps[current]; 3169 $('#conceptLoadingBar').style.width = `${progress}%`; 3170 $('#conceptLoadingText').textContent = label; 3171 current += 1; 3172 if (current === progressSteps.length) { 3173 clearInterval(timer); 3174 setTimeout(renderConcepts, 280); 3175 } 3176 }, 340); 3177} 3178 3179function selectConcept(index) { 3180 const concept = getActiveConcepts()[index]; 3181 if (!concept) return; 3182 state.flashpoc.selected = index; 3183 $$('.concept-card').forEach((card, cardIndex) => card.classList.toggle('selected', cardIndex === index)); 3184 $('#selectedConceptType').textContent = concept.type; 3185 $('#selectedConceptTitle').textContent = concept.title; 3186 $('#selectedConceptRationale').textContent = concept.rationale; 3187 $('#selectedConceptParams').innerHTML = concept.params.map(param => `<span>${param}</span>`).join(''); 3188 $('#conceptDetail').classList.remove('hidden'); 3189 $('#designTaskStatus').textContent = 'æ¹å已鿩'; 3190 $('#conceptDetail').scrollIntoView({ behavior: 'smooth', block: 'nearest' }); 3191} 3192 3193function confirmConcept() { 3194 if (state.flashpoc.outputMode !== 'verified') { 3195 showToast('å½åä» ä¸ºæ¹æ³æ¼ç¤ºï¼ä¸è½å建æ£å¼æ·±åä»»å¡'); 3196 return; 3197 } 3198 if (state.flashpoc.selected === null) { 3199 showToast('请å ä»ä¸ç»æ¹æ¡ä¸éæ©ä¸ä¸ªæ·±åæ¹å'); 3200 return; 3201 } 3202 const concept = getActiveConcepts()[state.flashpoc.selected]; 3203 state.flashpoc.confirmed = true; 3204 setDesignStep(4); 3205 $('#designTaskStatus').textContent = 'æ·±åä¸'; 3206 $('#confirmConceptButton').disabled = true; 3207 $('#confirmConceptButton').textContent = 'æ·±åä»»å¡å·²å建 â'; 3208 $('#estimatedDelivery').textContent = 'é¢è®¡ 48 å°æ¶'; 3209 updateContextProject({ stage: 'æ¦å¿µè®¾è®¡', progress: 42, update: 'å·²ç¡®è®¤ç»æ ¸éªçæ¦å¿µæ¹å' }); 3210 state.development = { 3211 created: true, 3212 taskId: `DEV-${new Date().toISOString().slice(2, 10).replaceAll('-', '')}-${String(Math.floor(Math.random() * 900) + 100)}`, 3213 phase: 0, 3214 concept, 3215 projectId: state.flashpoc.projectId, 3216 activities: [ 3217 { icon: 'â', title: 'æ·±åä»»å¡å·²å建', detail: `å·²éå®â${concept.title}âä½ä¸ºå·¥ç¨æ·±ååºçº¿ã`, time: 'åå' }, 3218 { icon: 'â', title: '设计è¾å ¥å·²åæ¥', detail: 'Medici 约æã访è°çæ¡åæ¦å¿µåæ°å·²åå ¥ä»»å¡å ã', time: 'åå' } 3219 ] 3220 }; 3221 persistDevelopmentTask(); 3222 $('#developmentNavCount').textContent = '1'; 3223 switchView('development'); 3224 showToast(`â${concept.title}âæ·±åä»»å¡å·²å建ï¼å·²è¿å ¥ä»»å¡ä¸å¿`); 3225} 3226 3227function exportConceptBrief() { 3228 if (state.flashpoc.selected === null) return; 3229 const concept = getActiveConcepts()[state.flashpoc.selected]; 3230 const content = [ 3231 'FlashPOC æ¦å¿µè®¾è®¡ç®æ¥', 3232 `项ç®ç¼å·ï¼${state.flashpoc.projectId}`, 3233 `é宿¹åï¼${concept.type} / ${concept.title}`, 3234 '', 3235 '临åºè®¾è®¡é»è¾ï¼', 3236 concept.rationale, 3237 '', 3238 'å ³é®å·¥ç¨åæ°ï¼', 3239 ...concept.params.map(param => `- ${param}`) 3240 ].join('\n'); 3241 const url = URL.createObjectURL(new Blob([content], { type: 'text/plain;charset=utf-8' })); 3242 const link = document.createElement('a'); 3243 link.href = url; 3244 link.download = `${state.flashpoc.projectId}-æ¦å¿µè®¾è®¡ç®æ¥.txt`; 3245 link.click(); 3246 URL.revokeObjectURL(url); 3247 showToast('æ¦å¿µè®¾è®¡ç®æ¥å·²å¯¼åº'); 3248} 3249 3250const developmentPhases = [ 3251 { progress: 36, title: 'å ³é®åæ°å»ç»å®æ', detail: 'å·²çæå ³é®åæ°è¡¨ v1.0ï¼å¤¹æåãæ²çä¸ç»ç»å强边çè¿å ¥åæ§ç¶æã', output: 'å ³é®åæ°è¡¨', file: 'å ³é®åæ°è¡¨-v1.0.txt', hint: 'åæ°å·²å»ç»ï¼ä¸ä¸æ¥å°è¿è¡åå䏿¥è§¦å强仿çã' }, 3252 { progress: 62, title: 'æ°å仿ç计ç®å®æ', detail: '已宿æ åå·¥åµãæ¹¿ææ»ç§»åå³°å¼è½½è·ä»¿çï¼æªåç°ä¸å¯æ¥åé£é©ã', output: 'æ°åä»¿çæ¥å', file: 'æ°åä»¿çæ¥å-v0.8.txt', hint: '仿çç»æå·²æ¶æï¼ä¸ä¸æ¥è¿å ¥å¯å¶é æ§åææå·¥èºè¯å®¡ã' }, 3253 { progress: 84, title: 'DFM ä¸å·¥èºè¯å®¡éè¿', detail: 'å·²ç¡®å®æå°ææãè£ é å ¬å·®ä¸æ¯æçç¥ï¼BOM åå¶é å å·²çæã', output: 'å¶é å ä¸ BOM', file: 'å¶é å ä¸BOM-v0.9.txt', hint: 'å¶é æä»¶å·²éæ¾ï¼ä¸ä¸æ¥å®æå¤æè´¨æ ·æºå¶ä½ä¸è§¦æéªè¯ã' }, 3254 { progress: 100, title: 'é¦ä»¶æ ·æºå¶é 宿', detail: 'å¤æè´¨è§¦ææ ·æºå·²å®æè£ é æ£æ¥ï¼çå¾ å»çè¿è¡é¦è½®ææè¯å®¡ã', output: 'æ ·æºè¯å®¡è®°å½', file: 'æ ·æºè¯å®¡è®°å½-v1.0.txt', hint: 'å ¨é¨æ·±åé¶æ®µå·²å®æï¼æ ·æºå·²è¿å ¥å»çè¯å®¡éåã' } 3255]; 3256 3257function hydrateDevelopment() { 3258 const task = state.development; 3259 $('#developmentEmpty').classList.toggle('hidden', task.created); 3260 $('#developmentContent').classList.toggle('hidden', !task.created); 3261 $('#developmentStatusText').textContent = task.created 3262 ? (task.evidenceMode === 'verified' ? (task.phase >= 4 ? 'æ ·æºå¾ å»çè¯å®¡' : 'å·¥ç¨æ·±åè¿è¡ä¸') : 'å岿¼ç¤ºä»»å¡ · å·²é离') 3263 : 'çå¾ çå®è®¾è®¡è¾å ¥'; 3264 renderCapabilityBanner('development'); 3265 if (!task.created) return; 3266 const concept = task.concept || getActiveConcepts()[state.flashpoc.selected] || getActiveConcepts()[0] || { title:'æªæ ¸éªæ¦å¿µè®°å½', type:'å岿¼ç¤º' }; 3267 $('#developmentTaskCode').textContent = task.taskId; 3268 $('#developmentConceptTitle').textContent = concept.title; 3269 $('#developmentConceptSubtitle').textContent = `${concept.type} · ${task.projectId || state.flashpoc.projectId || 'FlashPOC 项ç®'}`; 3270 renderDevelopmentTask(); 3271} 3272 3273function renderDevelopmentTask() { 3274 const task = state.development; 3275 const verified = task.evidenceMode === 'verified'; 3276 const progressValues = [12, 36, 62, 84, 100]; 3277 const progress = progressValues[task.phase]; 3278 $('#developmentProgressValue').textContent = `${progress}%`; 3279 $('#developmentProgressBar').style.width = `${progress}%`; 3280 $('#currentMilestoneLabel').textContent = task.phase >= 4 ? '4 / 4 å ¨é¨å®æ' : `é¶æ®µ ${task.phase + 1} / 4`; 3281 $$('.milestone').forEach((milestone, index) => { 3282 const isDone = index < task.phase; 3283 const isActive = index === task.phase && task.phase < 4; 3284 milestone.classList.toggle('done', isDone); 3285 milestone.classList.toggle('active', isActive); 3286 const status = $('b', milestone); 3287 status.textContent = verified ? (isDone ? '已宿' : isActive ? 'è¿è¡ä¸' : 'çå¾ ') : (isDone ? 'æ¼ç¤ºè®°å½' : 'å¾ ç宿æ'); 3288 $('i', milestone).textContent = isDone ? 'â' : String(index + 1).padStart(2, '0'); 3289 }); 3290 const hints = [ 3291 'å½åæ£å¨å»ç»å ³é®å·¥ç¨åæ°ã', 3292 'å½åæ£å¨æ§è¡ååãæ¥è§¦åå¼ºä¸æç«¯å·¥åµä»¿çã', 3293 'å½åæ£å¨è¿è¡å¯å¶é æ§ãææåè£ é å ¬å·®è¯å®¡ã', 3294 'å½åæ£å¨å¶ä½å¤æè´¨æ ·æºå¹¶åå¤å»ç触æè¯å®¡ã', 3295 'å ¨é¨æ·±åé¶æ®µå·²å®æï¼æ ·æºå·²è¿å ¥å»çè¯å®¡éåã' 3296 ]; 3297 $('#milestoneActionHint').textContent = verified ? hints[task.phase] : 'å·²åæ¢æ¨¡æå®æé¶æ®µã请ä¸ä¼ å¹¶å®¡æ ¸çå®åæ°è¡¨ãä»¿çæ¥åãDFMè®°å½ææ ·æºè¯å®¡è®°å½ååç»è®°ã'; 3298 $('#advanceMilestoneButton').disabled = true; 3299 $('#advanceMilestoneButton').textContent = 'é¡»æçå®äº¤ä»ç©åç»è®°'; 3300 $('#enterRegAIButton').classList.add('hidden'); 3301 $('#advanceMilestoneButton').classList.remove('hidden'); 3302 $('#developmentStatusText').textContent = verified ? (task.phase >= 4 ? 'æ ·æºå¾ å»çè¯å®¡' : 'å·¥ç¨æ·±åè¿è¡ä¸') : 'å岿¼ç¤ºä»»å¡ · ä¸è®¡å ¥æ£å¼æ¡£æ¡'; 3303 $('#deliverableCount').textContent = verified ? `${task.phase} / 4 å·²çæ` : '0 / 4 å·²æ ¸éª'; 3304 $$('#deliverableList button').forEach((button, index) => { 3305 const ready = index < task.phase; 3306 button.classList.toggle('ready', ready); 3307 $('small', button).textContent = verified && ready ? `${developmentPhases[index].file} · 坿¥ç` : (ready ? 'å岿¼ç¤ºè®°å½ · ä¸å¯ä½ä¸ºäº¤ä»ç©' : 'çå¾ ä¸ä¼ å¹¶å®¡æ ¸ç宿件'); 3308 $('b', button).textContent = verified && ready ? 'æ¥ç' : 'æªæ ¸éª'; 3309 }); 3310 $('#developmentActivityList').innerHTML = task.activities.map(item => `<div class="activity-item"><i>${item.icon}</i><div><strong>${item.title}</strong><p>${item.detail}</p></div><time>${item.time}</time></div>`).join(''); 3311} 3312 3313function advanceDevelopmentMilestone() { 3314 const task = state.development; 3315 if (task.evidenceMode !== 'verified') { 3316 showToast('å·²åæ¢æ¨¡ææ¨è¿ï¼è¯·å ä¸ä¼ å¹¶å®¡æ ¸çå®å·¥ç¨äº¤ä»ç©'); 3317 return; 3318 } 3319 if (!task.created || task.phase >= 4) return; 3320 const completed = developmentPhases[task.phase]; 3321 task.activities.unshift({ icon: 'â', title: completed.title, detail: completed.detail, time: 'åå' }); 3322 task.phase += 1; 3323 updateContextProject({ progress: [42, 55, 68, 86, 100][task.phase], stage: task.phase >= 4 ? 'æ ·æºè¯å®¡' : 'ç»ææ·±å', update: completed.title }); 3324 persistDevelopmentTask(); 3325 renderDevelopmentTask(); 3326 showToast(`${completed.output}å·²çæï¼ä»»å¡è¿åº¦æ´æ°ä¸º ${developmentPhases[task.phase - 1].progress}%`); 3327} 3328 3329function downloadDeliverable(index) { 3330 if (state.development.evidenceMode !== 'verified') { 3331 showToast('å岿¼ç¤ºè®°å½å·²é离ï¼ä¸è½ä½ä¸ºçå®å·¥ç¨äº¤ä»ç©å¯¼åº'); 3332 return; 3333 } 3334 if (index >= state.development.phase) { 3335 showToast('该交ä»ç©å°æªçæï¼è¯·å 宿坹åºéç¨ç¢'); 3336 return; 3337 } 3338 const concept = state.development.concept || getActiveConcepts()[state.flashpoc.selected] || getActiveConcepts()[0]; 3339 const phase = developmentPhases[index]; 3340 const content = `${phase.output}\nä»»å¡ç¼å·ï¼${state.development.taskId}\næ·±åæ¹åï¼${concept.title}\n\n${phase.detail}\n\næ¬æä»¶ç± FlashPOC MVP çæã`; 3341 const url = URL.createObjectURL(new Blob([content], { type: 'text/plain;charset=utf-8' })); 3342 const link = document.createElement('a'); 3343 link.href = url; 3344 link.download = phase.file; 3345 link.click(); 3346 URL.revokeObjectURL(url); 3347 showToast(`${phase.output}已导åº`); 3348} 3349 3350function persistDevelopmentTask() { 3351 try { 3352 localStorage.setItem('medici-development-task', JSON.stringify(state.development)); 3353 } catch (_) { 3354 // Local files may disable storage in strict browser modes; the in-session task remains available. 3355 } 3356} 3357 3358function restoreDevelopmentTask() { 3359 try { 3360 const saved = JSON.parse(localStorage.getItem('medici-development-task')); 3361 if (!saved?.created || !saved.taskId) return; 3362 state.development = { ...saved, evidenceMode: saved.evidenceMode || 'legacy_demo' }; 3363 state.flashpoc.projectId = saved.projectId || null; 3364 $('#developmentNavCount').textContent = '1'; 3365 const legacyProject = projects.find(item => item.id === saved.projectId); 3366 if (legacyProject) legacyProject.update = 'åç°å岿¼ç¤ºä»»å¡ï¼å·²é离'; 3367 } catch (_) { 3368 // Ignore invalid or unavailable local storage. 3369 } 3370} 3371 3372function hydrateRegAI() { 3373 const ready = state.development.created && state.development.phase >= 4; 3374 $('#regaiEmpty').classList.toggle('hidden', ready); 3375 $('#regaiContent').classList.toggle('hidden', !ready); 3376 renderCapabilityBanner('regai'); 3377 if (!ready) return; 3378 const concept = state.development.concept || getActiveConcepts()[0] || { title:'æªæ ¸éªåå²ä»»å¡' }; 3379 $('#regaiProjectSummary').textContent = `${concept.title}çå岿¼ç¤ºè®°å½å·²è¯å«ï¼ä½å°æªå½¢æå¯ä¾éªè¯çåæ§å·¥ç¨è¾å ¥ã`; 3380 state.regai.validated = false;
3381 state.regai.documentsGenerated = false; 3382 $('#generateRegDocsButton').disabled = true; 3383 $('#enterCommercialButton').classList.add('hidden'); 3384} 3385 3386function runTwinValidation() { 3387 $('#twinLoading').classList.add('hidden'); 3388 $('#twinStatus').textContent = 'çå®å¼æå¾ æ¥å ¥'; 3389 showCapabilityStop( 3390 $('#twinResults'), 3391 'ä¸è½è¿è¡æ¨¡ææ°ååªçéªè¯', 3392 'å½å没æå 使¨¡åãææåæ°ãè¾¹çæ¡ä»¶ãæ±è§£å¨æç宿£è æ°æ®ãç³»ç»ä¸ä¼åç¨è¿åº¦å¨ç»çæâéªè¯éè¿âç»æã', 3393 ['å¯¼å ¥åæ§ CAD / ç½æ ¼ä¸ææåæ°', 'ç±å·¥ç¨å¸å®ä¹è½½è·åè¾¹çæ¡ä»¶', 'æ¥å ¥ç»éªè¯çå妿±è§£å¨', 'ä¿å模åçæ¬ãè¾å ¥æä»¶ãæ¥å¿åå®¡æ ¸ç¾å'] 3394 ); 3395 $('#runTwinButton').textContent = 'çå®ä»¿çç¯å¢å¾ æ¥å ¥'; 3396 $('#generateRegDocsButton').disabled = true; 3397 showToast('å·²åæ¢æ¨¡æéªè¯ï¼æ²¡æç宿¨¡åä¸è®¡ç®æ¥å¿ï¼ä¸çæéè¿ç»è®º'); 3398} 3399 3400function showTwinResults() { 3401 runTwinValidation(); 3402} 3403 3404function generateRegDocuments() { 3405 showToast('缺å°çå®éªè¯è¯æ®ï¼ä¸è½çæåè§ææ¡£å '); 3406} 3407 3408function markRegDocumentsReady() { 3409 if (state.regai.outputMode !== 'verified') { 3410 showToast('æ¼ç¤ºéªè¯è®°å½å·²é离ï¼ä¸è½æ è®°åè§ææ¡£ä¸ºå·²çæ'); 3411 return; 3412 } 3413 const names = ['DHF-v1.0.txt','é£é©ç®¡çæ¥å-v0.9.txt','èæéªè¯æ¥å-v1.0.txt','临åºè¯ä»·æ¡æ¶-v0.8.txt']; 3414 $$('#regDocGrid button').forEach((button,index) => { 3415 button.classList.add('ready'); 3416 $('small',button).textContent = `${names[index]} · å¯å¯¼åº`; 3417 $('b',button).textContent = 'æ¥ç'; 3418 }); 3419 $('#generateRegDocsButton').disabled = false; 3420 $('#generateRegDocsButton').textContent = 'â» éæ°çæææ¡£å '; 3421 $('#regaiCompleteness').textContent = '94%'; 3422 $('#enterCommercialButton').classList.remove('hidden'); 3423 updateContextProject({ stage:'注ååå¤', progress:94, update:'ç»æ ¸éªçåè§ææ¡£å å·²çæ' }); 3424} 3425 3426function downloadRegDocument(index) { 3427 if (!state.regai.documentsGenerated) { showToast('请å è¿è¡éªè¯å¹¶çæåè§ææ¡£å '); return; } 3428 const names = ['设计å岿件 DHF','é£é©ç®¡çæ¥å','èææ§è½ä¸å®å ¨æ¥å','临åºè¯ä»·æ¥åæ¡æ¶']; 3429 const content = `${names[index]}\n项ç®ï¼${state.projectContext?.title || state.projectContext?.id || 'å½å项ç®'}\nçæ¬ï¼å¾ å®¡æ ¸èæ¡\n\nä» å¯çº³å ¥å·²æ ¸éªçéæ±ã设计å³çãå·¥ç¨åæ´è®°å½ä¸éªè¯è¯æ®ã\n\n注æï¼æ£å¼ç³æ¥åéç±æ³è§è´è´£äººå®¡æ ¸å¹¶ç¾åã`;
3430 const url = URL.createObjectURL(new Blob([content],{type:'text/plain;charset=utf-8'})); 3431 const link = document.createElement('a'); link.href=url; link.download=`${names[index]}.txt`; link.click(); URL.revokeObjectURL(url); 3432 showToast(`${names[index]}已导åº`); 3433} 3434 3435function hydrateCommercial() { 3436 const concept = state.development.concept || getActiveConcepts()[0]; 3437 $('#commercialProjectTitle').textContent = concept?.title || state.projectContext?.title || 'å°æªç»å®ç»æ ¸éªé¡¹ç®'; 3438 renderCapabilityBanner('commercial'); 3439} 3440 3441function selectAlgorithm(name) { 3442 state.commercial.algorithm = name; 3443 $$('#algorithmGrid button').forEach(button => button.classList.toggle('selected',button.dataset.algorithm===name)); 3444 $('#algorithmSelectionStatus').textContent = 'è§åå设'; 3445 $('#selectedAlgorithmName').textContent = name; 3446 const plans = { 3447 'ååé¦é¢è¦':'è§åå设ï¼å ç¡®è®¤ç®æ å¨ä½ä¸é£é©äºä»¶ï¼åè®¾è®¡ä¼ æééãæ æ³¨è§åã临åºå®å ¨éå¼ååç»æ§éªè¯æ¹æ¡ï¼å½åæªè¯ä¼°é¡¹ç®éé 度ã', 3448 'ç»ç»ç±»åè¾ å©è¯å«':'è§åå设ï¼å å®ä¹ä¸´åºç¨éãç®æ ç»ç»ç±»å«åé误åæï¼åå»ºç«æææçæ°æ®éä¸ç¬ç«éªè¯éï¼å½åæªè¯ä¼°é¡¹ç®éé 度ã', 3449 'æ¯ä¸æä½è´¨éåæ':'è§åå设ï¼å ç±ä¸´åºä¸å®¶å®ä¹å ³é®å¨ä½åå¯è§£éè´¨éç»ç¹ï¼åè®¾è®¡æ°æ®ééä¸éªè¯ï¼å½åæªè¯ä¼°é¡¹ç®éé 度ã' 3450 }; 3451 $('#algorithmPlanText').textContent = plans[name]; 3452 $('#algorithmPlan').classList.remove('hidden'); 3453 updateCommercialReady(); 3454} 3455 3456function matchKols() { 3457 state.commercial.kolsMatched = false; 3458 $('#kolResults').classList.add('hidden'); 3459 showCapabilityStop( 3460 $('#kolEmpty'), 3461 'çå® KOL æ°æ®åºå°æªæ¥å ¥', 3462 'ç³»ç»ä¸ä¼åèæä¸å®¶å§åãææ¯éæå¹é çãæ¥å ¥åè§æ°æ®å¹¶è·å¾ææåï¼åªè½å»ºç«åéä¸å®¶çéæ åã', 3463 ['æç¡®ç®æ ç§å®¤ãæ¯å¼ä¸åä½ä»»å¡', 'å¯¼å ¥ææ¥æºä¸è·ææçä¸å®¶èµæ', 'æ ¸éªä»»èãç ç©¶æ¹ååå使æ¿', 'ç±ä¸å¡è´è´£äººç¡®è®¤èç³»ä¸åä½å®æ'] 3464 ); 3465 $('#matchKolsButton').textContent = 'çå®ä¸å®¶æ°æ®å¾ æ¥å ¥'; 3466 updateCommercialReady(); 3467 showToast('å·²åæ¢æ¨¡æä¸å®¶å¹é ï¼ä¸ä¼çæèæå§å
3467åå¹é ç'); 3468} 3469 3470function updateCommercialReady() { 3471 $('#completeJourneyButton').disabled = !(state.commercial.algorithm && state.commercial.kolsMatched); 3472} 3473 3474function completeCommercialJourney() { 3475 showToast('缺å°çå® KOLãå¸åºä¸éªè¯æ°æ®ï¼ä¸è½çææ£å¼åä¸åæ¹æ¡'); 3476} 3477 3478function exportAlgorithmPlan() { 3479 if (!state.commercial.algorithm) return; 3480 const content = `ç®æ³è½åè°ç ä»»å¡èæ¡\n项ç®ï¼${state.projectContext?.title || state.projectContext?.id || 'å½å项ç®'}\nåéè½åï¼${state.commercial.algorithm}\n\n${$('#algorithmPlanText').textContent}\n\nè¾¹çï¼æ¬æä»¶æ¯è§åå设ï¼ä¸æ¯é¡¹ç®éé æ§ç»è®ºã`; 3481 const url=URL.createObjectURL(new Blob([content],{type:'text/plain;charset=utf-8'}));const link=document.createElement('a');link.href=url;link.download='AIç®æ³éæè·¯çº¿.txt';link.click();URL.revokeObjectURL(url); 3482} 3483 3484function runInvestmentReview() { 3485 if (!currentInvestmentProject) { showToast('请å 建ç«é¡¹ç®æ¡£æ¡ï¼åè¿è¡æè¦åç'); renderInvestmentWorkflowStep(1); return; } 3486 const text = $('#investmentSummary').value.trim(); 3487 if (text.length < 30) { $('#investmentSummary').focus(); showToast('请补å 产åãè¿å±ãå¢éåèèµéæ±çæ ¸å¿ææ'); return; } 3488 const dimensions = [ 3489 ['éæ±é»è¾', ['çç¹','æ£è ','临åº','éæ±','ç åä½'], 14], 3490 ['转åé»è¾', ['æ ·å','åå','å®éª','ç产','产ä¸å'], 11], 3491 ['ä¸´åºæ³¨åé»è¾', ['注å','临åº','æ£éª','nmpa'], 9], 3492 ['å¸åºé»è¾', ['å¸åº','客æ·','å»é¢','éå®'], 12], 3493 ['ç«äºé»è¾', ['ç«å','ç«äº','æ¿ä»£','åç±»'], 11], 3494 ['åä¸åé»è¾', ['å®ä»·','æ¶å ¥','æ¸ é','åä¸'], 9], 3495 ['å¢éé»è¾', ['å¢é','髿 ¡','å»é¢','è´è´£äºº'], 13], 3496 ['å£åé»è¾', ['ä¸å©','ç¥è¯äº§æ','æ°æ®','å·¥èº'], 12], 3497 ['忥é»è¾', ['èèµ','ä¼°å¼','éåº','ç°éæµ'], 9] 3498 ]; 3499 let weighted = 0; 3500 let evidenceHits = 0; 3501 const scored = dimensions.map(([name, keywords, weight]) => { 3502 const hits = keywords.filter(keyword => text.toLowerCase().includes(keyword)).length; 3503 evidenceHits += hits; 3504 const score = 5; 3505 const coverage = Math.min(100, Math.round(hits / keywords.length * 100)); 3506 const uncertainty = coverage >= 75 ? 1.3 : coverage >= 50 ? 2 : 2.8; 3507 weighted += score * weight / 100; 3508 return { name, score:score.toFixed(1), potential_score:score, score_range:{low:Number(Math.max(0,score-uncertainty).toFixed(1)),high:Number(Math.min(10,score+uncertainty).toFixed(1))}, evidence_coverage:coverage, hits, evidence:hits ? `æè¦ä¸å½ä¸ ${hits} 个ç¸å ³è¯ï¼å°æªå½¢æä¸ä¸ä»·å¼å¤æ` : 'BP䏿ªæåç¸å ³å 容', strengths:[], weaknesses:['æè¦è§åä¸è¶³ä»¥å¤æè¯¥ç»´åº¦ççå®é¡¹ç®ä»·å¼ï¼éAIå ¨æåææä¸å®¶æ ¸éª'], score_reason:`å½åä» ææè¦çº¿ç´¢ï¼éç¨5å䏿§å ä½ï¼ä¸å°ææç¼ºé¡¹è§£é为项ç®ç¼ºé·ï¼åçåºé´${Math.max(0,score-uncertainty).toFixed(1)}â${Math.min(10,score+uncertainty).toFixed(1)}åã`, score_audit:{method:'æè¦çº¿ç´¢è¯å« · ä¸è¯é¡¹ç®ä»·å¼',confidence:'ä½',evidence_coverage:coverage,criteria:[{criterion:'æ¯å¦å·²æå¯åæçº¿ç´¢',max_points:10,awarded_points:hits ? 5 : null,status:hits ? 'æåéè¿°' : 'å¾ éªè¯',reason:hits ? 'æè¦åºç°ç¸å ³è¯è¯ï¼ä½ä¸è½æ®æ¤å¤æé¡¹ç®ä»·å¼ã' : 'æè¦æªæè¿°ï¼ä¸å æ¤ç»é¡¹ç®æ£é¶åã',evidence_quote:hits ? `å½ä¸${hits}个ç¸å ³è¯` : '',data_status:hits ? 'ä» æè¦çº¿ç´¢' : 'æªç¥'}],boundary:'æè¦åçåªæ´ç线索åé®é¢ï¼ä¸è¾åºæå³çæä¹ç项ç®ä»·å¼å'}}; 3509 }); 3510 const missing = { 3511 'ä¸å©æå±': !/(ä¸å©|ç¥è¯äº§æ|æå±)/.test(text), 3512 '坿 ¸éªå®éªæ°æ®': !/(å®éª|æ ·å|æ°æ®|åå)/.test(text), 3513 '注åè·¯å¾ä¸é¢ç®': !/(注å|临åº|æ£éª|é¢ç®)/.test(text), 3514 'è´¢å¡é¢æµä¸èµéç¨é': !/(æ¶å ¥|ç°éæµ|èèµ|èµéç¨é|é¢ç®)/.test(text) 3515 }; 3516 const redFlags = Object.entries(missing).filter(([, absent]) => absent).map(([name]) => name); 3517 const coverage = Math.min(100, Math.round(evidenceHits / 36 * 100)); 3518 $('#investmentConfidence').textContent = `æè¦çº¿ç´¢è¦ç ${coverage}% · ä»·å¼ç½®ä¿¡åº¦ä½`; 3519 $('#investmentScore').innerHTML = `${weighted.toFixed(1)}<small>/10</small>`; 3520 $('#investmentVerdict').textContent = 'å¾ AIå ¨æç å¤ï¼æè¦ä» å»ºç«æ ¸éªæ¸ å'; 3521 $('#investmentJudgment').textContent = `æè¦ä¸è¯å«å° ${evidenceHits} 项ç¸å ³çº¿ç´¢ï¼5åæ¯ä¿¡æ¯ä¸è¶³æ¶ç䏿§å ä½ï¼ä¸ä»£è¡¨é¡¹ç®ä¸è¬ï¼æ´ä¸ä¼å BP没åèå¤é¡¹ç®æ²¡æä»·å¼ã`; 3522 currentBpDimensions = scored; 3523 $('#investmentResult').classList.remove('hidden'); 3524 currentInvestmentAnalysis = {overall_score:Number(weighted.toFixed(1)),potential_score:Number(weighted.toFixed(1)),score_range:{low:2.2,high:7.8},evidence_coverage:coverage,evidence_confidence:'ä½',recommendation:$('#investmentVerdict').textContent,summary:$('#investmentJudgment').textContent,dimensions:scored,risks:redFlags.map(description=>({level:'å¾ å®',category:'å¾ éªè¯',description})),scoring_audit:{method:'æè¦çº¿ç´¢è¯å« · ä¸è¯é¡¹ç®ä»·å¼'}}; 3525 renderInvestmentReviewChecklist(); 3526 renderInvestmentWorkflowStep(3, true); 3527 $('#investmentResult').scrollIntoView({behavior:'smooth',block:'nearest'}); 3528} 3529 3530async function investmentApiRequest(path, options = {}) { 3531 const token = localStorage.getItem('token'); 3532 const headers = {...(options.headers || {})}; 3533 if (token) headers.Authorization = `Bearer ${token}`; 3534 const response = await fetch(`${BP_API_BASE}${path}`, {...options,headers}); 3535 const raw = await response.text(); 3536 let payload; 3537 try { payload = JSON.parse(raw); } catch { throw new Error(`æèµé¡¹ç®æå¡è¿åå¼å¸¸ï¼${raw.slice(0,100)}`); } 3538 if (!response.ok) throw new Error(payload.detail || payload.error || 'æèµé¡¹ç®æä½å¤±è´¥'); 3539 return payload.data; 3540} 3541 3542function enterpriseArray(value) { 3543 return Array.isArray(value) ? value.filter(Boolean) : []; 3544} 3545 3546function renderEnterpriseTags(items, limit = 8) { 3547 const values = enterpriseArray(items).slice(0, limit); 3548 return values.length ? `<div class="base-enterprise-tags">${values.map(item => `<span>${escapeHtml(item)}</span>`).join('')}</div>` : '<span class="base-enterprise-missing">èµææªæä¾</span>'; 3549} 3550 3551function renderEnterpriseMatches(target, matches) { 3552 const node = typeof target === 'string' ? $(target) : target; 3553 if (!node) return; 3554 node.innerHTML = enterpriseArray(matches).length ? matches.map(item => `<article class="enterprise-match-card" data-company-id="${escapeHtml(item.company_id)}"><header><span><small>${escapeHtml(item.category || 'åç±»å¾ æ ¸éª')}</small><strong>${escapeHtml(item.company_name)}</strong></span><b>${escapeHtml(item.score)}<em>/100</em></b></header><div>${renderEnterpriseTags(item.matched_terms, 6)}</div><ul>${enterpriseArray(item.reasons).map(reason => `<li>${escapeHtml(reason)}</li>`).join('')}</ul>${enterpriseArray(item.matched_products).length ? `<p><b>ç¸å ³äº§åï¼</b>${escapeHtml(item.matched_products.join('ï¼'))}</p>` : ''}<footer><span>${escapeHtml(item.level)}</span><button class="outline-button" type="button" data-open-base-company="${escapeHtml(item.company_id)}">æ¥çä¼ä¸ç»å</button></footer></article>`).join('') : '<div class="base-enterprise-no-match"><span class="no-match-icon">ð</span><strong>ææªåç°è¾¾å°éå¼çä¼ä¸å ³è</strong><p>è¿ä¸ä»£è¡¨åºå°å 没æåéä¼ä¸ââå¯è½æ¯BPå¯¹ææ¯è·¯çº¿ä¸äº§åå ³é®è¯æè¿°ä¸è¶³ï¼æä¼ä¸åºå¯¹åºæ ç¾åæ®µå°æªè¡¥é½ã</p><ul class="no-match-tips"><li>å¨BPä¸è¡¥å ææ¯è·¯çº¿ãéåºçä¸äº§åå ³é®è¯åéæ°åæ</li><li>å°ä¼ä¸åºè¡¥é½ä¼ä¸æ ç¾ä¸äº§åææ¯åæ®µ</li><li>éå½éä½å¹é éå¼ï¼æç¨åéè¯å ³èåæ</li><li>ç¹å»å³ä¸è§âæå¼å®æ´ä¼ä¸åºâç´æ¥æµè§å ¨é¨ä¼ä¸</li></ul></div>'; 3555} 3556 3557function renderBaseEnterpriseStats(stats) { 3558 baseEnterpriseStats = stats || {}; 3559 const metrics = [ 3560 ['ä¼ä¸', stats.company_count || 0, 'å·²åå¹¶ååè®°å½'], 3561 ['产å/ææ¯è®°å½', stats.product_count || 0, `${stats.category_count || 0}个产ä¸åç±»`], 3562 ['æåä½éæ±', stats.companies_with_needs || 0, 'å¯è¿ä¸æ¥ç¡®è®¤ææ¿'], 3563 ['æå ±äº«èµæº', stats.companies_with_resources || 0, 'å¹³å°è½åå¾ æ ¸éª'], 3564 ]; 3565 $('#baseEnterpriseMetrics').innerHTML = metrics.map(item => `<article><small>${item[0]}</small><strong>
3565${Number(item[1]).toLocaleString()}</strong><span>${item[2]}</span></article>`).join(''); 3566 const categories = Object.keys(stats.category_counts || {}); 3567 $('#baseEnterpriseCategory').innerHTML = '<option value="">å ¨é¨åç±»</option>' + categories.map(item => `<option>${escapeHtml(item)}</option>`).join(''); 3568} 3569 3570function baseEnterpriseQueryPath() { 3571 const params = new URLSearchParams(); 3572 const query = $('#baseEnterpriseSearch')?.value.trim(); 3573 const category = $('#baseEnterpriseCategory')?.value; 3574 const stage = $('#baseEnterpriseStage')?.value; 3575 if (query) params.set('query', query); 3576 if (category) params.set('category', category); 3577 if (stage) params.set('stage', stage); 3578 params.set('limit', '500'); 3579 return `/api/base-enterprises?${params.toString()}`; 3580} 3581 3582// ---------- åºå°ä¼ä¸åæ°å¨æ ---------- 3583let baseEnterpriseNewsExpanded = false; 3584 3585function renderBaseEnterpriseNews(payload) { 3586 const body = $('#baseEnterpriseNewsBody'); 3587 if (!body) return; 3588 const updatedNode = $('#baseEnterpriseNewsUpdated'); 3589 if (updatedNode) updatedNode.textContent = payload?.updated_at ? `æ´æ°äº ${payload.updated_at}` : 'æ´æ°äº â'; 3590 const days = enterpriseArray(payload?.days).slice(0, 14); 3591 if (!days.length) { 3592 body.innerHTML = '<div class="base-enterprise-no-match"><strong>仿¥å¨ææåä»»å¡å°æªè¿è¡ï¼ææ 卿</strong><p>ç³»ç»æ¯æ¥èªå¨æ£ç´¢åºå°ä¼ä¸å ¬å¼æ°é»å卿¤å±ç¤ºï¼æ°é»æ¥èªå ¬å¼ç½ç»æ£ç´¢ï¼ä» ä¾ç ç©¶åèã</p></div>'; 3593 return; 3594 } 3595 const shown = baseEnterpriseNewsExpanded ? days : days.slice(0, 1); 3596 body.innerHTML = shown.map(day => { 3597 const items = enterpriseArray(day.items); 3598 const groups = {}; 3599 items.forEach(item => { const cat = item.category || 'å ¶ä»'; (groups[cat] = groups[cat] || []).push(item); }); 3600 const groupHtml = Object.entries(groups).map(([cat, list]) => `<div class="base-news-group"><strong class="base-news-category">${escapeHtml(cat)}<span>${list.length}æ¡</span></strong><div class="base-news-grid">${list.map(item => `<article class="base-news-item"><header><b>${escapeHtml(item.company_name || 'åºå°ä¼ä¸')}</b>${item.news_type ? `<span>${escapeHtml(item.news_type)}</span>` : ''}</header>${item.url ? `<a href="${escapeHtml(item.url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(item.title || 'æ¥ç详æ ')} â</a>` : `<strong class="base-news-title">${escapeHtml(item.title || '')}</strong>`}${item.summary ? `<p>${escapeHtml(item.summary)}</p>` : ''}<footer>${escapeHtml(item.source || 'å ¬å¼ç½ç»')} · ${escapeHtml(item.published_at || day.date || '')}</footer></article>`).join('')}</div></div>`).join(''); 3601 return `<section class="base-news-day"><h4>${escapeHtml(day.date)}<span>${items.length}æ¡å¨æ</span></h4>${groupHtml}</section>`; 3602 }).join('') + (days.length > 1 ? `<button class="outline-button base-news-expand" id="baseEnterpriseNewsExpand" type="button">${baseEnterpriseNewsExpanded ? 'æ¶èµ·åå²å¨æ' : `å±å¼åå²å¨æï¼å ±${days.length}天ï¼`}</button>` : ''); 3603 $('#baseEnterpriseNewsExpand')?.addEventListener('click', () => { 3604 baseEnterpriseNewsExpanded = !baseEnterpriseNewsExpanded; 3605 renderBaseEnterpriseNews(payload); 3606 }); 3607} 3608 3609async function loadBaseEnterpriseNews() { 3610 try { 3611 const payload = await investmentApiRequest('/api/base-enterprises/news'); 3612 renderBaseEnterpriseNews(payload); 3613 } catch (error) { 3614 const body = $('#baseEnterpriseNewsBody'); 3615 if (body) body.innerHTML = `<div class="base-enterprise-no-match"><strong>åæ°å¨æææªè¿æ¥</strong><p>${escapeHtml(error.message)}</p></div>`; 3616 } 3617} 3618 3619function renderBaseEnterpriseList() { 3620 $('#baseEnterpriseListCount').textContent = `${baseEnterpriseCompanies.length}å®¶`; 3621 $('#baseEnterpriseList').innerHTML = baseEnterpriseCompanies.length ? baseEnterpriseCompanies.map(company => `<button class="base-enterprise-list-item${currentBaseEnterprise?.id === company.id ? ' active' : ''}" type="button" data-base-company="${escapeHtml(company.id)}"><span><small>${escapeHtml(company.primary_category || 'åç±»å¾ æ ¸éª')}</small><strong>${escapeHtml(company.name)}</strong></span>
3621<p>${escapeHtml(enterpriseArray(company.technology_tags).slice(0,4).join(' · ') || enterpriseArray(company.products).slice(0,2).map(item=>item.name).join(' · ') || 'äº§åææ¯ä¿¡æ¯å¾ è¡¥å ')}</p><footer><b>${enterpriseArray(company.products).length}项产å/ææ¯</b><em>ç»å${escapeHtml(company.profile_completeness || 0)}%</em></footer></button>`).join('') : '<div class="base-enterprise-no-match"><strong>没æç¬¦åå½åçéçä¼ä¸</strong><p>å¯ä»¥æ¸ é¤çéææ¢ä¸ä¸ªäº§åãææ¯å ³é®è¯ã</p></div>'; 3622} 3623 3624function renderBaseEnterpriseDetail(company) { 3625 currentBaseEnterprise = company; 3626 renderBaseEnterpriseList(); 3627 const products = enterpriseArray(company.products); 3628 $('#baseEnterpriseDetail').innerHTML = `<header class="base-enterprise-detail-head"><div><span>${escapeHtml(company.primary_category || 'åç±»å¾ æ ¸éª')}</span><h2>${escapeHtml(company.name)}</h2><p>${escapeHtml(company.description || `${products.length}项产å/ææ¯è®°å½ï¼æ¥æºä¸ºåºå°å é¨ä¼ä¸èµæã`)}</p></div><b>ç»å宿´åº¦ ${escapeHtml(company.profile_completeness || 0)}%</b></header><div class="base-enterprise-detail-actions"><button class="analyze-button" type="button" id="importBaseEnterprisePolicy">å¯¼å ¥æ¿çè§åä¼ä¸æ¡£æ¡ â</button><span>å¯¼å ¥åä»å¯ç»§ç»ä¸ä¼ è¥ä¸æ§ç §ã审计ãèµè´¨çè¯æææ</span></div><section><h3>产ä¸ä¸ææ¯æ ç¾</h3>${renderEnterpriseTags([...(company.categories || []), ...(company.technology_tags || [])], 12)}</section><section><h3>产å / æ ¸å¿ææ¯</h3><div class="base-enterprise-product-list">${products.map(item => `<article><strong>${escapeHtml(item.name || 'æªå½å产å')}</strong><span>${escapeHtml(item.stage || 'é¶æ®µå¾ æ ¸éª')}</span>${item.advantage ? `<p>${escapeHtml(item.advantage)}</p>` : ''}</article>`).join('') || '<p>ææ ç»æå产åè®°å½</p>'}</div></section><div class="base-enterprise-detail-grid"><section><h3>åä½éæ±</h3>${renderEnterpriseTags(company.cooperation_needs, 10)}</section><section><h3>å¯å ±äº«å¹³å° / èµæº</h3>${renderEnterpriseTags(company.shared_resources, 10)}</section><section><h3>åä½é¢æ</h3>${renderEnterpriseTags(company.partner_institutes, 10)}</section><section><h3>è£èªåä¸é¡¹æ¯æ</h3>${renderEnterpriseTags(company.honors_support, 8)}</section></div><details class="base-enterprise-confidential"><summary>æ¥çå é¨èç»ä¸èèµä¿¡æ¯</summary><p><b>è系人ï¼</b>${escapeHtml(company.contact_name || 'æªæä¾')}ã<b>èç³»æ¹å¼ï¼</b>${escapeHtml(company.contact_phone || 'æªæä¾')}</p><p><b>åºéæ¯æï¼</b>${escapeHtml(company.funding_needed || 'æªæä¾')}ã${escapeHtml(company.funding_details || '')}</p><small>${escapeHtml(company.confidentiality || 'å é¨ä½¿ç¨ï¼ä¸å¾å¯¹å¤å±ç¤º')}</small></details><footer class="base-enterprise-source">æ¥æºï¼${escapeHtml(enterpriseArray(company.source_refs)[0]?.file || 'ä¼ä¸ä¿¡æ¯æ´ç表')} · æ´æ°å£å¾ ${escapeHtml(company.source_updated_at || 'å¾ æ ¸éª')}</footer>`; 3629 $('#importBaseEnterprisePolicy')?.addEventListener('click', () => importBaseEnterpriseToPolicy(company.id)); 3630} 3631 3632async function selectBaseEnterprise(companyId) { 3633 try { 3634 const company = await investmentApiRequest(`/api/base-enterprises/${encodeURIComponent(companyId)}`); 3635 renderBaseEnterpriseDetail(company); 3636 } catch (error) { showToast(error.message); } 3637} 3638 3639async function refreshBaseEnterpriseList() { 3640 $('#baseEnterpriseList').innerHTML = '<p>æ£å¨çéä¼ä¸â¦</p>'; 3641 try { 3642 baseEnterpriseCompanies = await investmentApiRequest(baseEnterpriseQueryPath()); 3643 renderBaseEnterpriseList(); 3644 } catch (error) { 3645 $('#baseEnterpriseList').innerHTML = `<div class="base-enterprise-no-match"><strong>ä¼ä¸åºææªè¿æ¥</strong><p>${escapeHtml(error.message)}</p></div>`; 3646 } 3647} 3648 3649async function loadBaseEnterpriseLibrary(force = false) { 3650 if (baseEnterpriseLoaded && !force) return; 3651 try { 3652 const [stats, projects] = await Promise.all([ 3653 investmentApiRequest('/api/base-enterprises/stats'), 3654 investmentApiRequest('/api/professional/projects'), 3655 ]); 3656 renderBaseEnterpriseStats(stats); 3657 loadBaseEnterpriseNews(); 3658 initBasePolicyScanPanel(); 3659 const analyzed = enterpriseArray(projects).filter(item => item.has_analysis); 3660 $('#baseEnterpriseProjectSelect').innerHTML = '<option value="">鿩已宿åæçBP项ç®</option>' + analyzed.map(item => `<option value="${escapeHtml(item.id)}">${escapeHtml(item.name)}${item.track ? ` · ${escapeHtml(item.track)}` : ''}</option>`).join(''); 3661 await refreshBaseEnterpriseList(); 3662 baseEnterpriseLoaded = true; 3663 } catch (error) { showToast(error.message); } 3664} 3665 3666// ============ åºå°ä¼ä¸ à æ¿çåºï¼æ¹éæºä¼æ«ææ»è§ ============ 3667 3668let basePolicyMatrixData = null; 3669let basePolicySortMode = 'pinyin'; 3670let basePolicyQuery = ''; 3671let basePolicyPollTimer = null; 3672 3673function initBasePolicyScanPanel() { 3674 const panel = $('#basePolicyScanPanel'); 3675 if (!panel || panel.dataset.ready === 'true') return; 3676 panel.dataset.ready = 'true'; 3677 $('#basePolicyScanButton').addEventListener('click', startBasePolicyScan); 3678 $('#basePolicySearch').addEventListener('input', event => { basePolicyQuery = event.target.value.trim().toLowerCase(); renderBasePolicyMatrix(); }); 3679 $('#basePolicySort').addEventListener('change', event => { basePolicySortMode = event.target.value; renderBasePolicyMatrix(); }); 3680 $('#basePolicyIndex').addEventListener('click', event => { 3681 const btn = event.target.closest('[data-initial]');
3682 if (btn) $(`[data-initial-group="${btn.dataset.initial}"]`)?.scrollIntoView({behavior:'smooth', block:'start'}); 3683 }); 3684 loadBasePolicyMatrix(); 3685 resumeBasePolicyPolling(); 3686} 3687 3688async function loadBasePolicyMatrix() { 3689 const container = $('#basePolicyMatrix'); 3690 try { 3691 basePolicyMatrixData = await investmentApiRequest('/api/base-enterprises/policy-opportunities'); 3692 renderBasePolicyMatrix(); 3693 } catch (error) { 3694 if (container) container.innerHTML = `<p>æ¿çæºä¼ç©éµè¯»å失败ï¼${escapeHtml(error.message)}</p>`; 3695 } 3696} 3697 3698function renderBasePolicyMatrix() { 3699 const container = $('#basePolicyMatrix'); 3700 const meta = $('#basePolicyScanMeta'); 3701 const indexBar = $('#basePolicyIndex'); 3702 if (!container || !basePolicyMatrixData) return; 3703 const data = basePolicyMatrixData; 3704 meta.textContent = data.scanned_count ? `å·²æ«æ ${data.scanned_count}/${data.total} å®¶` : `å ± ${data.total} å®¶ · å°æªæ¹éæ«æ`; 3705 let companies = data.companies || []; 3706 if (basePolicyQuery) { 3707 companies = companies.filter(c => 3708 c.name.toLowerCase().includes(basePolicyQuery) || 3709 c.short_name.toLowerCase().includes(basePolicyQuery) || 3710 (c.top_opportunities || []).some(o => (o.title || '').toLowerCase().includes(basePolicyQuery)) 3711 ); 3712 } 3713 if (!companies.length) { 3714 container.innerHTML = '<p>没æç¬¦åæç´¢æ¡ä»¶çä¼ä¸ã</p>'; 3715 indexBar.innerHTML = ''; 3716 return; 3717 } 3718 const companyCard = c => { 3719 const summary = c.summary || {}; 3720 const recommend = summary['å»ºè®®ç³æ¥']; 3721 const verify = summary['å¾ æ ¸éªåå®']; 3722 const stats = c.scanned 3723 ? `<b class="recommend-count">建议 ${recommend || 0}</b><b class="verify-count">å¾ æ ¸éª ${verify || 0}</b>` 3724 : '<b class="unscanned">æªæ«æ</b>'; 3725 const tops = (c.top_opportunities || []).map(o => `<li><i class="urgency-${escapeHtml(o.urgency)}">${o.urgency === 'é«' ? 'æ¥' : ''}</i><span><strong>${escapeHtml(o.title)}</strong><small>${escapeHtml(o.fit_reason || '')}${o.deadline ? ` · æªæ¢ ${escapeHtml(o.deadline)}` : ''}</small></span></li>`).join(''); 3726 return `<details class="base-policy-company${c.scanned ? '' : ' dimmed'}"><summary><span class="company-name"><strong>${escapeHtml(c.short_name)}</strong><small>${escapeHtml(c.name)}</small></span><em>${escapeHtml(c.primary_category || 'åç±»å¾ æ ¸éª')}</em>${stats}</summary>${tops ? `<ul class="base-policy-tops">${tops}</ul>` : `<p class="base-policy-none">${c.scanned ? '没æ"å»ºè®®ç³æ¥"级å«çæ¿çï¼å¾ æ ¸éªé¡¹è¯·æ«æè¯¦æ æ¥çã' : 'çå¾ æ¹éæ«æè¦ç该ä¼ä¸ã'}</p>`}</details>`; 3727 }; 3728 if (basePolicySortMode === 'recommend') { 3729 const sorted = [...companies].sort((a, b) => ((b.summary || {})['å»ºè®®ç³æ¥'] || 0) - ((a.summary || {})['å»ºè®®ç³æ¥'] || 0) || a.short_name.localeCompare(b.short_name, 'zh')); 3730 container.innerHTML = sorted.map(companyCard).join(''); 3731 indexBar.innerHTML = ''; 3732 return; 3733 } 3734 const groups = {}; 3735 companies.forEach(c => { (groups[c.initial] = groups[c.initial] || []).push(c); }); 3736 const letters = Object.keys(groups).sort(); 3737 indexBar.innerHTML = letters.map(l => `<button type="button" data-initial="${l}">${l}</button>`).join(''); 3738 container.innerHTML = letters.map(l => `<div class="base-policy-group" data-initial-group="${l}"><h4>${l}</h4>${groups[l].map(companyCard).join('')}</div>`).join(''); 3739} 3740 3741async function startBasePolicyScan() { 3742 const button = $('#basePolicyScanButton'); 3743 button.disabled = true; 3744 try { 3745 const result = await investmentApiRequest('/api/base-enterprises/policy-scan-async', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({})}); 3746 if (result.started === false) { 3747 showToast(result.message || 'æ¹éæ«ææ£å¨è¿è¡ä¸'); 3748 } else { 3749 showToast(`æ¹éæ«æå·²å¯å¨ï¼å ± ${result.total} å®¶ä¼ä¸ï¼å·²æ«æä¸æ¿çåºæªæ´æ°çä¼ä¸ä¼èªå¨è·³è¿`); 3750 } 3751 resumeBasePolicyPolling(true); 3752 } catch (error) { 3753 showToast(error.message); 3754 button.disabled = false; 3755 } 3756} 3757 3758async function resumeBasePolicyPolling(forceStart = false) { 3759 if (basePolicyPollTimer) { clearInterval(basePolicyPollTimer); basePolicyPollTimer = null; } 3760 const tick = async () => { 3761 let progress; 3762 try { 3763 progress = await investmentApiRequest('/api/base-enterprises/policy-scan-progress'); 3764 } catch { return; } 3765 const bar = $('#basePolicyProgress'); 3766 if (!bar) return; 3767 if (progress.status === 'running') { 3768 bar.classList.remove('hidden'); 3769 const pct = progress.total ? Math.round(progress.done / progress.total * 100) : 0; 3770 $('#basePolicyProgressBar').style.width = `${pct}%`;
3771 $('#basePolicyProgressText').textContent = `æ£å¨æ«æ ${progress.done}/${progress.total}ï¼${progress.current || ''}ï¼å·²è·³è¿ ${progress.skipped || 0} å®¶ï¼`; 3772 $('#basePolicyScanButton').disabled = true; 3773 if (progress.done > 0 && progress.done % 3 === 0) loadBasePolicyMatrix(); 3774 } else { 3775 bar.classList.add('hidden'); 3776 $('#basePolicyScanButton').disabled = false; 3777 $('#basePolicyScanButton').textContent = 'éæ°æ¹éæ«æï¼å¢éï¼'; 3778 if (progress.status === 'done' && progress.finished_at) { 3779 loadBasePolicyMatrix(); 3780 clearInterval(basePolicyPollTimer); 3781 basePolicyPollTimer = null; 3782 const errors = (progress.errors || []).length; 3783 showToast(`æ¹éæ«æå®æï¼${progress.done} å®¶ï¼è·³è¿ ${progress.skipped || 0} å®¶å·²æ¯ææ°ï¼${errors ? `ï¼${errors} 家失败ä¸è½®èªå¨éè¯` : ''}`); 3784 } else if (!forceStart) { 3785 clearInterval(basePolicyPollTimer); 3786 basePolicyPollTimer = null; 3787 } 3788 } 3789 }; 3790 await tick(); 3791 if (!basePolicyPollTimer) basePolicyPollTimer = setInterval(tick, 8000); 3792} 3793 3794 3795async function importBaseEnterpriseToPolicy(companyId) { 3796 const button = $('#importBaseEnterprisePolicy'); 3797 if (button) { button.disabled = true; button.textContent = 'æ£å¨å»ºç«ä¼ä¸æ¡£æ¡â¦'; } 3798 try { 3799 const result = await investmentApiRequest(`/api/base-enterprises/${encodeURIComponent(companyId)}/import-policy`, {method:'POST'}); 3800 policyEnterprisesLoaded = false; 3801 currentPolicyEnterprise = result.enterprise; 3802 await loadPolicyEnterprises(true); 3803 showToast(result.created ? 'å·²ä»åºå°ä¼ä¸åºå建æ¿çè§åæ¡£æ¡' : 'æ¿çè§åä¸çååä¼ä¸æ¡£æ¡å·²æ´æ°'); 3804 switchView('policyhub'); 3805 } catch (error) { 3806 showToast(error.message); 3807 if (button) { button.disabled = false; button.textContent = 'å¯¼å ¥æ¿çè§åä¼ä¸æ¡£æ¡ â'; } 3808 } 3809} 3810 3811async function runBaseEnterpriseProjectMatch(projectId, target = '#baseEnterpriseMatchResults') { 3812 if (!projectId) { showToast('请å éæ©ä¸ä¸ªå·²å®æåæçBP项ç®'); return; } 3813 const node = typeof target === 'string' ? $(target) : target; 3814 if (node) node.innerHTML = '<p class="base-enterprise-loading">æ£å¨æ¯å¯¹äº§åãææ¯è·¯çº¿ä¸ä¼ä¸èµæºâ¦</p>'; 3815 try { 3816 const result = await investmentApiRequest('/api/base-enterprises/match', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project_id:projectId,limit:8})}); 3817 renderEnterpriseMatches(node, result.matches); 3818 if ($('#baseEnterpriseMatchBoundary') && target === '#baseEnterpriseMatchResults') $('#baseEnterpriseMatchBoundary').textContent = `${result.method}ã${result.boundary}`; 3819 return result; 3820 } catch (error) { 3821 if (node) node.innerHTML = `<div class="base-enterprise-no-match"><strong>å ³èåææªå®æ</strong><p>${escapeHtml(error.message)}</p></div>`; 3822 return null; 3823 } 3824} 3825 3826async function loadInvestmentEnterpriseMatches() {
3827 const panel = $('#investmentEnterpriseMatch'); 3828 if (!panel || !currentInvestmentProject?.id || !currentInvestmentAnalysis) return; 3829 panel.classList.remove('hidden'); 3830 const result = await runBaseEnterpriseProjectMatch(currentInvestmentProject.id, '#investmentEnterpriseMatchResults'); 3831 if (result) $('#investmentEnterpriseMatchAudit').textContent = `${result.match_count}å®¶è¾¾å°ç 究线索éå¼ Â· ${result.boundary}`; 3832} 3833 3834function initializeInvestmentWorkflowLayout() { 3835 const view = $('#investmentView'); 3836 if (!view || view.dataset.workflowReady === 'true') return; 3837 view.dataset.workflowReady = 'true'; 3838 const title = $('.section-title', view); 3839 const workflow = document.createElement('div'); 3840 workflow.className = 'policy-workflow-shell investment-workflow-shell'; 3841 workflow.innerHTML = ` 3842 <nav class="policy-workflow-steps investment-workflow-steps" id="investmentWorkflowSteps" aria-label="BPåæå·¥ä½æ¥éª¤"> 3843 <button class="active" data-investment-step="1"><i>01</i><span><strong>项ç®å»ºæ¡£</strong><small>æ¥æºãèµéä¸è´è´£äºº</small></span></button> 3844 <button data-investment-step="2"><i>02</i><span><strong>ä¸ä¼ ææ</strong><small>BPå ¨æä¸æ ¸å¿éä»¶</small></span></button> 3845 <button data-investment-step="3"><i>03</i><span><strong>AIä»·å¼ç å¤</strong><small>é¡¹ç®æ½åä¸è¯æ®ç½®ä¿¡åº¦</small></span></button> 3846 <button data-investment-step="4"><i>04</i><span><strong>è¡¥è¯ä¸å审</strong><small>缺å£ãä¸å®¶ä¸å审æè§</small></span></button> 3847 <button data-investment-step="5"><i>05</i><span><strong>æå§ä¼å³ç</strong><small>ç»è®ºã责任人ä¸éç¨ç¢</small></span></button> 3848 </nav> 3849 <div class="policy-workflow-context investment-workflow-context"> 3850 <span>å½å项ç®<strong id="investmentContextProject">å°æªéæ©</strong></span> 3851 <b>·</b> 3852 <span>项ç®ç¶æ<strong id="investmentContextStatus">çå¾ å»ºæ¡£</strong></span> 3853 <div><button class="outline-button" id="investmentWorkflowBack">â ä¸ä¸æ¥</button><button class="analyze-button" id="investmentWorkflowNext">项ç®å·²å»ºæ¡£ï¼ä¸ä¼ BP â</button></div> 3854 </div> 3855 <div class="investment-workflow-stage active" data-investment-stage="1"></div> 3856 <div class="investment-workflow-stage" data-investment-stage="2"></div> 3857 <div class="investment-workflow-stage" data-investment-stage="3"></div> 3858 <div class="investment-workflow-stage" data-investment-stage="4"></div> 3859 <div class="investment-workflow-stage" data-investment-stage="5"></div>`; 3860 title.after(workflow); 3861 3862 const stage1 = $('[data-investment-stage="1"]', workflow); 3863 const stage2 = $('[data-investment-stage="2"]', workflow); 3864 const stage3 = $('[data-investment-stage="3"]', workflow); 3865 const stage4 = $('[data-investment-stage="4"]', workflow); 3866 const stage5 = $('[data-investment-stage="5"]', workflow); 3867 3868 stage1.innerHTML = `<div class="investment-archive-layout"><aside class="panel investment-project-sidebar"><div class="development-panel-head"><div><strong>æèµé¡¹ç®åº</strong><p>å»é¢ã髿 ¡é¢æåä¼ä¸é¡¹ç®åå«å»ºæ¡£ï¼åæä¸å³çä¸ä¼æ··ç¨</p></div><span id="investmentProjectCount">0项</span></div><button class="outline-button investment-new-project" id="newInvestmentProjectButton">ï¼ æ°å»ºé¡¹ç®æ¡£æ¡</button><div id="investmentProjectList" class="investment-project-list"><p>æ£å¨è¯»åé¡¹ç®æ¡£æ¡â¦</p></div></aside><section class="panel investment-project-editor"><div class="development-panel-head"><div><strong id="investmentProjectFormTitle">å»ºç«æèµé¡¹ç®æ¡£æ¡</strong><p>å è®°å½é¡¹ç®ä»åªéæ¥ãå¤äºä»ä¹é¶æ®µãç±è°è·è¿ï¼åä¸ä¼ BP</p></div><span id="investmentArchiveStatus">å¾ å»ºæ¡£</span></div><div class="investment-project-form"><label class="wide">项ç®åç§°<input id="investmentProjectName" type="text" placeholder="ä¾
3868å¦ï¼éª¨ç§æºè½ç©¿åºå¯¼èªé¡¹ç®"></label><label>é¡¹ç®æ¥æº<select id="investmentProjectSource"><option>å»é¢ä¸´åºé¡¹ç®</option><option>髿 ¡é¢æææ</option><option>å»è¯ä¼ä¸é¡¹ç®</option><option>åä¸å¢é</option><option>ååºä¸»å¨åç°</option><option>å ¶ä»æ¸ é</option></select></label><label>项ç®èµé<select id="investmentProjectTrack"><option>å»ç卿¢°</option><option>ä½å¤è¯æ IVD</option><option>åæ°è¯ / çç©è¯</option><option>AIå»ç软件</option><option>ç»èä¸åºå æ²»ç</option><option>CRO / å»è¯æå¡</option><option>å ¶ä»çç©å»è¯</option></select></label><label>å½åé¶æ®µ<select id="investmentProjectStage"><option>临åºéæ± / æ¦å¿µ</option><option>å®éªå®¤éªè¯</option><option>å·¥ç¨æ ·æº / å°è¯</option><option>注ååå¤ / 临åº</option><option>已注å / åä¸å</option></select></label><label>é¡¹ç®æ¹è系人<input id="investmentProjectContact" type="text" placeholder="å§åãåä½ãèç³»æ¹å¼"></label><label>åå«è·è¿è´è´£äºº<input id="investmentProjectOwner" type="text" placeholder="æèµç»ç / 项ç®ç»ç"></label><label class="wide">å»ºæ¡£å¤æ³¨<textarea id="investmentProjectNotes" placeholder="åä½èæ¯ãæææå±ãæ¤æ¬¡æ¥è§¦ç®çãéè¦ç¹å«å ³æ³¨çé®é¢â¦â¦"></textarea></label></div><div class="investment-project-actions"><button class="outline-button" id="saveInvestmentProjectButton">ä¿åæ¡£æ¡ä¿®æ¹</button><button class="analyze-button" id="createInvestmentProjectButton">å建并è¿å ¥ææä¸ä¼ â</button></div><div class="investment-current-archive hidden" id="investmentCurrentArchive"></div></section></div>`; 3869 3870 const engineConnection = $('.engine-connection', view); 3871 const workspace = $('.intelligence-workspace', view); 3872 if (engineConnection) stage2.append(engineConnection); 3873 if (workspace) stage2.append(workspace); 3874 const uploadGuide = document.createElement('section'); 3875 uploadGuide.className = 'panel investment-material-guide'; 3876 uploadGuide.innerHTML = '<div><strong>æ¬è½®ä»¥ä¸»BP为äºå®åºç¨¿</strong><p>ç³»ç»è¯»åå ¨æãè¡¨æ ¼åPPTæåå¹¶è®°å½å®é 读ååæ°ãä¸å©ãæ£æµãè´¢å¡å注åéä»¶å å¨å审鶿®µå为补è¯ä»»å¡ï¼æ²¡æåå§è¯æ®çå 容ä¸ä¼èªå¨å¤å®ä¸ºäºå®ã</p></div><span>ä¸ä¼ åèªå¨è¿å ¥ç¬¬3æ¥</span>'; 3877 stage2.prepend(uploadGuide); 3878 3879 ['investmentResult','investmentBiomedicalRoute','investmentAssistant','investmentDeepening','investmentEvidence','investmentKnowledgeReferences','investmentEnterpriseMatch'].forEach(id => { 3880 const node = $(`#${id}`); 3881 if (node) stage3.append(node); 3882 }); 3883 const analysisEmpty = document.createElement('section'); 3884 analysisEmpty.className = 'panel investment-stage-empty'; 3885 analysisEmpty.id = 'investmentAnalysisEmpty'; 3886 analysisEmpty.innerHTML = '<strong>å°æªå½¢æé¡¹ç®ä»·å¼ç 夿¥å</strong><p>请å å¨ç¬¬äºæ¥ä¸ä¼ 该项ç®çBPã宿åä¼çå°å®é AIè°ç¨è®°å½ãä¹ç»´é¡¹ç®æ½åãè¯æ®è¦ç度ä¸å¤æåºé´ãå¸åºåæ³¨åææ¬æµç®ååç±»äº§åæ ¸éªä»»å¡ã</p>'; 3887 stage3.prepend(analysisEmpty); 3888 3889 stage4.innerHTML = `<section class="panel investment-review-gate"><div class="development-panel-head"><div><strong>ææè¡¥è¯ä¸ä¸å®¶å审</strong><p>é项å¤çAIè¯å«çè¯æ®ç¼ºå£ï¼æªè¡¥é½å¯ä»¥ç»§ç»è®°å½ï¼ä½å¿ é¡»æç¡®æ´é²ç»æå§ä¼</p></div><span id="investmentReviewStatus">çå¾ åæ</span></div><div class="investment-review-layout"><article><strong>æ¬é¡¹ç®å¾ æ ¸éªä¸è¡¥è¯äºé¡¹</strong><div id="investmentReviewGaps" class="investment-review-gaps"><p>宿AIåæåèªå¨çæã</p></div></article><article class="investment-review-form"><label>å审è´è´£äºº<input id="investmentReviewer" type="text" placeholder="æèµç»ç / 临åºä¸å®¶ / å·¥ç¨ä¸å®¶"></label><label>å审æè§<select id="investmentReviewOpinion"><option>建议è¿å ¥æå§ä¼è®¨è®º</option><option>è¡¥å å ³é®ææååä¸ä¼</option><option>建议è¿å ¥å¨å¤è§å¯</option><option>å»ºè®®ç»æ¢æ¨è¿</option></select></label><label>å审记å½<textarea id="investmentReviewNotes" placeholder="è®°å½ä¸å®¶å¤æãå ³é®äºè®®ãå¾ è¡¥ææè´£ä»»äººåæªæ¢æ¶é´â¦â¦"></textarea></label><div class="investment-review-confirmations"><label><input type="checkbox" data-investment-review-confirm> å·²é 读ä¹ç»´è¯ååé项åæä¾æ®</label><label><input type="checkbox" data-investment-review-confirm> å·²åºåBPèªè¿°ãAI夿åå¤é¨ç宿°æ®</label><label><input type="checkbox" data-investment-review-confirm> å·²è®°å½æªè§£å³ç¼ºå£ï¼ä¸æå¾ æ ¸éªé¡¹å½ä½äºå®</label></div><button class="analyze-button" id="saveInvestmentReviewButton">ä¿ååå®¡ï¼æäº¤æå§ä¼ â</button></article></div></section>`; 3890 3891 stage5.innerHTML = `<section class="panel investment-committee-gate"><div class="development-panel-head"><div><strong>æå§ä¼å³çä¸ä¸ä¸éç¨ç¢</strong><p>è¯ååªæä¾å³çè¯æ®ï¼ä¸èªå¨ä»£æ¿æèµå¤æï¼æç»ç»è®ºã责任人å忢æ¡ä»¶å¿ 须人工填å</p></div><span id="investmentCommitteeScore">çå¾ åæ</span></div><div class="investment-committee-summary" id="investmentCommitteeSummary"></div><div class="investment-decision-form"><label>æå§ä¼ç»è®º<select id="investmentCommitteeDecision"><option>è¿å ¥æ£å¼å°½è°</option><option>è¡¥å ææåå¤å®¡</option><option>æç¼è§å¯</option><option>ç»æ¢æ¨è¿</option></select></label><label>å³çæ¥æ<input id="investmentCommitteeDate" type="date"></label><label>ä¸ä¸é¶æ®µè´è´£äºº<input id="investmentCommitteeOwner" type="text" placeholder="æèµè´è´£äºº / 项ç®è´è´£äºº"></label><label>ææèµæé¢ç®é¢åº¦ï¼ä¸å ï¼<input id="investmentCommitteeAmount" type="number" min="0" placeholder="坿ä¸å¡«å"></label><label class="wide">å³ççç±<textarea id="investmentCommitteeReason" placeholder="为ä»ä¹ç»§ç»ãæç¼æç»æ¢ï¼å¼ç¨æå ³é®çäºå®ãé£é©å缺å£â¦â¦"></textarea></label><label class="wide">ä¸ä¸éç¨ç¢ä¸åæ¢æ¡ä»¶<textarea id="investmentCommitteeMilestone" placeholder="ä¾
3891å¦ï¼å®æä¸å©æå±ç¡®è®¤ãç¬¬ä¸æ¹æ§è½å¤æµå注ååç±»é¢æ²éï¼ä»»ä¸æ ¸å¿ææ ä¸è¾¾æ å忢æ¨è¿"></textarea></label><label>宿æé<input id="investmentCommitteeDeadline" type="date"></label></div><div class="investment-committee-actions"><button class="outline-button" id="downloadInvestmentMemoButton">å¯¼åºæå³çºªè¦</button><button class="analyze-button" id="saveInvestmentDecisionButton">ç¡®è®¤å¹¶å½æ¡£æå§ä¼ç»è®º â</button></div><div class="investment-decision-record hidden" id="investmentDecisionRecord"></div></section>`; 3892 3893 $$('#investmentWorkflowSteps button', workflow).forEach(button => button.addEventListener('click', () => requestInvestmentWorkflowStep(Number(button.dataset.investmentStep)))); 3894 $('#investmentWorkflowBack').addEventListener('click', () => renderInvestmentWorkflowStep(Math.max(1, investmentWorkflowStep - 1))); 3895 $('#investmentWorkflowNext').addEventListener('click', advanceInvestmentWorkflow); 3896 $('#newInvestmentProjectButton').addEventListener('click', resetInvestmentProjectForm); 3897 $('#createInvestmentProjectButton').addEventListener('click', createInvestmentProject); 3898 $('#saveInvestmentProjectButton').addEventListener('click', saveInvestmentProject); 3899 $('#investmentProjectList').addEventListener('click', event => { const reportLink = event.target.closest('[data-open-report]'); const button = event.target.closest('[data-investment-project-id]'); if (!button) return; if (reportLink) { openInvestmentProjectReport(button.dataset.investmentProjectId); return; } selectInvestmentProject(button.dataset.investmentProjectId); }); 3900 $('#saveInvestmentReviewButton').addEventListener('click', saveInvestmentInitialReview); 3901 $('#saveInvestmentDecisionButton').addEventListener('click', saveInvestmentCommitteeDecision); 3902 $('#downloadInvestmentMemoButton').addEventListener('click', downloadInvestmentMemo); 3903 $('#investmentAssistantSend').addEventListener('click', () => sendInvestmentAssistantQuestion()); 3904 $('#investmentAssistantInput').addEventListener('keydown', event => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendInvestmentAssistantQuestion(); } }); 3905 $('#investmentAssistantPrompts').addEventListener('click', event => { const button=event.target.closest('[data-bp-assistant-question]'); if (button) sendInvestmentAssistantQuestion(button.dataset.bpAssistantQuestion); }); 3906 $('#investmentAssistantMessages').addEventListener('click', event => { const button=event.target.closest('[data-bp-assistant-question]'); if (button) sendInvestmentAssistantQuestion(button.dataset.bpAssistantQuestion); }); 3907 $('#clearInvestmentAssistantButton').addEventListener('click', clearInvestmentAssistantConversation); 3908 $('#investmentCommitteeDate').value = new Date().toISOString().slice(0,10); 3909 renderInvestmentWorkflowStep(1, true); 3910 loadInvestmentProjects(); 3911} 3912 3913function renderInvestmentWorkflowStep(step, bypassGate = false) { 3914 investmentWorkflowStep = Math.max(1, Math.min(5, Number(step) || 1)); 3915 $$('#investmentWorkflowSteps button').forEach(button => { 3916 const value = Number(button.dataset.investmentStep); 3917 button.classList.toggle('active', value === investmentWorkflowStep); 3918 button.classList.toggle('complete', value < investmentWorkflowStep); 3919 }); 3920 $$('.investment-workflow-stage').forEach(stage => stage.classList.toggle('active', Number(stage.dataset.investmentStage) === investmentWorkflowStep)); 3921 $('#investmentWorkflowBack').classList.toggle('hidden', investmentWorkflowStep === 1); 3922 $('#investmentWorkflowNext').classList.toggle('hidden', investmentWorkflowStep >= 4); 3923 const labels = {1:'项ç®å·²å»ºæ¡£ï¼ä¸ä¼ BP â',2:'æ¥çAIåæç»æ â',3:'è¿å ¥è¡¥è¯ä¸ä¸å®¶å审 â'}; 3924 $('#investmentWorkflowNext').textContent = labels[investmentWorkflowStep] || 'ç»§ç» â'; 3925 $('#investmentContextProject').textContent = currentInvestmentProject?.name || 'å°æªéæ©'; 3926 $('#investmentContextStatus').textContent = currentInvestmentProject?.status || 'çå¾ å»ºæ¡£'; 3927 refreshInvestmentAssistant(); 3928 if (investmentWorkflowStep === 3) $('#investmentAnalysisEmpty').classList.toggle('hidden', Boolean(currentInvestmentAnalysis)); 3929 if (!bypassGate) window.scrollTo({top:0,behavior:'smooth'}); 3930} 3931 3932function requestInvestmentWorkflowStep(step) { 3933 if (step > 1 && !currentInvestmentProject) { showToast('请å å建æéæ©æèµé¡¹ç®æ¡£æ¡'); renderInvestmentWorkflowStep(1); return; } 3934 if (step > 2 && !currentInvestmentAnalysis) { showToast('请å ä¸ä¼ BP并宿AIå ¨æåæ'); renderInvestmentWorkflowStep(2); return; } 3935 if (step > 3 && !currentInvestmentReportId) { showToast('æè¦åçä¸è½æäº¤ä¸å®¶å审ï¼è¯·ä¸ä¼ 宿´BPçææ£å¼æ¥å'); renderInvestmentWorkflowStep(2); return; } 3936 if (step > 4 && !Object.keys(currentInvestmentProject?.initial_review || {}).length) { showToast('请å ä¿åä¸å®¶å审记å½'); renderInvestmentWorkflowStep(4); return; } 3937 renderInvestmentWorkflowStep(step); 3938} 3939 3940function advanceInvestmentWorkflow() { 3941 if (investmentWorkflowStep === 1) { 3942 if (!currentInvestmentProject) { showToast('请å å建æéæ©æèµé¡¹ç®æ¡£æ¡'); return; } 3943 renderInvestmentWorkflowStep(2); return; 3944 } 3945 if (investmentWorkflowStep === 2) { 3946 if (!currentInvestmentAnalysis) { showToast('请å ä¸ä¼ BP并宿åæ'); return; } 3947 renderInvestmentWorkflowStep(3); return; 3948 } 3949 if (investmentWorkflowStep === 3) { 3950 if (!currentInvestmentReportId) { showToast('æè¦åçä¸è½è¿å ¥æ£å¼å审ï¼è¯·ä¸ä¼ 宿´BP'); renderInvestmentWorkflowStep(2); return; } 3951 renderInvestmentWorkflowStep(4); 3952 } 3953} 3954 3955function investmentProjectPayload() { 3956 return { 3957 name:$('#investmentProjectName').value.trim(),source_type:$('#investmentProjectSource').value, 3958 track:$('#investmentProjectTrack').value,stage:$('#investmentProjectStage').value, 3959 team_contact:$('#investmentProjectContact').value.trim(),owner:$('#investmentProjectOwner').value.trim(), 3960 notes:$('#investmentProjectNotes').value.trim() 3961 }; 3962} 3963
3964function resetInvestmentProjectForm() { 3965 currentInvestmentProject = null; currentInvestmentAnalysis = null; currentInvestmentReportId = null; 3966 investmentAssistantMessages = []; 3967 $('#investmentProjectFormTitle').textContent = 'å»ºç«æèµé¡¹ç®æ¡£æ¡'; 3968 ['investmentProjectName','investmentProjectContact','investmentProjectOwner','investmentProjectNotes'].forEach(id => { $(`#${id}`).value = ''; }); 3969 $('#investmentArchiveStatus').textContent = 'å¾ å»ºæ¡£'; 3970 $('#investmentCurrentArchive').classList.add('hidden'); 3971 $('#createInvestmentProjectButton').classList.remove('hidden'); 3972 $('#saveInvestmentProjectButton').classList.add('hidden'); 3973 renderInvestmentProjectList(); renderInvestmentWorkflowStep(1, true); $('#investmentProjectName').focus(); 3974} 3975 3976function fillInvestmentProjectForm(project) { 3977 $('#investmentProjectName').value = project.name || ''; 3978 $('#investmentProjectSource').value = project.source_type || 'å»é¢ä¸´åºé¡¹ç®'; 3979 $('#investmentProjectTrack').value = project.track || 'å»ç卿¢°'; 3980 $('#investmentProjectStage').value = project.stage || '临åºéæ± / æ¦å¿µ'; 3981 $('#investmentProjectContact').value = project.team_contact || ''; 3982 $('#investmentProjectOwner').value = project.owner || ''; 3983 $('#investmentProjectNotes').value = project.notes || ''; 3984 $('#investmentProjectFormTitle').textContent = 'å½åæèµé¡¹ç®æ¡£æ¡'; 3985 $('#investmentArchiveStatus').textContent = project.status || '已建档'; 3986 $('#createInvestmentProjectButton').classList.add('hidden'); 3987 $('#saveInvestmentProjectButton').classList.remove('hidden'); 3988 const archive = $('#investmentCurrentArchive'); 3989 archive.innerHTML = `<span><small>项ç®ç¼å·</small><strong>${escapeHtml(project.id.slice(0,8).toUpperCase())}</strong></span><span><small>æè¿BP</small><strong>${escapeHtml(project.latest_file_name || 'å°æªä¸ä¼ ')}</strong></span><span><small>æè¿è¯å</small><strong>${project.latest_score == null ? 'å¾ åæ' : `${escapeHtml(project.latest_score)} / 10`}</strong></span><span><small>æµç¨ç¶æ</small><strong>${escapeHtml(project.status || '已建档')}</strong></span>`; 3990 archive.classList.remove('hidden'); 3991} 3992 3993function renderInvestmentProjectList() { 3994 $('#investmentProjectCount').textContent = `${investmentProjects.length}项`; 3995 $('#investmentProjectList').innerHTML = investmentProjects.length ? investmentProjects.map(project => `<button class="investment-project-card ${currentInvestmentProject?.id===project.id?'active':''}" data-investment-project-id="${escapeHtml(project.id)}"><span><b>${escapeHtml(project.status || '已建档')}</b><small>${project.latest_score == null ? 'å¾ åæ' : `${escapeHtml(project.latest_score)}å`}</small></span><strong>${escapeHtml(project.name)}</strong><p>${escapeHtml(project.source_type || 'æ¥æºå¾ è¡¥å ')} · ${escapeHtml(project.track || 'èµéå¾ ç¡®è®¤')}</p>${project.latest_score != null ? '<em class="investment-open-report" data-open-report="1">æ¥çæ¥å â</em>' : ''}</button>`).join('') : '<div class="investment-project-empty"><strong>è¿æ²¡ææèµé¡¹ç®</strong><p>ç¹å»ä¸æ¹âæ°å»ºé¡¹ç®æ¡£æ¡âï¼å è®°å½é¡¹ç®æ¥æºåè´è´£äººã</p></div>'; 3996} 3997 3998async function loadInvestmentProjects(force = false, preferredId = '') { 3999 if (investmentProjectsLoaded && !force) return; 4000 try { 4001 investmentProjects = await investmentApiRequest('/api/investment/projects'); 4002 investmentProjectsLoaded = true; 4003 if (new URLSearchParams(window.location.search).get('demo') === 'investment') { renderInvestmentProjectList(); return; } 4004 const selectId = preferredId || currentInvestmentProject?.id || investmentProjects[0]?.id; 4005 if (selectId) await selectInvestmentProject(selectId, true); else resetInvestmentProjectForm(); 4006 renderInvestmentProjectList(); 4007 } catch (error) { 4008 $('#investmentProjectList').innerHTML = `<div class="investment-project-empty"><strong>é¡¹ç®æ¡£æ¡æå¡ææªè¿æ¥</strong><p>${escapeHtml(error.message)}</p></div>`; 4009 } 4010} 4011 4012async function selectInvestmentProject(projectId, silent = false) { 4013 try { 4014 currentInvestmentProject = await investmentApiRequest(`/api/investment/projects/${encodeURIComponent(projectId)}`); 4015 currentInvestmentAnalysis = Object.keys(currentInvestmentProject.analysis || {}).length ? currentInvestmentProject.analysis : null; 4016 currentInvestmentReportId = currentInvestmentProject.latest_report_id || null; 4017 loadInvestmentAssistantConversation(); 4018 fillInvestmentProjectForm(currentInvestmentProject); renderInvestmentProjectList(); renderInvestmentSavedReview(); renderInvestmentSavedDecision(); 4019 if (currentInvestmentAnalysis) renderProfessionalBpReport(currentInvestmentAnalysis, {silent:true}); 4020 else ['investmentResult','investmentBiomedicalRoute','investmentAssistant','investmentDeepening','investmentEvidence','investmentKnowledgeReferences'].forEach(id => $(`#${id}`)?.classList.add('hidden')); 4021 renderInvestmentReviewChecklist(); renderInvestmentWorkflowStep(investmentWorkflowStep, true); 4022 if (!silent) showToast(`已忢å°â${currentInvestmentProject.name}â`); 4023 } catch (error) { showToast(error.message); } 4024} 4025 4026async function openInvestmentProjectReport(projectId) { 4027 try { 4028 await selectInvestmentProject(projectId, true); 4029 renderInvestmentWorkflowStep(3, true); 4030 setTimeout(() => $('#investmentResult')?.scrollIntoView({behavior:'smooth', block:'start'}), 80);
4031 showToast(`å·²æå¼â${currentInvestmentProject?.name || '该项ç®'}âçåå²åææ¥å`); 4032 } catch (error) { showToast(error.message); } 4033} 4034 4035async function createInvestmentProject() { 4036 const payload = investmentProjectPayload(); 4037 if (!payload.name) { $('#investmentProjectName').focus(); showToast('请å å¡«å项ç®åç§°'); return; } 4038 try { 4039 currentInvestmentProject = await investmentApiRequest('/api/investment/projects',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); 4040 investmentProjectsLoaded = false; await loadInvestmentProjects(true,currentInvestmentProject.id); renderInvestmentWorkflowStep(2); showToast('é¡¹ç®æ¡£æ¡å·²å»ºç«ï¼ç°å¨å¯ä»¥ä¸ä¼ BP'); 4041 } catch (error) { showToast(error.message); } 4042} 4043 4044async function saveInvestmentProject() { 4045 if (!currentInvestmentProject) return; 4046 const payload = investmentProjectPayload(); 4047 if (!payload.name) { $('#investmentProjectName').focus(); showToast('项ç®åç§°ä¸è½ä¸ºç©º'); return; } 4048 try { 4049 currentInvestmentProject = await investmentApiRequest(`/api/investment/projects/${encodeURIComponent(currentInvestmentProject.id)}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); 4050 await loadInvestmentProjects(true,currentInvestmentProject.id); showToast('é¡¹ç®æ¡£æ¡å·²ä¿å'); 4051 } catch (error) { showToast(error.message); } 4052} 4053 4054function investmentGapItems() { 4055 if (!currentInvestmentAnalysis) return []; 4056 const dimensions = Array.isArray(currentInvestmentAnalysis.dimensions) ? currentInvestmentAnalysis.dimensions : []; 4057 const risks = Array.isArray(currentInvestmentAnalysis.risks) ? currentInvestmentAnalysis.risks : []; 4058 const gaps = []; 4059 dimensions.forEach(item => { 4060 const pending = (item.score_audit?.criteria || []).filter(criterion => criterion.status === 'å¾ éªè¯'); 4061 pending.slice(0,2).forEach(criterion => gaps.push({type:`å¾ éªè¯ · ${item.name}`,text:`${criterion.criterion}ï¼${criterion.reason || 'éè¦è¡¥å æææä¸å®¶æ ¸éª'}`})); 4062 if (Number(item.score) < 6 && item.weaknesses?.[0]) gaps.push({type:`ä»·å¼é£é© · ${item.name} ${item.score}å`,text:item.weaknesses[0]}); 4063 }); 4064 risks.filter(item => item.level === 'é«').forEach(item => gaps.unshift({type:`é«é£é© · ${item.category || 'å°½è°é£é©'}`,text:item.description || 'éè¦ä¸å®¶å¤æ ¸'})); 4065 return gaps.slice(0,10); 4066} 4067 4068function renderInvestmentReviewChecklist() { 4069 if (!$('#investmentReviewGaps')) return; 4070 const gaps = investmentGapItems(); 4071 $('#investmentReviewGaps').innerHTML = gaps.length ? gaps.map((item,index)=>`<label><input type="checkbox" data-investment-gap="${index}"><span><strong>${escapeHtml(item.type)}</strong><small>${escapeHtml(item.text)}</small></span><b>å¾ å¤ç</b></label>`).join('') : '<div class="investment-no-gap"><strong>ææ æ¨¡åçæçæç¡®ç¼ºå£</strong><p>ä»éæ ¸å¯¹æ°æ®æ¥æºãæææå±ã注ååç±»åè´¢å¡é¢æµã</p></div>'; 4072 $('#investmentReviewStatus').textContent = currentInvestmentAnalysis ? `${gaps.length}é¡¹å¾ æ ¸éª` : 'çå¾ åæ'; 4073 renderInvestmentSavedReview(); 4074} 4075 4076function renderInvestmentSavedReview() { 4077 const review = currentInvestmentProject?.initial_review || {}; 4078 if (!Object.keys(review).length || !$('#investmentReviewer')) return; 4079 $('#investmentReviewer').value = review.reviewer || ''; 4080 $('#investmentReviewOpinion').value = review.opinion || '建议è¿å ¥æå§ä¼è®¨è®º'; 4081 $('#investmentReviewNotes').value = review.notes || ''; 4082 $$('[data-investment-review-confirm]').forEach(input => { input.checked = true; }); 4083 $('#investmentReviewStatus').textContent = `å·²ç± ${review.reviewer || 'è´è´£äºº'} å审`; 4084} 4085 4086async function saveInvestmentInitialReview() { 4087 if (!currentInvestmentProject || !currentInvestmentAnalysis) { showToast('请å 宿项ç®BPåæ'); return; } 4088 if (!currentInvestmentReportId) { showToast('æè¦åçä¸è½æäº¤æå§ä¼ï¼è¯·å ä¸ä¼ 宿´BPçææ£å¼æ¥å'); renderInvestmentWorkflowStep(2); return; } 4089 const reviewer = $('#investmentReviewer').value.trim(); 4090 if (!reviewer) { $('#investmentReviewer').focus(); showToast('请填åå审è´è´£äºº'); return; } 4091 if ($$('[data-investment-review-confirm]:checked').length !== $$('[data-investment-review-confirm]').length) { showToast('è¯·å®æä¸é¡¹å审确认'); return; } 4092 const gaps = investmentGapItems(); 4093 const resolved = $$('[data-investment-gap]:checked').map(input => Number(input.dataset.investmentGap)); 4094 const payload = {reviewer,opinion:$('#investmentReviewOpinion').value,
4094notes:$('#investmentReviewNotes').value.trim(),resolved_gaps:resolved.map(index=>gaps[index]).filter(Boolean),open_gaps:gaps.filter((_,index)=>!resolved.includes(index)),report_id:currentInvestmentReportId || ''}; 4095 try { 4096 currentInvestmentProject = await investmentApiRequest(`/api/investment/projects/${encodeURIComponent(currentInvestmentProject.id)}/initial-review`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); 4097 await loadInvestmentProjects(true,currentInvestmentProject.id); renderInvestmentWorkflowStep(5); showToast('ä¸å®¶å审已彿¡£ï¼è¿å ¥æå§ä¼å³ç'); 4098 } catch (error) { showToast(error.message); } 4099} 4100 4101function renderInvestmentSavedDecision() { 4102 if (!$('#investmentCommitteeSummary')) return; 4103 const analysis = currentInvestmentProject?.analysis || currentInvestmentAnalysis || {}; 4104 const review = currentInvestmentProject?.initial_review || {}; 4105 $('#investmentCommitteeScore').textContent = analysis.overall_score == null ? 'çå¾ åæ' : `ä¹ç»´ ${analysis.overall_score} / 10`; 4106 $('#investmentCommitteeSummary').innerHTML = `<span><small>AI建议</small><strong>${escapeHtml(analysis.recommendation || 'å¾ åæ')}</strong></span><span><small>ä¸å®¶å审</small><strong>${escapeHtml(review.opinion || 'å¾ å审')}</strong></span><span><small>æªè§£å³ç¼ºå£</small><strong>${Number(review.open_gaps?.length || 0)}项</strong></span>`; 4107 const decision = currentInvestmentProject?.decision || {}; 4108 const record = $('#investmentDecisionRecord'); 4109 if (!Object.keys(decision).length) { record.classList.add('hidden'); return; } 4110 $('#investmentCommitteeDecision').value = decision.decision || 'è¿å ¥æ£å¼å°½è°'; 4111 $('#investmentCommitteeDate').value = decision.decision_date || new Date().toISOString().slice(0,10); 4112 $('#investmentCommitteeOwner').value = decision.owner || ''; 4113 $('#investmentCommitteeAmount').value = decision.amount_wan || ''; 4114 $('#investmentCommitteeReason').value = decision.reason || ''; 4115 $('#investmentCommitteeMilestone').value = decision.milestone || ''; 4116 $('#investmentCommitteeDeadline').value = decision.deadline || ''; 4117 record.innerHTML = `<strong>已彿¡£ï¼${escapeHtml(decision.decision || 'æå§ä¼å·²å³ç')}</strong><p>${escapeHtml(decision.reason || 'æªå¡«åå³ççç±')}</p><span>è´è´£äººï¼${escapeHtml(decision.owner || 'å¾ æç¡®')} · ä¸ä¸æéï¼${escapeHtml(decision.deadline || 'å¾ æç¡®')}</span>`; 4118 record.classList.remove('hidden'); 4119} 4120 4121async function saveInvestmentCommitteeDecision() { 4122 if (!currentInvestmentProject) return; 4123 const owner = $('#investmentCommitteeOwner').value.trim(); 4124 const reason = $('#investmentCommitteeReason').value.trim(); 4125 const milestone = $('#investmentCommitteeMilestone').value.trim(); 4126 if (!owner || !reason || !milestone) { showToast('请填åè´è´£äººãå³ççç±åä¸ä¸éç¨ç¢'); return; } 4127 const payload = {decision:$('#investmentCommitteeDecision').value,decision_date:$('#investmentCommitteeDate').value,owner,amount_wan:Number($('#investmentCommitteeAmount').value || 0),reason,milestone,deadline:$('#investmentCommitteeDeadline').value,report_id:currentInvestmentReportId || ''}; 4128 try { 4129 currentInvestmentProject = await investmentApiRequest(`/api/investment/projects/${encodeURIComponent(currentInvestmentProject.id)}/decision`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); 4130 await loadInvestmentProjects(true,currentInvestmentProject.id); renderInvestmentSavedDecision(); showToast('æå§ä¼ç»è®ºå·²å½æ¡£å°é¡¹ç®æ¡£æ¡'); 4131 } catch (error) { showToast(error.message); } 4132} 4133 4134function downloadInvestmentMemo() { 4135 if (!currentInvestmentProject) { showToast('请å 鿩项ç®'); return; } 4136 const analysis = currentInvestmentProject.analysis || currentInvestmentAnalysis || {}; 4137 const review = currentInvestmentProject.initial_review || {}; 4138 const saved = currentInvestmentProject.decision || {}; 4139 const decision = Object.keys(saved).length ? saved : {decision:$('#investmentCommitteeDecision').value,decision_date:$('#investmentCommitteeDate').value,owner:$('#investmentCommitteeOwner').value,amount_wan:$('#investmentCommitteeAmount').value,reason:$('#investmentCommitteeReason').value,milestone:$('#investmentCommitteeMilestone').value,deadline:$('#investmentCommitteeDeadline').value}; 4140 const content = `åå«å¤©åæèµé¡¹ç®æå³çºªè¦\n\n项ç®ï¼${currentInvestmentProject.name}\næ¥æºï¼${currentInvestmentProject.source_type}\nèµéï¼${currentInvestmentProject.track}\né¶æ®µï¼${currentInvestmentProject.stage}\nBPæä»¶ï¼${currentInvestmentProject.latest_file_name || 'æªè®°å½'}\nä¹ç»´è¯åï¼${analysis.overall_score ?? 'â'} / 10\nAI建议ï¼${analysis.recommendation || 'æªå½¢æ'}\n\nä¸å®¶å审ï¼${review.opinion || 'æªå½¢æ'}\nå审è´è´£äººï¼${review.reviewer || 'æªå¡«å'}\næªè§£å³ç¼ºå£ï¼${Number(review.open_gaps?.length || 0)}项\n\næå§ä¼ç»è®ºï¼${decision.decision || 'å¾ ç¡®è®¤'}\nå³çæ¥æï¼${decision.decision_date || 'å¾ ç¡®è®¤'}\nè´è´£äººï¼${decision.owner || 'å¾ ç¡®è®¤'}\nææèµ/é¢ç®ï¼${decision.amount_wan || 0}ä¸å \nå³ççç±ï¼${decision.reason || 'å¾ å¡«å'}\nä¸ä¸éç¨ç¢ä¸åæ¢æ¡ä»¶ï¼${decision.milestone || 'å¾ å¡«å'}\n宿æéï¼${decision.deadline || 'å¾ ç¡®è®¤'}\n\nè¾¹ç说æï¼æ¬çºªè¦ä¸çBPäºå®æ¥èªä¸ä¼ ææï¼AIè¯åä¸å»ºè®®å±äºç ç©¶èæ¡ï¼æç»ç»è®ºç±äººå·¥æå§ä¼ç¡®è®¤ã`; 4141 const url = URL.createObjectURL(new Blob([content],{type:'text/plain;charset=utf-8'})); const link=document.createElement('a'); link.href=url; link.download=`${currentInvestmentProject.name}-æå³çºªè¦.txt`; link.click(); URL.revokeObjectURL(url); 4142} 4143 4144const HUAWAI_PROFILE_VERSIONS = { 4145 company_intro: {
4146 label:'åå²å£å¾ · 2026å¹´4æèµæ',area:30000,incubated:50,served:100,patents:700,rd:800, 4147 source:'ãå京åå«å¤©åå¥åº·ç§æé墿éå ¬å¸æ¦åµ--2026.4.16ã', 4148 note:'åå²èµææ«é²ï¼3ä¸ã¡ãå¨åµ50å®¶ã累计åµå100å®¶ãä» ä¾çæ¬è¿½æº¯ï¼ä¸ä½ä¸ºå½åé»è®¤å£å¾ã' 4149 }, 4150 application_2025: { 4151 label:'å½åæ£å¼å£å¾ · 已确认',area:40000,incubated:61,served:126,patents:700,rd:800, 4152 source:'ãèµè½ååºç§åé«è´¨éåå±é¡¹ç®ç³æ¥ä¹¦ã', 4153 note:'å½åæ£å¼æ°æ®ï¼4ä¸ã¡ãå¨åµ61å®¶ã累计åµå126å®¶ï¼ç±åå«äº2026-07-16确认éç¨å¤§æ°å£å¾ã' 4154 } 4155}; 4156 4157function applyPolicyProfileVersion(versionKey) { 4158 if (versionKey === 'enterprise_archive' && currentPolicyEnterprise) { 4159 applyEnterpriseProfileToForm(currentPolicyEnterprise); 4160 return; 4161 } 4162 const profile = HUAWAI_PROFILE_VERSIONS[versionKey] || HUAWAI_PROFILE_VERSIONS.application_2025; 4163 $('#policyArea').value = profile.area; 4164 $('#policyIncubatedCompanies').value = profile.incubated; 4165 $('#policyServedCompanies').value = profile.served; 4166 $('#policyPatents').value = profile.patents; 4167 $('#policyRdSpend').value = profile.rd; 4168 $('#companyApplicationEvidence').innerHTML = `<strong>å½åå£å¾ï¼${escapeHtml(profile.label)}</strong><span>${escapeHtml(profile.note)}</span><small>æ¥æºï¼${escapeHtml(profile.source)}ãçæå 容ä»éç¨è¥ä¸æ§ç §ãèµè´¨è¯ä¹¦ãç»è®¡å°è´¦ã审计æè´¢å¡éä»¶é项ä½è¯ã</small>`; 4169} 4170 4171async function policyApiRequest(path, options = {}) { 4172 const token = localStorage.getItem('token'); 4173 const headers = {...(options.headers || {})}; 4174 if (token) headers.Authorization = `Bearer ${token}`; 4175 const response = await fetch(`${BP_API_BASE}${path}`, {...options,headers}); 4176 const raw = await response.text(); 4177 let payload; 4178 try { payload = JSON.parse(raw); } catch { throw new Error(`æ¿çç³æ¥æå¡è¿åå¼å¸¸ï¼${raw.slice(0,100)}`); } 4179 if (!response.ok) throw new Error(payload.detail || payload.error || 'æ¿çç³æ¥æä½å¤±è´¥'); 4180 return payload.data; 4181} 4182 4183function initializePolicyWorkflowLayout() { 4184 const view = $('#policyhubView'); 4185 if (!view || view.dataset.workflowReady === 'true') return; 4186 view.dataset.workflowReady = 'true'; 4187 const title = $('.section-title', view); 4188 const workflow = document.createElement('div'); 4189 workflow.className = 'policy-workflow-shell'; 4190 workflow.innerHTML = ` 4191 <nav class="policy-workflow-steps" id="policyWorkflowSteps" aria-label="æ¿çè§å工使¥éª¤"> 4192 <button class="active" data-policy-step="1"><i>01</i><span><strong>ä¼ä¸å»ºæ¡£</strong><small>åºæ¬ä¿¡æ¯ä¸é¿æèµæ</small></span></button> 4193 <button data-policy-step="2"><i>02</i><span><strong>æ¿çåº</strong><small>éæ©å·²æææ°å¢æ¿ç</small></span></button> 4194 <button data-policy-step="3"><i>03</i><span><strong>éæ©ä¸å¹é </strong><small>éä¼ä¸ãéæ¿çãè¯å</small></span></button> 4195 <button data-policy-step="4"><i>04</i><span><strong>人工确认</strong><small>夿æ¯å¦æ£å¼ç³æ¥</small></span></button> 4196 <button data-policy-step="5"><i>05</i><span><strong>项ç®ä¹¦ç¼å</strong><small>ææ¨¡æ¿å½¢æå¯æ ¸éªèæ¡</small></span></button> 4197 </nav> 4198 <div class="policy-workflow-context"> 4199 <span>å½åä¼ä¸<strong id="policyContextEnterprise">å°æªéæ©</strong></span> 4200 <b>Ã</b> 4201 <span>å½åæ¿ç<strong id="policyContextPolicy">å°æªéæ©</strong></span> 4202 <div><button class="outline-button" id="policyWorkflowBack">â ä¸ä¸æ¥</button><button class="analyze-button" id="policyWorkflowNext">å®ææ¬æ¥ï¼ç»§ç» â</button></div> 4203 </div> 4204 <div class="policy-workflow-stage active" data-policy-stage="1"></div> 4205 <div class="policy-workflow-stage" data-policy-stage="2"></div> 4206 <div class="policy-workflow-stage" data-policy-stage="3"></div> 4207 <div class="policy-workflow-stage" data-policy-stage="4"></div> 4208 <div class="policy-workflow-stage" data-policy-stage="5"></div>`; 4209 title.after(workflow); 4210 4211 const stage1 = $('[data-policy-stage="1"]', workflow); 4212 const stage2 = $('[data-policy-stage="2"]', workflow); 4213 const stage3 = $('[data-policy-stage="3"]', workflow); 4214 const stage4 = $('[data-policy-stage="4"]', workflow); 4215 const stage5 = $('[data-policy-stage="5"]', workflow); 4216 const enterpriseArchive = $('.enterprise-archive', view); 4217 const profile = $('.policy-profile', view); 4218 const documentHub = $('.policy-document-hub', view); 4219 const sourceRule = $('.policy-source-rule', view); 4220 const result = $('#policyMatchResults'); 4221 const proposal = $('#proposalWorkbench');
4222 const unifiedGrid = $('.policy-unified-grid', view); 4223 4224 stage1.append(enterpriseArchive); 4225 stage1.append(profile); 4226 const enterpriseUpload = document.createElement('section'); 4227 enterpriseUpload.className = 'panel policy-stage-enterprise-upload'; 4228 enterpriseUpload.innerHTML = '<div class="development-panel-head"><div><strong>è¡¥å ä¼ä¸é¿æèµæ</strong><p>èµæåªè¿å ¥å½åä¼ä¸æ¡£æ¡ï¼åç»æææ¿çå¹é é½å¯éå¤ä½¿ç¨</p></div><span>è¥ä¸æ§ç §ãèµè´¨ãè´¢å¡ã人åãç¥è¯äº§æã项ç®ä¸ç»©</span></div><div class="enterprise-upload-slot"></div>'; 4229 const enterpriseUploadLabel = $('label[for="policyMaterialsInput"]', documentHub); 4230 if (enterpriseUploadLabel) $('.enterprise-upload-slot', enterpriseUpload).append(enterpriseUploadLabel); 4231 const uploadQueue = document.createElement('div'); 4232 uploadQueue.className = 'enterprise-upload-queue hidden'; 4233 uploadQueue.id = 'enterpriseUploadQueue'; 4234 uploadQueue.innerHTML = '<strong id="enterpriseUploadQueueTitle">已鿩æä»¶</strong><div id="enterpriseUploadQueueFiles"></div>'; 4235 enterpriseUpload.append(uploadQueue); 4236 const enterpriseUploadStatus = $('#enterpriseUploadStatus'); 4237 if (enterpriseUploadStatus) enterpriseUpload.append(enterpriseUploadStatus); 4238 const materialLedger = $('#policyMaterialLedger'); 4239 if (materialLedger) enterpriseUpload.append(materialLedger); 4240 stage1.append(enterpriseUpload); 4241 4242 const catalogLayout = document.createElement('div'); 4243 catalogLayout.className = 'policy-catalog-layout'; 4244 catalogLayout.innerHTML = `<aside class="panel policy-catalog-sidebar"><div class="development-panel-head"><div><strong>æ¿çåº</strong><p>æåæ²æ·æ¿çåæåAIè§£æç»æï¼ä¾ä¸åä¼ä¸åå¤å¹é </p></div><span id="policyCatalogCount">0项</span></div><button class="analyze-button policy-catalog-new" id="newPolicyCatalogButton">ï¼ æ°å¢æ¿ç</button><div class="policy-catalog-tools"><input id="policyCatalogSearch" type="search" placeholder="æç´¢æ¿çåç§°ãåå¸åä½ãæ¯ææ¹å"><div><select id="policyCatalogLevelFilter"><option>å ¨é¨çº§å«</option><option>å½å®¶çº§</option><option>å京å¸</option><option>å¤§å ´åº</option><option>ç»å¼åº</option><option>å ¶ä»å°åº</option></select><select id="policyCatalogValidityFilter"><option>å ¨é¨ç¶æ</option><option>ææ</option><option>å³å°æªæ¢</option><option>å·²æªæ¢</option><option>å¾ æ ¸éª</option></select></div><label class="policy-catalog-toggle"><input type="checkbox" id="policyCatalogShowMismatch"><span>æ¾ç¤ºå·²éèæ¿çï¼åºåä¸ç¬¦/æ¥æºå¾ æ ¸éªï¼ <b id="policyCatalogMismatchCount"></b></span></label></div><div id="policyCatalogList" class="policy-catalog-list"><p>æ£å¨è¯»åæ¿çåºâ¦</p></div></aside><div class="policy-catalog-editor"><section class="panel policy-catalog-selected hidden" id="policyCatalogSelectedDetail"></section></div>`; 4245 $('.policy-catalog-editor', catalogLayout).append(documentHub); 4246 stage2.append(catalogLayout); 4247 const workingTitle = document.createElement('label'); 4248 workingTitle.className = 'wide-label policy-working-title'; 4249 workingTitle.innerHTML = 'æ¿ç项ç®åç§°<input id="policyWorkingTitle" type="text" placeholder="ä¾
4249å¦ï¼å京å¸ç§æä¼ä¸åµåå¨è½åæå项ç®">'; 4250 const uploadGrid = $('.document-hub-grid', documentHub); 4251 documentHub.insertBefore(workingTitle, uploadGrid || documentHub.firstChild); 4252 const policyMetadata = document.createElement('div'); 4253 policyMetadata.className = 'policy-library-metadata'; 4254 policyMetadata.innerHTML = `<label>æ¿ç级å«<select id="policyLibraryLevel"><option>å京å¸</option><option>å½å®¶çº§</option><option>å¤§å ´åº</option><option>ç»å¼åº</option><option>å ¶ä»å°åº</option></select></label><label>éç¨å°åº<input id="policyLibraryRegion" type="text" value="å京å¸" placeholder="å ¨å½ / åäº¬å¸ / å¤§å ´åº"></label><label>åå¸åä½<input id="policyLibraryIssuer" type="text" placeholder="ä¾å¦ï¼å京å¸ç§å§ãä¸å ³æç®¡å§ä¼"></label><label>æ¿çç±»å<select id="policyLibraryType"><option>èµéæ¯æ</option><option>èµè´¨è®¤å®</option><option>ç ååæ°</option><option>ææè½¬å</option><option>产ä¸åä¸è½å°</option><option>äººææ¯æ</option><option>åµåå¨ä¸ååº</option><option>å ¶ä»</option></select></label><label>æ¯ææ¹å<input id="policyLibraryDirection" type="text" placeholder="ä¾
4254å¦ï¼çç©å»è¯ææè½¬åãç§æä¼ä¸åµå"></label><label>ç³æ¥å¯¹è±¡<input id="policyLibrarySubject" type="text" placeholder="ä¾å¦ï¼ç§æä¼ä¸ãåµåå¨ã髿 ¡é¢æ"></label><label>æé«æ¯æéé¢<input id="policyLibraryAmount" type="text" placeholder="ä¾å¦ï¼æé«500ä¸å / 以éç¥ä¸ºå"></label><label>ç³æ¥æªæ¢æ¥æ<input id="policyLibraryDeadline" type="date"></label><label>å½åææç¶æ<select id="policyLibraryValidity"><option>å¾ æ ¸éª</option><option>ææ</option><option>å³å°æªæ¢</option><option>å·²æªæ¢</option></select></label>`; 4255 documentHub.insertBefore(policyMetadata, uploadGrid || documentHub.firstChild); 4256 const catalogAction = document.createElement('div'); 4257 catalogAction.className = 'policy-stage-actions'; 4258 catalogAction.innerHTML = '<p>AIä¼ç¬ç«çè§£ç³æ¥å¯¹è±¡ãç¡¬é¨æ§ãè¯åé¡¹åæææ¸ åï¼å¹¶å°æ¿çé¿æä¿åï¼æ¤é¶æ®µä¸å¯¹ä»»ä½ä¼ä¸æåã</p><div><button class="outline-button" id="cancelNewPolicyButton">åæ¶æ°å¢</button><button class="analyze-button" id="savePolicyCatalogButton">ä¿åå¹¶å å ¥æ¿çåº â</button></div>'; 4259 documentHub.append(catalogAction); 4260 if (sourceRule) stage2.append(sourceRule); 4261 4262 const runMatchButton = $('#runPolicyMatch'); 4263 const matchSelection = document.createElement('section'); 4264 matchSelection.className = 'panel policy-match-selection'; 4265 matchSelection.innerHTML = `<div class="development-panel-head"><div><strong>éæ©ä¼ä¸ä¸æ¿ç项ç®</strong><p>å¹é åªè¯»åæéä¼ä¸çç¬ç«æ¡£æ¡åæéæ¿çç已解æåæ</p></div><span>ç¡¬é¨æ§ä¼å </span></div><div class="policy-selection-grid"><article><label>ç³æ¥ä¼ä¸<select id="policyMatchEnterpriseSelect"><option value="">è¯·éæ©ä¼ä¸æ¡£æ¡</option></select></label><strong id="policyMatchEnterpriseName">å°æªéæ©</strong><p id="policyMatchEnterpriseMeta">请è¿åç¬¬ä¸æ¥å»ºç«ä¼ä¸æ¡£æ¡</p><button class="outline-button" data-policy-jump="1">æ¥çä¼ä¸æ¡£æ¡</button></article><article><label>æ¿ç项ç®<select id="policyCatalogSelect"><option value="">è¯·éæ©å·²è§£ææ¿ç</option></select></label><strong id="policyMatchPolicyName">å°æªéæ©</strong><p id="policyMatchPolicyMeta">请å å¨ç¬¬äºæ¥æ´çæ¿çéç¥åéä»¶</p><button class="outline-button" data-policy-jump="2">æ¥çæ¿çèµæ</button></article></div><div class="policy-match-start"><div><strong>æ¬æ¬¡è¾åº</strong><p>0â100å¹é åãæ»¡è¶³/ä¸ç¬¦å/å¾ æ ¸éªé¡¹ãäºå®å屿¸ åãè·¨ææ¡£å£å¾æ ¸å¯¹ãææç¼ºå£ãå¾ æ ¸å®äº¤æ¥æ¸ åå项ç®ä¹¦èæ¡ã</p></div></div><div class="policy-analysis-status hidden" id="policyMatchRunStatus"><i></i><span><strong>æ£å¨å¹é ä¼ä¸ä¸æ¿ç</strong><small>éé¡¹æ ¸å¯¹ç¡¬é¨æ§ãè¯å项åè¯æææï¼è¯·ç¨åâ¦</small></span></div>`; 4266 $('.policy-match-start', matchSelection).append(runMatchButton); 4267 const matchMaterials = document.createElement('section'); 4268 matchMaterials.className = 'panel policy-match-materials'; 4269 matchMaterials.innerHTML = `<div class="development-panel-head"><div><strong>è¡¥å ç³æ¥ææï¼éå¡«ï¼</strong><p>æ"ä¸è¯»ææ"æ¹æ³ï¼ä¸å¹´åº¦ç³æ¥ä¹¦æä¾ç¨³å®äºå®ãè¾ å¯¼ä¼çºªè¦å®è¯å导åãèªè¯æ¥åä¾å®ç»©ååºï¼éä»½æ æ³¨è§è²ï¼AI ä¼åºå«é 读</p></div><span>ä¸ä¸ä¼ ä¹å¯ç´æ¥å¹é </span></div><label class="document-upload policy-match-materials-upload" for="policyMatchMaterialsInput"><input class="hidden" id="policyMatchMaterialsInput" type="file" multiple accept=".pdf,.doc,.docx,.ppt,.pptx,.xlsx,.txt"><i>è¡¥</i><span><strong>æ·»å æ¬å¹´åº¦ç³æ¥ç¸å ³ææ</strong><small>ä¸å¹´åº¦ç³æ¥ä¹¦ãè¾ å¯¼ä¼çºªè¦ãèªè¯æ¥åã审计æ¥åçï¼å¯å¤é</small></span><b>éæ©æä»¶</b></label><div id="policyMatchMaterialsList" class="policy-match-materials-list"></div>`; 4270 matchSelection.append(matchMaterials); 4271 stage3.append(matchSelection); 4272 4273 const opportunityPanel = document.createElement('section'); 4274 opportunityPanel.className = 'panel policy-opportunity-panel'; 4275 opportunityPanel.id = 'policyOpportunityPanel'; 4276 opportunityPanel.innerHTML = `<div class="development-panel-head"><div><strong>
4276ååæºä¼åç° Â· è¿å®¶ä¼ä¸è¿è½æ¥ä»ä¹</strong><p>ä¸é®æ«ææ¿çåºå ¨é¨æææ¿çï¼æç³æ¥å»ºè®®æåºï¼ç¹å»ä»»æä¸é¡¹ç´æ¥å¸¦å ¥ä¸æ¹è¯¦ç»å¹é </p></div><span id="policyOpportunityMeta">å°æªæ«æ</span></div><div class="policy-opportunity-actions"><button class="analyze-button" id="scanPolicyOpportunitiesButton">æ«æè¯¥ä¼ä¸å¯ç³æ¥æ¿ç</button><span id="policyOpportunityHint">éæ©ä¼ä¸æ¡£æ¡åå¯è¯»åå岿«æç»æ</span></div><div id="policyOpportunityResults"></div>`; 4277 stage3.append(opportunityPanel); 4278 4279 stage4.append(result); 4280 const decision = document.createElement('section'); 4281 decision.className = 'panel policy-decision-gate hidden'; 4282 decision.id = 'policyDecisionGate'; 4283 decision.innerHTML = `<div class="development-panel-head"><div><strong>ç³æ¥ç«é¡¹ç¡®è®¤</strong><p>AIè´è´£æåºå»ºè®®ï¼è´è´£äººç¡®è®¤æ¿ç仿æãä¼ä¸äºå®æ 误ä¸å¼å¾æå ¥ç³æ¥</p></div><span id="policyDecisionScore">çå¾ å¹é </span></div><div class="policy-confirmation-grid"><label>æ¬æ¬¡ç³æ¥é¡¹ç®åç§°<input id="policyApplicationProjectName" type="text" placeholder="请è¾å ¥æç»ç¨äºé¡¹ç®ä¹¦ç项ç®åç§°"></label><label>ç³æ¥è´è´£äºº<input id="policyApplicationOwner" type="text" placeholder="ä¾
4283å¦ï¼ä¼ä¸æå¡é¨ / å¼ èå¸"></label></div><div class="policy-confirmation-checks"><label><input type="checkbox" data-policy-confirm="official"> å·²æå¼å®æ¹åæï¼ç¡®è®¤æ¿çæææåç³æ¥ç¶æ</label><label><input type="checkbox" data-policy-confirm="facts"> å·²æ ¸å¯¹ä¼ä¸åºæ¬ä¿¡æ¯åå ³é®æ°å</label><label><input type="checkbox" data-policy-confirm="gaps"> å·²æ¥åå½åææç¼ºå£ï¼å¹¶æç¡®åç»è¡¥å 责任人</label><label><input type="checkbox" data-policy-confirm="decision"> ç¡®è®¤éæ©è¯¥æ¿ç项ç®è¿å ¥æ£å¼ç³æ¥</label></div><div class="policy-stage-actions"><p id="policyDecisionAdvice">宿å¹é åæ¾ç¤ºç«é¡¹å»ºè®®ã</p><button class="outline-button" id="exportPolicyProposalButton">å¯¼åº Word ç³æ¥ä¹¦èæ¡</button><button class="analyze-button" id="confirmPolicyApplicationButton">确认ç«é¡¹ï¼è¿å ¥é¡¹ç®ä¹¦ç¼å â</button></div>`; 4284 stage4.append(decision); 4285 const matchHistory = document.createElement('section'); 4286 matchHistory.className = 'panel policy-match-history'; 4287 matchHistory.innerHTML = `<div class="development-panel-head"><div><strong>åå²å¹é è®°å½</strong><p>æ¯æ¬¡ä¼ä¸ä¸æ¿çå¹é çåæ°ãç»è®ºä¸ç«é¡¹ç¶æï¼å¯åç宿´åææå¯¼åºç³æ¥ä¹¦</p></div><span id="policyMatchHistoryCount">â</span></div><div id="policyMatchHistoryList"><p>æ£å¨è¯»åå¹é è®°å½â¦</p></div>`; 4288 stage4.append(matchHistory); 4289 loadPolicyMatchHistory(); 4290 stage5.append(proposal); 4291 if (unifiedGrid) unifiedGrid.remove(); 4292 4293 $$('#policyWorkflowSteps button', workflow).forEach(button => button.addEventListener('click', () => requestPolicyWorkflowStep(Number(button.dataset.policyStep)))); 4294 $$('[data-policy-jump]', workflow).forEach(button => button.addEventListener('click', () => requestPolicyWorkflowStep(Number(button.dataset.policyJump)))); 4295 $('#policyWorkflowBack').addEventListener('click', () => renderPolicyWorkflowStep(Math.max(1, policyWorkflowStep - 1))); 4296 $('#policyWorkflowNext').addEventListener('click', advancePolicyWorkflow); 4297 $('#savePolicyCatalogButton').addEventListener('click', savePolicyCatalogItem); 4298 $('#newPolicyCatalogButton').addEventListener('click', startNewPolicyCatalogItem); 4299 $('#cancelNewPolicyButton').addEventListener('click', () => { policyCatalogMode = 'library'; renderPolicyCatalogEditor(); }); 4300 $('#policyCatalogSearch').addEventListener('input', event => { policyCatalogQuery = event.target.value.trim().toLowerCase(); renderPolicyCatalog(); }); 4301 $('#policyCatalogLevelFilter').addEventListener('change', event => { policyCatalogLevel = event.target.value; renderPolicyCatalog(); }); 4302 $('#policyCatalogValidityFilter').addEventListener('change', event => { policyCatalogValidityFilterValue = event.target.value; renderPolicyCatalog(); }); 4303 $('#policyCatalogShowMismatch').addEventListener('change', event => { policyCatalogShowMismatch = event.target.checked; renderPolicyCatalog(); }); 4304 $('#policyCatalogList').addEventListener('click', event => { 4305 const button = event.target.closest('[data-policy-catalog-id]'); 4306 if (button) selectPolicyCatalog(button.dataset.policyCatalogId); 4307 }); 4308 $('#policyMatchEnterpriseSelect').addEventListener('change', event => loadPolicyEnterprise(event.target.value)); 4309 $('#scanPolicyOpportunitiesButton').addEventListener('click', scanPolicyOpportunities); 4310 $('#policyOpportunityResults').addEventListener('click', event => { 4311 const card = event.target.closest('[data-opportunity-policy-id]'); 4312 if (card) applyOpportunityToMatch(card.dataset.opportunityPolicyId); 4313 }); 4314 $('#policyCatalogSelect').addEventListener('change', event => selectPolicyCatalog(event.target.value)); 4315 $('#policyMatchMaterialsInput').addEventListener('change', event => { addPolicyMatchMaterials(event.target.files); event.target.value = ''; }); 4316 $('#policyMatchMaterialsList').addEventListener('change', event => { 4317 const select = event.target.closest('[data-match-material-role]'); 4318 if (select) policyMatchMaterialFiles[Number(select.dataset.matchMaterialRole)].role = select.value; 4319 }); 4320 $('#policyMatchMaterialsList').addEventListener('click', event => { 4321 const button = event.target.closest('[data-match-material-remove]'); 4322 if (button) { policyMatchMaterialFiles.splice(Number(button.dataset.matchMaterialRemove), 1); renderPolicyMatchMaterials(); } 4323 }); 4324 $('#policyCatalogSelectedDetail').addEventListener('click', event => { 4325 if (event.target.closest('[data-policy-action="match"]')) renderPolicyWorkflowStep(3); 4326 if (event.target.closest('[data-policy-action="new"]')) startNewPolicyCatalogItem(); 4327 }); 4328 $('#confirmPolicyApplicationButton').addEventListener('click', confirmPolicyApplication); 4329 $('#exportPolicyProposalButton').addEventListener('click', exportPolicyProposalDocx); 4330 const deadlineBanner = document.createElement('div'); 4331 deadlineBanner.className = 'policy-deadline-banner hidden'; 4332 deadlineBanner.id = 'policyDeadlineBanner'; 4333 workflow.before(deadlineBanner); 4334 loadPolicyDeadlineAlerts(); 4335 renderPolicyWorkflowStep(1); 4336} 4337 4338// ============ æªæ¢æ¥ä¸»å¨æéæ¨ªå¹ ============ 4339 4340async function loadPolicyDeadlineAlerts() { 4341 const banner = $('#policyDeadlineBanner'); 4342 if (!banner) return; 4343 try { 4344 const data = await policyApiRequest('/api/policy/deadline-alerts?days=14'); 4345 renderPolicyDeadlineBanner(data); 4346 } catch (error) { 4347 banner.classList.add('hidden'); 4348 } 4349} 4350 4351function renderPolicyDeadlineBanner(data) { 4352 const banner = $('#policyDeadlineBanner'); 4353 if (!banner) return; 4354 const alerts = Array.isArray(data?.alerts) ? data.alerts : []; 4355 if (!alerts.length || banner.dataset.dismissed === 'true') { banner.classList.add('hidden'); return; } 4356 const groups = [ 4357 ['ç´§æ¥ Â· 3 天å ', item => item.days_left <= 3], 4358 ['ä¸å¨å æªæ¢', item => item.days_left > 3 && item.days_left <= 7], 4359 ['两å¨å æªæ¢', item => item.days_left > 7], 4360 ]; 4361 const sections = groups.map(([label, test]) => { 4362 const items = alerts.filter(test); 4363 if (!items.length) return ''; 4364 return `<div class="policy-deadline-group"><h5>${label}<i>${items.length}项</i></h5>${items.map(item => `<button type="button" class="policy-deadline-row${item.days_left <= 3 ? ' urgent' : ''}" data-deadline-policy-id="${escapeHtml(item.policy_id)}"><b>${item.days_left === 0 ? 'ä»å¤©æªæ¢' : `å© ${item.days_left} 天`}</b><span class="policy-deadline-title">${escapeHtml(item.title)}</span><small>${escapeHtml(item.level || '')}${item.issuing_authority ? ` · ${escapeHtml(item.issuing_authority)}` : ''}</small>${item.matched_enterprise ? `<em>${escapeHtml(item.matched_enterprise)} · ${escapeHtml(item.matched_verdict)}</em>` : '<em class="policy-deadline-nomatch">å°æªå¹é ä¼ä¸</em>'}</button>`).join('')}</div>`; 4365 }).join(''); 4366 banner.innerHTML = `<div class="policy-deadline-head"><strong>â° ${alerts.length} 项æ¿ç 14 å¤©å æªæ¢</strong><span>æç´§æ¥ç¨åº¦åç»ï¼ç¹å»ä»»æä¸æ¡ç´æ¥å¸¦å ¥å¹é </span><i id="policyDeadlineBannerDismiss" title="æ¬æ¬¡ä¼è¯ä¸åæç¤º">Ã</i></div><div class="policy-deadline-items">${sections}</div>`; 4367 banner.classList.remove('hidden'); 4368 $('#policyDeadlineBannerDismiss').addEventListener('click', () => { banner.dataset.dismissed = 'true'; banner.classList.add('hidden'); }); 4369 $$('[data-deadline-policy-id]', banner).forEach(btn => btn.addEventListener('click', () => applyOpportunityToMatch(btn.dataset.deadlinePolicyId))); 4370} 4371 4372function renderPolicyWorkflowStep(step) { 4373 policyWorkflowStep = Math.max(1, Math.min(5, Number(step) || 1)); 4374 $$('#policyWorkflowSteps button').forEach(button => { 4375 const value = Number(button.dataset.policyStep); 4376 button.classList.toggle('active', value === policyWorkflowStep); 4377 button.classList.toggle('complete', value < policyWorkflowStep); 4378 }); 4379 $$('.policy-workflow-stage').forEach(stage => stage.classList.toggle('active', Number(stage.dataset.policyStage) === policyWorkflowStep)); 4380 $('#policyWorkflowBack').classList.toggle('hidden', policyWorkflowStep === 1); 4381 $('#policyWorkflowNext').classList.toggle('hidden', policyWorkflowStep >= 3); 4382 $('#policyWorkflowNext').textContent = policyWorkflowStep === 1 ? 'ä¼ä¸æ¡£æ¡å·²åå¤ï¼æ´çæ¿ç â' : 'æ¿çèµæå·²åå¤ï¼å¼å§å¹é â'; 4383 $('#policyContextEnterprise').textContent = currentPolicyEnterprise?.name || 'å°æªéæ©'; 4384 $('#policyContextPolicy').textContent = policyWorkflowStep === 2 && policyCatalogMode === 'new' ? 'æ£å¨æ°å¢æ¿ç' : (currentPolicyCatalog?.title || 'å°æªéæ©'); 4385 if (policyWorkflowStep === 5) renderProposalDraft(policyAnalysisResult); 4386 window.scrollTo({top:0,behavior:'smooth'}); 4387} 4388 4389function requestPolicyWorkflowStep(step) { 4390 if (step > 1 && !currentPolicyEnterprise) { showToast('请å 宿ä¼ä¸å»ºæ¡£'); renderPolicyWorkflowStep(1); return; } 4391 if (step > 2 && !currentPolicyCatalog) { showToast('请å ä¿åå¹¶éæ©ä¸é¡¹æ¿çèµæ'); renderPolicyWorkflowStep(2); return; } 4392 if (step > 3 && !policyAnalysisResult) { showToast('请å 宿ä¼ä¸ä¸æ¿çå¹é '); renderPolicyWorkflowStep(3); return; } 4393 if (step === 5 && !currentPolicyApplicationId) { showToast('请å ç±è´è´£äººç¡®è®¤ç³æ¥ç«é¡¹'); renderPolicyWorkflowStep(4); return; } 4394 renderPolicyWorkflowStep(step); 4395} 4396 4397function advancePolicyWorkflow() { 4398 if (policyWorkflowStep === 1) { 4399 if (!currentPolicyEnterprise) { showToast('请å å建æéæ©ä¼ä¸æ¡£æ¡'); return; } 4400 renderPolicyWorkflowStep(2); 4401 return; 4402 } 4403 if (policyWorkflowStep === 2) { 4404 if (!currentPolicyCatalog) { showToast('请å ä¿åæéæ©ä¸é¡¹å·²ç»è§£æçæ¿ç'); return; } 4405 renderPolicyWorkflowStep(3); 4406 } 4407} 4408 4409function policyCatalogOverview(policy) { 4410 return policy?.analysis?.policy_overview || {}; 4411} 4412 4413function policyCatalogMetadata(policy) { 4414 const metadata = policy?.metadata || {}; 4415 const overview = policyCatalogOverview(policy); 4416 return { 4417 level:metadata.level || '级å«å¾ æ ¸éª',region:metadata.region || 'å°åºå¾ æ ¸éª', 4418 issuing_authority:metadata.issuing_authority || overview.issuing_authority || 'åå¸åä½å¾ 确认',
4419 policy_type:metadata.policy_type || 'ç±»åå¾ åç±»',support_direction:metadata.support_direction || 'æ¯ææ¹åå¾ æå', 4420 eligible_subject:metadata.eligible_subject || 'ç³æ¥å¯¹è±¡å¾ æå',support_amount:metadata.support_amount || overview.support_amount || '以éç¥ä¸ºå', 4421 deadline:metadata.deadline || overview.deadline || 'æªæ¢æ¶é´å¾ 确认',validity_status:metadata.validity_status || 'å¾ æ ¸éª' 4422 }; 4423} 4424 4425function policyCatalogValidity(policy) { 4426 const metadata = policyCatalogMetadata(policy); 4427 const deadline = String(metadata.deadline || ''); 4428 if (/^\d{4}-\d{2}-\d{2}/.test(deadline)) { 4429 const end = new Date(`${deadline.slice(0,10)}T23:59:59`); 4430 const days = Math.ceil((end.getTime() - Date.now()) / 86400000); 4431 if (days < 0) return 'å·²æªæ¢'; 4432 if (days <= 30) return 'å³å°æªæ¢'; 4433 } 4434 return metadata.validity_status || 'å¾ æ ¸éª'; 4435} 4436 4437const POLICY_KEEP_LEVELS = ['å½å®¶çº§', 'å京å¸', 'å¤§å ´åº']; 4438 4439function policyCatalogIsMismatch(policy) { 4440 const metadata = policy?.metadata || {}; 4441 if (metadata.region_mismatch || metadata.unverified_seed) return true; 4442 return !POLICY_KEEP_LEVELS.includes(policyCatalogMetadata(policy).level); 4443} 4444 4445function filteredPolicyCatalog() { 4446 return policyCatalog.filter(policy => { 4447 const metadata = policyCatalogMetadata(policy); 4448 if (!policyCatalogShowMismatch && policyCatalogIsMismatch(policy)) return false; 4449 const haystack = [policy.title,metadata.issuing_authority,metadata.support_direction,metadata.region,metadata.policy_type].join(' ').toLowerCase(); 4450 const queryMatch = !policyCatalogQuery || haystack.includes(policyCatalogQuery); 4451 const levelMatch = policyCatalogLevel === 'å ¨é¨çº§å«' || metadata.level === policyCatalogLevel; 4452 const validityMatch = policyCatalogValidityFilterValue === 'å ¨é¨ç¶æ' || policyCatalogValidity(policy) === policyCatalogValidityFilterValue; 4453 return queryMatch && levelMatch && validityMatch; 4454 }); 4455} 4456 4457function groupPolicyCatalog(policies) { 4458 const groups = []; 4459 const buckets = {}; 4460 POLICY_KEEP_LEVELS.forEach(level => { buckets[level] = []; groups.push([level, buckets[level]]); }); 4461 buckets['å ¶ä»'] = []; 4462 groups.push(['å ¶ä»ï¼åºåä¸ç¬¦ï¼', buckets['å ¶ä»']]); 4463 policies.forEach(policy => { 4464 const level = policyCatalogMetadata(policy).level; 4465 (buckets[level] || buckets['å ¶ä»']).push(policy); 4466 }); 4467 const validityRank = {'å³å°æªæ¢':0,'ææ':1,'å¾ æ ¸éª':2,'å·²æªæ¢':3}; 4468 groups.forEach(([, items]) => items.sort((a, b) => { 4469 const rankDiff = (validityRank[policyCatalogValidity(a)] ?? 4) - (validityRank[policyCatalogValidity(b)] ?? 4); 4470 if (rankDiff) return rankDiff; 4471 return String(policyCatalogMetadata(a).deadline).localeCompare(String(policyCatalogMetadata(b).deadline)); 4472 })); 4473 return groups.filter(([, items]) => items.length); 4474} 4475 4476function renderPolicyCatalog() { 4477 const filtered = filteredPolicyCatalog(); 4478 const mismatchCount = policyCatalog.filter(policyCatalogIsMismatch).length; 4479 const mismatchLabel = $('#policyCatalogMismatchCount'); 4480 if (mismatchLabel) mismatchLabel.textContent = mismatchCount ? `ï¼${mismatchCount}ï¼` : ''; 4481 $('#policyCatalogCount').textContent = filtered.length === policyCatalog.length ? `${policyCatalog.length}项` : `${filtered.length}/${policyCatalog.length}项`; 4482 const groups = groupPolicyCatalog(filtered); 4483 const cardHtml = policy => { 4484 const metadata = policyCatalogMetadata(policy); 4485 const validity = policyCatalogValidity(policy); 4486 const active = currentPolicyCatalog?.id === policy.id ? 'active' : ''; 4487 return `<button class="policy-catalog-card ${active}" data-investment-policy-validity="${escapeHtml(validity)}" data-policy-catalog-id="${escapeHtml(policy.id)}"><span><b>${escapeHtml(metadata.level)} · ${escapeHtml(validity)}</b><small>${Number(policy.document_count || 0)}份éä»¶</small></span><strong>${escapeHtml(policy.title)}</strong><p>${escapeHtml(metadata.issuing_authority)} · ${escapeHtml(metadata.deadline)}</p><em>${escapeHtml(metadata.support_direction)}</em></button>`; 4488 }; 4489 $('#policyCatalogList').innerHTML = groups.length ? groups.map(([level, items]) => 4490 `<div class="policy-catalog-group"><h4>${escapeHtml(level)}<i>${items.length}项</i></h4>${items.map(cardHtml).join('')}</div>` 4491 ).join('') : policyCatalog.length ? '<div class="policy-catalog-empty"><strong>没æç¬¦åçéæ¡ä»¶çæ¿ç</strong><p>å¯ä»¥è°æ´å ³é®è¯ãæ¿ççº§å«æææç¶æã</p></div>' : '<div class="policy-catalog-empty"><strong>æ¿çåºè¿æ¯ç©ºç</strong><p>ç¹å»âæ°å¢æ¿çâï¼ä¸ä¼ éç¥ãé件忍¡æ¿ï¼AIè§£æåä¼é¿æä¿åå¨è¿éã</p></div>'; 4492 const selectable = groupPolicyCatalog(policyCatalog.filter(policy => policyCatalogShowMismatch || !policyCatalogIsMismatch(policy))); 4493 $('#policyCatalogSelect').innerHTML = `<option value="">è¯·éæ©å·²è§£ææ¿ç</option>${selectable.map(([level, items]) => 4494 `<optgroup label="${escapeHtml(level)}ï¼${items.length}
4494项ï¼">${items.map(policy => `<option value="${escapeHtml(policy.id)}">${escapeHtml(policy.title)}</option>`).join('')}</optgroup>` 4495 ).join('')}`; 4496 if (currentPolicyCatalog) $('#policyCatalogSelect').value = currentPolicyCatalog.id; 4497 renderPolicyCatalogEditor(); 4498} 4499 4500function resetPolicyCatalogForm() { 4501 policyGuideFiles = []; 4502 policyTemplateFile = null; 4503 $('#policyWorkingTitle').value = ''; 4504 $('#policyLibraryLevel').value = 'å京å¸'; 4505 $('#policyLibraryRegion').value = 'å京å¸'; 4506 $('#policyLibraryIssuer').value = ''; 4507 $('#policyLibraryType').value = 'èµéæ¯æ'; 4508 $('#policyLibraryDirection').value = ''; 4509 $('#policyLibrarySubject').value = ''; 4510 $('#policyLibraryAmount').value = ''; 4511 $('#policyLibraryDeadline').value = ''; 4512 $('#policyLibraryValidity').value = 'å¾ æ ¸éª'; 4513 $('#policySourceUrl').value = ''; 4514 $('#policyGuideText').value = ''; 4515 $('#policyGuideInput').value = ''; 4516 $('#policyTemplateInput').value = ''; 4517 $('#policyGuideFileName').textContent = 'éæ©æä»¶'; 4518 $('#policyTemplateFileName').textContent = 'éæ©æ¨¡æ¿'; 4519 $('#policyGuideLedger').classList.add('hidden'); 4520 $('#policyGuideList').innerHTML = ''; 4521 $('#policyAnalysisStatus').classList.add('hidden'); 4522} 4523 4524function startNewPolicyCatalogItem() { 4525 policyCatalogMode = 'new'; 4526 resetPolicyCatalogForm(); 4527 renderPolicyCatalogEditor(); 4528 $('#policyContextPolicy').textContent = 'æ£å¨æ°å¢æ¿ç'; 4529 $('#policyWorkingTitle').focus(); 4530} 4531 4532function renderPolicyCatalogEditor() { 4533 const editor = $('.policy-document-hub'); 4534 const detail = $('#policyCatalogSelectedDetail'); 4535 if (!editor || !detail) return; 4536 if (!policyCatalog.length && policyCatalogMode === 'library') policyCatalogMode = 'new'; 4537 if (policyCatalogMode === 'new' || !currentPolicyCatalog) { 4538 editor.classList.remove('hidden'); 4539 detail.classList.add('hidden'); 4540 return; 4541 } 4542 const policy = currentPolicyCatalog; 4543 const metadata = policyCatalogMetadata(policy); 4544 const overview = policyCatalogOverview(policy); 4545 const validity = policyCatalogValidity(policy); 4546 detail.innerHTML = `<div class="policy-selected-head"><div><span>已仿¿çåºéæ©</span><h2>${escapeHtml(policy.title)}</h2><p>${escapeHtml(metadata.issuing_authority)} · ${escapeHtml(metadata.region)} · ${escapeHtml(metadata.policy_type)}</p></div><b data-validity="${escapeHtml(validity)}">${escapeHtml(validity)}</b></div><div class="policy-selected-metadata"><span><small>æ¿ç级å«</small><strong>${escapeHtml(metadata.level)}</strong></span><span><small>æ¯ææ¹å</small><strong>${escapeHtml(metadata.support_direction)}</strong></span><span><small>ç³æ¥å¯¹è±¡</small><strong>${escapeHtml(metadata.eligible_subject)}</strong></span><span><small>æé«æ¯æ</small><strong>${escapeHtml(metadata.support_amount)}</strong></span><span><small>ç³æ¥æªæ¢</small><strong>${escapeHtml(metadata.deadline)}</strong></span><span><small>æ¿çéä»¶</small><strong>${Number(policy.document_count || 0)}份</strong></span></div><div class="policy-selected-summary"><strong>AIæ¿ççè§£</strong><p>${escapeHtml(overview.source_boundary || 'å·²ä¿åæ¿çåæåè§£æç»æï¼æ£å¼ç³æ¥åä»éæ ¸éªå®æ¹æææ§ä¸ææ°éç¥ã')}</p></div><div class="policy-selected-actions"><button class="outline-button" data-policy-action="new">ï¼ æ°å¢å¦ä¸é¡¹æ¿ç</button><button class="analyze-button" data-policy-action="match">使ç¨è¿é¡¹æ¿çéæ©ä¼ä¸å¹¶è¯å â</button></div>`; 4547 editor.classList.add('hidden'); 4548 detail.classList.remove('hidden'); 4549} 4550 4551async function loadPolicyCatalog(force = false) { 4552 if (policyCatalogLoaded && !force) return; 4553 try { 4554 policyCatalog = await policyApiRequest('/api/policy/catalog'); 4555 policyCatalogLoaded = true; 4556 if (currentPolicyCatalog && !policyCatalog.some(item=>item.id===currentPolicyCatalog.id)) currentPolicyCatalog = null; 4557 renderPolicyCatalog(); 4558 if (!currentPolicyCatalog && policyCatalog.length) await selectPolicyCatalog(policyCatalog[0].id); 4559 } catch (error) { 4560 $('#policyCatalogList').innerHTML = `<div class="policy-catalog-empty"><strong>æ¿çèµæåºææªè¿æ¥</strong><p>${escapeHtml(error.message)}</p></div>`; 4561 } 4562} 4563 4564async function selectPolicyCatalog(policyId) { 4565 if (!policyId) { currentPolicyCatalog = null; renderPolicyCatalog(); renderPolicyMatchContext(); return; } 4566 try { 4567 currentPolicyCatalog = await policyApiRequest(`/api/policy/catalog/${encodeURIComponent(policyId)}`); 4568 policyCatalogMode = 'library'; 4569 renderPolicyCatalog(); 4570 renderPolicyMatchContext(); 4571 } catch (error) { 4572 showToast(error.message); 4573 } 4574} 4575 4576async function savePolicyCatalogItem() { 4577 const pastedGuide = $('#policyGuideText').value.trim(); 4578 if (!policyGuideFiles.length && pastedGuide.length < 20) { $('#policyGuideText').focus(); showToast('请ä¸ä¼ æ¿çéç¥/éä»¶ï¼æç²è´´ä¸å°äº20åçæ¿çè¦æ±'); return; } 4579 const form = new FormData();
4580 policyGuideFiles.forEach(file=>form.append('guide_files',file)); 4581 if (policyTemplateFile) form.append('template_file',policyTemplateFile); 4582 form.append('title',$('#policyWorkingTitle').value.trim()); 4583 form.append('guide_text',pastedGuide); 4584 form.append('source_url',$('#policySourceUrl').value.trim()); 4585 form.append('metadata_json',JSON.stringify({ 4586 level:$('#policyLibraryLevel').value,region:$('#policyLibraryRegion').value.trim(), 4587 issuing_authority:$('#policyLibraryIssuer').value.trim(),policy_type:$('#policyLibraryType').value, 4588 support_direction:$('#policyLibraryDirection').value.trim(),eligible_subject:$('#policyLibrarySubject').value.trim(), 4589 support_amount:$('#policyLibraryAmount').value.trim(),deadline:$('#policyLibraryDeadline').value, 4590 validity_status:$('#policyLibraryValidity').value 4591 })); 4592 const button = $('#savePolicyCatalogButton'); 4593 const status = $('#policyAnalysisStatus'); 4594 button.disabled = true; 4595 button.textContent = 'æ£å¨è§£ææ¿çéç¥åéä»¶â¦'; 4596 status.classList.remove('hidden','error','complete'); 4597 $('#policyAnalysisStatusTitle').textContent = 'æ£å¨å»ºç«æ¿çèµæ'; 4598 $('#policyAnalysisStatusDetail').textContent = 'æåç³æ¥å¯¹è±¡ãç¡¬é¨æ§ãè¯åé¡¹ãæªæ¢æ¶é´ãæææ¸ å忍¡æ¿ç« è'; 4599 try { 4600 currentPolicyCatalog = await policyApiRequest('/api/policy/catalog',{method:'POST',body:form}); 4601 status.classList.add('complete'); 4602 $('#policyAnalysisStatusTitle').textContent = 'æ¿çèµæå·²ä¿å并宿AIè§£æ'; 4603 $('#policyAnalysisStatusDetail').textContent = 'ç°å¨å¯ä»¥éæ©ä»»ä¸ä¼ä¸ä¸è¿é¡¹æ¿çè¿è¡å¹é '; 4604 policyCatalogLoaded = false; 4605 await loadPolicyCatalog(true); 4606 await selectPolicyCatalog(currentPolicyCatalog.id); 4607 policyCatalogMode = 'library'; 4608 renderPolicyCatalogEditor(); 4609 showToast('æ¿çå·²è¿å ¥èµæåºï¼å¯ç¨äºä¸åä¼ä¸çå¹é '); 4610 } catch (error) { 4611 status.classList.add('error'); 4612 $('#policyAnalysisStatusTitle').textContent = 'æ¿çèµææªä¿å'; 4613 $('#policyAnalysisStatusDetail').textContent = error.message; 4614 } finally { 4615 button.disabled = false; 4616 button.textContent = 'ä¿åå¹¶è§£æä¸ºæ¿çèµæ â'; 4617 } 4618} 4619 4620function renderPolicyMatchContext() { 4621 if ($('#policyContextEnterprise')) $('#policyContextEnterprise').textContent = currentPolicyEnterprise?.name || 'å°æªéæ©'; 4622 if ($('#policyContextPolicy')) $('#policyContextPolicy').textContent = currentPolicyCatalog?.title || 'å°æªéæ©'; 4623 if ($('#policyMatchEnterpriseName')) $('#policyMatchEnterpriseName').textContent = currentPolicyEnterprise?.name || 'å°æªéæ©'; 4624 if ($('#policyMatchEnterpriseMeta')) $('#policyMatchEnterpriseMeta').textContent = currentPolicyEnterprise ? `${currentPolicyEnterprise.region || 'åºåå¾ è¡¥å '} · ${currentPolicyEnterprise.industry || 'è¡ä¸å¾ è¡¥å '} · ${Number(currentPolicyEnterprise.document_count || 0)}ä»½èµæ` : '请è¿åç¬¬ä¸æ¥å»ºç«ä¼ä¸æ¡£æ¡'; 4625 if ($('#policyMatchEnterpriseSelect') && currentPolicyEnterprise) $('#policyMatchEnterpriseSelect').value = currentPolicyEnterprise.id; 4626 const overview = policyCatalogOverview(currentPolicyCatalog); 4627 const metadata = policyCatalogMetadata(currentPolicyCatalog); 4628 if ($('#policyMatchPolicyName')) $('#policyMatchPolicyName').textContent = currentPolicyCatalog?.title || 'å°æªéæ©'; 4629 if ($('#policyMatchPolicyMeta')) $('#policyMatchPolicyMeta').textContent = currentPolicyCatalog ? `${metadata.level} · ${metadata.issuing_authority} · ${metadata.deadline} · ${Number(currentPolicyCatalog.document_count || 0)}份éä»¶` : '请å å¨ç¬¬äºæ¥éæ©å·²ææ¿çææ°å¢æ¿ç'; 4630 if ($('#policyCatalogSelect') && currentPolicyCatalog) $('#policyCatalogSelect').value = currentPolicyCatalog.id; 4631} 4632 4633async function confirmPolicyApplication() { 4634 if (!policyAnalysisResult || !currentPolicyApplicationId) { showToast('请å 宿æ¿çå¹é '); return; } 4635 const projectName = $('#policyApplicationProjectName').value.trim(); 4636 const owner = $('#policyApplicationOwner').value.trim(); 4637 const checks = $$('[data-policy-confirm]'); 4638 if (!projectName) { $('#policyApplicationProjectName').focus(); showToast('è¯·ç¡®è®¤æ¬æ¬¡ç³æ¥é¡¹ç®åç§°'); return; } 4639 if (!owner) { $('#policyApplicationOwner').focus(); showToast('请填åç³æ¥è´è´£äºº'); return; } 4640 if (checks.some(item=>!item.checked)) { showToast('è¯·å®æåé¡¹ç³æ¥ç«é¡¹ç¡®è®¤'); return; } 4641 const payload = {project_name:projectName,owner,confirmed_items:checks.map(item=>item.dataset.policyConfirm),confirmed_at:new Date().toISOString()}; 4642 const button = $('#confirmPolicyApplicationButton'); 4643 button.disabled = true; 4644 try { 4645 await policyApiRequest(`/api/policy/matches/${encodeURIComponent(currentPolicyApplicationId)}/confirm`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); 4646 loadPolicyMatchHistory(); 4647 renderProposalDraft(policyAnalysisResult); 4648 renderPolicyWorkflowStep(5); 4649 showToast('ç³æ¥ç«é¡¹å·²ç¡®è®¤ï¼é¡¹ç®ä¹¦å·¥ä½å°å·²å¼å¯'); 4650 } catch (error) { 4651 showToast(error.message); 4652 } finally { 4653 button.disabled = false;
4654 } 4655} 4656 4657function setPolicySelectValue(selector, value) { 4658 const select = $(selector); 4659 if (!select || !value) return; 4660 if (![...select.options].some(option => option.value === value)) { 4661 select.add(new Option(value, value)); 4662 } 4663 select.value = value; 4664} 4665 4666const POLICY_QUALIFICATION_OPTIONS = [ 4667 'å½å®¶é«æ°ææ¯ä¼ä¸','ç§æåä¸å°ä¼ä¸','ä¸ç²¾ç¹æ°ä¸å°ä¼ä¸','ä¸ç²¾ç¹æ°âå°å·¨äººâä¼ä¸','åæ°åä¸å°ä¼ä¸','çªç¾ä¼ä¸','ä¸å ³æé«æ°ææ¯ä¼ä¸', 4668 'å½å®¶çº§ç§æä¼ä¸åµåå¨','å¸çº§ç§æä¼ä¸åµåå¨','åºçº§ç§æä¼ä¸åµåå¨','å½å®¶å¤æ¡ä¼å空é´','å¸çº§ä¼å空é´','工信é¨ç§æä¼ä¸åµåå¨' 4669]; 4670const POLICY_QUAL_NONE = 'ææªåå¾'; 4671const POLICY_QUAL_OTHER = 'å ¶ä»'; 4672 4673function initQualTagGroup(groupId, otherInputId) { 4674 const group = document.getElementById(groupId); 4675 if (!group) return; 4676 const options = [...POLICY_QUALIFICATION_OPTIONS, POLICY_QUAL_NONE, POLICY_QUAL_OTHER]; 4677 group.innerHTML = options.map(name => `<label class="qual-tag${name === POLICY_QUAL_NONE ? ' qual-tag-none' : ''}"><input type="checkbox" value="${escapeHtml(name)}"><span>${escapeHtml(name)}</span></label>`).join(''); 4678 const otherInput = otherInputId ? document.getElementById(otherInputId) : null; 4679 group.addEventListener('change', event => { 4680 const target = event.target; 4681 if (!target || target.type !== 'checkbox') return; 4682 const noneBox = group.querySelector(`input[value="${POLICY_QUAL_NONE}"]`); 4683 if (noneBox && target === noneBox && target.checked) { 4684 group.querySelectorAll('input[type="checkbox"]').forEach(box => { if (box !== noneBox) box.checked = false; }); 4685 if (otherInput) { otherInput.value = ''; otherInput.classList.add('hidden'); } 4686 } else if (noneBox && target !== noneBox && target.checked) { 4687 noneBox.checked = false; 4688 } 4689 const otherBox = group.querySelector(`input[value="${POLICY_QUAL_OTHER}"]`); 4690 if (otherInput && otherBox) otherInput.classList.toggle('hidden', !otherBox.checked); 4691 if (otherInput && otherBox && !otherBox.checked) otherInput.value = ''; 4692 }); 4693} 4694 4695function collectQualifications(groupId, otherInputId) { 4696 const group = document.getElementById(groupId); 4697 if (!group) return []; 4698 const otherInput = otherInputId ? document.getElementById(otherInputId) : null; 4699 return [...group.querySelectorAll('input[type="checkbox"]:checked')].map(box => { 4700 if (box.value !== POLICY_QUAL_OTHER) return box.value; 4701 const extra = otherInput ? otherInput.value.trim() : ''; 4702 return extra ? `å ¶ä»ï¼${extra}` : POLICY_QUAL_OTHER; 4703 }); 4704} 4705 4706function matchStandardQualification(token) { 4707 const text = String(token || '').trim(); 4708 if (!text) return null; 4709 if (POLICY_QUALIFICATION_OPTIONS.includes(text)) return text; 4710 if (text.includes('ææªåå¾') || text.includes('ææ èµè´¨')) return POLICY_QUAL_NONE; 4711 if (text.includes('å°å·¨äºº')) return 'ä¸ç²¾ç¹æ°âå°å·¨äººâä¼ä¸'; 4712 if (text.includes('ä¸ç²¾ç¹æ°')) return 'ä¸ç²¾ç¹æ°ä¸å°ä¼ä¸'; 4713 if (text.includes('çªç¾')) return 'çªç¾ä¼ä¸'; 4714 if (text.includes('ä¸å ³æ')) return 'ä¸å ³æé«æ°ææ¯ä¼ä¸'; 4715 if (text.includes('åæ°åä¸å°ä¼ä¸')) return 'åæ°åä¸å°ä¼ä¸'; 4716 if (text.includes('ä¼å')) { 4717 if (text.includes('å½å®¶') || text.includes('夿¡')) return 'å½å®¶å¤æ¡ä¼å空é´'; 4718 if (text.includes('å¸')) return 'å¸çº§ä¼å空é´'; 4719 return null; 4720 } 4721 if (text.includes('åµåå¨')) { 4722 if (text.includes('工信é¨') || text.includes('é¨çº§')) return '工信é¨ç§æä¼ä¸åµåå¨'; 4723 if (text.includes('å½å®¶')) return 'å½å®¶çº§ç§æä¼ä¸åµåå¨'; 4724 if (text.includes('å¸')) return 'å¸çº§ç§æä¼ä¸åµåå¨'; 4725 if (text.includes('åº')) return 'åºçº§ç§æä¼ä¸åµåå¨'; 4726 return null; 4727 } 4728 if (text.includes('髿°')) return 'å½å®¶é«æ°ææ¯ä¼ä¸'; 4729 if (text.includes('ç§æåä¸å°ä¼ä¸')) return 'ç§æåä¸å°ä¼ä¸'; 4730 return null; 4731} 4732 4733function renderQualificationTags(groupId, otherInputId, values) { 4734 const group = document.getElementById(groupId); 4735 if (!group) return; 4736 const otherInput = otherInputId ? document.getElementById(otherInputId) : null; 4737 const tokens = Array.isArray(values) ? values : String(values || '').split(/[ãï¼,
4737ï¼;/]+/).map(item => item.trim()).filter(Boolean); 4738 const checked = new Set(); 4739 const otherTexts = []; 4740 tokens.forEach(token => { 4741 if (String(token).startsWith(POLICY_QUAL_OTHER)) { 4742 const extra = String(token).replace(/^å ¶ä»[ï¼:]?/, '').trim(); 4743 if (extra) otherTexts.push(extra); 4744 checked.add(POLICY_QUAL_OTHER); 4745 return; 4746 } 4747 const matched = matchStandardQualification(token); 4748 if (matched) checked.add(matched); 4749 else { checked.add(POLICY_QUAL_OTHER); otherTexts.push(token); } 4750 }); 4751 group.querySelectorAll('input[type="checkbox"]').forEach(box => { box.checked = checked.has(box.value); }); 4752 if (otherInput) { 4753 otherInput.value = otherTexts.join('ã'); 4754 const otherBox = group.querySelector(`input[value="${POLICY_QUAL_OTHER}"]`); 4755 otherInput.classList.toggle('hidden', !(otherBox && otherBox.checked)); 4756 } 4757} 4758 4759function resetQualificationTags(groupId, otherInputId) { 4760 const group = document.getElementById(groupId); 4761 if (!group) return; 4762 group.querySelectorAll('input[type="checkbox"]').forEach(box => { box.checked = false; }); 4763 const otherInput = otherInputId ? document.getElementById(otherInputId) : null; 4764 if (otherInput) { otherInput.value = ''; otherInput.classList.add('hidden'); } 4765} 4766 4767function renderTycSyncSummary(sync) { 4768 const box = $('#tycSyncSummary'); 4769 if (!box) return; 4770 const basic = sync.basic || {}; 4771 const rows = [ 4772 ['æç«æ¶é´', basic.established_date], 4773 ['注åèµæ¬', basic.reg_capital], 4774 ['æ³å®ä»£è¡¨äºº', basic.legal_person], 4775 ['ä¿¡ç¨ä»£ç ', basic.credit_code], 4776 ['ç»è®°ç¶æ', basic.reg_status], 4777 ['人åè§æ¨¡', basic.staff_range || (basic.social_staff_num != null ? `${basic.social_staff_num}人åä¿` : '')], 4778 ].filter(([, value]) => value); 4779 const shareholders = Array.isArray(sync.shareholders) ? sync.shareholders : []; 4780 const patents = sync.patents || {}; 4781 const patentItems = Array.isArray(patents.items) ? patents.items : []; 4782 const penalty = sync.administrative_penalty || {}; 4783 const tags = Array.isArray(sync.tags) ? sync.tags : []; 4784 const hints = Array.isArray(sync.qualification_hints) ? sync.qualification_hints : []; 4785 box.innerHTML = ` 4786 <div class="tyc-sync-grid">${rows.map(([key, value]) => `<span><small>${key}</small><b>${escapeHtml(String(value))}</b></span>`).join('')}</div> 4787 ${shareholders.length ? `<div class="tyc-sync-block"><strong>è¡ä¸ï¼${shareholders.length}åï¼</strong><span>${shareholders.map(item => `${escapeHtml(item.name || 'â')}${item.percent ? ` ${escapeHtml(item.percent)}` : ''}${item.amount ? `ï¼è®¤ç¼´${escapeHtml(item.amount)}ï¼` : ''}`).join('ï¼')}</span></div>` : ''} 4788 <div class="tyc-sync-block"><strong>ä¸å©</strong><span>å ± ${Number(patents.total || 0)} ä»¶${patentItems.length ? `ï¼${patentItems.map(item => escapeHtml(item.title || 'æªå½åä¸å©')).join('ï¼')}` : ''}</span></div> 4789 <div class="tyc-sync-block"><strong>è¡æ¿å¤ç½</strong><span>${escapeHtml(penalty.note || 'æªæ¥è¯¢')}</span></div> 4790 ${tags.length ? `<div class="tyc-sync-block"><strong>å¤©ç¼æ¥æ ç¾</strong><span>${tags.map(item => escapeHtml(item)).join('ï¼')}</span></div>` : ''} 4791 ${hints.length ? `<div class="tyc-sync-block"><strong>å·²èªå¨å¾éèµè´¨</strong><span>${hints.map(item => escapeHtml(item)).join('ã')}</span></div>` : ''} 4792 <p class="tyc-sync-source">æ°æ®æ¥æºï¼å¤©ç¼æ¥ï¼ä»¥å·¥åå ¬ç¤ºä¸ºå · æ¥è¯¢æ¶é´ ${escapeHtml(sync.queried_at || '')}</p>`; 4793 box.classList.remove('hidden'); 4794} 4795 4796async function syncTianyanchaForNewEnterprise() { 4797 const nameInput = $('#policyEnterpriseName'); 4798 const name = nameInput.value.trim(); 4799 if (!name) { nameInput.focus(); showToast('请å å¡«åä¼ä¸å ¨ç§°ï¼åä»å¤©ç¼æ¥åæ¥'); return; } 4800 const button = $('#tycSyncButton'); 4801 const originalText = button.textContent; 4802 button.disabled = true; 4803 button.textContent = '忥ä¸â¦'; 4804 try { 4805 const result = await policyApiRequest('/api/policy/enterprises/tianyancha-sync', { 4806 method: 'POST', 4807 headers: {'Content-Type': 'application/json'}, 4808 body: JSON.stringify({name}), 4809 }); 4810 const sync = result.sync || result; 4811 const basic = sync.basic || {}; 4812 const regionInput = $('#policyEnterpriseRegion'); 4813 const currentRegion = regionInput.value.trim();
4814 if (basic.suggested_region && (!currentRegion || currentRegion === (regionInput.defaultValue || '').trim())) { 4815 regionInput.value = basic.suggested_region; 4816 } 4817 const hints = Array.isArray(sync.qualification_hints) ? sync.qualification_hints : []; 4818 if (hints.length) { 4819 const existing = collectQualifications('policyEnterpriseQualGroup', 'policyEnterpriseCertificationOther').filter(item => item !== POLICY_QUAL_NONE); 4820 renderQualificationTags('policyEnterpriseQualGroup', 'policyEnterpriseCertificationOther', [...new Set([...existing, ...hints])]); 4821 } 4822 renderTycSyncSummary(sync); 4823 showToast('å¤©ç¼æ¥å·¥åæ°æ®åæ¥å®æï¼è¯·æ ¸å¯¹åå建档æ¡'); 4824 } catch (error) { 4825 showToast(error.message); 4826 } finally { 4827 button.disabled = false; 4828 button.textContent = originalText; 4829 } 4830} 4831 4832async function syncTianyanchaForExistingEnterprise() { 4833 if (!currentPolicyEnterprise) { showToast('请å éæ©ä¼ä¸æ¡£æ¡'); return; } 4834 const button = $('#tycSyncExistingButton'); 4835 const originalText = button.textContent; 4836 button.disabled = true; 4837 button.textContent = '忥ä¸â¦'; 4838 try { 4839 const result = await policyApiRequest('/api/policy/enterprises/tianyancha-sync', { 4840 method: 'POST', 4841 headers: {'Content-Type': 'application/json'}, 4842 body: JSON.stringify({name: currentPolicyEnterprise.name, enterprise_id: currentPolicyEnterprise.id}), 4843 }); 4844 if (result.enterprise) { 4845 currentPolicyEnterprise = result.enterprise; 4846 renderPolicyEnterprise(result.enterprise); 4847 } 4848 showToast('å·²åæ¥å¤©ç¼æ¥å·¥åæ°æ®ï¼è¯·ç¡®è®¤åä¿åç»å'); 4849 } catch (error) { 4850 showToast(error.message); 4851 } finally { 4852 button.disabled = false; 4853 button.textContent = originalText; 4854 } 4855} 4856 4857function applyEnterpriseProfileToForm(enterprise) { 4858 const profile = enterprise?.profile || {}; 4859 $('#policyProfileVersion').innerHTML = `<option value="enterprise_archive">${escapeHtml(enterprise.name)} · å½åæ¡£æ¡å£å¾</option>`; 4860 setPolicySelectValue('#policyRegion', profile.region || enterprise.region || 'å京å¸å¤§å ´åº'); 4861 setPolicySelectValue('#policyIndustry', profile.industry || enterprise.industry || 'å ¶ä»å»è¯å¥åº·ä¼ä¸'); 4862 renderQualificationTags('policyQualGroup', 'policyCertificationOther', profile.qualifications || profile.certification || enterprise.certification || 'ææªåå¾'); 4863 setPolicySelectValue('#policyStage', profile.stage || enterprise.stage || '稳å®è¿è¥ä¸è½åè¾åº'); 4864 $('#policyRdSpend').value = Number(profile.rd_spend_wan || 0); 4865 $('#policyPatents').value = Number(profile.intellectual_property_count || 0); 4866 $('#policyArea').value = Number(profile.incubation_area_sqm || 0); 4867 $('#policyIncubatedCompanies').value = Number(profile.current_incubated_companies || 0); 4868 $('#policyServedCompanies').value = Number(profile.cumulative_incubated_companies || 0); 4869 $('#companyApplicationEvidence').innerHTML = `<strong>å½åä¼ä¸æ¡£æ¡ï¼${escapeHtml(enterprise.name)}</strong><span>${escapeHtml(profile.profile_version || 'ä¼ä¸èµæåºå½åçæ¬')}</span><small>ä¼ä¸ç»ååAIæè¦æ¥èªæ¬ä¼ä¸ç¬ç«èµæåºï¼æ¿çç³æ¥ä»éé项ç»å®åå§è¯æéä»¶ã</small>`; 4870} 4871 4872function renderPolicyEnterprise(enterprise) { 4873 currentPolicyEnterprise = enterprise; 4874 const ai = enterprise.ai_profile || {}; 4875 const documents = Array.isArray(enterprise.documents) ? enterprise.documents : []; 4876 $('#policyEnterpriseDashboard').classList.remove('hidden'); 4877 $('#currentPolicyEnterpriseName').textContent = enterprise.name; 4878 $('#currentPolicyEnterpriseMeta').textContent = `${enterprise.region || 'åºåå¾ è¡¥å '} · ${enterprise.industry || 'è¡ä¸å¾ è¡¥å '} · ${enterprise.certification || 'èµè´¨å¾ è¡¥å '}`; 4879 $('#currentEnterpriseDocumentCount').textContent = `${Number(enterprise.document_count || documents.length)}份`; 4880 $('#currentEnterpriseCharacters').textContent = `${Number(enterprise.parsed_characters || 0).toLocaleString()}å`; 4881 $('#currentEnterpriseConfidence').textContent = ai.confidence || (documents.length ? 'å¾ éæ°åæ' : 'å¾ ä¸ä¼ èµæ');
4882 $('#currentEnterpriseSummary').textContent = ai.company_summary || 'ä¸ä¼ è¥ä¸æ§ç §ãèµè´¨ã审计ãç¥è¯äº§æã人ååé¡¹ç®ææåï¼ç³»ç»ä¼å½¢æä¼ä¸å ¨æ¯ç»åã'; 4883 const capabilities = [...(ai.capabilities || []), ...(ai.qualifications || [])].slice(0,12); 4884 $('#currentEnterpriseCapabilities').innerHTML = capabilities.length ? capabilities.map(item=>`<b>${escapeHtml(item)}</b>`).join('') : '<span>å°æªå½¢æè½åä¸èµè´¨æ ç¾</span>'; 4885 const gaps = Array.isArray(ai.missing_information) ? ai.missing_information : []; 4886 $('#currentEnterpriseGaps').innerHTML = gaps.length ? gaps.slice(0,10).map(item=>`<span>${escapeHtml(item)}</span>`).join('') : '<span>å°æªè¯å«æç¡®ç¼ºå£ï¼ä»éäººå·¥æ£æ¥èµæå®æ´æ§</span>'; 4887 $('#currentEnterpriseDocumentList').innerHTML = documents.length ? documents.map(item=>`<span title="已解æ ${Number(item.characters || 0).toLocaleString()} å">${escapeHtml(item.name)}</span>`).join('') : '<span>å°æªä¸ä¼ èµæ</span>'; 4888 $('#policyMaterialCount').textContent = `${documents.length} 份ä¼ä¸èµæ`; 4889 $('#policyMaterialLedger').classList.toggle('hidden', !documents.length); 4890 $('#policyMaterialList').innerHTML = documents.map(item=>`<span class="material-file">${escapeHtml(item.name)}</span>`).join(''); 4891 applyEnterpriseProfileToForm(enterprise); 4892 renderPolicyMatchContext(); 4893} 4894 4895async function loadPolicyEnterprise(enterpriseId) { 4896 if (!enterpriseId) return; 4897 $('#policyEnterpriseSelect').value = enterpriseId; 4898 if ($('#policyMatchEnterpriseSelect')) $('#policyMatchEnterpriseSelect').value = enterpriseId; 4899 const enterprise = await policyApiRequest(`/api/policy/enterprises/${encodeURIComponent(enterpriseId)}`); 4900 renderPolicyEnterprise(enterprise); 4901 loadPolicyOpportunities(); 4902} 4903 4904async function loadPolicyEnterprises(force = false) { 4905 if (policyEnterprisesLoaded && !force) return; 4906 try { 4907 policyEnterprises = await policyApiRequest('/api/policy/enterprises'); 4908 policyEnterprisesLoaded = true; 4909 $('#policyEnterpriseArchiveStatus').textContent = `${policyEnterprises.length} å®¶ä¼ä¸æ¡£æ¡`; 4910 $('#policyEnterpriseSelect').innerHTML = policyEnterprises.length ? policyEnterprises.map(item=>`<option value="${escapeHtml(item.id)}">${escapeHtml(item.name)} · ${Number(item.document_count || 0)}ä»½èµæ</option>`).join('') : '<option value="">å°æªå建ä¼ä¸æ¡£æ¡</option>'; 4911 if ($('#policyMatchEnterpriseSelect')) $('#policyMatchEnterpriseSelect').innerHTML = `<option value="">è¯·éæ©ä¼ä¸æ¡£æ¡</option>${policyEnterprises.map(item=>`<option value="${escapeHtml(item.id)}">${escapeHtml(item.name)} · ${Number(item.document_count || 0)}ä»½èµæ</option>`).join('')}`; 4912 const preferred = currentPolicyEnterprise && policyEnterprises.some(item=>item.id===currentPolicyEnterprise.id) ? currentPolicyEnterprise.id : policyEnterprises[0]?.id; 4913 if (preferred) await loadPolicyEnterprise(preferred); 4914 } catch (error) { 4915 $('#policyEnterpriseArchiveStatus').textContent = 'ä¼ä¸æ¡£æ¡æå¡æªè¿æ¥'; 4916 showToast(error.message); 4917 } 4918} 4919 4920async function createPolicyEnterprise() { 4921 const name = $('#policyEnterpriseName').value.trim(); 4922 if (!name) { $('#policyEnterpriseName').focus(); showToast('请填åä¼ä¸å ¨ç§°'); return; } 4923 const button = $('#createPolicyEnterpriseButton'); 4924 button.disabled = true; 4925 try { 4926 const enterpriseQuals = collectQualifications('policyEnterpriseQualGroup','policyEnterpriseCertificationOther'); 4927 const enterprise = await policyApiRequest('/api/policy/enterprises', { 4928 method:'POST', 4929 headers:{'Content-Type':'application/json'}, 4930 body:JSON.stringify({ 4931 name, 4932 region:$('#policyEnterpriseRegion').value.trim(), 4933 industry:$('#policyEnterpriseIndustry').value, 4934 certification:enterpriseQuals.join('ã'), 4935 stage:'ç åä¸ç³æ¥åå¤', 4936 profile:{rd_spend_wan:0,intellectual_property_count:0,incubation_area_sqm:0,current_incubated_companies:0,cumulative_incubated_companies:0,profile_version:'ä¼ä¸æ¡£æ¡åå§çæ¬',qualifications:enterpriseQuals} 4937 }) 4938 }); 4939 currentPolicyEnterprise = enterprise;
4940 $('#policyEnterpriseEditor').classList.add('hidden'); 4941 $('#policyEnterpriseName').value = ''; 4942 resetQualificationTags('policyEnterpriseQualGroup','policyEnterpriseCertificationOther'); 4943 policyEnterprisesLoaded = false; 4944 await loadPolicyEnterprises(true); 4945 await loadPolicyEnterprise(enterprise.id); 4946 showToast(`å·²å建â${enterprise.name}âç¬ç«ä¼ä¸æ¡£æ¡`); 4947 } catch (error) { 4948 showToast(error.message); 4949 } finally { 4950 button.disabled = false; 4951 } 4952} 4953 4954async function saveCurrentPolicyEnterpriseProfile(silent = false) { 4955 if (!currentPolicyEnterprise) { if (!silent) showToast('请å éæ©ä¼ä¸æ¡£æ¡'); return null; } 4956 try { 4957 const updated = await policyApiRequest(`/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}`, { 4958 method:'PUT', 4959 headers:{'Content-Type':'application/json'}, 4960 body:JSON.stringify({name:currentPolicyEnterprise.name,profile:buildPolicyProfilePayload()}) 4961 }); 4962 renderPolicyEnterprise(updated); 4963 policyEnterprisesLoaded = false; 4964 if (!silent) showToast('å½åä¼ä¸ç»åå·²ä¿åï¼åç»æ¿çå¹é å°ä½¿ç¨è¿å¥æ°æ®'); 4965 return updated; 4966 } catch (error) { 4967 if (!silent) showToast(error.message); 4968 throw error; 4969 } 4970} 4971 4972async function reanalyzePolicyEnterprise() { 4973 if (!currentPolicyEnterprise) { showToast('请å éæ©ä¼ä¸æ¡£æ¡'); return; } 4974 const status = $('#enterpriseUploadStatus'); 4975 status.className = 'enterprise-upload-status'; 4976 status.textContent = 'æ£å¨ç¨DeepSeekéæ°é 读该ä¼ä¸çå ¨é¨èµæå¹¶æ´æ°å ¨æ¯ç»åâ¦'; 4977 try { 4978 const token = localStorage.getItem('token'); 4979 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 4980 // 弿¥ä»»å¡æ¨¡å¼ï¼é¿å AI é 读å¤ä»½èµæè¶ è¿ 60 ç§è¢«ç½ç»è®¾å¤ææ 4981 const createResp = await fetch(`${BP_API_BASE}/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}/analyze-async`, {method:'POST', headers}); 4982 const createPayload = await createResp.json().catch(() => ({})); 4983 if (!createResp.ok) throw new Error(createPayload.detail || 'ç»åæ´æ°ä»»å¡å建失败'); 4984 const result = await pollAsyncJob(createPayload.data.job_id, { 4985 onProgress: (elapsed) => { status.textContent = `AI æ£å¨é份é 读ä¼ä¸èµæï¼å·²ç¨æ¶ ${elapsed} ç§â¦`; } 4986 }); 4987 const enterprise = result.data; 4988 renderPolicyEnterprise(enterprise); 4989 status.className = 'enterprise-upload-status complete'; 4990 status.textContent = 'ä¼ä¸å ¨æ¯ç»åå·²æ´æ°ï¼åç»æ¿çå¹é ä¼ä½¿ç¨æ°çè½åãèµè´¨åèµæç¼ºå£ã'; 4991 } catch (error) { 4992 status.className = 'enterprise-upload-status error'; 4993 status.textContent = error.message; 4994 } 4995} 4996 4997async function updatePolicyMaterials(files) { 4998 const selectedFiles = [...files]; 4999 if (!selectedFiles.length) return; 5000 const status = $('#enterpriseUploadStatus'); 5001 const uploadLabel = $('#policyEnterpriseUploadFileName'); 5002 const uploadZone = $('label[for="policyMaterialsInput"]'); 5003 const queue = $('#enterpriseUploadQueue'); 5004 const queueFiles = $('#enterpriseUploadQueueFiles'); 5005 const total = selectedFiles.reduce((sum,file)=>sum+file.size,0); 5006 const sizeText = total > 1024*1024 ? `${(total/1024/1024).toFixed(1)}MB` : `${Math.max(1,Math.round(total/1024))}KB`; 5007 uploadLabel.textContent = `${selectedFiles.length}份 · ${sizeText}`; 5008 $('#enterpriseUploadQueueTitle').textContent = `æ¬æ¬¡å·²éæ© ${selectedFiles.length} ä»½èµæ`; 5009 queueFiles.innerHTML = selectedFiles.map(file=>`<span class="material-file">${escapeHtml(file.name)}</span>`).join(''); 5010 queue.classList.remove('hidden'); 5011 if (!currentPolicyEnterprise) { 5012 status.className = 'enterprise-upload-status error'; 5013 status.textContent = 'æä»¶å·²éæ©ï¼ä½å½åè¿æ²¡æç»å®ä¼ä¸ã请å å¨ä¸æ¹å建æéæ©ä¼ä¸æ¡£æ¡ï¼åéæ°éæ©æä»¶ã'; 5014 uploadLabel.textContent = '请å éæ©ä¼ä¸'; 5015 showToast('请å å建æéæ©ä¼ä¸æ¡£æ¡ï¼åä¸ä¼ ä¼ä¸èµæ'); 5016 return; 5017 } 5018 const allowed = new Set(['pdf','doc','docx','ppt','pptx','xlsx','txt']); 5019 const unsupported = selectedFiles.filter(file=>!allowed.has((file.name.split('.').pop() || '').toLowerCase())); 5020 if (unsupported.length) { 5021 status.className = 'enterprise-upload-status error'; 5022 status.textContent = `æä¸æ¯æï¼${unsupported.map(file=>file.name).join('ã')}ã请ä¸ä¼ PDFãWordãPPTãXLSXæTXTã`;
5023 uploadLabel.textContent = 'æä»¶æ ¼å¼ä¸æ¯æ'; 5024 return; 5025 } 5026 if (selectedFiles.some(file=>file.size > 50*1024*1024) || total > 55*1024*1024) { 5027 status.className = 'enterprise-upload-status error'; 5028 status.textContent = 'å个æä»¶ä¸è½è¶ è¿50MBï¼æ¬æ¬¡å计ä¸è½è¶ è¿55MBï¼è¯·åæ¹ä¸ä¼ ã'; 5029 uploadLabel.textContent = 'æä»¶è¿å¤§ï¼è¯·åæ¹'; 5030 showToast('ä¼ä¸èµæè¿å¤§ï¼è¯·åæ¹ä¸ä¼ '); 5031 return; 5032 } 5033 const form = new FormData(); 5034 selectedFiles.forEach(file=>form.append('files',file)); 5035 status.className = 'enterprise-upload-status'; 5036 status.textContent = `æ£å¨æ ${selectedFiles.length} ä»½èµæå å ¥â${currentPolicyEnterprise.name}âï¼æåå ¨æå¹¶æ´æ°ä¼ä¸ç»åâ¦`; 5037 uploadZone.classList.add('uploading'); 5038 uploadZone.setAttribute('aria-busy','true'); 5039 const beforeCount = Number(currentPolicyEnterprise.document_count || (currentPolicyEnterprise.documents || []).length || 0); 5040 try { 5041 const result = await policyApiRequest(`/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}/documents`, {method:'POST',body:form}); 5042 renderPolicyEnterprise(result.enterprise); 5043 status.className = result.analysis_error ? 'enterprise-upload-status error' : 'enterprise-upload-status complete'; 5044 status.textContent = result.analysis_error ? `èµæå·²ä¿åï¼ä½AIç»åæ´æ°å¤±è´¥ï¼${result.analysis_error}` : `å·²ä¿åå¹¶è§£æ ${result.uploaded.length} ä»½èµæï¼ä¼ä¸å ¨æ¯ç»åå·²æ´æ°ã`; 5045 uploadLabel.textContent = result.analysis_error ? 'èµæå·²ä¿å · AIå¾ éè¯' : `已宿 ${result.uploaded.length} 份`; 5046 $('#enterpriseUploadQueueTitle').textContent = result.analysis_error ? 'æ¬æ¬¡èµæå·²ä¿å' : 'æ¬æ¬¡ä¸ä¼ ä¸è§£æå·²å®æ'; 5047 policyEnterprisesLoaded = false; 5048 } catch (error) { 5049 // 大æä»¶å ¨ææå+ç»ååæèæ¶é¿ï¼ç½ç»ä¸æä¸ä»£è¡¨å¤±è´¥ï¼å åæ¥æ¡£æ¡åä¸ç»è®º 5050 let recovered = false; 5051 try { 5052 const fresh = await policyApiRequest(`/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}`); 5053 const freshEnterprise = fresh && fresh.enterprise ? fresh.enterprise : fresh; 5054 const afterCount = Number(freshEnterprise.document_count || (freshEnterprise.documents || []).length || 0); 5055 if (afterCount >= beforeCount + selectedFiles.length) { 5056 renderPolicyEnterprise(freshEnterprise); 5057 status.className = 'enterprise-upload-status complete'; 5058 status.textContent = `${selectedFiles.length} ä»½èµæå·²ä¿åï¼æå¡å¨æåèæ¶è¾é¿ï¼é¡µé¢æ¾çææ ååºï¼å®é å·²ä¸ä¼ æåï¼ã`; 5059 uploadLabel.textContent = `已宿 ${selectedFiles.length} 份`; 5060 $('#enterpriseUploadQueueTitle').textContent = 'æ¬æ¬¡èµæå·²ä¿å'; 5061 policyEnterprisesLoaded = false; 5062 recovered = true; 5063 } 5064 } catch (_) {} 5065 if (!recovered) { 5066 status.className = 'enterprise-upload-status error'; 5067 status.textContent = error.message; 5068 uploadLabel.textContent = 'ä¸ä¼ 失败 · ç¹å»éè¯'; 5069 } 5070 } finally { 5071 uploadZone.classList.remove('uploading'); 5072 uploadZone.removeAttribute('aria-busy'); 5073 } 5074} 5075 5076function updatePolicyGuideFiles(files) { 5077 policyGuideFiles = [...files]; 5078 $('#policyGuideFileName').textContent = policyGuideFiles.length ? `${policyGuideFiles.length} 份æä»¶` : 'éæ©æä»¶'; 5079 $('#policyGuideLedger').classList.toggle('hidden', !policyGuideFiles.length); 5080 $('#policyGuideList').innerHTML = policyGuideFiles.map(file => `<span class="material-file">${escapeHtml(file.name)}</span>`).join(''); 5081} 5082 5083function updatePolicyTemplate(file) { 5084 policyTemplateFile = file || null; 5085 $('#policyTemplateFileName').textContent = policyTemplateFile ? policyTemplateFile.name : 'éæ©æ¨¡æ¿'; 5086} 5087 5088function buildPolicyProfilePayload() { 5089 const profileQualifications = collectQualifications('policyQualGroup','policyCertificationOther'); 5090 return { 5091 profile_version: `${currentPolicyEnterprise?.name || 'å½åä¼ä¸'} · ä¼ä¸æ¡£æ¡å½åçæ¬`, 5092 profile_source: 'ä¼ä¸æ¡£æ¡ä¸å¿ï¼ç¨æ·ç»´æ¤çåºæ¬ä¿¡æ¯ä¸æ¬ä¼ä¸ç¬ç«èµæåº', 5093 region: $('#policyRegion').value, 5094 industry: $('#policyIndustry').value, 5095 certification: profileQualifications.join('ã'), 5096 qualifications: profileQualifications, 5097 stage: $('#policyStage').value,
5098 rd_spend_wan: Number($('#policyRdSpend').value || 0), 5099 intellectual_property_count: Number($('#policyPatents').value || 0), 5100 incubation_area_sqm: Number($('#policyArea').value || 0), 5101 current_incubated_companies: Number($('#policyIncubatedCompanies').value || 0), 5102 cumulative_incubated_companies: Number($('#policyServedCompanies').value || 0), 5103 }; 5104} 5105 5106function policyStatusClass(status) { 5107 if (String(status).includes('䏿»¡è¶³')) return 'failed'; 5108 if (String(status).includes('满足') && !String(status).includes('䏿»¡è¶³')) return 'passed'; 5109 return 'unknown'; 5110} 5111 5112function highlightDraftPending(escapedHtml) { 5113 return escapedHtml 5114 .replace(/(ãå¾ å¡«[^ã]*ã)/g, '<span class="draft-pending">$1</span>') 5115 .replace(/(\[å¾ è¡¥å [^\]]*\])/g, '<span class="draft-pending">$1</span>'); 5116} 5117 5118function renderProposalDraft(result) { 5119 const proposal = result?.proposal || {}; 5120 const sections = Array.isArray(proposal.sections) ? proposal.sections : []; 5121 const handoffChecklist = Array.isArray(result?.handoff_checklist) ? result.handoff_checklist : []; 5122 const handoffHtml = handoffChecklist.length ? `<div class="proposal-handoff"><strong>å¾ æ ¸å®äºé¡¹äº¤æ¥æ¸ å</strong><p>æéè¦åº¦æåºï¼æ ¸å®å®æåï¼èæ¡ä¸ç<span class="draft-pending">ãå¾ å¡«ã</span>项å³å¯è½å®</p><ol>${handoffChecklist.map(item=>`<li class="priority-${escapeHtml(item.priority)}"><b>${escapeHtml(item.priority)}</b><span>${escapeHtml(item.item)}<small>æ ¸å®éå¾ï¼${escapeHtml(item.how_to_verify)}</small></span></li>`).join('')}</ol></div>` : ''; 5123 const exportHtml = currentPolicyApplicationId ? `<div class="proposal-export-bar"><button class="analyze-button" id="exportPolicyProposalButton2">å¯¼åº Word ç³æ¥ä¹¦èæ¡ï¼é»/红/é»åè²ï¼â</button><span>红è²ãå¾ å¡«ãä¸ºå¾ æ ¸å®å 容ï¼é»è²ä¸ºäº¤æ¥æ¸ åï¼å¯¼åºåå¯ç´æ¥ç¼è¾æå°</span></div>` : ''; 5124 $('#proposalWorkbenchContent').innerHTML = `<div class="proposal-workbench-content"><div class="proposal-draft-meta"><strong>${proposal.template_detected ? 'å·²æä¸ä¼ 模æ¿è¯å«ç« è' : 'æªä¸ä¼ æ¨¡æ¿ Â· éç¨éç¨ç³æ¥ç»æ'}</strong><span>${escapeHtml(proposal.writing_boundary || '缺失å å®¹å¿ é¡»ä¿çå¾ è¡¥å æ è®°')}</span></div><div class="proposal-draft-sections">${sections.map((section,index)=>`<details class="proposal-draft-section" ${index===0?'open':''}><summary><span>${escapeHtml(section.number || String(index+1).padStart(2,'0'))}</span><strong>${escapeHtml(section.title || `ç« è${index+1}`)}</strong><i>å±å¼æ£æ</i></summary><div><p>${highlightDraftPending(escapeHtml(section.draft || 'ãå¾ å¡«ï¼å°æ è¶³å¤ææå½¢ææ£æã')).replace(/\n/g,'<br>')}</p><footer><span><b>å·²ç¨è¯æ®</b>${(section.evidence_used || []).length ? section.evidence_used.map(item=>`<em>${escapeHtml(item)}</em>`).join('') : '<em>ææ å·²ç»å®è¯æ®</em>'}</span><span class="missing"><b>å¾ è¡¥ææ</b>${(section.missing_evidence || []).length ? section.missing_evidence.map(item=>`<em>${escapeHtml(item)}</em>`).join('') : '<em>æ æ¨¡åè¯å«ç¼ºå£ï¼ä»éäººå·¥å¤æ ¸</em>'}</span></footer></div></details>`).join('') || '<p>没æçæå¯ç¨ç« èï¼è¯·è¡¥å 项ç®ä¹¦æ¨¡æ¿åéæ°åæã</p>'}</div>${exportHtml}${handoffHtml}</div>`; 5125 const exportButton = $('#exportPolicyProposalButton2'); 5126 if (exportButton) exportButton.addEventListener('click', exportPolicyProposalDocx); 5127 $('#proposalWorkbench').classList.remove('hidden'); 5128} 5129 5130function renderPolicyAnalysis(result) { 5131 policyAnalysisResult = result; 5132 currentPolicyApplicationId = result.application_id || null; 5133 const overview = result.policy_overview || {}; 5134 const eligibility = result.eligibility || {}; 5135 const requirements = Array.isArray(result.hard_requirements) ? result.hard_requirements : []; 5136 const scoring = Array.isArray(result.scoring_items) ? result.scoring_items : []; 5137 const requiredMaterials = Array.isArray(result.required_materials) ? result.required_materials : []; 5138 const gaps = Array.isArray(result.material_gaps) ? result.material_gaps : [];
5139 const audit = result.analysis_audit || {}; 5140 const trace = result.document_trace || {}; 5141 const enterpriseName = trace.enterprise_name || currentPolicyEnterprise?.name || 'å½åä¼ä¸'; 5142 const matched = Array.isArray(eligibility.matched) ? eligibility.matched : []; 5143 const unmatched = Array.isArray(eligibility.unmatched) ? eligibility.unmatched : []; 5144 const unknown = Array.isArray(eligibility.unknown) ? eligibility.unknown : []; 5145 const conditionTotal = matched.length + unmatched.length + unknown.length; 5146 const hardRequirementRate = conditionTotal ? Math.round(matched.length / conditionTotal * 100) : null; 5147 selectedPolicyCandidate = {name:overview.title || 'ä¸ä¼ çæ¿çç³æ¥é¡¹ç®', score:hardRequirementRate}; 5148 $('#policyMatchResults').innerHTML = `<section class="policy-analysis-overview panel"><div class="policy-analysis-head"><div><span>ä¸ä¼ åæåæ · ${audit.ai_called ? 'DeepSeek AIå·²è°ç¨' : 'æªè°ç¨å¤§æ¨¡å'}</span><h2>${escapeHtml(overview.title || 'æ¿ç项ç®åç§°å¾ ç¡®è®¤')}</h2><p>${escapeHtml(eligibility.conclusion || 'éæ ¸å¯¹ç¡¬æ§æ¡ä»¶ä¸è¯æææ')}</p></div><strong>${Number(eligibility.match_score || 0)}<small>/100</small></strong></div><div class="policy-analysis-summary"><article><small>åå¸åä½</small><strong>${escapeHtml(overview.issuing_authority)}</strong></article><article><small>æå·</small><strong>${escapeHtml(overview.policy_number)}</strong></article><article><small>ç³æ¥æªæ¢</small><strong>${escapeHtml(overview.deadline)}</strong></article><article><small>æ¯æéé¢</small><strong>${escapeHtml(overview.support_amount)}</strong></article></div><div class="policy-source-audit"><b>${trace.officially_verified ? 'å®ç½å·²æ ¸éª' : 'ç¨æ·ä¸ä¼ · å®ç½æªèªå¨æ ¸éª'}</b><span>${escapeHtml(overview.source_boundary || '')}</span>${trace.source_url ? `<a href="${escapeHtml(trace.source_url)}" target="_blank" rel="noopener">æå¼å¡«åç宿¹åæç½å â</a>` : ''}</div></section><section class="policy-analysis-detail panel"><div class="development-panel-head"><div><strong>ç¡¬æ§æ¡ä»¶é项å¹é </strong><p>æ¯ä¸é¡¹é½æ¾ç¤ºæ¿çåæãåå«ç¶æåæéè¯æ</p></div><span>${requirements.length} 项æ¡ä»¶</span></div><div class="policy-requirement-list">${requirements.map((item,index)=>`<article class="${policyStatusClass(item.status)}"><i>${index+1}</i><div><strong>${escapeHtml(item.requirement)}</strong><blockquote>${escapeHtml(item.source_quote)}</blockquote><p>è¯æè¦æ±ï¼${escapeHtml(item.evidence)}</p></div><b>${escapeHtml(item.status)}</b></article>`).join('') || '<p>æªæåå°æç¡®ç¡¬é¨æ§ï¼è¯·æ£æ¥éç¥æ¯å¦å å«ç³æ¥æ¡ä»¶ç« èã</p>'}</div></section><div class="policy-analysis-columns"><section class="panel"><strong>è¯å项ä¸å¾åçç¥</strong><div class="policy-scoring-list">${scoring.map(item=>`<article><span><b>${escapeHtml(item.item)}</b><em>${escapeHtml(item.points)}</em></span><p>${escapeHtml(item.strategy)}</p><small>æéè¯æ®ï¼${escapeHtml(item.evidence_needed)}</small></article>`).join('') || '<p>éç¥ä¸æªè¯å«è¯å表ï¼å»ºè®®è¡¥å 宿½ç»åæè¯åéä»¶ã</p>'}</div></section><section class="panel"><strong>ç³æ¥ææä¸ç¼ºå£</strong><div class="policy-material-columns"><div><b>è¦æ±æäº¤</b>${requiredMaterials.map(item=>`<span>â ${escapeHtml(item)}</span>`).join('') || '<span>æªè¯å«å°æææ¸ å</span>'}</div><div class="missing"><b>å½åå¾ è¡¥</b>${gaps.map(item=>`<span>! ${escapeHtml(item)}</span>`).join('') || '<span>æªè¯å«ç¼ºå£ï¼ä»éäººå·¥æ ¸å¯¹</span>'}</div></div></section></div><p class="policy-ai-audit">è°ç¨è®°å½ï¼${escapeHtml(audit.model || 'æªè°ç¨å¤§æ¨¡å')} · 已解æéç¥/æå ${Number(audit.parsed_guide_characters || trace.pasted_characters || 0).toLocaleString()} å · ä¼ä¸ææ ${Number(audit.material_documents || (trace.material_files || []).length)} 份ãAIç»è®ºæ¯ç³æ¥ç ç©¶èæ¡ï¼ä¸çäºæ¿çæææ§ææç»èµæ ¼è®¤å®ã</p>`; 5149 const resultRoot = $('#policyMatchResults'); 5150 const rateDisplay = $('.policy-analysis-head > strong', resultRoot); 5151 if (rateDisplay) rateDisplay.innerHTML = `${hardRequirementRate === null ? 'â' : hardRequirementRate}<small>硬æ¡ä»¶æ»¡è¶³ç</small>`; 5152 $('.policy-analysis-head span', resultRoot).textContent = `${enterpriseName} à ${currentPolicyCatalog?.title || trace.policy_title || 'æéæ¿ç'} · ${audit.ai_called ? 'DeepSeek AIå·²è°ç¨' : 'æªè°ç¨å¤§æ¨¡å'}`; 5153 $('.policy-analysis-detail .development-panel-head p', resultRoot).textContent = `é项æ¾ç¤ºâ${enterpriseName}âæ»¡è¶³ã䏿»¡è¶³æå¾ æ ¸éªçæ¡ä»¶ä¸è¯æææ`; 5154 $('.policy-analysis-overview', resultRoot).insertAdjacentHTML('beforeend', `<div class="enterprise-match-breakdown"><article class="matched"><strong>已满足 ${matched.length} 项</strong>${matched.map(item=>`<span>â ${escapeHtml(item)}</span>`).join('') || '<span>ææ å·²ç¡®è®¤æ»¡è¶³é¡¹</span>'}</article><article class="unmatched"><strong>ä¸ç¬¦å ${unmatched.length} 项</strong>${unmatched.map(item=>`<span>à ${escapeHtml(item)}</span>`).join('') || '<span>ææ æç¡®ä¸ç¬¦å项</span>'}</article><article class="unknown"><strong>å¾ æ ¸éª ${unknown.length} 项</strong>${unknown.map(item=>`<span>? ${escapeHtml(item)}</span>`).join('') || '<span>ææ æ¨¡åè¯å«çå¾ æ ¸éªé¡¹</span>'}</article></div>`); 5155 appendRuleBasis(resultRoot, [`满足 ${matched.length} 项`, `䏿»¡è¶³ ${unmatched.length} 项`, `å¾ æ ¸éª ${unknown.length} 项`], '硬æ¡ä»¶æ»¡è¶³çæâ满足项 Ã·ï¼æ»¡è¶³ï¼ä¸æ»¡è¶³ï¼å¾ æ ¸éªï¼â计ç®ï¼å¾ æ ¸éªé¡¹ä¸è®¡ä½æ»¡è¶³ãæ¡ä»¶æ¬èº«ç± AI ä»åææåï¼ä»é¡»äººå·¥éæ¡æ ¸å¯¹ã'); 5156 5157 const factLayers = result.fact_layers || {}; 5158 const stableFacts = Array.isArray(factLayers.stable) ? factLayers.stable : []; 5159 const timeSensitiveFacts = Array.isArray(factLayers.time_sensitive) ? factLayers.time_sensitive : []; 5160 const missingFacts = Array.isArray(factLayers.missing) ? factLayers.missing : []; 5161 const conflicts = Array.isArray(result.consistency_check?.conflicts) ? result.consistency_check.conflicts : []; 5162 const handoffChecklist = Array.isArray(result.handoff_checklist) ? result.handoff_checklist : []; 5163 resultRoot.insertAdjacentHTML('beforeend', `<section class="panel policy-fact-layers-panel"><div class="development-panel-head"><div><strong>äºå®å屿¸ å</strong><p>稳å®äºå®ç´æ¥å¤ç¨ãæ¶æäºå®æ´æ°ä¸ºæ¬å¹´åº¦å£å¾ã缺失äºå®ææç¤ºè¡¥é½ââ${escapeHtml(factLayers.boundary || '缺失项ä¸ç¼é ')}</p></div><span>ä¸è¯»ææ</span></div><div class="policy-fact-layers"><article class="fact-layer stable"><strong>â 稳å®äºå® · å¯ç´æ¥å¤ç¨ ${stableFacts.length} 项</strong>${stableFacts.map(item=>`<span>${escapeHtml(item)}</span>`).join('') || '<span>æªè¯å«å°æç¡®ç¨³å®äºå®</span>'}</article><article class="fact-layer time-sensitive"><strong>â» æ¶æäºå® · å¿ é¡»æ´æ° ${timeSensitiveFacts.length} 项</strong>${timeSensitiveFacts.map(item=>`<span>${escapeHtml(item)}</span>`).join('') || '<span>æªè¯å«å°æ¶æäºå®</span>'}</article><article class="fact-layer missing"><strong>ï¼ç¼ºå¤±äºå® · å¾ è¡¥é½ ${missingFacts.length} 项</strong>${missingFacts.map(item=>`<span class="missing-fact"><b>${escapeHtml(item.item || '')}</b>${escapeHtml(item.hint || '')}${item.reference_value ? `<em>åèï¼${escapeHtml(item.reference_value)}</em>` : ''}</span>`).join('') || '<span>ææ æ¨¡åè¯å«ç缺失äºå®</span>'}</article></div></section>`); 5164 if (conflicts.length) { 5165 resultRoot.insertAdjacentHTML('beforeend', `<section class="panel policy-consistency-panel"><div class="development-panel-head"><div><strong>è·¨ææ¡£å£å¾æ ¸å¯¹ · åç° ${conflicts.length} å¤çç¾</strong><p>åä¸äºå®å¨ä¸åææä¸è¡¨è¿°ä¸ä¸è´ï¼ç³»ç»ä¸èªå¨åå¼ï¼é¡»äººå·¥ç»ä¸å£å¾ååå®ç¨¿</p></div><span>é人工è£å³</span></div><div class="policy-conflict-list">${conflicts.map((item,index)=>`<article class="policy-conflict-card"><header><i>${index+1}</i><strong>${escapeHtml(item.fact)}</strong></header><div class="conflict-values">${(item.values || []).map(v=>`<span><b>${escapeHtml(v.value)}</b><small>${escapeHtml(v.source)}</small></span>`).join('')}</div><p>建议ï¼${escapeHtml(item.suggestion)}</p></article>`).join('')}</div></section>`); 5166 } 5167 if (handoffChecklist.length) { 5168 const priorityOrder = {'é«':0,'ä¸':1,'ä½':2}; 5169 const sortedChecklist = [...handoffChecklist].sort((a,b)=>(priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1)); 5170 resultRoot.insertAdjacentHTML('beforeend', `<section class="panel policy-handoff-panel"><div class="development-panel-head"><div><strong>å¾ æ ¸å®äºé¡¹äº¤æ¥æ¸ å</strong><p>æéè¦åº¦æåºï¼å®æè¿äºæ ¸å®åï¼ç³æ¥ä¹¦èæ¡ä¸çãå¾ å¡«ã项æè½è½å®</p></div><span>${handoffChecklist.length} 项</span></div><ol class="policy-handoff-list">${sortedChecklist.map(item=>`<li class="priority-${escapeHtml(item.priority)}"><b>${escapeHtml(item.priority)}</b><span><strong>${escapeHtml(item.item)}</strong><small>æ ¸å®éå¾ï¼${escapeHtml(item.how_to_verify)}</small></span></li>`).join('')}</ol></section>`); 5171 } 5172 $('#policyMatchResults').classList.remove('hidden'); 5173 $('#proposalWorkbench').classList.add('hidden'); 5174 $('#policyDecisionGate').classList.remove('hidden'); 5175 $('#policyDecisionScore').textContent = `${hardRequirementRate === null ? 'æªè®¡ç®' : `${hardRequirementRate}%硬æ¡ä»¶æ»¡è¶³ç`} · ${unmatched.length}项ä¸ç¬¦å · ${unknown.length}é¡¹å¾ æ ¸éª`; 5176 $('#policyApplicationProjectName').value = overview.title || currentPolicyCatalog?.title || ''; 5177 $('#policyDecisionAdvice').textContent = hardRequirementRate !== null && hardRequirementRate >= 80 && unmatched.length === 0 && unknown.length === 0 5178 ? '硬æ¡ä»¶å ¨é¨æå¤§é¨åå·²ç±æææ¯æï¼æ ¸éªæ¿çæææ§ååå§è¯æåï¼å¯è¿å ¥é¡¹ç®ä¹¦ç¼åã' 5179 : hardRequirementRate !== null && hardRequirementRate >= 60 && unmatched.length === 0 5180 ? 'å ·å¤ä¸å®ç³æ¥åºç¡ï¼ä½å¿ é¡»å å®æå¾ æ ¸éªé¡¹ï¼ä¸è½åªä¾æ®æ¨¡å夿ç«é¡¹ã' 5181 : 'å½åä¸å»ºè®®ç´æ¥ç³æ¥ï¼åºå è§£å³ä¸ç¬¦å项ãè¡¥é½æªç¥é¡¹å¹¶æ ¸éªå®æ¹æææ§ã'; 5182 renderPolicyWorkflowStep(4); 5183 $('#policyMatchResults').scrollIntoView({behavior:'smooth',block:'start'}); 5184} 5185 5186async function runPolicyMatch() { 5187 if (!currentPolicyEnterprise) { 5188 showToast('请å å建æéæ©è¦ç³æ¥çä¼ä¸æ¡£æ¡'); 5189 return; 5190 } 5191 if (!currentPolicyCatalog) { 5192 showToast('请å 仿¿çèµæåºéæ©ä¸é¡¹å·²ç»è§£æçæ¿ç'); 5193 renderPolicyWorkflowStep(2); 5194 return; 5195 } 5196 const form = new FormData(); 5197 form.append('policy_id', currentPolicyCatalog.id); 5198 form.append('profile_json', JSON.stringify(buildPolicyProfilePayload())); 5199 form.append('enterprise_id', currentPolicyEnterprise.id); 5200 const materialRoles = {};
5201 policyMatchMaterialFiles.forEach(entry => { 5202 form.append('materials', entry.file, entry.file.name); 5203 materialRoles[entry.file.name] = entry.role; 5204 }); 5205 if (policyMatchMaterialFiles.length) form.append('material_roles_json', JSON.stringify(materialRoles)); 5206 const button = $('#runPolicyMatch'); 5207 const status = $('#policyAnalysisStatus'); 5208 const matchStatus = $('#policyMatchRunStatus'); 5209 button.disabled = true; 5210 button.textContent = 'æ£å¨å¯¹ç §ä¼ä¸æ¡£æ¡ä¸æ¿çè¦æ±â¦'; 5211 status.classList.remove('hidden','error','complete'); 5212 matchStatus.classList.remove('hidden','error','complete'); 5213 $('#policyAnalysisStatusTitle').textContent = 'æ£å¨è¿è¡ä¼ä¸ä¸æ¿çå¹é '; 5214 $('#policyAnalysisStatusDetail').textContent = 'éé¡¹æ ¸å¯¹ç¡¬é¨æ§ãè¯å项ãè¯æææåä¼ä¸èµæï¼é常éè¦30â90ç§'; 5215 try { 5216 await saveCurrentPolicyEnterpriseProfile(true); 5217 const token = localStorage.getItem('token'); 5218 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 5219 // 弿¥ä»»å¡æ¨¡å¼ï¼æäº¤åç«å³è¿å job_idï¼åç轮询åç»æã 5220 // é¿å 30â90 ç§ä»¥ä¸çé¿è¿æ¥è¢«ç¨æ·ä¾§ç½ç»è®¾å¤ææï¼Failed to fetchï¼ã 5221 const response = await fetch(`${BP_API_BASE}/api/policy/analyze-async`, {method:'POST',headers,body:form}); 5222 const createPayload = await response.json().catch(() => ({})); 5223 if (!response.ok) throw new Error(createPayload.detail || 'æ¿çå¹é ä»»å¡å建失败'); 5224 const payload = await pollAsyncJob(createPayload.data.job_id, { 5225 onProgress: (elapsed) => { $('#policyAnalysisStatusDetail').textContent = `AI æ£å¨éé¡¹æ ¸å¯¹æ¿çæ¡æ¬¾ä¸ä¼ä¸ææï¼å·²ç¨æ¶ ${elapsed} ç§â¦`; } 5226 }); 5227 status.classList.add('complete'); 5228 matchStatus.classList.add('complete'); 5229 $('strong',matchStatus).textContent = 'ä¼ä¸ä¸æ¿çå¹é 宿'; 5230 $('small',matchStatus).textContent = 'ç»æå·²è¿å ¥äººå·¥ç¡®è®¤é¶æ®µ'; 5231 $('#policyAnalysisStatusTitle').textContent = payload.data.analysis_audit?.ai_called ? 'ä¼ä¸ä¸æ¿çå¹é 已宿' : '已宿è§åå¹é ï¼ä½æ¬æ¬¡æªè°ç¨å¤§æ¨¡å'; 5232 $('#policyAnalysisStatusDetail').textContent = '请æ¥çå¹é åãä¸ç¬¦å项åå¾ æ ¸éªé¡¹ï¼åç±è´è´£äººå³å®æ¯å¦æ£å¼ç³æ¥'; 5233 renderPolicyAnalysis(payload.data); 5234 loadPolicyMatchHistory(); 5235 showToast('å¹é åæå·²å®æï¼è¯·è¿è¡äººå·¥ç«é¡¹ç¡®è®¤'); 5236 } catch (error) { 5237 status.classList.add('error'); 5238 matchStatus.classList.add('error'); 5239 $('strong',matchStatus).textContent = 'å¹é æªå®æ'; 5240 $('small',matchStatus).textContent = error.message; 5241 $('#policyAnalysisStatusTitle').textContent = 'æ¿çææåææªå®æ'; 5242 $('#policyAnalysisStatusDetail').textContent = error.message; 5243 showToast(error.message); 5244 } finally { 5245 button.disabled = false; 5246 button.textContent = 'éæ°è¿è¡ä¼ä¸ä¸æ¿çå¹é â'; 5247 } 5248} 5249 5250// ============ åå²å¹é è®°å½ ============ 5251 5252async function loadPolicyMatchHistory() { 5253 const list = $('#policyMatchHistoryList'); 5254 if (!list) return; 5255 try { 5256 const items = await policyApiRequest('/api/policy/matches'); 5257 renderPolicyMatchHistory(Array.isArray(items) ? items : []); 5258 } catch (error) { 5259 list.innerHTML = '<p>å¹é è®°å½è¯»å失败ï¼è¯·ç¨åå·æ°éè¯ã</p>'; 5260 } 5261} 5262 5263function renderPolicyMatchHistory(items) { 5264 const list = $('#policyMatchHistoryList'); 5265 const count = $('#policyMatchHistoryCount'); 5266 if (!list) return; 5267 count.textContent = `${items.length} æ¡è®°å½`; 5268 if (!items.length) { 5269 list.innerHTML = '<p>ææ å¹é è®°å½ï¼å®æä¸æ¬¡"éæ©ä¸å¹é "åä¼èªå¨åºç°å¨è¿éã</p>'; 5270 return; 5271 } 5272 list.innerHTML = `<table class="policy-match-history-table"><thead><tr><th>æ¶é´</th><th>ä¼ä¸</th><th>æ¿ç项ç®</th><th>å¹é å</th><th>ç¶æ</th><th>æä½</th></tr></thead><tbody>${items.map(item => { 5273 const time = String(item.updated_at || '').slice(0, 16).replace('T', ' '); 5274 const confirmed = String(item.status).includes('已确认'); 5275 return `<tr><td>${escapeHtml(time)}</td><td>${escapeHtml(item.enterprise_name || 'â')}</td><td>${escapeHtml(item.project_name || item.policy_title || 'â')}</td><td><b class="${Number(item.match_score) >= 60 ? 'score-high' : 'score-low'}">${Number(item.match_score || 0)}</b></td><td>
5275<span class="match-status ${confirmed ? 'confirmed' : 'pending'}">${escapeHtml(item.status)}</span></td><td><button class="outline-button" data-match-view="${escapeHtml(item.id)}">æ¥ç</button>${confirmed ? `<button class="outline-button" data-match-export="${escapeHtml(item.id)}">导åºç³æ¥ä¹¦</button>` : ''}</td></tr>`; 5276 }).join('')}</tbody></table>`; 5277 $$('[data-match-view]', list).forEach(btn => btn.addEventListener('click', () => viewPolicyMatchRecord(btn.dataset.matchView))); 5278 $$('[data-match-export]', list).forEach(btn => btn.addEventListener('click', () => exportPolicyMatchRecord(btn.dataset.matchExport))); 5279} 5280 5281async function viewPolicyMatchRecord(matchId) { 5282 try { 5283 const match = await policyApiRequest(`/api/policy/matches/${encodeURIComponent(matchId)}`); 5284 const result = match.result || {}; 5285 result.application_id = match.id; 5286 renderPolicyAnalysis(result); 5287 showToast(`å·²è½½å ¥ ${match.enterprise_name || '该ä¼ä¸'} çåå²å¹é ç»æ`); 5288 } catch (error) { 5289 showToast(error.message); 5290 } 5291} 5292 5293async function exportPolicyMatchRecord(matchId) { 5294 const token = localStorage.getItem('token'); 5295 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 5296 showToast('æ£å¨çæ Word ç³æ¥ä¹¦èæ¡â¦'); 5297 try { 5298 const response = await fetch(`${BP_API_BASE}/api/policy/matches/${encodeURIComponent(matchId)}/export-async`, {method:'POST', headers}); 5299 const createPayload = await response.json().catch(() => ({})); 5300 if (!response.ok) throw new Error(createPayload.detail || '导åºä»»å¡å建失败'); 5301 const result = await pollAsyncJob(createPayload.data.job_id); 5302 const dlResp = await fetch(`${BP_API_BASE}/api/analysis-jobs/${createPayload.data.job_id}/download`, {headers}); 5303 if (!dlResp.ok) throw new Error('ç³æ¥ä¹¦ä¸è½½å¤±è´¥'); 5304 const blob = await dlResp.blob(); 5305 const link = document.createElement('a'); 5306 link.href = URL.createObjectURL(blob); 5307 link.download = (result && result.filename) || 'ç³æ¥ä¹¦èæ¡.docx'; 5308 link.click(); 5309 URL.revokeObjectURL(link.href); 5310 } catch (error) { 5311 showToast(error.message); 5312 } 5313} 5314 5315 5316 5317function renderPolicyOpportunities() { 5318 const meta = $('#policyOpportunityMeta'); 5319 const hint = $('#policyOpportunityHint'); 5320 const container = $('#policyOpportunityResults'); 5321 if (!container) return; 5322 if (!currentPolicyEnterprise) { 5323 policyOpportunityScan = null; 5324 meta.textContent = 'å°æªæ«æ'; 5325 hint.textContent = '请å éæ©ä¼ä¸æ¡£æ¡'; 5326 container.innerHTML = ''; 5327 return; 5328 } 5329 if (!policyOpportunityScan || !Array.isArray(policyOpportunityScan.opportunities)) { 5330 meta.textContent = 'å°æªæ«æ'; 5331 hint.textContent = `ç¹å»æé®ï¼ä¸ºâ${currentPolicyEnterprise.name}âæ«ææ¿çåºå ¨é¨æææ¿ç`; 5332 container.innerHTML = ''; 5333 return; 5334 } 5335 const summary = policyOpportunityScan.summary || {}; 5336 const scannedAt = String(policyOpportunityScan.scanned_at || '').slice(0, 10); 5337 meta.textContent = `${policyOpportunityScan.policy_count || 0}项æ¿ç · ${scannedAt}æ«æ`; 5338 hint.textContent = `å»ºè®®ç³æ¥ ${summary['å»ºè®®ç³æ¥'] || 0} 项 · å¾ æ ¸éª ${summary['å¾ æ ¸éªåå®'] || 0} 项 · æä¸å»ºè®® ${summary['æä¸å»ºè®®'] || 0} 项`; 5339 if (policyOpportunityScan.stale) hint.textContent += ' · â æ¿çåºå·²ææ´æ°ï¼å»ºè®®éæ°æ«æ'; 5340 const verdictClass = {'å»ºè®®ç³æ¥':'recommend','å¾ æ ¸éªåå®':'verify','æä¸å»ºè®®':'skip'}; 5341 const urgencyLabel = {'é«':'â¤30å¤©æªæ¢','ä¸':'â¤90å¤©æªæ¢','ä½':'æ¶é´å®½è£'}; 5342 container.innerHTML = `<div class="policy-opportunity-list">${policyOpportunityScan.opportunities.map(item => { 5343 const klass = verdictClass[item.verdict] || 'verify'; 5344 return `<article class="policy-opportunity-card ${klass}" data-opportunity-policy-id="${escapeHtml(item.policy_id)}"><header><b class="verdict">${escapeHtml(item.verdict)}</b><strong>${escapeHtml(item.title)}</strong><i class="urgency-${escapeHtml(item.urgency)}">${escapeHtml(urgencyLabel[item.urgency] || '')}</i></header><p>${escapeHtml(item.fit_reason || '')}</p><footer><span>${escapeHtml(item.issuing_authority || 'åå¸åä½å¾ 确认')}</span><span>æªæ¢ ${escapeHtml(item.deadline || 'æªæç¡®')}</span><span>${escapeHtml(item.support_amount || 'éé¢ä»¥éç¥ä¸ºå')}</span>${item.key_gap ? `<em>缺å£ï¼${escapeHtml(item.key_gap)}</em>` : ''}</footer></article>`; 5345 }).join('')}</div>
5345<p class="policy-opportunity-boundary">${escapeHtml(policyOpportunityScan.analysis_audit?.boundary || 'åçç»æä» ä¾åç°æºä¼ï¼æ£å¼ç³æ¥åè¯·å®æè¯¦ç»å¹é ä¸äººå·¥ç¡®è®¤ã')}</p>`; 5346} 5347 5348async function loadPolicyOpportunities() { 5349 if (!currentPolicyEnterprise) { renderPolicyOpportunities(); return; } 5350 try { 5351 const data = await policyApiRequest(`/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}/opportunities`); 5352 policyOpportunityScan = data && Array.isArray(data.opportunities) ? data : null; 5353 } catch (error) { 5354 policyOpportunityScan = null; 5355 } 5356 renderPolicyOpportunities(); 5357} 5358 5359async function scanPolicyOpportunities() { 5360 if (!currentPolicyEnterprise) { showToast('请å éæ©ä¼ä¸æ¡£æ¡'); return; } 5361 const button = $('#scanPolicyOpportunitiesButton'); 5362 const hint = $('#policyOpportunityHint'); 5363 button.disabled = true; 5364 button.textContent = 'æ£å¨æ«ææ¿çåºâ¦'; 5365 try { 5366 const token = localStorage.getItem('token'); 5367 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 5368 const response = await fetch(`${BP_API_BASE}/api/policy/enterprises/${encodeURIComponent(currentPolicyEnterprise.id)}/scan-opportunities-async`, {method:'POST', headers}); 5369 const createPayload = await response.json().catch(() => ({})); 5370 if (!response.ok) throw new Error(createPayload.detail || 'æºä¼æ«æä»»å¡å建失败'); 5371 const result = await pollAsyncJob(createPayload.data.job_id, { 5372 onProgress: (elapsed) => { hint.textContent = `AI æ£å¨æ¹éåçæ¿çåºï¼å·²ç¨æ¶ ${elapsed} ç§â¦`; } 5373 }); 5374 policyOpportunityScan = result.data || result; 5375 renderPolicyOpportunities(); 5376 const summary = policyOpportunityScan.summary || {}; 5377 showToast(`æ«æå®æï¼å»ºè®®ç³æ¥ ${summary['å»ºè®®ç³æ¥'] || 0} 项ï¼ç¹å»ä»»ææ¿çå¯å¸¦å ¥è¯¦ç»å¹é `); 5378 } catch (error) { 5379 hint.textContent = error.message; 5380 showToast(error.message); 5381 } finally { 5382 button.disabled = false; 5383 button.textContent = 'éæ°æ«æè¯¥ä¼ä¸å¯ç³æ¥æ¿ç'; 5384 } 5385} 5386 5387async function applyOpportunityToMatch(policyId) { 5388 if (!policyId) return; 5389 await selectPolicyCatalog(policyId); 5390 if (currentPolicyCatalog && currentPolicyCatalog.id === policyId) { 5391 renderPolicyWorkflowStep(3); 5392 showToast(`å·²å¸¦å ¥ã${currentPolicyCatalog.title}ãï¼ç¹å»ä¸æ¹æé®å¼å§è¯¦ç»å¹é `); 5393 $('.policy-match-selection')?.scrollIntoView({behavior:'smooth', block:'start'}); 5394 } 5395} 5396 5397async function exportPolicyProposalDocx() { 5398 if (!currentPolicyApplicationId) { showToast('请å 宿å¹é å¹¶çæç³æ¥è®°å½ï¼é仿¿çåºéæ©æ¿çåå¹é ï¼'); return; } 5399 const token = localStorage.getItem('token'); 5400 const headers = token ? {Authorization:`Bearer ${token}`} : {}; 5401 showToast('æ£å¨çæ Word ç³æ¥ä¹¦èæ¡â¦'); 5402 try { 5403 // 弿¥ä»»å¡æ¨¡å¼ï¼æ¨¡æ¿å¡«å éè°ç¨å¤§æ¨¡åï¼å¯è½è¶ è¿ 60 ç§è¢«ç½ç»è®¾å¤ææ 5404 const createResp = await fetch(`${BP_API_BASE}/api/policy/matches/${encodeURIComponent(currentPolicyApplicationId)}/export-async`, {method:'POST', headers}); 5405 const createPayload = await createResp.json().catch(() => ({})); 5406 if (!createResp.ok) throw new Error(createPayload.detail || 'ç³æ¥ä¹¦çæä»»å¡å建失败'); 5407 const jobId = createPayload.data.job_id; 5408 const result = await pollAsyncJob(jobId, { 5409 timeoutMs: 5*60*1000, 5410 onProgress: (elapsed) => { showToast(`AI æ£å¨å¡«å ç³æ¥ä¹¦å 容ï¼å·²ç¨æ¶ ${elapsed} ç§â¦`); } 5411 }); 5412 const downloadResp = await fetch(`${BP_API_BASE}/api/analysis-jobs/${jobId}/download`, { headers }); 5413 if (!downloadResp.ok) throw new Error('ç³æ¥ä¹¦ä¸è½½å¤±è´¥ï¼è¯·éè¯'); 5414 const blob = await downloadResp.blob(); 5415 const url = URL.createObjectURL(blob); 5416 const link = document.createElement('a'); 5417 link.href = url; 5418 link.download = (result && result.filename) || 'ç³æ¥ä¹¦èæ¡.docx'; 5419 document.body.appendChild(link); 5420 link.click(); 5421 link.remove(); 5422 URL.revokeObjectURL(url); 5423 showToast('ç³æ¥ä¹¦èæ¡å·²å¼å§ä¸è½½'); 5424 } catch (error) { 5425 showToast(error.message); 5426 } 5427} 5428 5429function generateProposalDraft() { 5430 if (!policyAnalysisResult) { showToast('请å ä¸ä¼ æ¿çåæå¹¶å®æAIåæ'); return; } 5431 renderProposalDraft(policyAnalysisResult); 5432 $$('.proposal-draft-section').forEach(section => { section.open = true; }); 5433 $('#proposalWorkbench').scrollIntoView({behavior:'smooth',block:'start'}); 5434 showToast('项ç®ä¹¦èæ¡å·²å±å¼ï¼å¾ è¡¥ææåè¯æ®æ¥æºåå·²æ æ³¨'); 5435} 5436 5437const croModules = { 5438 project:{ eyebrow:'01 · PROJECT CREATION', title:'项ç®å建å导', description:'建ç«å®æ´äº§åæ¡£æ¡ï¼å¨æè¿½é®ä¼æ¹å注åè·¯å¾çå ³é®é®é¢ã', action:'å¼å§å»ºç«é¡¹ç®æ¡£æ¡ â', steps:[['产åè¾¹ç','颿ç¨éãéç¨äººç¾¤ã产åç»æ'],['ç åç¶æ','å·²æç ç©¶ãæ ·åãä½ç³»ä¸ææ¡£'],['ç®æ å¸åº','NMPAåå½é 注å计å']] }, 5439 path:{ eyebrow:'02 · PATH PLANNING', title:'注åè·¯å¾è§å', description:'æ¯è¾å¸¸è§ãåæ°æç¹æ®å®¡æ¥è·¯å¾ï¼å½¢æP50/P80/P95éç¨ç¢ã', action:'çæè·¯å¾å¯¹æ¯ â', steps:[['è·¯å¾å设','åç±»ãééä¸éç¨æ¡ä»¶'],['å ³é®èç¹','æ£æµã临åºãæ²éä¸ç³æ¥'],['ä¸ç¡®å®æ§','åç½®æ¡ä»¶ä¸å¤éè·¯å¾']] }, 5440 gap:{ eyebrow:'03 · GAP ANALYSIS', title:'æ³è§å·®è·åæ', description:'æç°æèµæéæ¡å¯¹ç §æ³è§ãæå¯¼ååå项ç®é¶æ®µè¦æ±ã', action:'å¯å¨å·®è·åæ â', steps:[['è¦æ±ç©éµ','éç¨æ¡æ¬¾ä¸è¯æ®è¦æ±'],['ç°ç¶æ å°','满足ãé¨å满足ã缺失'],['æ´æ¹è®¡å','ä¼å 级ãè´è´£äººåæªæ¢æ¶é´']] }, 5441 document:{ eyebrow:'04 · DOCUMENT WORKSPACE', title:'CTD / eCTD ææ¡£å·¥ä½å°', description:'å°ç ç©¶ãè´¨éã临åºå管çèµææ å°å°å·å®ç« èå¹¶ååæ°åã', action:'è¿å ¥ææ¡£å·¥ä½å° â', steps:[['ç´ æå°å¾','åå§ææä¸ç®æ ç« è'],['ç« èçæ','ææ¥æºå 容ä¸ç¼ºå¤±å ä½'],['çæ¬å®¡æ ¸','ä½è ãå®¡æ ¸è åä¿®æ¹è®°å½']] }, 5442 knowledge:{ eyebrow:'05 · REGULATION KNOWLEDGE', title:'æ³è§ç¥è¯åº', description:'æ£ç´¢NMPAãCDEãCMDEãICHä¸è¯å ¸ï¼å¹¶ä¿çåæãç
5442æ¬åææç¶æã', action:'æ£ç´¢æ³è§åæ â', steps:[['宿¹æ¥æº','å叿ºæãæå·å龿¥'],['éç¨å¤æ','产åãé¶æ®µåçææ¡ä»¶'],['å¼ç¨ç®¡ç','åæç段ãè§£é䏿´æ°æ¶é´']] }, 5443 review:{ eyebrow:'06 · COMPLIANCE REVIEW', title:'åè§å®¡æ¥', description:'å®¡æ¥ææ¡£ç¼ºé¡¹ãå¼ç¨é误ãååçç¾åæ æ³ç±è¯æ®æ¯æçæ¿è¯ºã', action:'å建审æ¥ä»»å¡ â', steps:[['宿´æ§','å¿ éç« èä¸éä»¶'],['ä¸è´æ§','æ°æ®ãçæ¬åæ¯è¯å²çª'],['å¯è¿½æº¯æ§','ç»è®ºå°åå§è¯æ®']] }, 5444 tasks:{ eyebrow:'07 · CRO DELIVERY', title:'CROä»»å¡ä¸å¿', description:'ææ³è§çç¥è½¬æå¯æ§è¡é¡¹ç®è®¡åï¼ç®¡çè´è´£äººãä¾èµã交ä»åè¡¥æ£ã', action:'æ¥ç项ç®ä»»å¡ â', steps:[['éç¨ç¢','计åãå®é ä¸å³çé¨'],['责任åå·¥','ç³åæ¹ãCROä¸ç¬¬ä¸æ¹'],['交ä»éªæ¶','è´¨éæ åä¸å®¡æ ¸ç¾ç½²']] } 5445}; 5446 5447function selectCroModule(moduleId) { 5448 const module = croModules[moduleId]; 5449 if (!module) return; 5450 $$('#croModuleGrid button').forEach(button => button.classList.toggle('active', button.dataset.croModule === moduleId)); 5451 $('#croModuleEyebrow').textContent = module.eyebrow; 5452 $('#croModuleTitle').textContent = module.title; 5453 $('#croModuleDescription').textContent = module.description; 5454 $('#croModuleAction').textContent = module.action; 5455 $('#croModuleAction').dataset.module = moduleId; 5456 $('#croModuleSteps').innerHTML = module.steps.map(([title,desc])=>`<div class="cro-work-step"><b>${title}</b><small>${desc}</small></div>`).join(''); 5457} 5458 5459async function runRegulatoryTriage() { 5460 const type = $('#regProductType').selectedOptions[0]?.textContent || $('#regProductType').value; 5461 const description = $('#regDescription').value.trim(); 5462 if (description.length < 20) { $('#regDescription').focus(); showToast('请补å ä½ç¨æºå¶ãåè¯ç©ç»æã颿ç¨éåå·²æè¯æ®'); return; } 5463 const button = $('#runRegulatoryTriage'); 5464 button.disabled = true; 5465 button.textContent = 'æ£å¨æ£ç´¢æ³è§å¹¶è¿è¡è·¯å¾ç å¤â¦'; 5466 try { 5467 const route = await investmentApiRequest('/api/regulatory/triage', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({product_type:type,stage:$('#regProjectStage').value,description})}); 5468 const audit = route.audit || {}; 5469 const references = Array.isArray(route.knowledge_references) ? route.knowledge_references : []; 5470 $('#regClassification').textContent = route.route_candidate || 'éçå®/åè·¯å¾æ¯é'; 5471 $('#regScopeJudgment').textContent = route.scope_judgment || 'å½åä¿¡æ¯ä¸è¶³'; 5472 $('#regDecisionGate').textContent = route.decision_gate || '宿屿§çå®'; 5473 $('#regCriticalGap').textContent = route.critical_gap || 'è¡¥å å ³é®ææ¯ä¿¡æ¯'; 5474 $('#regRiskLevel').textContent = route.risk_level || 'å¾ å¤å®'; 5475 $('#regRiskBasis').textContent = (route.risk_basis || []).slice(0,2).join('ï¼') || 'ä¿¡æ¯ä¸è¶³æ¶ä¸å¼ºè¡å®çº§'; 5476 $('#regRouteTitle').textContent = `${route.route_candidate || 'ç管路å¾'} · è·¯å¾ç å¤`; 5477 $('#regRouteCaveat').textContent = `è·¯å¾ç½®ä¿¡åº¦${route.route_confidence || 'ä½'} · ${audit.ai_called ? `å·²å®é è°ç¨ ${audit.model || 'DeepSeek'}` : audit.mode || 'æ¬æºåç'}`; 5478 $('#regAiStatus').textContent = audit.ai_called ? 'AIå·²è°ç¨ · éä¸å®¶å¤æ ¸' : 'æ¬æºåç · éä¸å®¶å¤æ ¸'; 5479 $('#regActionList').innerHTML = (route.actions || []).map(item=>`<article><span>${escapeHtml(item.step || '')}</span><strong>${escapeHtml(item.title || '')}</strong><p>${escapeHtml(item.description || '')}<small>${escapeHtml(item.owner || '')}</small></p></article>`).join(''); 5480 $('#regSourceList').innerHTML = references.length ? references.map((item,index)=>`<article><b>B${index + 1}</b><span><strong>${escapeHtml(item.source_name || 'æ³è§èµæ')}</strong><small>${escapeHtml(item.locator || 'ä½ç½®å¾ 确认')} · ${escapeHtml(item.source_date || 'æ¥æå¾ æ ¸éª')}</small><p>${escapeHtml(item.excerpt || '')}</p></span></article>`).join('') : '<p>æ¬è½®æªå½ä¸æ³è§åæï¼ç»è®ºæä½ç½®ä¿¡åº¦å¤çã</p>'; 5481 $('#regSourceBoundary').textContent = route.boundary || 'ç¨æ·æä¾çæ³è§å¯æ¬é¡»æ ¸éªç°è¡æææ§ï¼è·¯å¾ç
5481»è®ºé¡»ç±æ³è§è´è´£äººç¡®è®¤ã'; 5482 $('#regulatoryResult').classList.remove('hidden'); 5483 $('#regulatoryResult').scrollIntoView({behavior:'smooth',block:'nearest'}); 5484 } catch (error) { 5485 showToast(error.message || 'æ³è§è·¯å¾ç å¤å¤±è´¥'); 5486 } finally { 5487 button.disabled = false; 5488 button.textContent = 'AIç å¤è·¯å¾ãé£é©åå·®è· â'; 5489 } 5490} 5491 5492async function loadInternalKnowledgeStatus() { 5493 try { 5494 const data = await investmentApiRequest('/api/knowledge/status'); 5495 const categories = data.categories || {}; 5496 const issues = data.extraction_issues || {}; 5497 const issueTotal = Object.values(issues).reduce((sum,value)=>sum+Number(value || 0),0); 5498 const okCount = Number(data.document_count || 0) - issueTotal; 5499 $('#knowledgeDocumentCount').innerHTML = `${Number(data.document_count || 0).toLocaleString()}<em>份</em>`; 5500 $('#knowledgeExtractedCount').textContent = `${okCount.toLocaleString()}份已宿æåæå`; 5501 $('#knowledgeBpCount').innerHTML = `${Number(categories['ææè½¬åBP'] || 0).toLocaleString()}<em>份</em>`; 5502 $('#knowledgeReportCount').innerHTML = `${Number(categories['æ¥å'] || 0).toLocaleString()}<em>份</em>`; 5503 $('#knowledgeBiomedicalCount').innerHTML = `${Number(categories['çç©å»å¦æ°ææ¯æ³è§'] || 0).toLocaleString()}<em>份</em>`; 5504 $('#knowledgeChunkCount').innerHTML = `${Number(data.chunk_count || 0).toLocaleString()}<em>æ¡</em>`; 5505 $('#knowledgeCharacterCount').textContent = `约${Math.round(Number(data.character_count || 0) / 10000).toLocaleString()}ä¸åï¼æ¬æºä¿å`; 5506 $('#knowledgeOkCount').textContent = `${okCount.toLocaleString()}份`; 5507 $('#knowledgeOcrCount').textContent = `${Number(issues.needs_ocr || 0) + Number(issues.partial_needs_ocr || 0)}份`; 5508 $('#knowledgeDuplicateCount').textContent = `${Number(issues.duplicate || 0)}份`; 5509 $('#knowledgeLegacyCount').textContent = `${Number(issues.legacy_format || 0) + Number(issues.archive_skipped || 0)}份`; 5510 $('#knowledgeUpdatedAt').textContent = 'æ´æ°ï¼2026-07-20 · æºè½ä½èµæå·²æ¥å ¥'; 5511 } catch (error) { 5512 $('#knowledgeExtractedCount').textContent = `ç´¢å¼è¯»å失败ï¼${error.message}`; 5513 } 5514} 5515 5516function inferIncubationTrack(description) { 5517 const text = description.toLowerCase(); 5518 if (/(è¯å|æ£æµ|è¯æ|ivd|卿¢°|ææ¯|å½±å|设å¤|ä¼ æ)/i.test(text)) return 'å»ç卿¢°ä¸IVD'; 5519 if (/(ç»è|åºå |car-t|å¹²ç»è)/i.test(text)) return 'ç»èä¸åºå æ²»ç'; 5520 if (/(è¯ç©|é¶ç¹|ååç©|ç«è|æä½|å¶å)/i.test(text)) return 'åæ°è¯ä¸ç«è'; 5521 if (/(å »è|æ°åå¥åº·|软件|å¹³å°|æ ¢ç |äºèç½å»ç)/i.test(text)) return 'æ°åå¥åº·ä¸å »è'; 5522 if (/(ææ|ç»ç|å æ|设å¤|仪å¨|å·¥èºè£ å¤)/i.test(text)) return 'å»è¯ææä¸è£ å¤'; 5523 return 'å°ä¸ç¡®å®'; 5524} 5525 5526function evaluateIncubationProject() { 5527 const description = $('#intakeDescription').value.trim(); 5528 if (description.length < 10) { $('#intakeDescription').focus(); showToast('è¯·å æè¿°é¡¹ç®çææ¯ãè¿å±å转åéæ±'); return; } 5529 const projectName = $('#intakeProjectName').value.trim() || 'æªå½åå»è¯ææé¡¹ç®'; 5530 const track = state.intakeTrack === 'å°ä¸ç¡®å®' ? inferIncubationTrack(description) : state.intakeTrack; 5531 const stage = $('#intakeStage').value; 5532 const configurations = { 5533 'å»ç卿¢°ä¸IVD': { resource:'Medici à FlashPOC / 䏿£å®æ³°', gap:'临åºéæ±ã产ååç±»ä¸æ§è½è¯æ®', path:'workspace', steps:['宿䏴åºéæ±ä¸éç¨åºæ¯æ¾æ¸ ','å¼å±ä¸å©ãåç±»ä¸æ³¨åè·¯å¾é¢è¯ä¼°','å½¢ææ¦å¿µæ ·æºæè¯åæ§è½éªè¯æ¹æ¡'] }, 5534 'åæ°è¯ä¸ç«è': { resource:'ç ç©¶é¢ / CRO / åå«éª¥', gap:'ä½ç¨æºå¶ãæè¯æ§ä¸ä¸´åºåè¯æ®', path:'resources', steps:['宿é¶ç¹ææåä»·å¼ä¸ç«åè¯ä»·','梳ç临åºåç ç©¶åCMCè¯æ®ç¼ºå£','å¹é CROã产ä¸èµæ¬ä¸åç»æ¿æ¥ä¸»ä½'] }, 5535 'ç»èä¸åºå æ²»ç': { resource:'ç ç©¶é¢ / åä½é«æ ¡ä¸ååº', gap:'å·¥èºç¨³å®æ§ãè´¨éæ§å¶ä¸ä¸´åºåå®å ¨', path:'resources', steps:['æç¡®éåºçåæ ¸å¿ææ¯å£å','å¼å±å·¥èºãè´¨æ§åç¥è¯äº§æå°½è°','ç»ç»ä¸´åºãç ååæ³è§ä¸å®¶èåè¯å®¡'] },
5536 'æ°åå¥åº·ä¸å »è': { resource:'å»å®¶éæºæ §å »èåºæ¯ç½ç»', gap:'çå®ä½¿ç¨åºæ¯ãæ°æ®åè§ä¸ä»è´¹è·¯å¾', path:'resources', steps:['å®ä¹ç¨æ·ãåºæ¯åå¯éåå¥åº·ç»ç¹','å®ææ°æ®åè§å产å屿§é¢å¤','è¿å ¥å »èæºæå¼å±å°è§æ¨¡åºæ¯éªè¯'] }, 5537 'å»è¯ææä¸è£ å¤': { resource:'ææè½¬ååºå° / åä½ååº', gap:'å·¥èºæ¾å¤§ãè´¨éæ åä¸ä¸æ¸¸éªè¯', path:'resources', steps:['æç¡®æææè£ å¤çå ³é®æ§è½ææ ','宿å°è¯å°ä¸è¯çå·¥èºå·®è·åæ','å¹é 䏿¸¸ä¼ä¸ãåºå°å产ä¸èµæ¬'] }, 5538 'å°ä¸ç¡®å®': { resource:'éå¢é¡¹ç®è¯å®¡å§åä¼', gap:'ææ¯è¾¹çãæææå±ä¸ç®æ å¸åº', path:'resources', steps:['è¡¥å æææ¥æºåç¥è¯äº§ææå±','ç»ç»ä¸´åº/ç åä¸å®¶è¿è¡ææ¯è®¿è°','æ ¹æ®è¯æ®éæ°ç¡®å®èµéä¸è½¬åè·¯å¾'] } 5539 }; 5540 const config = configurations[track]; 5541 const triggers = []; 5542 if (/(ä¸å©|软è|ç¥è¯äº§æ)/.test(description)) triggers.push('æå°ç¥è¯äº§æ'); 5543 if (/(å®éª|æ ·æº|å°è¯|æ°æ®|注å)/.test(description)) triggers.push('æå°ç åææ³¨åè¿å±'); 5544 if (/(å»é¢|髿 ¡|å¢é|ä¼ä¸)/.test(description)) triggers.push('æå°æææå¢éæ¥æº'); 5545 if (track !== 'å°ä¸ç¡®å®') triggers.push(`è¯å«åéèµéï¼${track}`); 5546 createProjectContext({ 5547 newProject: true, 5548 title: projectName, 5549 description, 5550 summary: `${projectName}ç转åè·¯å¾è§ååç`, 5551 tags: [track, stage, $('#intakeSource').value], 5552 sourceMode: 'rule', 5553 audit: { method:'å ³é®è¯ä¸äººå·¥éæ©è§å', triggers } 5554 }); 5555 state.intakeRecommendedTrack = track; 5556 $('#intakeResultTitle').textContent = `${projectName} · è§ååç建议`; 5557 $('#intakeScore').innerHTML = `è§å<small>åç</small>`; 5558 $('#recommendedTrack').textContent = track; 5559 $('#readinessLevel').textContent = stage; 5560 $('#criticalGap').textContent = config.gap; 5561 $('#recommendedResource').textContent = config.resource; 5562 $('#intakeNextSteps').innerHTML = config.steps.map(step => `<li>${step}</li>`).join(''); 5563 $('#intakeResult').classList.remove('hidden'); 5564 appendRuleBasis($('#intakeResult'), triggers, 'æ¬æ¨¡åä¸è¯ä¼°åä¸ä»·å¼æææ¯å¯è¡æ§ï¼åªè´è´£åéèµéåæµãçå®åå ¥ç»è®ºéè¿å ¥ BP åæä¸è·¨ä¸ä¸ä¸å®¶è¯å®¡ã'); 5565 updateContextProject({ name:projectName, stage:'å¾ ä¸ä¸è¯ä¼°', progress:5, update:'å·²å®æè½¬åè·¯å¾è§ååç', source:'active_project' }); 5566 $('#intakeResult').scrollIntoView({ behavior:'smooth', block:'nearest' }); 5567} 5568 5569function enterIntakeRecommendedPath() { 5570 if (!state.intakeRecommendedTrack) return; 5571 if (state.intakeRecommendedTrack === 'å»ç卿¢°ä¸IVD') { 5572 const description = $('#intakeDescription').value.trim(); 5573 $('#clinicalInput').value = description; 5574 $('#charCount').textContent = description.length; 5575 switchView('workspace'); 5576 showToast('项ç®èµæå·²è½¬å ¥å»ç卿¢°ææè½¬å弿'); 5577 } else { 5578 $('#resourceNeedInput').value = `${state.intakeRecommendedTrack}项ç®éè¦ï¼${$('#criticalGap').textContent}`; 5579 switchView('resources'); 5580 matchGroupResources(); 5581 } 5582} 5583 5584function matchGroupResources() { 5585 const need = $('#resourceNeedInput').value.trim(); 5586 if (need.length < 4) { $('#resourceNeedInput').focus(); showToast('è¯·å æè¿°å½åéè¦è§£å³ç转åé®é¢'); return; } 5587 const resources = [ 5588 { type:'åµåè½½ä½', name:'åå«å¤©å大å¥åº·äº§ä¸åºå°', reason:'æ¿æ¥é¡¹ç®å ¥é©»ãæ¿çç³æ¥ã人æä¸ä¼ä¸æå¡ã' }, 5589 { type:'ææè½¬å', name:'ç ç©¶é¢ / CRO / ææè½¬ååºå°', reason:'ç»ç»ææ¯å°½è°ãç ç©¶éªè¯åæææ¿æ¥æ¹æ¡ã' }, 5590 { type:'IVD', name:'䏿£å®æ³°ä¸åº·æ°¸çç©', reason:'å¹é IVD注åãç产ãå½é 认è¯ä¸OEMç»éªã' }, 5591 { type:'çç©å¶å', name:'åå«éª¥å»è¯æå¡ç½ç»', reason:'è¿æ¥GSPãå·é¾ãç«èæ¨å¹¿åå ¨å½å®¢æ·æå¡ã' }, 5592 { type:'æ°åå¥åº·', name:'å»å®¶éæºæ §å »èç½ç»', reason:'æä¾å »èæºæè¯ç¹ãå¹³å°éæåè§æ¨¡ååºæ¯ã' }, 5593 { type:'ååºä¸èµæ¬', name:'åä½ååºä¸äº§ä¸æèµç½ç»', reason:'æ¯æååºè½å°ãèèµå产ä¸åä½å¯¹æ¥ã' } 5594 ]; 5595 let indexes; 5596 let triggers; 5597 if (/(è¯å|ivd|æ£æµ|注å|ç产)/i.test(need)) { indexes=[2,1,5];
5597 triggers=['è¯å / IVD / æ£æµ / 注å / ç产']; } 5598 else if (/(ç«è|çç©å¶å|å·é¾|æ¨å¹¿)/i.test(need)) { indexes=[3,1,5]; triggers=['ç«è / çç©å¶å / å·é¾ / æ¨å¹¿']; } 5599 else if (/(å »è|æ°å|软件|åºæ¯|æ ¢ç )/i.test(need)) { indexes=[4,0,5]; triggers=['å »è / æ°å / 软件 / åºæ¯ / æ ¢ç ']; } 5600 else if (/(ç å|å®éª|ææ|髿 ¡|ç»è|è¯ç©)/i.test(need)) { indexes=[1,0,5]; triggers=['ç å / å®éª / ææ / 髿 ¡ / ç»è / è¯ç©']; } 5601 else { indexes=[0,1,5]; triggers=[]; } 5602 const matches=indexes.map(index=>resources[index]); 5603 $$('.resource-network article').forEach(article=>article.classList.toggle('matched',matches.some(item=>item.type===article.dataset.resourceType))); 5604 $('#resourceMatchSummary').textContent=`é对â${need.slice(0,36)}${need.length>36?'â¦':''}âå½¢æçåå建议`; 5605 $('#matchedResourceList').innerHTML=matches.map((item,index)=>`<div class="matched-resource"><span>å¹é ${index+1}</span><strong>${item.name}</strong><p>${item.reason}</p></div>`).join(''); 5606 $('#resourceMatchResult').classList.remove('hidden'); 5607 appendRuleBasis($('#resourceMatchResult'), triggers, 'æ¨èä» è¡¨ç¤ºå é¨è½åæ¹åå¯è½ç¸å ³ï¼ä¸è¡¨ç¤ºèµæºæ¹å·²ç¡®è®¤æ¿æ¥ãæ¥ä»·ã卿æåè§å¯è¡æ§ã'); 5608 $('#resourceMatchResult').scrollIntoView({behavior:'smooth',block:'nearest'}); 5609} 5610 5611function professionalRiskClass(level) { 5612 return level === 'é«' ? 'high' : level === 'ä½' ? 'low' : 'mid'; 5613} 5614 5615function professionalProjectLabel(project) { 5616 const stateLabel = project.has_analysis ? '已宿BPåæ' : 'å°æ BPåæ'; 5617 return `${project.name || 'æªå½å项ç®'} · ${project.track || 'èµéå¾ ç¡®è®¤'} · ${stateLabel}`; 5618} 5619 5620function renderProfessionalProjectContext(project) { 5621 currentProfessionalProject = project || null; 5622 const facts = $('#professionalProjectFacts'); 5623 if (!project) { 5624 facts.innerHTML = '<span><small>产å身份</small><strong>çå¾ éæ©</strong></span><span><small>BPè¯æ®è¦ç</small><strong>â</strong></span><span><small>æè¿åæ</small><strong>â</strong></span>'; 5625 $('#professionalProjectHint').textContent = 'å å¨BPåæä¸å»ºç«é¡¹ç®å¹¶å®æå ¨æåæ'; 5626 return; 5627 } 5628 const identity = project.product_identity || {}; 5629 const audit = project.analysis_audit || {}; 5630 facts.innerHTML = `<span><small>产å身份</small><strong>${escapeHtml(identity.candidate_name || identity.product_scope || project.track || 'å¾ ç¡®è®¤')}</strong></span><span><small>BPè¯æ®è¦ç</small><strong>${project.evidence_coverage == null ? 'å¾ è®¡ç®' : `${escapeHtml(project.evidence_coverage)}%`}</strong></span><span><small>æè¿åæ</small><strong>${escapeHtml(audit.mode_label || (project.has_analysis ? 'å·²å½¢ææ¥å' : 'å°æªåæ'))}</strong></span>`; 5631 $('#professionalProjectHint').textContent = project.has_analysis 5632 ? `${project.latest_file_name || 'BPææ'} · 项ç®è´è´£äººï¼${project.owner || 'å¾ æå®'}` 5633 : '该项ç®åªææ¡£æ¡ï¼å°æªå®æBPå ¨æåæï¼ä¸ä¸å¼æä¸ä¼å空建ç«äº§åè¾¹ç'; 5634} 5635 5636async function loadProfessionalProjects(preferredId = '') { 5637 const select = $('#professionalProjectSelect'); 5638 try { 5639 professionalProjects = await investmentApiRequest('/api/professional/projects'); 5640 select.innerHTML = professionalProjects.length 5641 ? professionalProjects.map(project => `<option value="${escapeHtml(project.id)}" ${project.id === preferredId ? 'selected' : ''}>${escapeHtml(professionalProjectLabel(project))}</option>`).join('') 5642 : '<option value="">å°æ é¡¹ç®æ¡£æ¡ï¼è¯·å è¿å ¥BPåæå»ºæ¡£</option>'; 5643 const selectedId = preferredId && professionalProjects.some(item => item.id === preferredId) ? preferredId : select.value; 5644 if (selectedId) { 5645 select.value = selectedId; 5646 renderProfessionalProjectContext(professionalProjects.find(item => item.id === selectedId)); 5647 await Promise.all([loadProfessionalRuns(selectedId), loadProfessionalReviews(selectedId)]); 5648 } else { 5649 renderProfessionalProjectContext(null); 5650 renderProfessionalReviews([]); 5651 } 5652 } catch (error) { 5653 select.innerHTML = '<option value="">é¡¹ç®æ¡£æ¡æå¡ææªè¿æ¥</option>'; 5654 $('#professionalProjectHint').textContent = error.message; 5655 renderProfessionalProjectContext(null); 5656 } 5657} 5658 5659async function selectProfessionalProject(projectId) { 5660 const project = professionalProjects.find(item => item.id === projectId) || null; 5661 renderProfessionalProjectContext(project); 5662 currentProfessionalRun = null; 5663 $('#professionalResult').classList.add('hidden'); 5664 if (!projectId) return; 5665 await Promise.all([loadProfessionalRuns(projectId), loadProfessionalReviews(projectId)]); 5666} 5667 5668async function loadProfessionalRuns(projectId) { 5669 try { 5670 const runs = await investmentApiRequest(`/api/professional/projects/${encodeURIComponent(projectId)}/runs`); 5671 if (runs.length) renderProfessionalRun(runs[0]); 5672 } catch (error) { 5673 showToast(`ä¸ä¸ä»»å¡åå²è¯»å失败ï¼${error.message}`); 5674 } 5675} 5676 5677function renderProfessionalRun(run) { 5678 currentProfessionalRun = run; 5679 const result = run?.result || {}; 5680 const snapshot = result.project_snapshot || {}; 5681 const audit = result.audit || {model:run?.model,provider:run?.provider,request_id:run?.request_id}; 5682 $('#professionalResult').classList.remove('hidden'); 5683 $('#professionalResultEyebrow').textContent = result.engine === 'engineering' ? 'ENGINEERING TASK PACKAGE' : 'CRO REGULATORY TASK PACKAGE'; 5684 $('#professionalResultTitle').textContent = result.engine_name || (result.engine === 'engineering' ? 'å»ç卿¢°å·¥ç¨éªè¯å¼æ' : 'CRO注ååè§å¼æ'); 5685 $('#professionalResultBoundary').textContent = result.boundary || 'AIè¾åºæ¯ä¸ä¸å·¥ä½èæ¡ï¼é«é£é©ç»è®ºå¿ é¡»ç±å ·åä¸ä¸è´è´£äººå®¡æ ¸ã'; 5686 $('#professionalResultModel').textContent = audit.model || run?.model || 'AIè°ç¨è®°å½å¾ 读å'; 5687 $('#professionalResultTime').textContent = run?.created_at ? `çæäº ${new Date(run.created_at).toLocaleStr
5687ing('zh-CN')}` : 'æ¬è½®çæ'; 5688 const materials = Array.isArray(snapshot.materials_read) ? snapshot.materials_read : []; 5689 $('#professionalSnapshot').innerHTML = [ 5690 ['项ç®', snapshot.project_name || currentProfessionalProject?.name || 'å¾ ç¡®è®¤', 'æ¥èªç»ä¸é¡¹ç®æ¡£æ¡'], 5691 ['产å身份', snapshot.product_identity || currentProfessionalProject?.product_identity?.candidate_name || 'å¾ ç¡®è®¤', 'è·¯å¾åä»»å¡å¿ 须以产å身份为åæ'], 5692 ['å½åé¶æ®µ', snapshot.stage || currentProfessionalProject?.stage || 'å¾ ç¡®è®¤', 'ç¨äºç¡®å®å½åå³çé¨'], 5693 ['æ¬è½®ææ', materials.length ? `${materials.length}项已读å` : (currentProfessionalProject?.latest_file_name || 'ææå¾ 确认'), snapshot.evidence_boundary || 'æææ²¡ææè¿°çå å®¹ä¿ææªç¥'], 5694 ].map(item => `<article><span>${escapeHtml(item[0])}</span><strong>${escapeHtml(item[1])}</strong><small>${escapeHtml(item[2])}</small></article>`).join(''); 5695 5696 const conclusions = Array.isArray(result.conclusions) ? result.conclusions : []; 5697 $('#professionalConclusionCount').textContent = `${conclusions.length}项`; 5698 $('#professionalConclusionList').innerHTML = conclusions.length ? conclusions.map(item => { 5699 const basis = (item.basis || []).map(source => `<span><b>${escapeHtml(source.type || '便®')}ï¼</b>${escapeHtml(source.text || 'æªæä¾å ·ä½ä¾æ®')}${source.url ? ` <a href="${escapeHtml(source.url)}" target="_blank" rel="noopener">æ¥çæ¥æº â</a>` : ''}</span>`).join(''); 5700 return `<article class="professional-conclusion-card ${professionalRiskClass(item.risk_level)}"><header><span><small>${escapeHtml(item.id || '')} · ${escapeHtml(item.status || 'AIä¸ä¸å¤æ')} · 置信度${escapeHtml(item.confidence || 'ä½')}</small><strong>${escapeHtml(item.title || 'ä¸ä¸å¤æ')}</strong></span><b>${escapeHtml(item.risk_level || 'ä¸')}é£é©</b></header><p>${escapeHtml(item.judgment || '')}</p><div class="professional-conclusion-basis">${basis || '<span><b>便®ï¼</b>å°æªæä¾å¯æ ¸éªä¾æ®ï¼æ¬ç»è®ºä¸å¾è¿å ¥åæ§æ¡£æ¡ã</span>'}</div></article>`; 5701 }).join('') : '<div class="professional-review-empty">æ¬è½®æ²¡æå½¢æå¯å±ç¤ºçä¸ä¸ç»è®ºã</div>'; 5702 5703 const tasks = Array.isArray(result.tasks) ? result.tasks : []; 5704 $('#professionalTaskCount').textContent = `${tasks.length}项`; 5705 $('#professionalTaskList').innerHTML = tasks.length ? tasks.map(task => { 5706 const inputs = (task.input_needed || []).join('ï¼') || 'ç±è´è´£äººç¡®è®¤è¾å ¥'; 5707 return `<article class="professional-task-row"><i>${escapeHtml(task.id || '')}</i><span><strong>${escapeHtml(task.task || 'å¾ å®ä¹ä»»å¡')}</strong><small>${escapeHtml(task.workstream || '')} · ${escapeHtml(task.purpose || '')}</small></span><span><strong>æéè¾å ¥</strong><small>${escapeHtml(inputs)}</small></span><span><strong>交ä»ç©</strong><small>${escapeHtml(task.deliverable || 'å¾ å®ä¹')}</small></span><b>${escapeHtml(task.responsible_role || 'å¾ ææ´¾')}</b><em>${escapeHtml(task.priority || 'ä¸')}ä¼å </em></article>`; 5708 }).join('') : '<div class="professional-review-empty">å°æªå½¢æå¯æ§è¡ä»»å¡ã</div>'; 5709 5710 const roadmap = Array.isArray(result.next_30_days) ? result.next_30_days : []; 5711 $('#professionalRoadmap').innerHTML = roadmap.map(item => `<article><i>${escapeHtml(item.week || '')}</i><span><strong>${escapeHtml(item.objective || '')}</strong><small>${escapeHtml((item.outputs || []).join('ï¼'))}</small></span></article>`).join('') || '<div class="professional-review-empty">çå¾ å½¢æ30天计åã</div>'; 5712 const gates = Array.isArray(result.decision_gates) ? result.decision_gates : []; 5713 $('#professionalGateList').innerHTML = gates.map(item => `<article><i>${escapeHtml(item.id || '')}</i><span><strong>${escapeHtml(item.name || '')}</strong><small>éè¿æ¡ä»¶ï¼${escapeHtml((item.pass_condition || []).join('ï¼'))}<br>责任è§è²ï¼${escapeHtml(item.owner_role || 'å¾ æå®')}${item.blocking ? ' · 黿åå³çé¨' : ''}</small></span></article>`).join('') || '<div class="professional-review-empty">çå¾ å½¢æå³çé¨ã</div>'; 5714} 5715 5716async function runProfessionalEngine(engine) { 5717 if (professionalBusy) return; 5718 const project = currentProfessionalProject; 5719 if (!project) { showToast('请å éæ©ä¸ä¸ªé¡¹ç®æ¡£æ¡'); return; } 5720 if (!project.has_analysis) { showToast('该项ç®å°æªå®æBPå ¨æåæï¼è¯·å ä¸ä¼ BP'); return; } 5721 professionalBusy = true; 5722 $('#professionalRunning').classList.remove('hidden'); 5723 $('#professionalResult').classList.add('hidden'); 5724 $('#professionalRunningTitle').textContent = engine === 'engineering' ? 'å·¥ç¨å¸æ£å¨å»ºç«åæ§è®¾è®¡è¾å ¥ä¸éªè¯ä»»å¡' : 'CROè´è´£äººæ£å¨å»ºç«æ³¨åè·¯å¾ãèµæåç ç©¶ä»»å¡'; 5725 $('#professionalRunningStep').textContent = '读åBPäºå®ãä¹ç»´åæã产å身份ã宿¹æ¥æºåç°æè¯æ®ç¼ºå£â¦'; 5726 $$('[data-professional-engine]').forEach(button => button.disabled = true); 5727 try { 5728 const run = await investmentApiRequest('/api/professional/analyze', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({project_id:project.id,engine})}); 5729 renderProfessionalRun(run); 5730 await loadProfessionalReviews(project.id); 5731 $('#professionalResult').scrollIntoView({behavior:'smooth',block:'start'}); 5732 showToast(`${run.result?.engine_name || 'ä¸ä¸å¼æ'}已形æä»»å¡å ï¼é«é£é©ç»è®ºå·²éå ¥ä¸å®¶å®¡æ ¸å°`); 5733 } catch (error) { 5734 showToast(error.message); 5735 $('#professionalResultBoundary').textContent = error.message; 5736 } finally { 5737 professionalBusy = false;
5738 $('#professionalRunning').classList.add('hidden'); 5739 $$('[data-professional-engine]').forEach(button => button.disabled = false); 5740 } 5741} 5742 5743function renderProfessionalReviews(reviews) { 5744 const list = $('#professionalReviewList'); 5745 const empty = $('#professionalReviewEmpty'); 5746 const pending = reviews.filter(item => item.status !== 'å·²å®¡æ ¸').length; 5747 $('#professionalReviewCount').textContent = `${pending}é¡¹å¾ å®¡æ ¸`; 5748 empty.classList.toggle('hidden', reviews.length > 0); 5749 list.innerHTML = reviews.map(item => { 5750 const reviewed = item.status === 'å·²å®¡æ ¸'; 5751 return `<article class="professional-review-card${reviewed ? ' reviewed' : ''}" data-professional-review="${escapeHtml(item.id)}"><i>${escapeHtml(item.risk_level || 'ä¸')}</i><div><span>${escapeHtml(item.engine === 'engineering' ? 'å·¥ç¨éªè¯' : 'CROåè§')} · ${escapeHtml(item.reviewer_role || 'ä¸ä¸è´è´£äºº')} · ${escapeHtml(item.status || 'å¾ å®¡æ ¸')}</span><strong>${escapeHtml(item.title || 'ä¸ä¸ç»è®ºå®¡æ ¸')}</strong><p>${escapeHtml(item.question || '')}${reviewed ? `<br><b>å®¡æ ¸è®°å½ï¼</b>${escapeHtml(item.reviewer || '')} · ${escapeHtml(item.decision || '')} · ${escapeHtml(item.notes || 'æ è¡¥å 说æ')}` : ''}</p></div>${reviewed ? '' : `<form><input name="reviewer" placeholder="å®é å®¡æ ¸äººå§å"><select name="decision"><option value="">éæ©ç»è®º</option><option>éè¿</option><option>éåè¡¥å </option><option>å¦å³</option><option>æç¼</option></select><textarea name="notes" placeholder="è®°å½ä¾æ®ãæ¡ä»¶ãéè¡¥æææå¦å³çç±"></textarea><button type="submit">ä¿åå ·åå®¡æ ¸ç»è®º â</button></form>`}</article>`; 5752 }).join(''); 5753} 5754 5755async function loadProfessionalReviews(projectId) { 5756 try { 5757 const reviews = await investmentApiRequest(`/api/professional/reviews?project_id=${encodeURIComponent(projectId)}`); 5758 renderProfessionalReviews(reviews); 5759 } catch (error) { 5760 renderProfessionalReviews([]); 5761 $('#professionalReviewEmpty').textContent = `å®¡æ ¸éå读å失败ï¼${error.message}`; 5762 } 5763} 5764 5765async function submitProfessionalReview(form) { 5766 const card = form.closest('[data-professional-review]'); 5767 const reviewer = form.elements.reviewer.value.trim(); 5768 const decision = form.elements.decision.value; 5769 const notes = form.elements.notes.value.trim(); 5770 if (!reviewer || !decision) { showToast('请填åå®é å®¡æ ¸äººå¹¶éæ©æç¡®ç»è®º'); return; } 5771 const button = $('button', form); 5772 button.disabled = true; 5773 button.textContent = 'æ£å¨ä¿åâ¦'; 5774 try { 5775 await investmentApiRequest(`/api/professional/reviews/${encodeURIComponent(card.dataset.professionalReview)}/decision`, {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({reviewer,decision,notes})}); 5776 await loadProfessionalReviews(currentProfessionalProject.id); 5777 showToast('ä¸å®¶å®¡æ ¸ç»è®ºå·²å ·åä¿å'); 5778 } catch (error) { 5779 showToast(error.message); 5780 button.disabled = false; 5781 button.textContent = 'ä¿åå ·åå®¡æ ¸ç»è®º â'; 5782 } 5783} 5784 5785// ===== FlashPOC 90-day prototype pilot ===== 5786let prototypeProjects = []; 5787let currentPrototypeProject = null; 5788let prototypeActiveTab = 'charter'; 5789let prototypeDialogType = ''; 5790 5791function prototypeFormatDate(value) { 5792 if (!value) return 'å°æ è®°å½'; 5793 const date = new Date(value); 5794 return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN', {month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}); 5795} 5796 5797function prototypeEmptyRecord(text) { 5798 return `<div class="prototype-record-card"><i>â</i><div><h3>å°æ çå®è®°å½</h3><p>${escapeHtml(text)}</p></div><b>å¾ å½å ¥</b></div>`; 5799} 5800 5801async function loadPrototypeProjects(preferredId = '') { 5802 const select = $('#prototypeProjectSelect'); 5803 try { 5804 prototypeProjects = await investmentApiRequest('/api/prototype-pilot/projects'); 5805 const requestedId = preferredId || currentPrototypeProject?.id || ''; 5806 const chosenId = prototypeProjects.some(project => project.id === requestedId) 5807 ? requestedId 5808 : (prototypeProjects[0]?.id || ''); 5809 select.innerHTML = '<option value="">éæ©ä¸ä¸ªæ ·æºè¯ç¹é¡¹ç®</option>' + prototypeProjects.map(project => `<option value="${escapeHtml(project.id)}">
5809${escapeHtml(project.name)} · ${escapeHtml(project.status)}</option>`).join(''); 5810 if (chosenId) { 5811 select.value = chosenId; 5812 await selectPrototypeProject(chosenId); 5813 } else { 5814 currentPrototypeProject = null; 5815 renderPrototypeProject(); 5816 } 5817 } catch (error) { 5818 select.innerHTML = '<option value="">æ ·æºé¡¹ç®æå¡æä¸å¯ç¨</option>'; 5819 $('#prototypeProjectHint').textContent = error.message; 5820 renderPrototypeProject(); 5821 } 5822} 5823 5824async function selectPrototypeProject(projectId) { 5825 if (!projectId) { 5826 currentPrototypeProject = null; 5827 renderPrototypeProject(); 5828 return; 5829 } 5830 try { 5831 currentPrototypeProject = await investmentApiRequest(`/api/prototype-pilot/projects/${encodeURIComponent(projectId)}`); 5832 $('#prototypeProjectSelect').value = projectId; 5833 renderPrototypeProject(); 5834 } catch (error) { 5835 showToast(error.message); 5836 } 5837} 5838 5839function setPrototypeTab(tab) { 5840 prototypeActiveTab = tab; 5841 $$('#prototypeTabs button').forEach(button => button.classList.toggle('active', button.dataset.prototypeTab === tab)); 5842 $$('[data-prototype-panel]').forEach(panel => panel.classList.toggle('active', panel.dataset.prototypePanel === tab)); 5843} 5844 5845function renderPrototypeMetrics(project) { 5846 const metrics = project.metrics || {}; 5847 const items = [ 5848 ['追溯项', metrics.traceability_total || 0, `${metrics.engineering_defined || 0}项已形æå·¥ç¨è¾å ¥`], 5849 ['å·²è¯å«é£é©', metrics.risks_defined || 0, 'é£é©éæç»è¿ä»£'], 5850 ['æ ·æºçæ¬', metrics.prototype_versions || 0, 'ææåæ´åæ§çç'], 5851 ['å°æ¶æµè¯', `${metrics.tests_completed || 0}/${metrics.tests_total || 0}`, '宿 / å ¨é¨ä»»å¡'], 5852 ['å»ç确认', metrics.doctor_reviews || 0, 'å ·åæ¨¡æä½¿ç¨è®°å½'], 5853 ['DHF宿´åº¦', `${metrics.dhf_completion || 0}%`, `${metrics.dhf_complete || 0}/${metrics.dhf_total || 0}已彿¡£`], 5854 ]; 5855 $('#prototypeMetrics').innerHTML = items.map(item => `<article><small>${item[0]}</small><strong>${item[1]}</strong><span>${item[2]}</span></article>`).join(''); 5856} 5857 5858function renderPrototypeStages(project) { 5859 const current = Number(project.current_stage || 1); 5860 $('#prototypeCurrentStage').textContent = project.status === 'è¯ç¹å®æ' ? 'åé¶æ®µé¨å·²å®æ' : `å½åé¶æ®µ ${current} / 4`; 5861 $('#prototypeStageRail').innerHTML = (project.gates || []).map(gate => { 5862 const passed = gate.status === 'å·²éè¿'; 5863 const active = Number(gate.stage) === current && !passed; 5864 return `<article class="prototype-stage-card ${passed ? 'passed' : ''} ${active ? 'active' : ''}"> 5865 <header><i>0${gate.stage}</i><b>${escapeHtml(gate.status)}</b></header> 5866 <h3>${escapeHtml(gate.title)}</h3><p>${escapeHtml(gate.criteria)}</p> 5867 </article>`; 5868 }).join(''); 5869} 5870 5871function renderPrototypeTrace(project) { 5872 const rows = project.traceability || []; 5873 $('#prototypeTraceRows').innerHTML = rows.length ? rows.map(item => `<tr> 5874 <td><strong>${escapeHtml(item.clinical_need)}</strong><small>${escapeHtml(item.source_type)} · ${escapeHtml(item.ai_boundary)}</small></td> 5875 <td>${escapeHtml(item.engineering_requirement || 'å¾ å·¥ç¨å¸å®ä¹')}</td> 5876 <td>${escapeHtml(item.risk || 'å¾ é£é©è¯å«')}</td> 5877 <td>${escapeHtml(item.verification_method || 'å¾ å®ä¹')}<small>${escapeHtml(item.acceptance_criteria || 'éªæ¶æ åå¾ å»ç»')}</small></td> 5878 <td>${escapeHtml(item.result || 'å°æªéªè¯')}<small>${escapeHtml(item.evidence_reference || 'åå§è¯æ®å¾ å ³è')}</small></td> 5879 <td><span class="analysis-mode-badge">${escapeHtml(item.status)}</span></td> 5880 </tr>`).join('') : '<tr><td class="empty-cell" colspan="6">å°æ 追溯项ãå å½å ¥ä¸æ¡çå®ä¸´åºéè¦ï¼åç±å»çåå·¥ç¨å¸å ±åå»ç»å¯æµéææ ã</td></tr>'; 5881} 5882 5883function renderPrototypeVersions(project) { 5884 const versions = project.versions || []; 5885 $('#prototypeVersionList').innerHTML = versions.length ? versions.map(item => `<article class="prototype-record-card"> 5886 <i>${escapeHtml(item.version)}</i><div><h3>${escapeHtml(item.title)}</h3> 5887 <p>${escapeHtml(item.change_summary || 'æªè®°å½çæ¬åæ´åå ')}</p> 5888 <ul><li>ææï¼${escapeHtml(item.material || 'å¾ å®')}</li><li>å·¥èºï¼${escapeHtml(item.process || 'å¾ å®')}</li><li>BOMï¼${escapeHtml((item.bom || []).join('ï¼') || 'å¾ è¡¥å ')}</li><li>æä»¶ï¼${escapeHtml(item.file_reference || 'å¾ å ³è')}</li></ul></div><b>${escapeHtml(item.status)}</b> 5889 </article>`).join('') : prototypeEmptyRecord('æ ·æºå®ç©æCADçæ¬å½¢æååç»è®°ï¼
5889ä¸ä»¥AIçæå 容åå å·¥ç¨äº¤ä»ç©ã'); 5890} 5891 5892function renderPrototypeTests(project) { 5893 const tests = project.tests || []; 5894 $('#prototypeTestList').innerHTML = tests.length ? tests.map(item => `<article class="prototype-record-card"> 5895 <i>æµ</i><div><h3>${escapeHtml(item.name)}</h3> 5896 <p>${escapeHtml(item.method || 'æµè¯æ¹æ³å¾ è¡¥å ')}</p> 5897 <ul><li>é¢è®¾æ åï¼${escapeHtml(item.acceptance_criteria || 'å¾ å»ç»')}</li><li>宿µç»æï¼${escapeHtml(item.measured_result || 'å°æªæµè¯')}</li><li>åå·®ï¼${escapeHtml(item.deviation || 'æ è®°å½')}</li><li>åå§æ°æ®ï¼${escapeHtml(item.raw_data_reference || 'å¾ å ³è')}</li><li>æ§è¡äººï¼${escapeHtml(item.operator || 'å¾ æå®')}</li></ul></div><b>${escapeHtml(item.conclusion)}</b> 5898 </article>`).join('') : prototypeEmptyRecord('å å®ä¹éªè¯æ¹æ³ä¸éªæ¶æ åï¼åå½å ¥æµéç»æååå§æ°æ®ä½ç½®ã'); 5899} 5900 5901function renderPrototypeReviews(project) { 5902 const reviews = project.reviews || []; 5903 $('#prototypeReviewList').innerHTML = reviews.length ? reviews.map(item => `<article class="prototype-record-card"> 5904 <i>${escapeHtml((item.reviewer_role || '审').slice(0,1))}</i><div><h3>${escapeHtml(item.review_type)} · ${escapeHtml(item.reviewer)}</h3> 5905 <p>${escapeHtml(item.notes || 'æªè®°å½å®¡æ ¸æè§')}</p><ul><li>è§è²ï¼${escapeHtml(item.reviewer_role || 'å¾ æç¡®')}</li><li>é¶æ®µï¼${item.stage}</li><li>便®ï¼${escapeHtml(item.evidence_reference || 'å¾ å ³è')}</li><li>æ¶é´ï¼${escapeHtml(prototypeFormatDate(item.created_at))}</li></ul></div><b>${escapeHtml(item.decision)}</b> 5906 </article>`).join('') : prototypeEmptyRecord('å»çãå·¥ç¨å¸ãæ³è§æè´¨éè´è´£äººå®æå®é è¯å®¡åå ·åç»è®°ã'); 5907 const currentStage = Number(project.current_stage || 1); 5908 const gate = (project.gates || []).find(item => Number(item.stage) === currentStage) || (project.gates || [])[3]; 5909 $('#prototypeGateDecision').innerHTML = gate ? `<h3>é¶æ®µé¨ ${gate.stage} · ${escapeHtml(gate.title)}</h3> 5910 <p>${escapeHtml(gate.criteria)}</p> 5911 <form id="prototypeGateForm"> 5912 <label>å®é å®¡æ ¸äºº<input name="reviewer" required placeholder="å§å"></label> 5913 <label>责任è§è²<input name="reviewer_role" placeholder="临åº/å·¥ç¨/è´¨éè´è´£äºº"></label> 5914 <label>å®¡æ ¸ç»è®º<select name="decision"><option>éè¿</option><option>ææ¡ä»¶éè¿</option><option>éåè¡¥å </option><option>å¦å³</option></select></label> 5915 <label>å®¡æ ¸æè§<textarea name="notes" rows="3" placeholder="说æä¾æ®ãæ¡ä»¶æéååå "></textarea></label> 5916 <label>è¯æ®ä½ç½®<input name="evidence_reference" placeholder="ä¼è®®çºªè¦/æ¥å/æä»¶è·¯å¾"></label> 5917 <button class="analyze-button" type="submit" data-gate-stage="${gate.stage}">æäº¤é¶æ®µé¨å³ç</button> 5918 </form>` : '<p>é¶æ®µé¨è®°å½ä¸å¯ç¨</p>'; 5919} 5920 5921function renderPrototypeDhf(project) { 5922 const docs = project.documents || []; 5923 $('#prototypeDhfProgress').textContent = `${project.metrics?.dhf_completion || 0}% 已彿¡£`; 5924 $('#prototypeDhfList').innerHTML = docs.map(doc => `<div class="prototype-dhf-row" data-dhf-document="${escapeHtml(doc.id)}"> 5925 <i>${escapeHtml(doc.section.slice(0,2))}</i><strong>${escapeHtml(doc.title)}</strong> 5926 <input data-dhf-field="version" value="${escapeHtml(doc.version)}" aria-label="çæ¬"> 5927 <select data-dhf-field="status" aria-label="ç¶æ"><option ${doc.status === 'å¾ è¡¥å ' ? 'selected' : ''}>å¾ è¡¥å </option><option ${doc.status === 'è稿' ? 'selected' : ''}>è稿</option><option ${doc.status === 'å¾ å®¡æ ¸' ? 'selected' : ''}>å¾ å®¡æ ¸</option><option ${doc.status === 'å·²å®¡æ ¸' ? 'selected' : ''}>å·²å®¡æ ¸</option><option ${doc.status === '已彿¡£' ? 'selected' : ''}>已彿¡£</option></select> 5928 <button type="button" data-save-dhf>ä¿å</button> 5929 <input data-dhf-field="file_reference" value="${escapeHtml(doc.file_reference || '')}" placeholder="æä»¶ç¼å·æåæ¾ä½ç½®"> 5930 </div>`).join(''); 5931} 5932 5933function renderPrototypeCharter(project) { 5934 const form = $('#prototypeCharterForm'); 5935 ['name','owner','target_user','clinical_need','intended_use','use_environment','clinical_lead','engineering_lead','regulatory_lead','quality_lead','pilot_goal','scope_boundary'].forEach(name => { 5936 if (form.elements[name]) form.elements[name].value = project[name] || ''; 5937 }); 5938} 5939 5940function renderPrototypeProject() { 5941 const hasProject = Boolean(currentPrototypeProject); 5942 $('#prototypeEmpty').classList.toggle('hidden', hasProject); 5943 $('#prototypeWorkspace').classList.toggle('hidden', !hasProject); 5944 if (!hasProject) return; 5945 const project = currentPrototypeProject; 5946 $('#prototypeProjectCode').textContent = project.id; 5947 $('#prototypeProjectName').textContent = project.name; 5948 $('#prototypeProjectMeta').textContent = `${project.category} · ${project.status}`; 5949 $('#prototypeProjectOwner').textContent = project.owner || 'å¾ æå®'; 5950 $('#prototypeUpdatedAt').textContent = `æ´æ° ${prototypeFormatDate(project.updated_at)}`; 5951 $('#prototypeProjectHint').textContent = `${project.id} · ææç»è®ºä»¥æ¬é¡¹ç®çå®è®°å½ä¸ºå`; 5952 renderPrototypeMetrics(project); 5953 renderPrototypeStages(project); 5954 renderPrototypeCharter(project); 5955 renderPrototypeTrace(project); 5956 renderPrototypeVersions(project); 5957 renderPrototypeTests(project); 5958 renderPrototypeReviews(project); 5959 renderPrototypeDhf(project); 5960 setPrototypeTab(prototypeActiveTab); 5961} 5962 5963function togglePrototypeNewProject(show = true) { 5964 $('#prototypeNewProject').classList.toggle('hidden', !show); 5965 if (show) { 5966 $('#prototypeNewProjectForm').elements.name.focus(); 5967 $('#prototypeNewProject').scrollIntoView({behavior:'smooth',block:'nearest'}); 5968 } 5969} 5970 5971function prototypeField(label, name, options = {}) { 5972 const cls = options.wide ? ' class="wide"' : ''; 5973 if (options.type === 'textarea') return `<label${cls}>${label}<textarea name="${name}" rows="${options.rows || 3}" ${options.required ? 'required' : ''} placeholder="${escapeHtml(options.placeholder || '')}"></textarea></label>`; 5974 if (options.type === 'select') return `<label${cls}>${label}<select name="${name}">${(options.values || []).map(value => `<option>${escapeHtml(value)}</option>`).join('')}</select></label>`; 5975 return `<label${cls}>${label}<input name="${name}" ${options.required ? 'required' : ''} placeholder="${escapeHtml(options.placeholder || '')}"></label>`; 5976} 5977 5978function openPrototypeDialog(type) { 5979 if (!currentPrototypeProject) return showToast('请å åå»ºæ ·æºè¯ç¹é¡¹ç®'); 5980 prototypeDialogType = type; 5981 const configurations = { 5982 trace: ['æ°å¢éæ±è¿½æº¯é¡¹', [ 5983 prototypeField('临åºéè¦ / 使ç¨é®é¢','clinical_need',{wide:true,type:'textarea',required:true,placeholder:'å»ç确认çå ·ä½é®é¢'}), 5984 prototypeField('å·¥ç¨è®¾è®¡è¾å ¥','engineering_requirement',{wide:true,type:'textarea',placeholder:'å¯éåãå¯éªè¯çåæ°'}), 5985 prototypeField('åå§é£é©','risk',{type:'textarea'}), 5986 prototypeField('éªè¯æ¹æ³','verification_method',{type:'textarea'}), 5987 prototypeField('éªæ¶æ å','acceptance_criteria',{wide:true,type:'textarea',placeholder:'å¿ é¡»æ¯å¯æµéæ å'}), 5988 prototypeField('æ¥æºç±»å','source_type',{type:'select',values:['å»ç访è°','å·¥ç¨å¸å®ä¹','å ¬å¼è¯æ®','人工å½å ¥','AI建议']}), 5989 prototypeField('è¯æ®è¾¹ç','ai_boundary',{type:'select',values:['已确认äºå®','äºå®å¾ 人工确认','AIæ¨æå¾ å¤æ ¸']}), 5990 ]], 5991 version: ['ç»è®°æ ·æºçæ¬', [
5992 prototypeField('çæ¬å·','version',{required:true,placeholder:'v0.1'}), 5993 prototypeField('æ ·æºåç§°','title',{required:true,placeholder:'é¦è½®ææ¿æ ·æº'}), 5994 prototypeField('åæ´æè¦','change_summary',{wide:true,type:'textarea'}), 5995 prototypeField('ææ','material',{placeholder:'ææçå·/ä¾åºå'}), 5996 prototypeField('å·¥èº','process',{placeholder:'3Dæå°/æºå å·¥'}), 5997 prototypeField('BOMï¼æ¯è¡ä¸é¡¹ï¼','bom_text',{wide:true,type:'textarea',placeholder:'é¶ä»¶åç§° | è§æ ¼ | æ°é'}), 5998 prototypeField('å¾çº¸/æä»¶ä½ç½®','file_reference',{wide:true,placeholder:'åæ§æä»¶ç¼å·ææ¬æºè·¯å¾'}), 5999 prototypeField('ç»è®°äºº','created_by',{placeholder:'å®é å·¥ç¨å¸'}), 6000 ]], 6001 test: ['ç»è®°å°æ¶æµè¯', [ 6002 prototypeField('æµè¯ä»»å¡','name',{wide:true,required:true,placeholder:'湿æç»ç»å¤¹ææ»è±åæµè¯'}), 6003 prototypeField('æ ·æºçæ¬ID/çæ¬å·','prototype_version_id',{}), 6004 prototypeField('æ§è¡äºº','operator',{}), 6005 prototypeField('æµè¯æ¹æ³','method',{wide:true,type:'textarea'}), 6006 prototypeField('é¢è®¾éªæ¶æ å','acceptance_criteria',{wide:true,type:'textarea'}), 6007 prototypeField('宿µç»æ','measured_result',{wide:true,type:'textarea'}), 6008 prototypeField('ç»è®º','conclusion',{type:'select',values:['å¾ æµè¯','éè¿','é¨åéè¿','ä¸éè¿']}), 6009 prototypeField('æµè¯æ¥æ','tested_at',{placeholder:'2026-08-01'}), 6010 prototypeField('åå·®ä¸å¼å¸¸','deviation',{wide:true,type:'textarea'}), 6011 prototypeField('åå§æ°æ®ä½ç½®','raw_data_reference',{wide:true,placeholder:'æ°æ®æä»¶/è§é¢/æ¥åç¼å·'}), 6012 ]], 6013 review: ['æ°å¢å ·åè¯å®¡', [ 6014 prototypeField('è¯å®¡ç±»å','review_type',{type:'select',values:['æ¦å¿µè¯å®¡','å·¥ç¨è¯å®¡','é£é©è¯å®¡','å»ç确认','è´¨éè¯å®¡','æ³è§è¯å®¡']}), 6015 prototypeField('æå¤é¶æ®µ','stage',{type:'select',values:['1','2','3','4']}), 6016 prototypeField('å®é å®¡æ ¸äºº','reviewer',{required:true}), 6017 prototypeField('å®¡æ ¸äººè§è²','reviewer_role',{placeholder:'临åºå»ç/å·¥ç¨å¸'}), 6018 prototypeField('ç»è®º','decision',{type:'select',values:['è®°å½æè§','éè¿','ææ¡ä»¶éè¿','éåè¡¥å ','å¦å³']}), 6019 prototypeField('è¯æ®ä½ç½®','evidence_reference',{}), 6020 prototypeField('è¯å®¡æè§','notes',{wide:true,type:'textarea',placeholder:'说æå¤æä¾æ®ãé£é©åä¸ä¸æ¥'}), 6021 ]], 6022 }; 6023 const config = configurations[type]; 6024 if (!config) return; 6025 $('#prototypeDialogTitle').textContent = config[0]; 6026 $('#prototypeDialogFields').innerHTML = config[1].join(''); 6027 $('#prototypeDataDialog').showModal(); 6028} 6029 6030async function submitPrototypeDialog(event) { 6031 event.preventDefault(); 6032 if (!currentPrototypeProject || !prototypeDialogType) return; 6033 const payload = Object.fromEntries(new FormData(event.currentTarget).entries()); 6034 if (prototypeDialogType === 'version') { 6035 payload.bom = (payload.bom_text || '').split(/\r?\n/).map(value => value.trim()).filter(Boolean); 6036 delete payload.bom_text; 6037 } 6038 const paths = {trace:'traceability',version:'versions',test:'tests',review:'reviews'}; 6039 const button = event.currentTarget.querySelector('[type="submit"]'); 6040 button.disabled = true; 6041 try { 6042 currentPrototypeProject = await investmentApiRequest(`/api/prototype-pilot/projects/${encodeURIComponent(currentPrototypeProject.id)}/${paths[prototypeDialogType]}`, { 6043 method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload), 6044 }); 6045 $('#prototypeDataDialog').close(); 6046 renderPrototypeProject(); 6047 showToast('è®°å½å·²ä¿åå°æ ·æºé¡¹ç®æ¡£æ¡'); 6048 } catch (error) { 6049 showToast(error.message); 6050 } finally { 6051 button.disabled = false; 6052 } 6053} 6054 6055async function submitPrototypeGate(form) { 6056 if (!currentPrototypeProject) return; 6057 const stage = form.querySelector('[data-gate-stage]').dataset.gateStage; 6058 const payload = Object.fromEntries(new FormData(form).entries()); 6059 const button = form.querySelector('button'); 6060 button.disabled = true; 6061 try { 6062 currentPrototypeProject = await investmentApiRequest(`/api/prototype-pilot/projects/${encodeURIComponent(currentPrototypeProject.id)}/gates/${stage}/decision`, { 6063 method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload), 6064 }); 6065 renderPrototypeProject(); 6066 showToast(`é¶æ®µé¨${stage}å³çå·²å ·åä¿å`); 6067 } catch (error) { 6068 showToast(error.message); 6069 button.disabled = false;
6070 } 6071} 6072 6073async function savePrototypeDhf(row) { 6074 if (!currentPrototypeProject) return; 6075 const payload = {}; 6076 row.querySelectorAll('[data-dhf-field]').forEach(input => { payload[input.dataset.dhfField] = input.value; }); 6077 const button = row.querySelector('[data-save-dhf]'); 6078 button.disabled = true; 6079 try { 6080 currentPrototypeProject = await investmentApiRequest(`/api/prototype-pilot/projects/${encodeURIComponent(currentPrototypeProject.id)}/documents/${encodeURIComponent(row.dataset.dhfDocument)}`, { 6081 method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload), 6082 }); 6083 renderPrototypeProject(); 6084 showToast('DHFææ¡£ç¶æå·²ä¿å'); 6085 } catch (error) { 6086 showToast(error.message); 6087 button.disabled = false; 6088 } 6089} 6090 6091const NAV_GROUP_VIEWS = { 6092 experimentNav:new Set(['prototypepilot','opc','professional']), 6093 assessmentNav:new Set(['intake','investment','resources']), 6094 deviceWorkflowNav:new Set(['workspace','patent','flashpoc','development','regai','commercial']), 6095 assetNav:new Set(['enterpriselib','services','knowledge']), 6096 adminNav:new Set(['admin']) 6097}; 6098 6099function setInternalPlatformExpanded(view) { 6100 Object.entries(NAV_GROUP_VIEWS).forEach(([id,views]) => { 6101 const group = $(`#${id}`); 6102 if (group && views.has(view)) group.open = true; 6103 }); 6104} 6105 6106function switchView(view) { 6107 if (view === 'projects') view = 'command'; 6108 if (view === 'investment') checkBpEngine(); 6109 if (view === 'policyhub') { loadPolicyEnterprises(); loadPolicyCatalog(); renderPolicyMatchContext(); } 6110 if (view === 'patent') hydratePatent(); 6111 if (view === 'flashpoc') hydrateFlashPOC(); 6112 if (view === 'development') hydrateDevelopment(); 6113 if (view === 'regai') hydrateRegAI(); 6114 if (view === 'commercial') hydrateCommercial(); 6115 if (view === 'command') { renderProjects(); loadInvestmentProjects().then(() => { if (state.currentView === 'command') renderProjects(); }).catch(() => {}); } 6116 if (view === 'opc') loadOpcProject(); 6117 if (view === 'professional') loadProfessionalProjects(currentProfessionalProject?.id || currentInvestmentProject?.id || ''); 6118 if (view === 'prototypepilot') loadPrototypeProjects(currentPrototypeProject?.id || ''); 6119 if (view === 'enterpriselib') loadBaseEnterpriseLibrary(); 6120 if (view === 'knowledge') loadInternalKnowledgeStatus(); 6121 if (view === 'admin') loadAdminDashboard(); 6122 setInternalPlatformExpanded(view); 6123 state.currentView = view; 6124 $$('.nav-item').forEach(item => item.classList.toggle('active', item.dataset.view === view)); 6125 $$('.view').forEach(section => section.classList.remove('active')); 6126 $(`#${view}View`).classList.add('active'); 6127 renderCapabilityBanner(view); 6128 const names = { incubator:'é¦é¡µ', intake:'转åè·¯å¾è¯ä¼°', resources:'éå¢èµæºåå', command:'项ç®é©¾é©¶è±', prototypepilot:'FlashPOC æ ·æºè¯ç¹', opc:'å»ç卿¢° OPC', professional:'åä¸ä¸å¼æ', investment:'BPåæ', policyhub:'æ¿çè§å', regulatory:'æ³¨åæ³è§æ£ç´¢', workspace: '临åºè¯è¨ç¿»è¯æº', patent: 'ä¸å©ä¸èµéåæ', flashpoc: 'FlashPOC æ¦å¿µè®¾è®¡', development: 'å·¥ç¨æ·±åä»»å¡', regai: 'éªè¯ä¸æ³¨å', commercial:'产ä¸åä¸å¸åº', enterpriselib:'å»è¯åºå°ä¼ä¸åº', services:'æå¡äº§å', knowledge:'ç¥è¯èµäº§', admin:'ç¨æ·æ°æ®çæ¿' }; 6129 $('#pageCrumb').textContent = names[view]; 6130 $('#sidebar').classList.remove('open'); 6131 window.scrollTo({ top: 0, behavior: 'smooth' }); 6132} 6133 6134function renderProjects() { 6135 const projectRows = $('#projectRows'); 6136 if (!projectRows) return; 6137 const investmentCards = investmentProjects.map(project => ({ 6138 icon:'â', name:project.name, id:`inv-${project.id}`, investmentId:project.id, 6139 stage:project.status || '已建档', 6140 progress:project.latest_score != null ? Math.round(Number(project.latest_score) * 10) : 5, 6141 owner:project.owner || 'å¾ æå®', 6142 update:project.latest_score != null ? `BPè¯å ${project.latest_score}/10` : 'å¾ ä¸ä¼ BPåæ', 6143 source:'investment', 6144 })); 6145 const allProjects = [...projects, ...investmentCards]; 6146 const keyword = ($('#commandProjectSearch')?.value || '').trim().toLowerCase(); 6147 const stage = $('#commandProjectStageFilter')?.value || 'all'; 6148 const visibleProjects = allProjects.filter(project => { 6149 const matchesKeyword = !keyword || `${project.name} ${project.id} ${project.owner}`.toLowerCase().includes(keyword); 6150 return matchesKeyword && (stage === 'all' || project.stage === stage); 6151 }); 6152 if (visibleProjects.length && !visibleProjects.some(project => project.id === selectedCommandProjectId)) selectedCommandProjectId = visibleProjects[0].id; 6153 projectRows.innerHTML = visibleProjects.map(project => { 6154 const isDemo = project.source === 'demo'; 6155 return `<button class="command-project-item${project.id === selectedCommandProjectId ? ' selected' : ''}${isDemo ? ' demo-project' : ''}" type="button" data-project-id="${project.id}"><span class="command-project-item-head"><i>${project.icon}</i><span><strong>${project.name}</strong><small>${project.id} · ${project.owner}</small></span><b>${isDemo ? '示ä¾' : `${project.progress}%`}</b></span><span class="command-project-item-status"><em>${project.stage}</em><small>${project.update}</small></span><span class="command-project-progress"><i><b style="width:${isDemo ? 0 : project.progress}%"></b></i></span></button>`; 6156 }).join('') || '<div class="command-project-empty"><strong>没æç¬¦åæ¡ä»¶ç项ç®</strong><span>è¯·æ´æ¢æç´¢è¯æé¶æ®µçéã</span></div>'; 6157 $('#commandProjectCount').textContent = `${visibleProjects.length}项`; 6158 const liveProjects = allProjects.filter(project => project.source !== 'demo'); 6159 $('#commandActiveProjectCount').textContent = liveProjects.filter(project => project.progress < 100).length; 6160 const average = liveProjects.length ? Math.round(liveProjects.reduce((sum,project) => sum + project.progress,0) / liveProjects.length) : 0; 6161 $('#commandAverageProgress').innerHTML = liveProjects.length ? `${average}<em>%</em>` : 'â'; 6162 const selected = allProjects.find(project => project.id === selectedCommandProjectId) || visibleProjects[0] || allProjects[0]; 6163 renderCommandProject(selected); 6164} 6165 6166function renderCommandProject(project) { 6167 if (!project || !$('#commandProjectName')) return; 6168 const isDemo = project.source === 'demo'; 6169 const isWorkflow = project.source === 'workflow'; 6170 const detail = $('.command-project-detail'); 6171 detail.classList.toggle('demo-record', isDemo);
6172 $('#commandProjectIdentity').textContent = `ç»ä¸é¡¹ç® ID · ${project.id}`; 6173 $('#commandProjectName').textContent = project.name; 6174 $('#commandProjectMeta').textContent = `${project.owner}è´è´£ · å½åå¤äº${project.stage} · ${project.update}`; 6175 const projectTags = isDemo 6176 ? ['æ¼ç¤ºæ¡£æ¡','ä¸åä¸ç»è®¡','ä¸ä½ä¸ºå³ç便®'] 6177 : project.id === 'OPC-2026-001' 6178 ? ['OPCç¬ç«è¯ç¹','AIæºè½ä½å·²ç¼ç»','å ±äº«ä¸å®¶å¾ ææ´¾','人工å³çé¨'] 6179 : [`${project.stage}ä¸`,'æ¿çå¾ æ ¸éª','æ³è§è·¯å¾å¾ 确认','ä¸å®¶å®¡æ ¸å¾ 宿']; 6180 $('#commandProjectTags').innerHTML = projectTags.map(tag => `<b>${tag}</b>`).join(''); 6181 $('#commandProjectCompleteness').innerHTML = isDemo ? 'æªæ ¸éª' : (isWorkflow ? 'å¾ å»ºæ¡£' : 'å¾ è®¡ç®'); 6182 $('#commandProjectCompletenessNote').textContent = isDemo 6183 ? 'è¿æ¯é¡µé¢ç»ææ¼ç¤ºé¡¹ç®ï¼ä¸å å«ç宿ææè¯æ®' 6184 : '宿´åº¦å¿ é¡»æ ¹æ®å®é ä¸ä¼ å¹¶æ ¸éªçææé项计ç®ï¼ä¸è½ç±é¡¹ç®è¿åº¦æ¨ç®'; 6185 $('#commandCurrentStage').textContent = project.stage; 6186 const actionsBox = $('#commandProjectActions'); 6187 if (project.source === 'investment') { 6188 const analyzed = project.update.includes('è¯å'); 6189 $('#commandProjectTags').innerHTML = ['BPåæé¡¹ç®', analyzed ? project.update : 'å¾ åæ', 'ç¹å»æé®æ¥ç宿´æ¥å'].map(tag => `<b>${tag}</b>`).join(''); 6190 $('#commandProjectCompleteness').innerHTML = analyzed ? 'å·²åæ' : 'å¾ åæ'; 6191 $('#commandProjectCompletenessNote').textContent = '宿´æ¥åå¨BPåæå·¥ä½å䏿¥ç'; 6192 if (actionsBox) { 6193 actionsBox.innerHTML = `<button class="analyze-button" id="commandOpenInvestmentReport" type="button">æ¥ç该项ç®çBPåææ¥å â</button>`; 6194 $('#commandOpenInvestmentReport')?.addEventListener('click', () => { switchView('investment'); openInvestmentProjectReport(project.investmentId); }); 6195 } 6196 } else if (actionsBox) { 6197 actionsBox.innerHTML = ''; 6198 } 6199} 6200 6201function getOpcTasks(project = opcProjectState) { 6202 return (project?.stages || []).flatMap(stage => stage.tasks || []); 6203} 6204 6205function calculateOpcProgress(project = opcProjectState) { 6206 const tasks = getOpcTasks(project); 6207 const completed = tasks.filter(task => task.status === 'complete').length; 6208 return { completed, total: tasks.length, percent: tasks.length ? Math.round(completed / tasks.length * 100) : 0 }; 6209} 6210 6211function getOpcCurrentStage(project = opcProjectState) { 6212 const stages = project?.stages || []; 6213 return stages.find(stage => (stage.tasks || []).some(task => task.status !== 'complete')) || null; 6214} 6215 6216async function opcProjectRequest(method = 'GET', body = null) { 6217 const response = await fetch('/api/opc/project', { 6218 method, 6219 headers: body ? { 'Content-Type':'application/json' } : {}, 6220 body: body ? JSON.stringify(body) : undefined 6221 }); 6222 const raw = await response.text(); 6223 let payload = null; 6224 try { payload = raw ? JSON.parse(raw) : null; } catch { throw new Error(`OPC é¡¹ç®æå¡è¿åäºæ æ³è¯å«çå 容ï¼HTTP ${response.status}ï¼`); } 6225 if (!response.ok) throw new Error(payload?.error || `OPC é¡¹ç®æå¡ææ¶ä¸å¯ç¨ï¼HTTP ${response.status}ï¼`); 6226 return payload; 6227} 6228 6229function syncOpcProjectToCockpit() { 6230 if (!opcProjectState) return; 6231 const progress = calculateOpcProgress(); 6232 const currentStage = getOpcCurrentStage(); 6233 const cockpitProject = projects.find(project => project.id === opcProjectState.id); 6234 if (!cockpitProject) return; 6235 cockpitProject.name = opcProjectState.name; 6236 cockpitProject.owner = opcProjectState.owner || 'å¾ æå®ä¸»ç人'; 6237 cockpitProject.progress = progress.percent; 6238 cockpitProject.stage = 'OPCè¯ç¹'; 6239 cockpitProject.update = currentStage ? `${currentStage.name}è¿è¡ä¸` : 'è¯ç¹ä»»å¡å·²å®æ'; 6240 renderProjects(); 6241} 6242 6243function renderOpcProject() { 6244 if (!opcProjectState) return; 6245 const progress = calculateOpcProgress(); 6246 const currentStage = getOpcCurrentStage(); 6247 const stages = opcProjectState.stages || []; 6248 if (!stages.some(stage => stage.id === selectedOpcStageId)) selectedOpcStageId = currentStage?.id || stages.at(-1)?.id; 6249 const selectedStage = stages.find(stage => stage.id === selectedOpcStageId) || stages[0]; 6250 6251 $('#opcProjectIdentity').textContent = `${opcProjectState.id} · ${opcProjectState.status || 'è¯ç¹è¿è¡ä¸'}`;
6252 $('#opcProjectName').textContent = opcProjectState.name; 6253 $('#opcProjectPositioning').textContent = opcProjectState.positioning; 6254 $('#opcHeroTags').innerHTML = [opcProjectState.pilot_scope, `主ç人ï¼${opcProjectState.owner}`, `䏿¬¡è¯å®¡ï¼${opcProjectState.next_review || 'å¾ ç¡®å®'}`].map(tag => `<b>${escapeHtml(tag)}</b>`).join(''); 6255 $('#opcProgressValue').innerHTML = `${progress.percent}<em>%</em>`; 6256 $('#opcProgressBar').style.width = `${progress.percent}%`; 6257 $('#opcCurrentStage').textContent = currentStage ? `å½åï¼${currentStage.name}` : 'å ¨é¨é¶æ®µå·²å®æ'; 6258 $('#opcTaskSummary').textContent = `${progress.completed} / ${progress.total} 项宿`; 6259 6260 $('#opcStageTabs').innerHTML = stages.map((stage,index) => { 6261 const tasks = stage.tasks || []; 6262 const complete = tasks.filter(task => task.status === 'complete').length; 6263 const done = tasks.length > 0 && complete === tasks.length; 6264 return `<button type="button" class="${stage.id === selectedOpcStageId ? 'active' : ''}${done ? ' complete' : ''}" data-opc-stage="${escapeHtml(stage.id)}"><i>${done ? 'â' : String(index + 1).padStart(2,'0')}</i><span><strong>${escapeHtml(stage.name)}</strong><small>${complete}/${tasks.length}项</small></span></button>`; 6265 }).join(''); 6266 if (selectedStage) { 6267 $('#opcStageSummary').innerHTML = `<div><span>å½åé¶æ®µ</span><h3>${escapeHtml(selectedStage.name)}</h3><p>${escapeHtml(selectedStage.description || '')}</p></div><b>${(selectedStage.tasks || []).filter(task => task.status === 'complete').length}/${(selectedStage.tasks || []).length}</b>`; 6268 $('#opcTaskList').innerHTML = (selectedStage.tasks || []).map(task => `<article class="opc-task${task.status === 'complete' ? ' complete' : ''}"><button type="button" data-opc-task="${escapeHtml(task.id)}" aria-label="${task.status === 'complete' ? 'æ 记为æªå®æ' : 'æ è®°ä¸ºå®æ'}"><i>${task.status === 'complete' ? 'â' : ''}</i></button><div><strong>${escapeHtml(task.title)}</strong><span><b>${escapeHtml(task.owner || 'å¾ åé ')}</b><small>${escapeHtml(task.due || 'å¾ ææ')}</small><small>å®¡æ ¸ï¼${escapeHtml(task.review_role || '项ç®ä¸»ç人')}</small></span></div><em>${task.status === 'complete' ? '已宿' : 'å¾ æ¨è¿'}</em></article>`).join('') || '<div class="opc-empty-state">å½åé¶æ®µè¿æ²¡æä»»å¡ï¼å¯ä»¥å¨ä¸æ¹æ·»å ã</div>'; 6269 } 6270 6271 $('#opcAgentGrid').innerHTML = (opcProjectState.ai_agents || []).map((agent,index) => `<article><i>${String(index + 1).padStart(2,'0')}</i><div><span>${escapeHtml(agent.status)}</span><h3>${escapeHtml(agent.name)}</h3><p>${escapeHtml(agent.role)}</p></div></article>`).join(''); 6272 $('#opcExpertList').innerHTML = (opcProjectState.experts || []).map(expert => `<article><div><strong>${escapeHtml(expert.role)}</strong><p>${escapeHtml(expert.requirement)}</p></div><button type="button" data-opc-expert="${escapeHtml(expert.id)}" class="${expert.status === '已确认' ? 'confirmed' : ''}">${escapeHtml(expert.status)}</button></article>`).join(''); 6273 $('#opcGateGrid').innerHTML = (opcProjectState.gates || []).map((gate,index) => `<article class="${gate.status === 'å·²éè¿' ? 'passed' : ''}"><header><i>G${index + 1}</i><span>${escapeHtml(gate.status)}</span></header><h3>${escapeHtml(gate.name)}</h3><p>${escapeHtml(gate.rule)}</p><footer><span>${gate.reviewed_at ? `éè¿è®°å½ï¼${escapeHtml(gate.reviewed_by || opcProjectState.owner)} · ${escapeHtml(new Date(gate.reviewed_at).toLocaleString('zh-CN'))}` : `å®¡æ ¸äººï¼${escapeHtml(gate.reviewer)}`}</span><button type="button" data-opc-gate="${escapeHtml(gate.id)}">${gate.status === 'å·²éè¿' ? 'æ¤åéè¿' : gate.status === 'å¾ å®¡æ ¸' ? '确认éè¿' : 'æäº¤å®¡æ ¸'}</button></footer></article>`).join(''); 6274 6275 if (document.activeElement !== $('#opcOwnerInput')) $('#opcOwnerInput').value = opcProjectState.owner || ''; 6276 if (document.activeElement !== $('#opcScopeInput')) $('#opcScopeInput').value = opcProjectState.pilot_scope || ''; 6277 if (document.activeElement !== $('#opcReviewDateInput')) $('#opcReviewDateInput').value = opcProjectState.next_review || '';
6278 $('#opcLoadError').classList.add('hidden'); 6279 syncOpcProjectToCockpit(); 6280} 6281 6282async function loadOpcProject() { 6283 $('#opcLoadError')?.classList.add('hidden'); 6284 $('#opcSaveStatus').textContent = 'æ£å¨è¯»åæ¬æºé¡¹ç®æ¡£æ¡â¦'; 6285 try { 6286 opcProjectState = await opcProjectRequest(); 6287 renderOpcProject(); 6288 $('#opcSaveStatus').textContent = 'é¡¹ç®æ¡£æ¡å·²è¿æ¥ï¼ä¿®æ¹ä¼èªå¨ä¿å卿¬æº'; 6289 } catch (error) { 6290 $('#opcLoadError').classList.remove('hidden'); 6291 $('#opcLoadErrorText').textContent = `${error.message}ãè¯·éæ°å¯å¨âåå«å¤©ååµå卿ºè½ä¸å°âã`; 6292 $('#opcSaveStatus').textContent = 'é¡¹ç®æ¡£æ¡è¿æ¥å¤±è´¥'; 6293 } 6294} 6295 6296async function persistOpcProject(message = 'OPC 项ç®å·²ä¿å') { 6297 if (!opcProjectState) return false; 6298 $('#opcSaveStatus').textContent = 'æ£å¨ä¿åâ¦'; 6299 try { 6300 opcProjectState = await opcProjectRequest('PUT', opcProjectState); 6301 renderOpcProject(); 6302 $('#opcSaveStatus').textContent = `å·²ä¿å · ${new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})}`; 6303 if (message) showToast(message); 6304 return true; 6305 } catch (error) { 6306 $('#opcSaveStatus').textContent = `ä¿å失败ï¼${error.message}`; 6307 showToast('OPC 项ç®ä¿å失败ï¼è¯·ç¡®è®¤ç»ä¸å¹³å°æ£å¨è¿è¡'); 6308 return false; 6309 } 6310} 6311 6312async function toggleOpcTask(taskId) { 6313 const task = getOpcTasks().find(item => item.id === taskId); 6314 if (!task) return; 6315 task.status = task.status === 'complete' ? 'pending' : 'complete'; 6316 renderOpcProject(); 6317 await persistOpcProject(task.status === 'complete' ? 'ä»»å¡å·²å®æå¹¶ä¿å' : 'ä»»å¡å·²æ¢å¤ä¸ºå¾ æ¨è¿'); 6318} 6319 6320async function addOpcTask() { 6321 const title = $('#opcNewTaskTitle').value.trim(); 6322 const owner = $('#opcNewTaskOwner').value.trim(); 6323 if (!title) { showToast('请å å¡«åä»»å¡å 容'); $('#opcNewTaskTitle').focus(); return; } 6324 const stage = (opcProjectState?.stages || []).find(item => item.id === selectedOpcStageId); 6325 if (!stage) return; 6326 stage.tasks.push({ id:`custom-${Date.now()}`, title, owner:owner || '项ç®ä¸»ç人', due:'å¾ ææ', status:'pending', review_role:'项ç®ä¸»ç人' }); 6327 $('#opcNewTaskTitle').value = ''; 6328 $('#opcNewTaskOwner').value = ''; 6329 renderOpcProject(); 6330 await persistOpcProject('æ°ä»»å¡å·²å å ¥ OPC è¯ç¹'); 6331} 6332 6333async function updateOpcExpert(expertId) { 6334 const expert = (opcProjectState?.experts || []).find(item => item.id === expertId); 6335 if (!expert) return; 6336 const states = ['å¾ ææ´¾','åéå·²æ¶é','已确认']; 6337 expert.status = states[(states.indexOf(expert.status) + 1) % states.length]; 6338 renderOpcProject(); 6339 await persistOpcProject('ä¸å®¶å¸ä½ç¶æå·²æ´æ°'); 6340} 6341 6342async function updateOpcGate(gateId) { 6343 const gate = (opcProjectState?.gates || []).find(item => item.id === gateId); 6344 if (!gate) return; 6345 gate.status = gate.status === 'æªå¼å§' ? 'å¾ å®¡æ ¸' : gate.status === 'å¾ å®¡æ ¸' ? 'å·²éè¿' : 'å¾ å®¡æ ¸'; 6346 if (gate.status === 'å·²éè¿') { 6347 gate.reviewed_at = new Date().toISOString(); 6348 gate.reviewed_by = opcProjectState.owner || '项ç®è´£ä»»äºº'; 6349 } else { 6350 delete gate.reviewed_at; 6351 delete gate.reviewed_by; 6352 } 6353 renderOpcProject(); 6354 await persistOpcProject(gate.status === 'å·²éè¿' ? `${gate.name}å·²ç±äººå·¥ç¡®è®¤éè¿` : `${gate.name}ç¶æå·²æ´æ°`); 6355} 6356 6357async function saveOpcSettings() { 6358 if (!opcProjectState) return; 6359 opcProjectState.owner = $('#opcOwnerInput').value.trim() || 'å¾ æå®é¡¹ç®ä¸»ç人'; 6360 opcProjectState.pilot_scope = $('#opcScopeInput').value.trim() || 'æ¹è¯åäºç±»æ æºå»ç卿¢°'; 6361 opcProjectState.next_review = $('#opcReviewDateInput').value; 6362 opcProjectState.status = opcProjectState.owner.startsWith('å¾ æå®') ? 'è¯ç¹è®¾è®¡ä¸' : 'è¯ç¹æ§è¡ä¸'; 6363 renderOpcProject(); 6364 await persistOpcProject('OPC è¯ç¹è®¾ç½®å·²ä¿å'); 6365} 6366
6367function renderServices(stage = 'all') { 6368 const visible = stage === 'all' ? services : services.filter(item => item.stage === stage); 6369 $('#serviceGrid').innerHTML = visible.map(item => `<article class="service-card"><div class="service-card-top"><span class="service-number">${item.n}</span><span class="service-stage">${item.stageName} · ${item.status}</span></div><h3>${item.title}</h3><p>${item.desc}</p><div class="service-meta"><span>è½åç¶æ<b>${item.time}</b></span><span>åå¡ç¶æ<b>${item.price}</b></span><button data-service="${item.title}">äºè§£è¯¦æ â</button></div></article>`).join(''); 6370} 6371 6372$('#clinicalInput').addEventListener('input', event => $('#charCount').textContent = event.target.value.length); 6373$('#clinicalInput').addEventListener('keydown', event => { 6374 if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') runAnalysis(); 6375}); 6376$('#analyzeButton').addEventListener('click', runAnalysis); 6377$$('.example-chip').forEach(button => button.addEventListener('click', () => { 6378 $('#clinicalInput').value = button.dataset.example; 6379 $('#charCount').textContent = button.dataset.example.length; 6380 $('#clinicalInput').focus(); 6381})); 6382$('#resultPanel').addEventListener('click', event => { 6383 const tab = event.target.closest('[data-tab]'); 6384 if (tab) { 6385 $$('.result-tabs button').forEach(button => button.classList.toggle('active', button === tab)); 6386 $$('.tab-pane').forEach(pane => pane.classList.add('hidden')); 6387 $(`#${tab.dataset.tab}Pane`).classList.remove('hidden'); 6388 } 6389 const answer = event.target.closest('[data-interview-answer]'); 6390 if (answer) { 6391 submitInterviewAnswer(answer.dataset.interviewAnswer); 6392 } 6393}); 6394$('#sendAnswerButton').addEventListener('click', () => submitInterviewAnswer($('#freeAnswerInput').value)); 6395$('#freeAnswerInput').addEventListener('keydown', event => { 6396 if (event.key === 'Enter') submitInterviewAnswer(event.currentTarget.value); 6397}); 6398$('#generateReportButton').addEventListener('click', showReport); 6399$('#showMethodButton').addEventListener('click', () => $('#methodModal').classList.remove('hidden')); 6400$('#aiModeButton').addEventListener('click', () => { 6401 $('#aiConfigModal').classList.remove('hidden'); 6402 checkAiService(); 6403}); 6404$('#checkAiServiceButton').addEventListener('click', checkAiService); 6405$('#enableProfessionalAiButton').addEventListener('click', () => { 6406 setAiMode('professional'); 6407 $('#aiConfigModal').classList.add('hidden'); 6408 showToast('ç»ä¸ DeepSeek ä¸ä¸æ¨¡å¼å·²å¯ç¨ï¼ä¸ä¸æ¬¡åæå°è¿è¡å¨æä¸´åºè®¿è°'); 6409}); 6410$('#showAiEvidenceButton').addEventListener('click', () => { 6411 const citations = state.analysis?.citations || []; 6412 showToast(citations.length ? `æ¬è½®å·²è¿å ${citations.length} æ¡ä¾æ®ï¼è¯¦ç»è¯æ®å°åå ¥è§æ ¼ä¹¦` : 'æ¬è½®æªè¿å坿 ¸éªè¯æ®ï¼å¿ é¡»ç±ä¸å®¶å¤æ ¸'); 6413}); 6414$$('[data-close]').forEach(button => button.addEventListener('click', () => $(`#${button.dataset.close}`).classList.add('hidden'))); 6415$$('.modal-backdrop').forEach(backdrop => backdrop.addEventListener('click', event => { 6416 if (backdrop.id === 'authGate') return; // ç»å½é¨ç¦ï¼æªç»å½åä¸å 许ç¹å»ç©ºç½ç»è¿ 6417 if (event.target === backdrop) backdrop.classList.add('hidden'); 6418})); 6419document.addEventListener('keydown', event => { 6420 if (event.key === 'Escape') $$('.modal-backdrop').forEach(modal => { if (modal.id !== 'authGate') modal.classList.add('hidden'); }); 6421}); 6422$('#printReport').addEventListener('click', () => window.print()); 6423$('#enterDesign').addEventListener('click', () => { 6424 if (state.analysis?.sourceMode !== 'ai') { 6425 showToast('è§åæ¼ç¤ºç»æä¸è½è¿å ¥æ£å¼ä¸å©ä¸æ¦å¿µè®¾è®¡æµç¨'); 6426 return; 6427 } 6428 $('#reportModal').classList.add('hidden'); 6429 updateContextProject({ stage:'å¾ ä¸å©æ£ç´¢', progress:16, update:'éæ±è®¿è°å®æï¼çå¾ çå®ä¸å©æ°æ®' }); 6430 switchView('patent'); 6431 showToast('éæ±è¯ä¹å·²åæ¥ï¼çå®ä¸å©æ°æ®åºæ¥å ¥åä¸ä¼çæé£é©ç»è®º'); 6432}); 6433$('#scanPatentsButton').addEventListener('click', scanPatentLandscape); 6434$('#confirmPatentPathButton').addEventListener('click', confirmPatentPath); 6435$('#generateConceptsButton').addEventListener('click', generateConcepts); 6436$('#conceptGrid').addEventListener('click', event => { 6437 const card = event.target.closest('[data-concept-index]'); 6438 if (card) selectConcept(Number(card.dataset.conceptIndex)); 6439}); 6440$('#confirmConceptButton').addEventListener('click', confirmConcept); 6441$('#exportConceptButton').addEventListener('click', exportConceptBrief); 6442$('#advanceMilestoneButton').addEventListener('click', advanceDevelopmentMilestone); 6443$('#enterRegAIButton').addEventListener('click', () => switchView('regai')); 6444$('#goToFlashPOCButton').addEventListener('click', () => switchView('flashpoc')); 6445$('#viewProjectButton').addEventListener('click', () => switchView('command')); 6446$('#deliverableList').addEventListener('click', event => { 6447 const button = event.target.closest('[data-deliverable]'); 6448 if (button) downloadDeliverable(Number(button.dataset.deliverable)); 6449}); 6450$('#goToDevelopmentButton').addEventListener('click', () => switchView('development')); 6451$('#runTwinButton').addEventListener('click', runTwinValidation); 6452$('#generateRegDocsButton').addEventListener('click', generateRegDocuments); 6453$('#regDocGrid').addEventListener('click', event => { 6454 const button = event.target.closest('[data-regdoc]'); 6455 if (button) downloadRegDocument(Number(button.dataset.regdoc)); 6456}); 6457$('#enterCommercialButton').addEventListener('click', () => switchView('commercial')); 6458$('#algorithmGrid').addEventListener('click', event => { 6459 const button = event.target.closest('[data-algorithm]'); 6460 if (button) selectAlgorithm(button.dataset.algorithm); 6461}); 6462$('#matchKolsButton').addEventListener('click', matchKols); 6463$('#completeJourneyButton').addEventListener('click', completeCommercialJourney); 6464$('#exportAlgorithmPlanButton').addEventListener('click', exportAlgorithmPlan); 6465$('#backToProjectsButton').addEventListener('click', () => switchView('command')); 6466$('#opcStageTabs')?.addEventListener('click', event => { 6467 const button = event.target.closest('[data-opc-stage]'); 6468 if (!button) return; 6469 selectedOpcStageId = button.dataset.opcStage; 6470 renderOpcProject(); 6471}); 6472$('#opcTaskList')?.addEventListener('click', event => { 6473 const button = event.target.closest('[data-opc-task]'); 6474 if (button) toggleOpcTask(button.dataset.opcTask); 6475}); 6476$('#opcExpertList')?.addEventListener('click', event => { 6477 const button = event.target.closest('[data-opc-expert]'); 6478 if (button) updateOpcExpert(button.dataset.opcExpert); 6479}); 6480$('#opcGateGrid')?.addEventListener('click', event => { 6481 const button = event.target.closest('[data-opc-gate]'); 6482 if (button) updateOpcGate(button.dataset.opcGate); 6483}); 6484$('#opcAddTaskButton')?.addEventListener('click', addOpcTask); 6485$('#opcNewTaskTitle')?.addEventListener('keydown', event => { if (event.key === 'Enter') addOpcTask(); }); 6486$('#opcSaveSettingsButton')?.addEventListener('click', saveOpcSettings); 6487$('#opcRetryButton')?.addEventListener('click', loadOpcProject); 6488$('#opcOpenCockpitButton')?.addEventListener('click', () => { selectedCommandProjectId = 'OPC-2026-001'; switchView('command'); }); 6489$('#opcStartDeviceButton')?.addEventListener('click', () => switchView('workspace')); 6490$('#projectRows').addEventListener('click', event => { 6491 const item = event.target.closest('[data-project-id]'); 6492 if (!item) return; 6493 selectedCommandProjectId = item.dataset.projectId; 6494 renderProjects(); 6495 $('.command-project-detail').scrollIntoView({behavior:'smooth',block:'nearest'}); 6496}); 6497$('#commandProjectSearch').addEventListener('input', renderProjects); 6498$('#commandProjectStageFilter').addEventListener('change', renderProjects); 6499$('#professionalProjectSelect')?.addEventListener('change', event => selectProfessionalProject(event.target.value)); 6500$$('[data-professional-engine]').forEach(button => button.addEventListener('click', () => runProfessionalEngine(button.dataset.professionalEngine))); 6501$('#professionalOpenBp')?.addEventListener('click', () => switchView('investment')); 6502$('#professionalReviewList')?.addEventListener('submit', event => { 6503 event.preventDefault(); 6504 const form = event.target.closest('form'); 6505 if (form) submitProfessionalReview(form); 6506}); 6507$('#prototypeProjectSelect')?.addEventListener('change', event => selectPrototypeProject(event.target.value)); 6508$('#prototypeRefreshButton')?.addEventListener('click', () => loadPrototypeProjects(currentPrototypeProject?.id || '')); 6509$('#prototypeNewButton')?.addEventListener('click', () => togglePrototypeNewProject(true)); 6510$$('[data-prototype-create]').forEach(button => button.addEventListener('click', () => togglePrototypeNewProject(true))); 6511$('#prototypeCancelNewButton')?.addEventListener('click', () => togglePrototypeNewProject(false)); 6512$('#prototypeNewProjectForm')?.addEventListener('submit', async event => { 6513 event.preventDefault(); 6514 const form = event.currentTarget; 6515 const button = form.querySelector('[type="submit"]'); 6516 button.disabled = true; 6517 try { 6518 const payload = Object.fromEntries(new FormData(form).entries()); 6519 currentPrototypeProject = await investmentApiRequest('/api/prototype-pilot/projects', { 6520 method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload), 6521 }); 6522 form.reset(); 6523 form.elements.category.value = 'æ æºå¾®åææ¯å¨æ¢°'; 6524 togglePrototypeNewProject(false); 6525 await loadPrototypeProjects(currentPrototypeProject.id); 6526 showToast('90å¤©æ ·æºè¯ç¹é¡¹ç®å·²å»ºç«'); 6527 } catch (error) { 6528 showToast(error.message); 6529 } finally { 6530 button.disabled = false;
6531 } 6532}); 6533$('#prototypeCharterForm')?.addEventListener('submit', async event => { 6534 event.preventDefault(); 6535 if (!currentPrototypeProject) return; 6536 const form = event.currentTarget; 6537 const button = form.querySelector('[type="submit"]'); 6538 button.disabled = true; 6539 try { 6540 const payload = Object.fromEntries(new FormData(form).entries()); 6541 currentPrototypeProject = await investmentApiRequest(`/api/prototype-pilot/projects/${encodeURIComponent(currentPrototypeProject.id)}`, { 6542 method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(payload), 6543 }); 6544 renderPrototypeProject(); 6545 showToast('忧项ç®ç« ç¨å·²ä¿å'); 6546 } catch (error) { 6547 showToast(error.message); 6548 } finally { 6549 button.disabled = false; 6550 } 6551}); 6552$('#prototypeEditCharterButton')?.addEventListener('click', () => { 6553 setPrototypeTab('charter'); 6554 $('#prototypeCharterForm')?.scrollIntoView({behavior:'smooth',block:'start'}); 6555}); 6556$('#prototypeTabs')?.addEventListener('click', event => { 6557 const button = event.target.closest('[data-prototype-tab]'); 6558 if (button) setPrototypeTab(button.dataset.prototypeTab); 6559}); 6560$('#prototypeWorkspace')?.addEventListener('click', event => { 6561 const modalButton = event.target.closest('[data-prototype-modal]'); 6562 if (modalButton) openPrototypeDialog(modalButton.dataset.prototypeModal); 6563 const dhfButton = event.target.closest('[data-save-dhf]'); 6564 if (dhfButton) savePrototypeDhf(dhfButton.closest('[data-dhf-document]')); 6565}); 6566$('#prototypeWorkspace')?.addEventListener('submit', event => { 6567 const form = event.target.closest('#prototypeGateForm'); 6568 if (!form) return; 6569 event.preventDefault(); 6570 submitPrototypeGate(form); 6571}); 6572$('#prototypeDataForm')?.addEventListener('submit', submitPrototypeDialog); 6573$('#prototypeDialogClose')?.addEventListener('click', () => $('#prototypeDataDialog').close()); 6574$('#prototypeDialogCancel')?.addEventListener('click', () => $('#prototypeDataDialog').close()); 6575$('#baseEnterpriseList')?.addEventListener('click', event => { 6576 const button = event.target.closest('[data-base-company]'); 6577 if (button) selectBaseEnterprise(button.dataset.baseCompany); 6578}); 6579let baseEnterpriseSearchTimer = null; 6580$('#baseEnterpriseSearch')?.addEventListener('input', () => { 6581 clearTimeout(baseEnterpriseSearchTimer); 6582 baseEnterpriseSearchTimer = setTimeout(refreshBaseEnterpriseList, 250); 6583}); 6584$('#baseEnterpriseCategory')?.addEventListener('change', refreshBaseEnterpriseList); 6585$('#baseEnterpriseStage')?.addEventListener('change', refreshBaseEnterpriseList); 6586$('#baseEnterpriseReset')?.addEventListener('click', () => { 6587 $('#baseEnterpriseSearch').value = ''; 6588 $('#baseEnterpriseCategory').value = ''; 6589 $('#baseEnterpriseStage').value = ''; 6590 refreshBaseEnterpriseList(); 6591}); 6592$('#baseEnterpriseMatchButton')?.addEventListener('click', () => runBaseEnterpriseProjectMatch($('#baseEnterpriseProjectSelect').value)); 6593$('#baseEnterpriseNewsToggle')?.addEventListener('click', () => { 6594 const body = $('#baseEnterpriseNewsBody'); 6595 if (!body) return; 6596 const collapsed = body.classList.toggle('collapsed'); 6597 $('#baseEnterpriseNewsToggle').textContent = collapsed ? 'å±å¼' : 'æ¶èµ·'; 6598}); 6599[$('#baseEnterpriseMatchResults'), $('#investmentEnterpriseMatchResults')].filter(Boolean).forEach(container => container.addEventListener('click', event => { 6600 const button = event.target.closest('[data-open-base-company]'); 6601 if (!button) return; 6602 switchView('enterpriselib'); 6603 loadBaseEnterpriseLibrary().then(() => selectBaseEnterprise(button.dataset.openBaseCompany)); 6604})); 6605const policyEnterpriseSwitcher = $('.enterprise-switcher'); 6606if (policyEnterpriseSwitcher && !$('#openBaseEnterpriseLibraryButton')) { 6607 const policyLibraryButton = document.createElement('button'); 6608 policyLibraryButton.className = 'outline-button'; 6609 policyLibraryButton.id = 'openBaseEnterpriseLibraryButton'; 6610 policyLibraryButton.type = 'button'; 6611 policyLibraryButton.textContent = 'ä»åºå°ä¼ä¸åºå¯¼å ¥'; 6612 policyLibraryButton.addEventListener('click', () => switchView('enterpriselib')); 6613 policyEnterpriseSwitcher.insertBefore(policyLibraryButton, $('#newPolicyEnterpriseButton')); 6614}
6615$$('.nav-item').forEach(item => item.addEventListener('click', () => switchView(item.dataset.view))); 6616$$('[data-view-link]').forEach(button => button.addEventListener('click', () => switchView(button.dataset.viewLink))); 6617$('#trackSelector').addEventListener('click', event => { 6618 const button = event.target.closest('[data-track]'); 6619 if (!button) return; 6620 state.intakeTrack = button.dataset.track; 6621 $$('#trackSelector button').forEach(item => item.classList.toggle('selected', item === button)); 6622}); 6623$('#evaluateIntakeButton').addEventListener('click', evaluateIncubationProject); 6624$('#enterRecommendedPathButton').addEventListener('click', enterIntakeRecommendedPath); 6625$('#matchResourcesButton').addEventListener('click', matchGroupResources); 6626$('#runInvestmentReview').addEventListener('click', runInvestmentReview); 6627$('#runPolicyMatch').addEventListener('click', runPolicyMatch); 6628$('#runRegulatoryTriage').addEventListener('click', runRegulatoryTriage); 6629$('#bpFileInput').addEventListener('change', event => chooseBpFile(event.target.files[0])); 6630if ($('#bpUploadZone span')) $('#bpUploadZone span').textContent = 'PDF / DOCX / PPTXï¼æå¤§ 120MB'; 6631$('#bpAnalyzeFileButton').addEventListener('click', analyzeBpFile); 6632$('#bpLoginButton').addEventListener('click', loginBpEngine); 6633$('#bpRegisterButton').addEventListener('click', registerBpEngine); 6634$('#bpOpenLegacyButton').addEventListener('click', () => window.open(`${BP_LEGACY_BASE}/`, '_blank')); 6635['dragenter','dragover'].forEach(type => $('#bpUploadZone').addEventListener(type, event => { event.preventDefault(); $('#bpUploadZone').classList.add('dragover'); })); 6636['dragleave','drop'].forEach(type => $('#bpUploadZone').addEventListener(type, event => { event.preventDefault(); $('#bpUploadZone').classList.remove('dragover'); if (type === 'drop') chooseBpFile(event.dataTransfer.files[0]); })); 6637$('#policyMaterialsInput').addEventListener('change', async event => { await updatePolicyMaterials(event.target.files); event.target.value = ''; }); 6638$('#policyGuideInput').addEventListener('change', event => updatePolicyGuideFiles(event.target.files)); 6639$('#policyTemplateInput').addEventListener('change', event => updatePolicyTemplate(event.target.files[0])); 6640$('#policyProfileVersion')?.addEventListener('change', event => applyPolicyProfileVersion(event.target.value)); 6641$('#policyEnterpriseSelect').addEventListener('change', event => loadPolicyEnterprise(event.target.value)); 6642initQualTagGroup('policyEnterpriseQualGroup','policyEnterpriseCertificationOther'); 6643initQualTagGroup('policyQualGroup','policyCertificationOther'); 6644$('#newPolicyEnterpriseButton').addEventListener('click', () =>
6644 { $('#policyEnterpriseEditor').classList.remove('hidden'); resetQualificationTags('policyEnterpriseQualGroup','policyEnterpriseCertificationOther'); $('#policyEnterpriseName').focus(); }); 6645$('#cancelPolicyEnterpriseButton').addEventListener('click', () => $('#policyEnterpriseEditor').classList.add('hidden')); 6646$('#createPolicyEnterpriseButton').addEventListener('click', createPolicyEnterprise); 6647$('#tycSyncButton')?.addEventListener('click', syncTianyanchaForNewEnterprise); 6648$('#tycSyncExistingButton')?.addEventListener('click', syncTianyanchaForExistingEnterprise); 6649$('#savePolicyEnterpriseProfileButton').addEventListener('click', () => saveCurrentPolicyEnterpriseProfile(false)); 6650$('#reanalyzePolicyEnterpriseButton').addEventListener('click', reanalyzePolicyEnterprise); 6651$('#generateProposalButton').addEventListener('click', generateProposalDraft); 6652$$('#croModuleGrid button').forEach(button => button.addEventListener('click', () => selectCroModule(button.dataset.croModule))); 6653$('#croModuleAction').addEventListener('click', () => { const moduleId = $('#croModuleAction').dataset.module || 'project'; if (['project','path','gap'].includes(moduleId)) { $('#regDescription').scrollIntoView({behavior:'smooth',block:'center'}); $('#regDescription').focus(); } else { showToast(`${croModules[moduleId].title}å·²å±å¼ï¼ç宿³è§åºåé¡¹ç®æ°æ®åºå°å¨ä¸ä¸æ°æ®å±æ¥å ¥`); } }); 6654$('#resourceNeedInput').addEventListener('keydown', event => { if (event.key === 'Enter') matchGroupResources(); }); 6655$('#mobileMenu').addEventListener('click', () => $('#sidebar').classList.toggle('open')); 6656const sidebarCollapseButton = $('#sidebarCollapseButton'); 6657const applySidebarPreference = collapsed => { 6658 $('.app-shell').classList.toggle('sidebar-collapsed', collapsed); 6659 sidebarCollapseButton.querySelector('span').textContent = collapsed ? 'âº' : 'â¹'; 6660 sidebarCollapseButton.setAttribute('aria-expanded', String(!collapsed)); 6661 sidebarCollapseButton.setAttribute('aria-label', collapsed ? 'å±å¼å·¦ä¾§å¯¼èª' : 'æ¶èµ·å·¦ä¾§å¯¼èª'); 6662 sidebarCollapseButton.title = collapsed ? 'å±å¼å·¦ä¾§å¯¼èª' : 'æ¶èµ·å·¦ä¾§å¯¼èª'; 6663}; 6664let sidebarIsCollapsed = false; 6665try { sidebarIsCollapsed = localStorage.getItem('medici-sidebar-collapsed') === '1'; } catch (error) { sidebarIsCollapsed = false; } 6666applySidebarPreference(sidebarIsCollapsed); 6667sidebarCollapseButton.addEventListener('click', () => { 6668 sidebarIsCollapsed = !$('.app-shell').classList.contains('sidebar-collapsed'); 6669 applySidebarPreference(sidebarIsCollapsed); 6670 try { localStorage.setItem('medici-sidebar-collapsed', sidebarIsCollapsed ? '1' : '0'); } catch (error) { /* æµè§å¨ç¦ç¨æ¬å°å卿¶ä»ä¿ææ¬æ¬¡ä½¿ç¨ç¶æ */ } 6671}); 6672$('#newProjectButton').addEventListener('click', () => { switchView('intake'); $('#intakeProjectName').focus(); showToast('å·²å¼å§ä¸ä¸ªæ°çææè½¬å项ç®'); }); 6673$$('[data-action="new-project"]').forEach(button => button.addEventListener('click', () => { switchView('workspace'); $('#clinicalInput').focus(); })); 6674$('#voiceButton').addEventListener('click', () => showToast('æ¼ç¤ºçææªæ¥å ¥è¯é³è¯å«ï¼å¯ç´æ¥è¾å ¥æå')); 6675$('#imageButton').addEventListener('click', () => showToast('æ¼ç¤ºçææªæ¥å ¥èå¾è¯å«ï¼å¯å¨æ£å¼çå¯ç¨å¤æ¨¡æè¾å ¥')); 6676$('.stage-selector').addEventListener('click', event => { 6677 const button = event.target.closest('[data-stage]'); 6678 if (!button) return; 6679 $$('.stage-selector button').forEach(item => item.classList.toggle('active', item === button)); 6680 renderServices(button.dataset.stage); 6681}); 6682$('#serviceGrid').addEventListener('click', event => { 6683 const button = event.target.closest('[data-service]'); 6684 if (!button) return; 6685 const title = button.dataset.service; 6686 const target = title.includes('éæ±æ´å¯') ? 'workspace' 6687 : title.includes('ä¸å©') ? 'patent' 6688 : title.includes('FlashPOC') ? 'flashpoc' 6689 : title.includes('åè½åå') ? 'development' 6690 : title.includes('RegAI') ? 'regai' 6691 : 'commercial'; 6692 switchView(target); 6693 showToast(`å·²è¿å ¥â${title}â对åºå·¥ä½å°`); 6694}); 6695['marketTargetUnits','marketAnnualUsage','marketUnitPrice','marketReachRate','marketShareRate'].forEach(id => $(`#${id}`)?.addEventListener('input', calculateMarketScenario)); 6696$('#marketAiEstimateBtn')?.addEventListener('click', estimateMarketWithAI); 6697$('#transformationPathBtn')?.addEventListener('click', generateTransformationPath); 6698$$('[data-budget-item]').forEach(input => input.addEventListener('input', calculateRegistrationBudget)); 6699$('.budget-scenario-switch')?.addEventListener('click', event => { 6700 const button = event.target.closest('[data-budget-scenario]'); 6701 if (button) applyBudgetScenario(button.dataset.budgetScenario); 6702}); 6703$('#investmentDimensions')?.addEventListener('click', event => { 6704 const card = event.target.closest('[data-dimension-index]'); 6705 if (!card) return; 6706 const index = Number(card.dataset.dimensionIndex); 6707 renderDimensionEvidence(currentBpDimensions[index], index, true); 6708}); 6709$('#dimensionEvidencePanel')?.addEventListener('click', event => { 6710 if (!event.target.closest('.dimension-back-button')) return; 6711 $('#dimensionScoreTop').scrollIntoView({behavior:'smooth',block:'start'}); 6712}); 6713$('#comparableRows')?.addEventListener('click', event => { 6714 const button = event.target.closest('.verify-comparable'); 6715 if (button) toggleComparableVerification(button.dataset.certificate); 6716}); 6717 6718initializeInvestmentWorkflowLayout(); 6719initializePolicyWorkflowLayout(); 6720initializeTruthfulStaticCopy(); 6721renderProjects(); 6722renderServices(); 6723restoreDevelopmentTask(); 6724initializeAiMode(); 6725selectCroModule('project'); 6726checkBpEngine(); 6727 6728// Browser smoke-test mode: index.html?demo=interview 6729const demoMode = new URLSearchParams(window.location.search).get('demo'); 6730if (demoMode === 'interview') { 6731 const demoText = $('.example-chip').dataset.example; 6732 $('#clinicalInput').value = demoText; 6733 $('#charCount').textContent = demoText.length; 6734 runAnalysis();
6735 ['èå / è 管', 'æç»çµæ', '10â30 åé', 'ä¼å ä¿æ¤ç»ç»'].forEach((answer, index) => { 6736 setTimeout(() => submitInterviewAnswer(answer), 2300 + index * 420); 6737 }); 6738} else if (demoMode === 'investment') { 6739 switchView('investment'); 6740 renderProfessionalBpReport({ 6741 overall_score:6.5, 6742 recommendation:'è¿å ¥ææè¡¥å ä¸ä¸å®¶å审', 6743 summary:'æ¥å已宿ä¹ç»´ææåçï¼å¹¶è¡¥å 宿¹å¸åºä¸æ³¨åæ¶è´¹åèã', 6744 dimensions:['临åºéæ±','产å䏿æ¯','éªè¯è¯æ®','注åè·¯å¾','ç¥è¯äº§æ','å¸åºç«äº','å¢éè½å','产ä¸åè½å','èèµä¸åæ¥'].map((name,index)=>({name,score:[7,6.3,7.6,7.6,4.4,7.6,6.3,4.4,7.6][index],evidence:'æ¼ç¤ºææ',weaknesses:[]})), 6745 risks:[], self_check:{self_check_passed:'å¦'}, reference_data:{...defaultInvestmentReferenceData(),product_scope:'ä½å¤è¯æè¯å / IVD'} 6746 }); 6747 renderInvestmentWorkflowStep(3, true); 6748} else if (demoMode === 'flashpoc') { 6749 state.analysis = scenarioLibrary[2]; 6750 state.answers = { 6751 0: { answer: 'èå / è 管' }, 6752 1: { answer: 'æç»çµæ' }, 6753 2: { answer: '10â30 åé' }, 6754 3: { answer: 'ä¼å ä¿æ¤ç»ç»' } 6755 }; 6756 $('#confidenceValue').textContent = '94%'; 6757 switchView('flashpoc'); 6758 generateConcepts(); 6759 setTimeout(() => selectConcept(1), 2100); 6760} else if (demoMode === 'development') {
6761 state.analysis = scenarioLibrary[2]; 6762 state.answers = { 6763 0: { answer: 'èå / è 管' }, 6764 1: { answer: 'æç»çµæ' }, 6765 2: { answer: '10â30 åé' }, 6766 3: { answer: 'ä¼å ä¿æ¤ç»ç»' } 6767 }; 6768 state.flashpoc.projectId = 'FP-260711-286'; 6769 state.flashpoc.selected = 1; 6770 confirmConcept(); 6771 setTimeout(advanceDevelopmentMilestone, 450); 6772 setTimeout(advanceDevelopmentMilestone, 900); 6773} else if (demoMode === 'patent') { 6774 state.analysis = scenarioLibrary[2]; 6775 switchView('patent'); 6776 scanPatentLandscape(); 6777} else if (demoMode === 'regai') { 6778 state.analysis = scenarioLibrary[2]; 6779 state.flashpoc.selected = 1; 6780 state.development = { created:true, taskId:'DEV-260711-518', phase:4, concept:conceptLibraries.laparoscopic[1], projectId:'FP-260711-286', activities:[] }; 6781 switchView('regai'); 6782 runTwinValidation(); 6783 setTimeout(generateRegDocuments, 2300); 6784} else if (demoMode === 'commercial') { 6785 state.analysis = scenarioLibrary[2]; 6786 state.development = { created:true, taskId:'DEV-260711-518', phase:4, concept:conceptLibraries.laparoscopic[1], projectId:'FP-260711-286', activities:[] }; 6787 state.regai = { validated:true, documentsGenerated:true }; 6788 switchView('commercial'); 6789 selectAlgorithm('ååé¦é¢è¦'); 6790 matchKols(); 6791 setTimeout(completeCommercialJourney, 1300); 6792} else if (demoMode === 'journey') {
6793 state.analysis = scenarioLibrary[2]; 6794 state.answers = { 6795 0:{answer:'èå / è 管'},1:{answer:'æç»çµæ'},2:{answer:'10â30 åé'},3:{answer:'ä¼å ä¿æ¤ç»ç»'} 6796 }; 6797 switchView('patent'); 6798 scanPatentLandscape(); 6799 setTimeout(confirmPatentPath, 1800); 6800 setTimeout(() => { selectConcept(1); confirmConcept(); }, 4000); 6801 [4400,4750,5100,5450].forEach(time => setTimeout(advanceDevelopmentMilestone,time)); 6802 setTimeout(() => { switchView('regai'); runTwinValidation(); }, 5800); 6803 setTimeout(generateRegDocuments, 8000); 6804 setTimeout(() => { switchView('commercial'); selectAlgorithm('ååé¦é¢è¦'); matchKols(); }, 9200); 6805 setTimeout(completeCommercialJourney, 10400); 6806} else if (demoMode === 'ai-config') { 6807 $('#aiConfigModal').classList.remove('hidden'); 6808} else if (demoMode === 'intake') { 6809 switchView('intake'); 6810 $('#intakeProjectName').value = 'å¼å¸éå¤èæ£è¯å'; 6811 $('#intakeDescription').value = '髿 ¡å¢éå¼åäºå¼å¸éç åä½å¤èæ£è¯åï¼å·²æå®éªæ°æ®åä¸é¡¹ä¸å©ï¼å¸æå®æäº§å注åã临åºéªè¯åè§æ¨¡åç产ã'; 6812 evaluateIncubationProject(); 6813} else if (demoMode === 'resources') { 6814 switchView('resources'); 6815 $('#resourceNeedInput').value = 'IVD项ç®å·²æå®éªæ ·åï¼éè¦æ³¨åçç¥ã临åºéªè¯åéäº
6815§åºå°'; 6816 matchGroupResources(); 6817} 6818 6819// å¹³å°è´¦å·ä½ç³»ï¼æ token æ token å¤±ææ¶å ç»å½ï¼admin æ¾ç¤ºç¨æ·æ°æ®çæ¿å ¥å£ 6820initPlatformAuth();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.