TwinklData/Community_Collections_App
0
1import pandas as pd2 3def shortlist_applications(4 df: pd.DataFrame,5 k: int = None,6 threshold: float = None,7 weight_necessity: float = 0.55,8 weight_length: float = 0.1,9 weight_usage: float = 0.3510) -> pd.DataFrame:11 """12 Automatically shortlist grant applications by combining necessity index,13 application length (favoring longer submissions), and the specificity of the14 requested usage list.15 16 Args:17 df: Processed DataFrame including columns 'necessity_index', 'word_count', and 'Usage'.18 k: Number of top applications to select. Mutually exclusive with threshold.19 threshold: Score threshold above which to select applications. Mutually exclusive with k.20 weight_necessity: Weight for necessity_index (0 to 1).21 weight_length: Weight for length score (0 to 1).22 weight_usage: Weight for usage specificity (0 to 1).23 24 Returns:25 DataFrame of shortlisted applications sorted by descending combined score.26 """27 # Ensure exactly one of k or threshold is provided28 if (k is None and threshold is None) or (k is not None and threshold is not None):29 raise ValueError("Provide exactly one of k or threshold")30 31 # Normalize necessity_index (assumed already between 0 and 1)32 necessity = df['necessity_index']33 34 # Compute length score: longer applications score higher (more context is valued)35 word_counts = df['word_count']36 min_wc, max_wc = word_counts.min(), word_counts.max()37 if max_wc != min_wc:38 length_score = (word_counts - min_wc) / (max_wc - min_wc)39 else:40 length_score = pd.Series([0.5] * len(df), index=df.index)41 42 # Compute usage score based on *how many* concrete usage items were extracted43 # (previously this was a simple binary flag). Longer lists are taken as a44 # signal of greater specificity → higher score. We first count the number45 # of non‑empty items, then min‑max normalise the counts so the resulting46 # score is between 0 and 1 (mirroring the approach used for47 # `length_score`).48 49 def count_valid_usage(items):50 """Return the number of meaningful usage entries in *items*.51 52 The Usage column is expected to contain a list of strings (output of53 `extract_usage.extract_usage`). We treat empty strings and the literal54 "None" (case‑insensitive) as non‑entries.55 """56 if not isinstance(items, (list, tuple, set)):57 return 058 return sum(59 160 for item in items61 if isinstance(item, str) and item.strip() and item.strip().lower() != "none"62 )63 64 usage_counts = df["usage"].apply(count_valid_usage)65 66 min_uc, max_uc = usage_counts.min(), usage_counts.max()67 if max_uc != min_uc:68 usage_score = (usage_counts - min_uc) / (max_uc - min_uc)69 else:70 # If all rows have identical counts (e.g. all zero), assign a neutral 0.571 usage_score = pd.Series([0.5] * len(df), index=df.index)72 73 # Combine scores with normalized weights74 total_weight = weight_necessity + weight_length + weight_usage75 weights = {76 'necessity': weight_necessity / total_weight,77 'length': weight_length / total_weight,78 'usage': weight_usage / total_weight,79 }80 combined = (81 weights['necessity'] * necessity +82 weights['length'] * length_score +83 weights['usage'] * usage_score84 )85 df = df.copy()86 df['shortlist_score'] = combined87 88 # Select applications based on k or threshold89 df_sorted = df.sort_values('shortlist_score', ascending=False)90 if k is not None:91 result = df_sorted.head(k)92 else:93 result = df_sorted[df_sorted['shortlist_score'] >= threshold]94 95 return result96 