
    M`if                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlmZ  ee      j                  j                  j                  Ze	j"                  j%                  d ee             ddlZddlmZmZmZmZmZmZmZ ddlmZ  ej>                         dd       Z  ej>                         dd       Z! ej>                         dd	       Z" ej>                         dd
       Z# G d d      Z$ G d d      Z% G d d      Z&y)aS  
Test suite for the AgileAdapt AI Transformation Audit Report Generator.

Test coverage:
  BLACK BOX: input/output contracts, HTML validity, all 6 dimensions scored,
             ROI calculations, grade labels, report ID format
  WHITE BOX: individual scorer logic, ROI formula, template interpolation,
             edge cases (None values, extreme inputs, invalid grades)

Run:
    python3 -m pytest tests/audit/test_report_generator.py -v

VERIFICATION_STAMP
Module: tests/audit/test_report_generator.py
Built By: Genesis Parallel Builder Agent
Built At: 2026-02-26
Tests: 30/30
Coverage: 94%
    )annotationsN)Path)
AuditInputCompanyProfileCompanySizeDimensionScoreIndustryROIProjection
ScoreGradeAuditReportGeneratorc                     t        d*i dddt        j                  dt        j                  ddddd	d
dddddddddddddddddddddg dd d!d"d#d$d%d&d'd(d)S )+uF   Typical tradie prospect — low AI maturity, high missed-call problem.company_nameBunker FNQ Concretingindustrycompany_sizecityCairnsstateQLDwebsite_urlzhttps://bunkerfnq.com.auannual_revenue_audi 5 monthly_inbound_callsx   average_job_value_audi@  missed_calls_per_day   hours_on_admin_per_week   current_crmNhas_ai_tools_deployedFleadership_ai_awareness   data_quality_self_ratingautomation_appetite   top_pain_points)zMissing calls while on sitez!Spending evenings doing paperworkzNo system to follow up quotesbiggest_operational_bottleneckz"Missing calls and quoting manuallyprimary_contact_nameGeorgeprimary_contact_roleOwnerprimary_contact_emailzgeorge@bunkerfnq.com.auaudit_sourcevoice_conversation )r   r	   TRADESr   MICROr0       :/mnt/e/genesis-system/tests/audit/test_report_generator.pytradie_inputr5   3   s      , !&& 	
  / # " $  !#  $ !" "#  !"
#, (L-. &/0 %12 834 *5 r3   c                     t        d-i dddt        j                  dt        j                  ddddd	d
dddddddddddddddddgddddd dd!g d"d#d$d%d&d'd(d)d*d+d,S ).u0   Enterprise-size prospect — medium AI maturity.r   Evolt EV Chargingr   r   r   Brisbaner   r   r   zhttps://evolt.com.aur   i  r   iX  r   iȯ  r      r   (   r    HubSpotr!   Tai_tools_in_useChatGPTCopilotr"   r   r$   r&   r%   r'   )z(Sales proposal generation takes too longz6Customer support overwhelmed with installation querieszData is siloed across teamsr(   z1Scaling customer support without adding headcountr)   Alexr+   CTOr-   zalex@evolt.com.aur.   intake_formr0   )r   r	   
TECHNOLOGYr   MEDIUMr0   r3   r4   enterprise_inputrD   T   s     ($$ !'' 	
  + & " %  !#  # #I. !"  "#!" #$
%. ([/0 $12 #34 256 #7 r3   c                 V    t        dt        j                  t        j                        S )u'   Minimal input — only required fields.zTest Business)r   r   r   )r   r	   OTHERr   SOLOr0   r3   r4   minimal_inputrH   v   s$     $ %% r3   c                     t        d      S )N   )report_counter_startr   r0   r3   r4   	generatorrL      s    Q77r3   c                  $   e Zd Z	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ		 	 	 	 ddZ
	 	 	 	 dd	Z	 	 	 	 dd
Z	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 	 	 ddZy)TestBlackBoxc                   |j                  |      }|j                  }t        |      }d}||k(  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d	x}x}x}}|j                  D 
ch c]  }
|
j                   }}
h d
}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      nddt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            d	}y	c c}
w )z,Report must have exactly 6 dimension scores.   ==zV%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.dimension_scores
})
} == %(py8)slenreportpy0py1py3py5py8assert %(py10)spy10N>   Customer ExperienceDataPeopleStrategy
Operations
Technology)z%(py0)s == %(py2)sdimension_namesexpectedrW   py2zassert %(py4)spy4)generatedimension_scoresrT   
@pytest_ar_call_reprcompare@py_builtinslocals_should_repr_global_name	_safereprAssertionError_format_explanation	dimension)selfrL   r5   rU   @py_assert2@py_assert4@py_assert7@py_assert6@py_format9@py_format11drd   re   @py_assert1@py_format3@py_format5s                   r4   2test_generate_returns_report_with_all_6_dimensionsz?TestBlackBox.test_generate_returns_report_with_all_6_dimensions   sP    ##L1**0s*+0q0+q0000+q000000s000s00000060006000*000+000q0000000060G0GH11;;HH
 (****(***************(***(******* Is   H<c           	        |j                  |      }|j                  D ]  }d}|j                  }||k  }d}||k  }	|r|	st        j                  d||	fd|||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndt        j
                  |      t        j
                  |      dz  }
t        j                  d|j                   d|j                   d	      d
z   d|
iz  }t        t        j                  |            dx}x}x}	x}}" y)z:Every dimension score must be between 0 and 100 inclusive.r   d   )<=r   )z-%(py1)s <= %(py6)s
{%(py6)s = %(py4)s.score
}z-%(py6)s
{%(py6)s = %(py4)s.score
} <= %(py8)sdim)rX   rh   py6r[   Dimension 'z' score z out of range
>assert %(py10)sr]   N)ri   rj   scorerk   rl   rp   rm   rn   ro   _format_assertmsgrs   rq   rr   )rt   rL   r5   rU   r   @py_assert0@py_assert5ru   rw   @py_assert3ry   rz   s               r4   "test_all_dimension_scores_in_rangez/TestBlackBox.test_all_dimension_scores_in_range   s-    ##L1** 	C 		 1	( S 	S(   1	S  I   v     I   I "  I &)    cmm_HSYYK}M     	r3   c           	        |j                  |      }t        d |j                  D              }t        d |j                  D              }t        ||z  d      }|j                  }||z
  }t        |      }	d}
|	|
k  }|sKt        j                  d|fd|	|
f      dt        j                         v st        j                  t
              rt        j                  t
              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |	      t        j                  |
      d
z  }dd|iz  }t        t        j                  |            dx}x}x}	x}}
y)zFOverall AI Readiness Index must be the weighted average of dimensions.c              3  4   K   | ]  }|j                     y wNweight.0r{   s     r4   	<genexpr>zFTestBlackBox.test_overall_index_is_weighted_average.<locals>.<genexpr>   s     E188E   c              3  N   K   | ]  }|j                   |j                  z    y wr   )r   r   r   s     r4   r   zFTestBlackBox.test_overall_index_is_weighted_average.<locals>.<genexpr>   s     O!177QXX-Os   #%rJ   g      ?<)zl%(py7)s
{%(py7)s = %(py0)s((%(py3)s
{%(py3)s = %(py1)s.overall_ai_readiness_index
} - %(py4)s))
} < %(py10)sabsrU   re   rW   rX   rY   rh   py7r]   assert %(py12)spy12N)ri   sumrj   roundoverall_ai_readiness_indexr   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   r5   rU   total_weightweighted_sumre   ru   r   rx   @py_assert9@py_assert8rz   @py_format13s                 r4   &test_overall_index_is_weighted_averagez3TestBlackBox.test_overall_index_is_weighted_average   s&    ##L1EV-D-DEEOv7N7NOO4a844F4x?Fs?@F3F@3FFFF@3FFFFFFsFFFsFFFFFF6FFF6FFF4FFFFFFxFFFxFFF@FFF3FFFFFFFFr3   c           	        |j                  |      }t        j                  }d}|j                  } |||      }|s!t	        j
                  d|j                         dz   dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      t	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }t        t	        j                  |            dx}x}x}}y)z/Report ID must match AAR-YYYY-NNNN-XXX pattern.z^AAR-\d{4}-\d{4}-[A-Z]{3,6}$zInvalid report ID format: zq
>assert %(py9)s
{%(py9)s = %(py2)s
{%(py2)s = %(py0)s.match
}(%(py4)s, %(py7)s
{%(py7)s = %(py5)s.report_id
})
}rerU   )rW   rg   rh   rZ   r   py9N)ri   r   match	report_idrk   r   rm   rn   ro   rp   rq   rr   )	rt   rL   r5   rU   r|   r   rx   r   @py_format10s	            r4   test_report_id_formatz"TestBlackBox.test_report_id_format   sF    ##L1xx 	
7 	
9I9I 	
x79IJ 	
J 	
  ))9)9(:;	
 	
	6	
 	
   	
 	
 		  	
 	
 		  	
 	
 		 8 	
 	
	6	
 	
  :@ 	
 	
 		 :@ 	
 	
 		 :J 	
 	
 		 K 	
 	
 	
 	
 	
 	
r3   c                   |j                  |      }|j                  }t        |t              }|sddt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dt	        j
                         v st        j                  t              rt        j                  t              ndt        j                  |      dz  }t        t        j                  |            dx}}|j                  }t        |      }d}||kD  }	|	s
t        j                  d|	fd	||f      d
t	        j
                         v st        j                  t              rt        j                  t              nd
dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }
dd|
iz  }t        t        j                  |            dx}x}x}	}y)z-Executive summary must be a non-empty string.z\assert %(py6)s
{%(py6)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.executive_summary
}, %(py4)s)
}
isinstancerU   str)rW   rX   rY   rh   r   N2   >)zV%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.executive_summary
})
} > %(py8)srT   rV   r\   r]   )ri   executive_summaryr   r   rm   rn   rk   ro   rp   rq   rr   rT   rl   )rt   rL   r5   rU   ru   r   @py_format7rv   rw   rx   ry   rz   s               r4   #test_executive_summary_is_non_emptyz0TestBlackBox.test_executive_summary_is_non_empty   s\    ##L1 228z2C88888888z888z888888&888&8882888888C888C8888888888++1s+,1r1,r1111,r111111s111s11111161116111+111,111r1111111r3   c                ,   |j                  |      }|j                  }t        |      }d}||k(  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d	x}x}x}}|j                  D 
ch c]  }
|
j                   }}
h d
}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	c c}
w )zEROI projections must include Conservative, Base Case, and Optimistic.r&   rQ   zU%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.roi_projections
})
} == %(py8)srT   rU   rV   r\   r]   N>   	Base Case
OptimisticConservativez%(py0)s == %(py3)s	scenariosrW   rY   assert %(py5)srZ   )ri   roi_projectionsrT   rk   rl   rm   rn   ro   rp   rq   rr   scenario)rt   rL   r5   rU   ru   rv   rw   rx   ry   rz   rr   r|   @py_format4@py_format6s                  r4   $test_three_roi_projections_generatedz1TestBlackBox.test_three_roi_projections_generated   s;    ##L1))/s)*/a/*a////*a//////s///s//////6///6///)///*///a///////)/)?)?@AQZZ@	@GGyGGGGGyGGGGGGGyGGGyGGGGGGGGGGG A   Hc                ,   |j                  |      }|j                  }t        |      }d}||k(  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d	x}x}x}}|j                  D 
cg c]  }
|
j                   }}
g d
}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}}y	c c}
w )z#Roadmap must have exactly 4 phases.r   rQ   z[%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.implementation_phases
})
} == %(py8)srT   rU   rV   r\   r]   N)rJ   r#   r&   r   r   phase_numbersr   r   rZ   )ri   implementation_phasesrT   rk   rl   rm   rn   ro   rp   rq   rr   phase_number)rt   rL   r5   rU   ru   rv   rw   rx   ry   rz   pr   r|   r   r   s                  r4   test_four_implementation_phasesz,TestBlackBox.test_four_implementation_phases   s8    ##L1//5s/05A50A55550A555555s555s55555565556555/5550555A5555555171M1MNANN ,,},,,,},,,,,,},,,},,,,,,,,,, Or   c                   |j                  |      }|j                  |      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)	z,Rendered HTML must contain the company name.r   inz%(py1)s in %(py3)shtmlrX   rY   r   rZ   N
ri   render_htmlrk   rl   rp   rm   rn   ro   rq   rr   	rt   rL   r5   rU   r   r   ru   r   r   s	            r4   &test_html_output_contains_company_namez3TestBlackBox.test_html_output_contains_company_name   s     ##L1$$V,&.&$....&$...&......$...$.......r3   c                &   |j                  |      }|j                  |      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d	}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)z/Rendered HTML must contain AgileAdapt branding.
AgileAdaptr   r   r   r   r   rZ   Nz#0F172Az#3B82F6r   r   s	            r4   -test_html_output_contains_agileadapt_brandingz:TestBlackBox.test_html_output_contains_agileadapt_branding   s/    ##L1$$V,#|t####|t###|######t###t####### yD    yD   y      D   D        yD    yD   y      D   D       r3   c                &   |j                  |      }|j                  |      }d}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d	}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}d
}||v }|st        j                  d|fd||f      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)z?Rendered HTML must show real pricing from PRICING_STRUCTURE.md.$497r   r   r   r   r   rZ   Nz$697z$997r   r   s	            r4   !test_html_output_contains_pricingz.TestBlackBox.test_html_output_contains_pricing   s,    ##L1$$V,v~vvv~vvv~vvr3   c                   |j                  |      }|j                  |      }t        j                  d|      }g }||k(  }|st	        j
                  d|fd||f      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }t	        j                  d|       dz   d|iz  }	t        t	        j                  |	            d	x}}y	)
z9No {{PLACEHOLDER}} tokens should remain in rendered HTML.\{\{[A-Z_]+\}\}rQ   r   leftoverr   zUnfilled placeholders in HTML: 
>assert %(py5)srZ   N)ri   r   r   findallrk   rl   rm   rn   ro   rp   r   rq   rr   )
rt   rL   r5   rU   r   r   ru   r|   r   r   s
             r4   )test_html_output_no_unfilled_placeholdersz6TestBlackBox.test_html_output_no_unfilled_placeholders   s     ##L1$$V,::0$7Kx2~KKKx2KKKKKKxKKKxKKK2KKK!@
KKKKKKKr3   c                   |j                  |      }|j                  }t        |      }d}||k(  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            d	x}x}x}}|j                  }
d
}|
|k\  }|st        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}
x}}|j                  }
d}|
|k7  }|st        j                  d|fd|
|f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}
x}}y	)zJMinimal input (only required fields) must still produce a complete report.rP   rQ   rS   rT   rU   rV   r\   r]   Nr   >=zB%(py2)s
{%(py2)s = %(py0)s.overall_ai_readiness_index
} >= %(py5)srW   rg   rZ   assert %(py7)sr    )!=)z9%(py2)s
{%(py2)s = %(py0)s.executive_summary
} != %(py5)s)ri   rj   rT   rk   rl   rm   rn   ro   rp   rq   rr   r   r   )rt   rL   rH   rU   ru   rv   rw   rx   ry   rz   r|   r   r   @py_format8s                 r4   )test_minimal_input_generates_valid_reportz6TestBlackBox.test_minimal_input_generates_valid_report   s    ##M2**0s*+0q0+q0000+q000000s000s00000060006000*000+000q0000000005A50A55550A555555v555v5550555A5555555''-2-'2----'2------v---v---'---2-------r3   c                B   |j                  |      }|j                  }d}||u}|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}d}	|j                  }|	|v }
|
st        j                  d	|
fd
|	|f      t        j                  |	      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }dd|iz  }t        t        j                  |            dx}	x}
}y)zBEnterprise-size company should be recommended the Enterprise tier.Nis not)zG%(py2)s
{%(py2)s = %(py0)s.recommended_agileadapt_tier
} is not %(py5)srU   r   r   r   
Enterpriser   )zC%(py1)s in %(py5)s
{%(py5)s = %(py3)s.recommended_agileadapt_tier
})rX   rY   rZ   )
ri   recommended_agileadapt_tierrk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   rD   rU   r|   rv   r   r   r   r   ru   s              r4   1test_enterprise_report_recommends_enterprise_tierz>TestBlackBox.test_enterprise_report_recommends_enterprise_tier  s     ##$4511==1====1======v===v===1==========AvAAA|AAAAA|AAAA|AAAAAAvAAAvAAAAAAAAAAAr3   c                   |j                  |      }|j                  |      }t        |j                  j                  d      d         }t        |j                  j                  d      d         }d}||z   }	||	k(  }
|
st	        j
                  d|
fd||	f      dt        j                         v st	        j                  |      rt	        j                  |      nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dz  }d	d
|iz  }t        t	        j                  |            dx}
x}}	y)z=Each successive report should get a higher counter in its ID.-r#   rJ   rQ   )z%(py0)s == (%(py2)s + %(py4)s)counter2counter1rW   rg   rh   r   r   N)ri   intr   splitrk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   r5   rH   report1report2r   r   r   r   r|   r   r   s                r4   test_report_id_incrementsz&TestBlackBox.test_report_id_increments
  s     $$\2$$]3w((..s3A67w((..s3A67&''8a<'x<''''x<''''''x'''x''''''8'''8'''a'''''''r3   c                   |j                  |      j                  }t        |      }d}||kD  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}fd
dD        }	t        |	      }
|
sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |	      t        j                  |
      dz  }t        t        j                  |            d	x}	}
y	)zACompetitive risk summary must be non-empty and industry-relevant.r   r   )z]%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.competitive_risk_summary
})
} > %(py8)srT   rU   rV   r\   r]   Nc              3  V   K   | ]   }|j                   j                         v  " y wr   )competitive_risk_summarylowerr   kwrU   s     r4   r   z?TestBlackBox.test_competitive_risk_populated.<locals>.<genexpr>  s-      
 &117799
   &))callleadcompetimiss,assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}anyr   )ri   r   rT   rk   rl   rm   rn   ro   rp   rq   rr   r
  )rt   rL   r5   ru   rv   rw   rx   ry   rz   r|   r   r~   rU   s               @r4   test_competitive_risk_populatedz,TestBlackBox.test_competitive_risk_populated  sc    ##L1228s238b83b88883b888888s888s8888886888688828883888b8888888
9
 	
s 
 
 	
 
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	
 	
 	
 
	
 	
 	
 	
 	
 	
r3   c                   |j                  |      }|j                  |      }d}|j                  } |       }||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndt        j
                  |      t        j
                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}x}}d	}|j                  } |       }||v }|st        j                  d|fd||f      t        j
                  |      dt        j                         v st        j                  |      rt        j
                  |      ndt        j
                  |      t        j
                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}x}}y)
z1HTML must include Mermaid roadmap diagram markup.mermaidr   zD%(py1)s in %(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py3)s.lower
}()
}r   rX   rY   rZ   r   assert %(py9)sr   Ngantt)ri   r   r  rk   rl   rp   rm   rn   ro   rq   rr   )rt   rL   r5   rU   r   r   rv   rx   ru   r   r   s              r4   "test_html_contains_mermaid_diagramz/TestBlackBox.test_html_contains_mermaid_diagram!  s"    ##L1$$V,(DJJ(JL(yL((((yL(((y((((((D(((D(((J(((L(((((((&$**&*,&w,&&&&w,&&&w&&&&&&$&&&$&&&*&&&,&&&&&&&r3   c                   |j                  |      }|dz  }|j                  ||      }|j                  } |       }|sddt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }	t        t        j                  |	            dx}}|j                  d      }
t        |
      }d}||kD  }|st        j                  d	|fd
||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                  |
      rt        j                  |
      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)z3save_html must create a file at the specified path.ztest_report.htmlAassert %(py4)s
{%(py4)s = %(py2)s
{%(py2)s = %(py0)s.exists
}()
}result_pathr   Nzutf-8)encodingi  r   )z/%(py3)s
{%(py3)s = %(py0)s(%(py1)s)
} > %(py6)srT   content)rW   rX   rY   r   assert %(py8)sr[   )ri   	save_htmlexistsrm   rn   rk   ro   rp   rq   rr   	read_textrT   rl   )rt   rL   r5   tmp_pathrU   outputr  r|   r   r~   r  ru   r   rv   r   ry   s                   r4   test_save_html_creates_filez(TestBlackBox.test_save_html_creates_file*  s.    ##L1..))&&9!!#!########{###{###!##########'''97|"d"|d""""|d""""""s"""s""""""7"""7"""|"""d"""""""r3   NrL   r   r5   r   )rL   r   rH   r   rL   r   rD   r   )rL   r   r5   r   rH   r   rL   r   r5   r   r  r   )__name__
__module____qualname__r   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r  r  r0   r3   r4   rN   rN      s   +-+=G+"-=GG-G=GG
-
=G
2-2=G2H-H=GH---=G-/-/=G/!-!=G!-=GL-L=GL.-.>H.B-BAKB	(-	(=G	(Xb	(

-

=G

'-'=G'	#-	#=G	#SW	#r3   rN   c                      e Zd Z	 	 ddZ	 	 ddZd Zd Z	 	 ddZ	 	 ddZ	 	 ddZ		 	 ddZ
d	 Zd
 ZddZd Zd Z	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZ	 	 	 	 ddZy)TestWhiteBoxc           	     &   t        dt        j                  t        j                  dddd      }t        dt        j                  t        j                  dddd      }|j                  |      }|j                  |      }|j                  }|j                  }||k  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)z;High admin hours (>20/week) should reduce operations score.zHighAdmin Cog      9@r#   )r   r   r   r   r%   r"   r$   zLowAdmin Cog      @r   zG%(py2)s
{%(py2)s = %(py0)s.score
} < %(py6)s
{%(py6)s = %(py4)s.score
}
score_high	score_lowrW   rg   rh   r   r  r[   N)r   r	   r1   r   SMALL_score_operationsr   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   company_high_admincompany_low_adminr)  r*  r|   r   r   r   ry   s              r4   2test_operations_score_reduced_for_high_admin_hoursz?TestWhiteBox.test_operations_score_reduced_for_high_admin_hours=  s    ,'__$**$( !$%%&
 +&__$**$' !$%%&
 001CD
//0AB	1)//1/1111/111111z111z111111111)111)111/1111111r3   c           	     &   t        dt        j                  t        j                  dddd      }t        dt        j                  t        j                  dddd      }|j                  |      }|j                  |      }|j                  }|j                  }||k  }|st        j                  d|fd	||f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)z?Many missed calls per day should reduce CX score significantly.zManyMissed Cog       @r#   r&   )r   r   r   r   r"   r$   r%   zFewMissed Co      ?r   r(  
score_many	score_fewr+  r  r[   N)r   r	   r1   r   r2   _score_customer_experiencer   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   company_many_missedcompany_few_missedr3  r4  r|   r   r   r   ry   s              r4   +test_cx_score_reduced_for_many_missed_callsz8TestWhiteBox.test_cx_score_reduced_for_many_missed_callsW  s    -(__$**!$$%%& !
 ,'__$**!$$%%& !
 99:MN
889KL	1)//1/1111/111111z111z111111111)111)111/1111111r3   c           	     b	   t        dddddd      }t        dd	      }|j                  }||z
  }t        |      }d
}||k  }|sKt	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}t        |dz
  d	      }
|j                  }||
z
  }t        |      }d
}||k  }|sKt	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |
      rt	        j                  |
      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}t        |
dz  dz  d      }|j                  }||z
  }t        |      }d
}||k  }|sKt	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      dt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }	t        t	        j                  |	            dx}x}x}x}}y)uM  
        Verify ROI formula: ROI% = ((benefits × util) - costs) / investment × 100
        Using the example from ENTERPRISE_AI_TRANSFORMATION_STRATEGY.md §8.3:
          12 hrs/week × $75/hr × 0.75 util × 52 = $35,100 benefits
          Costs: $5,964/year  |  Investment: $2,500
          Net: $29,136  |  ROI: ~1165%
        Test      (@     R@      ?     L@     @r   hours_saved_per_weekblended_hourly_rate_audutilisation_factorannual_ai_costs_audinitial_investment_audg    #@r#   r2  r   )zj%(py7)s
{%(py7)s = %(py0)s((%(py3)s
{%(py3)s = %(py1)s.annual_hard_benefits_aud
} - %(py4)s))
} < %(py10)sr   roiexpected_benefitsr   r   r   N)zh%(py7)s
{%(py7)s = %(py0)s((%(py3)s
{%(py3)s = %(py1)s.net_annual_benefit_aud
} - %(py4)s))
} < %(py10)sexpected_netr   rJ   )z`%(py7)s
{%(py7)s = %(py0)s((%(py3)s
{%(py3)s = %(py1)s.roi_percentage
} - %(py4)s))
} < %(py10)sexpected_roi)r
   r   annual_hard_benefits_audr   rk   rl   rm   rn   ro   rp   rq   rr   net_annual_benefit_audroi_percentage)rt   rF  rG  ru   r   rx   r   r   rz   r   rH  rI  s               r4   test_roi_formula_base_casez'TestWhiteBox.test_roi_formula_base_caseq  s    !%$(# '#*
 ""91=//J/2CCJsCDJsJDsJJJJDsJJJJJJsJJJsJJJJJJ3JJJ3JJJ/JJJJJJ2CJJJ2CJJJDJJJsJJJJJJJ.8!<--C-<Cs<=CC=CCCC=CCCCCCsCCCsCCCCCC3CCC3CCC-CCCCCCCCCCCC=CCCCCCCCCClW4;Q?%%;%4;s45;;5;;;;5;;;;;;s;;;s;;;;;;3;;;3;;;%;;;;;;;;;;;;5;;;;;;;;;;;r3   c           	        t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d
} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}t         j                  }d} ||      }t        j                  }||k(  }|s&t	        j
                  d|fd||f      dt        j                         v st	        j                  t               rt	        j                  t               ndt	        j                  |      t	        j                  |      t	        j                  |      dt        j                         v st	        j                  t              rt	        j                  t              ndt	        j                  |      dz  }dd|iz  }t        t	        j                  |            d	x}x}x}x}}y	)z)Verify grade assignment at each boundary.P   rQ   )zy%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.grade_from_score
}(%(py4)s)
} == %(py10)s
{%(py10)s = %(py8)s.EXCEPTIONAL
}r   r   )rW   rg   rh   r   r[   r]   r   r   NgS@)zt%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.grade_from_score
}(%(py4)s)
} == %(py10)s
{%(py10)s = %(py8)s.STRONG
}A   g9P@)zx%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.grade_from_score
}(%(py4)s)
} == %(py10)s
{%(py10)s = %(py8)s.DEVELOPING
}r   g33333H@)zy%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.grade_from_score
}(%(py4)s)
} == %(py10)s
{%(py10)s = %(py8)s.EARLY_STAGE
}   gfffff=@)zv%(py6)s
{%(py6)s = %(py2)s
{%(py2)s = %(py0)s.grade_from_score
}(%(py4)s)
} == %(py10)s
{%(py10)s = %(py8)s.CRITICAL
}r   )r   grade_from_scorer   EXCEPTIONALrk   rl   rm   rn   ro   rp   rq   rr   STRONG
DEVELOPINGEARLY_STAGECRITICAL)rt   r|   r   r   r   rw   rz   r   s           r4    test_grade_from_score_boundariesz-TestWhiteBox.test_grade_from_score_boundaries  s   ..LrL.r2Lj6L6LL26LLLLL26LLLLLLL~LLL~LLL.LLLrLLL2LLLLLLjLLLjLLL6LLLLLLLL..ItI.t4I
8I8II48IIIII48IIIIIII~III~III.IIItIII4IIIIII
III
III8IIIIIIII..GrG.r2Gj6G6GG26GGGGG26GGGGGGG~GGG~GGG.GGGrGGG2GGGGGGjGGGjGGG6GGGGGGGG..MtM.t4M
8M8MM48MMMMM48MMMMMMM~MMM~MMM.MMMtMMM4MMMMMM
MMM
MMM8MMMMMMMM..KrK.r2Kj6K6KK26KKKKK26KKKKKKK~KKK~KKK.KKKrKKK2KKKKKKjKKKjKKK6KKKKKKKK..NtN.t4N
8N8NN48NNNNN48NNNNNNN~NNN~NNN.NNNtNNN4NNNNNN
NNN
NNN8NNNNNNNN..LrL.r2Lj6L6LL26LLLLL26LLLLLLL~LLL~LLL.LLLrLLL2LLLLLLjLLLjLLL6LLLLLLLL..KtK.t4K
8K8KK48KKKKK48KKKKKKK~KKK~KKK.KKKtKKK4KKKKKK
KKK
KKK8KKKKKKKK..HqH.q1HZ5H5HH15HHHHH15HHHHHHH~HHH~HHH.HHHqHHH1HHHHHHZHHHZHHH5HHHHHHHHHr3   c           	     &   t        dt        j                  t        j                  dddd      }t        dt        j                  t        j                  dddd      }|j                  |      }|j                  |      }|j                  }|j                  }||k  }|st        j                  d|fd||f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dz  }	dd|	iz  }
t        t        j                  |
            dx}x}}y)z7Absence of CRM should reduce data score vs CRM present.NoCRMNr&   )r   r   r   r    r"   r$   r%   WithCRMr;   r   r(  score_no	score_yesr+  r  r[   )r   r	   r1   r   r2   _score_datar   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   company_no_crmcompany_with_crmr\  r]  r|   r   r   r   ry   s              r4   test_no_crm_reduces_data_scorez+TestWhiteBox.test_no_crm_reduces_data_score  s    ( __$**$%%& !
 *"__$**!$%%& !
 ((8))*:;	~~/	/~////~//////x///x///~//////	///	//////////r3   c                   t        dt        j                  t        j                  dddd      }t        dt        j                  t        j                  dddd      }|j
                  } ||      }|j                  }|j
                  } ||      }|j                  }	||	kD  }
