From 327f2865d461e284abde7deefcfc36ce386eddb1 Mon Sep 17 00:00:00 2001 From: IliaDenisov Date: Thu, 5 Feb 2026 21:35:13 +0300 Subject: [PATCH] fix: ship production math --- internal/controller/planet.go | 128 ++++-------------- internal/controller/planet_test.go | 178 ++++++++++++++----------- internal/controller/report.go | 8 +- internal/controller/ship_group.go | 8 +- internal/controller/ship_group_test.go | 1 + internal/model/game/game.go | 2 +- internal/model/game/planet.go | 13 ++ internal/model/game/production.go | 6 +- internal/model/game/ship.go | 8 -- internal/model/report/ship.go | 11 +- 10 files changed, 158 insertions(+), 205 deletions(-) diff --git a/internal/controller/planet.go b/internal/controller/planet.go index c983c6d..5151e81 100644 --- a/internal/controller/planet.go +++ b/internal/controller/planet.go @@ -107,17 +107,16 @@ func (c *Cache) PlanetProduction(ri int, number int, prod game.ProductionType, s } if p.Production.Type == game.ProductionShip && (prod != game.ProductionShip || *subjectID != *p.Production.SubjectID) { - mat, _ := c.MustShipType(ri, *p.Production.SubjectID).ProductionCost() - p.Mat(p.Material.F() + mat*(*p.Production.Progress).F()) - *p.Production.Progress = 0. + p.ReleaseMaterial(c.MustShipType(ri, *p.Production.SubjectID).EmptyMass()) } else if prod == game.ProductionShip { // new ship class to produce; otherwise we must have been returned from the func earlier - progress := game.F(0.) - p.Production.Progress = &progress + p.Production.Progress = new(game.Float) + p.Production.ProdUsed = new(game.Float) } if prod != game.ProductionShip { p.Production.Progress = nil + p.Production.ProdUsed = nil } p.Production.Type = prod @@ -301,105 +300,40 @@ func (c *Cache) putMaterial(pn uint, v float64) { c.MustPlanet(pn).Mat(v) } -func ProduceShip_old(p *game.Planet, productionAvailable, shipMass float64) uint { - if productionAvailable <= 0 { - return 0 - } - // CAP_perShip := shipMass / p.Resources.F() - CAP_perShip := ShipCapitalCost(shipMass, float64(p.Resources)) - productionForMass := ShipProductionCost(shipMass) - ships := uint(0) - flZero := game.F(0.) - p.Production.Progress = &flZero - for productionAvailable > 0 { - var productionExtraCAP float64 - if CAP_deficit := p.Capital.F() - CAP_perShip; CAP_deficit < 0 { - productionExtraCAP = -CAP_deficit - } - productionCost := productionExtraCAP + productionForMass - if productionAvailable >= productionCost { - productionAvailable -= productionCost - p.Cap(p.Capital.F() - (CAP_perShip - productionExtraCAP)) - ships++ - } else { - progress := game.F(productionAvailable / productionCost) - productionAvailable -= productionCost * progress.F() - p.Production.Progress = &progress - - v := (productionForMass + CAP_perShip) * float64(progress) - fmt.Println("P=", v) - // fmt.Println("productionForMass", productionForMass, "CAP_perShip", CAP_perShip, "productionCost", productionCost, "productionAvailable", productionAvailable, "progress", progress) - break - } - } - return ships -} - func ProduceShip(p *game.Planet, productionAvailable, shipMass float64) uint { if productionAvailable <= 0 { return 0 } - // MAT_perShip := shipMass / p.Resources.F() - MAT_perShip := ShipMaterialCost(shipMass, float64(p.Resources)) - fmt.Println("MAT_perShip", MAT_perShip) - productionForMass := ShipProductionCost(shipMass) ships := uint(0) - fval := game.F(0.) - p.Production.Progress = &fval - for productionAvailable > 0 { - var productionExtraMAT float64 - MAT_deficit := float64(p.Material) - MAT_perShip - // fmt.Println("> MAT:", p.Material) - if MAT_deficit < 0 { - productionExtraMAT = (-MAT_deficit) - // p.Mat(0) - } else { - // p.Mat(float64(p.Material) - MAT_deficit) + pa := productionAvailable + PRODcost := ShipProductionCost(shipMass) + var MATneed, MATfarm, totalCost float64 + for { + MATneed = shipMass - float64(p.Material) + if MATneed < 0 { + MATneed = 0 } - // fmt.Println("< MAT:", p.Material) - productionCost := productionExtraMAT + productionForMass - // fmt.Println("ships:", ships, "cost:", productionCost, "avail:", productionAvailable) - fmt.Println() - fmt.Println("productionExtraMAT", productionExtraMAT) - fmt.Println("productionForMass", productionForMass) - fmt.Println("productionCost", productionCost) - fmt.Println("productionAvailable", productionAvailable) - if productionAvailable >= productionCost { - productionAvailable -= productionCost - // fmt.Println("> MAT:", p.Material) - if MAT_deficit < 0 { - // productionExtraMAT = -MAT_deficit - p.Mat(0) - } else { - p.Mat(float64(p.Material) - MAT_deficit) - } - // fmt.Println("< MAT:", p.Material) - // fmt.Println("> MAT:", p.Material) - // p.Mat(float64(p.Material) - (MAT_perShip - productionExtraMAT)) - // fmt.Println("< MAT:", p.Material) - ships++ - } else { - progress := productionAvailable / productionCost - // productionAvailable -= productionCost * progress + MATfarm = MATneed / float64(p.Resources) + totalCost = PRODcost + MATfarm + // fmt.Printf("PRODcost: %3.03f MATcost: %3.03f MAThave: %3.03f MATneed: %3.03f MATfarm: %3.03f total: %3.03f \n", + // PRODcost, shipMass, float64(p.Material), MATneed, MATfarm, totalCost) + if pa < totalCost { + progress := pa / totalCost pval := game.F(progress) + if p.Production.Progress != nil { + pval += *p.Production.Progress + } p.Production.Progress = &pval - aval := game.F(productionAvailable) - p.Production.FreeProd = &aval - - // fmt.Println("MAT_deficit", MAT_deficit) - - fmt.Println() - fmt.Println("productionExtraMAT", productionExtraMAT) - fmt.Println("productionForMass", productionForMass) - fmt.Println("productionCost", productionCost) - fmt.Println("productionAvailable", productionAvailable) - fmt.Println("progress", progress) - v := ShipProductionCost(MAT_perShip * float64(progress)) - fmt.Println("MAT=", v) - break + fval := game.F(pa) + p.Production.ProdUsed = &fval + // fmt.Println("pa", pa, "progress", progress, "MAT:", progress*shipMass) + return ships + } else { + pa -= totalCost + p.Mat(float64(p.Material) - shipMass + MATneed) + ships += 1 } } - return ships } func ShipProductionCost(shipMass float64) float64 { @@ -408,9 +342,3 @@ func ShipProductionCost(shipMass float64) float64 { func ShipMaterialCost(shipMass, planetResource float64) float64 { return shipMass / planetResource } - -func ShipCapitalCost(shipMass, planetResource float64) float64 { - return shipMass / planetResource -} - -// TODO: при смене производства на планете проверить, сколько материалов высвободится в MAT diff --git a/internal/controller/planet_test.go b/internal/controller/planet_test.go index b760784..b3768bf 100644 --- a/internal/controller/planet_test.go +++ b/internal/controller/planet_test.go @@ -8,6 +8,7 @@ import ( "github.com/iliadenisov/galaxy/internal/controller" e "github.com/iliadenisov/galaxy/internal/error" "github.com/iliadenisov/galaxy/internal/model/game" + "github.com/iliadenisov/galaxy/internal/number" "github.com/stretchr/testify/assert" ) @@ -165,66 +166,22 @@ func TestProduceShips(t *testing.T) { } func TestProduceShip(t *testing.T) { - // Drone := game.ShipType{ - // Name: "Drone", - // Drive: 1, - // Armament: 0, - // Weapons: 0, - // Shields: 0, - // Cargo: 0, - // } - // BattleShip := game.ShipType{ - // Name: "BattleShip", - // Drive: 25, - // Armament: 1, - // Weapons: 30, - // Shields: 35, - // Cargo: 0, - // } - TestDWShip := game.ShipType{ - Name: "DROCOLZ", - Drive: 11.32, + Drone := game.ShipType{ + Name: "Drone", + Drive: 1, Armament: 0, Weapons: 0, Shields: 0, - Cargo: 1, + Cargo: 0, + } + BattleShip := game.ShipType{ + Name: "BattleShip", + Drive: 25, + Armament: 1, + Weapons: 30, + Shields: 35, + Cargo: 0, } - id := uuid.New() - // p := controller.NewPlanet(0, "Planet_0", &id, 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil)) - - // r := controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass()) - // assert.Equal(t, uint(99), r) - // assert.InDelta(t, 0.0099, (*p.Production.Progress).F(), 0.000001) - - // (&p).Production = game.ProductionShip.AsType(uuid.Nil) - // (&p).Capital = 10. - // r = controller.ProduceShip(&p, p.ProductionCapacity(), Drone.EmptyMass()) - // assert.Equal(t, uint(100), r) - // assert.Equal(t, 0., (*p.Production.Progress).F()) - // assert.Equal(t, 0., number.Fixed3(p.Capital.F())) // TODO: zero CAP is not actual '0.0' after series of decrements - - // (&p).Production = game.ProductionShip.AsType(uuid.Nil) - // (&p).Capital = 0. - // r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass()) - // assert.Equal(t, uint(1), r) - // assert.InDelta(t, 0.1, (*p.Production.Progress).F(), 0.001) - - // (&p).Production = game.ProductionShip.AsType(uuid.Nil) - // (&p).Capital = 20. - // r = controller.ProduceShip(&p, p.ProductionCapacity(), BattleShip.EmptyMass()) - // assert.Equal(t, uint(1), r) - // assert.Equal(t, number.Fixed12(1./9.), (*p.Production.Progress).F()) - - // test "P" for report - dw := controller.NewPlanet(0, "DW_Planet", &id, 1, 1, 500, 500, 500, 10, game.ProductionShip.AsType(uuid.Nil)) - (&dw).Material = 0.32 - r := controller.ProduceShip(&dw, dw.ProductionCapacity(), TestDWShip.EmptyMass()) - assert.Equal(t, uint(4), r) - assert.Equal(t, 2.30, (*dw.Production.Progress).F()) - // TODO: test with insufficient production capacity -} - -func TestProduceShip_Research(t *testing.T) { TestShipCargo1 := game.ShipType{ Name: "Cargo1", Drive: 30.18, @@ -241,40 +198,103 @@ func TestProduceShip_Research(t *testing.T) { Shields: 0, Cargo: 1, } - _ = TestShipDROCOLZ - _ = TestShipCargo1 + TestShipElephant := game.ShipType{ + Name: "ElEphant", + Drive: 80, + Armament: 30, + Weapons: 50, + Shields: 100, + Cargo: 0, + } + id := uuid.New() + var r uint + hw := controller.NewPlanet(0, "Planet_0", &id, 1, 1, 1000, 1000, 1000, 10, game.ProductionShip.AsType(uuid.Nil)) + + // + // documented data + // + + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), Drone.EmptyMass()) + assert.Equal(t, uint(99), r) + assert.InDelta(t, 0.0099, (*hw.Production.Progress).F(), 0.000001) + assert.Equal(t, 0.009900990099, (*hw.Production.Progress).F()) // 0.0099 % = 99.0099 mass production per turn + + (&hw).Production = game.ProductionShip.AsType(uuid.Nil) + (&hw).Material = 100. // no material deficit + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), Drone.EmptyMass()) + assert.Equal(t, uint(100), r) + assert.Equal(t, 0., (*hw.Production.Progress).F()) + assert.Equal(t, 0., hw.Material.F()) + + (&hw).Production = game.ProductionShip.AsType(uuid.Nil) + (&hw).Material = 0. + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), BattleShip.EmptyMass()) + assert.Equal(t, uint(1), r) + assert.InDelta(t, 0.1, (*hw.Production.Progress).F(), 0.001) + + (&hw).Production = game.ProductionShip.AsType(uuid.Nil) + (&hw).Material = 900. // no material deficit + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), BattleShip.EmptyMass()) + assert.Equal(t, uint(1), r) + assert.Equal(t, number.Fixed12(1./9.), (*hw.Production.Progress).F()) + + // + // real report data + // + dw1 := controller.NewPlanet(0, "DW2", &id, 1, 1, 500, 500, 500, 10, game.ProductionShip.AsType(uuid.Nil)) dw2 := controller.NewPlanet(0, "DW1", &id, 1, 1, 500, 500, 500, 10, game.ProductionShip.AsType(uuid.Nil)) - _ = dw1 - // (&dw1).Material = 0.0 - // r := controller.ProduceShip(&dw1, dw1.ProductionCapacity(), TestShipDROCOLZ.EmptyMass()) - // assert.Equal(t, uint(4), r) - // assert.Equal(t, 2.272, (*dw1.Production.FreeProd).F()) + (&dw1).Material = 0.0 + r = controller.ProduceShip(&dw1, dw1.ProductionCapacity(), TestShipDROCOLZ.EmptyMass()) + assert.Equal(t, uint(4), r) + assert.Equal(t, 2.272, (*dw1.Production.ProdUsed).F()) - // -------- + (&dw2).Material = 0.0 + r = controller.ProduceShip(&dw2, dw2.ProductionCapacity(), TestShipCargo1.EmptyMass()) + assert.Equal(t, uint(1), r) + assert.Equal(t, 3.282, (*dw2.Production.ProdUsed).F()) - var r uint - - // (&dw2).Material = 0.0 - // r = controller.ProduceShip(&dw2, dw2.ProductionCapacity(), TestShipCargo1.EmptyMass()) - // assert.Equal(t, uint(1), r) - // assert.Equal(t, 3.282, (*dw2.Production.FreeProd).F()) - - // // production stopped and extra MAT released - // matPerShip := controller.ShipMaterialCost(TestShipCargo1.EmptyMass(), float64(dw2.Resources)) - // assert.Equal(t, 4.918, matPerShip) - // (&dw2).Material = game.F(controller.ShipProductionCost(matPerShip * float64(*dw2.Production.Progress))) - // assert.Equal(t, 0.32495049505, (&dw2).Material.F()) // old repoert: 0.32 + // production stopped and extra MAT released + (&dw2).ReleaseMaterial(TestShipCargo1.EmptyMass()) + assert.Equal(t, 0.32495049505, (&dw2).Material.F()) // from report: 0.32 // building new ship with extra MAT - (&dw2).Material = game.F(0.32495049505) r = controller.ProduceShip(&dw2, dw2.ProductionCapacity(), TestShipDROCOLZ.EmptyMass()) assert.Equal(t, uint(4), r) - assert.Equal(t, 2.3, (*dw2.Production.FreeProd).F()) + assert.Equal(t, 2.304495049505, (*dw2.Production.ProdUsed).F()) // from report: 2.3 - // TODO: test with insufficient production capacity + // + // insufficient production capacity to produce single ship at one turn + // + + assert.Greater(t, TestShipElephant.EmptyMass(), 100.) + + // one turn + (&hw).Production = game.ProductionShip.AsType(uuid.Nil) + (&hw).Material = 0. + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), TestShipElephant.EmptyMass()) + assert.Equal(t, uint(0), r) + assert.Equal(t, 0., (&hw).Material.F()) + assert.InDelta(t, 0.1, (*hw.Production.Progress).F(), 0.01) + (&hw).ReleaseMaterial(TestShipElephant.EmptyMass()) + assert.Equal(t, 99.009900990099, (&hw).Material.F()) + + // two turns + (&hw).Production = game.ProductionShip.AsType(uuid.Nil) + (&hw).Material = 0. + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), TestShipElephant.EmptyMass()) + assert.Equal(t, uint(0), r) + assert.Equal(t, 0., (&hw).Material.F()) + assert.InDelta(t, 0.1, (*hw.Production.Progress).F(), 0.01) + r = controller.ProduceShip(&hw, hw.ProductionCapacity(), TestShipElephant.EmptyMass()) + assert.Equal(t, uint(0), r) + assert.Equal(t, 0., (&hw).Material.F()) + assert.InDelta(t, 0.2, (*hw.Production.Progress).F(), 0.01) + + (&hw).ReleaseMaterial(TestShipElephant.EmptyMass()) + assert.Equal(t, 198.019801980198, (&hw).Material.F()) } func TestListProducingPlanets(t *testing.T) { diff --git a/internal/controller/report.go b/internal/controller/report.go index 3a4e4ac..890871f 100644 --- a/internal/controller/report.go +++ b/internal/controller/report.go @@ -536,14 +536,12 @@ func (c *Cache) ReportShipProduction(ri int, rep *mr.Report) { st := c.MustShipType(ri, *p.Production.SubjectID) sliceIndexValidate(&rep.ShipProduction, i) - free := c.PlanetProductionCapacity(p.Number) rep.ShipProduction[pi].Planet = p.Number rep.ShipProduction[pi].Class = st.Name rep.ShipProduction[pi].Cost = mr.F(ShipProductionCost(st.EmptyMass())) - rep.ShipProduction[pi].Free = mr.F(free) - - // FIXME: take logic from [ProduceShip] and test at [controller_test.TestProduceShip] - rep.ShipProduction[pi].Wasted = mr.F(free * (*p.Production.Progress).F()) + rep.ShipProduction[pi].Free = mr.F(c.PlanetProductionCapacity(p.Number)) + rep.ShipProduction[pi].ProdUsed = mr.F((*p.Production.ProdUsed).F()) + rep.ShipProduction[pi].Percent = mr.F((*p.Production.Progress).F()) i++ } } diff --git a/internal/controller/ship_group.go b/internal/controller/ship_group.go index d942369..54f426d 100644 --- a/internal/controller/ship_group.go +++ b/internal/controller/ship_group.go @@ -383,9 +383,9 @@ func (c *Cache) UnloadCargo(ri int, groupIndex uint, ships uint, quantity float6 toBeUnloaded := quantity if quantity == 0 { - toBeUnloaded = c.ShipGroup(sgi).Load.F() + toBeUnloaded = float64(c.ShipGroup(sgi).Load) } - if toBeUnloaded > c.ShipGroup(sgi).Load.F() { + if toBeUnloaded > float64(c.ShipGroup(sgi).Load) { return e.NewCargoUnoadNotEnoughError("load: %.03f", c.ShipGroup(sgi).Load) } @@ -420,7 +420,7 @@ func (c *Cache) unsafeUnloadCargo(sgi int, q float64) { } *availableOnPlanet = (*availableOnPlanet).Add(q) - c.ShipGroup(sgi).Load = c.ShipGroup(sgi).Load.Add(-q) // TODO: apply rounding for Load property? + c.ShipGroup(sgi).Load = c.ShipGroup(sgi).Load.Add(-q) if c.ShipGroup(sgi).Load == 0 { c.ShipGroup(sgi).CargoType = nil } @@ -536,7 +536,7 @@ func (c *Cache) breakGroupSafe(ri int, groupIndex uint, newGroupShips uint) (int func (c *Cache) breakGroupUnsafe(ri, sgi int, newGroupShips uint) int { newGroup := *c.ShipGroup(sgi) if c.ShipGroup(sgi).CargoType != nil { - newGroup.Load = game.F(c.ShipGroup(sgi).Load.F() / float64(c.ShipGroup(sgi).Number) * float64(newGroupShips)) + newGroup.Load = game.F(float64(c.ShipGroup(sgi).Load) / float64(c.ShipGroup(sgi).Number) * float64(newGroupShips)) } newGroup.Number = newGroupShips c.ShipGroupShipsNumber(sgi, c.ShipGroup(sgi).Number-newGroup.Number) diff --git a/internal/controller/ship_group_test.go b/internal/controller/ship_group_test.go index 94cd5da..837a24e 100644 --- a/internal/controller/ship_group_test.go +++ b/internal/controller/ship_group_test.go @@ -416,6 +416,7 @@ func TestUnloadCargo(t *testing.T) { assert.Equal(t, 27.273, number.Fixed3(c.MustPlanet(R1_Planet_1_num).Material.F())) assert.Len(t, slices.Collect(c.RaceShipGroups(Race_0_idx)), 7) assert.Equal(t, uint(3), c.ShipGroup(6).Number) + assert.Equal(t, 0., c.ShipGroup(6).Load.F()) assert.Nil(t, c.ShipGroup(6).CargoType) assert.Equal(t, 0.0, c.ShipGroup(6).Load.F()) assert.Equal(t, game.CargoMaterial.Ref(), c.ShipGroup(5).CargoType) diff --git a/internal/model/game/game.go b/internal/model/game/game.go index 10ae54a..b4af9a1 100644 --- a/internal/model/game/game.go +++ b/internal/model/game/game.go @@ -16,7 +16,7 @@ func F(v float64) Float { } func (f Float) Add(v float64) Float { - return F(f.F() + v) + return f + F(v) } func (f Float) F() float64 { diff --git a/internal/model/game/planet.go b/internal/model/game/planet.go index 5c82581..5e3eca9 100644 --- a/internal/model/game/planet.go +++ b/internal/model/game/planet.go @@ -83,6 +83,19 @@ func PlanetProduction(industry, population float64) float64 { return industry*0.75 + population*0.25 } +func (p *Planet) ReleaseMaterial(shipMass float64) { + if p.Production.Type != ProductionShip || p.Production.Progress == nil { + panic("planet is not producing any ships") + } + p.Material = p.Material.Add(ProducedMaterial(shipMass, float64(*p.Production.Progress))) + p.Production.Progress = new(Float) + p.Production.ProdUsed = new(Float) +} + +func ProducedMaterial(shipMass, progress float64) float64 { + return shipMass * progress +} + // Производство промышленности // TODO: test on real values // [x] ind * 5 + ind / res = prod diff --git a/internal/model/game/production.go b/internal/model/game/production.go index 451807e..4721e63 100644 --- a/internal/model/game/production.go +++ b/internal/model/game/production.go @@ -22,9 +22,9 @@ const ( type Production struct { Type ProductionType `json:"type"` - SubjectID *uuid.UUID `json:"subjectId"` // TODO: get rid of Nils? - Progress *Float `json:"progress"` - FreeProd *Float // TODO: rename, store + SubjectID *uuid.UUID `json:"subjectId,omitempty"` // TODO: get rid of Nils? + Progress *Float `json:"progress,omitempty"` + ProdUsed *Float `json:"prodUsed,omitempty"` } func (p ProductionType) AsType(subject uuid.UUID) Production { diff --git a/internal/model/game/ship.go b/internal/model/game/ship.go index da8390c..9a61f5c 100644 --- a/internal/model/game/ship.go +++ b/internal/model/game/ship.go @@ -60,11 +60,3 @@ func (st ShipType) EmptyMass() float64 { shipMass := st.DriveBlockMass() + st.ShieldsBlockMass() + st.CargoBlockMass() + st.WeaponsBlockMass() return shipMass } - -// ProductionCost returns Material (MAT) and Population (POP) to produce this [ShipType] -// TODO: do we need this? -func (st ShipType) ProductionCost() (mat float64, pop float64) { - mat = st.EmptyMass() - pop = mat * 10 - return -} diff --git a/internal/model/report/ship.go b/internal/model/report/ship.go index 840dee6..a4404c9 100644 --- a/internal/model/report/ship.go +++ b/internal/model/report/ship.go @@ -16,11 +16,12 @@ type OthersShipClass struct { } type ShipProduction struct { - Planet uint `json:"planet"` // Галактический номер планеты - Class string `json:"class"` // Наименование типа строящегося корабля - Cost Float `json:"cost"` // Стоимость постройки одного такого корабля (в производственных ед.) без учета расходов на добычу сырья - Wasted Float `json:"wasted"` // Сколько производственных единиц уже было затрачено на постройку этого корабля (уже учитывая производство сырья) - Free Float `json:"free"` // Свободный производственный потенциал + Planet uint `json:"planet"` // Галактический номер планеты + Class string `json:"class"` // Наименование типа строящегося корабля + Cost Float `json:"cost"` // Стоимость постройки одного такого корабля (в производственных ед.) без учета расходов на добычу сырья + ProdUsed Float `json:"prodUsed"` // Сколько производственных единиц уже было затрачено на постройку этого корабля (уже учитывая производство сырья) + Percent Float `json:"percent"` // Процент завершения активного производства + Free Float `json:"free"` // Свободный производственный потенциал } type IncomingGroup struct {