1
0
Fork 0

Save rating data to another database field

This maintains the old behavior while also writing rating result to another
field in the database.
master
Mike Gerwitz 2019-10-29 15:24:54 -04:00
commit d2f9f5f18f
31 changed files with 1933 additions and 1015 deletions

View File

@ -341,14 +341,17 @@ If this is a concern,
in conjunction with ease.js'
@url{https://www.gnu.org/software/easejs/manual/easejs.html#Type-Checks-and-Polymorphism,@samp{Class.isA}}.
Interfaces do not exist at runtime in TypeScript,
but they do in easejs.
Consequently,
you can continue to export an ease.js interface while also exporting
a TypeScript interface.
Often times you will need to reference a class or interface as a
dependency before it has been migrated away from ease.js.
To do this,
continue to export using @samp{module.exports} rather than
TypeScript's @samp{export =}.
create a corresponding @code{.d.ts} file in the same directory
as the dependency.
For example,
if a class @code{Foo} is contained in @file{Foo.js},
create a sibling @file{Foo.d.ts} file.
For more information,
see @url{https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html,Declaration Files}
in the TypeScript handbook.
ease.js implements stackable Scala-like traits.
Traits are @emph{not} provided by TypeScript.
@ -446,7 +449,7 @@ This can be done using
@verbatim
type PositiveInteger = NominalType<number, 'PositiveInteger'>;
const isPositiveInteger = ( x: number ): n is PositiveInteger => n > 0;
const isPositiveInteger = ( n: number ): n is PositiveInteger => n > 0;
const lookupIndex<T>( arr: T[], i: PositiveInteger ): T => arr[ i ];

View File

@ -0,0 +1,39 @@
/**
* Representation of actions to be performed by the client
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Action to be performed by the client
*
* TODO: More specific types
*/
export interface ClientAction
{
/** Action to be performed */
action: string;
/** Action arguments */
[P: string]: any;
}
/** Set of actions */
export type ClientActions = ClientAction[];

43
src/numeric.ts 100644
View File

@ -0,0 +1,43 @@
/**
* Numeric types
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* TypeScript's type system does not support algebraic numeric domains. A
* compromise is to provide nominal types that allow developers to assume
* that some constraint has been met, and then ensure that the type is only
* ever asserted when that constraint is explicitly validated at
* runtime. This allows us to have compile-time checks on numeric values
* under the assumption that the runtime will enforce them.
*
* For this to work, _it is important to always use type predicates_;
* if you explicit cast to one of these numeric types, it circumvents the
* safety provided by the system and may introduce nasty bugs, since users
* of these types assume the provided data has already been validated.
*/
/**
* Any number 0
*
* This is useful for array indexing.
*/
export type PositiveInteger = NominalType<number, 'PositiveInteger'>;
/** Whether the given number is suitable as a PositiveInteger */
export const isPositiveInteger = ( n: number ): n is PositiveInteger => n >= 0;

27
src/program/Program.d.ts vendored 100644
View File

@ -0,0 +1,27 @@
/**
* Contains Program base class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export declare abstract class Program
{
readonly ineligibleLockCount: number;
getId(): string;
}

93
src/quote/BaseQuote.d.ts vendored 100644
View File

@ -0,0 +1,93 @@
/**
* Contains program Quote class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../program/Program";
import { Quote, QuoteId } from "./Quote";
export declare class BaseQuote implements Quote
{
/**
* Retrieve Program associated with quote
*
* @return quote program
*/
getProgram(): Program;
/**
* Returns the program id associated with the quote
*
* @return program id
*/
getProgramId(): string;
/**
* Returns the quote id
*
* The quote id is immutable. A different quote id would represent a
* different quote, therefore a new object should be created with the
* desired quote id.
*
* @return quote id
*/
getId(): QuoteId;
/**
* Returns the id of the current step
*
* @return id of current step
*/
getCurrentStepId(): number;
/**
* Sets an explicit lock, providing a reason for doing so
*
* @param reason - lock reason
* @param step - step that user may not navigate prior
*
* @return self
*/
setExplicitLock( reason: string, step: number ): this;
/**
* Set the date that the premium was calculated as a Unix timestamp
*
* @param timestamp - Unix timestamp representing premium date
*
* @return self
*/
setLastPremiumDate( timestamp: UnixTimestamp ): this;
/**
* Retrieve the last time the premium was calculated
*
* @return last calculated time or 0
*/
getLastPremiumDate(): UnixTimestamp;
}

33
src/quote/Quote.d.ts vendored 100644
View File

@ -0,0 +1,33 @@
/**
* Generic interface to represent quotes
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../program/Program";
import { QuoteId } from "../document/Document";
export declare interface Quote
{
// TODO: the easejs interface is empty; is this actually needed?
}
export { QuoteId };

66
src/server/Server.d.ts vendored 100644
View File

@ -0,0 +1,66 @@
/**
* General server actions
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClientActions } from "../client/action/ClientAction";
import { ServerSideQuote } from "./quote/ServerSideQuote";
import { UserRequest } from "./request/UserRequest";
/**
* General server actions
*/
export declare class Server
{
/**
* Send response to user
*
* @param request - request to respond to
* @param quote - quote associated with request
* @param data - data with which to reply
* @param actions - optional client actions
*
* @return self
*/
sendResponse(
request: UserRequest,
quote: ServerSideQuote,
data: Record<string, any>,
actions?: ClientActions,
): this;
/**
* Send response to user
*
* @param request - request to respond to
* @param message - message to display to user
* @param actions - optional client actions
* @param btn_caption - optional caption for acknowledgement button
*
* @return self
*/
sendError(
request: UserRequest,
message: string,
actions?: ClientActions,
btn_caption?: string,
): this;
}

View File

@ -28,6 +28,8 @@ const {
ReplSetServers: ReplSetServers,
} = require( 'mongodb/lib/mongodb' );
const easejs = require( 'easejs' );
const regex_base = /^\/quote\/([a-z0-9-]+)\/?(?:\/(\d+)\/?(?:\/(.*))?|\/(program.js))?$/;
const regex_step = /^step\/(\d+)\/?(?:\/(post|visit))?$/;
@ -83,9 +85,8 @@ const {
ExportService,
},
RatingService,
RatingService: { RatingService },
RatingServicePublish,
RatingServiceSubmitNotify,
TokenedService,
},
@ -137,7 +138,7 @@ exports.init = function( logger, enc_service, conf )
server.init( server_cache, exports.rater );
// TODO: temporary proof-of-concept
rating_service = RatingService.use(
rating_service = easejs( RatingService ).use(
RatingServicePublish( amqplib, exports.post_rate_publish, logger )
)(
logger, dao, server, exports.rater
@ -535,11 +536,9 @@ function doRoute( program, request, data, resolve, reject )
{
var response = UserResponse( request );
rating_service.request( request, response, quote, alias, function()
{
// we're done; free the lock
free();
} );
rating_service.request( request, response, quote, alias )
.catch( () => {} )
.then( () => free() );
} );
}, true );
}

145
src/server/db/MongoServerDao.d.ts vendored 100644
View File