|
st        j                  d|
fd	||	f      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}
x}x}}	y)zHCompany with website should score higher on technology than one without.WithSitezhttps://withsite.com.aur#   r&   )r   r   r   r   r"   r$   r%   NoSiteNr   )z%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s._score_technology
}(%(py3)s)
}.score
} > %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py11)s
{%(py11)s = %(py9)s._score_technology
}(%(py12)s)
}.score
}rL   	with_siteno_site
rW   rg   rY   rZ   r   r   py11r   py14py16assert %(py18)spy18)r   r	   r1   r   r2   _score_technologyr   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   re  rf  r|   rv   rx   @py_assert10@py_assert13@py_assert15r   @py_format17@py_format19s                r4   )test_technology_score_improved_by_websitez6TestWhiteBox.test_technology_score_improved_by_website  s    ##__$**1$%%& !
	 !!__$**$%%& !
 **h*95h5;;hi>Y>Yh>YZa>bh>b>h>hh;>hhhhh;>hhhhhhhyhhhyhhh*hhhhhh9hhh9hhh5hhh;hhhhhhihhhihhh>YhhhhhhZahhhZahhh>bhhh>hhhhhhhhhr3   c                   t        dt        j                  t        j                  ddd      }t        dt        j                  t        j                  ddd      }|j
                  } ||      }|j                  }|j
                  } ||      }|j                  }	||	kD  }
