> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# relativities.analysis

> Compare policy premiums and summarize weighted segments and scenarios.

*Generated from the package's Python API.*

Compare carrier premiums on explicit populations and summarize them in pandas.

These functions analyze retained rating outputs locally. Coverage and premium
bases are caller-supplied evidence: equality of their labels establishes a
declared match, rather than independent verification of policy wording.
Amounts retain their supplied premium term. Annualization and currency
conversion belong to the program's explicit output adapter.

`compare` pairs two programs on a population and `summarize` totals the
pairs by segment, with optional percentiles, change buckets and premium per
unit of exposure. `rateability` counts how much of the population each
program rated. `policy_draw_ranges` and `group_draw_ranges` give the spread
of a study's draws. `rank` and `rank_summary` place several programs side
by side. `search_lever` finds the value of one rating parameter that meets a
premium target. `calibrate_weights` post-stratifies or rakes policy-count
weights to target counts. `double_lift` compares each program's premium
with actual losses, grouped by price ratio. `PremiumAnalysis` and `PremiumRanking` offer the
same calculations on typed records.

## Calibration

```python theme={null}
class Calibration(
    weights: tuple[Decimal, ...],
    targets: tuple[CalibrationTarget, ...],
    method: Literal['post-stratification', 'raking'],
    converged: bool,
    iterations: int,
    effective_size: Decimal | None,
    min_factor: Decimal | None,
    max_factor: Decimal | None,
    analysis: PremiumAnalysis | None = None,
)
```

The result of a weight calibration.

`weights` holds one calibrated weight per row, in input order.
`method` is `post-stratification` for one attribute and `raking` for
several. `effective_size` is (Σw)² / Σw², the number of equally weighted
rows carrying the same information (Kish); `min_factor` and `max_factor` are the
smallest and largest ratio of calibrated to source weight among rows with
source weight. `analysis` is the reweighted analysis when called through
`PremiumAnalysis.calibrate`.

## CalibrationTarget

```python theme={null}
class CalibrationTarget(
    attribute: str,
    value: Fact,
    source: Decimal,
    target: Decimal,
    achieved: Decimal,
    residual: Decimal,
)
```

One target cell: its attribute and value, the source and calibrated weight
totals, the target, and the residual (achieved minus target).

## DrawRanges

```python theme={null}
class DrawRanges(
    policies: tuple[PolicyDrawRange, ...],
    groups: tuple[GroupDrawRange, ...],
)
```

Draw quantiles per policy and per scenario/segment. They describe the
spread of simulated draws, rather than confidence intervals.

## GroupDrawRange

```python theme={null}
class GroupDrawRange(
    scenario_id: str,
    segments: Mapping[str, Fact],
    draw_count: int,
    paired_draw_count: int,
    paired_weight_share_min: Decimal | None,
    paired_weight_share_max: Decimal | None,
    baseline_premium: Mapping[Decimal, Decimal | None],
    candidate_premium: Mapping[Decimal, Decimal | None],
    delta: Mapping[Decimal, Decimal | None],
    portfolio_change_percent: Mapping[Decimal, Decimal | None],
    candidate_cheaper_share: Mapping[Decimal, Decimal | None],
    premium_cheaper_share: Mapping[Decimal, Decimal | None],
)
```

A scenario and segment's portfolio results across draws.

Each draw is summarized first; the mappings hold quantiles of those
per-draw results, keyed by level. `draw_count` counts draws holding the
group and `paired_draw_count` those with paired support.

## LeverSearch

```python theme={null}
class LeverSearch(
    value: Decimal,
    achieved: Decimal | None,
    target: Decimal,
    converged: bool,
    stop_reason: str,
    population_count: int,
    trace: tuple[LeverStep, ...],
)
```

The result of `search_lever`.

`value` is the rated value whose objective came closest to `target`,
and `achieved` that objective. `converged` says whether it is within
the tolerance; `stop_reason` is `tolerance`, `bracket` (the bracket
narrowed to one quantum, as rounding can make an exact match impossible) or
`max_iterations`. `population_count` is the fixed population: contexts
the reference and both bracket ends rated. `trace` lists every rated
value in order, with its support.

## LeverStep

```python theme={null}
class LeverStep(
    value: Decimal,
    achieved: Decimal | None,
    supported_count: int,
    lost_count: int,
)
```

One rated parameter value: the objective it achieved over the fixed
population, how many of those contexts it rated, and how many it lost.

## LiftGroup

```python theme={null}
class LiftGroup(
    scenario_id: str,
    draw_id: int,
    baseline_program: str,
    candidate_program: str,
    group: str,
    group_order: int,
    ratio_min: Decimal | None,
    ratio_max: Decimal | None,
    policy_count: int,
    weight: Decimal,
    weight_share: Decimal | None,
    baseline_premium: Decimal | None,
    candidate_premium: Decimal | None,
    losses: Decimal | None,
    baseline_loss_ratio: Decimal | None,
    candidate_loss_ratio: Decimal | None,
    baseline_loss_ratio_relativity: Decimal | None,
    candidate_loss_ratio_relativity: Decimal | None,
    claim_count: Decimal | None,
    credibility: Decimal | None,
    losses_excluded_count: int,
)
```

