Merge pull request 'feat/dev_251201' (#130) from feat/dev_251201 into develop
Reviewed-on: https://kamco.gitea.gs.dabeeo.com/dabeeo/kamco-dabeeo-backoffice/pulls/130
This commit is contained in:
@@ -147,6 +147,6 @@ public class InnopamApiController {
|
||||
detectMastSearch.setCprsBfYr(cprsBfYr);
|
||||
detectMastSearch.setDtctSno(Integer.parseInt(dtctSno));
|
||||
detectMastSearch.setFeatureId(featureId);
|
||||
return detectMastService.findPnuDataDetail(detectMastSearch);
|
||||
return new FeaturePnuDto();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.postgres.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.SequenceGenerator;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import java.util.UUID;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "detect_mast_pnu")
|
||||
public class DetectMastPnuEntity {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "detect_mast_pnu_id_gen")
|
||||
@SequenceGenerator(
|
||||
name = "detect_mast_pnu_id_gen",
|
||||
sequenceName = "seq_detect_mast_pnu_id",
|
||||
allocationSize = 1)
|
||||
@Column(name = "dtct_mst_pnu_id", nullable = false)
|
||||
private Long id;
|
||||
|
||||
@NotNull
|
||||
@ColumnDefault("gen_random_uuid()")
|
||||
@Column(name = "detect_mast_pnu_uuid", nullable = false)
|
||||
private UUID detectMastPnuUuid;
|
||||
|
||||
@NotNull
|
||||
@Column(name = "dtct_mst_id", nullable = false)
|
||||
private Long dtctMstId;
|
||||
|
||||
@Size(max = 4)
|
||||
@NotNull
|
||||
@Column(name = "pnu", nullable = false, length = 4)
|
||||
private String pnu;
|
||||
|
||||
@Column(name = "polygon", length = Integer.MAX_VALUE)
|
||||
private String polygon;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.postgres.repository;
|
||||
|
||||
import com.kamco.cd.kamcoback.Innopam.postgres.entity.DetectMastPnuEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface DetectMastPnuRepository
|
||||
extends JpaRepository<DetectMastPnuEntity, Long>, DetectMastPnuRepositoryCustom {}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.postgres.repository;
|
||||
|
||||
public interface DetectMastPnuRepositoryCustom {}
|
||||
@@ -14,11 +14,10 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.stream.Stream;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -27,12 +26,22 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@RequiredArgsConstructor
|
||||
public class DetectMastService {
|
||||
|
||||
private final JsonFactory factory = new JsonFactory();
|
||||
@Value("${spring.profiles.active:local}")
|
||||
private String profile;
|
||||
|
||||
private final DetectMastCoreService detectMastCoreService;
|
||||
private final JsonFactory jsonFactory = new JsonFactory();
|
||||
|
||||
@Transactional
|
||||
public void saveDetectMast(DetectMastReq detectMast) {
|
||||
detectMastCoreService.saveDetectMast(detectMast);
|
||||
//
|
||||
// String dirPath =
|
||||
// "local".equals(profile)
|
||||
// ? "/Users/bokmin/detect/result/2023_2024/4"
|
||||
// : detectMast.getPathNm();
|
||||
//
|
||||
// List<FeaturePnuDto> list = this.extractFeaturePnusRandom(dirPath);
|
||||
}
|
||||
|
||||
public List<Basic> selectDetectMast(DetectMastSearch detectMast) {
|
||||
@@ -43,29 +52,19 @@ public class DetectMastService {
|
||||
return detectMastCoreService.selectDetectMast(id);
|
||||
}
|
||||
|
||||
/** GeoJSON → polygon_id + 랜덤 PNU */
|
||||
public List<FeaturePnuDto> findPnuData(DetectMastSearch detectMast) {
|
||||
String pathNm = detectMastCoreService.findPnuData(detectMast);
|
||||
return this.extractFeaturePnusFast(pathNm);
|
||||
|
||||
String dirPath =
|
||||
"local".equals(profile)
|
||||
? "/Users/bokmin/detect/result/2023_2024/4"
|
||||
: detectMastCoreService.findPnuData(detectMast);
|
||||
|
||||
return extractFeaturePnusRandom(dirPath);
|
||||
}
|
||||
|
||||
public FeaturePnuDto findPnuDataDetail(DetectMastSearch detectMast) {
|
||||
String pathNm = detectMastCoreService.findPnuData(detectMast);
|
||||
List<FeaturePnuDto> pnu = this.extractFeaturePnusFast(pathNm);
|
||||
return pnu.get(0);
|
||||
}
|
||||
|
||||
private String randomPnu() {
|
||||
ThreadLocalRandom r = ThreadLocalRandom.current();
|
||||
|
||||
String lawCode = String.valueOf(r.nextLong(1000000000L, 9999999999L)); // 10자리
|
||||
String sanFlag = r.nextBoolean() ? "1" : "2"; // 산/대지
|
||||
String bon = String.format("%04d", r.nextInt(1, 10000)); // 본번
|
||||
String bu = String.format("%04d", r.nextInt(0, 10000)); // 부번
|
||||
|
||||
return lawCode + sanFlag + bon + bu;
|
||||
}
|
||||
|
||||
public List<FeaturePnuDto> extractFeaturePnusFast(String dirPath) {
|
||||
/** 하위 폴더까지 .geojson 파일들에서 polygon_id만 뽑음 병렬처리(parallel) 제거: IO + parallel은 거의 항상 느려짐 */
|
||||
private List<FeaturePnuDto> extractFeaturePnusRandom(String dirPath) {
|
||||
|
||||
Path basePath = Paths.get(dirPath);
|
||||
if (!Files.isDirectory(basePath)) {
|
||||
@@ -73,20 +72,17 @@ public class DetectMastService {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
// 병렬로 모으기 위한 thread-safe 컬렉션
|
||||
Queue<FeaturePnuDto> out = new ConcurrentLinkedQueue<>();
|
||||
List<FeaturePnuDto> out = new ArrayList<>(4096);
|
||||
|
||||
try (Stream<Path> stream = Files.walk(basePath)) {
|
||||
stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(p -> p.toString().toLowerCase().endsWith(".geojson"))
|
||||
.parallel() // 병렬
|
||||
.forEach(
|
||||
p -> {
|
||||
try (InputStream in = Files.newInputStream(p);
|
||||
JsonParser parser = factory.createParser(in)) {
|
||||
JsonParser parser = jsonFactory.createParser(in)) {
|
||||
|
||||
// "polygon_id" 키를 만나면 다음 토큰 값을 읽어서 저장
|
||||
while (parser.nextToken() != null) {
|
||||
if (parser.currentToken() == JsonToken.FIELD_NAME
|
||||
&& "polygon_id".equals(parser.getCurrentName())) {
|
||||
@@ -94,14 +90,14 @@ public class DetectMastService {
|
||||
JsonToken next = parser.nextToken(); // 값으로 이동
|
||||
if (next == JsonToken.VALUE_STRING) {
|
||||
String polygonId = parser.getValueAsString();
|
||||
out.add(new FeaturePnuDto(polygonId, this.randomPnu()));
|
||||
out.add(new FeaturePnuDto(polygonId, randomPnu()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
// 파일별 에러 로그는 최소화
|
||||
System.err.println("파싱 실패: " + p.getFileName() + " / " + e.getMessage());
|
||||
// 파일 단위 실패는 최소 로그
|
||||
System.err.println("GeoJSON 파싱 실패: " + p.getFileName() + " / " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -110,6 +106,18 @@ public class DetectMastService {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
return new ArrayList<>(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** 랜덤 PNU 생성 (임시) - 법정동코드(5) + 산구분(1) + 본번(4) + 부번(4) = 14자리 */
|
||||
private String randomPnu() {
|
||||
ThreadLocalRandom r = ThreadLocalRandom.current();
|
||||
|
||||
String dongCode = String.format("%05d", r.nextInt(10000, 99999));
|
||||
String san = r.nextBoolean() ? "1" : "2";
|
||||
String bon = String.format("%04d", r.nextInt(1, 10000));
|
||||
String bu = String.format("%04d", r.nextInt(0, 10000));
|
||||
|
||||
return dongCode + san + bon + bu;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.locationtech.jts.geom.Coordinate;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.GeometryFactory;
|
||||
import org.locationtech.jts.geom.LinearRing;
|
||||
import org.locationtech.jts.geom.MultiPolygon;
|
||||
import org.locationtech.jts.geom.Polygon;
|
||||
|
||||
public class GeoJsonGeometryConverter {
|
||||
|
||||
private static final GeometryFactory GF = new GeometryFactory();
|
||||
|
||||
public static Geometry toGeometry(JsonNode geomNode) {
|
||||
String type = geomNode.path("type").asText();
|
||||
|
||||
if ("Polygon".equals(type)) {
|
||||
return toPolygon(geomNode.path("coordinates"));
|
||||
}
|
||||
if ("MultiPolygon".equals(type)) {
|
||||
return toMultiPolygon(geomNode.path("coordinates"));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Polygon toPolygon(JsonNode coords) {
|
||||
LinearRing shell = GF.createLinearRing(toCoords(coords.get(0)));
|
||||
return GF.createPolygon(shell);
|
||||
}
|
||||
|
||||
private static MultiPolygon toMultiPolygon(JsonNode coords) {
|
||||
Polygon[] polys = new Polygon[coords.size()];
|
||||
for (int i = 0; i < coords.size(); i++) {
|
||||
polys[i] = toPolygon(coords.get(i));
|
||||
}
|
||||
return GF.createMultiPolygon(polys);
|
||||
}
|
||||
|
||||
private static Coordinate[] toCoords(JsonNode ring) {
|
||||
Coordinate[] c = new Coordinate[ring.size() + 1];
|
||||
for (int i = 0; i < ring.size(); i++) {
|
||||
c[i] = new Coordinate(ring.get(i).get(0).asDouble(), ring.get(i).get(1).asDouble());
|
||||
}
|
||||
c[c.length - 1] = c[0];
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.utils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class GeoJsonLoader {
|
||||
|
||||
private final ObjectMapper om = new ObjectMapper();
|
||||
|
||||
public GeoJsonFile load(File geoJsonFile) throws Exception {
|
||||
|
||||
JsonNode root = om.readTree(geoJsonFile);
|
||||
|
||||
long mapId = root.path("properties").path("map_id").asLong(-1);
|
||||
if (mapId <= 0) {
|
||||
throw new IllegalStateException(
|
||||
"GeoJSON top-level properties.map_id 없음: " + geoJsonFile.getName());
|
||||
}
|
||||
|
||||
List<JsonNode> features = new ArrayList<>();
|
||||
root.path("features").forEach(features::add);
|
||||
|
||||
return new GeoJsonFile(mapId, features);
|
||||
}
|
||||
|
||||
/** ✅ feature에서 polygon_id 추출 */
|
||||
public static String polygonId(JsonNode feature) {
|
||||
return feature.path("properties").path("polygon_id").asText(null);
|
||||
}
|
||||
|
||||
public static class GeoJsonFile {
|
||||
|
||||
public final long mapId;
|
||||
public final List<JsonNode> features;
|
||||
|
||||
public GeoJsonFile(long mapId, List<JsonNode> features) {
|
||||
this.mapId = mapId;
|
||||
this.features = features;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.utils;
|
||||
|
||||
public class MapIdUtils {
|
||||
|
||||
private MapIdUtils() {
|
||||
// util class
|
||||
}
|
||||
|
||||
/** map_id → 시도코드 예: 34602060 → "34" */
|
||||
public static String sidoCodeFromMapId(long mapId) {
|
||||
String s = String.valueOf(mapId);
|
||||
if (s.length() < 2) {
|
||||
throw new IllegalArgumentException("잘못된 map_id: " + mapId);
|
||||
}
|
||||
return s.substring(0, 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.geotools.api.data.DataStore;
|
||||
import org.geotools.api.data.DataStoreFinder;
|
||||
import org.geotools.api.data.SimpleFeatureSource;
|
||||
import org.geotools.api.feature.simple.SimpleFeature;
|
||||
import org.geotools.data.simple.SimpleFeatureCollection;
|
||||
import org.geotools.data.simple.SimpleFeatureIterator;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.index.strtree.STRtree;
|
||||
|
||||
public class ShpIndexManager {
|
||||
|
||||
private static final String SHP_ROOT = "/shp";
|
||||
private static final String SHP_YYYYMM = "202512";
|
||||
private static final String PNU_FIELD = "PNU";
|
||||
|
||||
private final Map<String, STRtree> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public STRtree getIndex(String sidoCode) {
|
||||
return cache.computeIfAbsent(sidoCode, this::loadIndex);
|
||||
}
|
||||
|
||||
private STRtree loadIndex(String sidoCode) {
|
||||
try {
|
||||
String path = SHP_ROOT + "/LSMD_CONT_LDREG_" + sidoCode + "_" + SHP_YYYYMM + ".shp";
|
||||
|
||||
File shp = new File(path);
|
||||
if (!shp.exists()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
STRtree index = new STRtree(10);
|
||||
|
||||
DataStore store = DataStoreFinder.getDataStore(Map.of("url", shp.toURI().toURL()));
|
||||
|
||||
String typeName = store.getTypeNames()[0];
|
||||
SimpleFeatureSource source = store.getFeatureSource(typeName);
|
||||
SimpleFeatureCollection col = source.getFeatures();
|
||||
|
||||
try (SimpleFeatureIterator it = col.features()) {
|
||||
while (it.hasNext()) {
|
||||
SimpleFeature f = it.next();
|
||||
Geometry geom = (Geometry) f.getDefaultGeometry();
|
||||
String pnu = Objects.toString(f.getAttribute(PNU_FIELD), null);
|
||||
if (geom != null && pnu != null) {
|
||||
index.insert(geom.getEnvelopeInternal(), new ShpRow(geom, pnu));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index.build();
|
||||
store.dispose();
|
||||
return index;
|
||||
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** SHP 한 row */
|
||||
public static class ShpRow {
|
||||
|
||||
public final Geometry geom;
|
||||
public final String pnu;
|
||||
|
||||
public ShpRow(Geometry geom, String pnu) {
|
||||
this.geom = geom;
|
||||
this.pnu = pnu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.kamco.cd.kamcoback.Innopam.utils;
|
||||
|
||||
import java.util.List;
|
||||
import org.locationtech.jts.geom.Envelope;
|
||||
import org.locationtech.jts.geom.Geometry;
|
||||
import org.locationtech.jts.geom.prep.PreparedGeometry;
|
||||
import org.locationtech.jts.geom.prep.PreparedGeometryFactory;
|
||||
import org.locationtech.jts.index.strtree.STRtree;
|
||||
|
||||
public class ShpPnuMatcher {
|
||||
|
||||
public static String pickByIntersectionMax(STRtree index, Geometry target) {
|
||||
|
||||
Envelope env = target.getEnvelopeInternal();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
List<ShpIndexManager.ShpRow> rows = index.query(env);
|
||||
|
||||
double best = 0;
|
||||
String bestPnu = null;
|
||||
|
||||
for (ShpIndexManager.ShpRow row : rows) {
|
||||
|
||||
PreparedGeometry prep = PreparedGeometryFactory.prepare(row.geom);
|
||||
|
||||
if (prep.contains(target) || prep.covers(target)) {
|
||||
return row.pnu;
|
||||
}
|
||||
|
||||
if (!prep.intersects(target)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double area = row.geom.intersection(target).getArea();
|
||||
if (area > best) {
|
||||
best = area;
|
||||
bestPnu = row.pnu;
|
||||
}
|
||||
}
|
||||
return bestPnu;
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,9 @@ package com.kamco.cd.kamcoback.label;
|
||||
|
||||
import com.kamco.cd.kamcoback.config.api.ApiResponseDto;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser;
|
||||
import com.kamco.cd.kamcoback.label.service.LabelAllocateService;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -17,50 +15,46 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@Slf4j
|
||||
@Tag(name = "라벨링 작업 관리", description = "라벨링 작업 관리")
|
||||
@RequestMapping({"/api/label/mng"})
|
||||
@RequestMapping({"/api/label"})
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class LabelAllocateApiController {
|
||||
|
||||
private final LabelAllocateService labelAllocateService;
|
||||
|
||||
// 라벨링 수량 할당하는 로직 테스트
|
||||
@PostMapping("/allocate")
|
||||
public ApiResponseDto<Void> labelAllocate(@RequestBody LabelAllocateDto dto) {
|
||||
|
||||
// 도엽별 카운트 쿼리
|
||||
List<Sheet> sheets =
|
||||
List.of(
|
||||
new Sheet("1", 261),
|
||||
new Sheet("2", 500),
|
||||
new Sheet("3", 350),
|
||||
new Sheet("4", 250),
|
||||
new Sheet("5", 380),
|
||||
new Sheet("6", 459));
|
||||
// List<Sheet> sheets =
|
||||
// List.of(
|
||||
// new Sheet("1", 261),
|
||||
// new Sheet("2", 500),
|
||||
// new Sheet("3", 350),
|
||||
// new Sheet("4", 250),
|
||||
// new Sheet("5", 380),
|
||||
// new Sheet("6", 459));
|
||||
|
||||
// 사용자별 할당 입력한 값
|
||||
// List<TargetUser> targets =
|
||||
// List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700));
|
||||
// LabelAllocateService.allocateSheetCount(new ArrayList<>(sheets), new
|
||||
// ArrayList<>(targets));
|
||||
|
||||
// targets.forEach(
|
||||
// t -> {
|
||||
// log.info("[" + t.getUserId() + "]");
|
||||
// t.getAssigned()
|
||||
// .forEach(
|
||||
// u -> {
|
||||
// log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")");
|
||||
// });
|
||||
// });
|
||||
|
||||
List<TargetUser> targets =
|
||||
List.of(new TargetUser("A", 1000), new TargetUser("B", 500), new TargetUser("C", 700));
|
||||
|
||||
LabelAllocateService.allocate(new ArrayList<>(sheets), new ArrayList<>(targets));
|
||||
|
||||
targets.forEach(
|
||||
t -> {
|
||||
log.info("[" + t.getUserId() + "]");
|
||||
t.getAssigned()
|
||||
.forEach(
|
||||
u -> {
|
||||
log.info(" - 도엽: " + u.getSheetId() + " (" + u.getCount() + ")");
|
||||
});
|
||||
});
|
||||
/**
|
||||
* [A] 입력한 수 : 1000 - 도엽: 2 (500) - 도엽: 6 (459) - 도엽: 5 (41)
|
||||
*
|
||||
* <p>[B] 입력한 수 : 500 - 도엽: 5 (339) - 도엽: 3 (161)
|
||||
*
|
||||
* <p>[C] 입력한 수 : 700 - 도엽: 3 (189) - 도엽: 1 (261) - 도엽: 4 (250)
|
||||
*/
|
||||
// A 에게 도엽 2 asc 해서 500건 할당 -> 도엽 6 asc 해서 459 할당 -> 도엽 5 asc 해서 41건 할당 -> insert
|
||||
// B 에게 도엽 5 위에 41건 할당한 것 빼고 asc 해서 339건 할당 -> 도엽 3 asc 해서 161건 할당 -> insert
|
||||
// .... for문에서 할당한 것 빼고 asc 해서 건수만큼 할당 insert 하고 다음 으로 넘어가기
|
||||
List.of(new TargetUser("1", 1000), new TargetUser("2", 400), new TargetUser("3", 440));
|
||||
labelAllocateService.allocateAsc(targets);
|
||||
|
||||
return ApiResponseDto.ok(null);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kamco.cd.kamcoback.label.service;
|
||||
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.Sheet;
|
||||
import com.kamco.cd.kamcoback.label.dto.LabelAllocateDto.TargetUser;
|
||||
import com.kamco.cd.kamcoback.postgres.core.LabelAllocateCoreService;
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -12,7 +14,19 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class LabelAllocateService {
|
||||
|
||||
public static void allocate(List<Sheet> sheets, List<TargetUser> targetUsers) {
|
||||
private final LabelAllocateCoreService labelAllocateCoreService;
|
||||
|
||||
public LabelAllocateService(LabelAllocateCoreService labelAllocateCoreService) {
|
||||
this.labelAllocateCoreService = labelAllocateCoreService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 도엽 count 수와 할당된 count 수를 비교해서 많은 수부터 먼저 배정하고 나머지를 분배 배정하는 로직
|
||||
*
|
||||
* @param sheets
|
||||
* @param targetUsers
|
||||
*/
|
||||
public static void allocateSheetCount(List<Sheet> sheets, List<TargetUser> targetUsers) {
|
||||
|
||||
// 1️⃣ 실제 도엽 기준 할당
|
||||
allocateSheets(sheets, targetUsers);
|
||||
@@ -75,4 +89,34 @@ public class LabelAllocateService {
|
||||
t.setShortage(share);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 도엽 기준 asc sorting 해서 할당 수만큼 배정하는 로직
|
||||
*
|
||||
* @param targetUsers
|
||||
*/
|
||||
@Transactional
|
||||
public void allocateAsc(List<TargetUser> targetUsers) {
|
||||
Long lastId = null;
|
||||
|
||||
for (TargetUser target : targetUsers) {
|
||||
int remaining = target.getDemand();
|
||||
|
||||
while (remaining > 0) {
|
||||
|
||||
int batchSize = Math.min(remaining, 500);
|
||||
List<Long> ids = labelAllocateCoreService.fetchNextIds(lastId, batchSize);
|
||||
|
||||
if (ids.isEmpty()) {
|
||||
return; // 더이상 할당할 데이터가 없으면 return
|
||||
}
|
||||
|
||||
labelAllocateCoreService.assignOwner(
|
||||
ids, Long.valueOf(target.getUserId())); // TODO : userId를 숫자값을 가져올지 사번을 가져올지 고민
|
||||
|
||||
remaining -= ids.size();
|
||||
lastId = ids.get(ids.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.kamco.cd.kamcoback.postgres.core;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.repository.label.LabelAllocateRepository;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class LabelAllocateCoreService {
|
||||
|
||||
private final LabelAllocateRepository labelAllocateRepository;
|
||||
|
||||
public List<Long> fetchNextIds(Long lastId, int batchSize) {
|
||||
return labelAllocateRepository.fetchNextIds(lastId, batchSize);
|
||||
}
|
||||
|
||||
public Long assignOwner(List<Long> ids, Long userId) {
|
||||
return labelAllocateRepository.assignOwner(ids, userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.label;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataInferenceGeomEntity;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface LabelAllocateRepository
|
||||
extends JpaRepository<MapSheetAnalDataInferenceGeomEntity, Long>,
|
||||
LabelAllocateRepositoryCustom {}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.label;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface LabelAllocateRepositoryCustom {
|
||||
|
||||
List<Long> fetchNextIds(Long lastId, int batchSize);
|
||||
|
||||
Long assignOwner(List<Long> ids, Long userId);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.kamco.cd.kamcoback.postgres.repository.label;
|
||||
|
||||
import static com.kamco.cd.kamcoback.postgres.entity.QMapSheetAnalDataInferenceGeomEntity.mapSheetAnalDataInferenceGeomEntity;
|
||||
|
||||
import com.kamco.cd.kamcoback.postgres.entity.MapSheetAnalDataGeomEntity;
|
||||
import com.querydsl.core.types.dsl.Expressions;
|
||||
import com.querydsl.core.types.dsl.StringExpression;
|
||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||
import java.util.List;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class LabelAllocateRepositoryImpl extends QuerydslRepositorySupport
|
||||
implements LabelAllocateRepositoryCustom {
|
||||
|
||||
private final JPAQueryFactory queryFactory;
|
||||
private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)");
|
||||
|
||||
public LabelAllocateRepositoryImpl(JPAQueryFactory queryFactory) {
|
||||
super(MapSheetAnalDataGeomEntity.class);
|
||||
this.queryFactory = queryFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> fetchNextIds(Long lastId, int batchSize) {
|
||||
return queryFactory
|
||||
.select(mapSheetAnalDataInferenceGeomEntity.geoUid)
|
||||
.from(mapSheetAnalDataInferenceGeomEntity)
|
||||
.where(
|
||||
// mapSheetAnalDataGeomEntity.pnu.isNotNull(), //TODO: Mockup 진행 이후 확인하기
|
||||
lastId == null ? null : mapSheetAnalDataInferenceGeomEntity.geoUid.gt(lastId),
|
||||
mapSheetAnalDataInferenceGeomEntity.compareYyyy.eq(2022),
|
||||
mapSheetAnalDataInferenceGeomEntity.targetYyyy.eq(2024),
|
||||
mapSheetAnalDataInferenceGeomEntity.labelerUid.isNull())
|
||||
.orderBy(mapSheetAnalDataInferenceGeomEntity.geoUid.asc())
|
||||
.limit(batchSize)
|
||||
.fetch();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long assignOwner(List<Long> ids, Long userId) {
|
||||
return queryFactory
|
||||
.update(mapSheetAnalDataInferenceGeomEntity)
|
||||
.set(mapSheetAnalDataInferenceGeomEntity.labelerUid, userId)
|
||||
.where(mapSheetAnalDataInferenceGeomEntity.geoUid.in(ids))
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user