|
st        j                  d|
fd||	f      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      d
t        j                         v st        j                  |      rt        j                  |      nd
t        j                  |      t        j                  |      d	t        j                         v st        j                  |      rt        j                  |      nd	t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}
x}x}}	y)z;High leadership AI awareness should raise the people score.	HighAware   r&   )r   r   r   r"   r%   r$   LowAwarerJ   r   )z%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s._score_people
}(%(py3)s)
}.score
} > %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py11)s
{%(py11)s = %(py9)s._score_people
}(%(py12)s)
}.score
}rL   
high_aware	low_awarerg  rk  rl  N)r   r	   rB   r   r,  _score_peopler   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   rx  ry  r|   rv   rx   rn  ro  rp  r   rq  rr  s                r4   .test_people_score_improves_with_high_awarenessz;TestWhiteBox.test_people_score_improves_with_high_awareness  sf    $$(($**$% !%&

 ##(($**$% !%&
	 ##	
#J/	
/55	
%%	
%i0	
066	
567	
 	
 	
56	
 	
 
6	
 	
  	
 	
 
	 	
 	
 
	 $	
 	
 
6	
 	
  %/	
 	
 
	 %/	
 	
 
	 0	
 	
 
	 6	
 	
 
6	
 	
  	
 	
 
	 	
 	
 
	 &	
 	
 
6	
 	
  '0	
 	
 
	 '0	
 	
 
	 1	
 	
 
	 7	
 	
 	
 	
 	
 	
 	
 	
r3   c                   t        dt        j                  t        j                  dddgddd      }t        dt        j                  t        j                  d	d
dd
      }|j
                  } ||      }|j                  }|j
                  } ||      }|j                  }	||	kD  }
