D46-F02-A04 / Complete engineering topic

Contingently Issuable Shares: Current Conditions, No Forecasts

Route legal status, freeze reporting-date conditions, weight effective terms, and test contingent shares for diluted EPS without hindsight.

D46 · EARNINGS AND PER-SHARE ANALYT…
D46-F02-A04Canonical / Tested / Open
D46 / D46-F02

A company promises five million shares if earnings reach USD 30 million and its share price reaches USD 20. The award does not end until next year. At this year-end, should diluted earnings per share include zero shares, five million shares, or a probability-weighted estimate?

The answer is not a forecast. The diluted-EPS calculation asks how many shares would be issuable if the reporting date were the end of the contingency period. It uses conditions as they stand now and assumes no future improvement.

That one idea turns a vague earn-out into an auditable sequence:

current conditionseligible sharestime weightingEPS control\text{current conditions} \rightarrow\text{eligible shares} \rightarrow\text{time weighting} \rightarrow\text{EPS control}

First decide whether the shares are still contingent

Shares issuable for little or no cash consideration belong in Basic EPS once all necessary issuance conditions are actually satisfied and the shares are no longer contingent. Before that point, they are potential ordinary shares and may enter diluted EPS.

The distinction is about substance, not the printing date of a share certificate. A share can belong in Basic EPS before physical delivery if nothing material remains contingent. Conversely, management’s confidence that a target will be achieved does not make a pending share ordinary.

This package deliberately accepts only an unresolved tranche whose contingency end is after the reporting date. A resolved award must be routed to the Basic-EPS weighted-average-share calculation.

IAS 33 paragraphs 52–56 and FASB Statement No. 128 paragraphs 30–34 describe the current-status treatment for pending contingent shares.

Do not confuse a reporting-date counterfactual with legal satisfaction. An earnings threshold can pass at December 31 for diluted-EPS measurement while a multi-year agreement remains legally contingent. Conversely, once every necessary issuance condition is irrevocably satisfied, the shares move to the Basic-EPS timing analysis even if certificates have not yet been delivered.

Legal stateWhat to do
Unresolved substantive conditionRun the current-conditions diluted-EPS test
All necessary conditions satisfiedRoute to Basic EPS from the applicable satisfaction date
Only passage of time remainsDo not invent a performance contingency; determine the framework’s time/service treatment
Agreement cancelledExclude and preserve the cancellation’s effective date and source
Outstanding shares subject to recallTreat as a reverse contingency; do not run a direct-issuance shortcut

Test today’s state, not tomorrow’s expectation

The canonical algorithm supports three kinds of threshold:

  • earnings accumulated to the reporting date;
  • a period-end market price or the elapsed average required by the agreement;
  • another observable status, such as a count of operating locations.

Let current value vjv_j be tested against threshold hjh_j:

gj={1,vjhj0,vj<hjg_j=\begin{cases} 1,&v_j\ge h_j\\ 0,&v_j<h_j \end{cases}

When the agreement requires every condition:

G=j=1kgjG=\prod_{j=1}^{k}g_j

One failed condition makes G=0G=0. There is no “almost achieved” share count in this fixed all-or-nothing tranche.

Every observed value needs a measurement date and a knowledge cutoff. A December 31 calculation may use a subsequently finalized ledger amount that measures conditions at December 31, but it may not import a January market move or January store opening into the December 31 state. The implementation requires each measurement_date to equal period end and refuses evidence admitted after the declared knowledge_cutoff.

Rendering system map…

The flow has two independent gates. Passing the contractual conditions is necessary, but it does not replace the EPS-control test.

Different conditions use different measurements

An earnings target uses earnings to date, not a forecast of full-period or future-period earnings. A future share-price target uses period-end price when the contract specifies a point measurement. If the contract specifies an average over a period extending beyond the reporting date, the elapsed-period average is used.

For a condition unrelated to earnings or price, the calculation assumes that the present status remains unchanged through contingency end. Four stores open against a five-store threshold means four—not five because a signed lease makes the fifth likely.

Combined earnings and price conditions require both to pass. The implementation therefore records a measurement basis for each condition and rejects invalid pairings:

Condition kindPermitted basis
EarningsEARNINGS_TO_DATE
Market pricePERIOD_END_PRICE or ELAPSED_PERIOD_AVERAGE
Other statusCURRENT_STATUS

The source, unit, observed value, and threshold remain in the output so a reviewer can reproduce each Boolean result.

Weight the agreement, not the date the math happened to pass

Potential ordinary shares are generally weighted from the beginning of the period or, if later, the contingent agreement date. A pending condition that is met at year-end does not start exposure only on 31 December.

Let QQ be fixed tranche shares. Let ded_e be days from the later of period start and agreement date through period end, and dpd_p be total period days:

t=dedpt=\frac{d_e}{d_p} Qe=GQQ_e=GQ Qw=QetQ_w=Q_e t

For a full-year agreement, t=365/365=1t=365/365=1. For a 1 July agreement in 2025:

t=184/365t=184/365

A five-million-share tranche then contributes:

5m×184365=184m732.520548m5m\times\frac{184}{365} =\frac{184m}{73} \approx2.520548m

The calculation retains the exact fraction until presentation.

If terms change during the period, preserve execution and effective dates, then segment the agreement history using a documented legal and accounting analysis. This package starts the supplied complete segment no earlier than the later of period start, agreement date, and terms_effective_date, but it rejects unsegmented_terms_revision: true. An amendment does not automatically reset a pre-existing agreement’s weighting clock. A signed but not-yet-effective change is not backdated, and cancellation cannot erase the valid pre-cancellation segment without analysis.

Complete worked example

Assume:

ItemValue
Basic continuing-operations numeratorUSD 120m
Basic weighted-average shares50m
Fixed contingent tranche5m
Earnings to dateUSD 36m
Earnings thresholdUSD 30m
Period-end share priceUSD 24
Price thresholdUSD 20
ExposureFull year

Both conditions pass:

3630,2420,G=136\ge30,\qquad24\ge20,\qquad G=1

The full tranche is eligible and fully weighted:

Qw=5m×1=5mQ_w=5m\times1=5m

Basic EPS is:

EPSB=12050=125=2.40EPS_B=\frac{120}{50}=\frac{12}{5}=2.40

The candidate is:

EPSC=12050+5=24112.18EPS_C=\frac{120}{50+5} =\frac{24}{11} \approx2.18

Because 24/11<12/524/11<12/5, the tranche is dilutive and enters the standalone candidate.

Condition gates and weighted denominator bridge

The diagram’s central lesson is that the five million shares appear only after both conditions pass. The subsequent EPS comparison remains a separate control.

A failed condition is not partial dilution

Keep earnings at USD 36 million but change period-end share price to USD 19.

3630,19<2036\ge30,\qquad19<20

The AND gate fails:

G=1×0=0,Qw=0G=1\times0=0,\qquad Q_w=0

Candidate EPS equals Basic EPS because the denominator has not changed. The reason is one_or_more_conditions_not_met, not antidilution. That distinction is useful in reconciliations: the award is absent because it is not currently issuable, not because an eligible award failed the EPS control.

A loss period still needs the second gate

Return both conditions to passing values and change the numerator to a USD 120 million loss:

EPSB=120/50=2.40EPS_B=-120/50=-2.40 EPSC=120/55=24/112.18EPS_C=-120/55=-24/11\approx-2.18

The candidate is numerically greater. Adding shares makes loss per share smaller in magnitude, so the tranche is antidilutive and excluded. It would be wrong to include it merely because the contractual conditions pass.

Explore the transition in the interactive contingent-share lab. The lab exposes each gate separately and lets you change thresholds, exposure, tranche size, and profit or loss.

The implementation contract prevents quiet assumptions

Both implementations require:

  • a point-in-time as_of timestamp after period end;
  • a strict framework/application pair;
  • exact Basic numerator and denominator;
  • fixed tranche size, agreement date, complete-segment effective date, and knowledge cutoff;
  • complete conditions with unique IDs, units, sources, and period-end measurement dates;
  • the explicit NO_FUTURE_PROJECTION policy;
  • scope assertions for fixed shares, little or no cash, zero numerator effect, an unresolved contingency, and no unsegmented revision, cancellation, time-only condition, or reverse contingency;
  • finality and rounding policies.

The code parses every decimal string into a reduced rational number. Threshold comparisons use exact integer cross-products. Day weights and EPS candidates stay exact. Display rounding happens only after the classification.

See the Python implementation, TypeScript implementation, and shared fixtures.

What the tests establish

FixtureExpected result
Earnings 36/30 and price 24/205m weighted shares; 24/11; include
Same inputs for a 92-day U.S.-GAAP quarterFramework pair accepted; full-quarter 1/1 weight
Price 19/20Conditions not met; zero shares
Observed equals thresholdPass because comparison is GTE
USD 120m lossConditions pass; candidate antidilutive
Complete term segment effective 1 JulyExact 184/365 weight
Four stores against fivePresent-status condition fails
Elapsed average equals targetCondition passes
Nonfinal award dataSame math, provisional status

Negative tests reject malformed dates and decimals, unsupported forecasting or OR logic, wrong measurement bases, negative market/status values, incomplete sources, duplicate condition IDs, stale timestamps, and incompatible framework applications. They also reject post-period condition measurements, future or unsegmented terms, legal satisfaction, cancellation, time-only routing, and contingently returnable shares.

These tests prove that code matches the stated algorithm. They do not replace the accounting judgment required to interpret an agreement or choose the authoritative measurement.

IFRS and U.S. GAAP period boundaries

The core current-status rule is closely aligned across IAS 33 and the originating FASB guidance. Period application can differ.

IAS 33 determines potential ordinary shares independently for each period presented. FASB’s published contingent-share illustration calculates a multi-quarter result using quarterly contingent-share effects. The canonical function therefore accepts:

  • IFRS direct-period calculations;
  • one U.S. GAAP quarter at a time;
  • another direct period only when its policy is declared.

An upstream U.S. GAAP year-to-date component must aggregate verified quarterly results. The function refuses a label that would conceal the difference.

TopicIAS 33U.S. GAAP ASC 260
Resolved conditions and Basic EPSInclude from the date all necessary conditions are satisfiedASC 260-10-45-13 applies the same core Basic-EPS boundary
Earnings or price still unresolvedUse earnings to date or the required current-price measurement as if period end ended the contingency periodEvaluate the applicable quarter using current-period facts; do not project
Other conditionHold current status unchangedASC 260’s retail-site illustration applies present status, not expected openings
Annual/YTD diluted denominatorApply IAS 33 to each presented periodAggregate verified quarterly effects when ASC 260 requires quarterly averaging
Dilution controlContinuing-operations measure under IAS 33U.S.-GAAP continuing-operations control number

A filed agreement shows why effective dates matter

CVSL’s SEC-filed Third Amendment to Share Exchange Agreement records an amendment executed in October 2014 whose effectiveness depended on the planned offering. CVSL’s 2014 Form 10-K reports December 1, 2014 as the effective date and describes 25,240,676 second-tranche shares tied to specified acquisition or tender/exchange-offer events. Public SEC correspondence records the issuer’s ASC 260 analysis.

Those documents establish filed terms and the issuer-reported dates and share count. They do not establish that a future trigger occurred, that the shares were eventually issued, or that CVSL’s conclusion should be copied to a different agreement. The 5-million-share numerical example in this article is synthetic and author-derived; it is not CVSL data.

The dates show why a system must version agreements, not why every amendment restarts weighting. Determine the amendment’s legal effect, retain complete before/after segments, and reject an unsegmented revision rather than guessing.

Boundaries that matter

This fixed direct-share algorithm is not a universal performance-award engine.

A formula-based award may require a payout schedule with caps, floors, and interpolation before the current share count is known. A performance option first passes the contingent gate and then uses the treasury-share method. A contingent convertible uses the if-converted method after eligibility. An award with nonforfeitable dividend rights may be a participating security. A resolved condition moves the shares into Basic EPS from the appropriate date.

Those routes are intentional. Returning a precise result from the wrong method is more dangerous than rejecting the input.

Summary

Contingent-share dilution freezes the future at today’s facts. Evaluate each contractual condition with its required current measurement, require all conditions to pass, weight the eligible tranche from agreement exposure, and then apply the exact continuing-operations EPS control.

Do not forecast. Do not include a fraction of a fixed all-or-nothing tranche because attainment feels likely. Do not skip antidilution after eligibility.

The next tutorial is Participating-Security Two-Class Allocation, where the problem moves from potential shares to allocating earnings between ordinary shares and securities that already participate in dividends.

Contingent-share inclusion flow

This flow separates Basic-EPS routing, current-condition eligibility, instrument-specific method routing, and the final antidilution control.

Rendering system map…

Takeaway: current conditions decide eligibility; the instrument method and exact EPS comparison still decide the reported denominator effect.

References8 primary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

R01 — IAS 33 Earnings per Share, current standard page

  • Organization or authors: IFRS Foundation
  • Source type: Official standard overview and history
  • Publication or effective date: Standard 2026 Issued
  • Version: Page current when accessed
  • URL or DOI: https://www.ifrs.org/issued-standards/list-of-standards/ias-33-earnings-per-share.html/
  • Accessed: 2026-07-23
  • Jurisdiction: IFRS Accounting Standards
  • Supports: Scope, continuing-operations presentation, dilution definition, and explicit coverage of contingently issuable and contingently returnable shares.
  • Limitations: The public overview is not the full paragraph-level standard.

R02 — IAS 33 Earnings per Share, issued standard text

  • Organization or authors: IFRS Foundation
  • Source type: Official issued-standard PDF
  • Publication or effective date: 2022 issued collection
  • Version: IAS 33 in the linked collection
  • URL or DOI: https://www.ifrs.org/content/dam/ifrs/publications/pdf-standards/english/2022/issued/part-a/ias-33-earnings-per-share.pdf?bypass=on
  • Accessed: 2026-07-23
  • Jurisdiction: IFRS Accounting Standards
  • Supports: Paragraph 24 for satisfied, time-only, and contingently returnable shares; paragraphs 41–44 for antidilution; paragraphs 52–57 for current earnings, market price, multiple conditions, and instrument routing.
  • Limitations: Check the current issued standard and local endorsement for a live filing; IFRS 18 made later numerator-related amendments.

R03 — FASB Statement No. 128, Earnings per Share

  • Organization or authors: Financial Accounting Standards Board
  • Source type: Official originating accounting standard
  • Publication or effective date: February 1997
  • Version: Statement No. 128 PDF hosted by FASB
  • URL or DOI: https://storage.fasb.org/fas128.pdf
  • Accessed: 2026-07-23
  • Jurisdiction: United States GAAP
  • Supports: Originating Basic-EPS boundary, antidilution principle, current-condition rules, and the quarterly contingent-share illustration.
  • Limitations: The FASB Accounting Standards Codification is the current source of authoritative U.S. GAAP; paragraph references in this package use ASC 260 where available.

R04 — CVSL response to SEC comments on contingent shares

  • Organization or authors: CVSL Inc.; filed through the U.S. Securities and Exchange Commission
  • Source type: Public issuer correspondence
  • Publication or effective date: October 21, 2014
  • Version: Response concerning Form S-1 File No. 333-196155
  • URL or DOI: https://www.sec.gov/Archives/edgar/data/1403085/000110465914072781/filename1.htm
  • Accessed: 2026-07-23
  • Jurisdiction: United States
  • Supports: Accessible issuer application of ASC 260-10-45-13, 45-48, and 45-54; distinction between Basic and diluted treatment; present-status logic; no projection; reproduced ASC quarterly illustration.
  • Limitations: Issuer correspondence is not general accounting authority and does not prove a later trigger or issuance.

R05 — CVSL Third Amendment to Share Exchange Agreement

  • Organization or authors: CVSL Inc. and Rochon Capital Partners, Ltd.
  • Source type: SEC-filed legal agreement exhibit
  • Publication or effective date: Executed October 10, 2014; effectiveness tied to the planned offering
  • Version: Exhibit 4.1 filed December 3, 2014
  • URL or DOI: https://www.sec.gov/Archives/edgar/data/1403085/000110465914084657/a14-25446_1ex4d1.htm
  • Accessed: 2026-07-23
  • Jurisdiction: United States
  • Supports: Legal provenance of the amended second-tranche terms and the distinction between execution and effectiveness.
  • Limitations: Read with the complete agreement and later filings; the exhibit is not an EPS conclusion.

R06 — CVSL 2014 Form 10-K

  • Organization or authors: CVSL Inc.
  • Source type: SEC-filed annual report
  • Publication or effective date: Year ended December 31, 2014; filed March 2015
  • Version: Form 10-K
  • URL or DOI: https://www.sec.gov/Archives/edgar/data/1403085/000114420415016468/v401786_10k.htm
  • Accessed: 2026-07-23
  • Jurisdiction: United States
  • Supports: Issuer-reported December 1, 2014 amendment effective date, 25,240,676 second-tranche share count, trigger description, and issuer-reported EPS treatment.
  • Limitations: Entity-specific disclosure; it does not establish that a later contingency occurred or determine another issuer’s accounting.

R07 — Applied performance-unit disclosure

  • Organization or authors: DXP Enterprises, Inc.
  • Source type: SEC-filed financial-statement note
  • Publication or effective date: Quarter ended March 31, 2026
  • Version: Filed 2026
  • URL or DOI: https://www.sec.gov/Archives/edgar/data/896156/000143774926013892/R20.htm
  • Accessed: 2026-07-20
  • Jurisdiction: United States
  • Supports: Entity disclosure of period-end current-condition evaluation and antidilution exclusion.
  • Limitations: Entity-specific policy and instruments; not a substitute for ASC 260.

R08 — Applied loss-period and PSU disclosure

  • Organization or authors: Affirm Holdings, Inc.
  • Source type: SEC-filed financial-statement note
  • Publication or effective date: Quarter ended March 31, 2026
  • Version: Filed 2026
  • URL or DOI: https://www.sec.gov/Archives/edgar/data/1411579/000141157926000051/R16.htm
  • Accessed: 2026-07-20
  • Jurisdiction: United States
  • Supports: Entity disclosure distinguishing satisfied issuance conditions from performance units still subject to contingent-share and antidilution tests.
  • Limitations: Some awards use the treasury-stock method and sit outside this package’s zero-proceeds direct-share scope.
contingentShares.ts
/** Exact diluted-EPS inclusion test for one unresolved contingent-share tranche. */

export type AccountingFramework = "IFRS" | "US_GAAP" | "OTHER_DECLARED";
export type RoundingMode = "half_even" | "half_up";
export type ConditionKind = "EARNINGS" | "MARKET_PRICE" | "OTHER_STATUS";

export interface ContingentCondition {
  condition_id: string;
  kind: ConditionKind;
  measurement_basis:
    | "EARNINGS_TO_DATE"
    | "PERIOD_END_PRICE"
    | "ELAPSED_PERIOD_AVERAGE"
    | "CURRENT_STATUS";
  comparison: "GTE";
  observed_value: string;
  threshold_value: string;
  measurement_unit: string;
  source: string;
  measurement_date: string;
}

export interface ContingentShareInput {
  entity_id: string;
  agreement_id: string;
  period_start: string;
  period_end: string;
  as_of: string;
  accounting_framework: AccountingFramework;
  period_application:
    | "IFRS_DIRECT_PERIOD"
    | "US_GAAP_SINGLE_QUARTER"
    | "OTHER_DECLARED_DIRECT_PERIOD";
  currency: string;
  basic_control_numerator: string;
  basic_weighted_average_shares: string;
  earnings_scale: string;
  share_scale: string;
  fixed_contingent_shares: string;
  agreement_date: string;
  terms_effective_date: string;
  knowledge_cutoff: string;
  contingency_end: string;
  legal_status: "UNRESOLVED";
  condition_logic: "ALL";
  conditions: ContingentCondition[];
  numerator_policy: string;
  denominator_policy: string;
  current_status_policy: "NO_FUTURE_PROJECTION";
  award_terms_source: string;
  day_count_policy: "ACTUAL_DAYS_IN_PERIOD";
  basic_components_complete: boolean;
  share_basis_restatement_complete: boolean;
  conditions_complete: boolean;
  award_terms_complete: boolean;
  pending_contingency_confirmed: boolean;
  legal_conditions_irrevocably_satisfied: boolean;
  time_only_condition_remaining: boolean;
  agreement_cancelled: boolean;
  contingently_returnable: boolean;
  unsegmented_terms_revision: boolean;
  little_or_no_cash_consideration: boolean;
  fixed_share_count: boolean;
  numerator_effect_is_zero: boolean;
  award_is_final: boolean;
  is_final: boolean;
  eps_decimal_places: number;
  share_decimal_places: number;
  rounding_mode: RoundingMode;
}

export interface ConditionResult extends ContingentCondition {
  met: boolean;
}

export interface ContingentShareResult {
  metric: "contingently_issuable_share_inclusion";
  entity_id: string;
  agreement_id: string;
  period_start: string;
  period_end: string;
  accounting_framework: AccountingFramework;
  period_application: string;
  legal_status: "UNRESOLVED";
  routing_decision: "DILUTED_CURRENT_STATUS_TEST";
  terms_effective_date: string;
  knowledge_cutoff: string;
  currency: string;
  basic_control_numerator_base: string;
  basic_weighted_average_shares_base: string;
  fixed_contingent_shares_base: string;
  condition_results: ConditionResult[];
  all_conditions_met: boolean;
  all_current_status_tests_met: boolean;
  conditions_met_count: number;
  conditions_total_count: number;
  exposure_start: string;
  exposure_end_exclusive: string;
  exposure_days: number;
  period_days: number;
  time_weight_exact: string;
  eligible_shares_unweighted_exact: string;
  weighted_contingent_shares_exact: string;
  weighted_contingent_shares: string;
  basic_eps_exact: string;
  basic_eps: string;
  candidate_eps_exact: string;
  candidate_eps: string;
  classification: "dilutive" | "antidilutive" | "conditions_not_met";
  include_in_standalone_candidate: boolean;
  reason_code:
    | "candidate_below_control"
    | "candidate_not_below_control"
    | "one_or_more_conditions_not_met";
  eps_decimal_places: number;
  share_decimal_places: number;
  rounding_mode: RoundingMode;
  rounding_adjusted: boolean;
  status: "final" | "provisional";
  is_final: boolean;
}

interface Rational { n: bigint; d: bigint }

const DECIMAL_RE = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/;
const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
const RFC3339_RE =
  /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
const REQUIRED_FIELDS: (keyof ContingentShareInput)[] = [
  "entity_id", "agreement_id", "period_start", "period_end", "as_of",
  "accounting_framework", "period_application", "currency",
  "basic_control_numerator", "basic_weighted_average_shares",
  "earnings_scale", "share_scale", "fixed_contingent_shares",
  "agreement_date", "terms_effective_date", "knowledge_cutoff",
  "contingency_end", "legal_status", "condition_logic", "conditions",
  "numerator_policy", "denominator_policy", "current_status_policy",
  "award_terms_source", "day_count_policy", "basic_components_complete",
  "share_basis_restatement_complete", "conditions_complete",
  "award_terms_complete", "pending_contingency_confirmed",
  "legal_conditions_irrevocably_satisfied", "time_only_condition_remaining",
  "agreement_cancelled", "contingently_returnable",
  "unsegmented_terms_revision",
  "little_or_no_cash_consideration", "fixed_share_count",
  "numerator_effect_is_zero", "award_is_final", "is_final",
  "eps_decimal_places", "share_decimal_places", "rounding_mode",
];

const PERMITTED_BASES: Record<ConditionKind, string[]> = {
  EARNINGS: ["EARNINGS_TO_DATE"],
  MARKET_PRICE: ["PERIOD_END_PRICE", "ELAPSED_PERIOD_AVERAGE"],
  OTHER_STATUS: ["CURRENT_STATUS"],
};

export class ContingentShareValidationError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ContingentShareValidationError";
  }
}

function abs(value: bigint): bigint { return value < 0n ? -value : value; }
function gcd(a: bigint, b: bigint): bigint {
  a = abs(a); b = abs(b);
  while (b !== 0n) [a, b] = [b, a % b];
  return a;
}
function rat(n: bigint, d = 1n): Rational {
  if (d === 0n) throw new ContingentShareValidationError("division by zero");
  if (d < 0n) { n = -n; d = -d; }
  const g = gcd(n, d);
  return {n: n / g, d: d / g};
}
function add(a: Rational, b: Rational): Rational {
  return rat(a.n * b.d + b.n * a.d, a.d * b.d);
}
function mul(a: Rational, b: Rational): Rational {
  return rat(a.n * b.n, a.d * b.d);
}
function div(a: Rational, b: Rational): Rational {
  return rat(a.n * b.d, a.d * b.n);
}
function less(a: Rational, b: Rational): boolean {
  return a.n * b.d < b.n * a.d;
}
function gte(a: Rational, b: Rational): boolean {
  return a.n * b.d >= b.n * a.d;
}
function fraction(value: Rational): string { return `${value.n}/${value.d}`; }
function decimal(value: unknown, field: string): Rational {
  if (typeof value !== "string" || !DECIMAL_RE.test(value)) {
    throw new ContingentShareValidationError(
      `${field} must be a canonical decimal string without exponent notation`,
    );
  }
  const unsigned = value.startsWith("-") ? value.slice(1) : value;
  const [whole, frac = ""] = unsigned.split(".");
  if (whole.length + frac.length > 30 || frac.length > 12) {
    throw new ContingentShareValidationError(
      `${field} exceeds 30 digits or 12 fraction digits`,
    );
  }
  return rat(BigInt(value.replace(".", "")), 10n ** BigInt(frac.length));
}
function normalDecimal(value: Rational): string {
  let denominator = value.d;
  let twos = 0, fives = 0;
  while (denominator % 2n === 0n) { denominator /= 2n; twos++; }
  while (denominator % 5n === 0n) { denominator /= 5n; fives++; }
  if (denominator !== 1n) return fraction(value);
  const places = Math.max(twos, fives);
  let scaled = value.n * 2n ** BigInt(places - twos) * 5n ** BigInt(places - fives);
  const negative = scaled < 0n;
  if (negative) scaled = -scaled;
  let digits = scaled.toString().padStart(places + 1, "0");
  if (places) {
    const split = digits.length - places;
    digits = `${digits.slice(0, split)}.${digits.slice(split)}`
      .replace(/0+$/, "").replace(/\.$/, "");
  }
  return negative && digits !== "0" ? `-${digits}` : digits;
}
function round(value: Rational, places: number, mode: RoundingMode): [string, boolean] {
  const negative = value.n < 0n;
  const scaled = abs(value.n) * 10n ** BigInt(places);
  let quotient = scaled / value.d;
  const remainder = scaled % value.d;
  const comparison = remainder * 2n - value.d;
  if (comparison > 0n ||
      (comparison === 0n &&
       (mode === "half_up" || (mode === "half_even" && quotient % 2n === 1n)))) {
    quotient++;
  }
  let digits = quotient.toString().padStart(places + 1, "0");
  if (places) {
    const split = digits.length - places;
    digits = `${digits.slice(0, split)}.${digits.slice(split)}`;
  }
  if (negative && quotient !== 0n) digits = `-${digits}`;
  return [digits, remainder !== 0n];
}
function text(value: unknown, field: string): string {
  if (typeof value !== "string" || value.trim() === "") {
    throw new ContingentShareValidationError(`${field} must be non-empty text`);
  }
  return value;
}
function validDate(value: unknown, field: string): string {
  if (typeof value !== "string" || !DATE_RE.test(value)) {
    throw new ContingentShareValidationError(`${field} must use YYYY-MM-DD`);
  }
  const parsed = new Date(`${value}T00:00:00Z`);
  if (Number.isNaN(parsed.valueOf()) || parsed.toISOString().slice(0, 10) !== value) {
    throw new ContingentShareValidationError(`${field} must be a real date`);
  }
  return value;
}
function dayNumber(value: string): number {
  return Math.floor(Date.parse(`${value}T00:00:00Z`) / 86400000);
}
function dateFromDay(value: number): string {
  return new Date(value * 86400000).toISOString().slice(0, 10);
}

export function calculateContingentShareInclusion(
  input: ContingentShareInput,
): ContingentShareResult {
  if (input === null || typeof input !== "object" || Array.isArray(input)) {
    throw new ContingentShareValidationError("input must be an object");
  }
  for (const field of REQUIRED_FIELDS) {
    if (!(field in input)) {
      throw new ContingentShareValidationError(`missing required field: ${field}`);
    }
  }
  const entityId = text(input.entity_id, "entity_id");
  const agreementId = text(input.agreement_id, "agreement_id");
  const start = validDate(input.period_start, "period_start");
  const end = validDate(input.period_end, "period_end");
  const agreement = validDate(input.agreement_date, "agreement_date");
  const termsEffective = validDate(input.terms_effective_date, "terms_effective_date");
  const contingencyEnd = validDate(input.contingency_end, "contingency_end");
  if (start > end) {
    throw new ContingentShareValidationError(
      "period_start must not be after period_end",
    );
  }
  if (agreement > end) {
    throw new ContingentShareValidationError(
      "agreement_date must not be after period_end",
    );
  }
  if (termsEffective < agreement || termsEffective > end) {
    throw new ContingentShareValidationError(
      "terms_effective_date must be between agreement_date and period_end",
    );
  }
  if (contingencyEnd <= end) {
    throw new ContingentShareValidationError(
      "contingency_end must be after period_end for this unresolved-tranche algorithm",
    );
  }
  if (agreement >= contingencyEnd) {
    throw new ContingentShareValidationError(
      "agreement_date must be before contingency_end",
    );
  }
  if (!RFC3339_RE.test(input.as_of) || Number.isNaN(Date.parse(input.as_of))) {
    throw new ContingentShareValidationError(
      "as_of must be a real RFC 3339 timestamp with an explicit offset",
    );
  }
  if (Date.parse(input.as_of) < Date.parse(`${end}T00:00:00Z`)) {
    throw new ContingentShareValidationError(
      "as_of must not precede the reporting-period end",
    );
  }
  if (!RFC3339_RE.test(input.knowledge_cutoff) ||
      Number.isNaN(Date.parse(input.knowledge_cutoff))) {
    throw new ContingentShareValidationError(
      "knowledge_cutoff must be a real RFC 3339 timestamp with an explicit offset",
    );
  }
  if (Date.parse(input.knowledge_cutoff) < Date.parse(`${end}T00:00:00Z`) ||
      Date.parse(input.knowledge_cutoff) > Date.parse(input.as_of)) {
    throw new ContingentShareValidationError(
      "knowledge_cutoff must be on or after period_end and not after as_of",
    );
  }
  if (!["IFRS", "US_GAAP", "OTHER_DECLARED"].includes(input.accounting_framework)) {
    throw new ContingentShareValidationError(
      "accounting_framework is not supported",
    );
  }
  const permittedApplication = {
    IFRS: "IFRS_DIRECT_PERIOD",
    US_GAAP: "US_GAAP_SINGLE_QUARTER",
    OTHER_DECLARED: "OTHER_DECLARED_DIRECT_PERIOD",
  }[input.accounting_framework];
  if (input.period_application !== permittedApplication) {
    throw new ContingentShareValidationError(
      `period_application must be ${permittedApplication} for the selected framework`,
    );
  }
  if (input.accounting_framework === "US_GAAP" &&
      dayNumber(end) - dayNumber(start) + 1 > 100) {
    throw new ContingentShareValidationError(
      "US_GAAP_SINGLE_QUARTER must not exceed 100 calendar days; aggregate verified quarters upstream",
    );
  }
  if (!/^[A-Z]{3}$/.test(input.currency)) {
    throw new ContingentShareValidationError(
      "currency must be a three-letter uppercase code",
    );
  }
  for (const field of [
    "numerator_policy", "denominator_policy", "award_terms_source",
  ] as const) text(input[field], field);
  if (input.current_status_policy !== "NO_FUTURE_PROJECTION") {
    throw new ContingentShareValidationError(
      "current_status_policy must be NO_FUTURE_PROJECTION",
    );
  }
  if (input.day_count_policy !== "ACTUAL_DAYS_IN_PERIOD") {
    throw new ContingentShareValidationError(
      "day_count_policy must be ACTUAL_DAYS_IN_PERIOD",
    );
  }
  if (input.condition_logic !== "ALL") {
    throw new ContingentShareValidationError(
      "condition_logic must be ALL in the canonical fixed-tranche contract",
    );
  }
  if (input.legal_status !== "UNRESOLVED") {
    throw new ContingentShareValidationError(
      "legal_status must be UNRESOLVED; satisfied, cancelled, or returnable shares require the documented routing analysis",
    );
  }
  const trueFlags = [
    "basic_components_complete", "share_basis_restatement_complete",
    "conditions_complete", "award_terms_complete",
    "pending_contingency_confirmed", "little_or_no_cash_consideration",
    "fixed_share_count", "numerator_effect_is_zero",
  ] as const;
  const falseFlags = [
    "legal_conditions_irrevocably_satisfied", "time_only_condition_remaining",
    "agreement_cancelled", "contingently_returnable",
    "unsegmented_terms_revision",
  ] as const;
  for (const field of [...trueFlags, ...falseFlags, "award_is_final", "is_final"] as const) {
    if (typeof input[field] !== "boolean") {
      throw new ContingentShareValidationError(`${field} must be boolean`);
    }
  }
  for (const field of trueFlags) {
    if (!input[field]) {
      throw new ContingentShareValidationError(`${field} must be true`);
    }
  }
  for (const field of falseFlags) {
    if (input[field]) {
      throw new ContingentShareValidationError(
        `${field} must be false for the unresolved direct-share engine`,
      );
    }
  }
  if (!Number.isInteger(input.eps_decimal_places) ||
      input.eps_decimal_places < 0 || input.eps_decimal_places > 12) {
    throw new ContingentShareValidationError(
      "eps_decimal_places must be an integer from 0 to 12",
    );
  }
  if (!Number.isInteger(input.share_decimal_places) ||
      input.share_decimal_places < 0 || input.share_decimal_places > 12) {
    throw new ContingentShareValidationError(
      "share_decimal_places must be an integer from 0 to 12",
    );
  }
  if (!["half_even", "half_up"].includes(input.rounding_mode)) {
    throw new ContingentShareValidationError(
      "rounding_mode must be half_even or half_up",
    );
  }

  const basicReported = decimal(
    input.basic_control_numerator, "basic_control_numerator",
  );
  const basicSharesReported = decimal(
    input.basic_weighted_average_shares, "basic_weighted_average_shares",
  );
  const earningsScale = decimal(input.earnings_scale, "earnings_scale");
  const shareScale = decimal(input.share_scale, "share_scale");
  const fixedSharesReported = decimal(
    input.fixed_contingent_shares, "fixed_contingent_shares",
  );
  if (basicSharesReported.n <= 0n || earningsScale.n <= 0n || shareScale.n <= 0n) {
    throw new ContingentShareValidationError(
      "basic shares and both unit scales must be positive",
    );
  }
  if (fixedSharesReported.n <= 0n) {
    throw new ContingentShareValidationError(
      "fixed_contingent_shares must be positive",
    );
  }

  if (!Array.isArray(input.conditions) ||
      input.conditions.length < 1 || input.conditions.length > 20) {
    throw new ContingentShareValidationError(
      "conditions must be an array containing 1 to 20 conditions",
    );
  }
  const seen = new Set<string>();
  const conditionResults: ConditionResult[] = input.conditions.map((condition, index) => {
    const field = `conditions[${index}]`;
    if (condition === null || typeof condition !== "object" || Array.isArray(condition)) {
      throw new ContingentShareValidationError(`${field} must be an object`);
    }
    for (const required of [
      "condition_id", "kind", "measurement_basis", "comparison",
      "observed_value", "threshold_value", "measurement_unit", "source",
      "measurement_date",
    ] as const) {
      if (!(required in condition)) {
        throw new ContingentShareValidationError(`${field} missing field: ${required}`);
      }
    }
    const conditionId = text(condition.condition_id, `${field}.condition_id`);
    if (seen.has(conditionId)) {
      throw new ContingentShareValidationError(
        "condition_id values must be unique",
      );
    }
    seen.add(conditionId);
    if (!["EARNINGS", "MARKET_PRICE", "OTHER_STATUS"].includes(condition.kind)) {
      throw new ContingentShareValidationError(
        `${field}.kind must be EARNINGS, MARKET_PRICE, or OTHER_STATUS`,
      );
    }
    if (!PERMITTED_BASES[condition.kind].includes(condition.measurement_basis)) {
      throw new ContingentShareValidationError(
        `${field}.measurement_basis is not permitted for ${condition.kind}`,
      );
    }
    if (condition.comparison !== "GTE") {
      throw new ContingentShareValidationError(`${field}.comparison must be GTE`);
    }
    const observed = decimal(condition.observed_value, `${field}.observed_value`);
    const threshold = decimal(
      condition.threshold_value, `${field}.threshold_value`,
    );
    if (condition.kind !== "EARNINGS" &&
        (observed.n < 0n || threshold.n < 0n)) {
      throw new ContingentShareValidationError(
        `${field} market-price and other-status values must be non-negative`,
      );
    }
    const unit = text(condition.measurement_unit, `${field}.measurement_unit`);
    const source = text(condition.source, `${field}.source`);
    const measurementDate = validDate(
      condition.measurement_date, `${field}.measurement_date`,
    );
    if (measurementDate !== end) {
      throw new ContingentShareValidationError(
        `${field}.measurement_date must equal period_end; do not use post-period hindsight`,
      );
    }
    return {
      condition_id: conditionId,
      kind: condition.kind,
      measurement_basis: condition.measurement_basis,
      comparison: "GTE",
      observed_value: normalDecimal(observed),
      threshold_value: normalDecimal(threshold),
      measurement_unit: unit,
      source,
      measurement_date: measurementDate,
      met: gte(observed, threshold),
    };
  });

  const startDay = dayNumber(start);
  const endExclusiveDay = dayNumber(end) + 1;
  const exposureStartDay = Math.max(
    startDay, dayNumber(agreement), dayNumber(termsEffective),
  );
  const periodDays = endExclusiveDay - startDay;
  const exposureDays = endExclusiveDay - exposureStartDay;
  if (exposureDays <= 0) {
    throw new ContingentShareValidationError(
      "agreement has no potential-share exposure in the period",
    );
  }

  const basicNumerator = mul(basicReported, earningsScale);
  const basicShares = mul(basicSharesReported, shareScale);
  const fixedShares = mul(fixedSharesReported, shareScale);
  const metCount = conditionResults.filter((condition) => condition.met).length;
  const allMet = metCount === conditionResults.length;
  const unweighted = allMet ? fixedShares : rat(0n);
  const timeWeight = rat(BigInt(exposureDays), BigInt(periodDays));
  const weighted = mul(unweighted, timeWeight);
  const basicEps = div(basicNumerator, basicShares);
  const candidateEps = div(basicNumerator, add(basicShares, weighted));
  const dilutive = allMet && less(candidateEps, basicEps);
  const classification = !allMet
    ? "conditions_not_met"
    : dilutive ? "dilutive" : "antidilutive";
  const reasonCode = !allMet
    ? "one_or_more_conditions_not_met"
    : dilutive ? "candidate_below_control" : "candidate_not_below_control";
  const [basicDisplay, basicAdjusted] = round(
    basicEps, input.eps_decimal_places, input.rounding_mode,
  );
  const [candidateDisplay, candidateAdjusted] = round(
    candidateEps, input.eps_decimal_places, input.rounding_mode,
  );
  const [weightedDisplay, weightedAdjusted] = round(
    weighted, input.share_decimal_places, input.rounding_mode,
  );
  const isFinal = input.is_final && input.award_is_final;

  return {
    metric: "contingently_issuable_share_inclusion",
    entity_id: entityId,
    agreement_id: agreementId,
    period_start: start,
    period_end: end,
    accounting_framework: input.accounting_framework,
    period_application: input.period_application,
    legal_status: "UNRESOLVED",
    routing_decision: "DILUTED_CURRENT_STATUS_TEST",
    terms_effective_date: termsEffective,
    knowledge_cutoff: input.knowledge_cutoff,
    currency: input.currency,
    basic_control_numerator_base: normalDecimal(basicNumerator),
    basic_weighted_average_shares_base: normalDecimal(basicShares),
    fixed_contingent_shares_base: normalDecimal(fixedShares),
    condition_results: conditionResults,
    all_conditions_met: allMet,
    all_current_status_tests_met: allMet,
    conditions_met_count: metCount,
    conditions_total_count: conditionResults.length,
    exposure_start: dateFromDay(exposureStartDay),
    exposure_end_exclusive: dateFromDay(endExclusiveDay),
    exposure_days: exposureDays,
    period_days: periodDays,
    time_weight_exact: fraction(timeWeight),
    eligible_shares_unweighted_exact: fraction(unweighted),
    weighted_contingent_shares_exact: fraction(weighted),
    weighted_contingent_shares: weightedDisplay,
    basic_eps_exact: fraction(basicEps),
    basic_eps: basicDisplay,
    candidate_eps_exact: fraction(candidateEps),
    candidate_eps: candidateDisplay,
    classification,
    include_in_standalone_candidate: dilutive,
    reason_code: reasonCode,
    eps_decimal_places: input.eps_decimal_places,
    share_decimal_places: input.share_decimal_places,
    rounding_mode: input.rounding_mode,
    rounding_adjusted: basicAdjusted || candidateAdjusted || weightedAdjusted,
    status: isFinal ? "final" : "provisional",
    is_final: isFinal,
  };
}
Full-height labcontingent share labOpen full screen