package gw.lob.wc.rating uses gw.api.domain.financials.PCFinancialsLogger uses gw.rating.CostData uses gw.rating.RateAdjFactorSearchCriteria uses java.lang.Double uses java.math.BigDecimal uses java.math.RoundingMode uses gw.api.util.JurisdictionMappingUtil uses java.util.Date /** * This class contains the methods needed to rate covered employees * This is an example implementation, it is expected that customers will use this as a pattern for their own * employee rater */ @Export class WCCoveredEmployeeRater { private construct() { } /** * Computes the rate using a given rating engine for a given covered employee * * @param engine The rating engine that would * @param covEmp The covered employee we want rated * * @returns {@link gw.lob.wc.rating.WCCovEmpCostData} that contains the rating information */ static function rate(engine : WCSysTableRatingEngine, covEmp : WCCoveredEmployee) : WCCovEmpCostData { var costData : WCCovEmpCostData var logMsg = " Rating " + covEmp + "..." PCFinancialsLogger.logDebug(logMsg) if (covEmp.NumDaysEffectiveForRating > 0) { // We can't just say this.WorkersCompLine.getJurisdiction(XYZ) because we're in window mode. The traversal // of this->WorkersCompLine will use the last second of this's effective period to look up the line. // That's fine, but then when we traverse WorkersCompLine->getJurisdiction(XYZY) it'll use the last second // of the line's effective period to look for the jurisdiction. This will fail if the jurisdiction was // removed in this job. var ratingDate = covEmp.WorkersCompLine.RepresentativeJurisdictions .firstWhere(\ j -> j.State == getRatingState(covEmp)).getPriorRatingDate(covEmp.EffectiveDate) var manualRate = getManualRateAndUpdateMinimumPremiums(engine, covEmp, ratingDate) var uwCompFactor = covEmp.Branch.getUWCompanyRateFactor(ratingDate, getRatingState(covEmp)) var uslhFactor = getUSLHFactor(covEmp, ratingDate) costData = rate_impl(engine, covEmp, manualRate, uwCompFactor, uslhFactor) } PCFinancialsLogger.logDebug(logMsg + "done") return costData } // Only exposed for testing purposes protected static function rate_impl(engine : WCSysTableRatingEngine, covEmp : WCCoveredEmployee, manualRate : BigDecimal, uwCompFactor : Double, uslhFactor : BigDecimal) : WCCovEmpCostData { var costData = new WCCovEmpCostData(covEmp.EffectiveDateForRating, covEmp.ExpirationDateForRating, engine.Branch.PreferredCoverageCurrency, engine.RateCache, covEmp.FixedId, covEmp.WorkersCompLine.WCWorkersCompCov.FixedId, JurisdictionMappingUtil.getJurisdiction(covEmp.Location)) var existingCost = costData.getExistingCost(engine.PolicyLine) costData.NumDaysInRatedTerm = covEmp.NumDaysEffectiveForRating costData.Basis = covEmp.BasisForRating costData.SubjectToReporting = true costData.StandardBaseRate = (manualRate * uwCompFactor).setScale(4, RoundingMode.HALF_UP) costData.StandardAdjRate = computeAdjustedRate(costData.StandardBaseRate, uslhFactor) costData.StandardTermAmount = computeTermAmount(covEmp, costData.StandardAdjRate, costData.Basis) costData.StandardAmount = costData.StandardTermAmount costData.copyOverridesFromCost(existingCost) computeValuesFromCostOverrides(existingCost, costData, covEmp) engine.Payroll.put(getRatingState(covEmp), engine.Payroll.get(getRatingState(covEmp)) + costData.Basis) return costData } // // PRIVATE SUPPORT FUNCTIONS // private static function computeValuesFromCostOverrides(cost : Cost, costData : CostData, covEmp : WCCoveredEmployee) { if (cost.OverrideBaseRate != null) { costData.ActualBaseRate = cost.OverrideBaseRate costData.ActualAdjRate = cost.OverrideBaseRate costData.ActualTermAmount = computeTermAmount(covEmp, costData.ActualAdjRate, costData.Basis) costData.ActualAmount = costData.ActualTermAmount } else if (cost.OverrideAdjRate != null) { costData.ActualBaseRate = 0 costData.ActualAdjRate = cost.OverrideAdjRate costData.ActualTermAmount = computeTermAmount(covEmp, costData.ActualAdjRate, costData.Basis) costData.ActualAmount = costData.ActualTermAmount } else if (cost.OverrideAmount != null) { costData.Basis = 0 costData.ActualBaseRate = 0 costData.ActualAdjRate = 0 costData.ActualTermAmount = cost.OverrideAmount costData.ActualAmount = cost.OverrideAmount } else { costData.copyStandardColumnsToActualColumns() } } private static function computeAdjustedRate(baseRate : BigDecimal, uslhFactor : BigDecimal) : BigDecimal { return (baseRate * uslhFactor).setScale(4, RoundingMode.HALF_UP) } private static function computeTermAmount(covEmp : WCCoveredEmployee, adjRate : BigDecimal, basis : BigDecimal) : BigDecimal { return (adjRate * basis * covEmp.ClassCode.Basis.RateFactor).setScale(getRoundingLevel(covEmp), getRoundingMode(covEmp)) } /** * Gets the manual rate for this covered employee on the ratingDate and updates the minimum premium as necessary. * See the RateWCClassCodeSearchCriteria for the full details on how the find is done. If no * rate is returned from the rates_workers_comp then a sample one is calculated based off of * the class code. The sample rate is guaranteed to consistently return the same rate for the same class code. * * @param engine the system table that contains the rating information * @param covEmp the covered employee * @param ratingDate the date used in the calculation * * @return the rate for the covered employee on a given date */ private static function getManualRateAndUpdateMinimumPremiums(engine : WCSysTableRatingEngine, covEmp : WCCoveredEmployee, ratingDate : Date) : BigDecimal { var criteria = new RateWCClassCodeSearchCriteria(ratingDate, covEmp.ClassCode, getRatingState(covEmp)) var rate = criteria.match() var theRate : BigDecimal // Number to return var theMinPremium : BigDecimal // Value to store for minimum premium checking later if (rate == null) { theRate = calculateDemoRate(convertClassCodeToNumber(criteria.NormalizedClassCode)) // seed with the normalized class code theMinPremium = 50 PCFinancialsLogger.logDebug("Could not find any rate for {Class Code=" + covEmp.ClassCode.Code + ", state=" + getRatingState(covEmp)+ ", date=" + ratingDate + "}. Using sample calculated rate of " + theRate) } else { theRate = rate.rate theMinPremium = rate.minPremium } engine.updateMinimumPremium(theMinPremium, getRatingState(covEmp), criteria.NormalizedClassCode) return theRate } /** * For demo purposes only, generate a consistent but completely unrealistic rate. */ private static function calculateDemoRate(seed : BigDecimal) : BigDecimal { var demoRate = (seed / 1000) - 1.24 if (demoRate < 1) { demoRate = 5 - demoRate } return demoRate } private static function convertClassCodeToNumber(classCode : String) : BigDecimal { var number = classCode.replaceAll("[^0-9]+", "") if (number.isEmpty()) { number = "0"; } return new BigDecimal(number) } private static function getUSLHFactor(covEmp : WCCoveredEmployee, ratingDate : Date) : BigDecimal { return (covEmp.SpecialCov.Code == "uslh") ? new RateAdjFactorSearchCriteria("wcUSLHFactor", ratingDate).match("base", getRatingState(covEmp)) : BigDecimal.ONE } private static function getRoundingLevel(emp : WCCoveredEmployee) : int { return emp.Branch.Policy.Product.QuoteRoundingLevel } private static function getRoundingMode(emp : WCCoveredEmployee) : RoundingMode { return emp.Branch.Policy.Product.QuoteRoundingMode } private static function getRatingState(emp : WCCoveredEmployee) : Jurisdiction { return JurisdictionMappingUtil.getJurisdiction(emp.Location) } }