Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 1 addition & 56 deletions mysite/dss/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms
from django.forms import inlineformset_factory
from .models import McdaSession, Criterion, CritGroup, GroupOrder, LocalOrder, DecisionMatrix
from .models import McdaSession, Criterion, CritGroup, DecisionMatrix


# ------------------------------------------------------------------
Expand Down Expand Up @@ -401,58 +401,3 @@ def save(self, session: McdaSession) -> None:
session.local_ranks[group][crit] = data[field_name]

session.save()


class GroupOrderForm(forms.ModelForm):
class Meta:
model = GroupOrder
fields = ["group1", "group2", "intensity"]
widgets = {
"group1": forms.Select(),
"group2": forms.Select(),
"intensity": forms.NumberInput(attrs={"min": 0, "step": "0.01"}),
}

def clean(self):
cleaned = super().clean()
if cleaned.get("group1") == cleaned.get("group2"):
raise forms.ValidationError("Choose two different groups.")
return cleaned


class LocalOrderForm(forms.ModelForm):
class Meta:
model = LocalOrder
fields = ["criterion1", "criterion2", "intensity"]
widgets = {
"criterion1": forms.Select(),
"criterion2": forms.Select(),
"intensity": forms.NumberInput(attrs={"min": 0, "step": "0.01"}),
}

def clean(self):
cleaned = super().clean()
if cleaned.get("criterion1") == cleaned.get("criterion2"):
raise forms.ValidationError("Choose two different criteria.")
# if cleaned.get("criterion1").group != cleaned.get("criterion2").group:
# raise forms.ValidationError("Choose criteria from the same group.")
return cleaned


# Formsets
# extra=1 shows one blank row by default; can_delete=True adds a remove checkbox
GroupOrderFormSet = inlineformset_factory(
parent_model = McdaSession,
model = GroupOrder,
form = GroupOrderForm,
extra = 1,
can_delete = True,
)

LocalOrderFormSet = inlineformset_factory(
parent_model = McdaSession,
model = LocalOrder,
form = LocalOrderForm,
extra = 1,
can_delete = True,
)
31 changes: 28 additions & 3 deletions mysite/dss/mcda.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,34 @@ def set_fixed_weights(config: McdaConfig):

config._weights = weights


# ============================================================
# WEIGHTING AND RANKING HELPER FUNCTIONS
# ============================================================

def ranking_to_pairwise(ranking: dict) -> list[tuple]:
"""
Convert an ordinal ranking dictionary into pairwise constraints.

Parameters:
ranking: Dict with groups/criteria as keys
and ordinal ranks (1/2/3/...) as values
Returns:
pairwise_list: List of pairwise constraint tuples
(superior, inferior, 1.0)
"""
filtered_items = [(k, v) for k, v in ranking.items() if v is not None]
sorted_items = sorted(filtered_items, key=lambda x: x[1])
pairwise_list = []
n = len(sorted_items)
for i in range(n):
for j in range(i + 1, n):
if sorted_items[i][1] < sorted_items[j][1]:
pairwise_list.append(
(sorted_items[i][0], sorted_items[j][0], 1.0)
)
return pairwise_list


