Skip to main content
Generated from the rslc compiler.

E-CYCLE

A calculation must avoid reading its own value. A rater computes each calculation once, after the calculations it reads. The check follows every read, across records, lists and map elements. It reports any chain of reads that returns to where it started. A calculation inside such a loop has an undefined value, so every rating would fail. The message prints the loop as a path, such as Policy.fee -> Policy.premium -> Policy.fee. Example The fee is a share of the premium, and the premium includes the fee.
Fix Compute the shared part in its own calculation, and let both read it. Here base holds the premium before the fee.
Other causes
  • A calculation reads its own value, on the same record or on another record of its list. The message reads attribute `Policy.premium` reads its own value.
  • One element of a map reads another element of the same map.
  • The when condition of a follow-up input reads that same input. Write the condition from inputs that are asked earlier.
bad/rater.rsl
good/rater.rsl

E-OPTION-MATCH

An optional value needs both match arms. An Option input is one a request may leave out. A calculation reads it with match, which has a Some(…) arm for a given value and a None arm for an absent one. The check asks for both arms. Both cases happen in real requests. The None arm makes you decide what the filing says for a missing answer, and the Some(…) arm puts the given value to use. Example The prior premium is optional, and the match covers only a request that gives it.
Fix Add a None arm with the value the filing gives when the answer is absent. A _ arm also covers the missing case.
bad/rater.rsl
good/rater.rsl

E-MATCH

A match needs an arm for every value. A match picks one arm by the value of its subject. The check asks for an arm for each value the subject can hold. For an enum, that means each member the subject can be. For a Bool, that means true and false. A value that lacks an arm would leave the calculation undefined for a request the rater accepts. The message names the missing members, so you learn which filed case to add. The check counts only the values that reach the match. An input narrowed with where self in {…} needs arms for the narrowed members only. Example Hazard has three members, and the match gives a factor for two of them.
Fix Add an arm for high. Where the filing gives one value to every remaining member, end the match with a _ arm.
Other causes
  • A match on a Bool has one arm. The message reads match on Bool must cover both true and false.
bad/rater.rsl
good/rater.rsl

E-TOPN-LIMIT

A topn limit must be a whole count. topn(list as x, score, limit) keeps the limit elements with the highest score. A count of elements is a whole number of 0 or more. The check works out the range of the limit from the inputs it reads. Every value in that range must be such a count. A negative or fractional limit would stop a rating. The message prints the limit and the range the compiler worked out for it. Example The rater charges every location except the smallest. With an empty list of locations the limit is -1.
Fix Clamp the limit with max(…, 0). Where the filing asks for at least one location, narrow the list in its place: where len(self) in [1, 10].
Other causes
  • The limit is a calculation that can give a fraction, such as len(self.locations) / 2. Make it whole with floor(…).
  • The limit is a literal such as -1 or 2.5. The message reads topn limit must be a nonnegative whole number.
  • The call has two arguments and leaves the limit out. The message reads topn requires a limit.
bad/rater.rsl
good/rater.rsl

E-LOOKUP-GAP

A lookup needs a row for every key. lookup! reads the one table row that matches its keys. The check works out which key values each lookup can receive, from the types and ranges of the inputs behind them. Every such combination needs a row in the table. A combination that lacks a row would stop the rating of a request the rater accepts. The message names the smallest missing combination and the ways to close the gap. Each lookup is checked on its own. A lookup behind a narrowed input or inside a match arm needs rows only for the keys that reach it. Example Hazard has three members, and the table files a factor for low and medium only.
Fix Decide what the filing says about high. Here the carrier declines it, so the input excludes it with where self != @Hazard.high. Where the filing prints a factor for high, add that row to the CSV. Where the filing has an all-other row, add otherwise [cite: manual:p4]: { factor: … } to the table.
Other causes
  • A row has the cell unfiled, which marks a cell the filing leaves blank. The lookup can still reach that row.
  • A table with two or more keys lacks one combination, while each key value has rows elsewhere. Add the row or an otherwise row.
  • A key is a Code, which accepts any text that fits its pattern. Type the input Key(Table) to accept the filed codes only, or add an otherwise row.
bad/rater.rsl
bad/tables/HazardFactor.csv
good/rater.rsl
good/tables/HazardFactor.csv

E-ROOT

