from pathlib import Path
from fractions import Fraction as F
import hashlib,json,re,datetime
import exact_matrix as sp

ROOT=Path('/workspace/scratch/1b40da62dcbd')
OUT=ROOT/'c932_c1131/prep/structural_transfer'
question_path=OUT/'questions_before_calculation.json'
questions=json.loads(question_path.read_text())
assert len(questions['questions'])==24
question_hash=hashlib.sha256(question_path.read_bytes()).hexdigest()
paths={
 'File58':ROOT/'project_sources/19-File_58.Numbers_28_29_Tishri_Sacrificial_Ledger-2-.md',
 'File43':ROOT/'project_sources/06-File_43.Genealogical_Bridge-2-.md',
 'File54':ROOT/'project_sources/07-File_54.Luke_70_Year_Genealogical_Lattice-5-.md',
 'File70':ROOT/'project_sources/24-File_70.Genesis_Toledot_Atonement_Weave-10-1-.md',
}
lines={key:p.read_text().splitlines() for key,p in paths.items()}
def excerpt(key,start,end):return {'source':key,'line_start':start,'line_end':end,'raw':'\n'.join(lines[key][start-1:end])}
def table(key,header):
 a=lines[key]; start=next(i for i,s in enumerate(a) if s.strip()==header);end=start
 while end<len(a) and a[end].startswith('|'):end+=1
 raw=a[start:end]
 return {'source':key,'line_start':start+1,'line_end':end,'raw':'\n'.join(raw),'header':[x.strip() for x in raw[0].strip('|').split('|')],'rows':[{'line':start+3+i,'raw':s,'cells':[x.strip() for x in s.strip('|').split('|')]} for i,s in enumerate(raw[2:])]}
