@chevre/domain
Version:
Chevre Domain Library for Node.js
443 lines (442 loc) • 22.9 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryPurposeType = exports.TelemetryScope = void 0;
exports.searchGlobalFlow = searchGlobalFlow;
exports.searchGlobalStock = searchGlobalStock;
exports.searchSellerFlow = searchSellerFlow;
exports.searchSellerStock = searchSellerStock;
exports.search = search;
exports.createFlow = createFlow;
exports.createStock = createStock;
const createDebug = require("debug");
const moment = require("moment");
const factory = require("../../factory");
const debug = createDebug('chevre-domain:service');
const TELEMETRY_UNIT_OF_MEASUREMENT_IN_SECONDS = 60; // 測定単位時間(秒)
var TelemetryScope;
(function (TelemetryScope) {
TelemetryScope["Global"] = "Global";
TelemetryScope["Seller"] = "Seller";
})(TelemetryScope || (exports.TelemetryScope = TelemetryScope = {}));
var TelemetryPurposeType;
(function (TelemetryPurposeType) {
TelemetryPurposeType["Flow"] = "Flow";
TelemetryPurposeType["Stock"] = "Stock";
})(TelemetryPurposeType || (exports.TelemetryPurposeType = TelemetryPurposeType = {}));
function searchGlobalFlow(searchConditions) {
return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Global, purpose: TelemetryPurposeType.Flow }));
}
function searchGlobalStock(searchConditions) {
return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Global, purpose: TelemetryPurposeType.Stock }));
}
function searchSellerFlow(searchConditions) {
return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Seller, purpose: TelemetryPurposeType.Flow }));
}
function searchSellerStock(searchConditions) {
return search(Object.assign(Object.assign({}, searchConditions), { scope: TelemetryScope.Seller, purpose: TelemetryPurposeType.Stock }));
}
/**
* 計測データを検索する
* @param searchConditions.measuredFrom 計測日時from
* @param searchConditions.measuredThrough 計測日時through
*/
function search(searchConditions) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
return repos.telemetry.telemetryModel.find({
'object.scope': {
$exists: true,
$eq: searchConditions.scope
},
'object.measuredAt': {
$exists: true,
$gte: searchConditions.measuredFrom,
$lt: searchConditions.measuredThrough
},
'purpose.typeOf': {
$exists: true,
$eq: searchConditions.purpose
}
})
.sort({ 'object.measuredAt': 1 })
.lean()
.exec();
});
}
/**
* フロー測定データを作成する
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createFlow(target) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
const startDate = new Date();
const measuredThrough = moment(target.measuredAt);
const measuredFrom = moment(measuredThrough)
.add(-TELEMETRY_UNIT_OF_MEASUREMENT_IN_SECONDS, 'seconds');
let telemetry;
if (target.sellerId !== undefined) {
const flowData = yield createSellerFlow(measuredFrom.toDate(), measuredThrough.toDate(), target.sellerId)({
transaction: repos.transaction,
action: repos.action
});
debug('flowData created.');
telemetry = {
purpose: { typeOf: TelemetryPurposeType.Flow },
object: {
scope: TelemetryScope.Seller,
measuredAt: target.measuredAt,
sellerId: target.sellerId
},
result: flowData,
startDate: startDate,
endDate: new Date()
};
}
else {
const flowData = yield createGlobalFlow(measuredFrom.toDate(), measuredThrough.toDate())({
task: repos.task
});
debug('flowData created.');
telemetry = {
purpose: { typeOf: TelemetryPurposeType.Flow },
object: {
scope: TelemetryScope.Global,
measuredAt: target.measuredAt
},
result: flowData,
startDate: startDate,
endDate: new Date()
};
}
yield repos.telemetry.telemetryModel.create(telemetry);
debug('telemetry saved.');
});
}
/**
* ストック測定データを作成する
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createStock(target) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
const startDate = new Date();
let telemetry;
if (target.sellerId !== undefined) {
const stockData = yield createSellerStock(target.measuredAt, target.sellerId)({
transaction: repos.transaction
});
debug('stockData created.');
telemetry = {
purpose: { typeOf: TelemetryPurposeType.Stock },
object: {
scope: TelemetryScope.Seller,
measuredAt: target.measuredAt,
sellerId: target.sellerId
},
result: stockData,
startDate: startDate,
endDate: new Date()
};
}
else {
const stockData = yield createGlobalStock(target.measuredAt)({ task: repos.task });
debug('stockData created.');
telemetry = {
purpose: { typeOf: TelemetryPurposeType.Stock },
object: {
scope: TelemetryScope.Global,
measuredAt: target.measuredAt
},
result: stockData,
startDate: startDate,
endDate: new Date()
};
}
yield repos.telemetry.telemetryModel.create(telemetry);
debug('telemetry saved.');
});
}
/**
* フロー計測データーを作成する
* @param measuredFrom 計測開始日時
* @param measuredThrough 計測終了日時
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createSellerFlow(measuredFrom, measuredThrough, sellerId) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
const { sellerFlowTransactionResult, expiredTransactionIds } = yield createSellerFlowTransactionResult(measuredFrom, measuredThrough, sellerId)(repos);
// 期限切れ取引数
const numberOfTransactionsExpired = sellerFlowTransactionResult.numberOfExpired;
// 期限切れ取引に対して作成されたアクションを取得
const actionsOnExpiredTransactions = yield repos.action.search({
typeOf: { $eq: factory.actionType.AuthorizeAction },
purpose: { id: { $in: expiredTransactionIds } }
}, []);
debug(actionsOnExpiredTransactions.length, 'actionsOnExpiredTransactions found.');
const numbersOfActionsOnExpired = expiredTransactionIds.map((transactionId) => {
return actionsOnExpiredTransactions.filter((action) => { var _a; return ((_a = action.purpose) === null || _a === void 0 ? void 0 : _a.id) === transactionId; }).length;
});
const totalNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => a + b, 0);
const maxNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => Math.max(a, b), 0);
const minNumberOfActionsOnExpired = numbersOfActionsOnExpired.reduce((a, b) => Math.min(a, b), (numberOfTransactionsExpired > 0) ? numbersOfActionsOnExpired[0] : 0);
const averageNumberOfActionsOnExpired = (numberOfTransactionsExpired > 0) ? totalNumberOfActionsOnExpired / numberOfTransactionsExpired : 0;
return {
transactions: Object.assign(Object.assign({}, sellerFlowTransactionResult), { totalNumberOfActionsOnExpired: totalNumberOfActionsOnExpired, maxNumberOfActionsOnExpired: maxNumberOfActionsOnExpired, minNumberOfActionsOnExpired: minNumberOfActionsOnExpired, averageNumberOfActionsOnExpired: parseFloat(averageNumberOfActionsOnExpired.toFixed(1)),
// tslint:disable-next-line:no-suspicious-comment
totalNumberOfOrderItems: 0,
// tslint:disable-next-line:no-suspicious-comment
maxNumberOfOrderItems: 0,
// tslint:disable-next-line:no-suspicious-comment
minNumberOfOrderItems: 0,
// tslint:disable-next-line:no-suspicious-comment
averageNumberOfOrderItems: 0 // TODO 実装
}),
measuredFrom: measuredFrom,
measuredThrough: measuredThrough
};
});
}
function createSellerFlowTransactionResult(measuredFrom, measuredThrough, sellerId) {
// tslint:disable-next-line:max-func-body-length
return (repos) => __awaiter(this, void 0, void 0, function* () {
// 計測期間内に開始された取引数を算出する
const { count } = yield repos.transaction.count({
typeOf: factory.transactionType.PlaceOrder,
seller: { ids: [sellerId] },
startFrom: measuredFrom,
startThrough: measuredThrough
});
const numberOfTransactionsStarted = count;
// 計測期間内に開始され、かつ、すでに終了している取引を検索
const startedAndEndedTransactions = yield repos.transaction.projectFields({
typeOf: factory.transactionType.PlaceOrder,
seller: { ids: [sellerId] },
startFrom: measuredFrom,
startThrough: measuredThrough,
endThrough: new Date(),
inclusion: ['status']
});
const numberOfStartedAndConfirmed = startedAndEndedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Confirmed).length;
const numberOfStartedAndExpired = startedAndEndedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Expired).length;
const endedTransactions = yield repos.transaction.projectFields({
typeOf: factory.transactionType.PlaceOrder,
seller: { ids: [sellerId] },
endFrom: measuredFrom,
endThrough: measuredThrough,
inclusion: ['status', 'endDate', 'startDate', 'result']
});
debug(endedTransactions.length, 'endedTransactions found.');
const confirmedTransactions = endedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Confirmed);
const expiredTransactions = endedTransactions.filter((transaction) => transaction.status === factory.transactionStatusType.Expired);
const numberOfTransactionsConfirmed = confirmedTransactions.length;
// 所要時間算出(期間の成立取引リストを取得し、開始時刻と成立時刻の差を所要時間とする)
const requiredTimesConfirmed = confirmedTransactions.map((transaction) => moment(transaction.endDate)
.diff(moment(transaction.startDate, 'milliseconds')));
const totalRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => a + b, 0);
const maxRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => Math.max(a, b), 0);
const minRequiredTimeInMilliseconds = requiredTimesConfirmed.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? requiredTimesConfirmed[0] : 0);
const averageRequiredTimeInMilliseconds = (numberOfTransactionsConfirmed > 0) ? totalRequiredTimeInMilliseconds / numberOfTransactionsConfirmed : 0;
// const totalTimeLeftUntilEventInMilliseconds = timesLeftUntilEvent.reduce((a, b) => a + b, 0);
// const maxTimeLeftUntilEventInMilliseconds = timesLeftUntilEvent.reduce((a, b) => Math.max(a, b), 0);
// const minTimeLeftUntilEventInMilliseconds =
// timesLeftUntilEvent.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? timesLeftUntilEvent[0] : 0);
// const averageTimeLeftUntilEventInMilliseconds =
// (numberOfTransactionsConfirmed > 0) ? totalTimeLeftUntilEventInMilliseconds / numberOfTransactionsConfirmed : 0;
// 金額算出
const amounts = confirmedTransactions.map((transaction) => transaction.result.order.price);
const totalAmount = amounts.reduce((a, b) => a + b, 0);
const maxAmount = amounts.reduce((a, b) => Math.max(a, b), 0);
const minAmount = amounts.reduce((a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? amounts[0] : 0);
const averageAmount = (numberOfTransactionsConfirmed > 0) ? totalAmount / numberOfTransactionsConfirmed : 0;
// アクション数集計
// const numbersOfActions = confirmedTransactions.map((t) => t.object.authorizeActions.length);
// const totalNumberOfActions = numbersOfActions.reduce((a, b) => a + b, 0);
// const maxNumberOfActions = numbersOfActions.reduce((a, b) => Math.max(a, b), 0);
// const minNumberOfActions = numbersOfActions.reduce(
// (a, b) => Math.min(a, b), (numberOfTransactionsConfirmed > 0) ? numbersOfActions[0] : 0
// );
// const averageNumberOfActions = (numberOfTransactionsConfirmed > 0) ? totalNumberOfActions / numberOfTransactionsConfirmed : 0;
// 期限切れ取引数
const numberOfTransactionsExpired = expiredTransactions.length;
const expiredTransactionIds = expiredTransactions.map((transaction) => transaction.id);
return {
sellerFlowTransactionResult: {
numberOfStarted: numberOfTransactionsStarted,
numberOfStartedAndConfirmed: numberOfStartedAndConfirmed,
numberOfStartedAndExpired: numberOfStartedAndExpired,
numberOfConfirmed: numberOfTransactionsConfirmed,
numberOfExpired: numberOfTransactionsExpired,
totalRequiredTimeInMilliseconds: totalRequiredTimeInMilliseconds,
maxRequiredTimeInMilliseconds: maxRequiredTimeInMilliseconds,
minRequiredTimeInMilliseconds: minRequiredTimeInMilliseconds,
averageRequiredTimeInMilliseconds: parseFloat(averageRequiredTimeInMilliseconds.toFixed(1)),
// totalTimeLeftUntilEventInMilliseconds: totalTimeLeftUntilEventInMilliseconds,
// maxTimeLeftUntilEventInMilliseconds: maxTimeLeftUntilEventInMilliseconds,
// minTimeLeftUntilEventInMilliseconds: minTimeLeftUntilEventInMilliseconds,
// averageTimeLeftUntilEventInMilliseconds: averageTimeLeftUntilEventInMilliseconds,
totalAmount: totalAmount,
maxAmount: maxAmount,
minAmount: minAmount,
averageAmount: parseFloat(averageAmount.toFixed(1)),
// totalNumberOfActionsOnConfirmed: totalNumberOfActions,
// maxNumberOfActionsOnConfirmed: maxNumberOfActions,
// minNumberOfActionsOnConfirmed: minNumberOfActions,
// averageNumberOfActionsOnConfirmed: parseFloat(averageNumberOfActions.toFixed(1)),
// tslint:disable-next-line:no-suspicious-comment
totalNumberOfOrderItems: 0, // TODO 実装
// tslint:disable-next-line:no-suspicious-comment
maxNumberOfOrderItems: 0, // TODO 実装
// tslint:disable-next-line:no-suspicious-comment
minNumberOfOrderItems: 0, // TODO 実装
// tslint:disable-next-line:no-suspicious-comment
averageNumberOfOrderItems: 0 // TODO 実装
},
expiredTransactionIds
};
});
}
/**
* ストック計測データを作成する
* @param measuredAt 計測日時
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createSellerStock(measuredAt, sellerId) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
// {measuredAt}以前に開始し、{measuredAt}以後に成立あるいは期限切れした取引
const { count } = yield repos.transaction.count({
typeOf: factory.transactionType.PlaceOrder,
seller: { ids: [sellerId] },
startThrough: measuredAt,
endFrom: measuredAt
});
const numTransactionsUnderway1 = count;
// {measuredAt}以前に開始し、いまだに進行中の取引
const count2Result = yield repos.transaction.count({
typeOf: factory.transactionType.PlaceOrder,
seller: { ids: [sellerId] },
startThrough: measuredAt,
statuses: [factory.transactionStatusType.InProgress]
});
const numTransactionsUnderway2 = count2Result.count;
const numberOfTransactionsUnderway = numTransactionsUnderway1 + numTransactionsUnderway2;
debug('numberOfTransactionsUnderway:', numberOfTransactionsUnderway);
return {
transactions: {
numberOfUnderway: numberOfTransactionsUnderway
},
measuredAt: measuredAt
};
});
}
/**
* フロー計測データーを作成する
* @param measuredFrom 計測開始日時
* @param measuredThrough 計測終了日時
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createGlobalFlow(measuredFrom, measuredThrough) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
// 全タスク名リスト
const targetTaskNames = Object.keys(factory.taskName)
.map((k) => factory.taskName[k]);
const taskResults = yield Promise.all(targetTaskNames.map((taskName) => __awaiter(this, void 0, void 0, function* () {
const numberOfTasksCreated = yield repos.task.taskModel.countDocuments({
name: taskName,
createdAt: {
$gte: measuredFrom,
$lt: measuredThrough
}
})
.exec();
debug('numberOfTasksCreated:', numberOfTasksCreated);
// 実行中止ステータスで、最終試行日時が範囲にあるものを実行タスク数とする
const numberOfTasksAborted = yield repos.task.taskModel.countDocuments({
name: taskName,
lastTriedAt: {
$type: 'date',
$gte: measuredFrom,
$lt: measuredThrough
},
status: factory.taskStatus.Aborted
})
.exec();
debug('numberOfTasksAborted:', numberOfTasksAborted);
// 実行済みステータスで、最終試行日時が範囲にあるものを実行タスク数とする
const executedTasks = yield repos.task.taskModel.find({
name: taskName,
lastTriedAt: {
$type: 'date',
$gte: measuredFrom,
$lt: measuredThrough
},
status: factory.taskStatus.Executed
}, 'runsAt lastTriedAt numberOfTried')
.exec()
.then((docs) => docs.map((doc) => doc.toObject()));
debug(executedTasks.length, 'executedTasks found.');
const numberOfTasksExecuted = executedTasks.length;
const latencies = executedTasks.map((task) => moment(task.lastTriedAt)
.diff(moment(task.runsAt, 'milliseconds')));
const totalLatency = latencies.reduce((a, b) => a + b, 0);
const maxLatency = latencies.reduce((a, b) => Math.max(a, b), 0);
const minLatency = latencies.reduce((a, b) => Math.min(a, b), (numberOfTasksExecuted > 0) ? latencies[0] : 0);
const numbersOfTrials = yield Promise.all(executedTasks.map((task) => task.numberOfTried));
const totalNumberOfTrials = numbersOfTrials.reduce((a, b) => a + b, 0);
const maxNumberOfTrials = numbersOfTrials.reduce((a, b) => Math.max(a, b), 0);
const minNumberOfTrials = numbersOfTrials.reduce((a, b) => Math.min(a, b), (numberOfTasksExecuted > 0) ? numbersOfTrials[0] : 0);
return {
name: taskName,
numberOfCreated: numberOfTasksCreated,
numberOfExecuted: numberOfTasksExecuted,
numberOfAborted: numberOfTasksAborted,
totalLatencyInMilliseconds: totalLatency,
maxLatencyInMilliseconds: maxLatency,
minLatencyInMilliseconds: minLatency,
totalNumberOfTrials: totalNumberOfTrials,
maxNumberOfTrials: maxNumberOfTrials,
minNumberOfTrials: minNumberOfTrials
};
})));
return {
tasks: taskResults,
measuredFrom: measuredFrom,
measuredThrough: measuredThrough
};
});
}
/**
* ストック計測データを作成する
* @param measuredAt 計測日時
*/
// tslint:disable-next-line:no-single-line-block-comment
/* istanbul ignore next */
function createGlobalStock(measuredAt) {
return (repos) => __awaiter(this, void 0, void 0, function* () {
// 待機状態のタスク数を算出
debug('counting waiting tasks globally...');
const numberOfTasksUnexecuted = yield repos.task.taskModel.countDocuments({
runsAt: { $lt: measuredAt }, // 実行日時を超過している
status: { $in: [factory.taskStatus.Ready, factory.taskStatus.Running] }
})
.exec();
debug('global waiting tasks count', numberOfTasksUnexecuted);
return {
tasks: {
numberOfUnexecuted: numberOfTasksUnexecuted
},
measuredAt: measuredAt
};
});
}
;