@ -0,0 +1,145 @@
/**
* Mongo DB DAO for program server
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ServerDao, Callback } from "./ServerDao";
import { ClassificationData, WorksheetData } from "../rater/Rater";
import { PositiveInteger } from "../../numeric";
import { QuoteId } from "../../document/Document";
import { ServerSideQuote } from "../quote/ServerSideQuote";
/**
* MongoDB-backed data store
*/
export declare class MongoServerDao implements ServerDao
{
/**
* Saves a quote to the database
*
* A full save will include all metadata.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
* @param save_data - quote data to save (optional)
*/
saveQuote(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
save_data: Record<string, any>,
): this;
/**
* Merges bucket data with the existing bucket (rather than overwriting the
* entire bucket)
*
* @param quote - quote to save
* @param data - bucket data
* @param success - successful callback
* @param failure - failure callback
*/
mergeBucket(
quote: ServerSideQuote,
data: Record<string, any>,
success: Callback,
failure: Callback,
): this;
/**
* Save quote classification data
*
* @param quote - quote to save
* @param classes - classification data
* @param success - successful callback
* @param failure - failure callback
*/
saveQuoteClasses(
quote: ServerSideQuote,
classes: ClassificationData,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote state to the database
*
* The quote state includes the current step, the top visited step and the
* explicit lock message.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote lock state to the database
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteLockState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this
/**
* Save worksheet data
*
* @param qid - quote identifier
* @param data - worksheet data
* @param callback - callback
*/
setWorksheets(
qid: QuoteId,
data: WorksheetData,
callback: NodeCallback<void>,
): this;
/**
* Retrieve worksheet data
*
* @param qid - quote identifier
* @param supplier - supplier id
* @param index - worksheet index
* @param callback - callback
*/
getWorksheet(
qid: QuoteId,
supplier: string,
index: PositiveInteger,
callback: ( data: WorksheetData | null ) => void,
): this;
}

149
src/server/db/ServerDao.d.ts vendored 100644
View File

@ -0,0 +1,149 @@
/**
* General server database operations
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This interface was created to satisfy MongoServerDao and may not be
* sufficiently general for other database abstractions.
*/
import { ClassificationData, WorksheetData } from "../rater/Rater";
import { PositiveInteger } from "../../numeric";
import { QuoteId } from "../../document/Document";
import { ServerSideQuote } from "../quote/ServerSideQuote";
/** Success or failure callback */
export type Callback = ( quote: ServerSideQuote ) => void;
/**
* Database abstraction
*/
export interface ServerDao
{
/**
* Saves a quote to the database
*
* A full save will include all metadata.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
* @param save_data - quote data to save (optional)
*/
saveQuote(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
save_data: Record<string, any>,
): this;
/**
* Merges bucket data with the existing bucket (rather than overwriting the
* entire bucket)
*
* @param quote - quote to save
* @param data - bucket data
* @param success - successful callback
* @param failure - failure callback
*/
mergeBucket(
quote: ServerSideQuote,
data: Record<string, any>,
success: Callback,
failure: Callback,
): this;
/**
* Save quote classification data
*
* @param quote - quote to save
* @param classes - classification data
* @param success - successful callback
* @param failure - failure callback
*/
saveQuoteClasses(
quote: ServerSideQuote,
classes: ClassificationData,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote state to the database
*
* The quote state includes the current step, the top visited step and the
* explicit lock message.
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this;
/**
* Saves the quote lock state to the database
*
* @param quote - the quote to save
* @param success - function to call on success
* @param failure - function to call if save fails
*/
saveQuoteLockState(
quote: ServerSideQuote,
success: Callback,
failure: Callback,
): this
/**
* Save worksheet data
*
* @param qid - quote identifier
* @param data - worksheet data
* @param callback - callback
*/
setWorksheets(
qid: QuoteId,
data: WorksheetData,
callback: NodeCallback<void>,
): this;
/**
* Retrieve worksheet data
*
* @param qid - quote identifier
* @param supplier - supplier id
* @param index - worksheet index
* @param callback - callback
*/
getWorksheet(
qid: QuoteId,
supplier: string,
index: PositiveInteger,
callback: ( data: WorksheetData | null ) => void,
): this;
}

45
src/server/log/PriorityLog.d.ts vendored 100644
View File

@ -0,0 +1,45 @@
/**
* Priority log typescript type definitions
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export declare interface PriorityLog
{
readonly PRIORITY_ERROR: number;
readonly PRIORITY_IMPORTANT: number;
readonly PRIORITY_DB: number;
readonly PRIORITY_INFO: number;
readonly PRIORITY_SOCKET: number;
/**
* Write to the log at the given priority
*
* If the priority is less than or equal to the set priority for this
* object, it will be logged. Otherwise, the message will be ignored.
*
* The first argument should be the priority. The remaining arguments should
* be provided in a sprintf()-style fashion
*
* @param priority - logging priority
* @param ...args - sprintf-style aruments
*
* @return self
*/
log( priority: number, ...args: Array<string|number> ): this;
}

View File

@ -0,0 +1,46 @@
/**
* Augments a quote with additional data for use by the quote server
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @todo Use ``document'' terminology in place of ``quote''
*/
import { Program } from "../../program/Program";
import { BaseQuote } from "../../quote/BaseQuote";
export declare class ServerSideQuote extends BaseQuote
{
/**
* Last rated date, if any
*
* @return last rated date
*/
getRatedDate(): UnixTimestamp;
/**
* Set the timestamp of the first time quote was rated
*
* @param timestamp - Unix timestamp representing first rated date
*
* @return self
*/
setRatedDate( timestamp: UnixTimestamp ): this;
}

View File

@ -20,7 +20,6 @@
*/
var Class = require( 'easejs' ).Class,
Rater = require( './Rater' ),
EventEmitter = require( 'events' ).EventEmitter,
DslRaterContext = require( './DslRaterContext' );

View File

@ -20,7 +20,6 @@
*/
var Class = require( 'easejs' ).Class,
Rater = require( './Rater' ),
EventEmitter = require( 'events' ).EventEmitter,
Quote = require( '../../quote/Quote' );

View File

@ -22,18 +22,14 @@
* "HttpRater"
*/
var Class = require( 'easejs' ).Class,
Rater = require( './Rater' ),
querystring = require( 'querystring' )
;
var Class = require( 'easejs' ).Class;
var querystring = require( 'querystring' );
/**
* Rates using one of the PHP raters
*/
module.exports = Class( 'HttpRater' )
.implement( Rater )
.extend(
{
/**

View File

@ -0,0 +1,35 @@
/**
* Rating process manager
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Rater } from "./Rater";
export declare class ProcessManager
{
/**
* Returns the rater associated with the given id
*
* @param id - rater id
*
* @return requested rater
*/
byId( id: string ): Rater;
}

View File

@ -69,6 +69,9 @@ const _signum = {
*
* Handles formatting and sending requests to the rating process; and
* processing replies.
*
* TODO: Rename this class and provide a more generic interface. The caller
* does not care that this sends data to another process for rating.
*/
module.exports = Class( 'ProcessManager',
{

121
src/server/rater/Rater.d.ts vendored 100644
View File

@ -0,0 +1,121 @@
/**
* Contains Rater interface
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClientActions } from "../../client/action/ClientAction";
import { ServerSideQuote } from "../quote/ServerSideQuote";
import { UserSession } from "../request/UserSession";
/** Result of rating */
export interface RateResult
{
/** Whether all suppliers were not able to provide rates */
_unavailable_all: '0' | '1';
/** Result data */
[P: string]: any;
}
/**
* Result of rating with an individual supplier
*
* This gets combined into a single RateResult prefixed with each supplier
* id and an underscore.
*/
export interface SupplierRateResult
{
/** Rating worksheet data */
__worksheet?: WorksheetData,
/** Classification system results */
__classes?: ClassificationData,
/** Basic profiling data */
__perf: PerformanceData,
/** Ineligible message, if any */
ineligible: string,
/** Submit message, if any */
submit: string,
/** Final premium */
premium: number,
/** Rating data */
[P: string]: any;
}
/** Basic profiling data */
export interface PerformanceData
{
/** Timestamp of beginning of rating */
start: UnixTimestampMillis;
/** Timestamp of end of rating */
end: UnixTimestampMillis;
/** Total rating time */
total: Milliseconds;
}
/**
* Worksheet data from rater
*
* These data come from the compiled raters.
*
* TODO: Fill in a schema here
*/
export type WorksheetData = Record<string, any>;
/** Classification results */
export type ClassificationData = Record<string, boolean>;
/**
* Represents a rater that will generate a quote from a given set of values
*/
export interface Rater
{
/**
* Asynchronously performs rating
*
* @param quote - quote to perform rating on
* @param session - user session
* @param indv - individual supplier to rate (otherwise empty)
* @param success - continuation when rating is successful
* @param failure - continuation when rating fails
*
* @return self
*/
rate(
quote: ServerSideQuote,
session: UserSession,
indv: string,
success: ( rate_data: RateResult, actions: ClientActions ) => void,
failure: ( message: string ) => void,
): this;
}

View File

@ -1,5 +1,5 @@
/**
* Contains Rater interface
* User request abstraction
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
@ -19,22 +19,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Interface = require( 'easejs' ).Interface;
import { UserSession } from "./UserSession";
/**
* Represents a rater that will generate a quote from a given set of values
* Representation of request from user
*/
module.exports = Interface( 'Rater',
export declare class UserRequest
{
/**
* Asynchronously performs rating using the data from the given bucket
* Retrieve the current session
*
* @param {Quote} quote to rate
* @param {function()} callback function to call when complete
*
* @return {Rater} self
* @return current session
*/
'public rate': [ 'quote', 'args', 'callback' ],
} );
getSession(): UserSession;
}

View File

@ -0,0 +1,28 @@
/**
* UserResponse class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Manipulates response to user request
*/
export declare class UserResponse
{
}

View File

@ -0,0 +1,34 @@
/**
* UserSession class
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Session management
*/
export declare class UserSession
{
/**
* Whether the user is logged in as an internal user
*
* @return true if internal user, otherwise false
*/
isInternal(): boolean;
}

View File

@ -1,420 +0,0 @@
/**
* Rating service
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var Class = require( 'easejs' ).Class;
/**
* XXX: Half-assed, quick refactoring to extract from Server class; this is not
* yet complete!
*
* TODO: Logging should be implemented by observers
*/
module.exports = Class( 'RatingService',
{
logger: null,
dao: null,
_server: null,
_raters: null,
__construct: function( logger, dao, server, raters )
{
this._logger = logger;
this._dao = dao;
this._server = server;
this._raters = raters;
},
/**
* Sends rates to the client
*
* Note that the continuation will be called after all data saving is
* complete; the request will be sent back to the client before then.
*
* @param {UserRequest} request user request to satisfy
* @param {UserResponse} response pending response
* @param {Quote} quote quote to export
* @param {string} cmd applicable of command request
* @param {Function} callback continuation after saving is complete
*
* @return Server self to allow for method chaining
*/
'public request': function( request, response, quote, cmd, callback )
{
// cmd represents a request for a single rater
if ( !cmd && this._isQuoteValid( quote ) )
{
// send an empty reply (keeps what is currently in the bucket)
this._server.sendResponse( request, quote, {
data: {},
}, [] );
callback();
return this;
}
var program = quote.getProgram();
try
{
this._performRating( request, program, quote, cmd, callback );
}
catch ( err )
{
this._sendRatingError( request, quote, program, err );
callback();
}
return this;
},
_getProgramRater: function( program, quote )
{
var rater = this._raters.byId( program.getId() );
// if a rater could not be found, we can't do any rating
if ( rater === null )
{
this._logger.log( this._logger.PRIORITY_ERROR,
"Rating for quote %d (program %s) failed; missing module",
quote.getId(),
program.getId()
);
}
return rater;
},
_isQuoteValid: function( quote )
{
// quotes are valid for 30 days
var re_date = Math.round( ( ( new Date() ).getTime() / 1000 ) -
( 60 * 60 * 24 * 30 )
);
if ( quote.getLastPremiumDate() > re_date )
{
this._logger.log( this._logger.PRIORITY_INFO,
"Skipping '%s' rating for quote #%s; quote is still valid",
quote.getProgramId(),
quote.getId()
);
return true;
}
return false;
},
_performRating: function( request, program, quote, indv, c )
{
var _self = this;
var rater = this._getProgramRater( program );
if ( !rater )
{
this._server.sendError( request, 'Unable to perform rating.' );
c();
}
this._logger.log( this._logger.PRIORITY_INFO,
"Performing '%s' rating for quote #%s",
quote.getProgramId(),
quote.getId()
);
rater.rate( quote, request.getSession(), indv,
function( rate_data, actions )
{
actions = actions || [];
_self.postProcessRaterData(
request, rate_data, actions, program, quote
);
const class_dest = {};
const cleaned = _self._cleanRateData(
rate_data,
class_dest
);
// TODO: move me during refactoring
_self._dao.saveQuoteClasses( quote, class_dest );
// save all data server-side (important: do after
// post-processing); async
_self._saveRatingData( quote, rate_data, indv, function()
{
// we're done
c();
} );
// no need to wait for the save; send the response
_self._server.sendResponse( request, quote, {
data: cleaned,
initialRatedDate: quote.getRatedDate(),
lastRatedDate: quote.getLastPremiumDate()
}, actions );
},
function( message )
{
_self._sendRatingError( request, quote, program,
Error( message )
);
c();
}
);
},
/**
* Saves rating data
*
* Data will be merged with existing bucket data and saved. The idea behind
* this is to allow us to reference the data (e.g. for reporting) even if
* the client does not save it.
*
* @param {Quote} quote quote to save data to
* @param {Object} data rating data
*
* @return {undefined}
*/
_saveRatingData: function( quote, data, indv, c )
{
// only update the last premium calc date on the initial request
if ( !indv )
{
var cur_date = Math.round(
( new Date() ).getTime() / 1000
);
quote.setLastPremiumDate( cur_date );
quote.setRatedDate( cur_date );
function done()
{
c();
}
// save the last prem status (we pass an empty object as the save
// data argument to ensure that we do not save the actual bucket
// data, which may cause a race condition with the below merge call)
this._dao.saveQuote( quote, done, done, {} );
}
else
{
c();
}
// we're not going to worry about whether or not this fails; if it does,
// an error will be automatically logged, but we still want to give the
// user a rate (if this save fails, it's likely we have bigger problems
// anyway); this can also be done concurrently with the above request
// since it only modifies a portion of the bucket
this._dao.mergeBucket( quote, data );
},
/**
* Process rater data returned from a rater
*
* @param {UserRequest} request user request to satisfy
* @param {Object} data rating data returned
* @param {Array} actions actions to send to client
* @param {Program} program program used to perform rating
* @param {Quote} quote quote used for rating
*
* @return {undefined}
*/
'virtual protected postProcessRaterData': function(
request, data, actions, program, quote
)
{
var meta = data._cmpdata || {};
// the metadata will not be provided to the client
delete data._cmpdata;
// rating worksheets are returned as metadata
this._processWorksheetData( quote.getId(), data );
if ( ( program.ineligibleLockCount > 0 )
&& ( +meta.count_ineligible >= program.ineligibleLockCount )
)
{
// lock the quote client-side (we don't send them the reason; they
// don't need it) to the current step
actions.push( { action: 'lock' } );
var lock_reason = 'Supplier ineligibility restriction';
var lock_step = quote.getCurrentStepId();
// the next step is the step that displays the rating results
quote.setExplicitLock( lock_reason, ( lock_step + 1 ) );
// important: only save the lock state, not the step states, as we
// have a race condition with async. rating (the /visit request may
// be made while we're rating, and when we come back we would then
// update the step id with a prior, incorrect step)
this._dao.saveQuoteLockState( quote );
}
// if any have been deferred, instruct the client to request them
// individually
if ( Array.isArray( meta.deferred ) && ( meta.deferred.length > 0 ) )
{
var torate = [];
meta.deferred.forEach( function( alias )
{
actions.push( { action: 'indvRate', id: alias } );
torate.push( alias );
} );
// we log that we're performing rating, so we should also log when
// it is deferred (otherwise the logs will be rather confusing)
this._logger.log( this._logger.PRIORITY_INFO,
"'%s' rating deferred for quote #%s; will rate: %s",
quote.getProgramId(),
quote.getId(),
torate.join( ',' )
);
}
},
_sendRatingError: function( request, quote, program, err )
{
// well that's no good
this._logger.log( this._logger.PRIORITY_ERROR,
"Rating for quote %d (program %s) failed: %s",
quote.getId(),
program.getId(),
err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' )
);
this._server.sendError( request,
'There was a problem during the rating process. Unable to ' +
'continue. Please contact our support team for assistance.' +
// show details for internal users
( ( request.getSession().isInternal() )
? '<br /><br />[Internal] ' + err.message + '<br /><br />' +
'<hr />' + err.stack.replace( /\n/g, '<br />' )
: ''
)
);
},
_processWorksheetData: function( qid, data )
{
// TODO: this should be done earlier on, so that this is not necessary
var wre = /^(.+)___worksheet$/,
worksheets = {};
// extract worksheets for each supplier
for ( var field in data )
{
var match;
if ( match = field.match( wre ) )
{
var name = match[ 1 ];
worksheets[ name ] = data[ field ];
delete data[ field ];
}
}
var _self = this;
this._dao.setWorksheets( qid, worksheets, function( err )
{
if ( err )
{
_self._logger.log( this._logger.PRIORITY_ERROR,
"Failed to save rating worksheets for quote %d",
quote.getId(),
err.message + '\n-!' + err.stack.replace( /\n/g, '\n-!' )
);
}
} );
},
serveWorksheet: function( request, quote, supplier, index )
{
var qid = quote.getId(),
_self = this;
this._dao.getWorksheet( qid, supplier, index, function( data )
{
_self._server.sendResponse( request, quote, {
data: data
} );
} );
},
/**
* Prepares rate data to be sent back to the client
*
* There are certain data saved server-side that there is no use serving to
* the client.
*
* @param {Object} data rate data
*
* @return {Object} modified rate data
*/
'private _cleanRateData': function( data, classes )
{
classes = classes || {};
var result = {};
// clear class data
for ( var key in data )
{
var mdata;
// supplier___classes
if ( mdata = key.match( /^(.*)___classes$/ ) )
{
classes[ mdata[ 1 ] ] = data[ key ];
continue;
}
result[ key ] = data[ key ];
}
return result;
},
} );

View File

@ -0,0 +1,502 @@
/**
* Rating service
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ClassificationData, RateResult, WorksheetData } from "../rater/Rater";
import { ClientActions } from "../../client/action/ClientAction";
import { PositiveInteger } from "../../numeric";
import { PriorityLog } from "../log/PriorityLog";
import { ProcessManager } from "../rater/ProcessManager";
import { Program } from "../../program/Program";
import { QuoteId } from "../../quote/Quote";
import { Server } from "../Server";
import { ServerDao } from "../db/ServerDao";
import { ServerSideQuote } from "../quote/ServerSideQuote";
import { UserRequest } from "../request/UserRequest";
import { UserResponse } from "../request/UserResponse";
type RequestCallback = () => void;
/** Result of rating */
export type RateRequestResult = {
data: RateResult,
initialRatedDate: UnixTimestamp,
lastRatedDate: UnixTimestamp,
};
/**
* Handle rating requests
*
* XXX: This class was extracted from Server and needs additional
* refactoring, testing, and cleanup.
*
* TODO: Logging should be implemented by observers
*/
export class RatingService
{
/**
* Initialize rating service
*
* @param _logger - logging system
* @param _dao - database connection
* @param _server - server actions
* @param _rater_manager - rating manager
*/
constructor(
private readonly _logger: PriorityLog,
private readonly _dao: ServerDao,
private readonly _server: Server,
private readonly _rater_manager: ProcessManager,
) {}
/**
* TODO: Remove once traits subtypes are converted to TS
*
* This works around an easejs bug where prototype constructors are not
* properly invoked. Note that technically the constructor above is
* invoked twice by easejs: once with no arguments, and again when
* calling this method with the proper arguments.
*/
__construct()
{
(<any>RatingService ).apply( this, arguments );
}
/**
* Sends rates to the client
*
* Note that the promise will be resolved after all data saving is
* complete; the request will be sent back to the client before then.
*
* @param request - user request to satisfy
* @param _response - pending response
* @param quote - quote to export
* @param cmd - applicable of command request
*
* @return result promise
*/
request(
request: UserRequest,
_response: UserResponse,
quote: ServerSideQuote,
cmd: string,
): Promise<RateRequestResult>
{
return new Promise<RateRequestResult>( resolve =>
{
// cmd represents a request for a single rater
if ( !cmd && this._isQuoteValid( quote ) )
{
// send an empty reply (keeps what is currently in the
// bucket)
this._server.sendResponse( request, quote, {
data: {},
}, [] );
// XXX: When this class is no longer responsible for
// sending the response to the server, this below data needs
// to represent the _current_ values, since as it is written
// now, it'll overwrite what is currently in the bucket
return resolve( {
data: { _unavailable_all: '0' },
initialRatedDate: <UnixTimestamp>0,
lastRatedDate: <UnixTimestamp>0,
} );
}
resolve( this._performRating( request, quote, cmd ) );
} )
.catch( err =>
{
this._sendRatingError( request, quote, err );
throw err;
} );
}
/**
* Whether quote is still valid
*
* TODO: This class shouldn't be making this determination, and this
* method is nondeterministic.
*
* @param quote - quote to check
*
* @return whether quote is still valid
*/
private _isQuoteValid( quote: ServerSideQuote ): boolean
{
// quotes are valid for 30 days
var re_date = Math.round( ( ( new Date() ).getTime() / 1000 ) -
( 60 * 60 * 24 * 30 )
);
if ( quote.getLastPremiumDate() > re_date )
{
this._logger.log( this._logger.PRIORITY_INFO,
"Skipping '%s' rating for quote #%s; quote is still valid",
quote.getProgramId(),
quote.getId()
);
return true;
}
return false;
}
/**
* Perform rating and process result
*
* @param request - user request to satisfy
* @param quote - quote to process
* @param indv - individual supplier to rate (or empty)
*
* @return promise for results of rating
*/
private _performRating(
request: UserRequest,
quote: ServerSideQuote,
indv: string,
): Promise<RateRequestResult>
{
return new Promise<RateRequestResult>( ( resolve, reject ) =>
{
const rater = this._rater_manager.byId( quote.getProgramId() );
this._logger.log( this._logger.PRIORITY_INFO,
"Performing '%s' rating for quote #%s",
quote.getProgramId(),
quote.getId()
);
rater.rate( quote, request.getSession(), indv,
( rate_data: RateResult, actions: ClientActions ) =>
{
actions = actions || [];
this.postProcessRaterData(
request, rate_data, actions, quote.getProgram(), quote
);
const class_dest = {};
const cleaned = this._cleanRateData(
rate_data,
class_dest
);
// TODO: move me during refactoring
this._dao.saveQuoteClasses(
quote, class_dest, () => {}, () => {}
);
const result = {
data: cleaned,
initialRatedDate: quote.getRatedDate(),
lastRatedDate: quote.getLastPremiumDate()
};
// save all data server-side (important: do after
// post-processing); async
this._saveRatingData( quote, rate_data, indv, function()
{
// we're done
resolve( result );
} );
// no need to wait for the save; send the response
this._server.sendResponse( request, quote, result, actions );
},
( message: string ) =>
{
this._sendRatingError( request, quote,
Error( message )
);
reject( Error( message ) );
}
);
} );
}
/**
* Saves rating data
*
* Data will be merged with existing bucket data and saved. The idea behind
* this is to allow us to reference the data (e.g. for reporting) even if
* the client does not save it.
*
* @param quote - quote to save data to
* @param data - rating data
* @param indv - individual supplier, or empty
* @param c - callback
*/
private _saveRatingData(
quote: ServerSideQuote,
data: RateResult,
indv: string,
c: RequestCallback
): void
{
// only update the last premium calc date on the initial request
if ( !indv )
{
var cur_date = <UnixTimestamp>Math.round(
( new Date() ).getTime() / 1000
);
quote.setLastPremiumDate( cur_date );
quote.setRatedDate( cur_date );
// save the last prem status (we pass an empty object as the save
// data argument to ensure that we do not save the actual bucket
// data, which may cause a race condition with the below merge call)
this._dao.saveQuote( quote, c, c, {
ratedata: data,
} );
}
else
{
c();
}
// we're not going to worry about whether or not this fails; if it does,
// an error will be automatically logged, but we still want to give the
// user a rate (if this save fails, it's likely we have bigger problems
// anyway); this can also be done concurrently with the above request
// since it only modifies a portion of the bucket
this._dao.mergeBucket( quote, data, () => {}, () => {} );
}
/**
* Process rater data returned from a rater
*
* @param _request - user request to satisfy
* @param data - rating data returned
* @param actions - actions to send to client
* @param program - program used to perform rating
* @param quote - quote used for rating
*/
protected postProcessRaterData(
_request: UserRequest,
data: RateResult,
actions: ClientActions,
program: Program,
quote: ServerSideQuote,
): void
{
var meta = data._cmpdata || {};
// the metadata will not be provided to the client
delete data._cmpdata;
// rating worksheets are returned as metadata
this._processWorksheetData( quote.getId(), data );
if ( ( program.ineligibleLockCount > 0 )
&& ( +meta.count_ineligible >= program.ineligibleLockCount )
)
{
// lock the quote client-side (we don't send them the reason; they
// don't need it) to the current step
actions.push( { action: 'lock' } );
var lock_reason = 'Supplier ineligibility restriction';
var lock_step = quote.getCurrentStepId();
// the next step is the step that displays the rating results
quote.setExplicitLock( lock_reason, ( lock_step + 1 ) );
// important: only save the lock state, not the step states, as we
// have a race condition with async. rating (the /visit request may
// be made while we're rating, and when we come back we would then
// update the step id with a prior, incorrect step)
this._dao.saveQuoteLockState( quote, () => {}, () => {} );
}
// if any have been deferred, instruct the client to request them
// individually
if ( Array.isArray( meta.deferred ) && ( meta.deferred.length > 0 ) )
{
var torate: string[] = [];
meta.deferred.forEach( ( alias: string ) =>
{
actions.push( { action: 'indvRate', after: alias } );
torate.push( alias );
} );
// we log that we're performing rating, so we should also log when
// it is deferred (otherwise the logs will be rather confusing)
this._logger.log( this._logger.PRIORITY_INFO,
"'%s' rating deferred for quote #%s; will rate: %s",
quote.getProgramId(),
quote.getId(),
torate.join( ',' )
);
}
}
/**
* Send rating error to user and log
*
* @param request - user request to satisfy
* @param quote - problem quote
* @param err - error
*/
private _sendRatingError(
request: UserRequest,
quote: ServerSideQuote,
err: Error,
): void
{
// well that's no good
this._logger.log( this._logger.PRIORITY_ERROR,
"Rating for quote %d (program %s) failed: %s",
quote.getId(),
quote.getProgramId(),
err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' )
);
this._server.sendError( request,
'There was a problem during the rating process. Unable to ' +
'continue. Please contact our support team for assistance.' +
// show details for internal users
( ( request.getSession().isInternal() )
? '<br /><br />[Internal] ' + err.message + '<br /><br />' +
'<hr />' + ( err.stack || "" ).replace( /\n/g, '<br />' )
: ''
)
);
}
/**
* Process and save worksheet data from rating results
*
* @param qid - quote id
* @param data - rating result
*/
private _processWorksheetData( qid: QuoteId, data: RateResult ): void
{
// TODO: this should be done earlier on, so that this is not necessary
const wre = /^(.+)___worksheet$/;
const worksheets: Record<string, WorksheetData> = {};
// extract worksheets for each supplier
for ( var field in data )
{
var match;
if ( match = field.match( wre ) )
{
var name = match[ 1 ];
worksheets[ name ] = data[ field ];
delete data[ field ];
}
}
this._dao.setWorksheets( qid, worksheets, ( err: Error | null ) =>
{
if ( err )
{
this._logger.log( this._logger.PRIORITY_ERROR,
"Failed to save rating worksheets for quote %d",
qid,
err.message + '\n-!' + ( err.stack || "" ).replace( /\n/g, '\n-!' )
);
}
} );
}
/**
* Serve worksheet data to user
*
* @param request - user request to satisfy
* @param quote - quote from which to look up worksheet data
* @param supplier - supplier name
* @param index - worksheet index
*/
serveWorksheet(
request: UserRequest,
quote: ServerSideQuote,
supplier: string,
index: PositiveInteger,
): void
{
var qid = quote.getId();
this._dao.getWorksheet( qid, supplier, index, data =>
{
this._server.sendResponse( request, quote, {
data: data
} );
} );
}
/**
* Prepares rate data to be sent back to the client
*
* There are certain data saved server-side that there is no use serving to
* the client.
*
* @param data - rate data
* @param classes - classification data
*
* @return modified rate data
*/
private _cleanRateData(
data: RateResult,
classes: ClassificationData
): RateResult
{
// forceful cast because the below loop will copy everything
const result = <RateResult>{};
// clear class data
for ( var key in data )
{
var mdata;
// supplier___classes
if ( mdata = key.match( /^(.*)___classes$/ ) )
{
classes[ mdata[ 1 ] ] = data[ key ];
continue;
}
result[ key ] = data[ key ];
}
return result;
}
}

View File

@ -21,8 +21,8 @@
'use strict';
const { Trait } = require( 'easejs' );
const RatingService = require( './RatingService' );
const { Interface, Trait } = require( 'easejs' );
const { RatingService } = require( './RatingService' );
/**
@ -51,7 +51,8 @@ const RatingService = require( './RatingService' );
* See the body of `#_sendMessage' for their values.
*/
module.exports = Trait( 'RatingServicePublish' )
.extend( RatingService,
.implement( Interface( { 'postProcessRaterData': [] } ) )
.extend(
{
/**
* AMQP library (amqplib API)
@ -75,7 +76,7 @@ module.exports = Trait( 'RatingServicePublish' )
*
* @type {DebugLog}
*/
'private _logger': null,
'private _log': null,
/**
@ -87,9 +88,9 @@ module.exports = Trait( 'RatingServicePublish' )
*/
__mixin( amqp, conf, logger )
{
this._amqp = amqp;
this._conf = conf;
this._logger = logger;
this._amqp = amqp;
this._conf = conf;
this._log = logger;
},
@ -104,7 +105,7 @@ module.exports = Trait( 'RatingServicePublish' )
*
* @return {undefined}
*/
'override protected postProcessRaterData'(
'abstract override postProcessRaterData'(
request, data, actions, program, quote
)
{
@ -127,13 +128,13 @@ module.exports = Trait( 'RatingServicePublish' )
quote
);
} )
.then( () => this._logger.log(
this._logger.PRIORITY_INFO,
.then( () => this._log.log(
this._log.PRIORITY_INFO,
"Published quote " + quote.getId() +
" to post-rate exchange '" + exchange + "'"
) )
.catch( e => this._logger.log(
this._logger.PRIORITY_ERROR,
.catch( e => this._log.log(
this._log.PRIORITY_ERROR,
"Post-rate exchange publish failure for quote " +
quote.getId() + ": " + e.message
) );

View File

@ -1,163 +0,0 @@
/**
* Notification on all submit
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of liza.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
const { Trait } = require( 'easejs' );
const DslRaterContext = require( '../rater/DslRaterContext' )
const RatingService = require( './RatingService' );
/**
* Triggers DataApi when no results are available
*
* This information is currently stored in `__prem_avail_count`. In the
* future, it may be worth accepting a parameter to configure this at
* runtime.
*
* Notification status will persist using the provided DAO. The next time
* such a notification is requested, it will only occur if the flag is not
* set. The flag is not set in the event of an error (determined by the
* DataApi; usually an HTTP error).
*/
module.exports = Trait( 'RatingServiceSubmitNotify' )
.extend( RatingService,
{
/**
* Function returning DataApi to trigger
* @type {Function(UserSession):DataApi}
*/
'private _dapif': null,
/**
* Data store for notification flag
* @type {ServerDao}
*/
'private _notifyDao': null,
/**
* Initialize mixin with DataApi to trigger
*
* @param {Function(UserSession):DataApi} dapif Function producing DataApi
* @param {ServerDao} dao store for notification flag
*/
__mixin( dapif, dao )
{
this._dapif = dapif;
this._notifyDao = dao;
},
/**
* Trigger previously provided DataApi when no results are available
*
* Result count is determined by DATA.__prem_avail_count. If the
* notification is successful (determined by the DataApi), then a
* flag will be set preventing the request from being trigerred for
* subsequent rating data.
*
* @param {UserRequest} request user request
* @param {Object} data rating data returned
* @param {Array} actions actions to send to client
* @param {Program} program program used to perform rating
* @param {Quote} quote quote used for rating
*
* @return {undefined}
*/
'override protected postProcessRaterData'(
request, data, actions, program, quote
)
{
const quote_id = quote.getId();
const avail = ( data.__prem_avail_count || [ 0 ] )[ 0 ];
if ( avail === 0 )
{
this._maybeNotify( quote_id, request );
}
this.__super( request, data, actions, program, quote );
},
/**
* Perform notification if flag has not been set
*
* See #postProcessRaterData for more information.
*
* @param {number} quote_id effective quote/document id
* @param {UserRequest} request user request
*
* @return {undefined}
*/
'private _maybeNotify'( quote_id, request )
{
this._getNotifyState( quote_id, notified =>
{
if ( notified === true )
{
return;
}
// make the request, only setting the notification flag if
// it is successful
this._dapif( request )
.request( { quote_id: quote_id }, err =>
{
err || this._setNotified( quote_id );
} );
} );
},
/**
* Get value of notification flag
*
* @param {number} quote_id id of quote
* @param {function(boolean)} callback callback to call when complete
*
* @return {undefined}
*/
'private _getNotifyState'( quote_id, callback )
{
this._notifyDao.getDocumentField(
quote_id,
'submitNotified',
( err, value ) => callback( value )
);
},
/**
* Set notification flag
*
* @param {number} quote_id id of quote
*
* @return {undefined}
*/
'private _setNotified'( quote_id )
{
this._notifyDao.setDocumentField(
quote_id, 'submitNotified', true
);
},
} );

View File

@ -1,92 +0,0 @@
/**
* Tests RatingService
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict'
exports.getStubs = function()
{
const program_id = 'foo';
const program = {
getId: () => program_id,
};
// rate reply
const stub_rate_data = {};
const rater = {
rate: ( quote, session, indv, callback ) => callback( stub_rate_data ),
};
const raters = {
byId: () => rater,
};
const logger = {
log: () => {},
};
const server = {
sendResponse: () => {},
sendError: () => {},
};
const dao = {
mergeBucket: () => {},
saveQuoteClasses: () => {},
setWorksheets: () => {},
saveQuote: () => {},
};
const session = {
isInternal: () => false,
};
const request = {
getSession: () => session,
getSessionIdName: () => {},
};
const response = {};
const quote = {
getProgramId: () => program_id,
getProgram: () => program,
getId: () => 0,
setLastPremiumDate: () => {},
setRatedDate: () => {},
getRatedDate: () => 0,
getLastPremiumDate: () => 0
};
return {
program: program,
stub_rate_data: stub_rate_data,
rater: rater,
raters: raters,
logger: logger,
server: server,
dao: dao,
session: session,
request: request,
response: response,
quote: quote,
};
};

14
src/types/misc.d.ts vendored
View File

@ -38,12 +38,24 @@
type NominalType<K, T> = K & { __nominal_type__: T };
/** Unit of time in seconds */
type Seconds = NominalType<number, 'Seconds'>;
/**
* Unix timestamp
*
* Number of seconds since the Unix epoch (1970-01-01 UTC).
*/
type UnixTimestamp = NominalType<number, 'UnixTimestamp'>;
type UnixTimestamp = NominalType<Seconds, 'UnixTimestamp'>;
/** Unit of time in milliseconds */
type Milliseconds = NominalType<number, 'Milliseconds'>;
/** Unix timestamp represented in milliseconds */
type UnixTimestampMillis = NominalType<Milliseconds, 'UnixTimestampMillis'>;
/**

View File

@ -0,0 +1,59 @@
/**
* Test numeric types
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { expect } from 'chai';
import { PositiveInteger, isPositiveInteger } from "../src/numeric";
describe( 'isPositiveInteger', () =>
{
[
0,
5,
].forEach( value => it( `accepts positive integers (${value})`, () =>
{
expect( isPositiveInteger( value ) ).to.be.true;
} ) );
[
-1,
-5,
].forEach( value => it( `rejects negative integers (${value})`, () =>
{
expect( isPositiveInteger( value ) ).to.be.false;
} ) );
it( "asserts type PositiveInteger", () =>
{
const n = 5;
if ( isPositiveInteger( n ) )
{
// TS should recognize as PositiveInteger within this block
checkPositiveInteger( n );
}
} );
} );
const checkPositiveInteger = ( _n: PositiveInteger ): void => {};

View File

@ -1,186 +0,0 @@
/**
* Tests RatingServiceSubmitNotify
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict'
const { Class } = require( 'easejs' );
const { expect } = require( 'chai' );
const {
dapi: {
DataApi,
},
server: {
service: {
RatingServiceSubmitNotify: Sut,
RatingService,
},
},
test: {
server: {
service: {
RatingServiceStub,
},
},
},
} = require( '../../../' );
describe( 'RatingServiceSubmitNotify', () =>
{
[
// not available; make successful request and save flag
{
prem_avail_count: [ 0 ],
prev_called: false,
expected_request: true,
request_err: null,
save: true,
},
// not available; make failing request, don't save flag
{
prem_avail_count: [ 0 ],
prev_called: false,
expected_request: true,
request_err: Error(),
save: false,
},
// available
{
prem_avail_count: [ 2 ],
prev_called: false,
expected_request: false,
request_err: null,
save: false,
},
// this shouldn't happen; ignore all but first index
{
prem_avail_count: [ 2, 2 ],
prev_called: false,
expected_request: false,
request_err: null,
save: false,
},
// save as above, but already saved
{
prem_avail_count: [ 0 ],
prev_called: true,
expected_request: false,
request_err: null,
save: false,
},
// available; don't make request
{
prem_avail_count: [ 2 ],
prev_called: true,
expected_request: false,
request_err: null,
save: false,
},
// this shouldn't happen; ignore all but first index
{
prem_avail_count: [ 2, 2 ],
prev_called: true,
expected_request: false,
request_err: null,
save: false,
},
].forEach( ( expected, i ) =>
it( `sends request on post process if no premiums (#${i})`, done =>
{
const {
dao,
logger,
quote,
raters,
request,
response,
server,
stub_rate_data,
} = RatingServiceStub.getStubs();
const quote_id = 1234;
let requested = false;
const dapif = given_request =>
Class.implement( DataApi ).extend(
{
// warning: if an expectation fails, because of how
// RatingService handles errors, it will cause the test to
// _hang_ rather than throw the assertion error
request( data, callback, id )
{
expect( given_request ).to.equal( request );
expect( data ).to.deep.equal( { quote_id: quote_id } );
requested = true;
callback( expected.request_err, null );
},
} )();
const sut = RatingService.use( Sut( dapif, dao ) )(
logger, dao, server, raters
);
quote.getId = () => quote_id;
// one of the methods that is called by the supertype
let save_called = false;
dao.setWorksheets = () => save_called = true;
// whether the notify flag is actually set
let notify_saved = false;
// request for notification status
dao.getDocumentField = ( qid, key, callback ) =>
{
expect( qid ).to.equal( quote_id );
expect( key ).to.equal( 'submitNotified' );
callback( expected.flag_error, expected.prev_called );
};
dao.setDocumentField = ( qid, key, value, callback ) =>
{
expect( qid ).to.equal( quote_id );
expect( key ).to.equal( 'submitNotified' );
expect( value ).to.equal( true );
notify_saved = true;
};
stub_rate_data.__prem_avail_count = expected.prem_avail_count;
sut.request( request, response, quote, 'something', () =>
{
expect( requested ).to.equal( expected.expected_request );
expect( save_called ).to.be.true;
// only save notification status if we're notifying
expect( notify_saved ).to.equal( expected.save );
done();
} );
} )
);
} );

View File

@ -1,105 +0,0 @@
/**
* Tests RatingService
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict'
const { expect } = require( 'chai' );
const Sut = require( '../../../' ).server.service.RatingService;
const RatingServiceStub = require( '../../../' ).test.server.service.RatingServiceStub;
describe( 'RatingService', () =>
{
describe( "protected API", () =>
{
it( "calls #postProcessRaterData after rating before save", done =>
{
let processed = false;
const {
logger,
server,
raters,
dao,
request,
response,
quote,
} = RatingServiceStub.getStubs();
dao.mergeBucket = () =>
{
expect( processed ).to.equal( true );
done();
};
const sut = Sut.extend(
{
'override postProcessRaterData'(
request, data, actions, program, quote
)
{
processed = true;
}
} )( logger, dao, server, raters );
sut.request( request, response, quote, 'something', () => {} );
} );
it( "calls getLastPremiumDate during #_performRating", done =>
{
let getLastPremiumDateCallCount = 0;
const last_date = 1234;
const initial_date = 2345;
const {
logger,
server,
raters,
dao,
request,
response,
quote,
} = RatingServiceStub.getStubs();
quote.getLastPremiumDate = () =>
{
getLastPremiumDateCallCount++;
return last_date
};
quote.getRatedDate = () => initial_date;
const sut = Sut( logger, dao, server, raters );
server.sendResponse = ( request, quote, resp, actions ) =>
{
expect( getLastPremiumDateCallCount ).to.equal( 2 );
expect( resp.initialRatedDate ).to.equal( initial_date );
expect( resp.lastRatedDate ).to.equal( last_date );
done();
};
sut.request( request, response, quote, null, () => {} );
} );
} );
} );

View File

@ -0,0 +1,411 @@
/**
* Tests RatingService
*
* Copyright (C) 2010-2019 R-T Specialty, LLC.
*
* This file is part of the Liza Data Collection Framework.
*
* liza is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { RatingService as Sut } from "../../../src/server/service/RatingService";
import { ClientActions } from "../../../src/client/action/ClientAction";
import { PriorityLog } from "../../../src/server/log/PriorityLog";
import { ProcessManager } from "../../../src/server/rater/ProcessManager";
import { Program } from "../../../src/program/Program";
import { QuoteId } from "../../../src/quote/Quote";
import { Rater, RateResult } from "../../../src/server/rater/Rater";
import { Server } from "../../../src/server/Server";
import { ServerSideQuote } from "../../../src/server/quote/ServerSideQuote";
import { UserRequest } from "../../../src/server/request/UserRequest";
import { UserResponse } from "../../../src/server/request/UserResponse";
import { UserSession } from "../../../src/server/request/UserSession";
import {
ServerDao,
Callback as ServerDaoCallback
} from "../../../src/server/db/ServerDao";
import { expect, use as chai_use } from 'chai';
chai_use( require( 'chai-as-promised' ) );
describe( 'RatingService', () =>
{
it( "returns rating results", () =>
{
const {
logger,
server,
raters,
dao,
request,
response,
quote,
stub_rate_data,
} = getStubs();
const sut = new Sut( logger, dao, server, raters );
const expected = {
data: stub_rate_data,
initialRatedDate: quote.getRatedDate(),
lastRatedDate: quote.getLastPremiumDate(),
};
return expect( sut.request( request, response, quote, "" ) )
.to.eventually.deep.equal( expected );
} );
it( "saves rate data to own field", () =>
{
const {
logger,
server,
raters,
dao,
request,
response,
quote,
stub_rate_data,
} = getStubs();
let saved_rates = false;
dao.saveQuote = (
quote: ServerSideQuote,
success: ServerDaoCallback,
_failure: ServerDaoCallback,
save_data: Record<string, any>,
) =>
{
expect( save_data ).to.deep.equal( {
ratedata: stub_rate_data,
} );
saved_rates = true;
success( quote );
return dao;
};
const sut = new Sut( logger, dao, server, raters );
return sut.request( request, response, quote, "" )
.then( () =>
{
expect( saved_rates ).to.be.true;
} );
} );
it( "rejects and responds with error", () =>
{
const {
dao,
logger,
program,
quote,
rater,
raters,
request,
response,
server,
} = getStubs();
const expected_error = new Error( "expected error" );
rater.rate = () => { throw expected_error; };
const sut = new Sut( logger, dao, server, raters );
let logged = false;
logger.log = function(
priority: number,
_format: string,
qid: QuoteId,
program_id: string,
message: string,
)
{
if ( typeof message === 'string' )
{
expect( priority ).to.equal( logger.PRIORITY_ERROR );
expect( qid ).to.equal( quote.getId() );
expect( program_id ).to.equal( program.getId() );
expect( message ).to.contain( expected_error.message );
logged = true;
}
return logger;
};
return expect( sut.request( request, response, quote, "" ) )
.to.eventually.rejectedWith( expected_error )
.then( () => expect( logged ).to.be.true );
} );
it( "returns error message from rater", () =>
{
const {
dao,
logger,
quote,
rater,
raters,
request,
response,
server,
} = getStubs();
const expected_message = 'expected foo';
const sut = new Sut( logger, dao, server, raters );
rater.rate = (
_quote: ServerSideQuote,
_session: UserSession,
_indv: string,
_success: ( data: RateResult, actions: ClientActions ) => void,
failure: ( message: string ) => void,
) =>
{
failure( expected_message );
return rater;
};
return expect( sut.request( request, response, quote, "" ) )
.to.eventually.rejectedWith( Error, expected_message );
} );
describe( "protected API", () =>
{
it( "calls #postProcessRaterData after rating before save", done =>
{
let processed = false;
const {
logger,
server,
raters,
dao,
request,
response,
quote,
} = getStubs();
dao.mergeBucket = () =>
{
expect( processed ).to.equal( true );
done();
return dao;
};
const sut = new class extends Sut
{
postProcessRaterData()
{
processed = true;
}
}( logger, dao, server, raters );
sut.request( request, response, quote, 'something' );
} );
it( "calls getLastPremiumDate during #_performRating", done =>
{
let getLastPremiumDateCallCount = 0;
const last_date = <UnixTimestamp>1234;
const initial_date = <UnixTimestamp>2345;
const {
logger,
server,
raters,
dao,
request,
response,
quote,
} = getStubs();
quote.getLastPremiumDate = () =>
{
getLastPremiumDateCallCount++;
return last_date
};
quote.getRatedDate = () => initial_date;
const sut = new Sut( logger, dao, server, raters );
server.sendResponse = ( _request: any, _quote: any, resp: any, _actions: any ) =>
{
expect( getLastPremiumDateCallCount ).to.equal( 2 );
expect( resp.initialRatedDate ).to.equal( initial_date );
expect( resp.lastRatedDate ).to.equal( last_date );
done();
return server;
};
sut.request( request, response, quote, "" );
} );
} );
} );
function getStubs()
{
const program_id = 'foo';
const program = <Program>{
getId: () => program_id,
ineligibleLockCount: 0,
};
// rate reply
const stub_rate_data: RateResult = {
_unavailable_all: '0',
};
const rater = new class implements Rater
{
rate(
_quote: ServerSideQuote,
_session: UserSession,
_indv: string,
success: ( data: RateResult, actions: ClientActions ) => void,
_failure: ( message: string ) => void,
)
{
// force to be async so that the tests resemble how the code
// actually runs
process.nextTick( () => success( stub_rate_data, [] ) );
return this;
}
};
const raters = <ProcessManager>{
byId: () => rater,
};
const logger = new class implements PriorityLog
{
readonly PRIORITY_ERROR: number = 0;
readonly PRIORITY_IMPORTANT: number = 1;
readonly PRIORITY_DB: number = 2;
readonly PRIORITY_INFO: number = 3;
readonly PRIORITY_SOCKET: number = 4;
log( _priority: number, ..._args: Array<string|number> ): this
{
return this;
}
};
const server = <Server>{
sendResponse: () => server,
sendError: () => server,
};
const dao = new class implements ServerDao
{
saveQuote(
quote: ServerSideQuote,
success: ServerDaoCallback,
_failure: ServerDaoCallback,
_save_data: Record<string, any>,
): this
{
success( quote );
return this;
}
mergeBucket(): this
{
return this;
}
saveQuoteClasses(): this
{
return this;
}
setWorksheets(): this
{
return this;
}
saveQuoteState(): this
{
throw new Error( "Unused method" );
}
saveQuoteLockState(): this
{
throw new Error( "Unused method" );
}
getWorksheet(): this
{
throw new Error( "Unused method" );
}
};
const session = <UserSession>{
isInternal: () => false,
};
const request = <UserRequest>{
getSession: () => session,
getSessionIdName: () => {},
};
const response = <UserResponse>{};
const quote = <ServerSideQuote>{
getProgramId: () => program_id,
getProgram: () => program,
getId: () => <QuoteId>0,
setLastPremiumDate: () => quote,
setRatedDate: () => quote,
getRatedDate: () => <UnixTimestamp>0,
getLastPremiumDate: () => <UnixTimestamp>0,
getCurrentStepId: () => 0,
setExplicitLock: () => quote,
};
return {
program: program,
stub_rate_data: stub_rate_data,
rater: rater,
raters: raters,
logger: logger,
server: server,
dao: dao,
session: session,
request: request,
response: response,
quote: quote,
};
};