tabs={
 'Esau':table('File58','| Species | Clean state | Sex | Printed numeral |'),
 'clean_walk':table('File58','| Step | Increment | Landing |'),
 'Tishri':table('File58','| Category | Bulls | Rams | Lambs | Goats | Total |'),
 'festival':table('File58','| Step | Day | Day total | `×7` | Cumulative | Landing |'),
 'toledot':table('File70','| Occurrence | Major section | Reference | File_70 function |'),
 'NT_display':table('File43',"| Primary 70-year generation | Matthew's genealogy | Luke's genealogy |"),
}
inputs={'status':'Preparation only; question register predates this calculation. No numbered root action.',
 'question_register_sha256':question_hash,
 'sources':{k:{'path':str(p),'sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'bytes':p.stat().st_size} for k,p in paths.items()},
 'literal_tables':tabs,
 'excerpts':[excerpt('File58',1337,1434),excerpt('File58',750,798),excerpt('File58',1015,1044),excerpt('File54',449,502),excerpt('File54',679,724),excerpt('File70',2969,3002),excerpt('File70',3401,3446)],
 'source_constraint_status':{
  'Esau_combined_measurements':'Published registers derived from the same nine counts; a reduced measurement basis reconstructs those counts, not their historical origin.',
  'Tishri_templates':'Ratios/support of the printed ritual rows are retained as source premises. Sukkot template itself retains70/14/98/7; this analysis does not claim those numbers arise from margins.',
  'Sukkot_sequence':'Total189 and seven source day totals are printed. Constant decrement1 summarizes those same source totals. Bull sequence additionally requires the earlier admitted uniform nonbull17/day allocation.',
  'NT_knots':'The source Enoch fork and named display positions are already fitted/source-controlled. Reverse solving is conditional identifiability, not a new independent witness.',
  'toledot':'Eleven formula occurrences and ten major sections are directly distinguished by File70. Reflection is an index comparison, not chronology or word-level textual symmetry.',
 },
 'prior_work_not_repeated':['C834–845 marginal/rank facts','C846–847 separate clean/female registers','C848 household-prefix example','C859–870 complete NT display and returns','C587–607 Enoch carrier fields','C632–710 Toledot calendar/chronological rails'],
}
(OUT/'inputs.json').write_text(json.dumps(inputs,indent=2,ensure_ascii=False)+'\n')

def matrix_json(A):return [[str(q) if q.denominator!=1 else int(q) for q in row] for row in A.tolist()]
def vjson(v):return [str(q) if getattr(q,'denominator',1)!=1 else int(q) for q in v]
def prefixes(order,n=9):
 rows=[];a=[0]*n
 for j in order:a[j]=1;rows.append(a.copy())
 return sp.Matrix(rows)
count=sp.Matrix([int(r['cells'][3].strip('`')) for r in tabs['Esau']['rows']])
C=prefixes([1,3,5,0,2,4]);FEMALE=prefixes([0,2,6,4,7]);U=prefixes([7,6,8]);T=sp.ones(1,9)
joint=C.col_join(FEMALE);joint_total=joint.col_join(T)
basis=C.col_join(FEMALE[2,:]).col_join(FEMALE[4,:]).col_join(T)
allwalk=C.col_join(FEMALE).col_join(U)
d={
 'ST01':{'combined_rank':joint_total.rank(),'counts':vjson(count),'measurements':vjson(joint_total*count),'recovered':vjson(joint_total.gauss_jordan_solve(joint_total*count)[0])},
 'ST02':{'rank_without_total':joint.rank(),'kernel': [vjson(x) for x in joint.nullspace()]},
 'ST03':{'basis_labels':['clean_prefix1','clean_prefix2','clean_prefix3','clean_prefix4','clean_prefix5','clean_prefix6','female_prefix3','female_prefix5','grand_total'],'basis_matrix':matrix_json(basis),'determinant':int(basis.det()),'measurement_values':vjson(basis*count),'inverse':matrix_json(basis.inv())},
 'ST04':{'clean_unclean_rank':C.col_join(U).rank(),'determinant':int(C.col_join(U).det()),'clean_prefixes':vjson(C*count),'unclean_prefixes':vjson(U*count)},
 'ST05':{'all_three_register_rows':allwalk.rows,'rank':allwalk.rank(),'dependency_dimension':len(allwalk.T.nullspace()),'left_relations': [vjson(x) for x in allwalk.T.nullspace()], 'source_only_equalities':{'clean_prefix1_equals_unclean_prefix1':int((C*count)[0])==int((U*count)[0]),'clean_prefix3_equals_unclean_prefix2':int((C*count)[2])==int((U*count)[1])},'note':'The two equalities at counts20 and50 require the supplied count values; they are not identities for arbitrary counts.'},
 'ST06':{'count_rank_with_both_anchored_walks':9,'absolute_coordinate_unknown':'A','gauge':'Adding the same constant to every landing and A leaves every reconstructed count unchanged. One absolute anchor removes this one-dimensional freedom.'},
}
# Templates are fixed to the source ratios/support; the seven amplitudes are unknown.
# Amplitudes for daily and Sabbath rows are numbers of lambs, not event counts.
templates=sp.Matrix([[0,0,2,1,1,70,1],[0,0,1,1,1,14,1],[1,1,7,7,7,98,7],[0,0,1,1,1,7,1]])
species=sp.Matrix([75,18,176,11]);equal=sp.Matrix([[0,0,0,1,-1,0,0],[0,0,0,0,1,0,-1]])
fixed_daily=sp.Matrix([[1,0,0,0,0,0,0]])
A=templates.col_join(equal).col_join(fixed_daily);b=species.col_join(sp.zeros(2,1)).col_join(sp.Matrix([44]))
amps=A.inv()*b
d['ST07']={'template_matrix':matrix_json(templates),'species_only_rank':templates.rank(),'species_only_kernel':[vjson(x) for x in templates.nullspace()],'complete_constraint_matrix':matrix_json(A),'rank':A.rank(),'amplitudes':vjson(amps),'meaning':['daily lamb count','Sabbath lamb count','New Moon row multiplier','Trumpets multiplier','Atonement multiplier','Sukkot block multiplier','Eighth Day multiplier']}
d['ST08']={'without_daily_rank':templates.col_join(equal).rank(),'without_daily_kernel':[vjson(x) for x in templates.col_join(equal).nullspace()],'without_festival_equalities_rank':templates.col_join(fixed_daily).rank(),'without_festival_equalities_kernel':[vjson(x) for x in templates.col_join(fixed_daily).nullspace()],'note':'Equal feast multipliers and the44 daily lamb count are independently retained source constraints; they are not inferred from species totals.'}
first=sp.Rational(189+sum(range(7)),7)
d['ST09']={'first_day_from_total189_n7_decrement1':int(first),'day_totals':[int(first-i) for i in range(7)],'conditional_bulls_with_uniform_nonbull17':[int(first-i-17) for i in range(7)],'same_total_positive_strictly_descending_day_sequences':[[27+3*k-i*k for i in range(7)] for k in range(1,9) if 27-3*k>0], 'same_total_positive_bull_sequences_given_nonbull17':[[10+3*k-i*k for i in range(7)] for k in range(1,4)], 'note':'Decrement1 remains the source constraint. The displayed alternative sequences are formal diagnostics with the same189 day total (or70 bull total), not admitted ritual alternatives.'}
fw=[0];rv=[0]
for v in [30,29,28,27,26,25,24,10]:fw.append(fw[-1]+v)
for v in [10,24,25,26,27,28,29,30]:rv.append(rv[-1]+v)
def fit(prefix):
 scale=sp.Rational(1446-536,prefix[7]-prefix[2]);anchor=1446+scale*prefix[2]
 return {'scale':str(scale),'anchor':str(anchor),'landings':[str(anchor-scale*c) for c in prefix]}
d['ST10']={'declared_landings':{'step2':1446,'step7':536},'forward_fit':fit(fw),'constraint_determinant':fw[7]-fw[2]}
d['ST11']={'wrong_order_same_positions_fit':fit(rv),'third_constraint':'The separately recorded head1859 or final466 rejects reverse order with unchanged step2/step7 role assignments. These are already fitted source tables, not untouched holdouts. Reverse order has its own correctly indexed source walk; no source route is repaired.'}
# Distances w, carrier u. The second equation is exactly6 times the first.
knot=sp.Matrix([[sp.Rational(1,69),-1],[sp.Rational(2,23),-6]])
metric=knot.col_join(sp.Matrix([[0,35]]));rhs=sp.Matrix([0,0,2450])
d['ST12']={'unknowns':['Enoch radius w','slot unit u'],'matrix':matrix_json(knot),'rank':knot.rank(),'second_row_multiple':6}
d['ST13']={'kernel':[vjson(x) for x in knot.nullspace()],'relation':'w=69u','free_metric_parameters':1}
d['ST14']={'augmented_metric_rank':metric.rank(),'solution_w_u':vjson(metric.gauss_jordan_solve(rhs)[0]),'retained_condition':'The source BJ trunk2450 occupies35 supplied NT slots.'}
place=sp.Matrix([[1,55],[1,20]]);ys=sp.Matrix([3856,1406]);au=place.inv()*ys
d['ST15']={'unknowns':['hinge A','unit u'],'placement_matrix':matrix_json(place),'determinant':int(place.det()),'source_BC_values':vjson(ys),'solution':vjson(au),'conditional_redundancy':'With these supplied co-registrations, separate6BC and70-year numeric premises are derivable. Because the displayed slots were generated with those premises, this is a consistency/reparameterization result, not independent provenance reduction.'}
d['ST16']={'with_head_bookend_slots':[21,35,21],'without_head_bookend_slots':[20,35,21],'outer_arm_difference_years':70,'status':'Role-removal diagnostic; no source span lock is changed.'}
# Token quotient on complete primary boundary labels (not genealogical identity).
ntrows=tabs['NT_display']['rows'];ntlabels=[r['cells'][2] for r in ntrows[:78]]
classes={}
for i,name in enumerate(ntlabels):classes.setdefault(name,[]).append(i)
viol=[]
for name,idx in classes.items():
 targets=sorted(set(ntlabels[77-i] for i in idx))
 if len(targets)>1:viol.append({'source_token':name,'indices':idx,'reflected_tokens':targets})
d['ST17']={'primary_boundary_labels':78,'repeated_token_classes':{k:v for k,v in classes.items() if len(v)>1},'reflection_descends_to_token_quotient':not viol,'violations':viol,'guard':'Token equality is used solely as a formal quotient diagnostic and never identifies the named persons.'}
# Exact literary occurrence quotient.
lit=tabs['toledot']['rows'];assert len(lit)==11
names=[r['cells'][1] for r in lit];major=[];ordered=[]
for n in names:
 if n not in ordered:ordered.append(n)
 major.append(ordered.index(n)+1)
partition=[[i+1 for i,v in enumerate(major) if v==j] for j in range(1,11)]
q_by_i={i+1:v for i,v in enumerate(major)}
bad=[]
for block in partition:
 images=sorted(set(q_by_i[12-i] for i in block))
 if len(images)>1:bad.append({'block':block,'reflected_occurrences':[12-i for i in block],'reflected_major_sections':images})
d['ST18']={'occurrence_names':names,'major_section_order':ordered,'occurrence_to_section':major,'partition':partition}
d['ST19']={'occurrence_reflection':'i -> 12-i','Terah_occurrence':6,'descends':not bad,'obstructions':bad}
multiplicities=[len(x) for x in partition]
d['ST20']={'section_multiplicities':multiplicities,'sum':sum(multiplicities),'weighted_Terah_slot':sum(multiplicities[:5])+1,'unweighted_Terah_rank':6,'before_after_other_sections':[5,4],'before_after_other_occurrences':[5,5]}
closure=[b for b in partition if b not in [[2],[3]]];closure.insert(1,[2,3]);closure.sort(key=min)
d['ST21']={'smallest_reflection_stable_closure':closure,'quotient_classes':len(closure),'extra_required_merge':['Adam','Noah'],'authorization':'Diagnostic only; source treats Adam and Noah as different major sections.'}
d['ST22']={'occurrence_poles':[1,6,11],'occurrence_arm_steps':[5,5],'major_section_poles':[1,6,10],'major_section_arm_steps':[5,4],'note':'Both counts are legitimate measured objects. Source section multiplicity recovers the occurrence measure without changing textual sections.'}
d['ST23']={'criterion':'A quotient map q carries an involution R iff q(x)=q(y) implies q(Rx)=q(Ry). A selected boundary subset carries R iff it is R-invariant. For ordered interval coarsening, R must permute complete blocks.','toledot':'Fails because Esau occurrences9,10 reflect into distinct Noah3 and Adam2 sections.','NT_490_blocks':{'fine_edges':77,'block_size':7,'blocks':11,'block_map':'b -> 10-b (b=0,...,10)','boundary_map':'m -> 11-m (m=0,...,11)'},'guard':'Do not conflate a point quotient, interval coarsening and invariant boundary subset.'}
d['ST24']={'strong_new_connections':['Joint Esau measurement basis is unimodular and recovers the complete list.','Tishri row predicates distinguish allowed cell changes from unrestricted margin-preserving changes.','NT marked Key equations have rank1, exposing their dependence.','Toledot contraction loses reflection unless multiplicity or a non-source regrouping is retained.'],'input_reduction_ceiling':'Each reconstruction is conditional. Derived measurements of the same source data provide exact alternative coordinates, not independent historical evidence for the values.'}
I9=[[int(i==j) for j in range(9)] for i in range(9)]
checks={
 'Esau_basis_inverse_identity':matrix_json(basis*basis.inv())==I9,
 'Esau_basis_recovers_literal_nine':list(basis.inv()*(basis*count))==list(count),
 'joint_kernel_certified':all(all(z==0 for z in joint*v) for v in joint.nullspace()),
 'all_register_relations_certified':all(all(z==0 for z in allwalk.T*v) for v in allwalk.T.nullspace()),
 'Tishri_constraints_recover_rhs':list(A*amps)==list(b),
 'Tishri_template_kernels_certified':all(all(z==0 for z in templates*v) for v in templates.nullspace()),
 'all_day_alternatives_preserve189':all(sum(v)==189 and all(x>0 for x in v) for v in d['ST09']['same_total_positive_strictly_descending_day_sequences']),
 'all_bull_alternatives_preserve70':all(sum(v)==70 and all(x>0 for x in v) for v in d['ST09']['same_total_positive_bull_sequences_given_nonbull17']),
 'NT_knot_dependence_exact':all(knot[1,i]==6*knot[0,i] for i in range(2)),
 'NT_placement_recovers_two_source_coordinates':list(place*au)==list(ys),
 'Toledot_closure_is_reflection_stable':all(sorted(12-i for i in block) in closure for block in closure),
 'all24_questions_have_diagnostics':set(d)=={x['id'] for x in questions['questions']},
}
assert all(checks.values()),checks
diagnostics={'status':'Provisional preparation diagnostics for root selection; no numbered actions.','question_register_sha256':question_hash,'created_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'diagnostics':d,'verification_checks':checks}
(OUT/'diagnostics.json').write_text(json.dumps(diagnostics,indent=2,ensure_ascii=False)+'\n')
print(json.dumps({'questions':24,'source_tables':len(tabs),'Esau_joint_rank':d['ST01']['combined_rank'],'Esau_basis_det':d['ST03']['determinant'],'Tishri_rank':d['ST07']['rank'],'knot_rank':d['ST12']['rank'],'toledot_descends':d['ST19']['descends'],'diagnostics':len(d)},indent=2))
