2525 BlueskyCredentials ,
2626 Content ,
2727 Entity ,
28+ EntityAuthoritySnapshot ,
2829 EntityCandidate ,
2930 EntityCandidateStatus ,
3031 EntityMention ,
3940from core .plugins import get_plugin_for_source_config , validate_plugin_config
4041
4142
43+ def _score_to_percent (value ):
44+ """Normalize score-like values for display as percentages."""
45+
46+ if value is None :
47+ return None
48+ numeric_value = float (value )
49+ if - 1.0 <= numeric_value <= 1.0 :
50+ return numeric_value * 100
51+ return numeric_value
52+
53+
54+ def _score_color (value ) -> str :
55+ """Return the admin display color for a score-like value."""
56+
57+ percent_value = _score_to_percent (value )
58+ if percent_value is None :
59+ return "inherit"
60+ if percent_value >= 75 :
61+ return "green"
62+ if percent_value >= 40 :
63+ return "orange"
64+ return "red"
65+
66+
4267class BlueskyCredentialsAdminForm (forms .ModelForm ):
4368 """Admin form that accepts a plaintext Bluesky app credential input."""
4469
@@ -205,26 +230,98 @@ class ProjectConfigAdmin(admin.ModelAdmin):
205230class EntityAdmin (admin .ModelAdmin ):
206231 """Admin configuration for tracked people, vendors, and organizations."""
207232
208- # Replace 'authority_score' with your new method name
209- list_display = ("name" , "project" , "type" , "colored_score" , "created_at" )
233+ list_display = (
234+ "name" ,
235+ "project" ,
236+ "type" ,
237+ "colored_score" ,
238+ "latest_snapshot_summary" ,
239+ "created_at" ,
240+ )
210241 search_fields = ("name" , "project__name" )
211242
212243 @admin .display (description = "Authority Score" , ordering = "authority_score" )
213244 def colored_score (self , obj ):
214245 """Render the authority score with a traffic-light color cue."""
215246
216- # Choose a color based on the value
217- if obj .authority_score >= 80 :
218- color = "green"
219- elif obj .authority_score >= 50 :
220- color = "orange"
221- else :
222- color = "red"
247+ percent_value = _score_to_percent (obj .authority_score )
248+ color = _score_color (obj .authority_score )
223249
224250 return format_html (
225251 '<span style="color: {}; font-weight: bold;">{}</span>' ,
226252 color ,
227- obj .authority_score ,
253+ f"{ percent_value :.1f} %" ,
254+ )
255+
256+ @admin .display (description = "Latest Snapshot" )
257+ def latest_snapshot_summary (self , obj ):
258+ """Show the latest authority component breakdown for an entity."""
259+
260+ latest_snapshot = obj .authority_snapshots .order_by ("-computed_at" ).first ()
261+ if latest_snapshot is None :
262+ return "-"
263+ mention_value = f"{ _score_to_percent (latest_snapshot .mention_component ):.1f} %"
264+ feedback_value = f"{ _score_to_percent (latest_snapshot .feedback_component ):.1f} %"
265+ duplicate_value = (
266+ f"{ _score_to_percent (latest_snapshot .duplicate_component ):.1f} %"
267+ )
268+ decayed_value = f"{ _score_to_percent (latest_snapshot .decayed_prior ):.1f} %"
269+ return format_html (
270+ (
271+ '<span title="Mention {}, Feedback {}, Duplicate {}, Carry {}">'
272+ "M {} | F {} | D {} | Carry {}</span>"
273+ ),
274+ mention_value ,
275+ feedback_value ,
276+ duplicate_value ,
277+ decayed_value ,
278+ mention_value ,
279+ feedback_value ,
280+ duplicate_value ,
281+ decayed_value ,
282+ )
283+
284+
285+ @admin .register (EntityAuthoritySnapshot )
286+ class EntityAuthoritySnapshotAdmin (admin .ModelAdmin ):
287+ """Admin view for persisted authority-score history."""
288+
289+ list_display = (
290+ "entity" ,
291+ "project" ,
292+ "display_final_score" ,
293+ "display_components" ,
294+ "computed_at" ,
295+ )
296+ list_filter = (("project" , admin .RelatedOnlyFieldListFilter ), "computed_at" )
297+ search_fields = ("entity__name" , "project__name" )
298+ autocomplete_fields = ("entity" , "project" )
299+
300+ @admin .display (description = "Final Score" , ordering = "final_score" )
301+ def display_final_score (self , obj ):
302+ """Render the recomputed final authority score as a percentage."""
303+
304+ percent_value = _score_to_percent (obj .final_score )
305+ return format_html (
306+ '<span style="color: {}; font-weight: bold;">{}</span>' ,
307+ _score_color (obj .final_score ),
308+ f"{ percent_value :.1f} %" ,
309+ )
310+
311+ @admin .display (description = "Components" )
312+ def display_components (self , obj ):
313+ """Render the stored authority components in a compact summary."""
314+
315+ mention_value = f"{ _score_to_percent (obj .mention_component ):.1f} %"
316+ feedback_value = f"{ _score_to_percent (obj .feedback_component ):.1f} %"
317+ duplicate_value = f"{ _score_to_percent (obj .duplicate_component ):.1f} %"
318+ decayed_value = f"{ _score_to_percent (obj .decayed_prior ):.1f} %"
319+ return format_html (
320+ "M {} | F {} | D {} | Carry {}" ,
321+ mention_value ,
322+ feedback_value ,
323+ duplicate_value ,
324+ decayed_value ,
228325 )
229326
230327
@@ -387,6 +484,7 @@ class ContentAdmin(admin.ModelAdmin):
387484
388485 list_display = (
389486 "display_relevance" ,
487+ "display_authority_adjusted_score" ,
390488 "duplicate_badge" ,
391489 "duplicate_parent" ,
392490 "is_active" ,
@@ -510,18 +608,31 @@ def view_trace(self, obj):
510608 link_label ,
511609 )
512610
513- @admin .display (description = "Score" )
611+ @admin .display (description = "Base Score" )
514612 def display_relevance (self , obj ):
515613 """Render the relevance score with a coarse color-coded severity band."""
516614
517615 if obj .relevance_score is None :
518616 return "-"
519- color = (
520- "green"
521- if obj .relevance_score > 75
522- else "orange" if obj .relevance_score > 40 else "red"
617+ percent_value = _score_to_percent (obj .relevance_score )
618+ return format_html (
619+ '<b style="color: {}">{}</b>' ,
620+ _score_color (obj .relevance_score ),
621+ f"{ percent_value :.1f} %" ,
622+ )
623+
624+ @admin .display (description = "Adjusted" )
625+ def display_authority_adjusted_score (self , obj ):
626+ """Render the authority-adjusted relevance score when available."""
627+
628+ if obj .authority_adjusted_score is None :
629+ return "-"
630+ percent_value = _score_to_percent (obj .authority_adjusted_score )
631+ return format_html (
632+ '<b style="color: {}">{}</b>' ,
633+ _score_color (obj .authority_adjusted_score ),
634+ f"{ percent_value :.1f} %" ,
523635 )
524- return format_html ('<b style="color: {};">{}%</b>' , color , obj .relevance_score )
525636
526637 @admin .display (description = "Duplicates" , ordering = "duplicate_signal_count" )
527638 def duplicate_badge (self , obj ):
@@ -546,15 +657,40 @@ def changelist_view(self, request, extra_context=None):
546657 """Augment the changelist with content dashboard statistics."""
547658
548659 queryset = self .get_queryset (request )
549- metrics = queryset .aggregate (avg_score = Avg ("relevance_score" ))
660+ metrics = queryset .aggregate (
661+ avg_score = Avg ("relevance_score" ),
662+ avg_adjusted_score = Avg ("authority_adjusted_score" ),
663+ )
550664
551665 extra_context = extra_context or {}
552666 extra_context ["dashboard_stats" ] = [
553667 {
554- "title" : "Avg Relevance" ,
555- "value" : f"{ metrics ['avg_score' ] or 0 :.1f} %" ,
668+ "title" : "Avg Base Score" ,
669+ "value" : (
670+ f"{ _score_to_percent (metrics ['avg_score' ]):.1f} %"
671+ if metrics ["avg_score" ] is not None
672+ else "-"
673+ ),
556674 "icon" : "insights" ,
557- "color" : "success" if (metrics ["avg_score" ] or 0 ) > 70 else "warning" ,
675+ "color" : (
676+ "success"
677+ if _score_color (metrics ["avg_score" ]) == "green"
678+ else "warning"
679+ ),
680+ },
681+ {
682+ "title" : "Avg Adjusted Score" ,
683+ "value" : (
684+ f"{ _score_to_percent (metrics ['avg_adjusted_score' ]):.1f} %"
685+ if metrics ["avg_adjusted_score" ] is not None
686+ else "-"
687+ ),
688+ "icon" : "auto_graph" ,
689+ "color" : (
690+ "success"
691+ if _score_color (metrics ["avg_adjusted_score" ]) == "green"
692+ else "warning"
693+ ),
558694 },
559695 {
560696 "title" : "Total Filtered" ,
0 commit comments