D30-F04-A03 / Released engineering topic

Intercompany Elimination: Build a Complete Reciprocal Worksheet Without Changing Entity Books

Reproduce exact loan and service eliminations, retain external balances and withhold incomplete group worksheets using trusted source receipts and Python/TypeScript parity.

Intercompany Elimination: Build a Complete Reciprocal Worksheet Without Changing Entity BooksD30 / D30-F04

A receivable in one entity and a payable in another can both be correct. Consolidation changes how the group presents that reciprocal relationship. It does not require deleting either entity's accounting record.

In this tutorial, you will build a separate worksheet for a narrowly defined group slice. You will trace both source positions, calculate the exact elimination legs and see why a single unresolved internal balance withholds the final worksheet. The worked amounts, Cedar entities and evidence receipts are synthetic.

You should already understand debit and credit signs from Double-Entry Posting Engine and source-account evidence from Trial-Balance Validation. The Balance-Proof Algorithm explains why a plausible total alone cannot establish source completeness.

The output we want

Assume a trusted host supplies two controlled members for the whole reporting period, CB-A and CB-B. Both positions use the same functional and reporting USD basis and aligned accounting policies. The host has identified one dedicated counterparty control account for each reciprocal relationship.

CB-A's receivable is 100000 USD minor units, or $1000.00. CB-B's payable is −100000 on a raw debit-minus-credit basis. The group worksheet debits CB-B's payable and credits CB-A's receivable, each by 100000. Both legal-entity books retain their source balances.

Entity books retain their positions while a separate worksheet reverses the reciprocal group slice

Open the full-size entity and worksheet figure.

For a service charge, supplied CB-A income of −2400 and CB-B expense of +2400 produce the opposite role pattern: debit income 2400 and credit expense 2400. Together, the loan and service worksheet has debits 102400 and credits 102400.

Supplied roleCombined before, raw USD minorWorksheet raw deltaCombined after
Receivable100000−1000000
Payable−1000001000000
Income−240024000
Expense2400−24000

These are role totals for the supplied position slice. They are not a complete consolidated balance sheet, trial balance or income statement. In particular, a cumulative account balance does not automatically represent a current-month income flow. The host must align the reporting interval, closing policy and meaning of the extracted positions.

What the accounting sources establish

IFRS 10's consolidation procedures include eliminating intragroup balances and transactions. This tutorial uses paragraph-level evidence from the current official AASB 10 compilation, which incorporates IFRS 10. B86(c) supports the elimination principle; B87 and B92–B93 address aligned policies and reporting information. The compilation applies to annual periods beginning in 2026 before 2027. Australian-specific provisions are outside this example. Official AASB 10 compilation.

The algorithm is deliberately narrower than the standard. It takes supplied control and full-period membership as trusted facts. It does not assess control, acquisitions, non-controlling interests, unrealized inventory or asset profits, tax, impairment or foreign-operation translation. The July 2024 annual-improvements amendment affecting IFRS 10 control guidance became effective in 2026; that control assessment remains outside the function. IFRS Foundation amendment notice.

Do not generalize “intragroup” into “eliminate every related effect.” IAS 21 paragraph 45 addresses currency effects that cannot simply be eliminated against reciprocal balances. The IFRIC March 2026 Update's April addendum discusses this interaction. The host's eligible reciprocal-account mapping must exclude those FX-effect accounts; an account class such as income is not enough to prove eligibility. IFRIC update, AASB 121 paragraph 45.

Freeze scope and source identity before matching

The API has three arguments:

Python
result = build_elimination_worksheet(request, member_snapshots, context)
TypeScript
const result = buildEliminationWorksheet(request, memberSnapshots, context);

The request identifies the worksheet, group, reporting period, knowledge cutoff K, common basis and policy. It contains explicit pair specifications. The context supplies the controlled-member registry, independently expected source receipts and scoped account catalog. Each member snapshot contains its whole supplied dedicated-account position slice.

A position reference identifies ledger, entity, evidence receipt and position. Amount equality cannot replace any of those fields. A pair must identify two different members, reciprocal counterparties, the same pair identity and the expected role combination. A stale evidence reference does not silently fall back to the newest snapshot.

Source receipts use the strict d30-close-source-v1 base. Their journal head is the visible whole-group position p at K. A separate reader may have validated later history through h; that does not make h visible in the extraction. A current close must bind p to its frozen current head. This function checks supplied receipt consistency, not the authenticity of the host's extraction process.

The downstream adapter must compare exact raw values against passed Trial-Balance account lines and the dedicated counterparty mapping. An aggregate account containing several counterparties cannot identify their separate positions by itself. This profile therefore permits one whole dedicated account once per member, rather than inventing allocations across pairs. Closed accounts with historical balances remain readable; that does not authorize new postings to them.

The complete strict data contract lists every field, accepted magnitude, precedence and typed result.

Compute the pair, then gate the whole worksheet

Raw signs are debit minus credit. Receivable and expense positions are nonnegative; payable and income positions are nonpositive. A contra sign is unsupported by this initial profile, rather than repaired through an extra sign flip.

For an eligible reciprocal pair:

Plain text
residual_minor = left_raw_minor + right_raw_minor

if residual_minor != 0:
    unresolved, no pair legs
elif both amounts are zero:
    matched zero, no zero-valued legs
else:
    reverse each source position in the separate worksheet

A positive raw position reverses with a credit; a negative raw position reverses with a debit. All emitted amounts are positive canonical integer strings. Each worksheet leg retains its entity, ledger, account, pair and exact position reference. These dimensions make the leg auditable. They do not turn a cross-entity worksheet into a legal-entity journal accepted by the posting engine.

The algorithm then checks every supplied internal position. Each must occur in exactly one explicit pair. A removed pair specification cannot hide its source positions. External counterparties stay in the supplied slice and are not eliminated.

A one-dollar mismatch must stay visible

Change CB-B's payable to −99900 while CB-A still reports 100000. The exact residual is 100 USD minor units, or $1.00. The function returns WORKSHEET_UNRESOLVED, with worksheet: null and rollup: null.

The service pair can still have matched arithmetic. Its diagnostic may expose conditional proposed legs, but those are not a final partial worksheet. No suspense, FX or rounding leg plugs the loan difference.

A residual of 100 minor units withholds the full worksheet even when another pair matches

Open the full-size no-plug and completeness figure.

Now add an external receivable of 500. When the internal pairs are all resolved, that external receivable remains 500 in the after-role slice. An omitted internal position instead blocks readiness. This distinction depends on trusted group membership and explicit counterparties, not on whether another amount happens to match.

Read the complete result

The result contains status, code, scope, canonical member sources, pair diagnostics, unassigned positions, the final worksheet and role rollup. Invalid structure or unavailable required sources returns no partial source slice or pair certification. Validly selected sources with unresolved internal issues retain diagnostics and withhold both final branches.

Only ELIMINATION_WORKSHEET_READY returns the complete bounded worksheet. Mutable branches are detached from the inputs and from one another. Source copies sort by entity and position identity; worksheet ordering is deterministic. Permuting delivery arrays cannot choose different partners or change the answer.

This implementation accepts at most 16 members, 128 positions per member, 256 explicit pairs and 2048 account records. A valid worksheet can contain 512 legs, with source magnitudes up to 60 decimal digits. These are worksheet resource limits. They are not the posting engine's 32-leg and 30-digit leg limits, or a statement about a bank's capacity. Indexing and sorting replace subset search or fuzzy amount matching.

Verify the implementation and explore the lab

The self-contained Python and TypeScript implementations agree on 69 independently authored complete expected outputs. The initial 58 expectations preceded implementation, including four literal arithmetic cases. Later resource regressions exercise the full 256-pair/512-leg profile and strict boundaries. Comparison covers source identity, errors, conditional diagnostics, complete legs and rollups, rather than only final totals.

The actual compiled TypeScript lab retains 24 source positions and 12 intended pairs. Step through the complete reciprocal case, a mismatch, an omitted pair, an external balance, a contra sign, late evidence, duplicate identity and a missing member. Change the mismatch from 100 to 1 to 0 minor units: only exact equality permits readiness. Focus changes the inspected pair; it never omits the other source positions from evaluation.

Open the guided intercompany worksheet lab. Read the validation evidence and reproduction commands.

The browser audit checked 189 pair or failure observations at 390, 768 and 1440 pixels. All 24 captured complete browser outputs also match Python. Reset, parameters, keyboard transport, Play/Pause, reduced motion, touch targets and console checks passed. These checks verify the local teaching implementation, not the host's real source extraction or a reporting-compliance opinion.

Connect the proposal to the close

The next host step is accepting the complete worksheet under the correct source evidence, reporting basis and current close generation. Period-Lock Control connects actual checks to the frozen journal head. A ready pure calculation alone does not close a period.

FX Revaluation Posting separately measures ordinary monetary currency effects. Preserve that distinction when reviewing intragroup positions: reciprocal balance elimination does not erase every FX consequence.

References and provenance

The source-role register distinguishes paragraph evidence, amendments and authored implementation choices. IFRS Foundation issued-standard PDF access redirected to subscription access; the official issuer compilation supplied the directly read paragraph text. No full copyrighted standard is reproduced.

A historical corporate example is not useful for this runnable case without licensed reciprocal ledgers, known-at receipts and actual group-policy decisions. Public consolidated aggregates do not expose those inputs. The examples remain explicitly synthetic, and no encoded or published video is claimed.

ReferencesPrimary sources and evidence notes

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

Accessed: 25 October 2026. All Cedar entities, positions, group membership and receipts are synthetic. The algorithm does not determine control, authenticate source receipts or certify complete consolidated financial statements.

Supports: the exact paragraph/version roles below. Limitations: this package implements a narrow supplied-position worksheet; account metadata and scope decisions remain trusted host inputs.

Primary sourceVersion / paragraphRole and limit
AASB 10 current 2026 compilation, official government HTMLCompilation no. 7, 31 December 2025, prepared 24 February 2026; annual periods beginning 1 January 2026 before 1 January 2027; comparison with IFRS 10; B86(a)–(c),B87,B92–B93First-party paragraph evidence for intragroup elimination and aligned policies/reporting dates. The selected worksheet covers reciprocal receivable/payable and charge income/expense positions, not all consolidation procedures. Australian-specific provisions are not used.
IFRS Foundation Annual Improvements Volume 11 notice and AASB2024-3 issuer amendmentIFRS issuance July 2024, effective annual periods beginning 1 January 2026; IFRS 10-equivalent B74 amendmentCurrent amendment check relates to de facto agent/control assessment. Membership is supplied rather than inferred by this algorithm.
IFRIC March 2026 Update, published April 2026 addendumIntragroup monetary-liability/asset FX-difference fact pattern and IAS 21¶45 discussionCurrent primary interaction evidence: eliminating reciprocal balances does not erase applicable currency-fluctuation effects. IFRS18 classification categories are outside this worksheet profile.
AASB121 issuer adoption of IAS 212025–2026 compilation, paragraph45 and related32–33Corroborates the exception boundary; this same-functional USD worksheet does not translate foreign operations, estimate FX or apply net-investment accounting.

IFRS Foundation issued-standard PDF links redirected to subscription access. The current official government issuer compilation was read directly at B86(c),B87 and B92–B93; its comparison section identifies IFRS 10 incorporation. The detailed production register records access/version decisions. No full copyrighted standard is reproduced.

Author choices: strict trusted receipt and dedicated-account schema, raw debit-minus-credit sign, exact reciprocal identity, no reuse, all supplied internal positions covered, all-or-nothing worksheet, resource caps and diagnostics. Arithmetic/examples are author-derived from synthetic inputs. These schemas and thresholds are not claimed as standard wording.

Historical example: not useful without licensed reciprocal entity ledgers, known-at snapshots and actual group/policy decisions. Public consolidated aggregates cannot reproduce those internal eliminations. No operating-efficiency or compliance-certification evidence is claimed.

intercompany.ts
/** Consolidation-only worksheet from trusted dedicated account receipts. No entity-book posting. */
type Obj=Record<string,any>;
const REQUEST='schema_version worksheet_id group_id period knowledge_cutoff basis_id policy_id profile pair_specs'.split(' '),SPEC='pair_id kind left_position_ref right_position_ref'.split(' '),REF='ledger_id entity_id evidence_id position_id'.split(' '),BASE='schema_version evidence_id ledger_id entity_id period currency basis knowledge_cutoff observed_at head_sequence receipt_ref'.split(' '),GROUP='schema_version group_id basis_id policy_id period knowledge_cutoff profile members available_at receipt_ref'.split(' '),SOURCE=[...BASE,...'policy_id available_at position_count positions'.split(' ')],POSITION='position_id account_id counterparty_entity_id pair_id role raw_balance_minor position_basis_ref'.split(' '),ACCOUNT='account_id ledger_id entity_id currency account_type normal_side status'.split(' ');
const NORMAL:Record<string,string>={asset:'debit',expense:'debit',liability:'credit',income:'credit',equity:'credit'},ROLE:Record<string,string>={receivable:'asset',payable:'liability',income:'income',expense:'expense'};
const INDETERMINATE=new Set(['MISSING_MEMBER_RECEIPT','MISSING_MEMBER_SNAPSHOT','SNAPSHOT_NOT_AVAILABLE','GROUP_NOT_AVAILABLE']);
class Invalid extends Error{}
function need(ok:any,code:string):asserts ok{if(!ok)throw new Invalid(code);}
const exact=(x:any,keys:string[])=>x!==null&&typeof x==='object'&&!Array.isArray(x)&&Object.keys(x).length===keys.length&&keys.every(k=>Object.hasOwn(x,k));
const full=(x:any,r:RegExp)=>typeof x==='string'&&r.exec(x)?.[0]===x;
const identifier=(x:any)=>full(x,/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/);
const unsigned=(x:any,digits=30)=>typeof x==='string'&&x.length<=digits&&full(x,/^(0|[1-9][0-9]*)$/);
const signed=(x:any)=>typeof x==='string'&&(unsigned(x,60)||(x.startsWith('-')&&unsigned(x.slice(1),60)&&x.slice(1)!=='0'));
const period=(x:any)=>full(x,/^[0-9]{4}-(0[1-9]|1[0-2])$/)&&x.slice(0,4)!=='0000';
function stamp(x:any):boolean{if(!full(x,/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/)||x.slice(0,4)==='0000')return false;const d=new Date(x);return !Number.isNaN(d.getTime())&&d.toISOString()===x;}
function closeAt(p:string):string{const y=Number(p.slice(0,4)),m=Number(p.slice(5)),leap=y%4===0&&(y%100!==0||y%400===0);return p+'-'+[31,leap?29:28,31,30,31,30,31,31,30,31,30,31][m-1]+'T23:59:59.999Z';}
const scope=(x:Obj)=>JSON.stringify([x.ledger_id,x.entity_id]),refkey=(x:Obj)=>JSON.stringify([x.ledger_id,x.entity_id,x.evidence_id,x.position_id]);
const accountKey=(x:Obj,id:string)=>JSON.stringify([x.ledger_id,x.entity_id,id]);
const posref=(s:Obj,p:Obj)=>({ledger_id:s.ledger_id,entity_id:s.entity_id,evidence_id:s.evidence_id,position_id:p.position_id});
const canonical=(x:any):any=>Array.isArray(x)?x.map(canonical):x!==null&&typeof x==='object'?Object.fromEntries(Object.keys(x).sort().map(k=>[k,canonical(x[k])])):x;
const equal=(a:any,b:any)=>JSON.stringify(canonical(a))===JSON.stringify(canonical(b));
const baseReceipt=(s:Obj)=>Object.fromEntries(BASE.map(k=>[k,s[k]]));
const compare=(a:string,b:string)=>a<b?-1:a>b?1:0;
const sourceOrder=(a:Obj,b:Obj)=>compare(a.entity_id,b.entity_id)||compare(a.ledger_id,b.ledger_id);
function failed(code:string):Obj{return {status:INDETERMINATE.has(code)?'indeterminate':'rejected',code,scope:null,member_sources:[],pair_results:[],unassigned:[],worksheet:null,rollup:null};}
function receiptOk(r:any):boolean{return exact(r,BASE)&&r.schema_version==='d30-close-source-v1'&&['evidence_id','ledger_id','entity_id','receipt_ref'].every(k=>identifier(r[k]))&&period(r.period)&&stamp(r.knowledge_cutoff)&&stamp(r.observed_at)&&unsigned(r.head_sequence);}
export function buildEliminationWorksheet(request:any,memberSnapshots:any,context:any):Obj{
 try{
  const q=request;need(exact(q,REQUEST),'INVALID_SCHEMA');need(q.schema_version==='d30-intercompany-elimination-v1','UNSUPPORTED_SCHEMA');need(['worksheet_id','group_id','basis_id','policy_id'].every(k=>identifier(q[k])),'INVALID_IDENTIFIER');need(period(q.period),'INVALID_PERIOD');need(stamp(q.knowledge_cutoff),'INVALID_TIMESTAMP');const K=q.knowledge_cutoff,measurement=closeAt(q.period);need(K>=measurement,'KNOWLEDGE_BEFORE_CLOSE');need(q.profile==='ifrs10-reciprocal-usd-v1','UNSUPPORTED_PROFILE');const specs=q.pair_specs;need(Array.isArray(specs)&&specs.length<=256,'INVALID_PAIR_SPEC');const pairIds=new Set<string>(),usedRefs=new Set<string>();
  for(const s of specs){need(exact(s,SPEC)&&identifier(s.pair_id)&&['receivable_payable','income_expense'].includes(s.kind),'INVALID_PAIR_SPEC');need(!pairIds.has(s.pair_id),'DUPLICATE_PAIR_ID');pairIds.add(s.pair_id);for(const side of ['left','right']){const r=s[side+'_position_ref'];need(exact(r,REF)&&Object.values(r).every(identifier),'INVALID_PAIR_SPEC');}need(s.left_position_ref.entity_id!==s.right_position_ref.entity_id,'PAIR_SCOPE_MISMATCH');for(const side of ['left','right']){const key=refkey(s[side+'_position_ref']);need(!usedRefs.has(key),'POSITION_REUSED');usedRefs.add(key);}}
  need(exact(context,['group','member_receipts','accounts']),'INVALID_CONTEXT');const g=context.group;need(exact(g,GROUP),'INVALID_GROUP');need(g.schema_version==='d30-consolidation-group-v1'&&g.profile==='supplied-controlled-full-period-usd-v1'&&['group_id','basis_id','policy_id','receipt_ref'].every(k=>identifier(g[k]))&&period(g.period)&&stamp(g.knowledge_cutoff)&&stamp(g.available_at),'INVALID_GROUP');need(['group_id','basis_id','policy_id','period','knowledge_cutoff'].every(k=>g[k]===q[k]),'GROUP_SCOPE_MISMATCH');need(g.available_at<=K,'GROUP_NOT_AVAILABLE');const members=g.members;need(Array.isArray(members)&&members.length>=2&&members.length<=16,'INVALID_GROUP');const memberMap=new Map<string,string>();for(const m of members){need(exact(m,['ledger_id','entity_id'])&&Object.values(m).every(identifier)&&!memberMap.has(m.entity_id),'INVALID_GROUP');memberMap.set(m.entity_id,m.ledger_id);}const memberScope=(r:Obj)=>memberMap.get(r.entity_id)===r.ledger_id;for(const s of specs)need(['left','right'].every(side=>memberScope(s[side+'_position_ref'])),'PAIR_SCOPE_MISMATCH');
  const registry=context.member_receipts;need(Array.isArray(registry)&&registry.length<=16,'INVALID_MEMBER_RECEIPT');const headers=new Map<string,Obj>(),evidence=new Set<string>();for(const r of registry){need(receiptOk(r),'INVALID_MEMBER_RECEIPT');need(memberScope(r),'GROUP_SCOPE_MISMATCH');need(!headers.has(scope(r))&&!evidence.has(r.evidence_id),'INVALID_MEMBER_RECEIPT');need(r.currency==='USD'&&r.basis==='functional-usd'&&r.period===q.period&&r.knowledge_cutoff===K,'SOURCE_BASIS_MISMATCH');need(r.observed_at>=K,'SNAPSHOT_CLOCK_VIOLATION');headers.set(scope(r),r);evidence.add(r.evidence_id);}need(headers.size===members.length,'MISSING_MEMBER_RECEIPT');
  const accounts=context.accounts;need(Array.isArray(accounts)&&accounts.length<=2048,'INVALID_ACCOUNTS');const catalog=new Map<string,Obj>();for(const a of accounts){need(exact(a,ACCOUNT)&&['account_id','ledger_id','entity_id'].every(k=>identifier(a[k])),'INVALID_ACCOUNTS');need(typeof a.account_type==='string'&&Object.hasOwn(NORMAL,a.account_type)&&a.normal_side===NORMAL[a.account_type]&&['USD','EUR'].includes(a.currency)&&['active','closed'].includes(a.status),'INVALID_ACCOUNTS');const key=accountKey(a,a.account_id);need(!catalog.has(key),'INVALID_ACCOUNTS');catalog.set(key,a);}
  need(Array.isArray(memberSnapshots)&&memberSnapshots.length<=32,'INVALID_SNAPSHOT');const sources=new Map<string,Obj>(),sourceIds=new Map<string,Obj>();for(const s of memberSnapshots){need(exact(s,SOURCE),'INVALID_SNAPSHOT');need(receiptOk(baseReceipt(s)),'INVALID_SNAPSHOT');need(memberScope(s),'GROUP_SCOPE_MISMATCH');if(sourceIds.has(s.evidence_id))throw new Invalid(equal(sourceIds.get(s.evidence_id),s)?'DUPLICATE_SNAPSHOT':'SNAPSHOT_ID_CONFLICT');need(!sources.has(scope(s)),'DUPLICATE_MEMBER_SNAPSHOT');sources.set(scope(s),s);sourceIds.set(s.evidence_id,s);}
  const positions=new Map<string,Obj>();let unavailable=false;
  for(const [key,s] of sources){need(equal(baseReceipt(s),headers.get(key)),'SOURCE_RECEIPT_MISMATCH');need(s.policy_id===q.policy_id,'SOURCE_BASIS_MISMATCH');need(stamp(s.available_at)&&s.available_at>=measurement&&s.available_at<=s.observed_at,'SNAPSHOT_CLOCK_VIOLATION');unavailable=unavailable||s.available_at>K;need(Array.isArray(s.positions)&&s.positions.length<=128&&unsigned(s.position_count,3)&&Number(s.position_count)===s.positions.length,'INVALID_POSITION_COUNT');const ids=new Set<string>(),accountIds=new Set<string>();for(const p of s.positions){need(exact(p,POSITION)&&['position_id','account_id','counterparty_entity_id','pair_id','position_basis_ref'].every(k=>identifier(p[k]))&&typeof p.role==='string'&&Object.hasOwn(ROLE,p.role),'INVALID_POSITION');need(signed(p.raw_balance_minor),'INVALID_RAW_BALANCE');need(!ids.has(p.position_id),'DUPLICATE_POSITION');ids.add(p.position_id);need(!accountIds.has(p.account_id),'DUPLICATE_CONTROL_ACCOUNT');accountIds.add(p.account_id);need(p.counterparty_entity_id!==s.entity_id,'UNSUPPORTED_SELF_COUNTERPARTY');const a=catalog.get(accountKey(s,p.account_id));need(a!==undefined&&a.currency==='USD'&&a.account_type===ROLE[p.role],'ACCOUNT_ROLE_MISMATCH');positions.set(refkey(posref(s,p)),p);}}
  need(sources.size===members.length,'MISSING_MEMBER_SNAPSHOT');need(!unavailable,'SNAPSHOT_NOT_AVAILABLE');const results:Obj[]=[],allLegs:Obj[]=[],used=new Set<string>();
  for(const spec of [...specs].sort((a,b)=>compare(a.pair_id,b.pair_id))){const refs:Obj={left:spec.left_position_ref,right:spec.right_position_ref},rows:Obj={left:positions.get(refkey(refs.left))??null,right:positions.get(refkey(refs.right))??null};let code:string|null=null,residual:string|null=null,legs:Obj[]=[];for(const side of ['left','right'])if(sources.get(scope(refs[side]))!.evidence_id!==refs[side].evidence_id){code='STALE_POSITION_REFERENCE';break;}if(code===null&&Object.values(rows).some(p=>p===null))code='POSITION_NOT_FOUND';for(const side of ['left','right'])if(rows[side]!==null)used.add(refkey(refs[side]));const left=rows.left,right=rows.right;
   if(code===null){const roles=spec.kind==='receivable_payable'?['receivable','payable']:['income','expense'];if(left.pair_id!==spec.pair_id||right.pair_id!==spec.pair_id)code='PAIR_IDENTITY_MISMATCH';else if(left.role!==roles[0]||right.role!==roles[1])code='PAIR_ROLE_MISMATCH';else if(left.counterparty_entity_id!==refs.right.entity_id||right.counterparty_entity_id!==refs.left.entity_id)code='COUNTERPARTY_MISMATCH';else{const raw=[BigInt(left.raw_balance_minor),BigInt(right.raw_balance_minor)];if([left,right].some((p,i)=>(['receivable','expense'].includes(p.role)&&raw[i]!<0n)||(['payable','income'].includes(p.role)&&raw[i]!>0n)))code='UNSUPPORTED_POSITION_SIGN';else{const sum=raw[0]!+raw[1]!;residual=String(sum);code=sum!==0n?'UNEQUAL_RECIPROCAL_BALANCES':raw.every(x=>x===0n)?'ZERO_RECIPROCAL':'MATCHED_RECIPROCAL';if(code==='MATCHED_RECIPROCAL'){for(const side of ['left','right']){const r=refs[side],p=rows[side],v=BigInt(p.raw_balance_minor);legs.push({ledger_id:r.ledger_id,entity_id:r.entity_id,account_id:p.account_id,side:v>0n?'credit':'debit',amount_minor:String(v<0n?-v:v),position_ref:structuredClone(r),pair_id:spec.pair_id});}legs.sort((a,b)=>(a.side==='debit'?0:1)-(b.side==='debit'?0:1));}}}}
   const status=({MATCHED_RECIPROCAL:'matched',ZERO_RECIPROCAL:'zero',UNSUPPORTED_POSITION_SIGN:'unsupported'} as Obj)[code!]??'unresolved';const view=(side:string)=>rows[side]===null?null:{position_ref:structuredClone(refs[side]),position:structuredClone(rows[side])};results.push({pair_spec:structuredClone(spec),status,code,left_position:view('left'),right_position:view('right'),residual_minor:residual,proposed_legs:legs});allLegs.push(...structuredClone(legs));
  }
  const orderedSources=[...sources.values()].sort(sourceOrder),unassigned:Obj[]=[];for(const s of orderedSources)for(const p of [...s.positions].sort((a,b)=>compare(a.position_id,b.position_id))){const r=posref(s,p);if(!used.has(refkey(r)))unassigned.push({position_ref:r,reason:memberMap.has(p.counterparty_entity_id)?'UNASSIGNED_INTRAGROUP_POSITION':'EXTERNAL_COUNTERPARTY'});}
  const unresolved=results.some(r=>!['matched','zero'].includes(r.status))||unassigned.some(u=>u.reason==='UNASSIGNED_INTRAGROUP_POSITION');let worksheet:Obj|null=null,rollup:Obj[]|null=null;
  if(!unresolved){const debit=allLegs.filter(l=>l.side==='debit').reduce((n,l)=>n+BigInt(l.amount_minor),0n),credit=allLegs.filter(l=>l.side==='credit').reduce((n,l)=>n+BigInt(l.amount_minor),0n);worksheet={worksheet_id:q.worksheet_id,currency:'USD',basis:'functional-usd',legs:structuredClone(allLegs),debit_minor:String(debit),credit_minor:String(credit),residual_minor:String(debit-credit)};rollup=[];for(const role of ['receivable','payable','income','expense']){const before=[...positions.values()].filter(p=>p.role===role).reduce((n,p)=>n+BigInt(p.raw_balance_minor),0n),delta=allLegs.filter(l=>positions.get(refkey(l.position_ref))!.role===role).reduce((n,l)=>n+BigInt(l.amount_minor)*(l.side==='debit'?1n:-1n),0n);rollup.push({role,combined_before_raw_minor:String(before),worksheet_raw_delta_minor:String(delta),combined_after_raw_minor:String(before+delta)});}}
  const copies=orderedSources.map(s=>{const copy=structuredClone(s);copy.positions.sort((a:Obj,b:Obj)=>compare(a.position_id,b.position_id));return copy;});return {status:unresolved?'indeterminate':'ok',code:unresolved?'WORKSHEET_UNRESOLVED':'ELIMINATION_WORKSHEET_READY',scope:{worksheet_id:q.worksheet_id,group_id:q.group_id,period:q.period,knowledge_cutoff:K,currency:'USD',basis:'functional-usd',basis_id:q.basis_id,policy_id:q.policy_id},member_sources:copies,pair_results:results,unassigned,worksheet,rollup};
 }catch(error){if(error instanceof Invalid)return failed(error.message);throw error;}
}
Full-height labintercompany labOpen full screen
Written by

Fintech engineer building market-data and financial systems, and the author of every article, glossary record, and reference implementation on The Fintech Builder.