Skip to content

Commit 3ea663e

Browse files
committed
wip
1 parent 5ca75f1 commit 3ea663e

7 files changed

Lines changed: 357 additions & 126 deletions

File tree

PrismaDotnetApi/PrismaApi.Api/Controllers/SolversController.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using PrismaApi.Domain.Extensions;
55
using System.Net;
66
using PrismaApi.Api.Extensions;
7+
using System.Text.Json;
78

89
namespace PrismaApi.Api.Controllers;
910

@@ -70,4 +71,66 @@ public async Task<ActionResult<ApiResponseDto>> GetSolutionWithEvidenceAsync([Fr
7071

7172
return StatusCode((int)fastApiResponse.StatusCode, fastApiResponse.Content);
7273
}
74+
75+
[HttpPost("solvers/project/{projectId:guid}/policy_table")]
76+
public async Task<ActionResult<List<PolicyTableDecisionOutgoingDto>>> GetPolicyTableAsync([FromRoute] Guid projectId, [FromBody] EvidenceRequestDto? evidence = null, CancellationToken ct = default)
77+
{
78+
UserOutgoingDto user = HttpContext.GetLoadedUser();
79+
var fastApiResponse = await _fastApiService.SendInfluenceDiagramPolicyTableToFastApiAsync(projectId, $"/solvers/project/{projectId}/policy_table", evidence, user, ct);
80+
if (fastApiResponse.StatusCode == HttpStatusCode.OK)
81+
{
82+
if (string.IsNullOrEmpty(fastApiResponse.Content))
83+
{
84+
return Ok(new List<PolicyTableDecisionOutgoingDto>());
85+
}
86+
87+
var response = JsonSerializer.Deserialize<Dictionary<string, List<Dictionary<string, JsonElement>>>>(
88+
fastApiResponse.Content,
89+
new JsonSerializerOptions
90+
{
91+
PropertyNameCaseInsensitive = true
92+
}
93+
);
94+
95+
if (response is null)
96+
{
97+
return Ok(new List<PolicyTableDecisionOutgoingDto>());
98+
}
99+
100+
var result = response
101+
.Select(kvp => new PolicyTableDecisionOutgoingDto
102+
{
103+
DecisionId = kvp.Key,
104+
Rows = kvp.Value.Select(row =>
105+
{
106+
var states = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
107+
double value = 0;
108+
109+
foreach (var entry in row)
110+
{
111+
if (string.Equals(entry.Key, "value", StringComparison.OrdinalIgnoreCase))
112+
{
113+
value = entry.Value.ValueKind == JsonValueKind.Number
114+
? entry.Value.GetDouble()
115+
: double.Parse(entry.Value.ToString());
116+
continue;
117+
}
118+
119+
states[entry.Key] = entry.Value.ToString();
120+
}
121+
122+
return new PolicyTableRowOutgoingDto
123+
{
124+
States = states,
125+
Value = value
126+
};
127+
}).ToList()
128+
})
129+
.ToList();
130+
131+
return Ok(result);
132+
}
133+
134+
return StatusCode((int)fastApiResponse.StatusCode, fastApiResponse.Content);
135+
}
73136
}

PrismaDotnetApi/PrismaApi.Application/Interfaces/Services/IFastApiService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ public interface IFastApiService
1010
Task<ApiResponseDto> SendInfluenceDiagramToFastApiAsync(Guid projectId, string endpoint, UserOutgoingDto user, CancellationToken ct = default);
1111
Task<ApiResponseDto> SendPartialInfluenceDiagramToFastApiAsync(Guid projectId, string endpoint, List<List<Guid>> paths, UserOutgoingDto user, CancellationToken ct = default);
1212
Task<ApiResponseDto> SendInfluenceDiagramWithEvidenceToFastApiAsync(Guid projectId, string endpoint, List<EvidenceRequestDto> data, UserOutgoingDto user, CancellationToken ct = default);
13+
Task<ApiResponseDto> SendInfluenceDiagramPolicyTableToFastApiAsync(Guid projectId, string endpoint, EvidenceRequestDto? evidence, UserOutgoingDto user, CancellationToken ct = default);
1314
}

PrismaDotnetApi/PrismaApi.Application/Services/FastApiService.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public async Task<ApiResponseDto> SendInfluenceDiagramToFastApiAsync(Guid projec
6464
public async Task<ApiResponseDto> SendPartialInfluenceDiagramToFastApiAsync(Guid projectId, string endpoint, List<List<Guid>> paths, UserOutgoingDto user, CancellationToken ct = default)
6565
{
6666
var influenceDiagram = await _influenceDiagramService.GetRestrictedInfluenceDiagramAsync(projectId, user, ct);
67-
67+
6868
var payload = new
6969
{
7070
issues = influenceDiagram.issues,
@@ -94,4 +94,20 @@ public async Task<ApiResponseDto> SendInfluenceDiagramWithEvidenceToFastApiAsync
9494
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
9595
return await CallDownstreamFastApiPostAsync(endpoint, content, ct);
9696
}
97+
public async Task<ApiResponseDto> SendInfluenceDiagramPolicyTableToFastApiAsync(Guid projectId, string endpoint, EvidenceRequestDto? evidence, UserOutgoingDto user, CancellationToken ct = default)
98+
{
99+
var influenceDiagram = await _influenceDiagramService.GetRestrictedInfluenceDiagramAsync(projectId, user, ct);
100+
var payload = new
101+
{
102+
issues = influenceDiagram.issues,
103+
edges = influenceDiagram.edges,
104+
discrete_probabilities = influenceDiagram.discreteProbabilities,
105+
discrete_utilities = influenceDiagram.discreteUtilities,
106+
restriction_tables = influenceDiagram.restrictionTables,
107+
evidence,
108+
};
109+
var content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json");
110+
return await CallDownstreamFastApiPostAsync(endpoint, content, ct);
111+
}
112+
97113
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace PrismaApi.Domain.Dtos;
4+
5+
public class PolicyTableRowOutgoingDto
6+
{
7+
[JsonPropertyName("states")]
8+
public Dictionary<string, string> States { get; set; } = [];
9+
10+
[JsonPropertyName("value")]
11+
public double Value { get; set; }
12+
}
13+
14+
public class PolicyTableDecisionOutgoingDto
15+
{
16+
[JsonPropertyName("decision_id")]
17+
public string DecisionId { get; set; } = string.Empty;
18+
19+
[JsonPropertyName("rows")]
20+
public List<PolicyTableRowOutgoingDto> Rows { get; set; } = [];
21+
}

PrismaFastApi/src/routes/solver_routes.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ async def get_optimal_decisions_for_project_from_dtos(
2323
discrete_utilities: list[DiscreteUtilityOutgoingDto] = [],
2424
solver_service: SolverService = Depends(get_solver_service),
2525
) -> SolutionDto:
26-
return await solver_service.find_optimal_decision_pyagrum_from_dtos(issues, edges, discrete_probabilities, discrete_utilities)
26+
return await solver_service.find_optimal_decision_pyagrum_from_dtos(
27+
issues, edges, discrete_probabilities, discrete_utilities
28+
)
29+
2730

2831
@router.post("/solvers/project/{project_id}/with_evidence")
2932
async def get_optimal_decisions_for_project_with_evidence(
@@ -35,29 +38,37 @@ async def get_optimal_decisions_for_project_with_evidence(
3538
solver_service: SolverService = Depends(get_solver_service),
3639
) -> list[EvidenceOutgoingDto]:
3740
evidence_state_ids = [e.state_ids for e in evidence]
38-
results: list[Optional[float]] = await solver_service.get_MEU_given_evidence(issues, edges, discrete_probabilities, discrete_utilities, evidence_state_ids)
41+
results: list[Optional[float]] = await solver_service.get_MEU_given_evidence(
42+
issues, edges, discrete_probabilities, discrete_utilities, evidence_state_ids
43+
)
3944
# decision_solutions[0].mean is the expected utility for the first optimal decision, i.e. the root node which represents the expected utility for the model
4045
populated_evidence = [
4146
EvidenceOutgoingDto(
4247
evidence_id=evi.evidence_id,
4348
state_ids=evi.state_ids,
44-
expected_utility=results[n]
45-
if len(results) > n and not math.isnan(results[n]) # type: ignore
46-
else None,
49+
expected_utility=(
50+
results[n]
51+
if len(results) > n and not math.isnan(results[n]) # type: ignore
52+
else None
53+
),
4754
)
4855
for n, evi in enumerate(evidence)
4956
]
5057
exception_message = ""
5158
for n, populated in enumerate(populated_evidence):
5259
if n == 0 and populated.expected_utility is not None and populated.expected_utility < -1e10:
53-
exception_message += f"Impossible state reached due to all possible paths being restricted"
54-
60+
exception_message += (
61+
"Impossible state reached due to all possible paths being restricted"
62+
)
63+
5564
if populated.expected_utility is None:
5665
exception_message += f"Impossible state reached for evidence {populated.evidence_id} with state_ids {populated.state_ids}\n"
57-
# If any of the evidence leads to an impossible state, we raise an exception with the details of which evidence caused the issue.
66+
# If any of the evidence leads to an impossible state, we raise an exception with the details of which evidence caused the issue.
5867
if exception_message:
59-
raise ValueError(f"Restrictions/Evidence states lead to an impossible state:\n{exception_message}")
60-
68+
raise ValueError(
69+
f"Restrictions/Evidence states lead to an impossible state:\n{exception_message}"
70+
)
71+
6172
return populated_evidence
6273

6374

@@ -91,7 +102,8 @@ async def get_optimal_decisions_for_project_as_tree_tmp_from_dtos(
91102
return await solver_service.get_decision_tree_for_optimal_decisions_from_dtos(
92103
project_id, issues, edges, discrete_probabilities, discrete_utilities
93104
)
94-
105+
106+
95107
@router.post("/solvers/project/{project_id}/partial_decision_tree/v3")
96108
async def get_optimal_decisions_for_project_as_tree_tmp_from_dtos_v3(
97109
project_id: uuid.UUID,
@@ -105,6 +117,32 @@ async def get_optimal_decisions_for_project_as_tree_tmp_from_dtos_v3(
105117
):
106118
async with lock_manager.acquire_project_lock(project_id):
107119
return await solver_service.get_decision_tree_for_optimal_decisions_from_dtos_by_constructing_paths(
108-
project_id, issues, edges, discrete_probabilities, discrete_utilities, paths,
120+
project_id,
121+
issues,
122+
edges,
123+
discrete_probabilities,
124+
discrete_utilities,
125+
paths,
109126
)
110127

128+
129+
@router.post("/solvers/project/{project_id}/policy_table")
130+
async def get_policy_table_for_project(
131+
project_id: uuid.UUID,
132+
issues: list[IssueOutgoingDto],
133+
edges: list[EdgeOutgoingDto],
134+
discrete_probabilities: list[DiscreteProbabilityOutgoingDto] = [],
135+
discrete_utilities: list[DiscreteUtilityOutgoingDto] = [],
136+
evidence: Optional[EvidenceIncomingDto] = None,
137+
solver_service: SolverService = Depends(get_solver_service),
138+
lock_manager: ProjectQueueManager = Depends(get_project_lock_manager),
139+
) -> dict[str, list[dict[str, str | float]]]:
140+
async with lock_manager.acquire_project_lock(project_id):
141+
evidence_state_ids = evidence.state_ids if evidence else None
142+
return await solver_service.get_policy_table(
143+
issues=issues,
144+
edges=edges,
145+
discrete_probabilities=discrete_probabilities,
146+
discrete_utilities=discrete_utilities,
147+
evidence=evidence_state_ids,
148+
)

0 commit comments

Comments
 (0)