Merge branch 'apiPR' into 'master'
Api pr complete apiReaquest.js cliConnection.js textAnalystic.js add description See merge request !3
Showing
3 changed files
with
342 additions
and
0 deletions
api/apiRequest.js
0 → 100644
1 | +// api key | ||
2 | +require('dotenv').config(); | ||
3 | + | ||
4 | +// api key error check | ||
5 | +try { | ||
6 | + if( process.env.TEST != "OKAY" ) { | ||
7 | + throw new Error( "키 오류 키파일을 확인하세요" ); | ||
8 | + } | ||
9 | +} | ||
10 | +catch( err ) { | ||
11 | + throw new Error(err); | ||
12 | +} | ||
13 | +const rp = require("request-promise"); | ||
14 | + | ||
15 | +// Api URL | ||
16 | +const URL = { | ||
17 | + "ETRI" : "http://aiopen.etri.re.kr:8000/", | ||
18 | + "Korean" : "https://search.naver.com/p/csearch/ocontent/util/SpellerProxy?_callback=&color_blindness=0&q=" | ||
19 | +} | ||
20 | + | ||
21 | +// ETRI Api Request Format | ||
22 | +const apiRequestJsonFrame = { | ||
23 | + "request_id" : "reserved field", | ||
24 | + "access_key" : process.env.ETRI_API_KEY, | ||
25 | + "argument" : {} | ||
26 | +}; | ||
27 | + | ||
28 | +let apiRequest = {}; | ||
29 | + | ||
30 | +/** | ||
31 | + * @param {String} query 세부 url / 형식은 api사이트 참조 | ||
32 | + * @param {Object} argument 필요한 argument / 형식은 api사이트 참조 | ||
33 | + * @returns {Object} api사이트에서 정해진 형식의 응답을 받아옵니다. | ||
34 | + * @description 이 함수는 이미 정해진 url(etri api)+query의 | ||
35 | + 경로로 argument와 함께 request를 보냅니다. | ||
36 | + 그 후 얻은 응답을 js object로 보내줍니다. | ||
37 | +*/ | ||
38 | +apiRequest.ETRI = async ( query, argument ) => { | ||
39 | + return new Promise( ( resolve, reject ) => { | ||
40 | + let apiReqJson = apiRequestJsonFrame; | ||
41 | + apiReqJson.argument = argument; | ||
42 | + let apiReqOption = { headers: { | ||
43 | + "Accept": "application/json", | ||
44 | + "Content-Type": "application/json", | ||
45 | + },uri : URL.ETRI + query, body : JSON.stringify( apiReqJson ) }; | ||
46 | + | ||
47 | + rp.post( apiReqOption ) | ||
48 | + .then( ( body ) => { | ||
49 | + body = JSON.parse( body ); | ||
50 | + if( body.result == "-1" ) { | ||
51 | + throw new Error( body.reason ); | ||
52 | + } | ||
53 | + resolve( body ); | ||
54 | + }) | ||
55 | + .catch( ( err ) => { | ||
56 | + throw new Error( err ); | ||
57 | + }); | ||
58 | + }) | ||
59 | +} | ||
60 | + | ||
61 | +/** | ||
62 | + * @param {String} text 고치고 싶은 문장 | ||
63 | + * @returns {Object} 정해진 형식의 응답을 보내줍니다. | ||
64 | + * @description 네이버 맞춤법 사이트로 text를 보내서 응답을 받아옵니다. | ||
65 | +*/ | ||
66 | +apiRequest.Korean = async ( text ) => { | ||
67 | + return new Promise( ( resolve,reject ) => { | ||
68 | + | ||
69 | + rp( { "uri" : URL.Korean+encodeURI( text ) } ) | ||
70 | + .then( ( body ) => { | ||
71 | + body = body.substring( 1, body.length - 2 ); | ||
72 | + resolve( JSON.parse( body ).message.result ); | ||
73 | + }) | ||
74 | + .catch( ( err ) => { | ||
75 | + throw new Error( err ); | ||
76 | + }); | ||
77 | + }); | ||
78 | +} | ||
79 | + | ||
80 | +apiRequest.multiETRI = async ( searchResults, keywordText ) => { | ||
81 | + try { | ||
82 | + const Promises = await searchResults.map((searchResult, index)=>{ | ||
83 | + //return makeOption( searchResults, keywordText, index ); | ||
84 | + return DOCVECAPI( searchResults, keywordText, index ); | ||
85 | + }); | ||
86 | + await Promise.all( Promises ); | ||
87 | + } | ||
88 | + catch ( err ) { | ||
89 | + throw new Error( err.message ); | ||
90 | + } | ||
91 | +} | ||
92 | + | ||
93 | + | ||
94 | +/** | ||
95 | + * @param {String} keywordText 사용자의 검색에서 textAnalystic을 거쳐 keyword만 남은 text | ||
96 | + * @param {{title:string,passage:string,ulr:string}} searchResult searchResults keywordtext문장을 검색하여 나온 결과들 | ||
97 | + * @returns confidence key와 해당 value가 추가된 searchResults | ||
98 | + * @description DOCVECAPI를 이용해 각각의 searchResult의 keywordtext에 대한 정확도(confidence, 검색문장에 대해 검색 결과가 얼마나 지)를 | ||
99 | + * 판단하고, confidence key와 해당 value를 searchResult에 추가해줍니다. | ||
100 | +*/ | ||
101 | +const DOCVECAPI = (searchResults, keywordText, index) => { | ||
102 | + return new Promise((resolve, reject) => { | ||
103 | + apiReqOption = { | ||
104 | + method: "POST", | ||
105 | + uri: "http://127.0.0.1:5000/analyze", | ||
106 | + body: { | ||
107 | + sentence1: searchResults[index].passage, | ||
108 | + sentence2: keywordText | ||
109 | + }, | ||
110 | + json: true | ||
111 | + }; | ||
112 | + rp.post(apiReqOption) | ||
113 | + .then(body => { | ||
114 | + if (body.result == "-1") { | ||
115 | + throw new Error(body.data + " index : " + index); | ||
116 | + } | ||
117 | + searchResults[index].confidence = Number(body.result); | ||
118 | + resolve(); | ||
119 | + }) | ||
120 | + .catch(err => { | ||
121 | + searchResults[index].confidence = 0; | ||
122 | + resolve(); | ||
123 | + }); | ||
124 | + }); | ||
125 | + }; | ||
126 | + | ||
127 | +module.exports = apiRequest; |
api/cliConnection.js
0 → 100644
1 | +const textAnalytic = require("./textAnalystic"); | ||
2 | +const search = require("./search"); | ||
3 | +const machineRead = require("./machineRead"); | ||
4 | + | ||
5 | +/** | ||
6 | + * @param req - request | ||
7 | + * @param req.body.data - client에서 보내는 데이터 req.body.data.text에 검색할 문장을 담아야 합니다 | ||
8 | + * @description client와 데이터를 받아 통신하는 함수입니다 | ||
9 | + */ | ||
10 | +const cliConnection = async (req, res) => { | ||
11 | + let clientData = {}, | ||
12 | + analyzeData = {}, | ||
13 | + searchData = []; | ||
14 | + | ||
15 | + // clientData | ||
16 | + try { | ||
17 | + clientData = req.body.data; | ||
18 | + if( !clientData.text.replace( /\s/g, '' ).length ) { | ||
19 | + throw new Error( "client text empty" ); | ||
20 | + } | ||
21 | + } | ||
22 | + catch ( err ) { | ||
23 | + console.log( err ); | ||
24 | + res.json( { "return_code" : -1, "error_code" : err.message } ); | ||
25 | + res.status( 403 ); | ||
26 | + return false; | ||
27 | + } | ||
28 | + | ||
29 | + // analyzeData | ||
30 | + try { | ||
31 | + analyzeData = await textAnalytic( clientData ); | ||
32 | + } | ||
33 | + catch ( err ) { | ||
34 | + console.log( err ); | ||
35 | + res.json( { "return_code" : -1, "error_code" : err.message } ); | ||
36 | + res.status( 502 ); | ||
37 | + return false; | ||
38 | + } | ||
39 | + | ||
40 | + // searchData | ||
41 | + searchData = searchData[ 0 ].concat( searchData[ 1 ] ); | ||
42 | + try { | ||
43 | + searchData = await machineRead( searchData, analyzeData.keywordText ); | ||
44 | + } | ||
45 | + catch ( err ) { | ||
46 | + console.log( err ); | ||
47 | + res.json( { "return_code" : -1, "error_code" : err.message } ); | ||
48 | + res.status( 502 ); | ||
49 | + return false; | ||
50 | + } | ||
51 | + | ||
52 | + analyzeData.searchResults = searchData; | ||
53 | + res.send({ return_code: 0, return_data: analyzeData }); | ||
54 | + res.status(200); | ||
55 | +}; | ||
56 | + | ||
57 | +module.exports = cliConnection; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
api/textAnalystic.js
0 → 100644
1 | +const apiRequest = require('./apiRequest'); | ||
2 | + | ||
3 | +const allowMorpChecklist = [ "NNG","NNP","NNB","VA","MM","MAG","SL","SH","SN","XPN","XSN","XSA","ETM","NOG" ]; | ||
4 | +const vvMorpChecklist = ["ETM","ETN"]; // 명사형 전성어미(ex) '-(으)ㅁ', '-기'), 관형사형 전성어미(ex) '-ㄴ', '-', '-던', '-ㄹ'), 지정자(VCP,VCN, ex) '-이다', '-아니다') | ||
5 | +const npMorpCecklist = ['어디','언제']; // 필요한 의문사 리스트 | ||
6 | +/** | ||
7 | + * @param {{lemma:string, position:number, type:string}[]} word - 한 단어의 형태소 ex) [{걸리},{었},{을}] | ||
8 | + * @param {{lemma:string, position:number, type:string}[][]} needMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}] | ||
9 | + * @param {{lemma:string, position:number, type:string}[][]} noNeedMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}] | ||
10 | + * @description word의 각 형태소의 type이 allowMorpChecklist에 있는지 확인하고 있으면 needMorp, 없으면 noNeedMorp에 추가합니다. | ||
11 | + */ | ||
12 | +const checkMorp = ( word, needMorp, noNeedMorp ) => { | ||
13 | + let needMorpTemp = [], | ||
14 | + noNeedMorpTemp = []; | ||
15 | + word.forEach( ( morp ) => { | ||
16 | + if( allowMorpChecklist.indexOf( morp.type ) !== -1 ) { | ||
17 | + needMorpTemp.push( morp ); | ||
18 | + } else if(npMorpCecklist.indexOf( morp.lemma ) !== -1 ) { | ||
19 | + needMorpTemp.push( morp ); | ||
20 | + } | ||
21 | + else { | ||
22 | + noNeedMorpTemp.push( morp ); | ||
23 | + } | ||
24 | + }); | ||
25 | + if( noNeedMorpTemp.length > 0) { | ||
26 | + noNeedMorp.push( noNeedMorpTemp ); | ||
27 | + } | ||
28 | + if( needMorpTemp.length > 0) { | ||
29 | + needMorp.push( needMorpTemp ); | ||
30 | + } | ||
31 | +} | ||
32 | + | ||
33 | + | ||
34 | +/** | ||
35 | + * @param {{lemma:string, position:number, type:string}[][]} tempMorps - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]] | ||
36 | + * @returns {{needMorp : {}[][], noNeedMorp : {}[][]}} morp를 needMorp와 noNeedMorp로 나눴습니다. | ||
37 | + * @description 공백 단위로 나뉜 morp를 받아 type과 의미에 따라 2가지로 분류합니다. | ||
38 | + */ | ||
39 | +const divideMorpbyMean = ( tempMorps ) => { | ||
40 | + let needMorp = [], | ||
41 | + noNeedMorp = []; | ||
42 | + | ||
43 | + tempMorps.forEach( ( word, j ) => { | ||
44 | + | ||
45 | + if( word[ 0 ].type === "VV" || word[ 0 ].type === "VA" || word[ 0 ].type === "MAG") { // 동사, 형용사, 부사 | ||
46 | + let checkV = true, | ||
47 | + checkM = true; | ||
48 | + word.find( ( Morp ) => { | ||
49 | + if( Morp.type === "EF" ) { // 종결어미 | ||
50 | + checkV = false; | ||
51 | + } else if( Morp.type === "EC" ) { // 연결어미 | ||
52 | + if( tempMorps.length > j + 1 ) { | ||
53 | + tempMorps[ j + 1 ].forEach( ( morp ) => { | ||
54 | + if( allowMorpChecklist.indexOf( morp.type ) === -1 ) { | ||
55 | + checkV = false; | ||
56 | + } | ||
57 | + }); | ||
58 | + } | ||
59 | + } else if( word[ 0 ].type === "MAG") { | ||
60 | + if( Morp.type === "XSV" ) { // 동사파생 접미사 ex) -하다 | ||
61 | + checkM = false; | ||
62 | + } | ||
63 | + } | ||
64 | + }); | ||
65 | + if( checkV && checkM) { | ||
66 | + needMorp.push( word ); | ||
67 | + } else { | ||
68 | + noNeedMorp.push( word ); | ||
69 | + } | ||
70 | + } | ||
71 | + else { | ||
72 | + checkMorp( word, needMorp, noNeedMorp ); | ||
73 | + } | ||
74 | + }); | ||
75 | + return [ needMorp, noNeedMorp ]; | ||
76 | +} | ||
77 | + | ||
78 | +/** | ||
79 | + * @param {String} result - 결과 담던거 | ||
80 | + * @param {{text : string, begin : number, end : number }[]} words 단어 분석 결과를 담는 어레이 | ||
81 | + * @param {{lemma:string, position:number, type:string, id : number}[][]} needMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]] | ||
82 | + * @returns {String} 필요한 단어만 남겨둔 문장입니다. | ||
83 | + * @description 필요한 morp와 원문 텍스트를 이용해 문장에서의 키워드를 분석해 문장으로 만들어 줍니다. | ||
84 | + */ | ||
85 | +const makeKeyword = ( result, words, needMorp ) => { | ||
86 | + let keywordText = ""; | ||
87 | + | ||
88 | + needMorp.forEach( ( morps ) => { | ||
89 | + words.forEach( ( word ) => { | ||
90 | + if( word.begin === morps[ 0 ].id ){ | ||
91 | + let tempByte = morps[ morps.length - 1 ].position - morps[0].position + Buffer.byteLength( morps[ morps.length - 1 ].lemma ); | ||
92 | + for( let ch of word.text ) { | ||
93 | + if( tempByte > 0 ) { | ||
94 | + keywordText += ch; | ||
95 | + tempByte -= Buffer.byteLength(ch) | ||
96 | + } | ||
97 | + } | ||
98 | + } | ||
99 | + }); | ||
100 | + keywordText += " "; | ||
101 | + }); | ||
102 | + result.keywordText = keywordText.trim(); | ||
103 | +} | ||
104 | + | ||
105 | +/** | ||
106 | + * @param {String} result - 결과 담던거 | ||
107 | + * @param {{NE : {}[], Morp : {}[]}} analysisResult 분석 결과가 담겼습니다. | ||
108 | + * @description morp를 처리하는 함수 입니다 ^^ | ||
109 | + */ | ||
110 | +const divideMorp = async ( result, analysisResult ) => { | ||
111 | + let tempResult = {}, | ||
112 | + tempMorps = []; | ||
113 | + | ||
114 | + analysisResult.NE.forEach( ( word ) => { | ||
115 | + analysisResult.morp.forEach( ( morp, index ) => { | ||
116 | + if( word.begin <= index && word.end >= index ) { | ||
117 | + morp.type = "NOG"; | ||
118 | + } | ||
119 | + }); | ||
120 | + }); | ||
121 | + | ||
122 | + analysisResult.word.forEach( ( word ) => { | ||
123 | + tempMorps.push( analysisResult.morp.slice( word.begin, word.end + 1 ) ); | ||
124 | + }); | ||
125 | + | ||
126 | + tempResult.originalMorp = analysisResult.morp; | ||
127 | + [ tempResult.needMorp, tempResult.noNeedMorp ] = await divideMorpbyMean( tempMorps ); | ||
128 | + await makeKeyword( result, analysisResult.word, tempResult.needMorp ); | ||
129 | + result.morps = tempResult; | ||
130 | +} | ||
131 | + | ||
132 | +/** | ||
133 | + * @param {Object} clientData - 클라이언트에서 받아온 데이터 | ||
134 | + * @param {String} clientData.text - 분석할 텍스트 | ||
135 | + * @returns {Object} 분석 결과 데이터 | ||
136 | + * @description 클라이언트 데이터를 받아 의미를 분석하고 맞춤법을 교정해 돌려줍니다. | ||
137 | + */ | ||
138 | +const textAnalystic = async ( clientData ) => { | ||
139 | + let result = { "originalText" : clientData.text }, | ||
140 | + fixedClientData, | ||
141 | + textAnalystic; | ||
142 | + | ||
143 | + fixedClientData = await apiRequest.Korean( result.originalText ); | ||
144 | + | ||
145 | + result.korean = fixedClientData; | ||
146 | + result.fixedText = result.korean.notag_html; | ||
147 | + try { | ||
148 | + textAnalystic = await apiRequest.ETRI( "WiseNLU", { "analysis_code" : "ner", "text" : result.fixedText } ); | ||
149 | + } | ||
150 | + catch( err ) { | ||
151 | + throw new Error( err.message ); | ||
152 | + } | ||
153 | + | ||
154 | + await divideMorp( result, textAnalystic.return_object.sentence[ 0 ] ); | ||
155 | + return result; | ||
156 | +} | ||
157 | + | ||
158 | +module.exports = textAnalystic; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment