"""Independent preparatory symbolic analysis; no numbered-action writer."""
from pathlib import Path
import exact as S
from fractions import Fraction
import json,hashlib
ROOT=Path(__file__).resolve().parent
BASE=ROOT.parents[2]
qdoc=ROOT/'questions_declared.json'
assert qdoc.exists()
names=['Q','T','qI','qX','xJ','jCall','callL','callZ','L_I','L_L','L_J','L_Z','L_A']
v=S.symbols(' '.join(names));z=dict(zip(names,v));Q,T,qI,qX,xJ,jc,cl,cz,LI,LL,LJ,LZ,LA=v
vals=[1866,1406,1,15,60,77,10,14,137,137,147,110,175];sub=dict(zip(v,vals))
node={'Q':Q,'Ishmael_birth':Q-qI,'Ishmael_death':Q-qI-LI,'Isaac_birth':Q-qX,'Jacob_birth':Q-qX-xJ,'Jacob_call':Q-qX-xJ-jc,'Jacob_death':Q-qX-xJ-LJ,'Levi_birth':Q-qX-xJ-jc-cl,'Levi_death':Q-qX-xJ-jc-cl-LL,'Joseph_birth':Q-qX-xJ-jc-cz,'Joseph_death':Q-qX-xJ-jc-cz-LZ,'terminal':T}
IB,ID,XB,JB,JC,JD,LB,LD,ZB,ZD=[node[k] for k in ['Ishmael_birth','Ishmael_death','Isaac_birth','Jacob_birth','Jacob_call','Jacob_death','Levi_birth','Levi_death','Joseph_birth','Joseph_death']]
claims={
 'rail_Isaac_Levi_equals_Jacob_life':XB-LB-LJ,
 'rail_call_Levi_death_equals_Jacob_life':JC-LD-LJ,
 'equal_Ishmael_Levi_lives':LI-LL,
 'twin_death_arms':ID-2*LD+T,
 'Joseph_double_Jacob_life':ZB-T-2*LJ,
 'common_E_J_landing':13*(Q-ZD)-12*(Q-LD),
 'E_birth_arm_equals_Abraham_life':25*(IB-LB)-23*LA,
 'centred_Jacob_Ishmael_Abraham':2*(IB-LB)-LJ-LA,
 'Ishmael_middle_gap_equals_household':IB-LB-LJ-cz,
}
consequences={
 'three147_vertical60':LB-JD-xJ,
 'three147_vertical77':JD-LD-jc,
 'three147_diagonal_Levi_life':XB-JC-LL,
 'equal_birth_death_displacement':(IB-LB)-(ID-LD),
 'Covenant_Levi_via_Isaac':Q-LD-qX-LJ-LL,
 'Covenant_Levi_via_Ishmael_identity':Q-LD-qI-(IB-LB)-LL,
 'Joseph_Levi_gap_biography_identity':ZD-LD-(LL-LZ-(cz-cl)),
 'birth_death_displacement_identity':(IB-LB)-(ID-LD)-(LI-LL),
 'common_transformed_point_difference':Q-S.Rational(25,23)*(Q-ZD)-(Q-S.Rational(300,299)*(Q-LD)),
}
def coeff(e,vs=v):return [S.expand(e).coeff(x) for x in vs]
def serial(x):
 if isinstance(x,Fraction):return int(x) if x.denominator==1 else str(x)
 if isinstance(x,S.MatrixBase):return [[serial(y) for y in row] for row in x.tolist()]
 if isinstance(x,S.Basic):return int(x) if x.is_Integer else str(x)
 if isinstance(x,dict):return {k:serial(y) for k,y in x.items()}
 if isinstance(x,(list,tuple)):return [serial(y) for y in x]
 return x
M=S.Matrix([coeff(e) for e in claims.values()]);rank=M.rank();basis_idx=list(M.T.rref()[1]);B=M[basis_idx,:]
relations={}
for label,e in consequences.items():
 row=S.Matrix([coeff(e)])
 if B.col_join(row).rank()==rank:
  w=S.linsolve((B.T,row.T));relations[label]={'expression':S.factor(e),'basis_weights':next(iter(w)),'zero_identity':S.expand(e)==0}
 else:relations[label]={'expression':S.factor(e),'additional_rank':1}
claim_records=[];old_rank=0
for i,(label,e) in enumerate(claims.items()):
 newrank=M[:i+1,:].rank();claim_records.append({'name':label,'expression':S.factor(e),'source_value':S.simplify(e.subs(sub)),'incremental_rank':newrank-old_rank});old_rank=newrank

Vcoord=S.Matrix([coeff(e, v[:-1]) for e in node.values()])
normalizations={'Ishmael_Levi_birth_arm161':IB-LB-161,'Levi_terminal_arm161':LD-T-161,'Covenant_Levi299':Q-LD-299,'Covenant_Joseph276':Q-ZD-276,'Jacob_life147':LJ-147,'Abraham_life175':LA-175,'absolute_Covenant1866':Q-1866}
normalization_result=[];A=M
for label,e in normalizations.items():
 new=A.col_join(S.Matrix([coeff(e)]));normalization_result.append({'name':label,'expression':S.factor(e),'added_rank':new.rank()-A.rank(),'source_value':e.subs(sub)});A=new
remaining_after_normalization=A.nullspace()
identifiers=[]
for label,e in [('Covenant_Ishmael_offset1',qI-1),('Isaac_Jacob_interval60',xJ-60)]:
 new=A.col_join(S.Matrix([coeff(e)]));identifiers.append({'name':label,'added_rank':new.rank()-A.rank(),'source_value':e.subs(sub)});A=new
centred_E_identity=S.expand(25*claims['centred_Jacob_Ishmael_Abraham']-2*claims['E_birth_arm_equals_Abraham_life'])

# Source tree sensitivity: columns are literal source-local offsets and lifespans.
sens={name:{k:int(S.diff(e,sy)) for k,e in node.items() if S.diff(e,sy)!=0} for name,sy in zip(names,v)}

# 42-coordinate field, declared before rank evaluation.
b=S.symbols('b0:7');phase,half=S.symbols('phase half');rv=list(b)+[phase,half]
field=[];labels=[]
for i in range(7):
 for j in range(2):
  for k in range(3):field.append(b[i]+j*phase-k*half);labels.append([i,j,k])
Fmat=S.Matrix([coeff(e,rv) for e in field]);obs_indices=[labels.index([i,0,0]) for i in range(7)]+[labels.index([0,1,0]),labels.index([0,0,1])]
obs=Fmat[obs_indices,:]
def rank_after(eqs):
 # Independent output dimensions equal image rank restricted to the equation kernel.
 eqmat=S.Matrix([coeff(e,rv) for e in eqs]);N=S.Matrix.hstack(*eqmat.nullspace())
 return (Fmat*N).rank()
repeat=(b[3]-b[4])-(b[5]-b[6])
clutch={
 'parameter_names':[str(x) for x in rv], 'shape':[7,2,3], 'rank':Fmat.rank(),'output_count':42,'independent_output_relations':42-Fmat.rank(),
 'observation_labels':[labels[i] for i in obs_indices],'observation_determinant':obs.det(),
 'relation_matrix':Fmat.T.nullspace(),
 'relation_half_equals_twice_phase':{'expression':half-2*phase,'rank':rank_after([half-2*phase])},
 'repeated137_source_equality':{'expression':repeat,'rank':rank_after([repeat])},
 'both_relations_rank':rank_after([half-2*phase,repeat]),
 'no_Aaron_phase_observations_rank':S.Matrix([Fmat[i,:] for i,x in enumerate(labels) if x[1]==0]).rank(),
 'no_shifted_observations_rank':S.Matrix([Fmat[i,:] for i,x in enumerate(labels) if x[2]==0]).rank(),
 'one_entire_row_absent_rank':S.Matrix([Fmat[i,:] for i,x in enumerate(labels) if x[0]!=3]).rank(),
 'half_plus_full_only_single_phase_rank':S.Matrix([Fmat[i,:] for i,x in enumerate(labels) if x[1]==0 and x[2]>0]).rank(),
 'exact_link_source_values':{'phase':'7/2','half':7,'half_minus_two_phase':0},
 'whole_year_link_source_values':{'phase':3,'half':7,'half_minus_two_phase':1},
}

# Endogenous clutch derived from maximum lifespan envelope.
Mo,Ex,shift,LX,LK,LAm=S.symbols('Moses_birth Exodus full_state_shift L_Isaac L_Kohath L_Amram')
LBf=LB+shift;LDf=LD+shift;XBf=XB+shift
cum=[Mo+LAm+LK+LL+LJ+LX+LA,Mo+LAm+LK+LL+LJ+LX,Mo+LAm+LK+LL+LJ,Mo+LAm+LK+LL,Mo+LAm+LK,Mo+LAm,Mo]
d=S.expand(LL+LK+LAm+(Mo-Ex)-(LBf-Ex))
translated=[S.simplify(x-d) for x in cum]
relative=[S.simplify(x-LDf) for x in translated]
join={'clutch_formula':S.factor(d),'clutch_equals_cumulative_Kohath_minus_regular_Levi_death':S.simplify(d-(cum[4]-LDf)),
 'Exodus_derivative_of_clutch':S.diff(d,Ex),'translated_Moses_anchor_derivatives':[S.diff(x,Mo) for x in translated],
 'translated_relative_to_Levi_death':relative,
 'Kohath_join_residual':S.simplify(translated[4]-LDf),
 'Levi_join_residual':S.simplify(translated[3]-LBf),
 'Jacob_to_Isaac_residual':S.factor(translated[2]-XBf),
 'Jacob_join_equals_negative_first_147_condition':S.simplify(translated[2]-XBf+claims['rail_Isaac_Levi_equals_Jacob_life']),
 'fixed_clutch_Aaron_phase_residual':phase,'recomputed_clutch_on_each_phase_residual':0,
 'source_values':{'full_state_shift':215,'Moses_birth':1526,'Exodus':1446,'L_Isaac':180,'L_Kohath':133,'L_Amram':137},
}
subfull={**sub,Mo:1526,Ex:1446,shift:215,LX:180,LK:133,LAm:137}
join['calculated_clutch']=d.subs(subfull);join['calculated_original']= [x.subs(subfull) for x in cum];join['calculated_translated']=[x.subs(subfull) for x in translated]

# Entire coupled map: the source-local regular variables plus new cumulative inputs.
coupled_vars=list(v)+[Mo,shift,LX,LK,LAm,phase]
coupled_obs=list(node.values())+[LA]+[S.expand(cum[i]+j*phase-S.Rational(k,2)*d) for i in range(7) for j in range(2) for k in range(3)]
CM=S.Matrix([coeff(e,coupled_vars) for e in coupled_obs])
coupled={'parameter_names':[str(x) for x in coupled_vars],'source_parameter_count':len(coupled_vars),'output_count':len(coupled_obs),'rank':CM.rank(),'new_scalar_clutch_parameter_count':0,'clutch_rule':'d = cumulative Kohath minus regular full-state Levi death; h=d/2','shared_lifespans':'L_A,L_J,L_L are one source value each, reused in both source paths; L_I and L_Amram remain separately labelled equal-valued source lifespans'}

out={'status':'PREPARATORY CALCULATION, not numbered research','question_file_sha256':hashlib.sha256(qdoc.read_bytes()).hexdigest(),
 'primitive_parameters':dict(zip(names,vals)),
 'primitive_caveat':'This is a minimal source-local packet for the declared path, not a claim of historically independent observations. qI/qX are author-bound Covenant offsets; their broader textual-age dependencies remain outside this local coordinate parameterization.',
 'node_expressions':node,'generated_values':{k:e.subs(sub) for k,e in node.items()},'coordinate_map':{'rank':Vcoord.rank(),'shape':Vcoord.shape,'determinant':Vcoord.det(),'matrix':Vcoord,'relative_dimension':Vcoord.rank()-1,'global_translation_vector':[1,1]+[0]*10},
 'claims':claim_records,'claim_matrix':M,'claim_rank':rank,'claim_basis':[list(claims)[i] for i in basis_idx],'claim_basis_indices':basis_idx,'claim_row_dependencies':M.T.nullspace(),'claim_nullspace':M.nullspace(),
 'consequence_dependencies':relations,'normalization_incremental_rank':normalization_result,'remaining_after_normalization':remaining_after_normalization,'additional_identifying_source_values':identifiers,'rank_after_identifying_values':A.rank(),'centred_vs_E_conditional_identity':{'expression':centred_E_identity,'source_value':centred_E_identity.subs(sub),'meaning':'25*centred−2*E_condition=21*L_A−25*L_J. When source L_A175 and L_J147 are fixed, the two radius conditions are the same condition, not independent.'},'source_sensitivity':sens,'clutch_field':clutch,'endogenous_clutch_joins':join,'coupled_field':coupled,
 'limits':['No generated161/299/276/300/1566 stored as primitive input','Formal sensitivity directions are diagnostics, not admitted source variants','Ranks are conditional algebraic dimensions, not probability or historical independence','No source edit, primer execution,1486 calculation, upstream Gear transport or second decimal inversion']}
(ROOT/'diagnostics.json').write_text(json.dumps(serial(out),indent=2,ensure_ascii=False)+'\n')
print(json.dumps({'coordinate_rank':Vcoord.rank(),'claim_rank':rank,'claim_basis':[list(claims)[i] for i in basis_idx],'claim_nullity':len(M.nullspace()),'clutch_rank':Fmat.rank(),'clutch_both_source_links_rank':clutch['both_relations_rank'],'coupled_rank':CM.rank(),'clutch_derived':int(d.subs(subfull))}))