def ranking_to_pairwise_constraints(ranking: list, expected_items: list=None):
"""
Convert a complete or incomplete ordinal ranking into
Expand Down Expand Up @@ -856,8 +880,9 @@ def deterministic_promethee(config: McdaConfig):
nfs_df = pd.DataFrame({
"FOR (phi+)": phi_plus,
"AGAINST (phi-)": phi_minus,
"NFS (phi)": nfs
}).sort_values("NFS (phi)", ascending=False)
"NFS (phi)": nfs,
"rank": nfs.rank(method="dense", ascending=False),
}).sort_values("rank")
return {"net_flow_scores": nfs_df, "pairwise_prefs": S}

def run_performance_uncertainty(config: McdaConfig, rng=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Generated by Django 6.0.5 on 2026-08-26 12:10

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dss', '0007_replace_results_data_with_sections'),
]

operations = [
migrations.RemoveField(
model_name='mcdasession',
name='group_order',
),
migrations.RemoveField(
model_name='mcdasession',
name='local_order',
),
migrations.RemoveField(
model_name='mcdasession',
name='sampling_mode',
),
migrations.AddField(
model_name='mcdasession',
name='crit_ranks',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='mcdasession',
name='criterion_weights',
field=models.JSONField(null=True),
),
migrations.AddField(
model_name='mcdasession',
name='group_ranks',
field=models.JSONField(default=dict),
),
migrations.AddField(
model_name='mcdasession',
name='local_ranks',
field=models.JSONField(default=dict),
),
migrations.AlterField(
model_name='mcdasession',
name='scenario',
field=models.CharField(choices=[('deterministic', 'Deterministic'), ('random', 'Random'), ('bounded', 'Bounded'), ('ordered', 'Ordered'), ('bounded_ordered', 'Bounded Ordered')], max_length=15, null=True),
),
]
35 changes: 35 additions & 0 deletions mysite/dss/migrations/0009_delete_localorder_group_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 6.0.5 on 2026-09-10 12:40

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('dss', '0008_remove_mcdasession_group_order_and_more'),
]

operations = [
migrations.AlterUniqueTogether(
name='localorder',
unique_together=None,
),
migrations.RemoveField(
model_name='localorder',
name='criterion1',
),
migrations.RemoveField(
model_name='localorder',
name='criterion2',
),
migrations.RemoveField(
model_name='localorder',
name='session',
),
migrations.DeleteModel(
name='GroupOrder',
),
migrations.DeleteModel(
name='LocalOrder',
),
]
49 changes: 4 additions & 45 deletions mysite/dss/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db import models
from .mcda import McdaConfig, WeightConstraints
from .mcda import McdaConfig, WeightConstraints, ranking_to_pairwise
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
import pandas as pd
Expand Down Expand Up @@ -107,19 +107,6 @@ def init_criteria_n_groups(self):
#TODO: if self.user_type == self.UserType.KAM:
# Add more groups and criteria

def ranking_to_pairwise(self, ranking: dict) -> list[tuple]:
filtered_items = [(k, v) for k, v in ranking.items() if v is not None]
sorted_items = sorted(filtered_items, key=lambda x: x[1])
pairwise_list = []
n = len(sorted_items)
for i in range(n):
for j in range(i + 1, n):
if sorted_items[i][1] < sorted_items[j][1]:
pairwise_list.append(
(sorted_items[i][0], sorted_items[j][0], 1.0)
)
return pairwise_list

def build_config(self) -> McdaConfig:
criteria = list(self.criteria)
criterion_names = [c.name for c in criteria]
Expand All @@ -138,12 +125,12 @@ def build_config(self) -> McdaConfig:
group = self.group_weights,
local = self.local_weights,
criterion = self.criterion_weights,
group_order = self.ranking_to_pairwise(self.group_ranks),
criterion_order = self.ranking_to_pairwise(self.crit_ranks)
group_order = ranking_to_pairwise(self.group_ranks),
criterion_order = ranking_to_pairwise(self.crit_ranks),
)
if self.local_ranks:
constraints.local_order = {
g: self.ranking_to_pairwise(self.local_ranks[g])
g: ranking_to_pairwise(self.local_ranks[g])
if g in self.local_ranks else []
for g in groups
}
Expand Down Expand Up @@ -214,34 +201,6 @@ def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)

class GroupOrder(models.Model):
"""Defines the importance order of groups"""
session = models.ForeignKey(McdaSession, on_delete=models.CASCADE, related_name="group_orders")
group1 = models.ForeignKey(CritGroup, on_delete=models.CASCADE, related_name="ordered_higher")
group2 = models.ForeignKey(CritGroup, on_delete=models.CASCADE, related_name="ordered_lower")
intensity = models.FloatField(validators=[MinValueValidator(0)], default=1)

class Meta:
unique_together = ['session', 'group1', 'group2']

class LocalOrder(models.Model):
"""Defines the importance order of criteria in a group"""
session = models.ForeignKey(McdaSession, on_delete=models.CASCADE, related_name="local_orders")
criterion1 = models.ForeignKey(Criterion, on_delete=models.CASCADE, related_name="ordered_higher")
criterion2 = models.ForeignKey(Criterion, on_delete=models.CASCADE, related_name="ordered_lower")
intensity = models.FloatField(validators=[MinValueValidator(0)], default=1)

class Meta:
unique_together = ['session', 'criterion1', 'criterion2']

def clean(self):
if self.criterion1.group != self.criterion2.group:
raise ValidationError("Criteria must belong to the same group.")

def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)

class Results(models.Model):
session = models.OneToOneField(
McdaSession, on_delete=models.CASCADE, primary_key=True, related_name='results'
Expand Down
Loading
Loading