from pathlib import Path
p=Path('/opt/struktur/social-media-radar/mas_relevance.py')
s=p.read_text()
old='def assess_mas_relevance(text: str, *, fields: dict | None = None) -> MASRelevance:\n'
new='''def content_eligibility(signal: dict, *, require_persisted_gate: bool = True) -> tuple[bool, str]:
"""Zentrales, fail-closed MAS-Content-Gate für alle nachgelagerten Consumer.
Content darf nur entstehen, wenn die persistierte Neubewertung ausdrücklich
mas_relevant=1 trägt und topic/short_summary nach derselben Fachlogik relevant
sind. NULL, 0, fehlende Felder und widersprüchliche Altwerte werden blockiert.
"""
if require_persisted_gate and signal.get("mas_relevant") != 1:
return False, "mas_relevant fehlt oder ist nicht 1"
if signal.get("content_blocked") == 1:
return False, signal.get("content_block_reason") or "fachlich/ethisch blockiert"
result = assess_mas_relevance(
" ".join(str(signal.get(k) or "") for k in ("topic", "short_summary")),
fields={"topic": signal.get("topic"), "short_summary": signal.get("short_summary")},
)
if not result.relevant:
return False, result.reason or "Kein fachlicher MAS-Anker"
return True, "MAS-relevant und persistiertes Fachgate=1"
def assess_mas_relevance(text: str, *, fields: dict | None = None) -> MASRelevance:
'''
assert s.count(old)==1
p.write_text(s.replace(old,new))
p=Path('/opt/struktur/social-media-radar/caption_gate.py'); s=p.read_text()
s=s.replace('from mas_relevance import assess_mas_relevance','from mas_relevance import content_eligibility')
old=''' relevance = assess_mas_relevance(" ".join(str(signal.get(k) or "") for k in ("topic", "short_summary", "source_name")), fields=signal)
if not relevance.relevant:
return _block(f"[{sid}] " + ("Nicht für MAS-Content geeignet: sensibles/fachfremdes Ereignis" if relevance.content_blocked else "Kein fachlicher MAS-Bezug"))
'''
new=''' eligible, reason = content_eligibility(signal)
if not eligible:
return _block(f"[{sid}] {reason}")
'''
assert old in s
p.write_text(s.replace(old,new))
p=Path('/opt/struktur/social-media-radar/visual_gate.py'); s=p.read_text()
s=s.replace('from content_safety import find_safety_block','from content_safety import find_safety_block\nfrom mas_relevance import content_eligibility')
old=''' # Relevanz, Score, briefing_winkel und expires_at sind Anzeige-/Entscheidungsdaten.
# Sie dürfen die Vorschau nicht blockieren; der Operator entscheidet über Nutzung.
score = signal.get("radar_score") or 0
'''
new=''' eligible, reason = content_eligibility(signal)
if not eligible:
return _block(f"[{sid}] {reason}")
score = signal.get("radar_score") or 0
'''
assert old in s
p.write_text(s.replace(old,new))
p=Path('/opt/struktur/social-media-agent/publish_ready.py'); s=p.read_text()
old='''from human_feedback import feedback_prompt_text, save_variant_feedback
'''
new='''from human_feedback import feedback_prompt_text, save_variant_feedback
sys.path.insert(0, str(RADAR_DIR))
from mas_relevance import content_eligibility
'''
# RADAR_DIR defined after imports, so import must be after constants instead
new='''from human_feedback import feedback_prompt_text, save_variant_feedback
'''
# only replace _relevant and load query; import locally avoids constant-order issue
oldrel='''def _relevant(signal: dict) -> bool:
category = _text(signal.get("signal_category")).lower()
haystack = " ".join(_text(signal.get(k)) for k in ("topic", "topic_de", "short_summary", "briefing_winkel")).lower()
return category in RELEVANT_CATEGORIES or any(term in haystack for term in RELEVANT_TERMS)
'''
newrel='''def _relevant(signal: dict) -> bool:
sys.path.insert(0, str(RADAR_DIR))
from mas_relevance import content_eligibility
eligible, _ = content_eligibility(signal)
return eligible
'''
assert oldrel in s
s=s.replace(oldrel,newrel)
oldq=''' "SELECT * FROM signals WHERE COALESCE(status,'') NOT IN ('ignoriert','veroeffentlicht') "
"AND COALESCE(publish_status,'') NOT IN ('gepostet','veroeffentlicht','manuell_veroeffentlicht') "
'''
newq=''' "SELECT * FROM signals WHERE COALESCE(status,'') NOT IN ('ignoriert','veroeffentlicht') "
"AND COALESCE(publish_status,'') NOT IN ('gepostet','veroeffentlicht','manuell_veroeffentlicht') "
"AND mas_relevant=1 AND COALESCE(content_blocked,0)=0 "
'''
assert oldq in s
s=s.replace(oldq,newq)
# refresh path must refuse old source rows unless gate passes
old=''' if source:
result.append(transform_signal(dict(source)) or dict(source))
'''
new=''' if source:
candidate = dict(source)
if _relevant(candidate):
result.append(transform_signal(candidate) or candidate)
'''
assert old in s
s=s.replace(old,new)
p.write_text(s)
# harden direct package loader as defense-in-depth
p=Path('/opt/struktur/social-media-agent/posting_package.py'); s=p.read_text()
s=s.replace('from caption_gate import can_generate_caption','from caption_gate import can_generate_caption\nfrom mas_relevance import content_eligibility')
old=''' package_key = loaded_signal_id
cap_gate = can_generate_caption(signal)
'''
new=''' package_key = loaded_signal_id
eligible, eligibility_reason = content_eligibility(signal)
if not eligible:
return _fail(eligibility_reason)
cap_gate = can_generate_caption(signal)
'''
assert old in s
p.write_text(s)