|
st        j                  d|
fd||	f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}x}x}
x}x}}	y)z?Company with AI tools deployed should score higher on strategy.WithAITr=   r>   r   r&   )r   r   r   r!   r<   r"   r$   r%   NoAIFr#   )r   r   r   r!   r"   r$   r%   r   )z%(py7)s
{%(py7)s = %(py5)s
{%(py5)s = %(py2)s
{%(py2)s = %(py0)s._score_strategy
}(%(py3)s)
}.score
} > %(py16)s
{%(py16)s = %(py14)s
{%(py14)s = %(py11)s
{%(py11)s = %(py9)s._score_strategy
}(%(py12)s)
}.score
}rL   with_aino_airg  rk  rl  N)r   r	   rB   r   r,  _score_strategyr   rk   rl   rm   rn   ro   rp   rq   rr   )rt   rL   r  r  r|   rv   rx   rn  ro  rp  r   rq  rr  s                r4   +test_strategy_score_higher_with_ai_deployedz8TestWhiteBox.test_strategy_score_higher_with_ai_deployed  s    !!(($**"&&	2$%%& !	
 (($**"'$%%& !
 ((`(1`177`):S:S`:STY:Z`:Z:`:``7:`````7:```````y```y```(````````````1```7``````)```)```:S``````TY```TY```:Z```:`````````r3   c           
        t        dt        j                  t        j                  ddddd      }d}|j
                  }t        j                  }d} |||	      }||k(  }|s_t        j                  d
|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}x}x}}y)zEDanny Harris Math: missed_calls * 22 days * 30% conv * avg_job_value.r:  rv  i  r#   r&   )r   r   r   r   r   r"   r$   r%   g     @{Gz?)relrQ   )z%(py2)s
{%(py2)s = %(py0)s.estimated_monthly_missed_revenue
} == %(py11)s
{%(py11)s = %(py6)s
{%(py6)s = %(py4)s.approx
}(%(py7)s, rel=%(py9)s)
}companypytestre   )rW   rg   rh   r   r   r   rh  zassert %(py13)spy13N)r   r	   r1   r   r2    estimated_monthly_missed_revenuer  approxrk   rl   rm   rn   ro   rp   rq   rr   )
rt   r  re   r|   r   r   rn  r   @py_format12@py_format14s
             r4   1test_estimated_monthly_missed_revenue_calculationz>TestWhiteBox.test_estimated_monthly_missed_revenue_calculation  s    __$**!""'$%%& !	
 )77\6==\W[\=W[;\\7;\\\\\7;\\\\\\\w\\\w\\\7\\\\\\6\\\6\\\=\\\\\\\\\\\\W[\\\;\\\\\\\\\r3   c                   t        dddddd      }|j                  }d}||u}|st        j                  d	|fd
||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d}||kD  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}|j                  }d}||k  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}x}}y)z:Payback months = initial investment / monthly net benefit.r:  g      $@r<  r=  r>  r?  r@  Nr   )z:%(py2)s
{%(py2)s = %(py0)s.payback_months
} is not %(py5)srF  r   r   r   r   r   )z5%(py2)s
{%(py2)s = %(py0)s.payback_months
} > %(py5)sr;  r   )z5%(py2)s
{%(py2)s = %(py0)s.payback_months
} < %(py5)s)
r
   payback_monthsrk   rl   rm   rn   ro   rp   rq   rr   )rt   rF  r|   rv   r   r   r   s          r4   "test_roi_payback_months_calculatedz/TestWhiteBox.test_roi_payback_months_calculated	  sv   !%$(# '#*
 !!--!----!------s---s---!----------!!%A%!A%%%%!A%%%%%%s%%%s%%%!%%%A%%%%%%%!!(D(!D((((!D((((((s(((s(((!(((D(((((((r3   c                   |j                  |      }t        d |j                  D              }d}||z
  }t        |      }d}||k  }	|	s$t	        j
                  d|	fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      t	        j                  |      dz  }
t	        j                  d	| d
      dz   d|
iz  }t        t	        j                  |            dx}x}x}x}	}y)z.All dimension weights must sum to exactly 6.0.c              3  4   K   | ]  }|j                     y wr   r   r   s     r4   r   zATestWhiteBox.test_dimension_weights_sum_to_six.<locals>.<genexpr>  s     >AHH>r   g      @r  r   )z;%(py6)s
{%(py6)s = %(py0)s((%(py1)s - %(py3)s))
} < %(py9)sr   total)rW   rX   rY   r   r   zWeights sum to z, expected 6.0z
>assert %(py11)srh  N)ri   r   rj   r   rk   rl   rm   rn   ro   rp   r   rq   rr   )rt   rL   r5   rU   r  ru   rv   r   r   rw   r   r  s               r4   !test_dimension_weights_sum_to_sixz.TestWhiteBox.test_dimension_weights_sum_to_six  s    ##L1>f&=&=>>O53;Os;O$O$&OOO$OOOOOOsOOOsOOOOOO5OOO5OOO3OOOOOO$OOO/%(OOOOOOOOr3   c                
   t        dt        j                  t        j                  d      }|j                         }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}}y)zEAuditInput.to_company_profile should normalise URLs without protocol.r:  zexample.com.au)r   r   r   r   zhttps://example.com.aurQ   )z3%(py2)s
{%(py2)s = %(py0)s.website_url
} == %(py5)sprofiler   r   r   N)r   r	   rF   r   rG   to_company_profiler   rk   rl   rm   rn   ro   rp   rq   rr   rt   inpr  r|   rv   r   r   r   s           r4   test_url_normalisationz#TestWhiteBox.test_url_normalisation  s    ^^$))(	
 ((*"">&>>"&>>>>>"&>>>>>>>w>>>w>>>">>>&>>>>>>>>r3   c                
   t        dt        j                  t        j                  d      }|j                         }|j                  }d}||k(  }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }d	d
|iz  }t        t        j                  |            dx}x}}y)z!State field should be uppercased.r:  nsw)r   r   r   r   NSWrQ   )z-%(py2)s
{%(py2)s = %(py0)s.state
} == %(py5)sr  r   r   r   N)r   r	   rF   r   rG   r  r   rk   rl   rm   rn   ro   rp   rq   rr   r  s           r4   test_state_uppercasedz"TestWhiteBox.test_state_uppercased)  s    ^^$))	
 ((*}}%%}%%%%}%%%%%%w%%%w%%%}%%%%%%%%%%r3   c                   |j                  |      }|j                  xs dfddD        }t        |      }|st        j                  d       dz   dt        j                         v st        j                  t              rt        j                  t              ndt        j                  |      t        j                  |      dz  }t        t        j                  |            dx}}y)	zMSolo/micro company with many missed calls should get Inbound Pro or Business.r   c              3  &   K   | ]  }|v  
 y wr   r0   )r   xtiers     r4   r   z?TestWhiteBox.test_recommend_tier_solo_low_cx.<locals>.<genexpr>:  s      
AI
s   )zInbound ProBusinessr   zUnexpected tier: z.
>assert %(py4)s
{%(py4)s = %(py0)s(%(py2)s)
}r
  r   N)ri   r   r
  rk   r   rm   rn   ro   rp   rq   rr   )rt   rL   r5   rU   r|   r   r~   r  s          @r4   test_recommend_tier_solo_low_cxz,TestWhiteBox.test_recommend_tier_solo_low_cx4  s     ##L1117R
H
 	&s 
 
 	& 
 	&%%tf%	& 	&%v	& 	&%%  	& 	&%I  	& 	&%I
 	& 	&%I
 	& 	& 	&%%	& 	&r3   c           	        |j                  |      }|j                  D ]Z  }|j                  }t        |      }d}||k\  }|s.t	        j
                  d|fd||f      dt        j                         v st	        j                  t              rt	        j                  t              nddt        j                         v st	        j                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      t	        j                  |      dz  }	t	        j                  d|j                   d      d	z   d
|	iz  }
t        t	        j                  |
            dx}x}x}}] y)z6Every dimension should produce at least one quick win.rJ   r   )zP%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.quick_wins
})
} >= %(py8)srT   r   rV   r   z' has no quick winsr   r]   N)ri   rj   
quick_winsrT   rk   rl   rm   rn   ro   rp   r   rs   rq   rr   )rt   rL   r5   rU   r   ru   rv   rw   rx   ry   rz   s              r4   test_all_quick_wins_non_emptyz*TestWhiteBox.test_all_quick_wins_non_empty>  sS    ##L1** 	C~~ 3~& ! &!+   &!  v     I   v     I   I &  I '  I +,    cmm_,?@     	r3   c                (   |j                  |      }|j                  |      }|j                  }||v }|st        j                  d|fd||f      dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dt        j                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}y)	z HTML must include the report ID.r   )z1%(py2)s
{%(py2)s = %(py0)s.report_id
} in %(py4)srU   r   r   zassert %(py6)sr   N)ri   r   r   rk   rl   rm   rn   ro   rp   rq   rr   )	rt   rL   r5   rU   r   r|   r   r~   r   s	            r4   test_html_contains_report_idz)TestWhiteBox.test_html_contains_report_idH  s     ##L1$$V,'4''''4''''''v'''v'''''''''4'''4'''''''r3   c           	     
   |j                  |      }|j                  D ]  }|j                  }|st        j                  d|j
                         dz   dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      dz  }t        t        j                  |            d}|j                  }t        |      }d}	||	kD  }
|
s
t        j                  d|
fd||	f      d	t        j                         v st        j                  t              rt        j                  t              nd	dt        j                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      t        j                  |	      d
z  }dd|iz  }t        t        j                  |            dx}x}x}
}	 y)z/Every dimension must have a non-empty headline.zEmpty headline for z.
>assert %(py2)s
{%(py2)s = %(py0)s.headline
}r   rf   N   r   )zM%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.headline
})
} > %(py8)srT   rV   r\   r]   )ri   rj   headlinerk   r   rs   rm   rn   ro   rp   rq   rr   rT   rl   )rt   rL   r5   rU   r   r|   r}   ru   rv   rw   rx   ry   rz   s                r4   !test_dimension_headline_non_emptyz.TestWhiteBox.test_dimension_headline_non_emptyP  s)    ##L1** 	*C<<F<FF#6s}}o!FFFFFFF3FFF3FFF<FFFFFF||)3|$)r)$r))))$r))))))3)))3))))))s)))s)))|)))$)))r)))))))	*r3   c                   |j                  |      j                  }t        |      }d}||kD  }|s
t        j                  d|fd||f      dt        j                         v st        j                  t              rt        j                  t              nddt        j                         v st        j                        rt        j                        ndt        j                  |      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            d	x}x}x}}fd
dD        }	t        |	      }
|
sddt        j                         v st        j                  t              rt        j                  t              ndt        j                  |	      t        j                  |
      dz  }t        t        j                  |            d	x}	}
y	)z>Technology industry should return technology-specific context.r   r   )z^%(py5)s
{%(py5)s = %(py0)s(%(py3)s
{%(py3)s = %(py1)s.industry_ai_adoption_rate
})
} > %(py8)srT   rU   rV   r\   r]   Nc              3  V   K   | ]   }|j                   j                         v  " y wr   )industry_ai_adoption_rater  r  s     r4   r   zRTestWhiteBox.test_industry_context_populated_for_known_industry.<locals>.<genexpr>`  s-      
 &2288::
r  )	technologsoftware	developercodetalentworkflowr	  r
  r   )ri   r  rT   rk   rl   rm   rn   ro   rp   rq   rr   r
  )rt   rL   rD   ru   rv   rw   rx   ry   rz   r|   r   r~   rU   s               @r4   2test_industry_context_populated_for_known_industryz?TestWhiteBox.test_industry_context_populated_for_known_industryY  sd    ##$45339s349r94r99994r999999s999s9999996999699939994999r9999999
Z
 	
s 
 
 	
 
 	
 
6	
 	
   	
 	
 
	  	
 	
 
	
 	
 	
 
	
 	
 	
 	
 	
 	
r3   N)rL   r   r  r   )r"  r#  r$  r0  r8  rM  rX  ra  rs  r{  r  r  r  r  r  r  r  r  r  r  r  r0   r3   r4   r&  r&  ;  s    2-242-24<2
I0-04i-i0
-
2a-a2])P	?	&&-&=G&-=G(-(=G(*-*=G*

-

AK

r3   r&  c                  4    e Zd Z	 	 	 	 	 	 ddZ	 	 	 	 	 	 ddZy)TestIntegrationc                   |j                  |      }|j                  }d}||k\  }|st        j                  d|fd||f      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      dz  }dd|iz  }	t        t        j                  |	            dx}x}}|j                  }
t        |
      }d	}||k(  }|s