A root needs a valid value and degree. root(x, n, scale, mode) gives the nth root of x, rounded to scale decimal places with the stated mode. A square root, or any even root, exists only for a value of 0 or more. The check works out the range of x from the inputs it reads and reports an even root whose value can go below 0. The check also covers the other arguments. The degree is a whole number of 1 or more. The scale is a whole-number literal, and the mode is one of the rounding modes. A root that passes gives a value for every request the rater accepts. Example The charge is the square root of payroll above 1,000,000. For a smaller payroll the value under the root is negative.
Fix Clamp the value with max(…, 0), so a smaller payroll gets a charge of 0. Where the charge applies to large accounts only, narrow the input in its place: where self in [1000000, 100000000].
Other causes
  • The call has fewer or more than four arguments. Write all four, as in root(x, 2, 2, half_up).
  • The degree is a literal below 1 or a fraction, such as 0 or 2.5.
  • The degree is an input or a calculation whose range includes a fraction or a value below 1.
  • The scale is a fraction or a calculation. Write a whole-number literal: 2 for cents, 0 for whole dollars.
  • The mode is a name outside half_up, half_down, half_even, up, down, ceiling and floor.
bad/rater.rsl
good/rater.rsl

E-POW-EXPONENT

A power needs an exponent it computes exactly. RSL calculates with exact numbers, so two systems that run the same rater agree to the last digit. pow(x, n) with a whole exponent is exact. A fractional power such as pow(x, 0.85) has endless decimal places, so it needs a rounding. The four-argument form states that rounding: pow(x, e, scale, mode) rounds the power to scale decimal places with the stated mode. The check asks every pow to be one of these two forms and to give a value for every request. Example The exponent 0.85 is fractional, and the call lacks a scale and a mode.
Fix Add the scale and the rounding mode that the filing states for the power.
Other causes
  • A whole exponent comes with a scale and a mode, as in pow(x, 2, 4, half_up). The power is exact: write round(pow(x, 2), 4, half_up).
  • The call has three arguments. Write two for a whole exponent and four for a fractional one.
  • The exponent of the four-argument form is an input or a calculation. Write a literal such as 0.85.
  • The exponent is a fraction whose lowest terms pass 1000, such as 0.8585, which is 1717/2000. Keep the numerator and the denominator at 1000 or below.
  • The exponent of the two-argument form is an input that accepts fractions. Type it with precision: int.
  • The exponent can be negative while the base can be 0. Narrow the base above 0, or guard it with if.
  • A large whole exponent can push the result past the size limit of an exact number. Round the base first, or narrow its range.
  • The scale or the mode is malformed. The scale is a whole-number literal, and the mode is a rounding mode such as half_up.
bad/rater.rsl
good/rater.rsl

E-KEY-RANGE

A lookup key must stay inside the bands. A table keyed by bands has a row for each band. The bands cover a stretch of the number line, such as 1 and up. The check works out the range of each lookup key from the inputs it reads. That range must fit inside the stretch the bands cover. A key outside the bands matches zero rows, so the rating of that request would stop. The message prints the range of the key and the stretch the bands cover. Example The bands of SizeBand start at 1, and the input employees accepts 0.
Fix Start the range of employees at 1. Where the filing rates the values below the first band, say how. Clamp the key with max(self.employees, 1), open the bottom band with ..<1, or add an otherwise row with its citation.
Other causes
  • The key can pass the top of the last band. Open the last band, as in 250.., or clamp the key with min.
  • The key is a calculation, such as self.employees - 1. Cover its whole range with the bands, or narrow the inputs it reads.
  • The table is keyed by a list of values, such as IntValues(1, 10, 50), and the key is a plain number. Type the input with the values type. This cause comes with E-LOOKUP-GAP.
bad/rater.rsl
bad/tables/SizeFactor.csv
good/rater.rsl
good/tables/SizeFactor.csv

E-KEY-HOLE

A calculated band key needs a filed rounding. Bands are filed at a precision. IntBands(0, 30000, 60000..) files whole numbers: one band ends at 29,999 and the next starts at 30,000. A calculated key can land between those two values, at 29,999.5. The filing decides which band such a value belongs to, and the rater has to say so. The check asks every calculated key of a band table to sit on the precision of its bands. An input key already does, because its type states its precision. Example The average wage is a division, so it can fall between two whole numbers. WageBand files whole numbers.
Fix Round the key the way the filing does, and cite the rule. Where the filing defines the bands over the unrounded value, declare that on the type: WageBand = IntBands(key: unrounded; 0, 30000, 60000..).
bad/rater.rsl
bad/tables/WageFactor.csv
good/rater.rsl
good/tables/WageFactor.csv

E-INTERP-RANGE

An interpolated key stays between the filed points. interpolate! reads a table whose last key lists the filed points. Between two points it draws a straight line from one filed value to the next. The check works out the range of the key and asks that it stays between the first point and the last. Outside the filed points the table is silent: the filing may hold the end value flat, extend the line or decline the risk. The rater states which, so the choice is the filing’s and a reviewer sees it. Example The filed points run from 100,000 to 5,000,000, and payroll accepts values from 0 to 100,000,000.
Fix Write the rule the filing gives for values outside the points. Here the filing holds the end factors flat, so the key is clamped to the first and last points. Where the carrier declines such risks, narrow the input: where self in [100000, 5000000].
bad/rater.rsl
bad/tables/SizeFactor.csv
good/rater.rsl
good/tables/SizeFactor.csv