One scenario/draw's price-ratio group: premiums, losses and loss ratios.

`group` is `q1` upward for equal-weight groups, or a ratio bucket
label. `ratio_min` and `ratio_max` bound the observed candidate over
baseline ratios. Loss ratio relativities divide each program's group loss
ratio by its loss ratio over all groups. Claim and credibility fields fill
when the population carries claim counts. Empty groups hold missing values.

## PolicyChange

```python theme={null}
class PolicyChange(
    pair: QuotePair,
    status: ChangeStatus,
    delta: Decimal | None,
    delta_percent: Decimal | None,
)
```

A paired policy result, retaining its quotes, support and exclusion status.

`delta` and `delta_percent` use candidate minus baseline and stay
missing for excluded rows. `missing_source` denotes absent provenance.
The original exposure and quotes retain context, attributes and reasons.
Status is `paired`, `unrated`, `missing_source`, `invalid_premium`,
`missing_basis` or `basis_mismatch`.

## PolicyDrawRange

```python theme={null}
class PolicyDrawRange(
    scenario_id: str,
    submission_id: object,
    draw_count: int,
    paired_draw_count: int,
    baseline_premium: Mapping[Decimal, Decimal | None],
    candidate_premium: Mapping[Decimal, Decimal | None],
    delta: Mapping[Decimal, Decimal | None],
    delta_percent: Mapping[Decimal, Decimal | None],
)
```

One policy's spread across a scenario's draws.

Each mapping is keyed by quantile level and computed over the draws where
the policy was paired; each measure's quantiles are taken on their own.

## PolicyExposure

```python theme={null}
class PolicyExposure(
    key: PolicyKey,
    weight: Decimal = Decimal('1'),
    attributes: Mapping[str, Fact] = <factory>,
    profile_id: RecordId | None = None,
    exposure: Decimal | None = None,
    exposure_unit: str | None = None,
    losses: Decimal | None = None,
    claim_count: Decimal | None = None,
)
```

One policy context with a Decimal count weight and user segment facts.

`attributes` holds scalar facts such as state, exposure band and deductible.
`profile_id` names a repeated source archetype, when supplied. Weight one
represents one policy in the selected population, rather than earned exposure.
`exposure` is the policy's exposure base, such as payroll or vehicle years,
in `exposure_unit`: a denominator for premium per unit, separate from the weight.
`losses` and `claim_count` hold the policy's actual incurred losses and
claims over its experience period, for `PremiumAnalysis.double_lift`.
Attributes are copied and read-only. Missing facts use `None`.

## PolicyKey

```python theme={null}
class PolicyKey(
    submission_id: RecordId,
    scenario_id: str = 'baseline',
    draw_id: int = 0,
)
```

One submission in one scenario and draw; draw zero is deterministic.

Keys preserve user identity. `draw_id` is a nonnegative integer.
`scenario_id` defaults to `baseline` for a single supplied population.

## PolicyQuote

```python theme={null}
class PolicyQuote(
    key: PolicyKey,
    program: str,
    status: QuoteStatus,
    premium: Decimal | None = None,
    terms: PremiumTerms | None = None,
    source: RatingSource | None = None,
    reason_code: str | None = None,
    reason: str | None = None,
)
```

One user-supplied program result for a policy context.

Successful and unsuccessful statuses remain distinct. A supplied premium
uses `Decimal`; missing output uses `None`. Success with missing terms,
provenance or a nonpositive baseline remains visible as an exclusion.
Negative finite premiums also remain visible for analytical validation.
A failed quote's amount stays outside paired premium calculations.
Status is `succeeded`, `invalid_book`, `mapping_failed`,
`invalid_inputs`, `refused`, `rating_failed`, `prediction_failed` or
`read_failed`.

## PremiumAnalysis

```python theme={null}
class PremiumAnalysis(
    population: Iterable[PolicyExposure],
    quotes: Iterable[PolicyQuote],
    *,
    baseline: str,
    candidate: str,
)
```

Analyze user-supplied exposures and quotes through typed methods.

Supply unique `PolicyExposure` rows and at most one `PolicyQuote` per
context/program. Every quote belongs to the population. Exactly the two
named programs are accepted. Missing quotes retain their population rows.
The object snapshots the iterables and performs local analysis only.
It reads supplied actual terms and provenance, with data loading left to you.

Monetary methods reuse the dataframe `compare` and `summarize` kernels.
Exposure attributes hold policy-level segment facts; choose their definitions
explicitly for policies with multiple insured assets. Matching terms is subject to review.
Each attribute name keeps a consistent scalar kind across the population.

### PremiumAnalysis.pair\_results

```python theme={null}
def pair_results(self) -> tuple[QuotePair, ...]
```

Return every population exposure with its optional two quotes.

### PremiumAnalysis.from\_book

```python theme={null}
@classmethod
def from_book(
    cls,
    book: Book | Study,
    quotes: Iterable[PolicyQuote],
    *,
    baseline: str,
    candidate: str,
    segments: Mapping[str, str] | None = None,
    weight: str | None = None,
    profile: str | None = None,
    exposure: str | None = None,
    exposure_unit: str | None = None,
    losses: str | None = None,
    claim_count: str | None = None,
) -> PremiumAnalysis
```

Pair supplied typed quotes on every submission of a Book or Study.

`segments` maps attribute names to submission column names. `weight`
and `profile` name submission columns; omitted weight means one policy.
Aggregate asset-level facts onto submissions explicitly before calling.
Study populations use each realized scenario/draw's submission facts.
Missing quotes retain their population rows. Numeric dataframe scalars
become Python integers or Decimal; missing attributes become `None`.
Weights express policy counts. `exposure` names a submission column
holding each policy's exposure base, such as payroll, labelled by
`exposure_unit`; summaries then report premium per unit of exposure.
`losses` and `claim_count` name columns of each policy's actual
incurred losses and claims, which `double_lift` compares with premium.
Supply quotes corresponding to this book; matching identities alone
establishes membership, rather than rating fidelity.

### PremiumAnalysis.from\_market

```python theme={null}
@classmethod
def from_market(
    cls,
    results: pd.DataFrame,
    book: Book | Study,
    *,
    baseline: str,
    candidate: str,
    premium: str | Mapping[str, str],
    terms: Callable[[Mapping[str, Any]], PremiumTerms | None],
    segments: Mapping[str, str] | None = None,
    weight: str | None = None,
    profile: str | None = None,
    exposure: str | None = None,
    exposure_unit: str | None = None,
    losses: str | None = None,
    claim_count: str | None = None,
) -> PremiumAnalysis
```

Adapt policy-level Market.rate results and their Book or Study.

`premium` names the amount column, or maps each selected program to
its amount column. `terms` maps each successful result row to actual
PremiumTerms, or `None` for unknown terms. The callback receives a detached,
read-only row mapping; its errors propagate with policy/context/program
information. Failed quotes skip this callback. Term evidence may come
from output columns or an explicitly keyed source captured by the callback.

Each result's bundle\_id becomes its rater\_bundle provenance. Missing
provenance remains missing. Other programs are ignored; duplicate or
out-of-population selected results raise ValueError. Results and population
must share the same Book/Study context shape. Aggregate item-level results
explicitly before calling. Premium and weight numeric scalars become Decimal.
Segment, weight, profile, exposure and loss mappings follow from\_book. Correspondence
between the supplied results and book remains the caller's responsibility.

### PremiumAnalysis.check\_comparability

```python theme={null}
def check_comparability(self) -> tuple[TermsCheck, ...]
```

Return field-level missing and unequal actual terms per context.

### PremiumAnalysis.premium\_changes

```python theme={null}
def premium_changes(self) -> tuple[PolicyChange, ...]
```

Return typed dollar/percent changes, retaining every excluded row.

### PremiumAnalysis.with\_weights

```python theme={null}
def with_weights(self, weights: Mapping[PolicyKey, Decimal]) -> PremiumAnalysis
```

Return a pure mix scenario using an explicit weight for every context.

Weight keys must match the whole population. Quotes and all risk facts
stay unchanged. Input changes require newly supplied rated quotes.

### PremiumAnalysis.calibrate

```python theme={null}
def calibrate(
    self,
    targets: Mapping[str, Mapping[Any, Any]],
    *,
    tolerance: Decimal | int = Decimal('1E-9'),
    max_iterations: int = 100,
) -> Calibration
```

Reweight the population so its attribute counts meet the targets.

`targets` maps exposure attribute names to target counts by value, as in
`analysis.calibrate_weights`: one attribute is post-stratified exactly and
several are raked. The population must hold one scenario/draw. Returns the
`Calibration` with its diagnostics, whose `analysis` reuses every quote
with the calibrated weights. Changed risk facts still need rerating.

### PremiumAnalysis.to\_pandas

```python theme={null}
def to_pandas(self) -> pd.DataFrame
```

Return detached chart-ready policy changes with explicit source kinds.

Declared actual terms are compared through a canonical fingerprint;
field-level diagnostics stay available through `check_comparability`.
`missing_source` preserves absent user or bundle provenance.

### PremiumAnalysis.summarize

```python theme={null}
def summarize(
    self,
    *,
    by: Sequence[str] = (),
    percentiles: Sequence[Any] = (),
    buckets: Sequence[Any] = (),
) -> tuple[PremiumSummary, ...]
```

Return typed portfolio or segment statistics for each scenario/draw.

`by` names user exposure attributes. Supplied count weights apply once;
scenarios and draws stay separate. Dataframe kernel denominators, source
profile support and missing-statistic rules apply unchanged.
`percentiles` and `buckets` add the distribution of policy changes as
in `analysis.summarize`. When the population carries exposure, each
summary also reports premium per unit of exposure; every exposure must
share one unit.

### PremiumAnalysis.double\_lift

```python theme={null}
def double_lift(
    self,
    *,
    groups: int = 5,
    edges: Sequence[Any] = (),
    full_credibility: Decimal | int = 1082,
) -> tuple[LiftGroup, ...]
```

Compare each program's premium with actual losses, grouped by price ratio.

Requires losses on the population, through `from_book(losses=...)` or
`PolicyExposure.losses`. `groups`, `edges` and `full_credibility`
follow `analysis.double_lift`. Returns one `LiftGroup` per
scenario/draw and group, in ratio order.

### PremiumAnalysis.rateability

```python theme={null}
def rateability(self, *, by: Sequence[str] = ()) -> tuple[Rateability, ...]
```

Count how each program's quotes ended, per scenario/draw and segment.

Each entry gives the population count; for each program, how many
contexts succeeded, were ineligible (refused), unratable, unattempted
or other, with reason-code counts; and the comparison status counts.
Report it with every summary, so readers see a subset as a subset.

### PremiumAnalysis.draw\_ranges

```python theme={null}
def draw_ranges(
    self,
    *,
    by: Sequence[str] = (),
    quantiles: Sequence[Any] = (Decimal('0.1'), Decimal('0.5'), Decimal('0.9')),
    include_draw_zero: bool = False,
    paired_in_every_draw: bool = False,
) -> DrawRanges
```

Return draw quantiles per policy and per scenario/segment.

For a population from a `Study`: each policy's premiums and changes
across its paired draws, and each scenario and segment's totals and
portfolio change across draws, summarized draw by draw first. Draw 0
(the reported book) is left out unless `include_draw_zero`.
`paired_in_every_draw` keeps only policies paired in every draw of
their scenario. These describe simulated spread, rather than confidence
intervals; see `analysis.policy_draw_ranges` and
`analysis.group_draw_ranges`.

## PremiumRanking

```python theme={null}
class PremiumRanking(
    population: Iterable[PolicyExposure],
    quotes: Iterable[PolicyQuote],
    *,
    programs: Sequence[str],
)
```

Rank two or more programs' quotes on every policy of a population.

Supply unique `PolicyExposure` rows and at most one `PolicyQuote` per
context and program; every quote belongs to the population and to one of
`programs`. The first program is the reference for `ratio_to_first`.
Quotes are paired on the same rules as `PremiumAnalysis`: a quote counts
when it succeeded, has provenance, a premium of zero or more and complete
terms that match the context's reference terms. The object snapshots its
inputs and analyzes locally.

### PremiumRanking.from\_market

```python theme={null}
@classmethod
def from_market(
    cls,
    results: pd.DataFrame,
    book: Book | Study,
    *,
    programs: Sequence[str],
    premium: str | Mapping[str, str],
    terms: Callable[[Mapping[str, Any]], PremiumTerms | None],
    segments: Mapping[str, str] | None = None,
    weight: str | None = None,
    profile: str | None = None,
) -> PremiumRanking
```

Adapt policy-level `Market.rate` results for several programs.

Arguments follow `PremiumAnalysis.from_market`, with `programs`
naming every program to rank, in order; `premium` names one amount
column or maps each program to its column.

### PremiumRanking.to\_pandas

```python theme={null}
def to_pandas(self, *, complete_only: bool = True) -> pd.DataFrame
```

Return one row per policy context and program, as `analysis.rank`.

Provenance appears as `source_id`, and a missing one as
`missing_source`. `complete_only` is as in `analysis.rank`.

### PremiumRanking.summarize

```python theme={null}
def summarize(self, *, by: Sequence[str] = ()) -> tuple[RankSummary, ...]
```

Return each program's competitive position per scenario/draw and segment.

Shares and totals use complete contexts and the population's count
weights; see `analysis.rank_summary`.

## PremiumSummary

```python theme={null}
class PremiumSummary(
    scenario_id: str,
    draw_id: int,
    segments: Mapping[str, Fact],
    baseline_program: str,
    candidate_program: str,
    currency: str | None,
    premium_basis: str | None,
    population_count: int,
    paired_count: int,
    population_weight: Decimal,
    paired_weight: Decimal,
    paired_weight_share: Decimal | None,
    baseline_premium: Decimal | None,
    candidate_premium: Decimal | None,
    delta: Decimal | None,
    mean_delta: Decimal | None,
    portfolio_change_percent: Decimal | None,
    candidate_cheaper_share: Decimal | None,
    tie_share: Decimal | None,
    profile_count: int,
    paired_profile_count: int,
    premium_cheaper_share: Decimal | None = None,
    delta_percent_percentiles: Mapping[Decimal, Decimal | None] = <factory>,
    bucket_shares: Mapping[str, Decimal | None] = <factory>,
    bucket_premium_shares: Mapping[str, Decimal | None] = <factory>,
    exposure_unit: str | None = None,
    exposure_total: Decimal | None = None,
    baseline_premium_per_exposure: Decimal | None = None,
    candidate_premium_per_exposure: Decimal | None = None,
    exposure_excluded_count: int = 0,
)
```

One scenario/draw and segment's weighted premium statistics.

Monetary values and shares use Decimal; excluded amounts use `None`.
Counts include zero-weight rows; profile counts describe positive-weight
support. `segments` retains the selected grouping values.
`premium_cheaper_share` is the share of weighted baseline premium where the
candidate is cheaper. Percentiles are keyed by level; bucket shares by the
bucket's label, such as `"-10_to_0"`. Exposure fields are filled when the
population carries exposure, and stay empty otherwise.

## PremiumTerms

```python theme={null}
class PremiumTerms(currency: str, premium_basis: str, values: Mapping[str, Fact])
```

Actual rated terms, their currency and the premium's stated basis.

`values` contains explicit scalar terms, such as `property.deductible` and
`liability.limit`. Monetary terms use `Decimal` in `currency`.
Supply a shared field vocabulary across programs. Empty maps, blank text,
missing fields and `None` terms produce incomplete comparisons.
Numeric Decimal and integer terms compare numerically; booleans stay distinct.
Matching supplied terms establishes declared equality, subject to review.

## QuotePair

```python theme={null}
class QuotePair(
    exposure: PolicyExposure,
    baseline: PolicyQuote | None,
    candidate: PolicyQuote | None,
)
```

One population exposure and its optional baseline and candidate quotes.

## RankSummary

```python theme={null}
class RankSummary(
    scenario_id: str,
    draw_id: int,
    segments: Mapping[str, Fact],
    program: str,
    population_count: int,
    complete_count: int,
    cheapest_share: Decimal | None,
    cheapest_premium_share: Decimal | None,
    premium_total: Decimal | None,
    ratio_to_first: Decimal | None,
)
```

One program's competitive position in a scenario/draw and segment.

Shares and totals use complete contexts, where every program quoted.
`cheapest_share` splits ties equally; `cheapest_premium_share` weights
each context by the first program's premium; `ratio_to_first` divides the
program's weighted premium total by the first program's.

## Rateability

```python theme={null}
class Rateability(
    scenario_id: str,
    draw_id: int,
    segments: Mapping[str, Fact],
    population_count: int,
    baseline: StatusCounts,
    candidate: StatusCounts,
    comparison: Mapping[str, int],
)
```

How much of a scenario/draw and segment each program rated and compared.

`comparison` counts each comparison status, such as `paired` or
`basis_mismatch`; `missing_source` covers absent provenance.

## RatingSource

```python theme={null}
class RatingSource(kind: Literal['rater_bundle', 'user_supplied'], identifier: str)
```

The supplied quote's source kind and immutable identifier or version.

`rater_bundle` identifies a compiled bundle. `user_supplied` identifies
supplied quote evidence, such as a versioned file or quote reference.
This records provenance; the analyst establishes the source's fidelity.

## StatusCounts

```python theme={null}
class StatusCounts(
    program: str,
    succeeded: int,
    ineligible: int,
    unratable: int,
    not_attempted: int,
    other: int,
    reason_codes: Mapping[str, int],
)
```

How one program's quotes ended for a group of policy contexts.

`ineligible` counts refusals; `unratable` counts invalid books,
mapping and input failures and rating, read or prediction failures;
`not_attempted` counts contexts lacking a quote. `reason_codes` counts
the reason codes of every unsuccessful quote.

## TermDifference

```python theme={null}
class TermDifference(
    field: str,
    baseline: Fact,
    candidate: Fact,
    kind: Literal['missing', 'mismatch'],
)
```

An actual term's supplied values and its missing or mismatch reason.

## TermsCheck

```python theme={null}
class TermsCheck(key: PolicyKey, differences: tuple[TermDifference, ...])
```

Field-level term differences for one policy context.

`matches` describes terms alone. Pricing and quote-status checks occur
in `premium_changes`; matching terms alone establishes a declared basis.

### TermsCheck.matches

```python theme={null}
@property
def matches(self) -> bool
```

Whether the supplied term sets are complete and equal.

## calibrate\_weights

```python theme={null}
def calibrate_weights(
    rows: pd.DataFrame,
    *,
    targets: Mapping[str, Mapping[Any, Any]],
    weight: str | None = None,
    tolerance: Decimal | int = Decimal('1E-9'),
    max_iterations: int = 100,
) -> Calibration
```

Calibrate row weights so each attribute's weighted counts meet its targets.

`rows` holds one row per policy (or policy context) with the attribute
columns. `targets` maps each attribute column to the target count for
every value it takes, such as `{"band": {"small": 600, "large": 400}}`; use
`None` as the key for rows whose value is missing. `weight` names the
source weight column; omitted means one per row.

One attribute is post-stratified exactly. Several attributes are raked:
each is matched in turn until every relative residual is within
`tolerance` or `max_iterations` passes are done; `converged` says
which. Raking needs every attribute's targets to share one total.

A target value absent from the rows, a row value that lacks a target, or a
positive target with zero source weight raises `ValueError`, rather than
dropping a constraint or inventing rows. Arithmetic uses a
fixed 38-digit decimal context. The rows themselves are left unchanged.

The methods follow Deming and Stephan (1940) for raking and Deville and
Särndal (1992) for calibration estimators. `effective_size` is Kish's
(1965) effective sample size.

## compare

```python theme={null}
def compare(
    results: pd.DataFrame,
    *,
    baseline: str,
    candidate: str,
    premium: str,
    basis: Sequence[str],
    population: pd.DataFrame,
) -> pd.DataFrame
```

Pair two programs on every row of an explicit population.

`results` is the submission-level table from `Market.rate`. `premium`
names its amount column. `basis` names a nonempty sequence of columns
whose nonblank string values must match, such as currency, term, coverage
limits, deductibles and valuation basis, or a reviewed terms fingerprint.
Include `currency` and `premium_basis` in `basis` to state units.
Each successful result also needs its immutable `bundle_id`.

`population` has unique `submission_id` rows, plus `scenario_id` and
`draw_id` together for study results. Include weights, segment labels and
source profile IDs here. Every selected result must belong to it.
Every population row remains visible, including missing results.
A study needs its full contextual population.
A policy with multiple insured assets needs an explicitly selected policy-level segment.

Returns population columns, each program's `status`, `reason_code`,
`reason`, `bundle_id`, `premium` and basis columns suffixed with
`_baseline` or `_candidate`. `comparison_status` records `paired`,
`unrated`, `missing_bundle`, `invalid_premium`, `missing_basis` or
`basis_mismatch` in that priority. Only paired rows have `delta` and
`delta_percent` (candidate minus baseline); the baseline must be positive.
Missing program rows have `missing_result` status and missing amounts.
Refused premiums remain missing in the paired view even if supplied.
Arithmetic uses a fixed 38-digit decimal context, independent of the caller.

Malformed keys, duplicate results, absent columns, colliding population
columns and blank program names raise `ValueError` before analysis.
`baseline_program` and `candidate_program` retain the program names.
Inputs remain unchanged. Group the returned rows with `summarize` or pandas.

## double\_lift

```python theme={null}
def double_lift(
    pairs: pd.DataFrame,
    *,
    losses: str,
    weight: str | None = None,
    claim_count: str | None = None,
    groups: int = 5,
    edges: Sequence[Any] = (),
    full_credibility: Decimal | int = 1082,
) -> pd.DataFrame
```

Compare each program's premium with actual losses, grouped by price ratio.

This is the double lift chart of CAS Monograph 5. `pairs` holds
`analysis.compare` rows whose population carries each policy's actual
losses in the `losses` column, in the premium's currency. Rows sort by
the price ratio: candidate premium over baseline premium. `groups` splits
them into that many groups of equal policy-count weight, labelled `q1`
upward; each row joins the group holding the midpoint of its weight, and
ties sort by submission ID. `edges` replaces those groups with
fixed ratio edges, such as `(1,)` for cheaper and dearer, labelled like
`summarize` buckets.

Each group reports its policy count and weight, both programs' weighted
premium, weighted losses and each program's loss ratio. Each loss ratio
also appears as a relativity to that program's loss ratio over all groups.
A baseline relativity that rises with the ratio means the candidate's
rating tracks these losses more closely. A flat one means the baseline's
rating tracks them at least as well. Low baseline loss ratios where the
candidate is cheaper mark profitable business at risk.

`claim_count` names a column of claim counts. Each group then reports
its weighted claims and a square-root-rule credibility against
`full_credibility` claims; 1,082 is the classical standard for
frequency. `weight` names a policy-count weight; omitted means one per
row. Studies keep each scenario and draw separate.

Paired positive-weight rows with a missing loss or claim count are
excluded and counted in `losses_excluded_count`. Empty groups keep their
row with missing statistics. Arithmetic uses a fixed 38-digit decimal
context. Invalid weights, premiums, losses or columns raise `ValueError`.

## group\_draw\_ranges

```python theme={null}
def group_draw_ranges(
    pairs: pd.DataFrame,
    *,
    by: str | Sequence[str] = (),
    weight: str | None = None,
    quantiles: Sequence[Any] = (Decimal('0.1'), Decimal('0.5'), Decimal('0.9')),
    include_draw_zero: bool = False,
    paired_in_every_draw: bool = False,
) -> pd.DataFrame
```

Portfolio and segment results across the draws of each scenario.

Summarizes each draw with `analysis.summarize` (`by` and `weight`
as there), then returns one row per scenario and segment: `draw_count`
(draws holding the group), `paired_draw_count` (draws with paired
support), the lowest and highest paired weight share, and draw quantiles
of the baseline and candidate premium totals, delta, portfolio change and
the cheaper shares, as `portfolio_change_percent_p50` and so on. Totals
are summed within each draw before quantiles are taken; per-policy
quantiles stay separate from these totals.

`paired_in_every_draw` keeps only the policies paired in every draw of
their scenario, so each draw compares the same policies. Draw 0 is left out
unless `include_draw_zero` is true. Scenarios stay separate. These are
spreads of simulated draws, rather than confidence intervals.

## policy\_draw\_ranges

```python theme={null}
def policy_draw_ranges(
    pairs: pd.DataFrame,
    *,
    quantiles: Sequence[Any] = (Decimal('0.1'), Decimal('0.5'), Decimal('0.9')),
    include_draw_zero: bool = False,
) -> pd.DataFrame
```

Each policy's spread across the draws of each scenario.

`pairs` is `analysis.compare` output for a `Study`. Returns one row
per scenario and submission: `draw_count`, `paired_draw_count`, and
for baseline premium, candidate premium, delta and percentage change the
draw quantiles over the draws where the policy was paired, as
`premium_baseline_p10`, `delta_percent_p90` and so on. Each measure's
quantiles are computed on their own: the quantile of a change differs from the
change between quantiles. Each draw counts once, and a quantile is the
smallest observed value whose share of draws reaches the level.

Draw 0 is the reported book, whose blanks stay blank, so it is left out
unless `include_draw_zero` is true. Scenarios stay separate. These are
spreads of simulated draws, rather than confidence intervals.

## rank

```python theme={null}
def rank(
    results: pd.DataFrame,
    *,
    programs: Sequence[str],
    premium: str,
    basis: Sequence[str],
    population: pd.DataFrame,
    complete_only: bool = True,
) -> pd.DataFrame
```

Rank every program's premium on each row of an explicit population.

`results` is `Market.rate` output; `programs` names two or more of
its programs, in order; `premium` names the amount column; `basis`
names the comparison-term columns as in `compare`, including `currency`
and `premium_basis`. `population` has one row per submission (or
study context), as in `compare`; every population row stays visible.

Returns one row per population row and program, in population order and
then program order, with the population columns and: `rank_status`
(`quoted`, `missing_result`, `unrated`, `missing_bundle`,
`invalid_premium`, `missing_basis` or `basis_mismatch`), the result's
`status`, `reason_code`, `reason`, `bundle_id` and `premium`;
`rank` (1 is cheapest; tied programs share the lowest rank),
`cheapest_programs` (every tied cheapest program), `gap_to_cheapest`
and `gap_to_cheapest_percent`; `quoted_count`; and `market_status`:
`complete` when every program quoted, `partial` when some did, and
`empty` when every program failed to quote.

The first program whose quote is otherwise usable sets the context's
comparison basis; another program with a different basis is
`basis_mismatch` there, and the context is partial. A premium may be zero
or more; a refused or failed quote keeps a missing premium. By
default only complete contexts are ranked; `complete_only=False` ranks
the quoted programs of partial contexts too. Contexts and scenarios stay
separate. Arithmetic uses a fixed 38-digit decimal context.

## rank\_summary

```python theme={null}
def rank_summary(
    ranked: pd.DataFrame,
    *,
    by: str | Sequence[str] = (),
    weight: str | None = None,
) -> pd.DataFrame
```

Summarize `rank` rows per context, segment and program.

Returns one row per scenario/draw, segment and program, with
`population_count`, `complete_count` (contexts where every program
quoted), and over complete contexts: `cheapest_share`, the weighted share
of contexts where the program is cheapest, with ties split equally;
`cheapest_premium_share`, the same share weighted by the first program's
premium; `premium_total`, the program's weighted premium total; and
`ratio_to_first`, that total divided by the first program's. `weight`
names a finite nonnegative policy-count weight column; omitted means one
per population row. Shares are missing when the complete contexts carry
zero total weight.

## rateability

```python theme={null}
def rateability(pairs: pd.DataFrame, *, by: str | Sequence[str] = ()) -> pd.DataFrame
```

Count how each program's quotes ended, per context and chosen segments.

`pairs` is the output of `analysis.compare` (or
`PremiumAnalysis.to_pandas`). Returns one row per scenario/draw and
segment group: `population_count`; for each side, `baseline_…` and
`candidate_…`, its program name and how many contexts `succeeded`,
were `ineligible` (`refused`), `unratable` (`invalid_book`,
`mapping_failed`, `invalid_inputs`, `rating_failed`, `read_failed`
or `prediction_failed`), `not_attempted` (absent result) or ended in any
`other` status, plus `…_reason_codes`, a mapping from each reason code
of the unsuccessful quotes to its count; and one
`comparison_<status>` count per comparison status. A missing bundle or
source counts as `comparison_missing_source`.

