| 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"}""" |
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 | topics = RELATED.get(topic, [topic]) |
71 | topics = RELATED.get(topic, [topic]) |
| 64 | rows = s.scalars(select(PolicyProposal).where(PolicyProposal.party_id == party_id, PolicyProposal.topic.in_(topics), |
72 | rows = s.scalars(select(PolicyProposal).where(PolicyProposal.party_id == party_id, PolicyProposal.topic.in_(topics), |
| 65 | PolicyProposal.status == "accepted").order_by(PolicyProposal.confidence.desc())).all() |
73 | PolicyProposal.status == "accepted").order_by(PolicyProposal.confidence.desc())).all() |
| 66 | # sujet exact d'abord |
74 | # sujet exact d'abord |
| 67 | rows.sort(key=lambda p: (0 if p.topic == topic else 1, -p.confidence)) |
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 | return rows[:limit] |
89 | return rows[:limit] |
| 69 | |
90 | |
| 70 | |
91 | |
| 79 | if pos and only_missing and pos.status in ("extracted", "manual"): |
100 | if pos and only_missing and pos.status in ("extracted", "manual"): |
| 80 | skipped += 1 |
101 | skipped += 1 |
| 81 | continue |
102 | continue |
| 82 | − props = _proposals_for(s, pid, q.topic) |
103 | + props = _proposals_for(s, pid, q.topic, question_text=q.text) |
| 83 | if not pos: |
104 | if not pos: |
| 84 | pos = PartyPosition(question_id=q.id, party_id=pid) |
105 | pos = PartyPosition(question_id=q.id, party_id=pid) |
| 85 | s.add(pos) |
106 | s.add(pos) |