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 asPolicy.fee -> Policy.premium -> Policy.fee.
Example
The fee is a share of the premium, and the premium includes the fee.
base holds the premium before the fee.
- 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
whencondition of a follow-up input reads that same input. Write the condition from inputs that are asked earlier.
Complete files
Complete files
bad/rater.rsl
good/rater.rsl
E-OPTION-MATCH
An optional value needs both match arms. AnOption 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.
None arm with the value the filing gives when the answer is absent. A _ arm also covers the missing case.
Complete files
Complete files
bad/rater.rsl
good/rater.rsl
E-MATCH
A match needs an arm for every value. Amatch 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.
high. Where the filing gives one value to every remaining member, end the match with a _ arm.
- A
matchon aBoolhas one arm. The message readsmatch on Bool must cover both true and false.
Complete files
Complete files
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.
max(…, 0). Where the filing asks for at least one location, narrow the list in its place: where len(self) in [1, 10].
- The limit is a calculation that can give a fraction, such as
len(self.locations) / 2. Make it whole withfloor(…). - The limit is a literal such as
-1or2.5. The message readstopn limit must be a nonnegative whole number. - The call has two arguments and leaves the limit out. The message reads
topn requires a limit.
Complete files
Complete files
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.
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.
- 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
otherwiserow. - A key is a
Code, which accepts any text that fits its pattern. Type the inputKey(Table)to accept the filed codes only, or add anotherwiserow.
Complete files
Complete files
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.
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].
- 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
0or2.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,ceilingandfloor.
Complete files
Complete files
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.
- A whole exponent comes with a scale and a mode, as in
pow(x, 2, 4, half_up). The power is exact: writeround(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.
Complete files
Complete files
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 ofSizeBand start at 1, and the input employees accepts 0.
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.
- The key can pass the top of the last band. Open the last band, as in
250.., or clamp the key withmin. - 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 withE-LOOKUP-GAP.
Complete files
Complete files
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.
WageBand = IntBands(key: unrounded; 0, 30000, 60000..).
Complete files
Complete files
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.
where self in [100000, 5000000].
Complete files
Complete files
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 inputemployees accepts 0, and the premium divides payroll by it.
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 ….
Complete files
Complete files
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.
add_years(self.effective, 1), needs a latest date in the same way: where self in [_, 2100-12-31].
Complete files
Complete files
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.
if len(self.locations) > 0 then max(…) else 0.
max,min,avgorsumfolds over a narrowed enum that keeps zero members. The message reads`NoPeril` has no members, so the fold has no operands.
Complete files
Complete files
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.
Policy, and read it from each location as Policy.total_payroll.
- 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.
Complete files
Complete files
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.
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.
- 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
[…].
Complete files
Complete files
bad/rater.rsl
good/rater.rsl