Commit f11971ca authored by Tong Li's avatar Tong Li

MP

parent 2ec4b057
......@@ -13,6 +13,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
import com.aps.macroplanner.output.ResultWriter;
import com.aps.macroplanner.output.dto.*;
import com.aps.service.MacroPlannerResultService;
import com.aps.service.MacroPlannerProductNetworkService;
import com.aps.service.MpPispipResultPersistenceService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.ortools.Loader;
......@@ -67,6 +68,9 @@ public class MacroPlannerResultController {
@Autowired
private MacroPlannerResultService macroPlannerResultService;
@Autowired
private MacroPlannerProductNetworkService macroPlannerProductNetworkService;
@Autowired
private MacroPlannerDataConverter macroPlannerDataConverter;
......@@ -304,11 +308,15 @@ public class MacroPlannerResultController {
@RequestParam(value = "productId", required = false) @Parameter(description = "按产品ID过滤成品根节点(可选)") String productId,
@RequestParam(value = "spId", required = false) @Parameter(description = "按库存点ID过滤(可选)") String spId,
@RequestParam(value = "period", required = false) @Parameter(description = "按周期过滤, 仅保留该周期内有供应的节点/来源(可选)") String period) {
OptimizationResult result = loadResult(sceneId);
if (result == null) {
return R.failed("未找到场景 " + sceneId + " 的排产结果文件");
if (productId == null || productId.trim().isEmpty() || spId == null || spId.trim().isEmpty()) {
return R.failed("productId 和 spId 不能为空");
}
SupplyChainNode node = macroPlannerProductNetworkService.buildProductNetwork(
sceneId, productId.trim(), spId.trim(), period);
if (node == null) {
return R.failed("未找到场景 " + sceneId + " 中 " + productId + "@" + spId + " 的产品网络");
}
return R.ok(filterProductNetwork(result.getProductNetwork(), productId, spId, period));
return R.ok(node);
}
/**
......@@ -451,132 +459,6 @@ public class MacroPlannerResultController {
return new ArrayList<>(merged.values());
}
/**
* 过滤产品生产网络:
* <ul>
* <li>productId / spId — 仅保留匹配的成品根节点及其子树;</li>
* <li>unitId — 仅保留该设备上的供应来源;</li>
* <li>periodIndex — 仅保留该周期内有供应(生产/消耗/库存/销售)的节点与来源, 其余剪枝。</li>
* </ul>
* 无过滤条件时原样返回。
*/
private SupplyChainNode filterProductNetwork(ProductNetworkResult network,
String productId,
String spId,
String periodIndex) {
if (network == null) {
return null;
}
if (!hasText(productId) && !hasText(spId) && periodIndex == null) {
return null;
}
ProductNetworkResult filtered = new ProductNetworkResult();
SupplyChainNode childSrc = network.getAllNodes().get(productId + "@" + spId);
if (childSrc == null) {
return null;
}
SupplyChainNode childNode= copyNetworkNode(childSrc, network, filtered, periodIndex);
if (childNode == null) {
return null;
}
return childNode;
}
/**
* 递归拷贝网络节点, 同时按设备与周期剪枝供应来源、子节点。
*/
private SupplyChainNode copyNetworkNode(SupplyChainNode src,
ProductNetworkResult source,
ProductNetworkResult target,
String periodIndex) {
if (periodIndex != null && !src.getActivePeriods().contains(periodIndex)) {
return null;
}
String key = src.getProductId() + "@" + src.getSpId();
SupplyChainNode existing = target.getAllNodes().get(key);
if (existing != null) {
return existing;
}
SupplyChainNode copy = target.getOrCreateNode(src.getProductId(), src.getSpId(),src.getSpName());
copy.setLevel(src.getLevel());
copy.setTotalSalesDemand(src.getTotalSalesDemand());
copy.setTotalSalesFulfilled(src.getTotalSalesFulfilled());
copy.setTotalDependentDemand(src.getTotalDependentDemand());
// copy.setSummary(src.getSummary());
// 供应来源: 按设备 + 周期过滤
for (SupplyChainNode.SupplySource ss : src.getSupplySources()) {
for (Map.Entry<String, SupplyChainNode.SupplySourceDetail> entry : ss.productionByPeriod.entrySet()) {
String period = entry.getKey(); // 周期key,对应截图里的 0、3
if(period.equals(periodIndex))
{
SupplyChainNode.SupplySourceDetail detail = entry.getValue(); // 该周期的产能详情
ss.production=detail.production;
ss.unitId=detail.unitId;
ss.unitName=detail.unitName;
ss.productionByPeriod.clear();
copy.getSupplySources().add(ss);
break;
}
}
}
// 消费者: 按周期过滤
for (SupplyChainNode.ConsumerInfo ci : src.getConsumers()) {
if (periodIndex != null && !ci.consumedByPeriod.containsKey(periodIndex)) {
continue;
}
copy.getConsumers().add(ci);
}
// BOM 子物料: 按周期剪枝
for (SupplyChainNode.BomChild child : src.getChildren()) {
if (periodIndex != null && !child.consumedByPeriod.containsKey(periodIndex)) {
continue;
}
SupplyChainNode childSrc = source.getAllNodes().get(child.productId + "@" + child.spId);
if (childSrc == null) {
continue;
}
SupplyChainNode childNode= copyNetworkNode(childSrc, source, target, periodIndex);
if (childNode == null) {
continue; // 该子节点在该周期无活动, 剪枝
}
for (SupplyChainNode.SupplySource ss : childNode.getSupplySources()) {
child.totalConsumedQty = ss.production;
child.unitId = ss.unitId;
child.unitName = ss.unitName;
child.consumedByPeriod.clear();
break;
}
copy.getChildren().add(child);
}
return copy;
}
/**
* 供应来源在指定周期是否有产量 (在途/外部采购视为全周期可用)。
*/
private boolean isSourceActiveInPeriod(SupplyChainNode.SupplySource ss, int periodIndex) {
if ("IN_TRANSIT".equals(ss.type) || "EXTERNAL".equals(ss.type)) {
return true;
}
return ss.productionByPeriod.containsKey(periodIndex);
}
private void enrichSalesDemandDisplayFields(List<SalesDemandResult> demands) {
if (demands == null || demands.isEmpty()) {
return;
......
package com.aps.macroplanner;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.output.dto.OptimizationResult;
import com.aps.service.MpPispipResultPersistenceService;
import com.google.ortools.Loader;
import com.google.ortools.linearsolver.MPSolver;
import com.aps.macroplanner.constraint.ConstraintFactory;
......@@ -11,6 +13,7 @@ import com.aps.macroplanner.objective.ObjectiveBuilder;
import com.aps.macroplanner.objective.StrategyLevel;
import com.aps.macroplanner.output.ResultWriter;
import com.aps.macroplanner.output.SolutionPrinter;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.FileOutputStream;
import java.io.PrintStream;
......@@ -83,6 +86,8 @@ import java.util.logging.Logger;
*/
public class MacroPlannerOptimizer {
/** LP 模型文件和日志文件的输出目录 */
private static final String LOG_DIR = "mp";
......@@ -382,7 +387,12 @@ public class MacroPlannerOptimizer {
// 回写业务对象到JSON文件(预留)
ResultWriter rw = new ResultWriter(model, data, startTimeMs);
boolean jsonPath = rw.saveResultToFile(sceneId);
OptimizationResult optimizationResult = rw.buildResult();
boolean jsonPath = rw.saveResultToFile(sceneId, optimizationResult);
if (jsonPath) {
writeLog("\n[OK] 优化结果JSON已导出: " + jsonPath);
}
......
package com.aps.macroplanner;
import com.aps.ApsApplication;
import com.aps.common.util.FileHelper;
import com.aps.macroplanner.data.BenchmarkDataBuilder;
import com.aps.macroplanner.data.LargeScaleBomTestDataBuilder;
import com.aps.macroplanner.output.ResultWriter;
import com.aps.macroplanner.output.dto.OptimizationResult;
import com.aps.service.MpPispipResultPersistenceService;
import com.google.ortools.Loader;
import com.aps.macroplanner.data.MultiLevelBomTestDataBuilder;
import com.aps.macroplanner.data.TestDataBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
/**
* 多级BOM + 多成品 + 共享半成品 测试运行器。
......@@ -26,6 +32,7 @@ import com.aps.macroplanner.data.TestDataBuilder;
* </ol>
*/
public class MultiLevelBomTestRunner {
private static final String LOG_DIR = "mp";
/** LP 模型文件路径 */
......@@ -46,29 +53,44 @@ public class MultiLevelBomTestRunner {
System.out.println("Demand: P1=40/day, P2=30/day");
System.out.println();
// 通过 Spring 容器获取 service 实例 (static main 中 @Autowired 不会生效)
ApplicationContext ctx = SpringApplication.run(ApsApplication.class, args);
try {
MultiLevelBomTestDataBuilder data=new MultiLevelBomTestDataBuilder();
data.init();
MultiLevelBomTestDataBuilder builder=new MultiLevelBomTestDataBuilder();
builder.init();
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(builder);
MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
optimizer.buildModel();
optimizer.solve("bom");
// for (int scale : BenchmarkDataBuilder.SUPPORTED_SCALES) {
// MpPispipResultPersistenceService service =
// ctx.getBean(MpPispipResultPersistenceService.class);
//
// int scale = 10000;
// BenchmarkDataBuilder data = BenchmarkDataBuilder.forScale(scale);
// writeLog("===== TEST RUNNER START " + scale + "=====");
//
// int scale=10000;
// BenchmarkDataBuilder data =
// BenchmarkDataBuilder.forScale(scale);
// writeLog("===== TEST RUNNER START "+scale+"=====");
////
// data.init();
// writeLog("Data loaded: " + data.getProducts().size() + " products, "
// + data.getOperations().size() + " operations");
//
// long startTime = System.currentTimeMillis();
// MacroPlannerOptimizer optimizer = new MacroPlannerOptimizer(data);
// optimizer.buildModel();
// optimizer.solve(String.valueOf(scale));
//
// // 构建结果并调用 save 入库
// ResultWriter writer = new ResultWriter(optimizer.getModel(), optimizer.getData(), startTime);
// OptimizationResult result = writer.buildResult();
// int saved = service.save(String.valueOf(scale), result);
// writeLog("PISPIP result saved: " + saved);
//
// writeLog("===== TEST RUNNER END =====");
// }
} catch (Exception e) {
} finally {
SpringApplication.exit(ctx);
}
}
}
\ No newline at end of file
package com.aps.macroplanner.output.dto;
import java.util.ArrayList;
import java.util.List;
/**
* BOM 结构持久化数据 — 求解后将静态工艺/BOM 关系固化,供查询接口按需组装产品生产网络。
*
* <p>与求解结果 (period_tasks / pispips / salesdemand / operation_demands) 分离:
* 求解结果存 "数值", 本结构存 "关系" (谁产出什么、谁消耗什么、消耗因子)。</p>
*
* <p>字段全部使用扁平化嵌套 POJO + public 字段, 便于 Jackson 序列化/反序列化。</p>
*/
public class BomStructureData {
public List<ProductInfo> products = new ArrayList<>();
public List<StockingPointInfo> stockingPoints = new ArrayList<>();
public List<ProductSpMappingInfo> productSpMappings = new ArrayList<>();
public List<OperationInfo> operations = new ArrayList<>();
public List<OperationInputInfo> operationInputs = new ArrayList<>();
public List<PeriodInfo> periods = new ArrayList<>();
public List<InitialInventoryInfo> initialInventories = new ArrayList<>();
public List<InTransitSupplyInfo> inTransitSupplies = new ArrayList<>();
// ==================== 扁平 POJO ====================
public static class ProductInfo {
public String id;
public String name;
public String code;
}
public static class StockingPointInfo {
public String id;
public String name;
}
public static class ProductSpMappingInfo {
public String productId;
public String spId;
}
public static class OperationInfo {
public String id;
public String name;
public int leadTimeDays;
public boolean hasLotSize;
public Double lotSize;
public List<OperationOutputInfo> outputs = new ArrayList<>();
public List<UnitOperationInfo> unitOperations = new ArrayList<>();
}
public static class OperationOutputInfo {
public String productId;
public String spId;
}
public static class UnitOperationInfo {
public String unitId;
public String unitName;
public double capacityCoeff;
}
public static class OperationInputInfo {
public String operationId;
public String inputProductId;
public String inputSpId;
public String inputSpName;
public double factor;
}
public static class PeriodInfo {
public int index;
public String name;
public String startDate;
}
public static class InitialInventoryInfo {
public String productId;
public String spId;
public double quantity;
}
public static class InTransitSupplyInfo {
public String productId;
public String spId;
public String arrivalDate;
public double quantity;
}
}
package com.aps.macroplanner.output.dto;
/**
* 工序消耗物料结果 — 每个工序在每周期消耗每种 BOM 输入物料的量 (OperationDemandQty)。
*
* <p>用于按需组装产品生产网络时还原 "消费者 consumedQty" 与 "BOM 子物料 consumedQty"。
* 扁平 POJO, 直接映射到 operation_demands.parquet。</p>
*/
public class OperationDemandResult {
private String operationId;
private String inputProductId;
private String inputSpId;
private int periodIndex;
private double quantity;
public OperationDemandResult() {
}
public OperationDemandResult(String operationId, String inputProductId, String inputSpId,
int periodIndex, double quantity) {
this.operationId = operationId;
this.inputProductId = inputProductId;
this.inputSpId = inputSpId;
this.periodIndex = periodIndex;
this.quantity = quantity;
}
public String getOperationId() { return operationId; }
public void setOperationId(String v) { this.operationId = v; }
public String getInputProductId() { return inputProductId; }
public void setInputProductId(String v) { this.inputProductId = v; }
public String getInputSpId() { return inputSpId; }
public void setInputSpId(String v) { this.inputSpId = v; }
public int getPeriodIndex() { return periodIndex; }
public void setPeriodIndex(int v) { this.periodIndex = v; }
public double getQuantity() { return quantity; }
public void setQuantity(double v) { this.quantity = v; }
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment