Skip to content

Commit d0c4ef6

Browse files
committed
added
1 parent 4126e7b commit d0c4ef6

7 files changed

Lines changed: 68 additions & 29 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,11 @@ prosody-data/
3333
*.log
3434
logs/
3535

36+
# Coverage / test artifacts
37+
.coverage
38+
.coverage.*
39+
htmlcov/
40+
.pytest_cache/
41+
3642
# Jupyter (se usado)
3743
.ipynb_checkpoints/

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
![SPADE](https://img.shields.io/badge/SPADE-3.3+-green)
55
![FIPA-ACL](https://img.shields.io/badge/Protocol-FIPA--ACL-orange)
66
![XMPP](https://img.shields.io/badge/Transport-XMPP-blueviolet)
7-
![Tests](https://img.shields.io/badge/tests-193%20passed-brightgreen)
7+
![Tests](https://img.shields.io/badge/tests-203%20passed-brightgreen)
88
![Coverage](https://img.shields.io/badge/coverage-64%25-green)
99
![Lint](https://img.shields.io/badge/ruff-clean-brightgreen)
1010
![License](https://img.shields.io/badge/License-Academic-lightgrey)
@@ -169,7 +169,7 @@ SmartStudyRoom/
169169
│ ├── massive_negotiation.py # Contract Net massivo (throughput)
170170
│ └── multi_campus_cfp.py # Escalação multicampus via GlobalCoord.
171171
172-
├── tests/ # 193 testes pytest (unit + integração)
172+
├── tests/ # 203 testes pytest (unit + integração)
173173
│ ├── test_ontology.py, test_fipa.py, test_config.py, …
174174
│ └── test_contract_net.py implícito em test_coordinator_async.py
175175
@@ -278,7 +278,7 @@ Editar `config.py` ou definir variáveis de ambiente:
278278

279279
## 🧪 Testes
280280

281-
Suite de 193 testes (unit + assíncronos) com configuração em `pyproject.toml`:
281+
Suite de 203 testes (unit + assíncronos) com configuração em `pyproject.toml`:
282282

283283
```bash
284284
# Correr todos os testes

agents/energy_agent.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
ADJUSTMENTS_HISTORY_LIMIT = 100
2424

2525

26-
def compute_energy_utility(env, occupancy_ratio: float,
27-
weights: dict = ENERGY_UTILITY_WEIGHTS) -> float:
28-
"""Função de utilidade pura: U = we·E + wc·(1−C) + wq·(1−Q).
26+
def energy_indices(env, occupancy_ratio: float) -> tuple[float, float, float]:
27+
"""Componentes (E, C, Q) da função de utilidade energética — fonte única.
2928
3029
E = custo energético estimado (aumenta com ocupação)
3130
C = índice de conforto (temperatura+ruído normalizados) ∈ [0,1]
@@ -36,6 +35,13 @@ def compute_energy_utility(env, occupancy_ratio: float,
3635
noise_ok = 1.0 if env.noise <= TH["noise"]["max"] else 0.3
3736
C = (temp_ok + noise_ok) / 2.0
3837
Q = max(0.0, min(1.0, 1.0 - (env.co2 - 400) / 1000))
38+
return E, C, Q
39+
40+
41+
def compute_energy_utility(env, occupancy_ratio: float,
42+
weights: dict = ENERGY_UTILITY_WEIGHTS) -> float:
43+
"""Função de utilidade pura: U = we·E + wc·(1−C) + wq·(1−Q)."""
44+
E, C, Q = energy_indices(env, occupancy_ratio)
3945
return weights["energy"] * E + weights["comfort"] * (1 - C) + weights["air_quality"] * (1 - Q)
4046

4147

@@ -190,11 +196,9 @@ async def run(self):
190196
env = room.environment
191197
occupancy_ratio = room.occupied_seats / max(room.total_seats, 1)
192198

193-
E = occupancy_ratio * 0.5 + 0.3
194-
temp_ok = 1.0 if TH["temperature"]["min"] <= env.temperature <= TH["temperature"]["max"] else 0.5
195-
noise_ok = 1.0 if env.noise <= TH["noise"]["max"] else 0.3
196-
C = (temp_ok + noise_ok) / 2.0
197-
Q = max(0.0, min(1.0, 1.0 - (env.co2 - 400) / 1000))
199+
# Componentes partilhados com compute_energy_utility (fonte única):
200+
# afinar a fórmula em energy_indices propaga-se aos dois sítios.
201+
E, C, Q = energy_indices(env, occupancy_ratio)
198202
U = agent.we * E + agent.wc * (1 - C) + agent.wq * (1 - Q)
199203

200204
action = "maintain"

agents/room_agent.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,23 @@ def map_action_to_parameter(action: str) -> str:
6262

6363

6464
def compute_comfort_score(env) -> float:
65-
"""Score de conforto ∈ [0,1] usado nas PROPOSE do Contract Net."""
65+
"""Score de conforto contínuo ∈ [0,1] para ordenar PROPOSEs no Contract Net.
66+
67+
NOTA DE DESIGN: os limiares aqui são *deliberadamente mais apertados* do que
68+
os limiares "duros" de COMFORT_THRESHOLDS usados por Room._check_comfort /
69+
comfort_ok (CO2 ≤ 1000 ppm, ruído ≤ 60 dB). A intenção é que este score seja
70+
um discriminador mais fino entre salas que estão todas dentro do limite duro
71+
(comfort_ok=True): uma sala a 900 ppm continua "ok" mas é menos desejável do
72+
que outra a 450 ppm, e o score capta essa diferença ao penalizar mais cedo.
73+
Não é uma inconsistência mas uma escolha: comfort_ok responde "aceitável?";
74+
comfort_score responde "quão bom?".
75+
"""
6676
comfort_score = 1.0
67-
if env.temperature < 18 or env.temperature > 26:
77+
if env.temperature < 18 or env.temperature > 26: # banda de conforto (= config)
6878
comfort_score -= 0.3
69-
if env.co2 > 800:
79+
if env.co2 > 800: # soft: abaixo do limiar duro (1000)
7080
comfort_score -= 0.2
71-
if env.noise > 50:
81+
if env.noise > 50: # soft: abaixo do limiar duro (60)
7282
comfort_score -= 0.2
7383
return max(0.0, comfort_score)
7484

config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ def jid(agent_name: str) -> str:
1818

1919
# --- Configuração do Campus ---
2020
# Totais garantidos: 2 bibliotecas, 6 salas, 22 mesas, 104 lugares
21-
# Biblioteca Geral (BG): 4 salas, 15 mesas, 68 lugares
22-
# Biblioteca Azurém (BA): 2 salas, 7 mesas, 36 lugares
21+
# Biblioteca Geral (BG): 4 salas, 14 mesas, 68 lugares
22+
# Biblioteca Azurém (BA): 2 salas, 8 mesas, 36 lugares
2323
CAMPUS_CONFIG = {
2424
"campus_id": "C1",
2525
"campus_name": "Campus Gualtar",

main.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,8 @@
1010
import random
1111
import signal
1212
import sys
13-
# --- Patch slixmpp para permitir SASL PLAIN/SCRAM sem TLS (dev local) ---
14-
# Por default o slixmpp recusa autenticar sem encriptação; em dev local com
15-
# Prosody em internal_plain isso impede o login. Habilitamos os mecanismos
16-
# inseguros porque toda a comunicação fica em loopback (127.0.0.1).
13+
1714
import slixmpp
18-
_orig_xmpp_init = slixmpp.ClientXMPP.__init__
19-
def _patched_xmpp_init(self, *args, **kwargs):
20-
_orig_xmpp_init(self, *args, **kwargs)
21-
self["feature_mechanisms"].unencrypted_plain = True
22-
self["feature_mechanisms"].unencrypted_scram = True
23-
self["feature_starttls"].do_starttls = False
24-
slixmpp.ClientXMPP.__init__ = _patched_xmpp_init
2515

2616
from agents.comfort_agent import ComfortAgent
2717
from agents.coordinator_agent import CoordinatorAgent
@@ -51,6 +41,25 @@ def _patched_xmpp_init(self, *args, **kwargs):
5141
from simulation.mqtt_bus import MQTTSensorPublisher
5242
from simulation.mqtt_bus import is_available as mqtt_available
5343

44+
# --- Patch slixmpp para permitir SASL PLAIN/SCRAM sem TLS (dev local) ---
45+
# Por default o slixmpp recusa autenticar sem encriptação; em dev local com
46+
# Prosody em internal_plain isso impede o login. Habilitamos os mecanismos
47+
# inseguros porque toda a comunicação fica em loopback (127.0.0.1).
48+
# O patch é aplicado a nível de módulo (antes de qualquer agent.start()); a
49+
# importação de SPADE acima não instancia nenhum ClientXMPP, apenas regista as
50+
# classes, pelo que o monkey-patch entra em vigor antes do primeiro cliente.
51+
_orig_xmpp_init = slixmpp.ClientXMPP.__init__
52+
53+
54+
def _patched_xmpp_init(self, *args, **kwargs):
55+
_orig_xmpp_init(self, *args, **kwargs)
56+
self["feature_mechanisms"].unencrypted_plain = True
57+
self["feature_mechanisms"].unencrypted_scram = True
58+
self["feature_starttls"].do_starttls = False
59+
60+
61+
slixmpp.ClientXMPP.__init__ = _patched_xmpp_init
62+
5463
# ─── Logging ─────────────────────────────────────────────────────────────────
5564

5665
configure_logging(level="INFO", to_file=True)
@@ -153,10 +162,20 @@ async def main():
153162
# em valor 0 desde o arranque.
154163
from core.metrics import (
155164
active_negotiations as _an,
165+
)
166+
from core.metrics import (
156167
cfp_sent_total as _cs,
168+
)
169+
from core.metrics import (
157170
escalations_total as _es,
171+
)
172+
from core.metrics import (
158173
proposals_received_total as _pr,
174+
)
175+
from core.metrics import (
159176
redistributions_total as _rt,
177+
)
178+
from core.metrics import (
160179
refusals_received_total as _rf,
161180
)
162181
for lib in campus.libraries:

scenarios/reproducibility.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def run() -> int:
141141
for i, r in enumerate(runs)],
142142
["corrida", "seed", "hash_output"],
143143
)
144-
print(f"\nCorrida controlo (seed diferente):")
144+
print("\nCorrida controlo (seed diferente):")
145145
print(f" seed={other['seed']} hash={other_hash}")
146146

147147
# Diff detalhado das corridas com seed fixa (so para inspecao visual)

0 commit comments

Comments
 (0)