E-DIV-ZERO

A divisor must exclude zero. A division by zero stops a rating. The check works out the range of every divisor from the ranges of the inputs it reads. It reports any divisor whose range includes 0. A rater that passes divides safely for every input it accepts. You learn about the zero case when you write the rater, before a quote fails on it. Example The input employees accepts 0, and the premium divides payroll by it.
Fix Start the range of employees at 1, so the divisor stays above zero. Where the filing rates a zero count, guard the division: if self.employees != 0 then … else ….
bad/rater.rsl
good/rater.rsl

E-DATE-RANGE

A shifted date must stay in the calendar. add_days, add_months and add_years move a date by a whole number of days, months or years. RSL dates run from 0001-01-01 to 9999-12-31. The check works out the earliest and latest value of the date being moved. The moved date has to stay inside the calendar for every one of them. A Date input accepts any date in the calendar until you bound it. A shift back from the first year, or forward from the last, leaves the calendar, so that rating would stop. The bound also documents the input. A policy effective date has a real earliest value, and stating it rejects a mistyped year before rating starts. Example The rater moves effective back three years, and the input accepts dates as early as 0001-01-01.
Fix Give the input an earliest date. An underscore leaves the other side open. A forward shift, such as add_years(self.effective, 1), needs a latest date in the same way: where self in [_, 2100-12-31].
bad/rater.rsl
good/rater.rsl

E-EMPTY-FOLD

Max, min and avg need one element. max, min and avg over a list give the largest, smallest or average value of its elements. An empty list has zero elements, so its largest value is undefined. sum and count are different: they give 0 for an empty list. The check asks every list read by max, min or avg to hold at least one element. A request with an empty list then stops at the input, with a clear reason, before rating starts. Example The list of locations accepts zero elements, and max reads it.
Fix Ask for at least one location in the list’s own range. Where the filing rates a policy with an empty list, guard the read: if len(self.locations) > 0 then max(…) else 0.
Other causes
  • max, min, avg or sum folds over a narrowed enum that keeps zero members. The message reads `NoPeril` has no members, so the fold has no operands.
bad/rater.rsl
good/rater.rsl

E-QUADRATIC

Walk a list once, on its parent. A calculation on a list element runs once for each element. When that calculation also walks the whole list, a list of 10 costs 100 steps and a list of 1,000 costs a million. The check reports such a walk when its result is the same for every element. The same total computed many times is wasted work, and it hides a value worth naming. Computed once on the parent record, it gets a name that every element reads. A walk whose result differs by element passes the check. rslc check prints the cost of each outcome, and such a walk shows there as a squared term: |Policy.locations|^2. Example Each location compares its payroll with the policy total, and each one adds up every location to get that total.
Fix Compute the total once on Policy, and read it from each location as Policy.total_payroll.
Other causes
  • A fold inside another fold over the same list repeats a walk, as in count(self.locations as l, l.payroll * 2 > sum(self.locations as x, x.payroll)). Give the inner fold its own calculation and read that.
bad/rater.rsl
good/rater.rsl

E-MAP-DOMAIN

A map read must name a defined member. A map is a calculation with one value for each member of an enum: rate[c: Coverage]. A map over a narrowed enum, such as rate[c: StandardCoverage], has values for the narrowed members only. A read names the member it wants: self.rate[@Coverage.general] or self.rate[self.coverage]. The check works out which members the key of each read can be. Every one of them must be a member the map defines. A read at any other member has an undefined value, so the rating would stop. The message lists the members the read can reach and the members the map defines. Example rate has values for general and professional. The input coverage also accepts abuse, and the premium reads rate at that input.
Fix Type the input with the narrowed enum, so the key stays inside the map. Where the filing rates the other members elsewhere, test the key first: if self.coverage in StandardCoverage then self.rate[self.coverage] else …. A match on the key with an arm for abuse works the same way.
Other causes
  • A read leaves the key out, as in self.rate. Name the member: self.rate[c].
  • A map or a fold names a type that is a number or a band, as in rate[c: SizeBand]. Key it by an enum or a narrowed enum.
  • A map-valued table column is read with an input as its key, as in lookup!(T, …).rate[self.coverage]. Read the column into a map first, and index that map.
  • A map-valued table column is read with a member or a binder of another enum.
  • A map-valued table column is read at a member that the column leaves out.
  • A plain table column is read with a subscript. Remove the […].
bad/rater.rsl
good/rater.rsl