Counts are of population contexts, whatever their weight. Report them with
every result, so readers see a subset of the book as a subset.

## search\_lever

```python theme={null}
def search_lever(
    make_program: Callable[[Decimal], Program],
    *,
    reference: Program,
    book: Book | Study,
    low: Decimal | int,
    high: Decimal | int,
    target: Decimal | int = Decimal('1'),
    premium: str = 'premium',
    objective: Callable[[Mapping[ContextKey, Decimal], Mapping[ContextKey, Decimal]], Decimal | None] | None = None,
    tolerance: Decimal | int = Decimal('0.0001'),
    quantum: Decimal | int = Decimal('0.0001'),
    max_iterations: int = 30,
) -> LeverSearch
```

Find the parameter value at which a program meets a premium target.

`make_program(value)` returns a new `Program` for one value of the
parameter, usually by editing a table with `rater.with_tables` and
`program.with_rater`; it must build a fresh program each call, because
the search closes it after rating. `reference` is the program to compare
against, such as a competitor's; it is rated once and left open.

The objective is computed over a fixed population: the contexts that the
reference and the programs at both `low` and `high` rated. By default
it is the ratio of the candidate's premium total to the reference's, so
`target=1` finds premium parity. A custom `objective` receives the two
programs' premiums by context, over the contexts both rated, and returns a
`Decimal`. If a value loses support, meaning a context it stops rating, its
step records how many contexts it lost.

The search bisects between `low` and `high`, rounding each value to
`quantum`. The objective at the two ends must straddle `target`, or it
raises `ValueError`. It stops when the objective is within `tolerance`
of the target, when the bracket narrows to one quantum, or after
`max_iterations` values; rounding makes premiums step, so the closest value
may miss the target slightly. Values, targets and tolerances are `Decimal` or `int`.
Each step recompiles one rater and rates the whole book.

## summarize

```python theme={null}
def summarize(
    pairs: pd.DataFrame,
    *,
    by: str | Sequence[str] = (),
    weight: str | None = None,
    profile: str | None = None,
    percentiles: Sequence[Any] = (),
    buckets: Sequence[Any] = (),
    exposure: str | None = None,
    exposure_unit: str | None = None,
) -> pd.DataFrame
```

Summarize `analysis.compare` rows by context and chosen segments.

`by` names population columns, such as state, length band or deductible.
Study `scenario_id` and `draw_id` always stay separate, even with empty
`by`. Missing segment values form their own group. `weight` names a
finite nonnegative policy-count weight; omitted means one per population
row. Pure mix changes reuse the same paired premiums with different weights.
A policy's annual premium is weighted once, rather than multiplied by its
earned exposure. Changed rating inputs require rerating before comparison.

Returns population and paired counts/weights; paired baseline and candidate
premium totals; total and mean delta; portfolio change as 100 times the ratio
of weighted premium totals minus one; weighted candidate-cheaper and tie
shares; and `premium_cheaper_share`, the share of weighted baseline premium
in policies where the candidate is cheaper. `profile` adds distinct
source-profile counts for all and paired rows, counting positive-weight rows
with known profiles. Counts describe support, rather than statistical
independence or credibility.

`percentiles` lists levels between 0 and 1, such as `(0.1, 0.5, 0.9)`,
and adds weighted percentiles of each policy's percentage change as
`delta_percent_p10` and so on: the smallest observed change whose
cumulative weight share reaches the level, so each one is an observed change.
`buckets` lists increasing edges in percent, such as `(-10, 0, 10)`,
and adds each bucket's share of weight (`bucket_share_lt_-10`,
`bucket_share_-10_to_0`, `bucket_share_0_to_10`,
`bucket_share_ge_10`) and of weighted baseline premium
(`bucket_premium_share_…`). Buckets include their lower edge and exclude
their upper edge.

`exposure` names a population column holding each policy's exposure, such
as payroll or vehicle years, and `exposure_unit` labels it, such as
`"USD payroll"`. The summary then adds `exposure_total` and each program's
premium per unit of exposure (weighted premium over weighted exposure) over
paired policies with positive exposure, and `exposure_excluded_count` for
paired policies whose exposure is missing or zero. Exposure is a
denominator, separate from the weights. A negative exposure raises.

Premium statistics use positive-weight paired rows. An empty paired group
has missing premium statistics, rather than zero totals. An all-zero-weight
group retains counts and has missing shares. Arithmetic uses a fixed
38-digit decimal context. Scenario/draw rows remain individual realizations;
this function produces descriptive results rather than confidence intervals.
Each group requires a single program pair, currency and premium basis.
Their labels are retained in the summary; select consistent units first.
Invalid weights, duplicate policy/context keys, invalid paired premiums or
unknown columns raise `ValueError`. The input remains unchanged.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.