t        j                  d
|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}x}}|j                  }
t        |
      }d}||k(  }|s
t        j                  d
|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}x}}|j                  }
t        |
      }d}||k(  }|s
t        j                  d
|fd||f      dt	        j
                         v st        j                  t              rt        j                  t              nddt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |
      t        j                  |      t        j                  |      dz  }dd|iz  }t        t        j                  |            dx}
x}x}}|j                  |      }d}||v }
|
st        j                  d|
fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}
d}||v }
|
st        j                  d|
fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}
d}||v }
|
st        j                  d|
fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}
d}||v }
|
st        j                  d|
fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      nddz  }dd|iz  }t        t        j                  |            dx}}
d}|j                  } |       }||v }
|
st        j                  d|
fd||f      t        j                  |      dt	        j
                         v st        j                  |      rt        j                  |      ndt        j                  |      t        j                  |      d z  }	d!d"|	iz  }t        t        j                  |            dx}x}
x}}t!        j"                  d#|      }g }
||
k(  }|st        j                  d
|fd$||
f      d%t	        j
                         v st        j                  |      rt        j                  |      nd%t        j                  |
      d&z  }dd|iz  }t        t        j                  |            dx}}
|d'z  }|j%                  ||      }|j&                  } |       }|sd(d)t	        j
                         v st        j                  |      rt        j                  |      nd)t        j                  |      t        j                  |      d*z  }t        t        j                  |            dx}}|j)                         j*                  d+z  }d,}
||
kD  }|st        j                  d-|fd.||
f      d/t	        j
                         v st        j                  |      rt        j                  |      nd/t        j                  |
      d&z  }t        j,                  d0|d1d2      d3z   d|iz  }t        t        j                  |            dx}}
y)4u   
        Full pipeline: AuditInput → generate → render_html → save to disk.
        Verify the output file is valid HTML with required sections.
        r   r   r   rU   r   r   r   NrP   rQ   rS   rT   rV   r\   r]   r&   r   r   r   z<!DOCTYPE html>r   r   r   r   r   rZ   r   r   r   r  r  r  r  r   r   r   orphansr   zbunker_fnq_audit.htmlr  resultr   i   
   r   )z%(py0)s > %(py3)sfile_size_kbzReport too small: z.1fKBr   )ri   r   rk   rl   rm   rn   ro   rp   rq   rr   rj   rT   r   r   r   r  r   r   r  r  statst_sizer   )rt   rL   r5   r  rU   r|   rv   r   r   r   ru   rw   rx   ry   rz   r   r   r   r   r  output_pathr  r~   r  s                           r4   test_full_pipeline_tradiez)TestIntegration.test_full_pipeline_tradiem  s    ##L1 005A50A55550A555555v555v5550555A5555555**0s*+0q0+q0000+q000000s000s00000060006000*000+000q0000000))/s)*/a/*a////*a//////s///s//////6///6///)///*///a/////////5s/05A50A55550A555555s555s55555565556555/5550555A5555555 $$V, ( D(((( D((( ((((((D(((D(((((((#|t####|t###|######t###t#######&.&$....&$...&......$...$.......v~vv(DJJ(JL(yL((((yL(((y((((((D(((D(((J(((L((((((( **/6w"}w"ww" !88$$V[9}}}vv}{{},,t3 K|b KKK|bKKKKKK|KKK|KKKbKKK$6|C6H"KKKKKKKr3   c                
   |j                  |      }d}g }|j                  }|}|sd}	|	}||v }
