4040 },
4141 "Write a complete README." : {
4242 "penalty_codes" : {"README_MISSING" , "README_TOO_SHORT" , "USAGE_MISSING" },
43- "fallback_points" : 0 .0 ,
43+ "fallback_points" : 6 .0 ,
4444 "effort_units" : 2.0 ,
4545 "category" : "documentation" ,
4646 },
47- "Add and apply a proper .gitignore." : {
48- "penalty_codes" : {"PYCACHE_COMMITTED" , "BUILD_ARTIFACTS_COMMITTED" },
49- "fallback_points" : 0.0 ,
50- "effort_units" : 1.0 ,
51- "category" : "hygiene" ,
52- },
53- "Delete committed __pycache__ directories." : {
54- "penalty_codes" : {"PYCACHE_COMMITTED" },
55- "fallback_points" : 0.0 ,
47+ "Add a concise GitHub description." : {
48+ "penalty_codes" : {"DESCRIPTION_MISSING" },
49+ "fallback_points" : 1.0 ,
5650 "effort_units" : 0.5 ,
57- "category" : "hygiene " ,
51+ "category" : "documentation " ,
5852 },
59- "Remove generated build/cache artifacts ." : {
60- "penalty_codes" : {"BUILD_ARTIFACTS_COMMITTED " },
61- "fallback_points" : 0 .0 ,
62- "effort_units" : 0.75 ,
63- "category" : "hygiene " ,
53+ "Expose a demo or homepage link when relevant ." : {
54+ "penalty_codes" : {"HOMEPAGE_MISSING" , "DEMO_MISSING " },
55+ "fallback_points" : 2 .0 ,
56+ "effort_units" : 0.5 ,
57+ "category" : "portfolio_relevance " ,
6458 },
65- "Review large committed files and keep only necessary assets ." : {
66- "penalty_codes" : {"OVERSIZED_FILES " },
67- "fallback_points" : 0 .0 ,
59+ "Clarify the portfolio positioning and business value ." : {
60+ "penalty_codes" : {"PORTFOLIO_VALUE_UNCLEAR " },
61+ "fallback_points" : 3 .0 ,
6862 "effort_units" : 1.5 ,
69- "category" : "hygiene " ,
63+ "category" : "portfolio_relevance " ,
7064 },
71- "Expose a demo or homepage link when relevant ." : {
72- "penalty_codes" : set () ,
73- "fallback_points" : 1.5 ,
74- "effort_units" : 1 .0 ,
75- "category" : "portfolio_signal " ,
65+ "Strengthen the technical depth of the implementation ." : {
66+ "penalty_codes" : { "TECHNICAL_DEPTH_LIMITED" } ,
67+ "fallback_points" : 4.0 ,
68+ "effort_units" : 3 .0 ,
69+ "category" : "technical_depth " ,
7670 },
77- "Add a concise GitHub description ." : {
78- "penalty_codes" : set () ,
79- "fallback_points" : 1 .0 ,
80- "effort_units" : 0.5 ,
81- "category" : "portfolio_signal " ,
71+ "Improve maintainability and code cleanliness ." : {
72+ "penalty_codes" : { "MAINTAINABILITY_WEAK" } ,
73+ "fallback_points" : 3 .0 ,
74+ "effort_units" : 2.0 ,
75+ "category" : "maintainability " ,
8276 },
8377}
8478
@@ -103,24 +97,42 @@ def estimate_action_impact(
10397 action_text ,
10498 {
10599 "penalty_codes" : set (),
106- "fallback_points" : 0.75 ,
107- "effort_units" : 1.5 ,
100+ "fallback_points" : 2.0 ,
101+ "effort_units" : 2.0 ,
108102 "category" : "general" ,
109103 },
110104 )
111105
112- penalty_points = repo_penalty_points (score_entry )
113- matched_codes = sorted (code for code in rule ["penalty_codes" ] if penalty_points .get (code , 0.0 ) > 0 )
114- estimated_points = sum (penalty_points .get (code , 0.0 ) for code in matched_codes )
106+ penalty_index = repo_penalty_points (score_entry )
115107
116- if estimated_points <= 0 and action_text == "Expose a demo or homepage link when relevant." :
117- homepage = str (repo_row .get ("homepage" ) or "" ).strip ()
118- if not homepage :
108+ review_matched_codes = [
109+ str (code ).strip ()
110+ for code in (review .get ("matched_penalty_codes" , []) or [])
111+ if str (code ).strip ()
112+ ]
113+
114+ if review_matched_codes :
115+ matched_codes = [code for code in review_matched_codes if code in rule ["penalty_codes" ]]
116+ else :
117+ matched_codes = [code for code in penalty_index if code in rule ["penalty_codes" ]]
118+
119+ estimated_points = round (sum (penalty_index .get (code , 0.0 ) for code in matched_codes ), 2 )
120+
121+ if estimated_points <= 0 and action_text == "Write a complete README." :
122+ readme_length = int (repo_row .get ("readme_length" , 0 ) or 0 )
123+ if readme_length == 0 :
119124 estimated_points = float (rule ["fallback_points" ])
125+
120126 elif estimated_points <= 0 and action_text == "Add a concise GitHub description." :
121127 description = str (repo_row .get ("description" ) or "" ).strip ()
122128 if not description :
123129 estimated_points = float (rule ["fallback_points" ])
130+
131+ elif estimated_points <= 0 and action_text == "Expose a demo or homepage link when relevant." :
132+ homepage = str (repo_row .get ("homepage" ) or "" ).strip ()
133+ if not homepage :
134+ estimated_points = float (rule ["fallback_points" ])
135+
124136 elif estimated_points <= 0 and action_text not in ACTION_IMPACT_RULES :
125137 estimated_points = float (rule ["fallback_points" ])
126138
@@ -187,7 +199,8 @@ def derive_repo_optimizer_fields(
187199
188200
189201def build_next_actions (
190- df : pd .DataFrame , review_index : dict [str , dict [str , Any ]]
202+ df : pd .DataFrame ,
203+ review_index : dict [str , dict [str , Any ]],
191204) -> list [dict [str , Any ]]:
192205 counter : Counter [str ] = Counter ()
193206 action_rows : dict [str , list [dict [str , Any ]]] = {}
@@ -210,12 +223,14 @@ def build_next_actions(
210223 "estimated_score_lift" : float (opportunity .get ("estimated_score_lift" , 0.0 )),
211224 "effort_units" : float (opportunity .get ("effort_units" , 0.0 )),
212225 "roi" : float (opportunity .get ("roi" , 0.0 )),
213- "matched_penalty_codes" : list (opportunity .get ("matched_penalty_codes" , [])),
226+ "matched_penalty_codes" : list (
227+ opportunity .get ("matched_penalty_codes" , [])
228+ ),
214229 }
215230 )
216231
217232 ranked_actions : list [dict [str , Any ]] = []
218- for action_text , _count in counter . most_common ( 12 ) :
233+ for action_text in counter :
219234 repos = sorted (
220235 action_rows [action_text ],
221236 key = lambda item : (
@@ -241,7 +256,16 @@ def build_next_actions(
241256 "repos" : repos [:6 ],
242257 }
243258 )
244- return ranked_actions
259+
260+ return sorted (
261+ ranked_actions ,
262+ key = lambda item : (
263+ - item ["roi" ],
264+ - item ["estimated_total_score_lift" ],
265+ - item ["affected_repo_count" ],
266+ item ["action" ],
267+ ),
268+ )[:12 ]
245269
246270
247271def simulate_portfolio (
@@ -263,12 +287,15 @@ def simulate_portfolio(
263287 def _project_for_scope (scope_df : pd .DataFrame , top_n : int ) -> float :
264288 if scope_df .empty :
265289 return 0.0
290+
266291 repo_lifts : dict [str , float ] = {row ["repo_name" ]: 0.0 for _ , row in scope_df .iterrows ()}
292+
267293 for action in prioritized_actions [:top_n ]:
268294 for repo in action ["repos" ]:
269295 repo_name = str (repo .get ("repo_name" ))
270296 if repo_name not in repo_lifts :
271297 continue
298+
272299 current_score = float (
273300 scope_df .loc [scope_df ["repo_name" ] == repo_name , "global_score" ].iloc [0 ]
274301 )
@@ -278,6 +305,7 @@ def _project_for_scope(scope_df: pd.DataFrame, top_n: int) -> float:
278305 remaining = max (0.0 , ceiling - current_score - repo_lifts [repo_name ])
279306 lift = min (float (repo .get ("estimated_score_lift" , 0.0 )), remaining )
280307 repo_lifts [repo_name ] += lift
308+
281309 projected_scores = [
282310 min (100.0 , float (row ["global_score" ]) + repo_lifts .get (str (row ["repo_name" ]), 0.0 ))
283311 for _ , row in scope_df .iterrows ()
@@ -298,4 +326,4 @@ def _project_for_scope(scope_df: pd.DataFrame, top_n: int) -> float:
298326 "selected_scope_current" : selected_scope_quality ,
299327 "selected_scope_after_top_3" : selected_scope_after_top_three ,
300328 "top_actions" : prioritized_actions [:3 ],
301- }
329+ }
0 commit comments