| 59 |
59 |
Réponds : {"value": -2|-1|0|1|2|null, "confidence": 0-1, "proposal_ids": [ids utilisés], "rationale": "1-2 phrases citant les propositions"}""" |
| 60 |
60 |
|
| 61 |
61 |
|
| 62 |
|
−def _proposals_for(s: Session, party_id: str, topic: str, limit: int = 40) -> list[PolicyProposal]: |
|
62 |
+_STOP = {"les", "des", "une", "pour", "dans", "avec", "plus", "être", "devrait", "devraient", "leur", "leurs", "cette", "sont", "que", "qui", "par", "sur", "aux", "est", "pas", "même", "sans", "tout", "tous", "afin", "cela", "quitte", "aussi", "entre"} |
|
63 |
+ |
|
64 |
+ |
|
65 |
+def _keywords(text: str) -> set[str]: |
|
66 |
+ import re |
|
67 |
+ return {w for w in re.findall(r"[a-zàâçéèêëîïôûùüÿœ\-]{4,}", text.lower()) if w not in _STOP} |
|
68 |
+ |
|
69 |
+ |
|
70 |
+def _proposals_for(s: Session, party_id: str, topic: str, limit: int = 40, question_text: str | None = None) -> list[PolicyProposal]: |
| 63 |
71 |
topics = RELATED.get(topic, [topic]) |
| 64 |
72 |
rows = s.scalars(select(PolicyProposal).where(PolicyProposal.party_id == party_id, PolicyProposal.topic.in_(topics), |
| 65 |
73 |
PolicyProposal.status == "accepted").order_by(PolicyProposal.confidence.desc())).all() |
| 66 |
74 |
# sujet exact d'abord |
| 67 |
75 |
rows.sort(key=lambda p: (0 if p.topic == topic else 1, -p.confidence)) |
|
76 |
+ if len(rows) < 8 and question_text: |
|
77 |
+ # peu de propositions classées dans les sujets voisins : on élargit par recouvrement lexical avec l'énoncé |
|
78 |
+ kw = _keywords(question_text) |
|
79 |
+ others = s.scalars(select(PolicyProposal).where(PolicyProposal.party_id == party_id, PolicyProposal.status == "accepted", |
|
80 |
+ PolicyProposal.topic.not_in(topics))).all() |
|
81 |
+ scored = [] |
|
82 |
+ for p in others: |
|
83 |
+ words = _keywords(f"{p.proposal} {p.position or ''} {p.subtopic or ''} {p.quote}") |
|
84 |
+ score = len(kw & words) |
|
85 |
+ if score >= 2: |
|
86 |
+ scored.append((score, p)) |
|
87 |
+ scored.sort(key=lambda z: -z[0]) |
|
88 |
+ rows += [p for _, p in scored[: max(0, 25 - len(rows))]] |
| 68 |
89 |
return rows[:limit] |
| 69 |
90 |
|
| 70 |
91 |
|
| 79 |
100 |
if pos and only_missing and pos.status in ("extracted", "manual"): |
| 80 |
101 |
skipped += 1 |
| 81 |
102 |
continue |
| 82 |
|
− props = _proposals_for(s, pid, q.topic) |
|
103 |
+ props = _proposals_for(s, pid, q.topic, question_text=q.text) |
| 83 |
104 |
if not pos: |
| 84 |
105 |
pos = PartyPosition(question_id=q.id, party_id=pid) |
| 85 |
106 |
s.add(pos) |