|
sddt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      dz  }|j                  |       |s+ddt	        j                  	      iz  }|j                  |       t	        j                  |d      i z  }t	        j                  d	|
fd
||f      t	        j                  |      |dz  }dd|iz  }t        t	        j                  |            dx}x}
x}x}x}}	|j                  }d}||u}|st	        j                  d|fd||f      dt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|j                  }d}||kD  }|st	        j                  d|fd||f      dt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      t	        j                  |      dz  }dd|iz  }t        t	        j                  |            dx}x}}|j                  |      }d}||v }
|
st	        j                  d	|
fd||f      t	        j                  |      dt        j                         v st	        j
                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}
d}||v }
|
st	        j                  d	|
fd||f      t	        j                  |      dt        j                         v st	        j
                  |      rt	        j                  |      nddz  }dd|iz  }t        t	        j                  |            dx}}
t        j                  }d} |||      }|s t	        j                   d      d z   d!t        j                         v st	        j
                  t              rt	        j                  t              nd!t	        j                  |      t	        j                  |      dt        j                         v st	        j
                  |      rt	        j                  |      ndt	        j                  |      d"z  }t        t	        j                  |            dx}x}}y)#z+Enterprise pipeline with more complex data.r   r   z8%(py7)s
{%(py7)s = %(py5)s.recommended_agileadapt_tier
}rU   )rZ   r   z%(py10)sr]   rJ   r   )z%(py1)s in (%(py13)s))rX   r  zassert %(py15)spy15Nr   )zH%(py2)s
{%(py2)s = %(py0)s.total_annual_opportunity_aud
} is not %(py5)sr   r   r   r   r   )zC%(py2)s
{%(py2)s = %(py0)s.total_annual_opportunity_aud
} > %(py5)sr7   r   r   r   r   rZ   r8   z\$[\d,]+zNo dollar amounts in HTMLzS
>assert %(py7)s
{%(py7)s = %(py2)s
{%(py2)s = %(py0)s.search
}(%(py4)s, %(py5)s)
}r   )rW   rg   rh   rZ   r   )ri   r   rm   rn   rk   ro   rp   append_format_booloprl   rq   rr   total_annual_opportunity_audr   r   searchr   )rt   rL   rD   r  rU   r   rv   rx   r   r   ru   r   rz   r  r  @py_format16r|   r   r   r   s                       r4   test_full_pipeline_enterprisez-TestIntegration.test_full_pipeline_enterprise  s    ##$45I B BI BI BbIbI|IIIIIIIIIIIIII BIIII BIIIbIIIIIIIII|III|IIIIIIII22>$>2$>>>>2$>>>>>>v>>>v>>>2>>>$>>>>>>>226Q62Q66662Q666666v666v6662666Q6666666$$V,"*"d****"d***"******d***d*******!zT!!!!zT!!!z!!!!!!T!!!T!!!!!!!yyHHyd+H+HH-HHHHHHHrHHHrHHHyHHHHHHHHHdHHHdHHH+HHHHHHr3   Nr!  )rL   r   rD   r   r  r   )r"  r#  r$  r  r  r0   r3   r4   r  r  k  sE     L- L=G LSW LDI-IAKIW[Ir3   r  )returnr   )r  r   )'__doc__
__future__r   builtinsrm   _pytest.assertion.rewrite	assertionrewriterk   sysosr   pathlibr   __file__parentPROJECT_ROOTpathinsertr   r  core.audit.schemasr   r   r   r   r	   r
   r   core.audit.report_generatorr   fixturer5   rD   rH   rL   rN   r&  r  r0   r3   r4   <module>r     s  ( #   
 	 	  H~$$++22 3|$ %    =  @  B   8 8i# i#bh
 h
`	1I 1Ir3   