bluejoyq

Merge branch 'apiPR' into 'master'

Api pr

complete
apiReaquest.js
cliConnection.js
textAnalystic.js

add description

See merge request !3
// api key
require('dotenv').config();
// api key error check
try {
if( process.env.TEST != "OKAY" ) {
throw new Error( "키 오류 키파일을 확인하세요" );
}
}
catch( err ) {
throw new Error(err);
}
const rp = require("request-promise");
// Api URL
const URL = {
"ETRI" : "http://aiopen.etri.re.kr:8000/",
"Korean" : "https://search.naver.com/p/csearch/ocontent/util/SpellerProxy?_callback=&color_blindness=0&q="
}
// ETRI Api Request Format
const apiRequestJsonFrame = {
"request_id" : "reserved field",
"access_key" : process.env.ETRI_API_KEY,
"argument" : {}
};
let apiRequest = {};
/**
* @param {String} query 세부 url / 형식은 api사이트 참조
* @param {Object} argument 필요한 argument / 형식은 api사이트 참조
* @returns {Object} api사이트에서 정해진 형식의 응답을 받아옵니다.
* @description 이 함수는 이미 정해진 url(etri api)+query의
경로로 argument와 함께 request를 보냅니다.
그 후 얻은 응답을 js object로 보내줍니다.
*/
apiRequest.ETRI = async ( query, argument ) => {
return new Promise( ( resolve, reject ) => {
let apiReqJson = apiRequestJsonFrame;
apiReqJson.argument = argument;
let apiReqOption = { headers: {
"Accept": "application/json",
"Content-Type": "application/json",
},uri : URL.ETRI + query, body : JSON.stringify( apiReqJson ) };
rp.post( apiReqOption )
.then( ( body ) => {
body = JSON.parse( body );
if( body.result == "-1" ) {
throw new Error( body.reason );
}
resolve( body );
})
.catch( ( err ) => {
throw new Error( err );
});
})
}
/**
* @param {String} text 고치고 싶은 문장
* @returns {Object} 정해진 형식의 응답을 보내줍니다.
* @description 네이버 맞춤법 사이트로 text를 보내서 응답을 받아옵니다.
*/
apiRequest.Korean = async ( text ) => {
return new Promise( ( resolve,reject ) => {
rp( { "uri" : URL.Korean+encodeURI( text ) } )
.then( ( body ) => {
body = body.substring( 1, body.length - 2 );
resolve( JSON.parse( body ).message.result );
})
.catch( ( err ) => {
throw new Error( err );
});
});
}
apiRequest.multiETRI = async ( searchResults, keywordText ) => {
try {
const Promises = await searchResults.map((searchResult, index)=>{
//return makeOption( searchResults, keywordText, index );
return DOCVECAPI( searchResults, keywordText, index );
});
await Promise.all( Promises );
}
catch ( err ) {
throw new Error( err.message );
}
}
/**
* @param {String} keywordText 사용자의 검색에서 textAnalystic을 거쳐 keyword만 남은 text
* @param {{title:string,passage:string,ulr:string}} searchResult searchResults keywordtext문장을 검색하여 나온 결과들
* @returns confidence key와 해당 value가 추가된 searchResults
* @description DOCVECAPI를 이용해 각각의 searchResult의 keywordtext에 대한 정확도(confidence, 검색문장에 대해 검색 결과가 얼마나 지)를
* 판단하고, confidence key와 해당 value를 searchResult에 추가해줍니다.
*/
const DOCVECAPI = (searchResults, keywordText, index) => {
return new Promise((resolve, reject) => {
apiReqOption = {
method: "POST",
uri: "http://127.0.0.1:5000/analyze",
body: {
sentence1: searchResults[index].passage,
sentence2: keywordText
},
json: true
};
rp.post(apiReqOption)
.then(body => {
if (body.result == "-1") {
throw new Error(body.data + " index : " + index);
}
searchResults[index].confidence = Number(body.result);
resolve();
})
.catch(err => {
searchResults[index].confidence = 0;
resolve();
});
});
};
module.exports = apiRequest;
const textAnalytic = require("./textAnalystic");
const search = require("./search");
const machineRead = require("./machineRead");
/**
* @param req - request
* @param req.body.data - client에서 보내는 데이터 req.body.data.text에 검색할 문장을 담아야 합니다
* @description client와 데이터를 받아 통신하는 함수입니다
*/
const cliConnection = async (req, res) => {
let clientData = {},
analyzeData = {},
searchData = [];
// clientData
try {
clientData = req.body.data;
if( !clientData.text.replace( /\s/g, '' ).length ) {
throw new Error( "client text empty" );
}
}
catch ( err ) {
console.log( err );
res.json( { "return_code" : -1, "error_code" : err.message } );
res.status( 403 );
return false;
}
// analyzeData
try {
analyzeData = await textAnalytic( clientData );
}
catch ( err ) {
console.log( err );
res.json( { "return_code" : -1, "error_code" : err.message } );
res.status( 502 );
return false;
}
// searchData
searchData = searchData[ 0 ].concat( searchData[ 1 ] );
try {
searchData = await machineRead( searchData, analyzeData.keywordText );
}
catch ( err ) {
console.log( err );
res.json( { "return_code" : -1, "error_code" : err.message } );
res.status( 502 );
return false;
}
analyzeData.searchResults = searchData;
res.send({ return_code: 0, return_data: analyzeData });
res.status(200);
};
module.exports = cliConnection;
\ No newline at end of file
const apiRequest = require('./apiRequest');
const allowMorpChecklist = [ "NNG","NNP","NNB","VA","MM","MAG","SL","SH","SN","XPN","XSN","XSA","ETM","NOG" ];
const vvMorpChecklist = ["ETM","ETN"]; // 명사형 전성어미(ex) '-(으)ㅁ', '-기'), 관형사형 전성어미(ex) '-ㄴ', '-', '-던', '-ㄹ'), 지정자(VCP,VCN, ex) '-이다', '-아니다')
const npMorpCecklist = ['어디','언제']; // 필요한 의문사 리스트
/**
* @param {{lemma:string, position:number, type:string}[]} word - 한 단어의 형태소 ex) [{걸리},{었},{을}]
* @param {{lemma:string, position:number, type:string}[][]} needMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]
* @param {{lemma:string, position:number, type:string}[][]} noNeedMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]
* @description word의 각 형태소의 type이 allowMorpChecklist에 있는지 확인하고 있으면 needMorp, 없으면 noNeedMorp에 추가합니다.
*/
const checkMorp = ( word, needMorp, noNeedMorp ) => {
let needMorpTemp = [],
noNeedMorpTemp = [];
word.forEach( ( morp ) => {
if( allowMorpChecklist.indexOf( morp.type ) !== -1 ) {
needMorpTemp.push( morp );
} else if(npMorpCecklist.indexOf( morp.lemma ) !== -1 ) {
needMorpTemp.push( morp );
}
else {
noNeedMorpTemp.push( morp );
}
});
if( noNeedMorpTemp.length > 0) {
noNeedMorp.push( noNeedMorpTemp );
}
if( needMorpTemp.length > 0) {
needMorp.push( needMorpTemp );
}
}
/**
* @param {{lemma:string, position:number, type:string}[][]} tempMorps - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]]
* @returns {{needMorp : {}[][], noNeedMorp : {}[][]}} morp를 needMorp와 noNeedMorp로 나눴습니다.
* @description 공백 단위로 나뉜 morp를 받아 type과 의미에 따라 2가지로 분류합니다.
*/
const divideMorpbyMean = ( tempMorps ) => {
let needMorp = [],
noNeedMorp = [];
tempMorps.forEach( ( word, j ) => {
if( word[ 0 ].type === "VV" || word[ 0 ].type === "VA" || word[ 0 ].type === "MAG") { // 동사, 형용사, 부사
let checkV = true,
checkM = true;
word.find( ( Morp ) => {
if( Morp.type === "EF" ) { // 종결어미
checkV = false;
} else if( Morp.type === "EC" ) { // 연결어미
if( tempMorps.length > j + 1 ) {
tempMorps[ j + 1 ].forEach( ( morp ) => {
if( allowMorpChecklist.indexOf( morp.type ) === -1 ) {
checkV = false;
}
});
}
} else if( word[ 0 ].type === "MAG") {
if( Morp.type === "XSV" ) { // 동사파생 접미사 ex) -하다
checkM = false;
}
}
});
if( checkV && checkM) {
needMorp.push( word );
} else {
noNeedMorp.push( word );
}
}
else {
checkMorp( word, needMorp, noNeedMorp );
}
});
return [ needMorp, noNeedMorp ];
}
/**
* @param {String} result - 결과 담던거
* @param {{text : string, begin : number, end : number }[]} words 단어 분석 결과를 담는 어레이
* @param {{lemma:string, position:number, type:string, id : number}[][]} needMorp - 공백 단위로 묶어둠 ex) [[{감기}],[{걸리},{었},{을}],[{때}]]
* @returns {String} 필요한 단어만 남겨둔 문장입니다.
* @description 필요한 morp와 원문 텍스트를 이용해 문장에서의 키워드를 분석해 문장으로 만들어 줍니다.
*/
const makeKeyword = ( result, words, needMorp ) => {
let keywordText = "";
needMorp.forEach( ( morps ) => {
words.forEach( ( word ) => {
if( word.begin === morps[ 0 ].id ){
let tempByte = morps[ morps.length - 1 ].position - morps[0].position + Buffer.byteLength( morps[ morps.length - 1 ].lemma );
for( let ch of word.text ) {
if( tempByte > 0 ) {
keywordText += ch;
tempByte -= Buffer.byteLength(ch)
}
}
}
});
keywordText += " ";
});
result.keywordText = keywordText.trim();
}
/**
* @param {String} result - 결과 담던거
* @param {{NE : {}[], Morp : {}[]}} analysisResult 분석 결과가 담겼습니다.
* @description morp를 처리하는 함수 입니다 ^^
*/
const divideMorp = async ( result, analysisResult ) => {
let tempResult = {},
tempMorps = [];
analysisResult.NE.forEach( ( word ) => {
analysisResult.morp.forEach( ( morp, index ) => {
if( word.begin <= index && word.end >= index ) {
morp.type = "NOG";
}
});
});
analysisResult.word.forEach( ( word ) => {
tempMorps.push( analysisResult.morp.slice( word.begin, word.end + 1 ) );
});
tempResult.originalMorp = analysisResult.morp;
[ tempResult.needMorp, tempResult.noNeedMorp ] = await divideMorpbyMean( tempMorps );
await makeKeyword( result, analysisResult.word, tempResult.needMorp );
result.morps = tempResult;
}
/**
* @param {Object} clientData - 클라이언트에서 받아온 데이터
* @param {String} clientData.text - 분석할 텍스트
* @returns {Object} 분석 결과 데이터
* @description 클라이언트 데이터를 받아 의미를 분석하고 맞춤법을 교정해 돌려줍니다.
*/
const textAnalystic = async ( clientData ) => {
let result = { "originalText" : clientData.text },
fixedClientData,
textAnalystic;
fixedClientData = await apiRequest.Korean( result.originalText );
result.korean = fixedClientData;
result.fixedText = result.korean.notag_html;
try {
textAnalystic = await apiRequest.ETRI( "WiseNLU", { "analysis_code" : "ner", "text" : result.fixedText } );
}
catch( err ) {
throw new Error( err.message );
}
await divideMorp( result, textAnalystic.return_object.sentence[ 0 ] );
return result;
}
module.exports = textAnalystic;
\ No newline at end of file