bluejoyq

Merge branch 'apiPR' into 'master'

Api pr

complete
apiReaquest.js
cliConnection.js
textAnalystic.js

add description

See merge request !3
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;
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
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